returnType: string,
language: string,
body: string,
options?: QueryOptions
): Promise<void>;
/**
* Postgres only. Drops a function
*/

keep keep keep replace keep keep keep keep keep

optionsArray?: string[],
options?: QueryOptionsWithForce

'use strict';

const _ = require('lodash');

const Utils = require('./utils');
const DataTypes = require('./data-types');
const SQLiteQueryInterface = require('./dialects/sqlite/query-interface');
const MSSQLQueryInterface = require('./dialects/mssql/query-interface');
const MySQLQueryInterface = require('./dialects/mysql/query-interface');
const PostgresQueryInterface = require('./dialects/postgres/query-interface');
const Transaction = require('./transaction');
const Promise = require('./promise');
const QueryTypes = require('./query-types');
const Op = require('./operators');

/**
 * The interface that Sequelize uses to talk to all databases
 *
 * @class QueryInterface
 */
class QueryInterface {
  constructor(sequelize) {
    this.sequelize = sequelize;
    this.QueryGenerator = this.sequelize.dialect.QueryGenerator;
  }

  /**
   * Create a database
   *
   * @param {string} database  Database name to create
   * @param {Object} [options] Query options
   * @param {string} [options.charset] Database default character set, MYSQL only
   * @param {string} [options.collate] Database default collation
   * @param {string} [options.encoding] Database default character set, PostgreSQL only
   * @param {string} [options.ctype] Database character classification, PostgreSQL only
   * @param {string} [options.template] The name of the template from which to create the new database, PostgreSQL only
   *
   * @returns {Promise}
   */
  createDatabase(database, options) {
    options = options || {};
    const sql = this.QueryGenerator.createDatabaseQuery(database, options);
    return this.sequelize.query(sql, options);
  }

  /**
   * Drop a database
   *
   * @param {string} database  Database name to drop
   * @param {Object} [options] Query options
   *
   * @returns {Promise}
   */
  dropDatabase(database, options) {
    options = options || {};
    const sql = this.QueryGenerator.dropDatabaseQuery(database);
    return this.sequelize.query(sql, options);
  }

  /**
   * Create a schema
   *
   * @param {string} schema    Schema name to create
   * @param {Object} [options] Query options
   *
   * @returns {Promise}
   */
  createSchema(schema, options) {
    options = options || {};
    const sql = this.QueryGenerator.createSchema(schema);
    return this.sequelize.query(sql, options);
  }

  /**
   * Drop a schema
   *
   * @param {string} schema    Schema name to drop
   * @param {Object} [options] Query options
   *
   * @returns {Promise}
   */
  dropSchema(schema, options) {
    options = options || {};
    const sql = this.QueryGenerator.dropSchema(schema);
    return this.sequelize.query(sql, options);
  }

  /**
   * Drop all schemas
   *
   * @param {Object} [options] Query options
   *
   * @returns {Promise}
   */
  dropAllSchemas(options) {
    options = options || {};

    if (!this.QueryGenerator._dialect.supports.schemas) {
      return this.sequelize.drop(options);
    }
    return this.showAllSchemas(options).map(schemaName => this.dropSchema(schemaName, options));
  }

  /**
   * Show all schemas
   *
   * @param {Object} [options] Query options
   *
   * @returns {Promise<Array>}
   */
  showAllSchemas(options) {
    options = Object.assign({}, options, {
      raw: true,
      type: this.sequelize.QueryTypes.SELECT
    });

    const showSchemasSql = this.QueryGenerator.showSchemasQuery(options);

    return this.sequelize.query(showSchemasSql, options).then(schemaNames => _.flatten(
      schemaNames.map(value => value.schema_name ? value.schema_name : value)
    ));
  }

  /**
   * Return database version
   *
   * @param {Object}    [options]      Query options
   * @param {QueryType} [options.type] Query type
   *
   * @returns {Promise}
   * @private
   */
  databaseVersion(options) {
    return this.sequelize.query(
      this.QueryGenerator.versionQuery(),
      Object.assign({}, options, { type: QueryTypes.VERSION })
    );
  }

  /**
   * Create a table with given set of attributes
   *
   * ```js
   * queryInterface.createTable(
   *   'nameOfTheNewTable',
   *   {
   *     id: {
   *       type: Sequelize.INTEGER,
   *       primaryKey: true,
   *       autoIncrement: true
   *     },
   *     createdAt: {
   *       type: Sequelize.DATE
   *     },
   *     updatedAt: {
   *       type: Sequelize.DATE
   *     },
   *     attr1: Sequelize.STRING,
   *     attr2: Sequelize.INTEGER,
   *     attr3: {
   *       type: Sequelize.BOOLEAN,
   *       defaultValue: false,
   *       allowNull: false
   *     },
   *     //foreign key usage
   *     attr4: {
   *       type: Sequelize.INTEGER,
   *       references: {
   *         model: 'another_table_name',
   *         key: 'id'
   *       },
   *       onUpdate: 'cascade',
   *       onDelete: 'cascade'
   *     }
   *   },
   *   {
   *     engine: 'MYISAM',    // default: 'InnoDB'
   *     charset: 'latin1',   // default: null
   *     schema: 'public',    // default: public, PostgreSQL only.
   *     comment: 'my table', // comment for table
   *     collate: 'latin1_danish_ci' // collation, MYSQL only
   *   }
   * )
   * ```
   *
   * @param {string} tableName  Name of table to create
   * @param {Object} attributes Object representing a list of table attributes to create
   * @param {Object} [options] create table and query options
   * @param {Model}  [model] model class
   *
   * @returns {Promise}
   */
  createTable(tableName, attributes, options, model) {
    let sql = '';
    let promise;

    options = _.clone(options) || {};

    if (options && options.uniqueKeys) {
      _.forOwn(options.uniqueKeys, uniqueKey => {
        if (uniqueKey.customIndex === undefined) {
          uniqueKey.customIndex = true;
        }
      });
    }

    if (model) {
      options.uniqueKeys = options.uniqueKeys || model.uniqueKeys;
    }

    attributes = _.mapValues(
      attributes,
      attribute => this.sequelize.normalizeAttribute(attribute)
    );

    // Postgres requires special SQL commands for ENUM/ENUM[]
    if (this.sequelize.options.dialect === 'postgres') {
      promise = PostgresQueryInterface.ensureEnums(this, tableName, attributes, options, model);
    } else {
      promise = Promise.resolve();
    }

    if (
      !tableName.schema &&
      (options.schema || !!model && model._schema)
    ) {
      tableName = this.QueryGenerator.addSchema({
        tableName,
        _schema: !!model && model._schema || options.schema
      });
    }

    attributes = this.QueryGenerator.attributesToSQL(attributes, { table: tableName, context: 'createTable' });
    sql = this.QueryGenerator.createTableQuery(tableName, attributes, options);

    return promise.then(() => this.sequelize.query(sql, options));
  }

  /**
   * Drop a table from database
   *
   * @param {string} tableName Table name to drop
   * @param {Object} options   Query options
   *
   * @returns {Promise}
   */
  dropTable(tableName, options) {
    // if we're forcing we should be cascading unless explicitly stated otherwise
    options = _.clone(options) || {};
    options.cascade = options.cascade || options.force || false;

    let sql = this.QueryGenerator.dropTableQuery(tableName, options);

    return this.sequelize.query(sql, options).then(() => {
      const promises = [];

      // Since postgres has a special case for enums, we should drop the related
      // enum type within the table and attribute
      if (this.sequelize.options.dialect === 'postgres') {
        const instanceTable = this.sequelize.modelManager.getModel(tableName, { attribute: 'tableName' });

        if (instanceTable) {
          const getTableName = (!options || !options.schema || options.schema === 'public' ? '' : `${options.schema}_`) + tableName;

          const keys = Object.keys(instanceTable.rawAttributes);
          const keyLen = keys.length;

          for (let i = 0; i < keyLen; i++) {
            if (instanceTable.rawAttributes[keys[i]].type instanceof DataTypes.ENUM) {
              sql = this.QueryGenerator.pgEnumDrop(getTableName, keys[i]);
              options.supportsSearchPath = false;
              promises.push(this.sequelize.query(sql, Object.assign({}, options, { raw: true })));
            }
          }
        }
      }

      return Promise.all(promises).get(0);
    });
  }

  /**
   * Drop all tables from database
   *
   * @param {Object} [options] query options
   * @param {Array}  [options.skip] List of table to skip
   *
   * @returns {Promise}
   */
  dropAllTables(options) {
    options = options || {};
    const skip = options.skip || [];

    const dropAllTables = tableNames => Promise.each(tableNames, tableName => {
      // if tableName is not in the Array of tables names then don't drop it
      if (!skip.includes(tableName.tableName || tableName)) {
        return this.dropTable(tableName, Object.assign({}, options, { cascade: true }) );
      }
    });

    return this.showAllTables(options).then(tableNames => {
      if (this.sequelize.options.dialect === 'sqlite') {
        return this.sequelize.query('PRAGMA foreign_keys;', options).then(result => {
          const foreignKeysAreEnabled = result.foreign_keys === 1;

          if (foreignKeysAreEnabled) {
            return this.sequelize.query('PRAGMA foreign_keys = OFF', options)
              .then(() => dropAllTables(tableNames))
              .then(() => this.sequelize.query('PRAGMA foreign_keys = ON', options));
          }
          return dropAllTables(tableNames);
        });
      }
      return this.getForeignKeysForTables(tableNames, options).then(foreignKeys => {
        const queries = [];

        tableNames.forEach(tableName => {
          let normalizedTableName = tableName;
          if (_.isObject(tableName)) {
            normalizedTableName = `${tableName.schema}.${tableName.tableName}`;
          }

          foreignKeys[normalizedTableName].forEach(foreignKey => {
            queries.push(this.QueryGenerator.dropForeignKeyQuery(tableName, foreignKey));
          });
        });

        return Promise.each(queries, q => this.sequelize.query(q, options))
          .then(() => dropAllTables(tableNames));
      });
    });
  }

  /**
   * Drop specified enum from database (Postgres only)
   *
   * @param {string} [enumName]  Enum name to drop
   * @param {Object} options Query options
   *
   * @returns {Promise}
   * @private
   */
  dropEnum(enumName, options) {
    if (this.sequelize.getDialect() !== 'postgres') {
      return Promise.resolve();
    }

    options = options || {};

    return this.sequelize.query(
      this.QueryGenerator.pgEnumDrop(null, null, this.QueryGenerator.pgEscapeAndQuote(enumName)),
      Object.assign({}, options, { raw: true })
    );
  }

  /**
   * Drop all enums from database (Postgres only)
   *
   * @param {Object} options Query options
   *
   * @returns {Promise}
   * @private
   */
  dropAllEnums(options) {
    if (this.sequelize.getDialect() !== 'postgres') {
      return Promise.resolve();
    }

    options = options || {};

    return this.pgListEnums(null, options).map(result => this.sequelize.query(
      this.QueryGenerator.pgEnumDrop(null, null, this.QueryGenerator.pgEscapeAndQuote(result.enum_name)),
      Object.assign({}, options, { raw: true })
    ));
  }

  /**
   * List all enums (Postgres only)
   *
   * @param {string} [tableName]  Table whose enum to list
   * @param {Object} [options]    Query options
   *
   * @returns {Promise}
   * @private
   */
  pgListEnums(tableName, options) {
    options = options || {};
    const sql = this.QueryGenerator.pgListEnums(tableName);
    return this.sequelize.query(sql, Object.assign({}, options, { plain: false, raw: true, type: QueryTypes.SELECT }));
  }

  /**
   * Rename a table
   *
   * @param {string} before    Current name of table
   * @param {string} after     New name from table
   * @param {Object} [options] Query options
   *
   * @returns {Promise}
   */
  renameTable(before, after, options) {
    options = options || {};
    const sql = this.QueryGenerator.renameTableQuery(before, after);
    return this.sequelize.query(sql, options);
  }

  /**
   * Get all tables in current database
   *
   * @param {Object}    [options] Query options
   * @param {boolean}   [options.raw=true] Run query in raw mode
   * @param {QueryType} [options.type=QueryType.SHOWTABLE] query type
   *
   * @returns {Promise<Array>}
   * @private
   */
  showAllTables(options) {
    options = Object.assign({}, options, {
      raw: true,
      type: QueryTypes.SHOWTABLES
    });

    const showTablesSql = this.QueryGenerator.showTablesQuery();
    return this.sequelize.query(showTablesSql, options).then(tableNames => _.flatten(tableNames));
  }

  /**
   * Describe a table structure
   *
   * This method returns an array of hashes containing information about all attributes in the table.
   *
   * ```js
   * {
   *    name: {
   *      type:         'VARCHAR(255)', // this will be 'CHARACTER VARYING' for pg!
   *      allowNull:    true,
   *      defaultValue: null
   *    },
   *    isBetaMember: {
   *      type:         'TINYINT(1)', // this will be 'BOOLEAN' for pg!
   *      allowNull:    false,
   *      defaultValue: false
   *    }
   * }
   * ```
   *
   * @param {string} tableName table name
   * @param {Object} [options] Query options
   *
   * @returns {Promise<Object>}
   */
  describeTable(tableName, options) {
    let schema = null;
    let schemaDelimiter = null;

    if (typeof options === 'string') {
      schema = options;
    } else if (typeof options === 'object' && options !== null) {
      schema = options.schema || null;
      schemaDelimiter = options.schemaDelimiter || null;
    }

    if (typeof tableName === 'object' && tableName !== null) {
      schema = tableName.schema;
      tableName = tableName.tableName;
    }

    const sql = this.QueryGenerator.describeTableQuery(tableName, schema, schemaDelimiter);
    options = Object.assign({}, options, { type: QueryTypes.DESCRIBE });

    return this.sequelize.query(sql, options).then(data => {
      /*
       * If no data is returned from the query, then the table name may be wrong.
       * Query generators that use information_schema for retrieving table info will just return an empty result set,
       * it will not throw an error like built-ins do (e.g. DESCRIBE on MySql).
       */
      if (_.isEmpty(data)) {
        throw new Error(`No description found for "${tableName}" table. Check the table name and schema; remember, they _are_ case sensitive.`);
      }

      return data;
    }).catch(e => {
      if (e.original && e.original.code === 'ER_NO_SUCH_TABLE') {
        throw Error(`No description found for "${tableName}" table. Check the table name and schema; remember, they _are_ case sensitive.`);
      }

      throw e;
    });
  }

  /**
   * Add a new column to a table
   *
   * ```js
   * queryInterface.addColumn('tableA', 'columnC', Sequelize.STRING, {
   *    after: 'columnB' // after option is only supported by MySQL
   * });
   * ```
   *
   * @param {string} table     Table to add column to
   * @param {string} key       Column name
   * @param {Object} attribute Attribute definition
   * @param {Object} [options] Query options
   *
   * @returns {Promise}
   */
  addColumn(table, key, attribute, options) {
    if (!table || !key || !attribute) {
      throw new Error('addColumn takes at least 3 arguments (table, attribute name, attribute definition)');
    }

    options = options || {};
    attribute = this.sequelize.normalizeAttribute(attribute);
    return this.sequelize.query(this.QueryGenerator.addColumnQuery(table, key, attribute), options);
  }

  /**
   * Remove a column from a table
   *
   * @param {string} tableName      Table to remove column from
   * @param {string} attributeName  Columns name to remove
   * @param {Object} [options]      Query options
   *
   * @returns {Promise}
   */
  removeColumn(tableName, attributeName, options) {
    options = options || {};
    switch (this.sequelize.options.dialect) {
      case 'sqlite':
        // sqlite needs some special treatment as it cannot drop a column
        return SQLiteQueryInterface.removeColumn(this, tableName, attributeName, options);
      case 'mssql':
        // mssql needs special treatment as it cannot drop a column with a default or foreign key constraint
        return MSSQLQueryInterface.removeColumn(this, tableName, attributeName, options);
      case 'mysql':
      case 'mariadb':
        // mysql/mariadb need special treatment as it cannot drop a column with a foreign key constraint
        return MySQLQueryInterface.removeColumn(this, tableName, attributeName, options);
      default:
        return this.sequelize.query(this.QueryGenerator.removeColumnQuery(tableName, attributeName), options);
    }
  }

  /**
   * Change a column definition
   *
   * @param {string} tableName          Table name to change from
   * @param {string} attributeName      Column name
   * @param {Object} dataTypeOrOptions  Attribute definition for new column
   * @param {Object} [options]          Query options
   *
   * @returns {Promise}
   */
  changeColumn(tableName, attributeName, dataTypeOrOptions, options) {
    const attributes = {};
    options = options || {};

    if (_.values(DataTypes).includes(dataTypeOrOptions)) {
      attributes[attributeName] = { type: dataTypeOrOptions, allowNull: true };
    } else {
      attributes[attributeName] = dataTypeOrOptions;
    }

    attributes[attributeName] = this.sequelize.normalizeAttribute(attributes[attributeName]);

    if (this.sequelize.options.dialect === 'sqlite') {
      // sqlite needs some special treatment as it cannot change a column
      return SQLiteQueryInterface.changeColumn(this, tableName, attributes, options);
    }
    const query = this.QueryGenerator.attributesToSQL(attributes, {
      context: 'changeColumn',
      table: tableName
    });
    const sql = this.QueryGenerator.changeColumnQuery(tableName, query);

    return this.sequelize.query(sql, options);
  }

  /**
   * Rename a column
   *
   * @param {string} tableName        Table name whose column to rename
   * @param {string} attrNameBefore   Current column name
   * @param {string} attrNameAfter    New column name
   * @param {Object} [options]        Query option
   *
   * @returns {Promise}
   */
  renameColumn(tableName, attrNameBefore, attrNameAfter, options) {
    options = options || {};
    return this.describeTable(tableName, options).then(data => {
      if (!data[attrNameBefore]) {
        throw new Error(`Table ${tableName} doesn't have the column ${attrNameBefore}`);
      }

      data = data[attrNameBefore] || {};

      const _options = {};

      _options[attrNameAfter] = {
        attribute: attrNameAfter,
        type: data.type,
        allowNull: data.allowNull,
        defaultValue: data.defaultValue
      };

      // fix: a not-null column cannot have null as default value
      if (data.defaultValue === null && !data.allowNull) {
        delete _options[attrNameAfter].defaultValue;
      }

      if (this.sequelize.options.dialect === 'sqlite') {
        // sqlite needs some special treatment as it cannot rename a column
        return SQLiteQueryInterface.renameColumn(this, tableName, attrNameBefore, attrNameAfter, options);
      }
      const sql = this.QueryGenerator.renameColumnQuery(
        tableName,
        attrNameBefore,
        this.QueryGenerator.attributesToSQL(_options)
      );
      return this.sequelize.query(sql, options);
    });
  }

  /**
   * Add an index to a column
   *
   * @param {string|Object}  tableName Table name to add index on, can be a object with schema
   * @param {Array}   [attributes]     Use options.fields instead, List of attributes to add index on
   * @param {Object}  options          indexes options
   * @param {Array}   options.fields   List of attributes to add index on
   * @param {boolean} [options.concurrently] Pass CONCURRENT so other operations run while the index is created
   * @param {boolean} [options.unique] Create a unique index
   * @param {string}  [options.using]  Useful for GIN indexes
   * @param {string}  [options.operator] Index operator
   * @param {string}  [options.type]   Type of index, available options are UNIQUE|FULLTEXT|SPATIAL
   * @param {string}  [options.name]   Name of the index. Default is <table>_<attr1>_<attr2>
   * @param {Object}  [options.where]  Where condition on index, for partial indexes
   * @param {string}  [rawTablename]   table name, this is just for backward compatibiity
   *
   * @returns {Promise}
   */
  addIndex(tableName, attributes, options, rawTablename) {
    // Support for passing tableName, attributes, options or tableName, options (with a fields param which is the attributes)
    if (!Array.isArray(attributes)) {
      rawTablename = options;
      options = attributes;
      attributes = options.fields;
    }

    if (!rawTablename) {
      // Map for backwards compat
      rawTablename = tableName;
    }

    options = Utils.cloneDeep(options);
    options.fields = attributes;
    const sql = this.QueryGenerator.addIndexQuery(tableName, options, rawTablename);
    return this.sequelize.query(sql, Object.assign({}, options, { supportsSearchPath: false }));
  }

  /**
   * Show indexes on a table
   *
   * @param {string} tableName table name
   * @param {Object} [options]   Query options
   *
   * @returns {Promise<Array>}
   * @private
   */
  showIndex(tableName, options) {
    const sql = this.QueryGenerator.showIndexesQuery(tableName, options);
    return this.sequelize.query(sql, Object.assign({}, options, { type: QueryTypes.SHOWINDEXES }));
  }

  getForeignKeysForTables(tableNames, options) {
    if (tableNames.length === 0) {
      return Promise.resolve({});
    }

    options = Object.assign({}, options || {}, { type: QueryTypes.FOREIGNKEYS });

    return Promise.map(tableNames, tableName =>
      this.sequelize.query(this.QueryGenerator.getForeignKeysQuery(tableName, this.sequelize.config.database), options)
    ).then(results => {
      const result = {};

      tableNames.forEach((tableName, i) => {
        if (_.isObject(tableName)) {
          tableName = `${tableName.schema}.${tableName.tableName}`;
        }

        result[tableName] = Array.isArray(results[i])
          ? results[i].map(r => r.constraint_name)
          : [results[i] && results[i].constraint_name];

        result[tableName] = result[tableName].filter(_.identity);
      });

      return result;
    });
  }

  /**
   * Get foreign key references details for the table
   *
   * Those details contains constraintSchema, constraintName, constraintCatalog
   * tableCatalog, tableSchema, tableName, columnName,
   * referencedTableCatalog, referencedTableCatalog, referencedTableSchema, referencedTableName, referencedColumnName.
   * Remind: constraint informations won't return if it's sqlite.
   *
   * @param {string} tableName table name
   * @param {Object} [options]  Query options
   *
   * @returns {Promise}
   */
  getForeignKeyReferencesForTable(tableName, options) {
    const queryOptions = Object.assign({}, options, {
      type: QueryTypes.FOREIGNKEYS
    });
    const catalogName = this.sequelize.config.database;
    switch (this.sequelize.options.dialect) {
      case 'sqlite':
        // sqlite needs some special treatment.
        return SQLiteQueryInterface.getForeignKeyReferencesForTable(this, tableName, queryOptions);
      case 'postgres':
      {
        // postgres needs some special treatment as those field names returned are all lowercase
        // in order to keep same result with other dialects.
        const query = this.QueryGenerator.getForeignKeyReferencesQuery(tableName, catalogName);
        return this.sequelize.query(query, queryOptions)
          .then(result => result.map(Utils.camelizeObjectKeys));
      }
      case 'mssql':
      case 'mysql':
      case 'mariadb':
      default: {
        const query = this.QueryGenerator.getForeignKeysQuery(tableName, catalogName);
        return this.sequelize.query(query, queryOptions);
      }
    }
  }

  /**
   * Remove an already existing index from a table
   *
   * @param {string} tableName             Table name to drop index from
   * @param {string} indexNameOrAttributes Index name
   * @param {Object} [options]             Query options
   *
   * @returns {Promise}
   */
  removeIndex(tableName, indexNameOrAttributes, options) {
    options = options || {};
    const sql = this.QueryGenerator.removeIndexQuery(tableName, indexNameOrAttributes);
    return this.sequelize.query(sql, options);
  }

  /**
   * Add a constraint to a table
   *
   * Available constraints:
   * - UNIQUE
   * - DEFAULT (MSSQL only)
   * - CHECK (MySQL - Ignored by the database engine )
   * - FOREIGN KEY
   * - PRIMARY KEY
   *
   * @example <caption>UNIQUE</caption>
   * queryInterface.addConstraint('Users', ['email'], {
   *   type: 'unique',
   *   name: 'custom_unique_constraint_name'
   * });
   *
   * @example <caption>CHECK</caption>
   * queryInterface.addConstraint('Users', ['roles'], {
   *   type: 'check',
   *   where: {
   *      roles: ['user', 'admin', 'moderator', 'guest']
   *   }
   * });
   *
   * @example <caption>Default - MSSQL only</caption>
   * queryInterface.addConstraint('Users', ['roles'], {
   *    type: 'default',
   *    defaultValue: 'guest'
   * });
   *
   * @example <caption>Primary Key</caption>
   * queryInterface.addConstraint('Users', ['username'], {
   *    type: 'primary key',
   *    name: 'custom_primary_constraint_name'
   * });
   *
   * @example <caption>Foreign Key</caption>
   * queryInterface.addConstraint('Posts', ['username'], {
   *   type: 'foreign key',
   *   name: 'custom_fkey_constraint_name',
   *   references: { //Required field
   *     table: 'target_table_name',
   *     field: 'target_column_name'
   *   },
   *   onDelete: 'cascade',
   *   onUpdate: 'cascade'
   * });
   *
   * @param {string} tableName                  Table name where you want to add a constraint
   * @param {Array}  attributes                 Array of column names to apply the constraint over
   * @param {Object} options                    An object to define the constraint name, type etc
   * @param {string} options.type               Type of constraint. One of the values in available constraints(case insensitive)
   * @param {string} [options.name]             Name of the constraint. If not specified, sequelize automatically creates a named constraint based on constraint type, table & column names
   * @param {string} [options.defaultValue]     The value for the default constraint
   * @param {Object} [options.where]            Where clause/expression for the CHECK constraint
   * @param {Object} [options.references]       Object specifying target table, column name to create foreign key constraint
   * @param {string} [options.references.table] Target table name
   * @param {string} [options.references.field] Target column name
   * @param {string} [rawTablename]             Table name, for backward compatibility
   *
   * @returns {Promise}
   */
  addConstraint(tableName, attributes, options, rawTablename) {
    if (!Array.isArray(attributes)) {
      rawTablename = options;
      options = attributes;
      attributes = options.fields;
    }

    if (!options.type) {
      throw new Error('Constraint type must be specified through options.type');
    }

    if (!rawTablename) {
      // Map for backwards compat
      rawTablename = tableName;
    }

    options = Utils.cloneDeep(options);
    options.fields = attributes;

    if (this.sequelize.dialect.name === 'sqlite') {
      return SQLiteQueryInterface.addConstraint(this, tableName, options, rawTablename);
    }
    const sql = this.QueryGenerator.addConstraintQuery(tableName, options, rawTablename);
    return this.sequelize.query(sql, options);
  }

  showConstraint(tableName, constraintName, options) {
    const sql = this.QueryGenerator.showConstraintsQuery(tableName, constraintName);
    return this.sequelize.query(sql, Object.assign({}, options, { type: QueryTypes.SHOWCONSTRAINTS }));
  }

  /**
   * Remove a constraint from a table
   *
   * @param {string} tableName       Table name to drop constraint from
   * @param {string} constraintName  Constraint name
   * @param {Object} options         Query options
   *
   * @returns {Promise}
   */
  removeConstraint(tableName, constraintName, options) {
    options = options || {};

    switch (this.sequelize.options.dialect) {
      case 'mysql':
      case 'mariadb':
        //does not support DROP CONSTRAINT. Instead DROP PRIMARY, FOREIGN KEY, INDEX should be used
        return MySQLQueryInterface.removeConstraint(this, tableName, constraintName, options);
      case 'sqlite':
        return SQLiteQueryInterface.removeConstraint(this, tableName, constraintName, options);
      default:
        const sql = this.QueryGenerator.removeConstraintQuery(tableName, constraintName);
        return this.sequelize.query(sql, options);
    }
  }

  insert(instance, tableName, values, options) {
    options = Utils.cloneDeep(options);
    options.hasTrigger = instance && instance.constructor.options.hasTrigger;
    const sql = this.QueryGenerator.insertQuery(tableName, values, instance && instance.constructor.rawAttributes, options);

    options.type = QueryTypes.INSERT;
    options.instance = instance;

    return this.sequelize.query(sql, options).then(results => {
      if (instance) results[0].isNewRecord = false;
      return results;
    });
  }

  /**
   * Upsert
   *
   * @param {string} tableName    table to upsert on
   * @param {Object} insertValues values to be inserted, mapped to field name
   * @param {Object} updateValues values to be updated, mapped to field name
   * @param {Object} where        various conditions
   * @param {Model}  model        Model to upsert on
   * @param {Object} options      query options
   *
   * @returns {Promise<boolean,?number>} Resolves an array with <created, primaryKey>
   */
  upsert(tableName, insertValues, updateValues, where, model, options) {
    const wheres = [];
    const attributes = Object.keys(insertValues);
    let indexes = [];
    let indexFields;

    options = _.clone(options);

    if (!Utils.isWhereEmpty(where)) {
      wheres.push(where);
    }

    // Lets combine unique keys and indexes into one
    indexes = _.map(model.uniqueKeys, value => {
      return value.fields;
    });

    model._indexes.forEach(value => {
      if (value.unique) {
        // fields in the index may both the strings or objects with an attribute property - lets sanitize that
        indexFields = value.fields.map(field => {
          if (_.isPlainObject(field)) {
            return field.attribute;
          }
          return field;
        });
        indexes.push(indexFields);
      }
    });

    for (const index of indexes) {
      if (_.intersection(attributes, index).length === index.length) {
        where = {};
        for (const field of index) {
          where[field] = insertValues[field];
        }
        wheres.push(where);
      }
    }

    where = { [Op.or]: wheres };

    options.type = QueryTypes.UPSERT;
    options.raw = true;

    const sql = this.QueryGenerator.upsertQuery(tableName, insertValues, updateValues, where, model, options);
    return this.sequelize.query(sql, options).then(result => {
      switch (this.sequelize.options.dialect) {
        case 'postgres':
          return [result.created, result.primary_key];

        case 'mssql':
          return [
            result.$action === 'INSERT',
            result[model.primaryKeyField]
          ];

        // MySQL returns 1 for inserted, 2 for updated
        // http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html.
        case 'mysql':
        case 'mariadb':
          return [result === 1, undefined];

        default:
          return [result, undefined];
      }
    });
  }

  /**
   * Insert multiple records into a table
   *
   * @example
   * queryInterface.bulkInsert('roles', [{
   *    label: 'user',
   *    createdAt: new Date(),
   *    updatedAt: new Date()
   *  }, {
   *    label: 'admin',
   *    createdAt: new Date(),
   *    updatedAt: new Date()
   *  }]);
   *
   * @param {string} tableName   Table name to insert record to
   * @param {Array}  records     List of records to insert
   * @param {Object} options     Various options, please see Model.bulkCreate options
   * @param {Object} attributes  Various attributes mapped by field name
   *
   * @returns {Promise}
   */
  bulkInsert(tableName, records, options, attributes) {
    options = _.clone(options) || {};
    options.type = QueryTypes.INSERT;

    return this.sequelize.query(
      this.QueryGenerator.bulkInsertQuery(tableName, records, options, attributes),
      options
    ).then(results => results[0]);
  }

  update(instance, tableName, values, identifier, options) {
    options = _.clone(options || {});
    options.hasTrigger = !!(instance && instance._modelOptions && instance._modelOptions.hasTrigger);

    const sql = this.QueryGenerator.updateQuery(tableName, values, identifier, options, instance.constructor.rawAttributes);

    options.type = QueryTypes.UPDATE;

    options.instance = instance;
    return this.sequelize.query(sql, options);
  }

  /**
   * Update multiple records of a table
   *
   * @example
   * queryInterface.bulkUpdate('roles', {
   *     label: 'admin',
   *   }, {
   *     userType: 3,
   *   },
   * );
   *
   * @param {string} tableName     Table name to update
   * @param {Object} values        Values to be inserted, mapped to field name
   * @param {Object} identifier    A hash with conditions OR an ID as integer OR a string with conditions
   * @param {Object} [options]     Various options, please see Model.bulkCreate options
   * @param {Object} [attributes]  Attributes on return objects if supported by SQL dialect
   *
   * @returns {Promise}
   */
  bulkUpdate(tableName, values, identifier, options, attributes) {
    options = Utils.cloneDeep(options);
    if (typeof identifier === 'object') identifier = Utils.cloneDeep(identifier);

    const sql = this.QueryGenerator.updateQuery(tableName, values, identifier, options, attributes);
    const table = _.isObject(tableName) ? tableName : { tableName };
    const model = _.find(this.sequelize.modelManager.models, { tableName: table.tableName });

    options.model = model;
    return this.sequelize.query(sql, options);
  }

  delete(instance, tableName, identifier, options) {
    const cascades = [];
    const sql = this.QueryGenerator.deleteQuery(tableName, identifier, {}, instance.constructor);

    options = _.clone(options) || {};

    // Check for a restrict field
    if (!!instance.constructor && !!instance.constructor.associations) {
      const keys = Object.keys(instance.constructor.associations);
      const length = keys.length;
      let association;

      for (let i = 0; i < length; i++) {
        association = instance.constructor.associations[keys[i]];
        if (association.options && association.options.onDelete &&
          association.options.onDelete.toLowerCase() === 'cascade' &&
          association.options.useHooks === true) {
          cascades.push(association.accessors.get);
        }
      }
    }

    return Promise.each(cascades, cascade => {
      return instance[cascade](options).then(instances => {
        // Check for hasOne relationship with non-existing associate ("has zero")
        if (!instances) {
          return Promise.resolve();
        }

        if (!Array.isArray(instances)) instances = [instances];

        return Promise.each(instances, instance => instance.destroy(options));
      });
    }).then(() => {
      options.instance = instance;
      return this.sequelize.query(sql, options);
    });
  }

  /**
   * Delete multiple records from a table
   *
   * @param {string}  tableName            table name from where to delete records
   * @param {Object}  where                where conditions to find records to delete
   * @param {Object}  [options]            options
   * @param {boolean} [options.truncate]  Use truncate table command
   * @param {Model}   [model]             Model
   *
   * @returns {Promise}
   */
  bulkDelete(tableName, where, options, model) {
    options = Utils.cloneDeep(options);
    options = _.defaults(options, { limit: null });

    if (options.truncate === true) {
      return this.sequelize.query(
        this.QueryGenerator.truncateTableQuery(tableName, options),
        options
      );
    }

    if (typeof identifier === 'object') where = Utils.cloneDeep(where);

    return this.sequelize.query(
      this.QueryGenerator.deleteQuery(tableName, where, options, model),
      options
    );
  }

  select(model, tableName, options) {
    options = Utils.cloneDeep(options);
    options.type = QueryTypes.SELECT;
    options.model = model;

    return this.sequelize.query(
      this.QueryGenerator.selectQuery(tableName, options, model),
      options
    );
  }

  increment(model, tableName, values, identifier, options) {
    options = Utils.cloneDeep(options);

    const sql = this.QueryGenerator.arithmeticQuery('+', tableName, values, identifier, options, options.attributes);

    options.type = QueryTypes.UPDATE;
    options.model = model;

    return this.sequelize.query(sql, options);
  }

  decrement(model, tableName, values, identifier, options) {
    options = Utils.cloneDeep(options);

    const sql = this.QueryGenerator.arithmeticQuery('-', tableName, values, identifier, options, options.attributes);

    options.type = QueryTypes.UPDATE;
    options.model = model;

    return this.sequelize.query(sql, options);
  }

  rawSelect(tableName, options, attributeSelector, Model) {
    options = Utils.cloneDeep(options);
    options = _.defaults(options, {
      raw: true,
      plain: true,
      type: QueryTypes.SELECT
    });

    const sql = this.QueryGenerator.selectQuery(tableName, options, Model);

    if (attributeSelector === undefined) {
      throw new Error('Please pass an attribute selector!');
    }

    return this.sequelize.query(sql, options).then(data => {
      if (!options.plain) {
        return data;
      }

      const result = data ? data[attributeSelector] : null;

      if (!options || !options.dataType) {
        return result;
      }

      const dataType = options.dataType;

      if (dataType instanceof DataTypes.DECIMAL || dataType instanceof DataTypes.FLOAT) {
        if (result !== null) {
          return parseFloat(result);
        }
      }
      if (dataType instanceof DataTypes.INTEGER || dataType instanceof DataTypes.BIGINT) {
        return parseInt(result, 10);
      }
      if (dataType instanceof DataTypes.DATE) {
        if (result !== null && !(result instanceof Date)) {
          return new Date(result);
        }
      }
      return result;
    });
  }

  createTrigger(tableName, triggerName, timingType, fireOnArray, functionName, functionParams, optionsArray, options) {
    const sql = this.QueryGenerator.createTrigger(tableName, triggerName, timingType, fireOnArray, functionName, functionParams, optionsArray);
    options = options || {};
    if (sql) {
      return this.sequelize.query(sql, options);
    }
    return Promise.resolve();
  }

  dropTrigger(tableName, triggerName, options) {
    const sql = this.QueryGenerator.dropTrigger(tableName, triggerName);
    options = options || {};

    if (sql) {
      return this.sequelize.query(sql, options);
    }
    return Promise.resolve();
  }

  renameTrigger(tableName, oldTriggerName, newTriggerName, options) {
    const sql = this.QueryGenerator.renameTrigger(tableName, oldTriggerName, newTriggerName);
    options = options || {};

    if (sql) {
      return this.sequelize.query(sql, options);
    }
    return Promise.resolve();
  }

  /**
   * Create an SQL function
   *
   * @example
   * queryInterface.createFunction(
   *   'someFunction',
   *   [
   *     {type: 'integer', name: 'param', direction: 'IN'}
   *   ],
   *   'integer',
   *   'plpgsql',
   *   'RETURN param + 1;',
   *   [
   *     'IMMUTABLE',
   *     'LEAKPROOF'
   *   ]
   * );
   *
   * @param {string} functionName Name of SQL function to create
   * @param {Array}  params       List of parameters declared for SQL function
   * @param {string} returnType   SQL type of function returned value
   * @param {string} language     The name of the language that the function is implemented in
   * @param {string} body         Source code of function
   * @param {Array}  optionsArray Extra-options for creation
   * @param {Object} [options]    query options
   *
   * @returns {Promise}
   */
  createFunction(functionName, params, returnType, language, body, optionsArray, options) {
    const sql = this.QueryGenerator.createFunction(functionName, params, returnType, language, body, optionsArray);
    options = options || {};

    if (sql) {
      return this.sequelize.query(sql, options);
    }
    return Promise.resolve();
  }

  /**
   * Drop an SQL function
   *
   * @example
   * queryInterface.dropFunction(
   *   'someFunction',
   *   [
   *     {type: 'varchar', name: 'param1', direction: 'IN'},
   *     {type: 'integer', name: 'param2', direction: 'INOUT'}
   *   ]
   * );
   *
   * @param {string} functionName Name of SQL function to drop
   * @param {Array}  params       List of parameters declared for SQL function
   * @param {Object} [options]    query options
   *
   * @returns {Promise}
   */
  dropFunction(functionName, params, options) {
    const sql = this.QueryGenerator.dropFunction(functionName, params);
    options = options || {};

    if (sql) {
      return this.sequelize.query(sql, options);
    }
    return Promise.resolve();
  }

  /**
   * Rename an SQL function
   *
   * @example
   * queryInterface.renameFunction(
   *   'fooFunction',
   *   [
   *     {type: 'varchar', name: 'param1', direction: 'IN'},
   *     {type: 'integer', name: 'param2', direction: 'INOUT'}
   *   ],
   *   'barFunction'
   * );
   *
   * @param {string} oldFunctionName  Current name of function
   * @param {Array}  params           List of parameters declared for SQL function
   * @param {string} newFunctionName  New name of function
   * @param {Object} [options]        query options
   *
   * @returns {Promise}
   */
  renameFunction(oldFunctionName, params, newFunctionName, options) {
    const sql = this.QueryGenerator.renameFunction(oldFunctionName, params, newFunctionName);
    options = options || {};

    if (sql) {
      return this.sequelize.query(sql, options);
    }
    return Promise.resolve();
  }

  // Helper methods useful for querying

  /**
   * Escape an identifier (e.g. a table or attribute name)
   *
   * @param {string} identifier identifier to quote
   * @param {boolean} [force]   If force is true,the identifier will be quoted even if the `quoteIdentifiers` option is false.
   *
   * @private
   */
  quoteIdentifier(identifier, force) {
    return this.QueryGenerator.quoteIdentifier(identifier, force);
  }

  quoteTable(identifier) {
    return this.QueryGenerator.quoteTable(identifier);
  }

  /**
   * Quote array of identifiers at once
   *
   * @param {string[]} identifiers array of identifiers to quote
   * @param {boolean} [force]   If force is true,the identifier will be quoted even if the `quoteIdentifiers` option is false.
   *
   * @private
   */
  quoteIdentifiers(identifiers, force) {
    return this.QueryGenerator.quoteIdentifiers(identifiers, force);
  }

  /**
   * Escape a value (e.g. a string, number or date)
   *
   * @param {string} value string to escape
   *
   * @private
   */
  escape(value) {
    return this.QueryGenerator.escape(value);
  }

  setIsolationLevel(transaction, value, options) {
    if (!transaction || !(transaction instanceof Transaction)) {
      throw new Error('Unable to set isolation level for a transaction without transaction object!');
    }

    if (transaction.parent || !value) {
      // Not possible to set a separate isolation level for savepoints
      return Promise.resolve();
    }

    options = Object.assign({}, options, {
      transaction: transaction.parent || transaction
    });

    const sql = this.QueryGenerator.setIsolationLevelQuery(value, {
      parent: transaction.parent
    });

    if (!sql) return Promise.resolve();

    return this.sequelize.query(sql, options);
  }

  startTransaction(transaction, options) {
    if (!transaction || !(transaction instanceof Transaction)) {
      throw new Error('Unable to start a transaction without transaction object!');
    }

    options = Object.assign({}, options, {
      transaction: transaction.parent || transaction
    });
    options.transaction.name = transaction.parent ? transaction.name : undefined;
    const sql = this.QueryGenerator.startTransactionQuery(transaction);

    return this.sequelize.query(sql, options);
  }

  deferConstraints(transaction, options) {
    options = Object.assign({}, options, {
      transaction: transaction.parent || transaction
    });

    const sql = this.QueryGenerator.deferConstraintsQuery(options);

    if (sql) {
      return this.sequelize.query(sql, options);
    }

    return Promise.resolve();
  }

  commitTransaction(transaction, options) {
    if (!transaction || !(transaction instanceof Transaction)) {
      throw new Error('Unable to commit a transaction without transaction object!');
    }
    if (transaction.parent) {
      // Savepoints cannot be committed
      return Promise.resolve();
    }

    options = Object.assign({}, options, {
      transaction: transaction.parent || transaction,
      supportsSearchPath: false,
      completesTransaction: true
    });

    const sql = this.QueryGenerator.commitTransactionQuery(transaction);
    const promise = this.sequelize.query(sql, options);

    transaction.finished = 'commit';

    return promise;
  }

  rollbackTransaction(transaction, options) {
    if (!transaction || !(transaction instanceof Transaction)) {
      throw new Error('Unable to rollback a transaction without transaction object!');
    }

    options = Object.assign({}, options, {
      transaction: transaction.parent || transaction,
      supportsSearchPath: false,
      completesTransaction: true
    });
    options.transaction.name = transaction.parent ? transaction.name : undefined;
    const sql = this.QueryGenerator.rollbackTransactionQuery(transaction);
    const promise = this.sequelize.query(sql, options);

    transaction.finished = 'rollback';

    return promise;
  }
}

module.exports = QueryInterface;
module.exports.QueryInterface = QueryInterface;
module.exports.default = QueryInterface;


'use strict';

const chai = require('chai'),
  sinon = require('sinon'),
  expect = chai.expect,
  Support = require('../support'),
  DataTypes = require('../../../lib/data-types'),
  Sequelize = require('../../../index'),
  Promise = Sequelize.Promise,
  current = Support.sequelize,
  dialect = Support.getTestDialect();

describe(Support.getTestDialectTeaser('BelongsTo'), () => {
  describe('Model.associations', () => {
    it('should store all associations when associating to the same table multiple times', function() {
      const User = this.sequelize.define('User', {}),
        Group = this.sequelize.define('Group', {});

      Group.belongsTo(User);
      Group.belongsTo(User, { foreignKey: 'primaryGroupId', as: 'primaryUsers' });
      Group.belongsTo(User, { foreignKey: 'secondaryGroupId', as: 'secondaryUsers' });

      expect(
        Object.keys(Group.associations)
      ).to.deep.equal(['User', 'primaryUsers', 'secondaryUsers']);
    });
  });

  describe('get', () => {
    describe('multiple', () => {
      it('should fetch associations for multiple instances', function() {
        const User = this.sequelize.define('User', {}),
          Task = this.sequelize.define('Task', {});

        Task.User = Task.belongsTo(User, { as: 'user' });

        return this.sequelize.sync({ force: true }).then(() => {
          return Promise.join(
            Task.create({
              id: 1,
              user: { id: 1 }
            }, {
              include: [Task.User]
            }),
            Task.create({
              id: 2,
              user: { id: 2 }
            }, {
              include: [Task.User]
            }),
            Task.create({
              id: 3
            })
          );
        }).then(tasks => {
          return Task.User.get(tasks).then(result => {
            expect(result[tasks[0].id].id).to.equal(tasks[0].user.id);
            expect(result[tasks[1].id].id).to.equal(tasks[1].user.id);
            expect(result[tasks[2].id]).to.be.undefined;
          });
        });
      });
    });
  });

  describe('getAssociation', () => {
    if (current.dialect.supports.transactions) {
      it('supports transactions', function() {
        return Support.prepareTransactionTest(this.sequelize).then(sequelize => {
          const User = sequelize.define('User', { username: Support.Sequelize.STRING }),
            Group = sequelize.define('Group', { name: Support.Sequelize.STRING });

          Group.belongsTo(User);

          return sequelize.sync({ force: true }).then(() => {
            return User.create({ username: 'foo' }).then(user => {
              return Group.create({ name: 'bar' }).then(group => {
                return sequelize.transaction().then(t => {
                  return group.setUser(user, { transaction: t }).then(() => {
                    return Group.findAll().then(groups => {
                      return groups[0].getUser().then(associatedUser => {
                        expect(associatedUser).to.be.null;
                        return Group.findAll({ transaction: t }).then(groups => {
                          return groups[0].getUser({ transaction: t }).then(associatedUser => {
                            expect(associatedUser).to.be.not.null;
                            return t.rollback();
                          });
                        });
                      });
                    });
                  });
                });
              });
            });
          });
        });
      });
    }

    it('should be able to handle a where object that\'s a first class citizen.', function() {
      const User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING, gender: Sequelize.STRING }),
        Task = this.sequelize.define('TaskXYZ', { title: Sequelize.STRING, status: Sequelize.STRING });

      Task.belongsTo(User);

      return User.sync({ force: true }).then(() => {
        // Can't use Promise.all cause of foreign key references
        return Task.sync({ force: true });
      }).then(() => {
        return Promise.all([
          User.create({ username: 'foo', gender: 'male' }),
          User.create({ username: 'bar', gender: 'female' }),
          Task.create({ title: 'task', status: 'inactive' })
        ]);
      }).then(([userA, , task]) => {
        return task.setUserXYZ(userA).then(() => {
          return task.getUserXYZ({ where: { gender: 'female' } });
        });
      }).then(user => {
        expect(user).to.be.null;
      });
    });

    it('supports schemas', function() {
      const User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING, gender: Sequelize.STRING }).schema('archive'),
        Task = this.sequelize.define('TaskXYZ', { title: Sequelize.STRING, status: Sequelize.STRING }).schema('archive');

      Task.belongsTo(User);

      return Support.dropTestSchemas(this.sequelize).then(() => {
        return this.sequelize.createSchema('archive');
      }).then(() => {
        return User.sync({ force: true });
      }).then(() => {
        return Task.sync({ force: true });
      }).then(() => {
        return Promise.all([
          User.create({ username: 'foo', gender: 'male' }),
          Task.create({ title: 'task', status: 'inactive' })
        ]);
      }).then(([user, task]) => {
        return task.setUserXYZ(user).then(() => {
          return task.getUserXYZ();
        });
      }).then(user => {
        expect(user).to.be.ok;
        return this.sequelize.dropSchema('archive').then(() => {
          return this.sequelize.showAllSchemas().then(schemas => {
            if (dialect === 'postgres' || dialect === 'mssql' || dialect === 'mariadb') {
              expect(schemas).to.not.have.property('archive');
            }
          });
        });
      });
    });

    it('supports schemas when defining custom foreign key attribute #9029', function() {
      const User = this.sequelize.define('UserXYZ', {
          uid: {
            type: Sequelize.INTEGER,
            primaryKey: true,
            autoIncrement: true,
            allowNull: false
          }
        }).schema('archive'),
        Task = this.sequelize.define('TaskXYZ', {
          user_id: {
            type: Sequelize.INTEGER,
            references: { model: User, key: 'uid' }
          }
        }).schema('archive');

      Task.belongsTo(User, { foreignKey: 'user_id' });

      return Support.dropTestSchemas(this.sequelize).then(() => {
        return this.sequelize.createSchema('archive');
      }).then(() => {
        return User.sync({ force: true });
      }).then(() => {
        return Task.sync({ force: true });
      }).then(() => {
        return User.create({});
      }).then(user => {
        return Task.create({}).then(task => {
          return task.setUserXYZ(user).then(() => {
            return task.getUserXYZ();
          });
        });
      }).then(user => {
        expect(user).to.be.ok;
        return this.sequelize.dropSchema('archive');
      });
    });
  });

  describe('setAssociation', () => {

    if (current.dialect.supports.transactions) {
      it('supports transactions', function() {
        return Support.prepareTransactionTest(this.sequelize).then(sequelize => {
          const User = sequelize.define('User', { username: Support.Sequelize.STRING }),
            Group = sequelize.define('Group', { name: Support.Sequelize.STRING });

          Group.belongsTo(User);

          return sequelize.sync({ force: true }).then(() => {
            return User.create({ username: 'foo' }).then(user => {
              return Group.create({ name: 'bar' }).then(group => {
                return sequelize.transaction().then(t => {
                  return group.setUser(user, { transaction: t }).then(() => {
                    return Group.findAll().then(groups => {
                      return groups[0].getUser().then(associatedUser => {
                        expect(associatedUser).to.be.null;
                        return t.rollback();
                      });
                    });
                  });
                });
              });
            });
          });
        });
      });
    }

    it('can set the association with declared primary keys...', function() {
      const User = this.sequelize.define('UserXYZ', { user_id: { type: DataTypes.INTEGER, primaryKey: true }, username: DataTypes.STRING }),
        Task = this.sequelize.define('TaskXYZ', { task_id: { type: DataTypes.INTEGER, primaryKey: true }, title: DataTypes.STRING });

      Task.belongsTo(User, { foreignKey: 'user_id' });

      return this.sequelize.sync({ force: true }).then(() => {
        return User.create({ user_id: 1, username: 'foo' }).then(user => {
          return Task.create({ task_id: 1, title: 'task' }).then(task => {
            return task.setUserXYZ(user).then(() => {
              return task.getUserXYZ().then(user => {
                expect(user).not.to.be.null;

                return task.setUserXYZ(null).then(() => {
                  return task.getUserXYZ().then(user => {
                    expect(user).to.be.null;
                  });
                });
              });
            });
          });
        });
      });
    });

    it('clears the association if null is passed', function() {
      const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING }),
        Task = this.sequelize.define('TaskXYZ', { title: DataTypes.STRING });

      Task.belongsTo(User);

      return this.sequelize.sync({ force: true }).then(() => {
        return User.create({ username: 'foo' }).then(user => {
          return Task.create({ title: 'task' }).then(task => {
            return task.setUserXYZ(user).then(() => {
              return task.getUserXYZ().then(user => {
                expect(user).not.to.be.null;

                return task.setUserXYZ(null).then(() => {
                  return task.getUserXYZ().then(user => {
                    expect(user).to.be.null;
                  });
                });
              });
            });
          });
        });
      });
    });

    it('should throw a ForeignKeyConstraintError if the associated record does not exist', function() {
      const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING }),
        Task = this.sequelize.define('TaskXYZ', { title: DataTypes.STRING });

      Task.belongsTo(User);

      return this.sequelize.sync({ force: true }).then(() => {
        return expect(Task.create({ title: 'task', UserXYZId: 5 })).to.be.rejectedWith(Sequelize.ForeignKeyConstraintError).then(() => {
          return Task.create({ title: 'task' }).then(task => {
            return expect(Task.update({ title: 'taskUpdate', UserXYZId: 5 }, { where: { id: task.id } })).to.be.rejectedWith(Sequelize.ForeignKeyConstraintError);
          });
        });
      });
    });

    it('supports passing the primary key instead of an object', function() {
      const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING }),
        Task = this.sequelize.define('TaskXYZ', { title: DataTypes.STRING });

      Task.belongsTo(User);

      return this.sequelize.sync({ force: true }).then(() => {
        return User.create({ id: 15, username: 'jansemand' }).then(user => {
          return Task.create({}).then(task => {
            return task.setUserXYZ(user.id).then(() => {
              return task.getUserXYZ().then(user => {
                expect(user.username).to.equal('jansemand');
              });
            });
          });
        });
      });
    });

    it('should support logging', function() {
      const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING }),
        Task = this.sequelize.define('TaskXYZ', { title: DataTypes.STRING }),
        spy = sinon.spy();

      Task.belongsTo(User);

      return this.sequelize.sync({ force: true }).then(() => {
        return User.create().then(user => {
          return Task.create({}).then(task => {
            return task.setUserXYZ(user, { logging: spy }).then(() => {
              expect(spy.called).to.be.ok;
            });
          });
        });
      });
    });

    it('should not clobber atributes', function() {
      const Comment = this.sequelize.define('comment', {
        text: DataTypes.STRING
      });

      const Post = this.sequelize.define('post', {
        title: DataTypes.STRING
      });

      Post.hasOne(Comment);
      Comment.belongsTo(Post);

      return this.sequelize.sync().then(() => {
        return Post.create({
          title: 'Post title'
        }).then(post => {
          return Comment.create({
            text: 'OLD VALUE'
          }).then(comment => {
            comment.text = 'UPDATED VALUE';
            return comment.setPost(post).then(() => {
              expect(comment.text).to.equal('UPDATED VALUE');
            });
          });
        });
      });
    });

    it('should set the foreign key value without saving when using save: false', function() {
      const Comment = this.sequelize.define('comment', {
        text: DataTypes.STRING
      });

      const Post = this.sequelize.define('post', {
        title: DataTypes.STRING
      });

      Post.hasMany(Comment, { foreignKey: 'post_id' });
      Comment.belongsTo(Post, { foreignKey: 'post_id' });

      return this.sequelize.sync({ force: true }).then(() => {
        return Promise.join(
          Post.create(),
          Comment.create()
        ).then(([post, comment]) => {
          expect(comment.get('post_id')).not.to.be.ok;

          const setter = comment.setPost(post, { save: false });

          expect(setter).to.be.undefined;
          expect(comment.get('post_id')).to.equal(post.get('id'));
          expect(comment.changed('post_id')).to.be.true;
        });
      });
    });

    it('supports setting same association twice', function() {
      const Home = this.sequelize.define('home', {}),
        User = this.sequelize.define('user');

      Home.belongsTo(User);

      const ctx = {};
      return this.sequelize.sync({ force: true }).then(() => {
        return Promise.all([
          Home.create(),
          User.create()
        ]);
      }).then(([home, user]) => {
        ctx.home = home;
        ctx.user = user;
        return home.setUser(user);
      }).then(() => {
        return ctx.home.setUser(ctx.user);
      }).then(() => {
        return expect(ctx.home.getUser()).to.eventually.have.property('id', ctx.user.get('id'));
      });
    });
  });

  describe('createAssociation', () => {
    it('creates an associated model instance', function() {
      const User = this.sequelize.define('User', { username: DataTypes.STRING }),
        Task = this.sequelize.define('Task', { title: DataTypes.STRING });

      Task.belongsTo(User);

      return this.sequelize.sync({ force: true }).then(() => {
        return Task.create({ title: 'task' }).then(task => {
          return task.createUser({ username: 'bob' }).then(user => {
            expect(user).not.to.be.null;
            expect(user.username).to.equal('bob');
          });
        });
      });
    });

    if (current.dialect.supports.transactions) {
      it('supports transactions', function() {
        return Support.prepareTransactionTest(this.sequelize).then(sequelize => {
          const User = sequelize.define('User', { username: Support.Sequelize.STRING }),
            Group = sequelize.define('Group', { name: Support.Sequelize.STRING });

          Group.belongsTo(User);

          return sequelize.sync({ force: true }).then(() => {
            return Group.create({ name: 'bar' }).then(group => {
              return sequelize.transaction().then(t => {
                return group.createUser({ username: 'foo' }, { transaction: t }).then(() => {
                  return group.getUser().then(user => {
                    expect(user).to.be.null;

                    return group.getUser({ transaction: t }).then(user => {
                      expect(user).not.to.be.null;

                      return t.rollback();
                    });
                  });
                });
              });
            });
          });
        });
      });
    }
  });

  describe('foreign key', () => {
    it('should setup underscored field with foreign keys when using underscored', function() {
      const User = this.sequelize.define('User', { username: Sequelize.STRING }, { underscored: true }),
        Account = this.sequelize.define('Account', { name: Sequelize.STRING }, { underscored: true });

      User.belongsTo(Account);

      expect(User.rawAttributes.AccountId).to.exist;
      expect(User.rawAttributes.AccountId.field).to.equal('account_id');
    });

    it('should use model name when using camelcase', function() {
      const User = this.sequelize.define('User', { username: Sequelize.STRING }, { underscored: false }),
        Account = this.sequelize.define('Account', { name: Sequelize.STRING }, { underscored: false });

      User.belongsTo(Account);

      expect(User.rawAttributes.AccountId).to.exist;
      expect(User.rawAttributes.AccountId.field).to.equal('AccountId');
    });

    it('should support specifying the field of a foreign key', function() {
      const User = this.sequelize.define('User', { username: Sequelize.STRING }, { underscored: false }),
        Account = this.sequelize.define('Account', { title: Sequelize.STRING }, { underscored: false });

      User.belongsTo(Account, {
        foreignKey: {
          name: 'AccountId',
          field: 'account_id'
        }
      });

      expect(User.rawAttributes.AccountId).to.exist;
      expect(User.rawAttributes.AccountId.field).to.equal('account_id');

      return Account.sync({ force: true }).then(() => {
        // Can't use Promise.all cause of foreign key references
        return User.sync({ force: true });
      }).then(() => {
        return Promise.all([
          User.create({ username: 'foo' }),
          Account.create({ title: 'pepsico' })
        ]);
      }).then(([user, account]) => {
        return user.setAccount(account).then(() => {
          return user.getAccount();
        });
      }).then(user => {
        expect(user).to.not.be.null;
        return User.findOne({
          where: { username: 'foo' },
          include: [Account]
        });
      }).then(user => {
        // the sql query should correctly look at account_id instead of AccountId
        expect(user.Account).to.exist;
      });
    });

    it('should set foreignKey on foreign table', function() {
      const Mail = this.sequelize.define('mail', {}, { timestamps: false });
      const Entry = this.sequelize.define('entry', {}, { timestamps: false });
      const User = this.sequelize.define('user', {}, { timestamps: false });

      Entry.belongsTo(User, {
        as: 'owner',
        foreignKey: {
          name: 'ownerId',
          allowNull: false
        }
      });
      Entry.belongsTo(Mail, {
        as: 'mail',
        foreignKey: {
          name: 'mailId',
          allowNull: false
        }
      });
      Mail.belongsToMany(User, {
        as: 'recipients',
        through: 'MailRecipients',
        otherKey: {
          name: 'recipientId',
          allowNull: false
        },
        foreignKey: {
          name: 'mailId',
          allowNull: false
        },
        timestamps: false
      });
      Mail.hasMany(Entry, {
        as: 'entries',
        foreignKey: {
          name: 'mailId',
          allowNull: false
        }
      });
      User.hasMany(Entry, {
        as: 'entries',
        foreignKey: {
          name: 'ownerId',
          allowNull: false
        }
      });

      return this.sequelize.sync({ force: true })
        .then(() => User.create({}))
        .then(() => Mail.create({}))
        .then(mail =>
          Entry.create({ mailId: mail.id, ownerId: 1 })
            .then(() => Entry.create({ mailId: mail.id, ownerId: 1 }))
            // set recipients
            .then(() => mail.setRecipients([1]))
        )
        .then(() => Entry.findAndCountAll({
          offset: 0,
          limit: 10,
          order: [['id', 'DESC']],
          include: [
            {
              association: Entry.associations.mail,
              include: [
                {
                  association: Mail.associations.recipients,
                  through: {
                    where: {
                      recipientId: 1
                    }
                  },
                  required: true
                }
              ],
              required: true
            }
          ]
        })).then(result => {
          expect(result.count).to.equal(2);
          expect(result.rows[0].get({ plain: true })).to.deep.equal(
            {
              id: 2,
              ownerId: 1,
              mailId: 1,
              mail: {
                id: 1,
                recipients: [{
                  id: 1,
                  MailRecipients: {
                    mailId: 1,
                    recipientId: 1
                  }
                }]
              }
            }
          );
        });
    });
  });

  describe('foreign key constraints', () => {
    it('are enabled by default', function() {
      const Task = this.sequelize.define('Task', { title: DataTypes.STRING }),
        User = this.sequelize.define('User', { username: DataTypes.STRING });

      Task.belongsTo(User); // defaults to SET NULL

      return this.sequelize.sync({ force: true }).then(() => {
        return User.create({ username: 'foo' }).then(user => {
          return Task.create({ title: 'task' }).then(task => {
            return task.setUser(user).then(() => {
              return user.destroy().then(() => {
                return task.reload().then(() => {
                  expect(task.UserId).to.equal(null);
                });
              });
            });
          });
        });
      });
    });

    it('sets to NO ACTION if allowNull: false', function() {
      const Task = this.sequelize.define('Task', { title: DataTypes.STRING }),
        User = this.sequelize.define('User', { username: DataTypes.STRING });

      Task.belongsTo(User, { foreignKey: { allowNull: false } }); // defaults to NO ACTION

      return this.sequelize.sync({ force: true }).then(() => {
        return User.create({ username: 'foo' }).then(user => {
          return Task.create({ title: 'task', UserId: user.id }).then(() => {
            return expect(user.destroy()).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError).then(() => {
              return Task.findAll().then(tasks => {
                expect(tasks).to.have.length(1);
              });
            });
          });
        });
      });
    });

    it('should be possible to disable them', function() {
      const Task = this.sequelize.define('Task', { title: Sequelize.STRING }),
        User = this.sequelize.define('User', { username: Sequelize.STRING });

      Task.belongsTo(User, { constraints: false });

      return this.sequelize.sync({ force: true }).then(() => {
        return User.create({ username: 'foo' }).then(user => {
          return Task.create({ title: 'task' }).then(task => {
            return task.setUser(user).then(() => {
              return user.destroy().then(() => {
                return task.reload().then(() => {
                  expect(task.UserId).to.equal(user.id);
                });
              });
            });
          });
        });
      });
    });

    it('can cascade deletes', function() {
      const Task = this.sequelize.define('Task', { title: DataTypes.STRING }),
        User = this.sequelize.define('User', { username: DataTypes.STRING });

      Task.belongsTo(User, { onDelete: 'cascade' });

      return this.sequelize.sync({ force: true }).then(() => {
        return User.create({ username: 'foo' }).then(user => {
          return Task.create({ title: 'task' }).then(task => {
            return task.setUser(user).then(() => {
              return user.destroy().then(() => {
                return Task.findAll().then(tasks => {
                  expect(tasks).to.have.length(0);
                });
              });
            });
          });
        });
      });
    });

    if (current.dialect.supports.constraints.restrict) {
      it('can restrict deletes', function() {
        const Task = this.sequelize.define('Task', { title: DataTypes.STRING }),
          User = this.sequelize.define('User', { username: DataTypes.STRING });

        Task.belongsTo(User, { onDelete: 'restrict' });

        return this.sequelize.sync({ force: true }).then(() => {
          return User.create({ username: 'foo' }).then(user => {
            return Task.create({ title: 'task' }).then(task => {
              return task.setUser(user).then(() => {
                return expect(user.destroy()).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError).then(() => {
                  return Task.findAll().then(tasks => {
                    expect(tasks).to.have.length(1);
                  });
                });
              });
            });
          });
        });
      });

      it('can restrict updates', function() {
        const Task = this.sequelize.define('Task', { title: DataTypes.STRING }),
          User = this.sequelize.define('User', { username: DataTypes.STRING });

        Task.belongsTo(User, { onUpdate: 'restrict' });

        return this.sequelize.sync({ force: true }).then(() => {
          return User.create({ username: 'foo' }).then(user => {
            return Task.create({ title: 'task' }).then(task => {
              return task.setUser(user).then(() => {

                // Changing the id of a DAO requires a little dance since
                // the `UPDATE` query generated by `save()` uses `id` in the
                // `WHERE` clause

                const tableName = user.sequelize.getQueryInterface().QueryGenerator.addSchema(user.constructor);
                return expect(
                  user.sequelize.getQueryInterface().update(user, tableName, { id: 999 }, { id: user.id })
                ).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError).then(() => {
                  // Should fail due to FK restriction
                  return Task.findAll().then(tasks => {
                    expect(tasks).to.have.length(1);
                  });
                });
              });
            });
          });
        });
      });
    }

    // NOTE: mssql does not support changing an autoincrement primary key
    if (Support.getTestDialect() !== 'mssql') {
      it('can cascade updates', function() {
        const Task = this.sequelize.define('Task', { title: DataTypes.STRING }),
          User = this.sequelize.define('User', { username: DataTypes.STRING });

        Task.belongsTo(User, { onUpdate: 'cascade' });

        return this.sequelize.sync({ force: true }).then(() => {
          return User.create({ username: 'foo' }).then(user => {
            return Task.create({ title: 'task' }).then(task => {
              return task.setUser(user).then(() => {

                // Changing the id of a DAO requires a little dance since
                // the `UPDATE` query generated by `save()` uses `id` in the
                // `WHERE` clause

                const tableName = user.sequelize.getQueryInterface().QueryGenerator.addSchema(user.constructor);
                return user.sequelize.getQueryInterface().update(user, tableName, { id: 999 }, { id: user.id })
                  .then(() => {
                    return Task.findAll().then(tasks => {
                      expect(tasks).to.have.length(1);
                      expect(tasks[0].UserId).to.equal(999);
                    });
                  });
              });
            });
          });
        });
      });
    }
  });

  describe('association column', () => {
    it('has correct type and name for non-id primary keys with non-integer type', function() {
      const User = this.sequelize.define('UserPKBT', {
        username: {
          type: DataTypes.STRING
        }
      });

      const Group = this.sequelize.define('GroupPKBT', {
        name: {
          type: DataTypes.STRING,
          primaryKey: true
        }
      });

      User.belongsTo(Group);

      return this.sequelize.sync({ force: true }).then(() => {
        expect(User.rawAttributes.GroupPKBTName.type).to.an.instanceof(DataTypes.STRING);
      });
    });

    it('should support a non-primary key as the association column on a target without a primary key', function() {
      const User = this.sequelize.define('User', { username: { type: DataTypes.STRING, unique: true } });
      const Task = this.sequelize.define('Task', { title: DataTypes.STRING });

      User.removeAttribute('id');
      Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username' });

      return this.sequelize.sync({ force: true })
        .then(() => User.create({ username: 'bob' }))
        .then(newUser => Task.create({ title: 'some task' })
          .then(newTask => newTask.setUser(newUser)))
        .then(() => Task.findOne({ where: { title: 'some task' } }))
        .then(foundTask => foundTask.getUser())
        .then(foundUser => expect(foundUser.username).to.equal('bob'))
        .then(() => this.sequelize.getQueryInterface().getForeignKeyReferencesForTable('Tasks'))
        .then(foreignKeysDescriptions => {
          expect(foreignKeysDescriptions[0]).to.includes({
            referencedColumnName: 'username',
            referencedTableName: 'Users',
            columnName: 'user_name'
          });
        });
    });

    it('should support a non-primary unique key as the association column', function() {
      const User = this.sequelize.define('User', {
        username: {
          type: DataTypes.STRING,
          field: 'user_name',
          unique: true
        }
      });
      const Task = this.sequelize.define('Task', {
        title: DataTypes.STRING
      });

      Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username' });

      return this.sequelize.sync({ force: true })
        .then(() => User.create({ username: 'bob' }))
        .then(newUser => Task.create({ title: 'some task' })
          .then(newTask => newTask.setUser(newUser)))
        .then(() => Task.findOne({ where: { title: 'some task' } }))
        .then(foundTask => foundTask.getUser())
        .then(foundUser => expect(foundUser.username).to.equal('bob'))
        .then(() => this.sequelize.getQueryInterface().getForeignKeyReferencesForTable('Tasks'))
        .then(foreignKeysDescriptions => {
          expect(foreignKeysDescriptions[0]).to.includes({
            referencedColumnName: 'user_name',
            referencedTableName: 'Users',
            columnName: 'user_name'
          });
        });
    });

    it('should support a non-primary key as the association column with a field option', function() {
      const User = this.sequelize.define('User', {
        username: {
          type: DataTypes.STRING,
          field: 'the_user_name_field',
          unique: true
        }
      });
      const Task = this.sequelize.define('Task', { title: DataTypes.STRING });

      User.removeAttribute('id');
      Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username' });

      return this.sequelize.sync({ force: true })
        .then(() => User.create({ username: 'bob' }))
        .then(newUser => Task.create({ title: 'some task' })
          .then(newTask => newTask.setUser(newUser)))
        .then(() => Task.findOne({ where: { title: 'some task' } }))
        .then(foundTask => foundTask.getUser())
        .then(foundUser => expect(foundUser.username).to.equal('bob'))
        .then(() => this.sequelize.getQueryInterface().getForeignKeyReferencesForTable('Tasks'))
        .then(foreignKeysDescriptions => {
          expect(foreignKeysDescriptions[0]).to.includes({
            referencedColumnName: 'the_user_name_field',
            referencedTableName: 'Users',
            columnName: 'user_name'
          });
        });
    });

    it('should support a non-primary key as the association column in a table with a composite primary key', function() {
      const User = this.sequelize.define('User', {
        username: {
          type: DataTypes.STRING,
          field: 'the_user_name_field',
          unique: true
        },
        age: {
          type: DataTypes.INTEGER,
          field: 'the_user_age_field',
          primaryKey: true
        },
        weight: {
          type: DataTypes.INTEGER,
          field: 'the_user_weight_field',
          primaryKey: true
        }
      });
      const Task = this.sequelize.define('Task', { title: DataTypes.STRING });

      Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username' });

      return this.sequelize.sync({ force: true })
        .then(() => User.create({ username: 'bob', age: 18, weight: 40 }))
        .then(newUser => Task.create({ title: 'some task' })
          .then(newTask => newTask.setUser(newUser)))
        .then(() => Task.findOne({ where: { title: 'some task' } }))
        .then(foundTask => foundTask.getUser())
        .then(foundUser => expect(foundUser.username).to.equal('bob'))
        .then(() => this.sequelize.getQueryInterface().getForeignKeyReferencesForTable('Tasks'))
        .then(foreignKeysDescriptions => {
          expect(foreignKeysDescriptions[0]).to.includes({
            referencedColumnName: 'the_user_name_field',
            referencedTableName: 'Users',
            columnName: 'user_name'
          });
        });
    });
  });

  describe('association options', () => {
    it('can specify data type for auto-generated relational keys', function() {
      const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING }),
        dataTypes = [DataTypes.INTEGER, DataTypes.BIGINT, DataTypes.STRING],
        Tasks = {};

      dataTypes.forEach(dataType => {
        const tableName = `TaskXYZ_${dataType.key}`;
        Tasks[dataType] = this.sequelize.define(tableName, { title: DataTypes.STRING });
        Tasks[dataType].belongsTo(User, { foreignKey: 'userId', keyType: dataType, constraints: false });
      });

      return this.sequelize.sync({ force: true }).then(() => {
        dataTypes.forEach(dataType => {
          expect(Tasks[dataType].rawAttributes.userId.type).to.be.an.instanceof(dataType);
        });
      });
    });

    describe('allows the user to provide an attribute definition object as foreignKey', () => {
      it('works with a column that hasnt been defined before', function() {
        const Task = this.sequelize.define('task', {}),
          User = this.sequelize.define('user', {});

        Task.belongsTo(User, {
          foreignKey: {
            allowNull: false,
            name: 'uid'
          }
        });

        expect(Task.rawAttributes.uid).to.be.ok;
        expect(Task.rawAttributes.uid.allowNull).to.be.false;
        expect(Task.rawAttributes.uid.references.model).to.equal(User.getTableName());
        expect(Task.rawAttributes.uid.references.key).to.equal('id');
      });

      it('works when taking a column directly from the object', function() {
        const User = this.sequelize.define('user', {
            uid: {
              type: Sequelize.INTEGER,
              primaryKey: true
            }
          }),
          Profile = this.sequelize.define('project', {
            user_id: {
              type: Sequelize.INTEGER,
              allowNull: false
            }
          });

        Profile.belongsTo(User, { foreignKey: Profile.rawAttributes.user_id });

        expect(Profile.rawAttributes.user_id).to.be.ok;
        expect(Profile.rawAttributes.user_id.references.model).to.equal(User.getTableName());
        expect(Profile.rawAttributes.user_id.references.key).to.equal('uid');
        expect(Profile.rawAttributes.user_id.allowNull).to.be.false;
      });

      it('works when merging with an existing definition', function() {
        const Task = this.sequelize.define('task', {
            projectId: {
              defaultValue: 42,
              type: Sequelize.INTEGER
            }
          }),
          Project = this.sequelize.define('project', {});

        Task.belongsTo(Project, { foreignKey: { allowNull: true } });

        expect(Task.rawAttributes.projectId).to.be.ok;
        expect(Task.rawAttributes.projectId.defaultValue).to.equal(42);
        expect(Task.rawAttributes.projectId.allowNull).to.be.ok;
      });
    });

    it('should throw an error if foreignKey and as result in a name clash', function() {
      const Person = this.sequelize.define('person', {}),
        Car = this.sequelize.define('car', {});

      expect(Car.belongsTo.bind(Car, Person, { foreignKey: 'person' })).to
        .throw('Naming collision between attribute \'person\' and association \'person\' on model car. To remedy this, change either foreignKey or as in your association definition');
    });

    it('should throw an error if an association clashes with the name of an already define attribute', function() {
      const Person = this.sequelize.define('person', {}),
        Car = this.sequelize.define('car', {
          person: Sequelize.INTEGER
        });

      expect(Car.belongsTo.bind(Car, Person, { as: 'person' })).to
        .throw('Naming collision between attribute \'person\' and association \'person\' on model car. To remedy this, change either foreignKey or as in your association definition');
    });
  });

  describe('Eager loading', () => {
    beforeEach(function() {
      this.Individual = this.sequelize.define('individual', {
        name: Sequelize.STRING
      });
      this.Hat = this.sequelize.define('hat', {
        name: Sequelize.STRING
      });
      this.Individual.belongsTo(this.Hat, {
        as: 'personwearinghat'
      });
    });

    it('should load with an alias', function() {
      return this.sequelize.sync({ force: true }).then(() => {
        return Promise.join(
          this.Individual.create({ name: 'Foo Bar' }),
          this.Hat.create({ name: 'Baz' }));
      }).then(([individual, hat]) => {
        return individual.setPersonwearinghat(hat);
      }).then(() => {
        return this.Individual.findOne({
          where: { name: 'Foo Bar' },
          include: [{ model: this.Hat, as: 'personwearinghat' }]
        });
      }).then(individual => {
        expect(individual.name).to.equal('Foo Bar');
        expect(individual.personwearinghat.name).to.equal('Baz');
      }).then(() => {
        return this.Individual.findOne({
          where: { name: 'Foo Bar' },
          include: [{
            model: this.Hat,
            as: { singular: 'personwearinghat' }
          }]
        });
      }).then(individual => {
        expect(individual.name).to.equal('Foo Bar');
        expect(individual.personwearinghat.name).to.equal('Baz');
      });
    });

    it('should load all', function() {
      return this.sequelize.sync({ force: true }).then(() => {
        return Promise.join(
          this.Individual.create({ name: 'Foo Bar' }),
          this.Hat.create({ name: 'Baz' }));
      }).then(([individual, hat]) => {
        return individual.setPersonwearinghat(hat);
      }).then(() => {
        return this.Individual.findOne({
          where: { name: 'Foo Bar' },
          include: [{ all: true }]
        });
      }).then(individual => {
        expect(individual.name).to.equal('Foo Bar');
        expect(individual.personwearinghat.name).to.equal('Baz');
      });
    });
  });
});


'use strict';

const chai = require('chai'),
  expect = chai.expect,
  Support = require('./support'),
  Promise = require('../../lib/promise'),
  Transaction = require('../../lib/transaction'),
  current = Support.sequelize;

if (current.dialect.supports.transactions) {

  describe(Support.getTestDialectTeaser('Sequelize#transaction'), () => {

    describe('then', () => {
      it('gets triggered once a transaction has been successfully committed', function() {
        let called = false;
        return this
          .sequelize
          .transaction().then(t => {
            return t.commit().then(() => {
              called = 1;
            });
          })
          .then(() => {
            expect(called).to.be.ok;
          });
      });

      it('gets triggered once a transaction has been successfully rolled back', function() {
        let called = false;
        return this
          .sequelize
          .transaction().then(t => {
            return t.rollback().then(() => {
              called = 1;
            });
          })
          .then(() => {
            expect(called).to.be.ok;
          });
      });

      if (Support.getTestDialect() !== 'sqlite') {
        it('works for long running transactions', function() {
          return Support.prepareTransactionTest(this.sequelize).then(sequelize => {
            this.sequelize = sequelize;

            this.User = sequelize.define('User', {
              name: Support.Sequelize.STRING
            }, { timestamps: false });

            return sequelize.sync({ force: true });
          }).then(() => {
            return this.sequelize.transaction();
          }).then(t => {
            let query = 'select sleep(2);';

            switch (Support.getTestDialect()) {
              case 'postgres':
                query = 'select pg_sleep(2);';
                break;
              case 'sqlite':
                query = 'select sqlite3_sleep(2000);';
                break;
              case 'mssql':
                query = 'WAITFOR DELAY \'00:00:02\';';
                break;
              default:
                break;
            }

            return this.sequelize.query(query, { transaction: t }).then(() => {
              return this.User.create({ name: 'foo' });
            }).then(() => {
              return this.sequelize.query(query, { transaction: t });
            }).then(() => {
              return t.commit();
            });
          }).then(() => {
            return this.User.findAll();
          }).then(users => {
            expect(users.length).to.equal(1);
            expect(users[0].name).to.equal('foo');
          });
        });
      }
    });

    describe('complex long running example', () => {
      it('works with promise syntax', function() {
        return Support.prepareTransactionTest(this.sequelize).then(sequelize => {
          const Test = sequelize.define('Test', {
            id: { type: Support.Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
            name: { type: Support.Sequelize.STRING }
          });

          return sequelize.sync({ force: true }).then(() => {
            return sequelize.transaction().then(transaction => {
              expect(transaction).to.be.instanceOf(Transaction);

              return Test
                .create({ name: 'Peter' }, { transaction })
                .then(() => {
                  return Promise.delay(1000).then(() => {
                    return transaction
                      .commit()
                      .then(() => { return Test.count(); })
                      .then(count => {
                        expect(count).to.equal(1);
                      });
                  });
                });
            });
          });
        });
      });
    });

    describe('concurrency', () => {
      describe('having tables with uniqueness constraints', () => {
        beforeEach(function() {
          return Support.prepareTransactionTest(this.sequelize).then(sequelize => {
            this.sequelize = sequelize;

            this.Model = sequelize.define('Model', {
              name: { type: Support.Sequelize.STRING, unique: true }
            }, {
              timestamps: false
            });

            return this.Model.sync({ force: true });
          });
        });

        it('triggers the error event for the second transactions', function() {
          return this.sequelize.transaction().then(t1 => {
            return this.sequelize.transaction().then(t2 => {
              return this.Model.create({ name: 'omnom' }, { transaction: t1 }).then(() => {
                return Promise.all([
                  this.Model.create({ name: 'omnom' }, { transaction: t2 }).catch(err => {
                    expect(err).to.be.ok;
                    return t2.rollback();
                  }),
                  Promise.delay(100).then(() => {
                    return t1.commit();
                  })
                ]);
              });
            });
          });
        });
      });
    });
  });

}


'use strict';

const BaseError = require('./base-error');

/**
 * A base class for all database related errors.
 */
class DatabaseError extends BaseError {
  constructor(parent) {
    super(parent.message);
    this.name = 'SequelizeDatabaseError';
    /**
     * @type {Error}
     */
    this.parent = parent;
    /**
     * @type {Error}
     */
    this.original = parent;
    /**
     * The SQL that triggered the error
     * @type {string}
     */
    this.sql = parent.sql;
    Error.captureStackTrace(this, this.constructor);
  }
}

module.exports = DatabaseError;


keep keep keep keep keep replace keep keep keep

/**
* @author       Richard Davey <rich@photonstorm.com>
* @copyright    2013 Photon Storm Ltd.
* @license      {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/

/**
* "This world is but a canvas to our imagination." - Henry David Thoreau
*
* A game has only one world. The world is an abstract place in which all game objects live. It is not bound
* by stage limits and can be any size. You look into the world via cameras. All game objects live within
* the world at world-based coordinates. By default a world is created the same size as your Stage.
*
* @class Phaser.World
* @constructor
* @param {Phaser.Game} game - Reference to the current game instance.
*/
Phaser.World = function (game) {

    Phaser.Group.call(this, game, null, '__world', false);

    /**
    * @property {Phaser.Point} scale - Replaces the PIXI.Point with a slightly more flexible one.
    */
    this.scale = new Phaser.Point(1, 1);

    /**
    * The World has no fixed size, but it does have a bounds outside of which objects are no longer considered as being "in world" and you should use this to clean-up the display list and purge dead objects.
    * By default we set the Bounds to be from 0,0 to Game.width,Game.height. I.e. it will match the size given to the game constructor with 0,0 representing the top-left of the display.
    * However 0,0 is actually the center of the world, and if you rotate or scale the world all of that will happen from 0,0.
    * So if you want to make a game in which the world itself will rotate you should adjust the bounds so that 0,0 is the center point, i.e. set them to -1000,-1000,2000,2000 for a 2000x2000 sized world centered around 0,0.
	* @property {Phaser.Rectangle} bounds - Bound of this world that objects can not escape from.
	*/
	this.bounds = new Phaser.Rectangle(0, 0, game.width, game.height);

    /**
	* @property {Phaser.Camera} camera - Camera instance.
	*/
	this.camera = null;

    /**
	* @property {number} currentRenderOrderID - Reset each frame, keeps a count of the total number of objects updated.
	*/
	this.currentRenderOrderID = 0;

};

Phaser.World.prototype = Object.create(Phaser.Group.prototype);
Phaser.World.prototype.constructor = Phaser.World;

/**
* Initialises the game world.
*
* @method Phaser.World#boot
* @protected
*/
Phaser.World.prototype.boot = function () {

    this.camera = new Phaser.Camera(this.game, 0, 0, 0, this.game.width, this.game.height);

    this.camera.displayObject = this._container;

    this.game.camera = this.camera;

}

/**
* This is called automatically every frame, and is where main logic happens.
*
* @method Phaser.World#update
*/
Phaser.World.prototype.update = function () {

	this.currentRenderOrderID = 0;

	if (this.game.stage._stage.first._iNext)
	{
		var currentNode = this.game.stage._stage.first._iNext;

		do
		{
			if (currentNode['preUpdate'])
			{
				currentNode.preUpdate();
			}

			if (currentNode['update'])
			{
				currentNode.update();
			}

			currentNode = currentNode._iNext;
		}
		while (currentNode != this.game.stage._stage.last._iNext)
	}

}

/**
* This is called automatically every frame, and is where main logic happens.
* @method Phaser.World#postUpdate
*/
Phaser.World.prototype.postUpdate = function () {

    this.camera.update();

    if (this.game.stage._stage.first._iNext)
    {
        var currentNode = this.game.stage._stage.first._iNext;

        do
        {
            if (currentNode['postUpdate'])
            {
                currentNode.postUpdate();
            }

            currentNode = currentNode._iNext;
        }
        while (currentNode != this.game.stage._stage.last._iNext)
    }

}

/**
* Updates the size of this world. Note that this doesn't modify the world x/y coordinates, just the width and height.
* If you need to adjust the bounds of the world
* @method Phaser.World#setBounds
* @param {number} x - Top left most corner of the world.
* @param {number} y - Top left most corner of the world.
* @param {number} width - New width of the world.
* @param {number} height - New height of the world.
*/
Phaser.World.prototype.setBounds = function (x, y, width, height) {

    this.bounds.setTo(x, y, width, height);

    if (this.camera.bounds)
    {
        this.camera.bounds.setTo(x, y, width, height);
    }

}

/**
* Destroyer of worlds.
* @method Phaser.World#destroy
*/
Phaser.World.prototype.destroy = function () {

    this.camera.x = 0;
    this.camera.y = 0;

    this.game.input.reset(true);

    this.removeAll();

}

/**
* @name Phaser.World#width
* @property {number} width - Gets or sets the current width of the game world.
*/
Object.defineProperty(Phaser.World.prototype, "width", {

    get: function () {
        return this.bounds.width;
    },

    set: function (value) {
        this.bounds.width = value;
    }

});

/**
* @name Phaser.World#height
* @property {number} height - Gets or sets the current height of the game world.
*/
Object.defineProperty(Phaser.World.prototype, "height", {

    get: function () {
        return this.bounds.height;
    },

    set: function (value) {
        this.bounds.height = value;
    }

});

/**
* @name Phaser.World#centerX
* @property {number} centerX - Gets the X position corresponding to the center point of the world.
* @readonly
*/
Object.defineProperty(Phaser.World.prototype, "centerX", {

    get: function () {
        return this.bounds.halfWidth;
    }

});

/**
* @name Phaser.World#centerY
* @property {number} centerY - Gets the Y position corresponding to the center point of the world.
* @readonly
*/
Object.defineProperty(Phaser.World.prototype, "centerY", {

    get: function () {
        return this.bounds.halfHeight;
    }

});

/**
* @name Phaser.World#randomX
* @property {number} randomX - Gets a random integer which is lesser than or equal to the current width of the game world.
* @readonly
*/
Object.defineProperty(Phaser.World.prototype, "randomX", {

    get: function () {

        if (this.bounds.x < 0)
        {
            return this.game.rnd.integerInRange(this.bounds.x, (this.bounds.width - Math.abs(this.bounds.x)));
        }
        else
        {
            return this.game.rnd.integerInRange(this.bounds.x, this.bounds.width);
        }

    }

});

/**
* @name Phaser.World#randomY
* @property {number} randomY - Gets a random integer which is lesser than or equal to the current height of the game world.
* @readonly
*/
Object.defineProperty(Phaser.World.prototype, "randomY", {

    get: function () {

        if (this.bounds.y < 0)
        {
            return this.game.rnd.integerInRange(this.bounds.y, (this.bounds.height - Math.abs(this.bounds.y)));
        }
        else
        {
            return this.game.rnd.integerInRange(this.bounds.y, this.bounds.height);
        }

    }

});

/**
* @name Phaser.World#visible
* @property {boolean} visible - Gets or sets the visible state of the World.
*/
Object.defineProperty(Phaser.World.prototype, "visible", {

    get: function () {
        return this._container.visible;
    },

    set: function (value) {
        this._container.visible = value;
    }

});