keep keep keep keep replace keep keep keep keep keep

this.addFile(new ScriptFile(this, key, url, type, xhrSettings));

/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2020 Photon Storm Ltd.
 * @license      {@link https://opensource.org/licenses/MIT|MIT License}
 */

var Class = require('../../utils/Class');
var CONST = require('../const');
var File = require('../File');
var FileTypesManager = require('../FileTypesManager');
var GetFastValue = require('../../utils/object/GetFastValue');
var IsPlainObject = require('../../utils/object/IsPlainObject');

/**
 * @classdesc
 * A single Script File suitable for loading by the Loader.
 *
 * These are created when you use the Phaser.Loader.LoaderPlugin#script method and are not typically created directly.
 *
 * For documentation about what all the arguments and configuration options mean please see Phaser.Loader.LoaderPlugin#script.
 *
 * @class ScriptFile
 * @extends Phaser.Loader.File
 * @memberof Phaser.Loader.FileTypes
 * @constructor
 * @since 3.0.0
 *
 * @param {Phaser.Loader.LoaderPlugin} loader - A reference to the Loader that is responsible for this file.
 * @param {(string|Phaser.Types.Loader.FileTypes.ScriptFileConfig)} key - The key to use for this file, or a file configuration object.
 * @param {string} [url] - The absolute or relative URL to load this file from. If undefined or `null` it will be set to `<key>.js`, i.e. if `key` was "alien" then the URL will be "alien.js".
 * @param {Phaser.Types.Loader.XHRSettingsObject} [xhrSettings] - Extra XHR Settings specifically for this file.
 */
var ScriptFile = new Class({

    Extends: File,

    initialize:

    function ScriptFile (loader, key, url, xhrSettings)
    {
        var extension = 'js';

        if (IsPlainObject(key))
        {
            var config = key;

            key = GetFastValue(config, 'key');
            url = GetFastValue(config, 'url');
            xhrSettings = GetFastValue(config, 'xhrSettings');
            extension = GetFastValue(config, 'extension', extension);
        }

        var fileConfig = {
            type: 'script',
            cache: false,
            extension: extension,
            responseType: 'text',
            key: key,
            url: url,
            xhrSettings: xhrSettings
        };

        File.call(this, loader, fileConfig);
    },

    /**
     * Called automatically by Loader.nextFile.
     * This method controls what extra work this File does with its loaded data.
     *
     * @method Phaser.Loader.FileTypes.ScriptFile#onProcess
     * @since 3.7.0
     */
    onProcess: function ()
    {
        this.state = CONST.FILE_PROCESSING;

        this.data = document.createElement('script');
        this.data.language = 'javascript';
        this.data.type = 'text/javascript';
        this.data.defer = false;
        this.data.text = this.xhrLoader.responseText;

        document.head.appendChild(this.data);

        this.onProcessComplete();
    }

});

/**
 * Adds a Script file, or array of Script files, to the current load queue.
 *
 * You can call this method from within your Scene's `preload`, along with any other files you wish to load:
 *
 * ```javascript
 * function preload ()
 * {
 *     this.load.script('aliens', 'lib/aliens.js');
 * }
 * ```
 *
 * The file is **not** loaded right away. It is added to a queue ready to be loaded either when the loader starts,
 * or if it's already running, when the next free load slot becomes available. This happens automatically if you
 * are calling this from within the Scene's `preload` method, or a related callback. Because the file is queued
 * it means you cannot use the file immediately after calling this method, but must wait for the file to complete.
 * The typical flow for a Phaser Scene is that you load assets in the Scene's `preload` method and then when the
 * Scene's `create` method is called you are guaranteed that all of those assets are ready for use and have been
 * loaded.
 *
 * The key must be a unique String and not already in-use by another file in the Loader.
 *
 * Instead of passing arguments you can pass a configuration object, such as:
 *
 * ```javascript
 * this.load.script({
 *     key: 'aliens',
 *     url: 'lib/aliens.js'
 * });
 * ```
 *
 * See the documentation for `Phaser.Types.Loader.FileTypes.ScriptFileConfig` for more details.
 *
 * Once the file has finished loading it will automatically be converted into a script element
 * via `document.createElement('script')`. It will have its language set to JavaScript, `defer` set to
 * false and then the resulting element will be appended to `document.head`. Any code then in the
 * script will be executed.
 *
 * The URL can be relative or absolute. If the URL is relative the `Loader.baseURL` and `Loader.path` values will be prepended to it.
 *
 * If the URL isn't specified the Loader will take the key and create a filename from that. For example if the key is "alien"
 * and no URL is given then the Loader will set the URL to be "alien.js". It will always add `.js` as the extension, although
 * this can be overridden if using an object instead of method arguments. If you do not desire this action then provide a URL.
 *
 * Note: The ability to load this type of file will only be available if the Script File type has been built into Phaser.
 * It is available in the default build but can be excluded from custom builds.
 *
 * @method Phaser.Loader.LoaderPlugin#script
 * @fires Phaser.Loader.LoaderPlugin#ADD
 * @since 3.0.0
 *
 * @param {(string|Phaser.Types.Loader.FileTypes.ScriptFileConfig|Phaser.Types.Loader.FileTypes.ScriptFileConfig[])} key - The key to use for this file, or a file configuration object, or array of them.
 * @param {string} [url] - The absolute or relative URL to load this file from. If undefined or `null` it will be set to `<key>.js`, i.e. if `key` was "alien" then the URL will be "alien.js".
 * @param {Phaser.Types.Loader.XHRSettingsObject} [xhrSettings] - An XHR Settings configuration object. Used in replacement of the Loaders default XHR Settings.
 *
 * @return {this} The Loader instance.
 */
FileTypesManager.register('script', function (key, url, xhrSettings)
{
    if (Array.isArray(key))
    {
        for (var i = 0; i < key.length; i++)
        {
            //  If it's an array it has to be an array of Objects, so we get everything out of the 'key' object
            this.addFile(new ScriptFile(this, key[i]));
        }
    }
    else
    {
        this.addFile(new ScriptFile(this, key, url, xhrSettings));
    }

    return this;
});

module.exports = ScriptFile;


/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2020 Photon Storm Ltd.
 * @license      {@link https://opensource.org/licenses/MIT|MIT License}
 */

/**
 * The Matter Physics Drag Start Event.
 *
 * This event is dispatched by a Matter Physics World instance when a Pointer Constraint
 * starts dragging a body.
 *
 * Listen to it from a Scene using: `this.matter.world.on('dragstart', listener)`.
 *
 * @event Phaser.Physics.Matter.Events#DRAG_START
 * @since 3.16.2
 *
 * @param {MatterJS.BodyType} body - The Body that has started being dragged. This is a Matter Body, not a Phaser Game Object.
 * @param {MatterJS.BodyType} part - The part of the body that was clicked on.
 * @param {Phaser.Physics.Matter.PointerConstraint} constraint - The Pointer Constraint that is dragging the body.
 */
module.exports = 'dragstart';


/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2020 Photon Storm Ltd.
 * @license      {@link https://opensource.org/licenses/MIT|MIT License}
 */

/**
 * The Renderer Resize Event.
 *
 * This event is dispatched by the Phaser Renderer when it is resized, usually as a result
 * of the Scale Manager resizing.
 *
 * @event Phaser.Renderer.Events#RESIZE
 * @since 3.50.0
 *
 * @param {number} width - The new width of the renderer.
 * @param {number} height - The new height of the renderer.
 */
module.exports = 'resize';


/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2020 Photon Storm Ltd.
 * @license      {@link https://opensource.org/licenses/MIT|MIT License}
 */

/**
 * This is a stub function for Rope.Render. There is no Canvas renderer for Rope objects.
 *
 * @method Phaser.GameObjects.Rope#renderCanvas
 * @since 3.23.0
 * @private
 *
 * @param {Phaser.Renderer.Canvas.CanvasRenderer} renderer - A reference to the current active Canvas renderer.
 * @param {Phaser.GameObjects.Rope} src - The Game Object being rendered in this call.
 * @param {Phaser.Cameras.Scene2D.Camera} camera - The Camera that is rendering the Game Object.
 */
var RopeCanvasRenderer = function ()
{
};

module.exports = RopeCanvasRenderer;


* @property {string} key - The key of the file. Must be unique within the Loader.

* @property {string} [url] - The absolute or relative URL to load the file from.

* @property {string} [extension='js'] - The default file extension to use if no url is provided.

* @property {Phaser.Types.Loader.XHRSettingsObject} [xhrSettings] - Extra XHR Settings specifically for this file.

* @property {string} [type='script'] - The script type. Should be either 'script' for classic JavaScript, or 'module' if the file contains an exported module.


/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2020 Photon Storm Ltd.
 * @license      {@link https://opensource.org/licenses/MIT|MIT License}
 */

/**
 * Calculate the length of the given line.
 *
 * @function Phaser.Geom.Line.Length
 * @since 3.0.0
 *
 * @param {Phaser.Geom.Line} line - The line to calculate the length of.
 *
 * @return {number} The length of the line.
 */
var Length = function (line)
{
    return Math.sqrt((line.x2 - line.x1) * (line.x2 - line.x1) + (line.y2 - line.y1) * (line.y2 - line.y1));
};

module.exports = Length;


/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2020 Photon Storm Ltd.
 * @license      {@link https://opensource.org/licenses/MIT|MIT License}
 */

/**
 * @namespace Phaser.Math.Easing.Elastic
 */

module.exports = {

    In: require('./In'),
    Out: require('./Out'),
    InOut: require('./InOut')

};


/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2020 Photon Storm Ltd.
 * @license      {@link https://opensource.org/licenses/MIT|MIT License}
 */

/**
 * @namespace Phaser.Structs
 */

module.exports = {

    Events: require('./events'),
    List: require('./List'),
    Map: require('./Map'),
    ProcessQueue: require('./ProcessQueue'),
    RTree: require('./RTree'),
    Set: require('./Set'),
    Size: require('./Size')

};


if (!options.order || options.include && !orders.subQueryOrder.length) {

const tablePkFragment = `${this.quoteTable(options.tableAs || model.name)}.${this.quoteIdentifier(model.primaryKeyField)}`;

if (!options.order || !options.order.length || options.include && !orders.subQueryOrder.length) {

'use strict';

const Support   = require('../support'),
  util = require('util'),
  expectsql = Support.expectsql,
  current   = Support.sequelize,
  sql       = current.dialect.queryGenerator;

// Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation

describe(Support.getTestDialectTeaser('SQL'), () => {
  describe('offset/limit', () => {
    const testsql = function(options, expectation) {
      const model = options.model;

      it(util.inspect(options, { depth: 2 }), () => {
        return expectsql(
          sql.addLimitAndOffset(
            options,
            model
          ),
          expectation
        );
      });
    };

    testsql({
      limit: 10, //when no order by present, one is automagically prepended, test its existence
      model: { primaryKeyField: 'id', name: 'tableRef' }
    }, {
      default: ' LIMIT 10',
      mssql: ' ORDER BY [tableRef].[id] OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY'
    });

    testsql({
      limit: 10,
      order: [
        ['email', 'DESC'] // for MSSQL
      ]
    }, {
      default: ' LIMIT 10',
      mssql: ' OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY'
    });

    testsql({
      limit: 10,
      offset: 20,
      order: [
        ['email', 'DESC'] // for MSSQL
      ]
    }, {
      default: ' LIMIT 20, 10',
      postgres: ' LIMIT 10 OFFSET 20',
      mssql: ' OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY'
    });

    testsql({
      limit: "';DELETE FROM user",
      order: [
        ['email', 'DESC'] // for MSSQL
      ]
    }, {
      default: " LIMIT ''';DELETE FROM user'",
      mariadb: " LIMIT '\\';DELETE FROM user'",
      mysql: " LIMIT '\\';DELETE FROM user'",
      mssql: " OFFSET 0 ROWS FETCH NEXT N''';DELETE FROM user' ROWS ONLY"
    });

    testsql({
      limit: 10,
      offset: "';DELETE FROM user",
      order: [
        ['email', 'DESC'] // for MSSQL
      ]
    }, {
      sqlite: " LIMIT ''';DELETE FROM user', 10",
      postgres: " LIMIT 10 OFFSET ''';DELETE FROM user'",
      mariadb: " LIMIT '\\';DELETE FROM user', 10",
      mysql: " LIMIT '\\';DELETE FROM user', 10",
      mssql: " OFFSET N''';DELETE FROM user' ROWS FETCH NEXT 10 ROWS ONLY"
    });
  });
});


'use strict';

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

/**
 * Thrown when a connection to a database is closed while an operation is in progress
 */
class AsyncQueueError extends BaseError {
  constructor(message) {
    super(message);
    this.name = 'SequelizeAsyncQueueError';
  }
}

exports.AsyncQueueError = AsyncQueueError;

class AsyncQueue {
  constructor() {
    this.previous = Promise.resolve();
    this.closed = false;
    this.rejectCurrent = () => {};
  }
  close() {
    this.closed = true;
    this.rejectCurrent(new ConnectionError(new AsyncQueueError('the connection was closed before this query could finish executing')));
  }
  enqueue(asyncFunction) {
    // This outer promise might seems superflous since down below we return asyncFunction().then(resolve, reject).
    // However, this ensures that this.previous will never be a rejected promise so the queue will
    // always keep going, while still communicating rejection from asyncFunction to the user.
    return new Promise((resolve, reject) => {
      this.previous = this.previous.then(
        () => {
          this.rejectCurrent = reject;
          if (this.closed) {
            return reject(new ConnectionError(new AsyncQueueError('the connection was closed before this query could be executed')));
          }
          return asyncFunction().then(resolve, reject);
        }
      );
    });
  }
}

exports.default = AsyncQueue;


'use strict';

const ConnectionError = require('./../connection-error');

/**
 * Thrown when a connection to a database has a hostname that was not found
 */
class HostNotFoundError extends ConnectionError {
  constructor(parent) {
    super(parent);
    this.name = 'SequelizeHostNotFoundError';
  }
}

module.exports = HostNotFoundError;


import {
  BelongsTo,
  BelongsToCreateAssociationMixin,
  BelongsToGetAssociationMixin,
  BelongsToSetAssociationMixin,
  DataTypes,
  FindOptions,
  Model,
  ModelCtor,
  Op
} from 'sequelize';
import { sequelize } from '../connection';

export class User extends Model {
  public static associations: {
    group: BelongsTo<User, UserGroup>;
  };

  public id!: number;
  public username!: string;
  public firstName!: string;
  public lastName!: string;
  public createdAt!: Date;
  public updatedAt!: Date;

  // mixins for association (optional)
  public groupId!: number;
  public group?: UserGroup;
  public getGroup!: BelongsToGetAssociationMixin<UserGroup>;
  public setGroup!: BelongsToSetAssociationMixin<UserGroup, number>;
  public createGroup!: BelongsToCreateAssociationMixin<UserGroup>;
}

User.init(
  {
    firstName: DataTypes.STRING,
    lastName: DataTypes.STRING,
    username: DataTypes.STRING,
  },
  {
    version: true,
    getterMethods: {
    a() {
      return 1;
    },
    },
    setterMethods: {
      b(val: string) {
        (<User>this).username = val;
      },
    },
    scopes: {
      custom(a: number) {
        return {
          where: {
            firstName: a,
          },
        };
      },
      custom2() {
        return {}
      }
    },
    indexes: [{
      fields: ['firstName'],
      using: 'BTREE',
      name: 'firstNameIdx',
      concurrently: true,
    }],
    sequelize,
  }
);

User.afterSync(() => {
  sequelize.getQueryInterface().addIndex(User.tableName, {
      fields: ['lastName'],
      using: 'BTREE',
      name: 'lastNameIdx',
      concurrently: true,
  })
})

// Hooks
User.afterFind((users, options) => {
  console.log('found');
});

// TODO: VSCode shows the typing being correctly narrowed but doesn't do it correctly
User.addHook('beforeFind', 'test', (options: FindOptions) => {
  return undefined;
});

// Model#addScope
User.addScope('withoutFirstName', {
  where: {
    firstName: {
      [Op.is]: null,
    },
  },
});

User.addScope(
  'withFirstName',
  (firstName: string) => ({
    where: { firstName },
  }),
);

// associate
// it is important to import _after_ the model above is already exported so the circular reference works.
import { UserGroup } from './UserGroup';
export const Group = User.belongsTo(UserGroup, { as: 'group', foreignKey: 'groupId' });

// associations refer to their Model
const userType: ModelCtor<User> = User.associations.group.source;
const groupType: ModelCtor<UserGroup> = User.associations.group.target;

User.scope([
  'custom2',
  { method: [ 'custom', 32 ] }
])

const instance = new User({ username: 'foo', firstName: 'bar', lastName: 'baz' });
instance.isSoftDeleted()


fragment += ` ORDER BY ${tablePkFragment}`;

const orderFieldNames = _.map(options.order, order => order[0]);

const primaryKeyFieldAlreadyPresent = _.includes(orderFieldNames, model.primaryKeyField);

if (!options.order || !options.order.length) {


'use strict';

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

describe(Support.getTestDialectTeaser('Instance'), () => {
  describe('get', () => {
    beforeEach(function() {
      this.getSpy = sinon.spy();
      this.User = current.define('User', {
        name: {
          type: DataTypes.STRING,
          get: this.getSpy
        }
      });
    });

    it('invokes getter if raw: false', function() {
      this.User.build().get('name');

      expect(this.getSpy).to.have.been.called;
    });

    it('does not invoke getter if raw: true', function() {
      this.User.build().get('name', { raw: true });

      expect(this.getSpy).not.to.have.been.called;
    });
  });
});


'use strict';

const Support   = require('../support'),
  QueryTypes = require('../../../lib/query-types'),
  util = require('util'),
  _ = require('lodash'),
  expectsql = Support.expectsql,
  current   = Support.sequelize,
  Sequelize = Support.Sequelize,
  sql       = current.dialect.queryGenerator;

// Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation

describe(Support.getTestDialectTeaser('SQL'), () => {
  describe('delete', () => {
    const User = current.define('test_user', {}, {
      timestamps: false,
      schema: 'public'
    });

    describe('truncate #4306', () => {
      const options = {
        table: User.getTableName(),
        where: {},
        truncate: true,
        cascade: true,
        limit: 10,
        type: QueryTypes.BULKDELETE
      };

      it(util.inspect(options, { depth: 2 }), () => {
        return expectsql(
          sql.truncateTableQuery(
            options.table,
            options
          ), {
            postgres: 'TRUNCATE "public"."test_users" CASCADE',
            mssql: 'TRUNCATE TABLE [public].[test_users]',
            mariadb: 'TRUNCATE `public`.`test_users`',
            mysql: 'TRUNCATE `public.test_users`',
            sqlite: 'DELETE FROM `public.test_users`'
          }
        );
      });
    });

    describe('truncate with cascade and restartIdentity', () => {
      const options = {
        table: User.getTableName(),
        where: {},
        truncate: true,
        cascade: true,
        restartIdentity: true,
        limit: 10,
        type: QueryTypes.BULKDELETE
      };

      it(util.inspect(options, { depth: 2 }), () => {
        return expectsql(
          sql.truncateTableQuery(
            options.table,
            options
          ), {
            postgres: 'TRUNCATE "public"."test_users" RESTART IDENTITY CASCADE',
            mssql: 'TRUNCATE TABLE [public].[test_users]',
            mariadb: 'TRUNCATE `public`.`test_users`',
            mysql: 'TRUNCATE `public.test_users`',
            sqlite: 'DELETE FROM `public.test_users`; DELETE FROM `sqlite_sequence` WHERE `name` = \'public.test_users\';'
          }
        );
      });
    });

    describe('delete without limit', () => {
      const options = {
        table: User.getTableName(),
        where: { name: 'foo' },
        limit: null,
        type: QueryTypes.BULKDELETE
      };

      it(util.inspect(options, { depth: 2 }), () => {
        return expectsql(
          sql.deleteQuery(
            options.table,
            options.where,
            options,
            User
          ), {
            default: "DELETE FROM [public.test_users] WHERE `name` = 'foo'",
            postgres: 'DELETE FROM "public"."test_users" WHERE "name" = \'foo\'',
            mariadb: 'DELETE FROM `public`.`test_users` WHERE `name` = \'foo\'',
            sqlite: "DELETE FROM `public.test_users` WHERE `name` = 'foo'",
            mssql: "DELETE FROM [public].[test_users] WHERE [name] = N'foo'; SELECT @@ROWCOUNT AS AFFECTEDROWS;"
          }
        );
      });
    });

    describe('delete with limit', () => {
      const options = {
        table: User.getTableName(),
        where: { name: "foo';DROP TABLE mySchema.myTable;" },
        limit: 10,
        type: QueryTypes.BULKDELETE
      };

      it(util.inspect(options, { depth: 2 }), () => {
        return expectsql(
          sql.deleteQuery(
            options.table,
            options.where,
            options,
            User
          ), {
            postgres: 'DELETE FROM "public"."test_users" WHERE "id" IN (SELECT "id" FROM "public"."test_users" WHERE "name" = \'foo\'\';DROP TABLE mySchema.myTable;\' LIMIT 10)',
            mariadb: "DELETE FROM `public`.`test_users` WHERE `name` = 'foo\\';DROP TABLE mySchema.myTable;' LIMIT 10",
            sqlite: "DELETE FROM `public.test_users` WHERE rowid IN (SELECT rowid FROM `public.test_users` WHERE `name` = 'foo'';DROP TABLE mySchema.myTable;' LIMIT 10)",
            mssql: "DELETE TOP(10) FROM [public].[test_users] WHERE [name] = N'foo'';DROP TABLE mySchema.myTable;'; SELECT @@ROWCOUNT AS AFFECTEDROWS;",
            default: "DELETE FROM [public.test_users] WHERE `name` = 'foo\\';DROP TABLE mySchema.myTable;' LIMIT 10"
          }
        );
      });
    });

    describe('delete with limit and without model', () => {
      const options = {
        table: User.getTableName(),
        where: { name: "foo';DROP TABLE mySchema.myTable;" },
        limit: 10,
        type: QueryTypes.BULKDELETE
      };

      it(util.inspect(options, { depth: 2 }), () => {
        let query;
        try {
          query = sql.deleteQuery(
            options.table,
            options.where,
            options,
            null
          );
        } catch (err) {
          query = err;
        }

        return expectsql(
          query, {
            postgres: new Error('Cannot LIMIT delete without a model.'),
            mariadb: "DELETE FROM `public`.`test_users` WHERE `name` = 'foo\\';DROP TABLE mySchema.myTable;' LIMIT 10",
            sqlite: "DELETE FROM `public.test_users` WHERE rowid IN (SELECT rowid FROM `public.test_users` WHERE `name` = 'foo'';DROP TABLE mySchema.myTable;' LIMIT 10)",
            mssql: "DELETE TOP(10) FROM [public].[test_users] WHERE [name] = N'foo'';DROP TABLE mySchema.myTable;'; SELECT @@ROWCOUNT AS AFFECTEDROWS;",
            default: "DELETE FROM [public.test_users] WHERE `name` = 'foo\\';DROP TABLE mySchema.myTable;' LIMIT 10"
          }
        );
      });
    });

    describe('delete when the primary key has a different field name', () => {
      const User = current.define('test_user', {
        id: {
          type: Sequelize.INTEGER,
          primaryKey: true,
          field: 'test_user_id'
        }
      }, {
        timestamps: false,
        schema: 'public'
      });

      const options = {
        table: 'test_user',
        where: { 'test_user_id': 100 },
        type: QueryTypes.BULKDELETE
      };

      it(util.inspect(options, { depth: 2 }), () => {
        return expectsql(
          sql.deleteQuery(
            options.table,
            options.where,
            options,
            User
          ), {
            postgres: 'DELETE FROM "test_user" WHERE "test_user_id" = 100',
            sqlite: 'DELETE FROM `test_user` WHERE `test_user_id` = 100',
            mssql: 'DELETE FROM [test_user] WHERE [test_user_id] = 100; SELECT @@ROWCOUNT AS AFFECTEDROWS;',
            default: 'DELETE FROM [test_user] WHERE [test_user_id] = 100'
          }
        );
      });
    });

    describe('delete with undefined parameter in where', () => {
      const options = {
        table: User.getTableName(),
        type: QueryTypes.BULKDELETE,
        where: { name: undefined },
        limit: null
      };

      it(util.inspect(options, { depth: 2 }), () => {
        const sqlOrError = _.attempt(
          sql.deleteQuery.bind(sql),
          options.table,
          options.where,
          options,
          User
        );
        return expectsql(sqlOrError, {
          default: new Error('WHERE parameter "name" has invalid "undefined" value')
        });
      });
    });
  });
});


/**
 * The datatypes are used when defining a new model using `Model.init`, like this:
 * ```js
 * class MyModel extends MyModel {}
 * MyModel.init({ column: DataTypes.INTEGER }, { sequelize });
 * ```
 * When defining a model you can just as easily pass a string as type, but often using the types defined here is beneficial. For example, using `DataTypes.BLOB`, mean
 * that that column will be returned as an instance of `Buffer` when being fetched by sequelize.
 *
 * Some data types have special properties that can be accessed in order to change the data type.
 * For example, to get an unsigned integer with zerofill you can do `DataTypes.INTEGER.UNSIGNED.ZEROFILL`.
 * The order you access the properties in do not matter, so `DataTypes.INTEGER.ZEROFILL.UNSIGNED` is fine as well. The available properties are listed under each data type.
 *
 * To provide a length for the data type, you can invoke it like a function: `INTEGER(2)`
 *
 * Three of the values provided here (`NOW`, `UUIDV1` and `UUIDV4`) are special default values, that should not be used to define types. Instead they are used as shorthands for
 * defining default values. For example, to get a uuid field with a default value generated following v1 of the UUID standard:
 * ```js
 * class MyModel extends Model {}
 * MyModel.init({
 *   uuid: {
 *     type: DataTypes.UUID,
 *     defaultValue: DataTypes.UUIDV1,
 *     primaryKey: true
 *   }
 * }, { sequelize })
 * ```
 * There may be times when you want to generate your own UUID conforming to some other algorithm. This is accomplised
 * using the defaultValue property as well, but instead of specifying one of the supplied UUID types, you return a value
 * from a function.
 * ```js
 * class MyModel extends Model {}
 * MyModel.init({
 *   uuid: {
 *     type: DataTypes.UUID,
 *     defaultValue() {
 *       return generateMyId()
 *     },
 *     primaryKey: true
 *    }
 * }, { sequelize })
 * ```
 */

/**
 *
 */
export type DataType = string | AbstractDataTypeConstructor | AbstractDataType;

export const ABSTRACT: AbstractDataTypeConstructor;

interface AbstractDataTypeConstructor {
  key: string;
  warn(link: string, text: string): void;
}

export interface AbstractDataType {
  key: string;
  dialectTypes: string;
  toSql(): string;
  stringify(value: unknown, options?: object): string;
  toString(options: object): string;
}

/**
 * A variable length string. Default length 255
 */
export const STRING: StringDataTypeConstructor;

interface StringDataTypeConstructor extends AbstractDataTypeConstructor {
  new (length?: number, binary?: boolean): StringDataType;
  new (options?: StringDataTypeOptions): StringDataType;
  (length?: number, binary?: boolean): StringDataType;
  (options?: StringDataTypeOptions): StringDataType;
}

export interface StringDataType extends AbstractDataType {
  options?: StringDataTypeOptions;
  BINARY: this;
  validate(value: unknown): boolean;
}

export interface StringDataTypeOptions {
  length?: number;
  binary?: boolean;
}

/**
 * A fixed length string. Default length 255
 */
export const CHAR: CharDataTypeConstructor;

interface CharDataTypeConstructor extends StringDataTypeConstructor {
  new (length?: number, binary?: boolean): CharDataType;
  new (options?: CharDataTypeOptions): CharDataType;
  (length?: number, binary?: boolean): CharDataType;
  (options?: CharDataTypeOptions): CharDataType;
}

export interface CharDataType extends StringDataType {
  options: CharDataTypeOptions;
}

export interface CharDataTypeOptions extends StringDataTypeOptions {}

export type TextLength = 'tiny' | 'medium' | 'long';

/**
 * An (un)limited length text column. Available lengths: `tiny`, `medium`, `long`
 */
export const TEXT: TextDataTypeConstructor;

interface TextDataTypeConstructor extends AbstractDataTypeConstructor {
  new (length?: TextLength): TextDataType;
  (options?: TextDataTypeOptions): TextDataType;
}

export interface TextDataType extends AbstractDataType {
  options: TextDataTypeOptions;
  validate(value: unknown): boolean;
}

export interface TextDataTypeOptions {
  length?: TextLength;
}

export const NUMBER: NumberDataTypeConstructor;

interface NumberDataTypeConstructor extends AbstractDataTypeConstructor {
  options: NumberDataTypeOptions;
  UNSIGNED: this;
  ZEROFILL: this;
  new (options?: NumberDataTypeOptions): NumberDataType;
  (options?: NumberDataTypeOptions): NumberDataType;
  validate(value: unknown): boolean;
}

export interface NumberDataType extends AbstractDataType {
  options: NumberDataTypeOptions;
  UNSIGNED: this;
  ZEROFILL: this;
  validate(value: unknown): boolean;
}

export interface IntegerDataTypeOptions {
  length?: number;
  zerofill?: boolean;
  unsigned?: boolean;
}
export interface NumberDataTypeOptions extends IntegerDataTypeOptions {
  decimals?: number;
  precision?: number;
  scale?: number;
}

/**
 * A 8 bit integer.
 */
export const TINYINT: TinyIntegerDataTypeConstructor;

interface TinyIntegerDataTypeConstructor extends NumberDataTypeConstructor {
  new (options?: IntegerDataTypeOptions): TinyIntegerDataType;
  (options?: IntegerDataTypeOptions): TinyIntegerDataType;
}

export interface TinyIntegerDataType extends NumberDataType {
  options: IntegerDataTypeOptions;
}

/**
 * A 16 bit integer.
 */
export const SMALLINT: SmallIntegerDataTypeConstructor;

interface SmallIntegerDataTypeConstructor extends NumberDataTypeConstructor {
  new (options?: IntegerDataTypeOptions): SmallIntegerDataType;
  (options?: IntegerDataTypeOptions): SmallIntegerDataType;
}

export interface SmallIntegerDataType extends NumberDataType {
  options: IntegerDataTypeOptions;
}

/**
 * A 24 bit integer.
 */
export const MEDIUMINT: MediumIntegerDataTypeConstructor;

interface MediumIntegerDataTypeConstructor extends NumberDataTypeConstructor {
  new (options?: IntegerDataTypeOptions): MediumIntegerDataType;
  (options?: IntegerDataTypeOptions): MediumIntegerDataType;
}

export interface MediumIntegerDataType extends NumberDataType {
  options: IntegerDataTypeOptions;
}

/**
 * A 32 bit integer.
 */
export const INTEGER: IntegerDataTypeConstructor;

interface IntegerDataTypeConstructor extends NumberDataTypeConstructor {
  new (options?: NumberDataTypeOptions): IntegerDataType;
  (options?: NumberDataTypeOptions): IntegerDataType;
}

export interface IntegerDataType extends NumberDataType {
  options: NumberDataTypeOptions;
}

/**
 * A 64 bit integer.
 *
 * Available properties: `UNSIGNED`, `ZEROFILL`
 *
 */
export const BIGINT: BigIntDataTypeConstructor;

interface BigIntDataTypeConstructor extends NumberDataTypeConstructor {
  new (options?: IntegerDataTypeOptions): BigIntDataType;
  (options?: IntegerDataTypeOptions): BigIntDataType;
}

export interface BigIntDataType extends NumberDataType {
  options: IntegerDataTypeOptions;
}

/**
 * Floating point number (4-byte precision). Accepts one or two arguments for precision
 */
export const FLOAT: FloatDataTypeConstructor;

interface FloatDataTypeConstructor extends NumberDataTypeConstructor {
  new (length?: number, decimals?: number): FloatDataType;
  new (options?: FloatDataTypeOptions): FloatDataType;
  (length?: number, decimals?: number): FloatDataType;
  (options?: FloatDataTypeOptions): FloatDataType;
}

export interface FloatDataType extends NumberDataType {
  options: FloatDataTypeOptions;
}

export interface FloatDataTypeOptions {
  length?: number;
  decimals?: number;
}

/**
 * Floating point number (4-byte precision). Accepts one or two arguments for precision
 */
export const REAL: RealDataTypeConstructor;

interface RealDataTypeConstructor extends NumberDataTypeConstructor {
  new (length?: number, decimals?: number): RealDataType;
  new (options?: RealDataTypeOptions): RealDataType;
  (length?: number, decimals?: number): RealDataType;
  (options?: RealDataTypeOptions): RealDataType;
}

export interface RealDataType extends NumberDataType {
  options: RealDataTypeOptions;
}

export interface RealDataTypeOptions {
  length?: number;
  decimals?: number;
}

/**
 * Floating point number (8-byte precision). Accepts one or two arguments for precision
 */
export const DOUBLE: DoubleDataTypeConstructor;

interface DoubleDataTypeConstructor extends NumberDataTypeConstructor {
  new (length?: number, decimals?: number): DoubleDataType;
  new (options?: DoubleDataTypeOptions): DoubleDataType;
  (length?: number, decimals?: number): DoubleDataType;
  (options?: DoubleDataTypeOptions): DoubleDataType;
}

export interface DoubleDataType extends NumberDataType {
  options: DoubleDataTypeOptions;
}

export interface DoubleDataTypeOptions {
  length?: number;
  decimals?: number;
}

/**
 * Decimal number. Accepts one or two arguments for precision
 */
export const DECIMAL: DecimalDataTypeConstructor;

interface DecimalDataTypeConstructor extends NumberDataTypeConstructor {
  PRECISION: this;
  SCALE: this;
  new (precision?: number, scale?: number): DecimalDataType;
  new (options?: DecimalDataTypeOptions): DecimalDataType;
  (precision?: number, scale?: number): DecimalDataType;
  (options?: DecimalDataTypeOptions): DecimalDataType;
}

export interface DecimalDataType extends NumberDataType {
  options: DecimalDataTypeOptions;
}

export interface DecimalDataTypeOptions {
  precision?: number;
  scale?: number;
}

/**
 * A boolean / tinyint column, depending on dialect
 */
export const BOOLEAN: AbstractDataTypeConstructor;

/**
 * A time column
 */
export const TIME: AbstractDataTypeConstructor;

/**
 * A datetime column
 */
export const DATE: DateDataTypeConstructor;

interface DateDataTypeConstructor extends AbstractDataTypeConstructor {
  new (length?: string | number): DateDataType;
  new (options?: DateDataTypeOptions): DateDataType;
  (length?: string | number): DateDataType;
  (options?: DateDataTypeOptions): DateDataType;
}

export interface DateDataType extends AbstractDataTypeConstructor {
  options: DateDataTypeOptions;
}

export interface DateDataTypeOptions {
  length?: string | number;
}

/**
 * A date only column
 */
export const DATEONLY: DateOnlyDataTypeConstructor;

interface DateOnlyDataTypeConstructor extends AbstractDataTypeConstructor {
  new (): DateOnlyDataType;
  (): DateOnlyDataType;
}

export interface DateOnlyDataType extends AbstractDataType {
}

/**
 * A key / value column. Only available in postgres.
 */
export const HSTORE: AbstractDataTypeConstructor;

/**
 * A JSON string column. Only available in postgres.
 */
export const JSON: AbstractDataTypeConstructor;

/**
 * A pre-processed JSON data column. Only available in postgres.
 */
export const JSONB: AbstractDataTypeConstructor;

/**
 * A default value of the current timestamp
 */
export const NOW: AbstractDataTypeConstructor;

/**
 * Binary storage. Available lengths: `tiny`, `medium`, `long`
 */
export const BLOB: BlobDataTypeConstructor;

export type BlobSize = 'tiny' | 'medium' | 'long';

interface BlobDataTypeConstructor extends AbstractDataTypeConstructor {
  new (length?: BlobSize): BlobDataType;
  new (options?: BlobDataTypeOptions): BlobDataType;
  (length?: BlobSize): BlobDataType;
  (options?: BlobDataTypeOptions): BlobDataType;
}

export interface BlobDataType extends AbstractDataType {
  options: BlobDataTypeOptions;
  escape: boolean;
}

export interface BlobDataTypeOptions {
  length?: BlobSize;
  escape?: boolean;
}

/**
 * Range types are data types representing a range of values of some element type (called the range's subtype).
 * Only available in postgres.
 *
 * See [Postgres documentation](http://www.postgresql.org/docs/9.4/static/rangetypes.html) for more details
 */
export const RANGE: RangeDataTypeConstructor;

export type RangeableDataType =
  | IntegerDataTypeConstructor
  | IntegerDataType
  | BigIntDataTypeConstructor
  | BigIntDataType
  | DecimalDataTypeConstructor
  | DecimalDataType
  | DateOnlyDataTypeConstructor
  | DateOnlyDataType
  | DateDataTypeConstructor
  | DateDataType;

interface RangeDataTypeConstructor extends AbstractDataTypeConstructor {
  new <T extends RangeableDataType>(subtype?: T): RangeDataType<T>;
  new <T extends RangeableDataType>(options: RangeDataTypeOptions<T>): RangeDataType<T>;
  <T extends RangeableDataType>(subtype?: T): RangeDataType<T>;
  <T extends RangeableDataType>(options: RangeDataTypeOptions<T>): RangeDataType<T>;
}

export interface RangeDataType<T extends RangeableDataType> extends AbstractDataType {
  options: RangeDataTypeOptions<T>;
}

export interface RangeDataTypeOptions<T extends RangeableDataType> {
  subtype?: T;
}

/**
 * A column storing a unique universal identifier. Use with `UUIDV1` or `UUIDV4` for default values.
 */
export const UUID: AbstractDataTypeConstructor;

/**
 * A default unique universal identifier generated following the UUID v1 standard
 */
export const UUIDV1: AbstractDataTypeConstructor;

/**
 * A default unique universal identifier generated following the UUID v4 standard
 */
export const UUIDV4: AbstractDataTypeConstructor;

/**
 * A virtual value that is not stored in the DB. This could for example be useful if you want to provide a default value in your model that is returned to the user but not stored in the DB.
 *
 * You could also use it to validate a value before permuting and storing it. Checking password length before hashing it for example:
 * ```js
 * class User extends Model {}
 * User.init({
 *   password_hash: DataTypes.STRING,
 *   password: {
 *    type: DataTypes.VIRTUAL,
 *    set (val) {
 *      this.setDataValue('password', val); // Remember to set the data value, otherwise it won't be validated
 *      this.setDataValue('password_hash', this.salt + val);
 *    },
 *    validate: {
 *      isLongEnough (val) {
 *        if (val.length < 7) {
 *          throw new Error("Please choose a longer password")
 *        }
 *      }
 *    }
 *   }
 * }, { sequelize });
 * ```
 *
 * VIRTUAL also takes a return type and dependency fields as arguments
 * If a virtual attribute is present in `attributes` it will automatically pull in the extra fields as well.
 * Return type is mostly useful for setups that rely on types like GraphQL.
 * ```js
 * {
 *   active: {
 *   type: new DataTypes.VIRTUAL(DataTypes.BOOLEAN, ['createdAt']),
 *   get() {
 *     return this.get('createdAt') > Date.now() - (7 * 24 * 60 * 60 * 1000)
 *   }
 *   }
 * }
 * ```
 *
 * In the above code the password is stored plainly in the password field so it can be validated, but is never stored in the DB.
 */
export const VIRTUAL: VirtualDataTypeConstructor;

interface VirtualDataTypeConstructor extends AbstractDataTypeConstructor {
  new <T extends AbstractDataTypeConstructor | AbstractDataType>(ReturnType: T, fields?: string[]): VirtualDataType<
    T
  >;
  <T extends AbstractDataTypeConstructor | AbstractDataType>(ReturnType: T, fields?: string[]): VirtualDataType<T>;
}

export interface VirtualDataType<T extends AbstractDataTypeConstructor | AbstractDataType> extends AbstractDataType {
  returnType: T;
  fields: string[];
}

/**
 * An enumeration. `DataTypes.ENUM('value', 'another value')`.
 */
export const ENUM: EnumDataTypeConstructor;

interface EnumDataTypeConstructor extends AbstractDataTypeConstructor {
  new <T extends string>(...values: T[]): EnumDataType<T>;
  new <T extends string>(options: EnumDataTypeOptions<T>): EnumDataType<T>;
  <T extends string>(...values: T[]): EnumDataType<T>;
  <T extends string>(options: EnumDataTypeOptions<T>): EnumDataType<T>;
}

export interface EnumDataType<T extends string> extends AbstractDataType {
  values: T[];
  options: EnumDataTypeOptions<T>;
}

export interface EnumDataTypeOptions<T extends string> {
  values: T[];
}

/**
 * An array of `type`, e.g. `DataTypes.ARRAY(DataTypes.DECIMAL)`. Only available in postgres.
 */
export const ARRAY: ArrayDataTypeConstructor;

interface ArrayDataTypeConstructor extends AbstractDataTypeConstructor {
  new <T extends AbstractDataTypeConstructor | AbstractDataType>(type: T): ArrayDataType<T>;
  new <T extends AbstractDataTypeConstructor | AbstractDataType>(options: ArrayDataTypeOptions<T>): ArrayDataType<T>;
  <T extends AbstractDataTypeConstructor | AbstractDataType>(type: T): ArrayDataType<T>;
  <T extends AbstractDataTypeConstructor | AbstractDataType>(options: ArrayDataTypeOptions<T>): ArrayDataType<T>;
  is<T extends AbstractDataTypeConstructor | AbstractDataType>(obj: unknown, type: T): obj is ArrayDataType<T>;
}

export interface ArrayDataType<T extends AbstractDataTypeConstructor | AbstractDataType> extends AbstractDataType {
  options: ArrayDataTypeOptions<T>;
}

export interface ArrayDataTypeOptions<T extends AbstractDataTypeConstructor | AbstractDataType> {
  type: T;
}

/**
 * A geometry datatype represents two dimensional spacial objects.
 */
export const GEOMETRY: GeometryDataTypeConstructor;

interface GeometryDataTypeConstructor extends AbstractDataTypeConstructor {
  new (type: string, srid?: number): GeometryDataType;
  new (options: GeometryDataTypeOptions): GeometryDataType;
  (type: string, srid?: number): GeometryDataType;
  (options: GeometryDataTypeOptions): GeometryDataType;
}

export interface GeometryDataType extends AbstractDataType {
  options: GeometryDataTypeOptions;
  type: string;
  srid?: number;
  escape: boolean;
}

export interface GeometryDataTypeOptions {
  type: string;
  srid?: number;
}

/**
 * A geography datatype represents two dimensional spacial objects in an elliptic coord system.
 */
export const GEOGRAPHY: GeographyDataTypeConstructor;

interface GeographyDataTypeConstructor extends AbstractDataTypeConstructor {
  new (type: string, srid?: number): GeographyDataType;
  new (options: GeographyDataTypeOptions): GeographyDataType;
  (type: string, srid?: number): GeographyDataType;
  (options: GeographyDataTypeOptions): GeographyDataType;
}

export interface GeographyDataType extends AbstractDataType {
  options: GeographyDataTypeOptions;
  type: string;
  srid?: number;
  escape: boolean;
}

export interface GeographyDataTypeOptions {
  type: string;
  srid?: number;
}

export const CIDR: AbstractDataTypeConstructor;

export const INET: AbstractDataTypeConstructor;

export const MACADDR: AbstractDataTypeConstructor;

/**
 * Case incenstive text
 */
export const CITEXT: AbstractDataTypeConstructor;

// umzug compatibility
export type DataTypeAbstract = AbstractDataTypeConstructor;


mariadb: " LIMIT '\\';DELETE FROM user', 10",

mysql: " LIMIT '\\';DELETE FROM user', 10",

mssql: " OFFSET N''';DELETE FROM user' ROWS FETCH NEXT 10 ROWS ONLY"

order: [], // When the order is an empty array, one is automagically prepended

model: { primaryKeyField: 'id', name: 'tableRef' }

mssql: ' ORDER BY [tableRef].[id] OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY'

'use strict';

const _ = require('lodash');
const Utils = require('../../utils');
const DataTypes = require('../../data-types');
const TableHints = require('../../table-hints');
const AbstractQueryGenerator = require('../abstract/query-generator');
const randomBytes = require('crypto').randomBytes;
const semver = require('semver');
const Op = require('../../operators');

/* istanbul ignore next */
const throwMethodUndefined = function(methodName) {
  throw new Error(`The method "${methodName}" is not defined! Please add it to your sql dialect.`);
};

class MSSQLQueryGenerator extends AbstractQueryGenerator {
  createDatabaseQuery(databaseName, options) {
    options = { collate: null, ...options };

    const collation = options.collate ? `COLLATE ${this.escape(options.collate)}` : '';

    return [
      'IF NOT EXISTS (SELECT * FROM sys.databases WHERE name =', wrapSingleQuote(databaseName), ')',
      'BEGIN',
      'CREATE DATABASE', this.quoteIdentifier(databaseName),
      `${collation};`,
      'END;'
    ].join(' ');
  }

  dropDatabaseQuery(databaseName) {
    return [
      'IF EXISTS (SELECT * FROM sys.databases WHERE name =', wrapSingleQuote(databaseName), ')',
      'BEGIN',
      'DROP DATABASE', this.quoteIdentifier(databaseName), ';',
      'END;'
    ].join(' ');
  }

  createSchema(schema) {
    return [
      'IF NOT EXISTS (SELECT schema_name',
      'FROM information_schema.schemata',
      'WHERE schema_name =', wrapSingleQuote(schema), ')',
      'BEGIN',
      "EXEC sp_executesql N'CREATE SCHEMA",
      this.quoteIdentifier(schema),
      ";'",
      'END;'
    ].join(' ');
  }

  dropSchema(schema) {
    // Mimics Postgres CASCADE, will drop objects belonging to the schema
    const quotedSchema = wrapSingleQuote(schema);
    return [
      'IF EXISTS (SELECT schema_name',
      'FROM information_schema.schemata',
      'WHERE schema_name =', quotedSchema, ')',
      'BEGIN',
      'DECLARE @id INT, @ms_sql NVARCHAR(2000);',
      'DECLARE @cascade TABLE (',
      'id INT NOT NULL IDENTITY PRIMARY KEY,',
      'ms_sql NVARCHAR(2000) NOT NULL );',
      'INSERT INTO @cascade ( ms_sql )',
      "SELECT CASE WHEN o.type IN ('F','PK')",
      "THEN N'ALTER TABLE ['+ s.name + N'].[' + p.name + N'] DROP CONSTRAINT [' + o.name + N']'",
      "ELSE N'DROP TABLE ['+ s.name + N'].[' + o.name + N']' END",
      'FROM sys.objects o',
      'JOIN sys.schemas s on o.schema_id = s.schema_id',
      'LEFT OUTER JOIN sys.objects p on o.parent_object_id = p.object_id',
      "WHERE o.type IN ('F', 'PK', 'U') AND s.name = ", quotedSchema,
      'ORDER BY o.type ASC;',
      'SELECT TOP 1 @id = id, @ms_sql = ms_sql FROM @cascade ORDER BY id;',
      'WHILE @id IS NOT NULL',
      'BEGIN',
      'BEGIN TRY EXEC sp_executesql @ms_sql; END TRY',
      'BEGIN CATCH BREAK; THROW; END CATCH;',
      'DELETE FROM @cascade WHERE id = @id;',
      'SELECT @id = NULL, @ms_sql = NULL;',
      'SELECT TOP 1 @id = id, @ms_sql = ms_sql FROM @cascade ORDER BY id;',
      'END',
      "EXEC sp_executesql N'DROP SCHEMA", this.quoteIdentifier(schema), ";'",
      'END;'
    ].join(' ');
  }

  showSchemasQuery() {
    return [
      'SELECT "name" as "schema_name" FROM sys.schemas as s',
      'WHERE "s"."name" NOT IN (',
      "'INFORMATION_SCHEMA', 'dbo', 'guest', 'sys', 'archive'",
      ')', 'AND', '"s"."name" NOT LIKE', "'db_%'"
    ].join(' ');
  }

  versionQuery() {
    // Uses string manipulation to convert the MS Maj.Min.Patch.Build to semver Maj.Min.Patch
    return [
      'DECLARE @ms_ver NVARCHAR(20);',
      "SET @ms_ver = REVERSE(CONVERT(NVARCHAR(20), SERVERPROPERTY('ProductVersion')));",
      "SELECT REVERSE(SUBSTRING(@ms_ver, CHARINDEX('.', @ms_ver)+1, 20)) AS 'version'"
    ].join(' ');
  }

  createTableQuery(tableName, attributes, options) {
    const primaryKeys = [],
      foreignKeys = {},
      attributesClauseParts = [];

    let commentStr = '';

    for (const attr in attributes) {
      if (Object.prototype.hasOwnProperty.call(attributes, attr)) {
        let dataType = attributes[attr];
        let match;

        if (dataType.includes('COMMENT ')) {
          const commentMatch = dataType.match(/^(.+) (COMMENT.*)$/);
          const commentText = commentMatch[2].replace('COMMENT', '').trim();
          commentStr += this.commentTemplate(commentText, tableName, attr);
          // remove comment related substring from dataType
          dataType = commentMatch[1];
        }

        if (dataType.includes('PRIMARY KEY')) {
          primaryKeys.push(attr);

          if (dataType.includes('REFERENCES')) {
            // MSSQL doesn't support inline REFERENCES declarations: move to the end
            match = dataType.match(/^(.+) (REFERENCES.*)$/);
            attributesClauseParts.push(`${this.quoteIdentifier(attr)} ${match[1].replace('PRIMARY KEY', '')}`);
            foreignKeys[attr] = match[2];
          } else {
            attributesClauseParts.push(`${this.quoteIdentifier(attr)} ${dataType.replace('PRIMARY KEY', '')}`);
          }
        } else if (dataType.includes('REFERENCES')) {
          // MSSQL doesn't support inline REFERENCES declarations: move to the end
          match = dataType.match(/^(.+) (REFERENCES.*)$/);
          attributesClauseParts.push(`${this.quoteIdentifier(attr)} ${match[1]}`);
          foreignKeys[attr] = match[2];
        } else {
          attributesClauseParts.push(`${this.quoteIdentifier(attr)} ${dataType}`);
        }
      }
    }

    const pkString = primaryKeys.map(pk => this.quoteIdentifier(pk)).join(', ');

    if (options.uniqueKeys) {
      _.each(options.uniqueKeys, (columns, indexName) => {
        if (columns.customIndex) {
          if (typeof indexName !== 'string') {
            indexName = `uniq_${tableName}_${columns.fields.join('_')}`;
          }
          attributesClauseParts.push(`CONSTRAINT ${
            this.quoteIdentifier(indexName)
          } UNIQUE (${
            columns.fields.map(field => this.quoteIdentifier(field)).join(', ')
          })`);
        }
      });
    }

    if (pkString.length > 0) {
      attributesClauseParts.push(`PRIMARY KEY (${pkString})`);
    }

    for (const fkey in foreignKeys) {
      if (Object.prototype.hasOwnProperty.call(foreignKeys, fkey)) {
        attributesClauseParts.push(`FOREIGN KEY (${this.quoteIdentifier(fkey)}) ${foreignKeys[fkey]}`);
      }
    }

    const quotedTableName = this.quoteTable(tableName);

    return Utils.joinSQLFragments([
      `IF OBJECT_ID('${quotedTableName}', 'U') IS NULL`,
      `CREATE TABLE ${quotedTableName} (${attributesClauseParts.join(', ')})`,
      ';',
      commentStr
    ]);
  }

  describeTableQuery(tableName, schema) {
    let sql = [
      'SELECT',
      "c.COLUMN_NAME AS 'Name',",
      "c.DATA_TYPE AS 'Type',",
      "c.CHARACTER_MAXIMUM_LENGTH AS 'Length',",
      "c.IS_NULLABLE as 'IsNull',",
      "COLUMN_DEFAULT AS 'Default',",
      "pk.CONSTRAINT_TYPE AS 'Constraint',",
      "COLUMNPROPERTY(OBJECT_ID(c.TABLE_SCHEMA+'.'+c.TABLE_NAME), c.COLUMN_NAME, 'IsIdentity') as 'IsIdentity',",
      "CAST(prop.value AS NVARCHAR) AS 'Comment'",
      'FROM',
      'INFORMATION_SCHEMA.TABLES t',
      'INNER JOIN',
      'INFORMATION_SCHEMA.COLUMNS c ON t.TABLE_NAME = c.TABLE_NAME AND t.TABLE_SCHEMA = c.TABLE_SCHEMA',
      'LEFT JOIN (SELECT tc.table_schema, tc.table_name, ',
      'cu.column_name, tc.CONSTRAINT_TYPE ',
      'FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc ',
      'JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE  cu ',
      'ON tc.table_schema=cu.table_schema and tc.table_name=cu.table_name ',
      'and tc.constraint_name=cu.constraint_name ',
      'and tc.CONSTRAINT_TYPE=\'PRIMARY KEY\') pk ',
      'ON pk.table_schema=c.table_schema ',
      'AND pk.table_name=c.table_name ',
      'AND pk.column_name=c.column_name ',
      'INNER JOIN sys.columns AS sc',
      "ON sc.object_id = object_id(t.table_schema + '.' + t.table_name) AND sc.name = c.column_name",
      'LEFT JOIN sys.extended_properties prop ON prop.major_id = sc.object_id',
      'AND prop.minor_id = sc.column_id',
      "AND prop.name = 'MS_Description'",
      'WHERE t.TABLE_NAME =', wrapSingleQuote(tableName)
    ].join(' ');

    if (schema) {
      sql += `AND t.TABLE_SCHEMA =${wrapSingleQuote(schema)}`;
    }

    return sql;
  }

  renameTableQuery(before, after) {
    return `EXEC sp_rename ${this.quoteTable(before)}, ${this.quoteTable(after)};`;
  }

  showTablesQuery() {
    return "SELECT TABLE_NAME, TABLE_SCHEMA FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE';";
  }

  dropTableQuery(tableName) {
    const quoteTbl = this.quoteTable(tableName);
    return Utils.joinSQLFragments([
      `IF OBJECT_ID('${quoteTbl}', 'U') IS NOT NULL`,
      'DROP TABLE',
      quoteTbl,
      ';'
    ]);
  }

  addColumnQuery(table, key, dataType) {
    // FIXME: attributeToSQL SHOULD be using attributes in addColumnQuery
    //        but instead we need to pass the key along as the field here
    dataType.field = key;
    let commentStr = '';

    if (dataType.comment && _.isString(dataType.comment)) {
      commentStr = this.commentTemplate(dataType.comment, table, key);
      // attributeToSQL will try to include `COMMENT 'Comment Text'` when it returns if the comment key
      // is present. This is needed for createTable statement where that part is extracted with regex.
      // Here we can intercept the object and remove comment property since we have the original object.
      delete dataType['comment'];
    }

    return Utils.joinSQLFragments([
      'ALTER TABLE',
      this.quoteTable(table),
      'ADD',
      this.quoteIdentifier(key),
      this.attributeToSQL(dataType, { context: 'addColumn' }),
      ';',
      commentStr
    ]);
  }

  commentTemplate(comment, table, column) {
    return ' EXEC sp_addextendedproperty ' +
        `@name = N'MS_Description', @value = ${this.escape(comment)}, ` +
        '@level0type = N\'Schema\', @level0name = \'dbo\', ' +
        `@level1type = N'Table', @level1name = ${this.quoteIdentifier(table)}, ` +
        `@level2type = N'Column', @level2name = ${this.quoteIdentifier(column)};`;
  }

  removeColumnQuery(tableName, attributeName) {
    return Utils.joinSQLFragments([
      'ALTER TABLE',
      this.quoteTable(tableName),
      'DROP COLUMN',
      this.quoteIdentifier(attributeName),
      ';'
    ]);
  }

  changeColumnQuery(tableName, attributes) {
    const attrString = [],
      constraintString = [];
    let commentString = '';

    for (const attributeName in attributes) {
      const quotedAttrName = this.quoteIdentifier(attributeName);
      let definition = attributes[attributeName];
      if (definition.includes('COMMENT ')) {
        const commentMatch = definition.match(/^(.+) (COMMENT.*)$/);
        const commentText = commentMatch[2].replace('COMMENT', '').trim();
        commentString += this.commentTemplate(commentText, tableName, attributeName);
        // remove comment related substring from dataType
        definition = commentMatch[1];
      }
      if (definition.includes('REFERENCES')) {
        constraintString.push(`FOREIGN KEY (${quotedAttrName}) ${definition.replace(/.+?(?=REFERENCES)/, '')}`);
      } else {
        attrString.push(`${quotedAttrName} ${definition}`);
      }
    }

    return Utils.joinSQLFragments([
      'ALTER TABLE',
      this.quoteTable(tableName),
      attrString.length && `ALTER COLUMN ${attrString.join(', ')}`,
      constraintString.length && `ADD ${constraintString.join(', ')}`,
      ';',
      commentString
    ]);
  }

  renameColumnQuery(tableName, attrBefore, attributes) {
    const newName = Object.keys(attributes)[0];
    return Utils.joinSQLFragments([
      'EXEC sp_rename',
      `'${this.quoteTable(tableName)}.${attrBefore}',`,
      `'${newName}',`,
      "'COLUMN'",
      ';'
    ]);
  }

  bulkInsertQuery(tableName, attrValueHashes, options, attributes) {
    const quotedTable = this.quoteTable(tableName);
    options = options || {};
    attributes = attributes || {};

    const tuples = [];
    const allAttributes = [];
    const allQueries = [];

    let needIdentityInsertWrapper = false,
      outputFragment = '';

    if (options.returning) {
      const returnValues = this.generateReturnValues(attributes, options);

      outputFragment = returnValues.outputFragment;
    }

    const emptyQuery = `INSERT INTO ${quotedTable}${outputFragment} DEFAULT VALUES`;

    attrValueHashes.forEach(attrValueHash => {
      // special case for empty objects with primary keys
      const fields = Object.keys(attrValueHash);
      const firstAttr = attributes[fields[0]];
      if (fields.length === 1 && firstAttr && firstAttr.autoIncrement && attrValueHash[fields[0]] === null) {
        allQueries.push(emptyQuery);
        return;
      }

      // normal case
      _.forOwn(attrValueHash, (value, key) => {
        if (value !== null && attributes[key] && attributes[key].autoIncrement) {
          needIdentityInsertWrapper = true;
        }

        if (!allAttributes.includes(key)) {
          if (value === null && attributes[key] && attributes[key].autoIncrement)
            return;

          allAttributes.push(key);
        }
      });
    });

    if (allAttributes.length > 0) {
      attrValueHashes.forEach(attrValueHash => {
        tuples.push(`(${
          allAttributes.map(key =>
            this.escape(attrValueHash[key])).join(',')
        })`);
      });

      const quotedAttributes = allAttributes.map(attr => this.quoteIdentifier(attr)).join(',');
      allQueries.push(tupleStr => `INSERT INTO ${quotedTable} (${quotedAttributes})${outputFragment} VALUES ${tupleStr};`);
    }
    const commands = [];
    let offset = 0;
    const batch = Math.floor(250 / (allAttributes.length + 1)) + 1;
    while (offset < Math.max(tuples.length, 1)) {
      const tupleStr = tuples.slice(offset, Math.min(tuples.length, offset + batch));
      let generatedQuery = allQueries.map(v => typeof v === 'string' ? v : v(tupleStr)).join(';');
      if (needIdentityInsertWrapper) {
        generatedQuery = `SET IDENTITY_INSERT ${quotedTable} ON; ${generatedQuery}; SET IDENTITY_INSERT ${quotedTable} OFF;`;
      }
      commands.push(generatedQuery);
      offset += batch;
    }
    return commands.join(';');
  }

  updateQuery(tableName, attrValueHash, where, options, attributes) {
    const sql = super.updateQuery(tableName, attrValueHash, where, options, attributes);
    if (options.limit) {
      const updateArgs = `UPDATE TOP(${this.escape(options.limit)})`;
      sql.query = sql.query.replace('UPDATE', updateArgs);
    }
    return sql;
  }

  upsertQuery(tableName, insertValues, updateValues, where, model) {
    const targetTableAlias = this.quoteTable(`${tableName}_target`);
    const sourceTableAlias = this.quoteTable(`${tableName}_source`);
    const primaryKeysAttrs = [];
    const identityAttrs = [];
    const uniqueAttrs = [];
    const tableNameQuoted = this.quoteTable(tableName);
    let needIdentityInsertWrapper = false;

    //Obtain primaryKeys, uniquekeys and identity attrs from rawAttributes as model is not passed
    for (const key in model.rawAttributes) {
      if (model.rawAttributes[key].primaryKey) {
        primaryKeysAttrs.push(model.rawAttributes[key].field || key);
      }
      if (model.rawAttributes[key].unique) {
        uniqueAttrs.push(model.rawAttributes[key].field || key);
      }
      if (model.rawAttributes[key].autoIncrement) {
        identityAttrs.push(model.rawAttributes[key].field || key);
      }
    }

    //Add unique indexes defined by indexes option to uniqueAttrs
    for (const index of model._indexes) {
      if (index.unique && index.fields) {
        for (const field of index.fields) {
          const fieldName = typeof field === 'string' ? field : field.name || field.attribute;
          if (!uniqueAttrs.includes(fieldName) && model.rawAttributes[fieldName]) {
            uniqueAttrs.push(fieldName);
          }
        }
      }
    }

    const updateKeys = Object.keys(updateValues);
    const insertKeys = Object.keys(insertValues);
    const insertKeysQuoted = insertKeys.map(key => this.quoteIdentifier(key)).join(', ');
    const insertValuesEscaped = insertKeys.map(key => this.escape(insertValues[key])).join(', ');
    const sourceTableQuery = `VALUES(${insertValuesEscaped})`; //Virtual Table
    let joinCondition;

    //IDENTITY_INSERT Condition
    identityAttrs.forEach(key => {
      if (updateValues[key] && updateValues[key] !== null) {
        needIdentityInsertWrapper = true;
        /*
         * IDENTITY_INSERT Column Cannot be updated, only inserted
         * http://stackoverflow.com/a/30176254/2254360
         */
      }
    });

    //Filter NULL Clauses
    const clauses = where[Op.or].filter(clause => {
      let valid = true;
      /*
       * Exclude NULL Composite PK/UK. Partial Composite clauses should also be excluded as it doesn't guarantee a single row
       */
      for (const key in clause) {
        if (!clause[key]) {
          valid = false;
          break;
        }
      }
      return valid;
    });

    /*
     * Generate ON condition using PK(s).
     * If not, generate using UK(s). Else throw error
     */
    const getJoinSnippet = array => {
      return array.map(key => {
        key = this.quoteIdentifier(key);
        return `${targetTableAlias}.${key} = ${sourceTableAlias}.${key}`;
      });
    };

    if (clauses.length === 0) {
      throw new Error('Primary Key or Unique key should be passed to upsert query');
    } else {
      // Search for primary key attribute in clauses -- Model can have two separate unique keys
      for (const key in clauses) {
        const keys = Object.keys(clauses[key]);
        if (primaryKeysAttrs.includes(keys[0])) {
          joinCondition = getJoinSnippet(primaryKeysAttrs).join(' AND ');
          break;
        }
      }
      if (!joinCondition) {
        joinCondition = getJoinSnippet(uniqueAttrs).join(' AND ');
      }
    }

    // Remove the IDENTITY_INSERT Column from update
    const updateSnippet = updateKeys.filter(key => !identityAttrs.includes(key))
      .map(key => {
        const value = this.escape(updateValues[key]);
        key = this.quoteIdentifier(key);
        return `${targetTableAlias}.${key} = ${value}`;
      }).join(', ');

    const insertSnippet = `(${insertKeysQuoted}) VALUES(${insertValuesEscaped})`;
    let query = `MERGE INTO ${tableNameQuoted} WITH(HOLDLOCK) AS ${targetTableAlias} USING (${sourceTableQuery}) AS ${sourceTableAlias}(${insertKeysQuoted}) ON ${joinCondition}`;
    query += ` WHEN MATCHED THEN UPDATE SET ${updateSnippet} WHEN NOT MATCHED THEN INSERT ${insertSnippet} OUTPUT $action, INSERTED.*;`;
    if (needIdentityInsertWrapper) {
      query = `SET IDENTITY_INSERT ${tableNameQuoted} ON; ${query} SET IDENTITY_INSERT ${tableNameQuoted} OFF;`;
    }
    return query;
  }

  truncateTableQuery(tableName) {
    return `TRUNCATE TABLE ${this.quoteTable(tableName)}`;
  }

  deleteQuery(tableName, where, options = {}, model) {
    const table = this.quoteTable(tableName);
    const whereClause = this.getWhereConditions(where, null, model, options);

    return Utils.joinSQLFragments([
      'DELETE',
      options.limit && `TOP(${this.escape(options.limit)})`,
      'FROM',
      table,
      whereClause && `WHERE ${whereClause}`,
      ';',
      'SELECT @@ROWCOUNT AS AFFECTEDROWS',
      ';'
    ]);
  }

  showIndexesQuery(tableName) {
    return `EXEC sys.sp_helpindex @objname = N'${this.quoteTable(tableName)}';`;
  }

  showConstraintsQuery(tableName) {
    return `EXEC sp_helpconstraint @objname = ${this.escape(this.quoteTable(tableName))};`;
  }

  removeIndexQuery(tableName, indexNameOrAttributes) {
    let indexName = indexNameOrAttributes;

    if (typeof indexName !== 'string') {
      indexName = Utils.underscore(`${tableName}_${indexNameOrAttributes.join('_')}`);
    }

    return `DROP INDEX ${this.quoteIdentifiers(indexName)} ON ${this.quoteIdentifiers(tableName)}`;
  }

  attributeToSQL(attribute) {
    if (!_.isPlainObject(attribute)) {
      attribute = {
        type: attribute
      };
    }

    // handle self referential constraints
    if (attribute.references) {

      if (attribute.Model && attribute.Model.tableName === attribute.references.model) {
        this.sequelize.log('MSSQL does not support self referencial constraints, '
          + 'we will remove it but we recommend restructuring your query');
        attribute.onDelete = '';
        attribute.onUpdate = '';
      }
    }

    let template;

    if (attribute.type instanceof DataTypes.ENUM) {
      if (attribute.type.values && !attribute.values) attribute.values = attribute.type.values;

      // enums are a special case
      template = attribute.type.toSql();
      template += ` CHECK (${this.quoteIdentifier(attribute.field)} IN(${attribute.values.map(value => {
        return this.escape(value);
      }).join(', ') }))`;
      return template;
    }
    template = attribute.type.toString();

    if (attribute.allowNull === false) {
      template += ' NOT NULL';
    } else if (!attribute.primaryKey && !Utils.defaultValueSchemable(attribute.defaultValue)) {
      template += ' NULL';
    }

    if (attribute.autoIncrement) {
      template += ' IDENTITY(1,1)';
    }

    // Blobs/texts cannot have a defaultValue
    if (attribute.type !== 'TEXT' && attribute.type._binary !== true &&
        Utils.defaultValueSchemable(attribute.defaultValue)) {
      template += ` DEFAULT ${this.escape(attribute.defaultValue)}`;
    }

    if (attribute.unique === true) {
      template += ' UNIQUE';
    }

    if (attribute.primaryKey) {
      template += ' PRIMARY KEY';
    }

    if (attribute.references) {
      template += ` REFERENCES ${this.quoteTable(attribute.references.model)}`;

      if (attribute.references.key) {
        template += ` (${this.quoteIdentifier(attribute.references.key)})`;
      } else {
        template += ` (${this.quoteIdentifier('id')})`;
      }

      if (attribute.onDelete) {
        template += ` ON DELETE ${attribute.onDelete.toUpperCase()}`;
      }

      if (attribute.onUpdate) {
        template += ` ON UPDATE ${attribute.onUpdate.toUpperCase()}`;
      }
    }

    if (attribute.comment && typeof attribute.comment === 'string') {
      template += ` COMMENT ${attribute.comment}`;
    }

    return template;
  }

  attributesToSQL(attributes, options) {
    const result = {},
      existingConstraints = [];
    let key,
      attribute;

    for (key in attributes) {
      attribute = attributes[key];

      if (attribute.references) {
        if (existingConstraints.includes(attribute.references.model.toString())) {
          // no cascading constraints to a table more than once
          attribute.onDelete = '';
          attribute.onUpdate = '';
        } else {
          existingConstraints.push(attribute.references.model.toString());

          // NOTE: this really just disables cascading updates for all
          //       definitions. Can be made more robust to support the
          //       few cases where MSSQL actually supports them
          attribute.onUpdate = '';
        }

      }

      if (key && !attribute.field) attribute.field = key;
      result[attribute.field || key] = this.attributeToSQL(attribute, options);
    }

    return result;
  }

  createTrigger() {
    throwMethodUndefined('createTrigger');
  }

  dropTrigger() {
    throwMethodUndefined('dropTrigger');
  }

  renameTrigger() {
    throwMethodUndefined('renameTrigger');
  }

  createFunction() {
    throwMethodUndefined('createFunction');
  }

  dropFunction() {
    throwMethodUndefined('dropFunction');
  }

  renameFunction() {
    throwMethodUndefined('renameFunction');
  }

  /**
   * Generate common SQL prefix for ForeignKeysQuery.
   *
   * @param {string} catalogName
   * @returns {string}
   */
  _getForeignKeysQueryPrefix(catalogName) {
    return `${'SELECT ' +
        'constraint_name = OBJ.NAME, ' +
        'constraintName = OBJ.NAME, '}${
      catalogName ? `constraintCatalog = '${catalogName}', ` : ''
    }constraintSchema = SCHEMA_NAME(OBJ.SCHEMA_ID), ` +
        'tableName = TB.NAME, ' +
        `tableSchema = SCHEMA_NAME(TB.SCHEMA_ID), ${
          catalogName ? `tableCatalog = '${catalogName}', ` : ''
        }columnName = COL.NAME, ` +
        `referencedTableSchema = SCHEMA_NAME(RTB.SCHEMA_ID), ${
          catalogName ? `referencedCatalog = '${catalogName}', ` : ''
        }referencedTableName = RTB.NAME, ` +
        'referencedColumnName = RCOL.NAME ' +
      'FROM sys.foreign_key_columns FKC ' +
        'INNER JOIN sys.objects OBJ ON OBJ.OBJECT_ID = FKC.CONSTRAINT_OBJECT_ID ' +
        'INNER JOIN sys.tables TB ON TB.OBJECT_ID = FKC.PARENT_OBJECT_ID ' +
        'INNER JOIN sys.columns COL ON COL.COLUMN_ID = PARENT_COLUMN_ID AND COL.OBJECT_ID = TB.OBJECT_ID ' +
        'INNER JOIN sys.tables RTB ON RTB.OBJECT_ID = FKC.REFERENCED_OBJECT_ID ' +
        'INNER JOIN sys.columns RCOL ON RCOL.COLUMN_ID = REFERENCED_COLUMN_ID AND RCOL.OBJECT_ID = RTB.OBJECT_ID';
  }

  /**
   * Generates an SQL query that returns all foreign keys details of a table.
   *
   * @param {string|object} table
   * @param {string} catalogName database name
   * @returns {string}
   */
  getForeignKeysQuery(table, catalogName) {
    const tableName = table.tableName || table;
    let sql = `${this._getForeignKeysQueryPrefix(catalogName)
    } WHERE TB.NAME =${wrapSingleQuote(tableName)}`;

    if (table.schema) {
      sql += ` AND SCHEMA_NAME(TB.SCHEMA_ID) =${wrapSingleQuote(table.schema)}`;
    }
    return sql;
  }

  getForeignKeyQuery(table, attributeName) {
    const tableName = table.tableName || table;
    return Utils.joinSQLFragments([
      this._getForeignKeysQueryPrefix(),
      'WHERE',
      `TB.NAME =${wrapSingleQuote(tableName)}`,
      'AND',
      `COL.NAME =${wrapSingleQuote(attributeName)}`,
      table.schema && `AND SCHEMA_NAME(TB.SCHEMA_ID) =${wrapSingleQuote(table.schema)}`
    ]);
  }

  getPrimaryKeyConstraintQuery(table, attributeName) {
    const tableName = wrapSingleQuote(table.tableName || table);
    return Utils.joinSQLFragments([
      'SELECT K.TABLE_NAME AS tableName,',
      'K.COLUMN_NAME AS columnName,',
      'K.CONSTRAINT_NAME AS constraintName',
      'FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS C',
      'JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS K',
      'ON C.TABLE_NAME = K.TABLE_NAME',
      'AND C.CONSTRAINT_CATALOG = K.CONSTRAINT_CATALOG',
      'AND C.CONSTRAINT_SCHEMA = K.CONSTRAINT_SCHEMA',
      'AND C.CONSTRAINT_NAME = K.CONSTRAINT_NAME',
      'WHERE C.CONSTRAINT_TYPE = \'PRIMARY KEY\'',
      `AND K.COLUMN_NAME = ${wrapSingleQuote(attributeName)}`,
      `AND K.TABLE_NAME = ${tableName}`,
      ';'
    ]);
  }

  dropForeignKeyQuery(tableName, foreignKey) {
    return Utils.joinSQLFragments([
      'ALTER TABLE',
      this.quoteTable(tableName),
      'DROP',
      this.quoteIdentifier(foreignKey)
    ]);
  }

  getDefaultConstraintQuery(tableName, attributeName) {
    const quotedTable = this.quoteTable(tableName);
    return Utils.joinSQLFragments([
      'SELECT name FROM sys.default_constraints',
      `WHERE PARENT_OBJECT_ID = OBJECT_ID('${quotedTable}', 'U')`,
      `AND PARENT_COLUMN_ID = (SELECT column_id FROM sys.columns WHERE NAME = ('${attributeName}')`,
      `AND object_id = OBJECT_ID('${quotedTable}', 'U'))`,
      ';'
    ]);
  }

  dropConstraintQuery(tableName, constraintName) {
    return Utils.joinSQLFragments([
      'ALTER TABLE',
      this.quoteTable(tableName),
      'DROP CONSTRAINT',
      this.quoteIdentifier(constraintName),
      ';'
    ]);
  }

  setIsolationLevelQuery() {

  }

  generateTransactionId() {
    return randomBytes(10).toString('hex');
  }

  startTransactionQuery(transaction) {
    if (transaction.parent) {
      return `SAVE TRANSACTION ${this.quoteIdentifier(transaction.name)};`;
    }

    return 'BEGIN TRANSACTION;';
  }

  commitTransactionQuery(transaction) {
    if (transaction.parent) {
      return;
    }

    return 'COMMIT TRANSACTION;';
  }

  rollbackTransactionQuery(transaction) {
    if (transaction.parent) {
      return `ROLLBACK TRANSACTION ${this.quoteIdentifier(transaction.name)};`;
    }

    return 'ROLLBACK TRANSACTION;';
  }

  selectFromTableFragment(options, model, attributes, tables, mainTableAs, where) {
    this._throwOnEmptyAttributes(attributes, { modelName: model && model.name, as: mainTableAs });

    const dbVersion = this.sequelize.options.databaseVersion;
    const isSQLServer2008 = semver.valid(dbVersion) && semver.lt(dbVersion, '11.0.0');

    if (isSQLServer2008 && options.offset) {
      // For earlier versions of SQL server, we need to nest several queries
      // in order to emulate the OFFSET behavior.
      //
      // 1. The outermost query selects all items from the inner query block.
      //    This is due to a limitation in SQL server with the use of computed
      //    columns (e.g. SELECT ROW_NUMBER()...AS x) in WHERE clauses.
      // 2. The next query handles the LIMIT and OFFSET behavior by getting
      //    the TOP N rows of the query where the row number is > OFFSET
      // 3. The innermost query is the actual set we want information from

      const offset = options.offset || 0;
      const isSubQuery = options.hasIncludeWhere || options.hasIncludeRequired || options.hasMultiAssociation;
      let orders = { mainQueryOrder: [] };
      if (options.order) {
        orders = this.getQueryOrders(options, model, isSubQuery);
      }

      if (orders.mainQueryOrder.length === 0) {
        orders.mainQueryOrder.push(this.quoteIdentifier(model.primaryKeyField));
      }

      const tmpTable = mainTableAs || 'OffsetTable';

      return Utils.joinSQLFragments([
        'SELECT TOP 100 PERCENT',
        attributes.join(', '),
        'FROM (',
        [
          'SELECT',
          options.limit && `TOP ${options.limit}`,
          '* FROM (',
          [
            'SELECT ROW_NUMBER() OVER (',
            [
              'ORDER BY',
              orders.mainQueryOrder.join(', ')
            ],
            `) as row_num, * FROM ${tables} AS ${tmpTable}`,
            where && `WHERE ${where}`
          ],
          `) AS ${tmpTable} WHERE row_num > ${offset}`
        ],
        `) AS ${tmpTable}`
      ]);
    }

    return Utils.joinSQLFragments([
      'SELECT',
      isSQLServer2008 && options.limit && `TOP ${options.limit}`,
      attributes.join(', '),
      `FROM ${tables}`,
      mainTableAs && `AS ${mainTableAs}`,
      options.tableHint && TableHints[options.tableHint] && `WITH (${TableHints[options.tableHint]})`
    ]);
  }

  addLimitAndOffset(options, model) {
    // Skip handling of limit and offset as postfixes for older SQL Server versions
    if (semver.valid(this.sequelize.options.databaseVersion) && semver.lt(this.sequelize.options.databaseVersion, '11.0.0')) {
      return '';
    }

    const offset = options.offset || 0;
    const isSubQuery = options.subQuery === undefined
      ? options.hasIncludeWhere || options.hasIncludeRequired || options.hasMultiAssociation
      : options.subQuery;

    let fragment = '';
    let orders = {};

    if (options.order) {
      orders = this.getQueryOrders(options, model, isSubQuery);
    }

    if (options.limit || options.offset) {
      if (!options.order || options.include && !orders.subQueryOrder.length) {
        const tablePkFragment = `${this.quoteTable(options.tableAs || model.name)}.${this.quoteIdentifier(model.primaryKeyField)}`;
        if (!options.order) {
          fragment += ` ORDER BY ${tablePkFragment}`;
        } else {
          const orderFieldNames = _.map(options.order, order => order[0]);
          const primaryKeyFieldAlreadyPresent = _.includes(orderFieldNames, model.primaryKeyField);

          if (!primaryKeyFieldAlreadyPresent) {
            fragment += options.order && !isSubQuery ? ', ' : ' ORDER BY ';
            fragment += tablePkFragment;
          }
        }
      }

      if (options.offset || options.limit) {
        fragment += ` OFFSET ${this.escape(offset)} ROWS`;
      }

      if (options.limit) {
        fragment += ` FETCH NEXT ${this.escape(options.limit)} ROWS ONLY`;
      }
    }

    return fragment;
  }

  booleanValue(value) {
    return value ? 1 : 0;
  }
}

// private methods
function wrapSingleQuote(identifier) {
  return Utils.addTicks(Utils.removeTicks(identifier, "'"), "'");
}

module.exports = MSSQLQueryGenerator;


'use strict';

const chai = require('chai'),
  expect = chai.expect,
  Support = require('../../support'),
  dialect = Support.getTestDialect(),
  DataTypes = require('../../../../lib/data-types');

if (dialect !== 'mariadb') return;

describe('[MariaDB Specific] Associations', () => {
  describe('many-to-many', () => {
    describe('where tables have the same prefix', () => {
      it('should create a table wp_table1wp_table2s', async function() {
        const Table2 = this.sequelize.define('wp_table2', { foo: DataTypes.STRING }),
          Table1 = this.sequelize.define('wp_table1',
            { foo: DataTypes.STRING });

        Table1.belongsToMany(Table2, { through: 'wp_table1swp_table2s' });
        Table2.belongsToMany(Table1, { through: 'wp_table1swp_table2s' });
        await Table1.sync({ force: true });
        await Table2.sync({ force: true });
        expect(this.sequelize.modelManager.getModel(
          'wp_table1swp_table2s')).to.exist;
      });
    });

    describe('when join table name is specified', () => {
      beforeEach(async function() {
        const Table2 = this.sequelize.define('ms_table1', { foo: DataTypes.STRING }),
          Table1 = this.sequelize.define('ms_table2',
            { foo: DataTypes.STRING });

        Table1.belongsToMany(Table2, { through: 'table1_to_table2' });
        Table2.belongsToMany(Table1, { through: 'table1_to_table2' });
        await Table1.sync({ force: true });
        await Table2.sync({ force: true });
      });

      it('should not use only a specified name', function() {
        expect(this.sequelize.modelManager.getModel(
          'ms_table1sms_table2s')).not.to.exist;
        expect(
          this.sequelize.modelManager.getModel('table1_to_table2')).to.exist;
      });
    });
  });

  describe('HasMany', () => {
    beforeEach(async function() {
      //prevent periods from occurring in the table name since they are used to delimit (table.column)
      this.User = this.sequelize.define(`User${Math.ceil(Math.random() * 10000000)}`, { name: DataTypes.STRING });
      this.Task = this.sequelize.define(`Task${Math.ceil(Math.random() * 10000000)}`, { name: DataTypes.STRING });
      this.users = null;
      this.tasks = null;

      this.User.belongsToMany(this.Task, { as: 'Tasks', through: 'UserTasks' });
      this.Task.belongsToMany(this.User, { as: 'Users', through: 'UserTasks' });

      const users = [];
      const tasks = [];

      for (let i = 0; i < 5; ++i) {
        users[i] = { name: `User${Math.random()}` };
        tasks[i] = { name: `Task${Math.random()}` };
      }

      await this.sequelize.sync({ force: true });
      await this.User.bulkCreate(users);
      await this.Task.bulkCreate(tasks);
    });

  });
});


'use strict';

const DatabaseError = require('./../database-error');

/**
 * Thrown when a database query times out because of a deadlock
 */
class TimeoutError extends DatabaseError {
  constructor(parent) {
    super(parent);
    this.name = 'SequelizeTimeoutError';
  }
}

module.exports = TimeoutError;