'use strict';

const chai = require('chai');

const expect = chai.expect;
const Support = require('./support');
const { DataTypes } = require('@sequelize/core');

const dialect = Support.getTestDialect();
const sinon = require('sinon');
const isUUID = require('validator').isUUID;

describe(Support.getTestDialectTeaser('Instance'), () => {
  before(function () {
    this.clock = sinon.useFakeTimers();
  });

  afterEach(function () {
    this.clock.reset();
  });

  after(function () {
    this.clock.restore();
  });

  beforeEach(async function () {
    this.User = this.sequelize.define('User', {
      username: { type: DataTypes.STRING },
      uuidv1: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV1 },
      uuidv4: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4 },
      touchedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },
      aNumber: { type: DataTypes.INTEGER },
      bNumber: { type: DataTypes.INTEGER },
      aDate: { type: DataTypes.DATE },

      validateTest: {
        type: DataTypes.INTEGER,
        allowNull: true,
        validate: { isInt: true },
      },
      validateCustom: {
        type: DataTypes.STRING,
        allowNull: true,
        validate: { len: { msg: 'Length failed.', args: [1, 20] } },
      },

      dateAllowNullTrue: {
        type: DataTypes.DATE,
        allowNull: true,
      },

      isSuperUser: {
        type: DataTypes.BOOLEAN,
        defaultValue: false,
      },
    });

    await this.User.sync({ force: true });
  });

  describe('Escaping', () => {
    it('is done properly for special characters', async function () {
      // Ideally we should test more: "\0\n\r\b\t\\\'\"\x1a"
      // But this causes sqlite to fail and exits the entire test suite immediately
      const bio = `${dialect}'"\n`; // Need to add the dialect here so in case of failure I know what DB it failed for

      const u1 = await this.User.create({ username: bio });
      const u2 = await this.User.findByPk(u1.id);
      expect(u2.username).to.equal(bio);
    });
  });

  describe('isNewRecord', () => {
    it('returns true for non-saved objects', function () {
      const user = this.User.build({ username: 'user' });
      expect(user.id).to.be.null;
      expect(user.isNewRecord).to.be.ok;
    });

    it('returns false for saved objects', async function () {
      const user = await this.User.build({ username: 'user' }).save();
      expect(user.isNewRecord).to.not.be.ok;
    });

    it('returns false for created objects', async function () {
      const user = await this.User.create({ username: 'user' });
      expect(user.isNewRecord).to.not.be.ok;
    });

    it('returns false for upserted objects', async function () {
      // adding id here so MSSQL doesn't fail. It needs a primary key to upsert
      const [user] = await this.User.upsert({ id: 2, username: 'user' });
      expect(user.isNewRecord).to.not.be.ok;
    });

    it('returns false for objects found by find method', async function () {
      await this.User.create({ username: 'user' });
      const user = await this.User.create({ username: 'user' });
      const user0 = await this.User.findByPk(user.id);
      expect(user0.isNewRecord).to.not.be.ok;
    });

    it('returns false for objects found by findAll method', async function () {
      const users = [];

      for (let i = 0; i < 10; i++) {
        users[i] = { username: 'user' };
      }

      await this.User.bulkCreate(users);
      const users0 = await this.User.findAll();
      for (const u of users0) {
        expect(u.isNewRecord).to.not.be.ok;
      }
    });
  });

  describe('default values', () => {
    describe('uuid', () => {
      it('should store a string in uuidv1 and uuidv4', function () {
        const user = this.User.build({ username: 'a user' });
        expect(user.uuidv1).to.be.a('string');
        expect(user.uuidv4).to.be.a('string');
      });

      it('should store a string of length 36 in uuidv1 and uuidv4', function () {
        const user = this.User.build({ username: 'a user' });
        expect(user.uuidv1).to.have.length(36);
        expect(user.uuidv4).to.have.length(36);
      });

      it('should store a valid uuid in uuidv1 and uuidv4 that conforms to the UUID v1 and v4 specifications', function () {
        const user = this.User.build({ username: 'a user' });
        expect(isUUID(user.uuidv1)).to.be.true;
        expect(isUUID(user.uuidv4, 4)).to.be.true;
      });

      it('should store a valid uuid if the multiple primary key fields used', function () {
        const Person = this.sequelize.define('Person', {
          id1: {
            type: DataTypes.UUID,
            defaultValue: DataTypes.UUIDV1,
            primaryKey: true,
          },
          id2: {
            type: DataTypes.UUID,
            defaultValue: DataTypes.UUIDV1,
            primaryKey: true,
          },
        });

        const person = Person.build({});
        expect(person.id1).to.be.ok;
        expect(person.id1).to.have.length(36);

        expect(person.id2).to.be.ok;
        expect(person.id2).to.have.length(36);
      });
    });
    describe('current date', () => {
      it('should store a date in touchedAt', function () {
        const user = this.User.build({ username: 'a user' });
        expect(user.touchedAt).to.be.instanceof(Date);
      });

      it('should store the current date in touchedAt', function () {
        this.clock.tick(5000);
        const user = this.User.build({ username: 'a user' });
        this.clock.restore();
        expect(Number(user.touchedAt)).to.be.equal(5000);
      });
    });

    describe('allowNull date', () => {
      it('should be just "null" and not Date with Invalid Date', async function () {
        await this.User.build({ username: 'a user' }).save();
        const user = await this.User.findOne({ where: { username: 'a user' } });
        expect(user.dateAllowNullTrue).to.be.null;
      });

      it('should be the same valid date when saving the date', async function () {
        const date = new Date('2018-01-01T12:12:12.000Z');
        await this.User.build({ username: 'a user', dateAllowNullTrue: date }).save();
        const user = await this.User.findOne({ where: { username: 'a user' } });
        expect(user.dateAllowNullTrue.toISOString()).to.equal(date.toISOString());
      });
    });

    describe('super user boolean', () => {
      it('should default to false', async function () {
        await this.User.build({
          username: 'a user',
        })
          .save();

        const user = await this.User.findOne({
          where: {
            username: 'a user',
          },
        });

        expect(user.isSuperUser).to.be.false;
      });

      it('should override default when given truthy boolean', async function () {
        await this.User.build({
          username: 'a user',
          isSuperUser: true,
        })
          .save();

        const user = await this.User.findOne({
          where: {
            username: 'a user',
          },
        });

        expect(user.isSuperUser).to.be.true;
      });

      it('should throw error when given value of incorrect type', async function () {
        let callCount = 0;

        try {
          await this.User.build({
            username: 'a user',
            isSuperUser: 'INCORRECT_VALUE_TYPE',
          })
            .save();

          callCount += 1;
        } catch (error) {
          expect(callCount).to.equal(0);
          expect(error).to.exist;
          expect(error.message).to.exist;
        }
      });
    });
  });

  describe('complete', () => {
    it('gets triggered if an error occurs', async function () {
      try {
        await this.User.findOne({ where: ['asdasdasd'] });
      } catch (error) {
        expect(error).to.exist;
        expect(error.message).to.exist;
      }
    });

    it('gets triggered if everything was ok', async function () {
      const result = await this.User.count();
      expect(result).to.exist;
    });
  });

  describe('findAll', () => {
    beforeEach(async function () {
      this.ParanoidUser = this.sequelize.define('ParanoidUser', {
        username: { type: DataTypes.STRING },
      }, { paranoid: true });

      this.ParanoidUser.hasOne(this.ParanoidUser, { as: 'paranoidParent', inverse: { as: 'paranoidChild' } });
      await this.ParanoidUser.sync({ force: true });
    });

    it('sql should have paranoid condition', async function () {
      await this.ParanoidUser.create({ username: 'cuss' });
      const users0 = await this.ParanoidUser.findAll();
      expect(users0).to.have.length(1);
      await users0[0].destroy();
      const users = await this.ParanoidUser.findAll();
      expect(users).to.have.length(0);
    });

    it('sequelize.and as where should include paranoid condition', async function () {
      await this.ParanoidUser.create({ username: 'cuss' });

      const users0 = await this.ParanoidUser.findAll({
        where: this.sequelize.and({
          username: 'cuss',
        }),
      });

      expect(users0).to.have.length(1);
      await users0[0].destroy();

      const users = await this.ParanoidUser.findAll({
        where: this.sequelize.and({
          username: 'cuss',
        }),
      });

      expect(users).to.have.length(0);
    });

    it('sequelize.or as where should include paranoid condition', async function () {
      await this.ParanoidUser.create({ username: 'cuss' });

      const users0 = await this.ParanoidUser.findAll({
        where: this.sequelize.or({
          username: 'cuss',
        }),
      });

      expect(users0).to.have.length(1);
      await users0[0].destroy();

      const users = await this.ParanoidUser.findAll({
        where: this.sequelize.or({
          username: 'cuss',
        }),
      });

      expect(users).to.have.length(0);
    });

    it('escapes a single single quotes properly in where clauses', async function () {
      await this.User
        .create({ username: 'user\'name' });

      const users = await this.User.findAll({
        where: { username: 'user\'name' },
      });

      expect(users.length).to.equal(1);
      expect(users[0].username).to.equal('user\'name');
    });

    it('escapes two single quotes properly in where clauses', async function () {
      await this.User
        .create({ username: 'user\'\'name' });

      const users = await this.User.findAll({
        where: { username: 'user\'\'name' },
      });

      expect(users.length).to.equal(1);
      expect(users[0].username).to.equal('user\'\'name');
    });

    it('returns the timestamps if no attributes have been specified', async function () {
      await this.User.create({ username: 'fnord' });
      const users = await this.User.findAll();
      expect(users[0].createdAt).to.exist;
    });

    it('does not return the timestamps if the username attribute has been specified', async function () {
      await this.User.create({ username: 'fnord' });
      const users = await this.User.findAll({ attributes: ['username'] });
      expect(users[0].createdAt).not.to.exist;
      expect(users[0].username).to.exist;
    });

    it('creates the deletedAt property, when defining paranoid as true', async function () {
      await this.ParanoidUser.create({ username: 'fnord' });
      const users = await this.ParanoidUser.findAll();
      expect(users[0].deletedAt).to.be.null;
    });

    it('destroys a record with a primary key of something other than id', async function () {
      const UserDestroy = this.sequelize.define('UserDestroy', {
        newId: {
          type: DataTypes.STRING,
          primaryKey: true,
        },
        email: DataTypes.STRING,
      });

      await UserDestroy.sync();
      await UserDestroy.create({ newId: '123ABC', email: 'hello' });
      const user = await UserDestroy.findOne({ where: { email: 'hello' } });

      await user.destroy();
    });

    it('sets deletedAt property to a specific date when deleting an instance', async function () {
      await this.ParanoidUser.create({ username: 'fnord' });
      const users = await this.ParanoidUser.findAll();
      await users[0].destroy();
      expect(users[0].deletedAt.getMonth).to.exist;

      const user = await users[0].reload({ paranoid: false });
      expect(user.deletedAt.getMonth).to.exist;
    });

    it('keeps the deletedAt-attribute with value null, when running update', async function () {
      await this.ParanoidUser.create({ username: 'fnord' });
      const users = await this.ParanoidUser.findAll();
      const user = await users[0].update({ username: 'newFnord' });
      expect(user.deletedAt).not.to.exist;
    });

    it('keeps the deletedAt-attribute with value null, when updating associations', async function () {
      await this.ParanoidUser.create({ username: 'fnord' });
      const users = await this.ParanoidUser.findAll();
      const linkedUser = await this.ParanoidUser.create({ username: 'linkedFnord' });
      const user = await users[0].setParanoidParent(linkedUser);
      expect(user.deletedAt).not.to.exist;
    });

    it('can reuse query option objects', async function () {
      await this.User.create({ username: 'fnord' });
      const query = { where: { username: 'fnord' } };
      const users = await this.User.findAll(query);
      expect(users[0].username).to.equal('fnord');
      const users0 = await this.User.findAll(query);
      expect(users0[0].username).to.equal('fnord');
    });
  });

  describe('findOne', () => {
    it('can reuse query option objects', async function () {
      await this.User.create({ username: 'fnord' });
      const query = { where: { username: 'fnord' } };
      const user = await this.User.findOne(query);
      expect(user.username).to.equal('fnord');
      const user0 = await this.User.findOne(query);
      expect(user0.username).to.equal('fnord');
    });
    it('returns null for null, undefined, and unset boolean values', async function () {
      const Setting = this.sequelize.define('SettingHelper', {
        setting_key: DataTypes.STRING,
        bool_value: { type: DataTypes.BOOLEAN, allowNull: true },
        bool_value2: { type: DataTypes.BOOLEAN, allowNull: true },
        bool_value3: { type: DataTypes.BOOLEAN, allowNull: true },
      }, { timestamps: false, logging: false });

      await Setting.sync({ force: true });
      await Setting.create({ setting_key: 'test', bool_value: null, bool_value2: undefined });
      const setting = await Setting.findOne({ where: { setting_key: 'test' } });
      expect(setting.bool_value).to.equal(null);
      expect(setting.bool_value2).to.equal(null);
      expect(setting.bool_value3).to.equal(null);
    });
  });

  describe('equals', () => {
    it('can compare records with Date field', async function () {
      const user1 = await this.User.create({ username: 'fnord' });
      const user2 = await this.User.findOne({ where: { username: 'fnord' } });
      expect(user1.equals(user2)).to.be.true;
    });

    it('does not compare the existence of associations', async function () {
      this.UserAssociationEqual = this.sequelize.define('UserAssociationEquals', {
        username: DataTypes.STRING,
        age: DataTypes.INTEGER,
      }, { timestamps: false });

      this.ProjectAssociationEqual = this.sequelize.define('ProjectAssocationEquals', {
        title: DataTypes.STRING,
        overdue_days: DataTypes.INTEGER,
      }, { timestamps: false });

      this.UserAssociationEqual.hasMany(this.ProjectAssociationEqual, { as: 'Projects', foreignKey: 'userId' });
      this.ProjectAssociationEqual.belongsTo(this.UserAssociationEqual, { as: 'Users', foreignKey: 'userId' });

      await this.UserAssociationEqual.sync({ force: true });
      await this.ProjectAssociationEqual.sync({ force: true });
      const user1 = await this.UserAssociationEqual.create({ username: 'jimhalpert' });
      const project1 = await this.ProjectAssociationEqual.create({ title: 'A Cool Project' });
      await user1.setProjects([project1]);
      const user2 = await this.UserAssociationEqual.findOne({ where: { username: 'jimhalpert' }, include: [{ model: this.ProjectAssociationEqual, as: 'Projects' }] });
      const user3 = await this.UserAssociationEqual.create({ username: 'pambeesly' });
      expect(user1.get('Projects')).to.not.exist;
      expect(user2.get('Projects')).to.exist;
      expect(user1.equals(user2)).to.be.true;
      expect(user2.equals(user1)).to.be.true;
      expect(user1.equals(user3)).to.not.be.true;
      expect(user3.equals(user1)).to.not.be.true;
    });
  });

  describe('values', () => {
    it('returns all values', async function () {
      const User = this.sequelize.define('UserHelper', {
        username: DataTypes.STRING,
      }, { timestamps: false, logging: false });

      await User.sync();
      const user = User.build({ username: 'foo' });
      expect(user.get({ plain: true })).to.deep.equal({ username: 'foo', id: null });
    });
  });

  describe('isSoftDeleted', () => {
    beforeEach(async function () {
      this.ParanoidUser = this.sequelize.define('ParanoidUser', {
        username: { type: DataTypes.STRING },
      }, { paranoid: true });

      await this.ParanoidUser.sync({ force: true });
    });

    it('should return false when model is just created', async function () {
      const user = await this.ParanoidUser.create({ username: 'foo' });
      expect(user.isSoftDeleted()).to.be.false;
    });

    it('returns false if user is not soft deleted', async function () {
      await this.ParanoidUser.create({ username: 'fnord' });
      const users = await this.ParanoidUser.findAll();
      expect(users[0].isSoftDeleted()).to.be.false;
    });

    it('returns true if user is soft deleted', async function () {
      await this.ParanoidUser.create({ username: 'fnord' });
      const users = await this.ParanoidUser.findAll();
      await users[0].destroy();
      expect(users[0].isSoftDeleted()).to.be.true;

      const user = await users[0].reload({ paranoid: false });
      expect(user.isSoftDeleted()).to.be.true;
    });

    it('works with custom `deletedAt` field name', async function () {
      this.ParanoidUserWithCustomDeletedAt = this.sequelize.define('ParanoidUserWithCustomDeletedAt', {
        username: { type: DataTypes.STRING },
      }, {
        deletedAt: 'deletedAtThisTime',
        paranoid: true,
      });

      this.ParanoidUserWithCustomDeletedAt.hasOne(this.ParanoidUser);

      await this.ParanoidUserWithCustomDeletedAt.sync({ force: true });
      await this.ParanoidUserWithCustomDeletedAt.create({ username: 'fnord' });
      const users = await this.ParanoidUserWithCustomDeletedAt.findAll();
      expect(users[0].isSoftDeleted()).to.be.false;

      await users[0].destroy();
      expect(users[0].isSoftDeleted()).to.be.true;

      const user = await users[0].reload({ paranoid: false });
      expect(user.isSoftDeleted()).to.be.true;
    });
  });

  describe('restore', () => {
    it('returns an error if the model is not paranoid', async function () {
      const user = await this.User.create({ username: 'Peter', secretValue: '42' });
      await expect(user.restore()).to.be.rejectedWith(Error, 'Model is not paranoid');
    });

    it('restores a previously deleted model', async function () {
      const ParanoidUser = this.sequelize.define('ParanoidUser', {
        username: DataTypes.STRING,
        secretValue: DataTypes.STRING,
        data: DataTypes.STRING,
        intVal: { type: DataTypes.INTEGER, defaultValue: 1 },
      }, {
        paranoid: true,
      });
      const data = [{ username: 'Peter', secretValue: '42' },
        { username: 'Paul', secretValue: '43' },
        { username: 'Bob', secretValue: '44' }];

      await ParanoidUser.sync({ force: true });
      await ParanoidUser.bulkCreate(data);
      const user0 = await ParanoidUser.findOne({ where: { secretValue: '42' } });
      await user0.destroy();
      await user0.restore();
      const user = await ParanoidUser.findOne({ where: { secretValue: '42' } });
      expect(user).to.be.ok;
      expect(user.username).to.equal('Peter');
    });

    it('supports custom deletedAt field', async function () {
      const ParanoidUser = this.sequelize.define('ParanoidUser', {
        username: DataTypes.STRING,
        destroyTime: DataTypes.DATE,
      }, { paranoid: true, deletedAt: 'destroyTime' });

      await ParanoidUser.sync({ force: true });

      const user2 = await ParanoidUser.create({
        username: 'username',
      });

      const user1 = await user2.destroy();
      expect(user1.destroyTime).to.be.ok;
      expect(user1.deletedAt).to.not.be.ok;
      const user0 = await user1.restore();
      expect(user0.destroyTime).to.not.be.ok;
      const user = await ParanoidUser.findOne({ where: { username: 'username' } });
      expect(user).to.be.ok;
      expect(user.destroyTime).to.not.be.ok;
      expect(user.deletedAt).to.not.be.ok;
    });

    it('supports custom deletedAt field name', async function () {
      const ParanoidUser = this.sequelize.define('ParanoidUser', {
        username: DataTypes.STRING,
        deletedAt: { type: DataTypes.DATE, field: 'deleted_at' },
      }, { paranoid: true });

      await ParanoidUser.sync({ force: true });

      const user2 = await ParanoidUser.create({
        username: 'username',
      });

      const user1 = await user2.destroy();
      expect(user1.dataValues.deletedAt).to.be.ok;
      expect(user1.dataValues.deleted_at).to.not.be.ok;
      const user0 = await user1.restore();
      expect(user0.dataValues.deletedAt).to.not.be.ok;
      expect(user0.dataValues.deleted_at).to.not.be.ok;
      const user = await ParanoidUser.findOne({ where: { username: 'username' } });
      expect(user).to.be.ok;
      expect(user.deletedAt).to.not.be.ok;
      expect(user.deleted_at).to.not.be.ok;
    });

    it('supports custom deletedAt field and database column', async function () {
      const ParanoidUser = this.sequelize.define('ParanoidUser', {
        username: DataTypes.STRING,
        destroyTime: { type: DataTypes.DATE, field: 'destroy_time' },
      }, { paranoid: true, deletedAt: 'destroyTime' });

      await ParanoidUser.sync({ force: true });

      const user2 = await ParanoidUser.create({
        username: 'username',
      });

      const user1 = await user2.destroy();
      expect(user1.dataValues.destroyTime).to.be.ok;
      expect(user1.dataValues.deletedAt).to.not.be.ok;
      expect(user1.dataValues.destroy_time).to.not.be.ok;
      const user0 = await user1.restore();
      expect(user0.dataValues.destroyTime).to.not.be.ok;
      expect(user0.dataValues.destroy_time).to.not.be.ok;
      const user = await ParanoidUser.findOne({ where: { username: 'username' } });
      expect(user).to.be.ok;
      expect(user.destroyTime).to.not.be.ok;
      expect(user.destroy_time).to.not.be.ok;
    });

    it('supports custom default value', async function () {
      const ParanoidUser = this.sequelize.define('ParanoidUser', {
        username: DataTypes.STRING,
        deletedAt: { type: DataTypes.DATE, defaultValue: new Date(0) },
      }, { paranoid: true });

      await ParanoidUser.sync({ force: true });

      const user2 = await ParanoidUser.create({
        username: 'username',
      });

      const user1 = await user2.destroy();
      const user0 = await user1.restore();
      expect(user0.dataValues.deletedAt.toISOString()).to.equal(new Date(0).toISOString());
      const user = await ParanoidUser.findOne({ where: { username: 'username' } });
      expect(user).to.be.ok;
      expect(user.deletedAt.toISOString()).to.equal(new Date(0).toISOString());
    });
  });
});


keep keep keep replace keep keep keep keep

Files in this directory are imported by the documentation.
Check where they are used in the documentation before modifying them.


'use strict';

const chai = require('chai');

const expect = chai.expect;
const Support = require('../../../support');

const dialect = Support.getTestDialect();
const _ = require('lodash');
const { Op, IndexHints } = require('@sequelize/core');
const { SnowflakeQueryGenerator: QueryGenerator } = require('@sequelize/core/_non-semver-use-at-your-own-risk_/dialects/snowflake/query-generator.js');

if (dialect === 'snowflake') {
  describe('[SNOWFLAKE Specific] QueryGenerator', () => {
    const suites = {
      attributesToSQL: [
        {
          arguments: [{ id: 'INTEGER' }],
          expectation: { id: 'INTEGER' },
        },
        {
          arguments: [{ id: 'INTEGER', foo: 'VARCHAR(255)' }],
          expectation: { id: 'INTEGER', foo: 'VARCHAR(255)' },
        },
        {
          arguments: [{ id: { type: 'INTEGER' } }],
          expectation: { id: 'INTEGER' },
        },
        {
          arguments: [{ id: { type: 'INTEGER', allowNull: false } }],
          expectation: { id: 'INTEGER NOT NULL' },
        },
        {
          arguments: [{ id: { type: 'INTEGER', allowNull: true } }],
          expectation: { id: 'INTEGER' },
        },
        {
          arguments: [{ id: { type: 'INTEGER', primaryKey: true, autoIncrement: true } }],
          expectation: { id: 'INTEGER AUTOINCREMENT PRIMARY KEY' },
        },
        {
          arguments: [{ id: { type: 'INTEGER', defaultValue: 0 } }],
          expectation: { id: 'INTEGER DEFAULT 0' },
        },
        {
          title: 'Add column level comment',
          arguments: [{ id: { type: 'INTEGER', comment: 'Test' } }],
          expectation: { id: 'INTEGER COMMENT \'Test\'' },
        },
        {
          arguments: [{ id: { type: 'INTEGER', unique: true } }],
          expectation: { id: 'INTEGER UNIQUE' },
        },
        {
          arguments: [{ id: { type: 'INTEGER', after: 'Bar' } }],
          expectation: { id: 'INTEGER AFTER "Bar"' },
        },
        // No Default Values allowed for certain types
        {
          title: 'No Default value for SNOWFLAKE BLOB allowed',
          arguments: [{ id: { type: 'BLOB', defaultValue: [] } }],
          expectation: { id: 'BLOB' },
        },
        {
          title: 'No Default value for SNOWFLAKE TEXT allowed',
          arguments: [{ id: { type: 'TEXT', defaultValue: [] } }],
          expectation: { id: 'TEXT' },
        },
        {
          title: 'No Default value for SNOWFLAKE GEOMETRY allowed',
          arguments: [{ id: { type: 'GEOMETRY', defaultValue: [] } }],
          expectation: { id: 'GEOMETRY' },
        },
        {
          title: 'No Default value for SNOWFLAKE JSON allowed',
          arguments: [{ id: { type: 'JSON', defaultValue: [] } }],
          expectation: { id: 'JSON' },
        },
        // New references style
        {
          arguments: [{ id: { type: 'INTEGER', references: { model: 'Bar' } } }],
          expectation: { id: 'INTEGER REFERENCES "Bar" ("id")' },
        },
        {
          arguments: [{ id: { type: 'INTEGER', references: { model: 'Bar', key: 'pk' } } }],
          expectation: { id: 'INTEGER REFERENCES "Bar" ("pk")' },
        },
        {
          arguments: [{ id: { type: 'INTEGER', references: { model: 'Bar' }, onDelete: 'CASCADE' } }],
          expectation: { id: 'INTEGER REFERENCES "Bar" ("id") ON DELETE CASCADE' },
        },
        {
          arguments: [{ id: { type: 'INTEGER', references: { model: 'Bar' }, onUpdate: 'RESTRICT' } }],
          expectation: { id: 'INTEGER REFERENCES "Bar" ("id") ON UPDATE RESTRICT' },
        },
        {
          arguments: [{ id: { type: 'INTEGER', allowNull: false, autoIncrement: true, defaultValue: 1, references: { model: 'Bar' }, onDelete: 'CASCADE', onUpdate: 'RESTRICT' } }],
          expectation: { id: 'INTEGER NOT NULL AUTOINCREMENT DEFAULT 1 REFERENCES "Bar" ("id") ON DELETE CASCADE ON UPDATE RESTRICT' },
        },

        // Variants when quoteIdentifiers is false
        {
          arguments: [{ id: { type: 'INTEGER', references: { model: 'Bar' } } }],
          expectation: { id: 'INTEGER REFERENCES Bar (id)' },
          context: { options: { quoteIdentifiers: false } },
        },
        {
          arguments: [{ id: { type: 'INTEGER', references: { model: 'Bar', key: 'pk' } } }],
          expectation: { id: 'INTEGER REFERENCES Bar (pk)' },
          context: { options: { quoteIdentifiers: false } },
        },
        {
          arguments: [{ id: { type: 'INTEGER', references: { model: 'Bar' }, onDelete: 'CASCADE' } }],
          expectation: { id: 'INTEGER REFERENCES Bar (id) ON DELETE CASCADE' },
          context: { options: { quoteIdentifiers: false } },
        },
        {
          arguments: [{ id: { type: 'INTEGER', references: { model: 'Bar' }, onUpdate: 'RESTRICT' } }],
          expectation: { id: 'INTEGER REFERENCES Bar (id) ON UPDATE RESTRICT' },
          context: { options: { quoteIdentifiers: false } },
        },
        {
          arguments: [{ id: { type: 'INTEGER', allowNull: false, autoIncrement: true, defaultValue: 1, references: { model: 'Bar' }, onDelete: 'CASCADE', onUpdate: 'RESTRICT' } }],
          expectation: { id: 'INTEGER NOT NULL AUTOINCREMENT DEFAULT 1 REFERENCES Bar (id) ON DELETE CASCADE ON UPDATE RESTRICT' },
          context: { options: { quoteIdentifiers: false } },
        },
      ],

      createTableQuery: [
        {
          arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)' }],
          expectation: 'CREATE TABLE IF NOT EXISTS "myTable" ("title" VARCHAR(255), "name" VARCHAR(255));',
        },
        {
          arguments: ['myTable', { data: 'BLOB' }],
          expectation: 'CREATE TABLE IF NOT EXISTS "myTable" ("data" BLOB);',
        },
        {
          arguments: ['myTable', { data: 'LONGBLOB' }],
          expectation: 'CREATE TABLE IF NOT EXISTS "myTable" ("data" LONGBLOB);',
        },
        {
          arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)' }],
          expectation: 'CREATE TABLE IF NOT EXISTS "myTable" ("title" VARCHAR(255), "name" VARCHAR(255));',
        },
        {
          arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)' }, { charset: 'utf8', collate: 'utf8_unicode_ci' }],
          expectation: 'CREATE TABLE IF NOT EXISTS "myTable" ("title" VARCHAR(255), "name" VARCHAR(255)) DEFAULT CHARSET=utf8 COLLATE utf8_unicode_ci;',
        },
        {
          arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)' }, { charset: 'latin1' }],
          expectation: 'CREATE TABLE IF NOT EXISTS "myTable" ("title" VARCHAR(255), "name" VARCHAR(255)) DEFAULT CHARSET=latin1;',
        },
        {
          arguments: ['myTable', { title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)' }, { charset: 'latin1' }],
          expectation: 'CREATE TABLE IF NOT EXISTS "myTable" ("title" ENUM("A", "B", "C"), "name" VARCHAR(255)) DEFAULT CHARSET=latin1;',
        },
        {
          arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)' }, { rowFormat: 'default' }],
          expectation: 'CREATE TABLE IF NOT EXISTS "myTable" ("title" VARCHAR(255), "name" VARCHAR(255)) ROW_FORMAT=default;',
        },
        {
          arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)', id: 'INTEGER PRIMARY KEY' }],
          expectation: 'CREATE TABLE IF NOT EXISTS "myTable" ("title" VARCHAR(255), "name" VARCHAR(255), "id" INTEGER , PRIMARY KEY ("id"));',
        },
        {
          arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)', otherId: 'INTEGER REFERENCES "otherTable" ("id") ON DELETE CASCADE ON UPDATE NO ACTION' }],
          expectation: 'CREATE TABLE IF NOT EXISTS "myTable" ("title" VARCHAR(255), "name" VARCHAR(255), "otherId" INTEGER, FOREIGN KEY ("otherId") REFERENCES "otherTable" ("id") ON DELETE CASCADE ON UPDATE NO ACTION);',
        },
        {
          arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)' }, { uniqueKeys: [{ fields: ['title', 'name'], customIndex: true }] }],
          expectation: 'CREATE TABLE IF NOT EXISTS "myTable" ("title" VARCHAR(255), "name" VARCHAR(255), UNIQUE "uniq_myTable_title_name" ("title", "name"));',
        },
        // Variants when quoteIdentifiers is false
        {
          arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)' }],
          expectation: 'CREATE TABLE IF NOT EXISTS myTable (title VARCHAR(255), name VARCHAR(255));',
          context: { options: { quoteIdentifiers: false } },
        },
        {
          arguments: ['myTable', { data: 'BLOB' }],
          expectation: 'CREATE TABLE IF NOT EXISTS myTable (data BLOB);',
          context: { options: { quoteIdentifiers: false } },
        },
        {
          arguments: ['myTable', { data: 'LONGBLOB' }],
          expectation: 'CREATE TABLE IF NOT EXISTS myTable (data LONGBLOB);',
          context: { options: { quoteIdentifiers: false } },
        },
        {
          arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)' }],
          expectation: 'CREATE TABLE IF NOT EXISTS myTable (title VARCHAR(255), name VARCHAR(255));',
          context: { options: { quoteIdentifiers: false } },
        },
        {
          arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)' }, { charset: 'utf8', collate: 'utf8_unicode_ci' }],
          expectation: 'CREATE TABLE IF NOT EXISTS myTable (title VARCHAR(255), name VARCHAR(255)) DEFAULT CHARSET=utf8 COLLATE utf8_unicode_ci;',
          context: { options: { quoteIdentifiers: false } },
        },
        {
          arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)' }, { charset: 'latin1' }],
          expectation: 'CREATE TABLE IF NOT EXISTS myTable (title VARCHAR(255), name VARCHAR(255)) DEFAULT CHARSET=latin1;',
          context: { options: { quoteIdentifiers: false } },
        },
        {
          arguments: ['myTable', { title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)' }, { charset: 'latin1' }],
          expectation: 'CREATE TABLE IF NOT EXISTS myTable (title ENUM("A", "B", "C"), name VARCHAR(255)) DEFAULT CHARSET=latin1;',
          context: { options: { quoteIdentifiers: false } },
        },
        {
          arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)' }, { rowFormat: 'default' }],
          expectation: 'CREATE TABLE IF NOT EXISTS myTable (title VARCHAR(255), name VARCHAR(255)) ROW_FORMAT=default;',
          context: { options: { quoteIdentifiers: false } },
        },
        {
          arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)', id: 'INTEGER PRIMARY KEY' }],
          expectation: 'CREATE TABLE IF NOT EXISTS myTable (title VARCHAR(255), name VARCHAR(255), id INTEGER , PRIMARY KEY (id));',
          context: { options: { quoteIdentifiers: false } },
        },
        {
          arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)', otherId: 'INTEGER REFERENCES otherTable (id) ON DELETE CASCADE ON UPDATE NO ACTION' }],
          expectation: 'CREATE TABLE IF NOT EXISTS myTable (title VARCHAR(255), name VARCHAR(255), otherId INTEGER, FOREIGN KEY (otherId) REFERENCES otherTable (id) ON DELETE CASCADE ON UPDATE NO ACTION);',
          context: { options: { quoteIdentifiers: false } },
        },
        {
          arguments: ['myTable', { title: 'VARCHAR(255)', name: 'VARCHAR(255)' }, { uniqueKeys: [{ fields: ['title', 'name'], customIndex: true }] }],
          expectation: 'CREATE TABLE IF NOT EXISTS myTable (title VARCHAR(255), name VARCHAR(255), UNIQUE uniq_myTable_title_name (title, name));',
          context: { options: { quoteIdentifiers: false } },
        },
      ],

      tableExistsQuery: [
        {
          arguments: ['myTable'],
          expectation: 'SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = \'BASE TABLE\' AND TABLE_SCHEMA = CURRENT_SCHEMA() AND TABLE_NAME = \'myTable\';',
        },
        {
          arguments: [{ tableName: 'myTable', schema: 'mySchema' }],
          expectation: 'SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = \'BASE TABLE\' AND TABLE_SCHEMA = \'mySchema\' AND TABLE_NAME = \'myTable\';',
        },
      ],

      selectQuery: [
        {
          arguments: ['myTable'],
          expectation: 'SELECT * FROM "myTable";',
          context: QueryGenerator,
        }, {
          arguments: ['myTable', { attributes: ['id', 'name'] }],
          expectation: 'SELECT "id", "name" FROM "myTable";',
          context: QueryGenerator,
        }, {
          arguments: ['myTable', { where: { id: 2 } }],
          expectation: 'SELECT * FROM "myTable" WHERE "myTable"."id" = 2;',
          context: QueryGenerator,
        }, {
          arguments: ['myTable', { where: { name: 'foo' } }],
          expectation: 'SELECT * FROM "myTable" WHERE "myTable"."name" = \'foo\';',
          context: QueryGenerator,
        }, {
          arguments: ['myTable', { where: { name: 'foo\';DROP TABLE myTable;' } }],
          expectation: 'SELECT * FROM "myTable" WHERE "myTable"."name" = \'foo\'\';DROP TABLE myTable;\';',
          context: QueryGenerator,
        }, {
          arguments: ['myTable', { where: 2 }],
          expectation: 'SELECT * FROM "myTable" WHERE "myTable"."id" = 2;',
          context: QueryGenerator,
        }, {
          arguments: ['foo', { attributes: [['count(*)', 'count']] }],
          expectation: 'SELECT count(*) AS "count" FROM "foo";',
          context: QueryGenerator,
        }, {
          arguments: ['myTable', { order: ['id'] }],
          expectation: 'SELECT * FROM "myTable" ORDER BY "id";',
          context: QueryGenerator,
        }, {
          arguments: ['myTable', { order: ['id', 'DESC'] }],
          expectation: 'SELECT * FROM "myTable" ORDER BY "id", "DESC";',
          context: QueryGenerator,
        }, {
          arguments: ['myTable', { order: ['myTable.id'] }],
          expectation: 'SELECT * FROM "myTable" ORDER BY "myTable"."id";',
          context: QueryGenerator,
        }, {
          arguments: ['myTable', { order: [['myTable.id', 'DESC']] }],
          expectation: 'SELECT * FROM "myTable" ORDER BY "myTable"."id" DESC;',
          context: QueryGenerator,
        }, {
          arguments: ['myTable', { order: [['id', 'DESC']] }, function (sequelize) {
            return sequelize.define('myTable', {});
          }],
          expectation: 'SELECT * FROM "myTable" AS "myTable" ORDER BY "myTable"."id" DESC;',
          context: QueryGenerator,
          needsSequelize: true,
        }, {
          arguments: ['myTable', { order: [['id', 'DESC'], ['name']] }, function (sequelize) {
            return sequelize.define('myTable', {});
          }],
          expectation: 'SELECT * FROM "myTable" AS "myTable" ORDER BY "myTable"."id" DESC, "myTable"."name";',
          context: QueryGenerator,
          needsSequelize: true,
        }, {
          title: 'functions can take functions as arguments',
          arguments: ['myTable', function (sequelize) {
            return {
              order: [[sequelize.fn('f1', sequelize.fn('f2', sequelize.col('id'))), 'DESC']],
            };
          }],
          expectation: 'SELECT * FROM "myTable" ORDER BY f1(f2("id")) DESC;',
          context: QueryGenerator,
          needsSequelize: true,
        }, {
          title: 'functions can take all types as arguments',
          arguments: ['myTable', function (sequelize) {
            return {
              order: [
                [sequelize.fn('f1', sequelize.col('myTable.id')), 'DESC'],
                [sequelize.fn('f2', 12, 'lalala', new Date(Date.UTC(2011, 2, 27, 10, 1, 55))), 'ASC'],
              ],
            };
          }],
          expectation: `SELECT * FROM "myTable" ORDER BY f1("myTable"."id") DESC, f2(12, 'lalala', '2011-03-27 10:01:55.000') ASC;`,
          context: QueryGenerator,
          needsSequelize: true,
        }, {
          title: 'sequelize.where with .fn as attribute and default comparator',
          arguments: ['myTable', function (sequelize) {
            return {
              where: sequelize.and(
                sequelize.where(sequelize.fn('LOWER', sequelize.col('user.name')), 'jan'),
                { type: 1 },
              ),
            };
          }],
          expectation: 'SELECT * FROM "myTable" WHERE (LOWER("user"."name") = \'jan\' AND "myTable"."type" = 1);',
          context: QueryGenerator,
          needsSequelize: true,
        }, {
          title: 'sequelize.where with .fn as attribute and LIKE comparator',
          arguments: ['myTable', function (sequelize) {
            return {
              where: sequelize.and(
                sequelize.where(sequelize.fn('LOWER', sequelize.col('user.name')), 'LIKE', '%t%'),
                { type: 1 },
              ),
            };
          }],
          expectation: 'SELECT * FROM "myTable" WHERE (LOWER("user"."name") LIKE \'%t%\' AND "myTable"."type" = 1);',
          context: QueryGenerator,
          needsSequelize: true,
        }, {
          title: 'single string argument should be quoted',
          arguments: ['myTable', { group: 'name' }],
          expectation: 'SELECT * FROM "myTable" GROUP BY "name";',
          context: QueryGenerator,
        }, {
          arguments: ['myTable', { group: ['name'] }],
          expectation: 'SELECT * FROM "myTable" GROUP BY "name";',
          context: QueryGenerator,
        }, {
          title: 'functions work for group by',
          arguments: ['myTable', function (sequelize) {
            return {
              group: [sequelize.fn('YEAR', sequelize.col('createdAt'))],
            };
          }],
          expectation: 'SELECT * FROM "myTable" GROUP BY YEAR("createdAt");',
          context: QueryGenerator,
          needsSequelize: true,
        }, {
          title: 'It is possible to mix sequelize.fn and string arguments to group by',
          arguments: ['myTable', function (sequelize) {
            return {
              group: [sequelize.fn('YEAR', sequelize.col('createdAt')), 'title'],
            };
          }],
          expectation: 'SELECT * FROM "myTable" GROUP BY YEAR("createdAt"), "title";',
          context: QueryGenerator,
          needsSequelize: true,
        }, {
          arguments: ['myTable', { group: 'name', order: [['id', 'DESC']] }],
          expectation: 'SELECT * FROM "myTable" GROUP BY "name" ORDER BY "id" DESC;',
          context: QueryGenerator,
        }, {
          title: 'HAVING clause works with where-like hash',
          arguments: ['myTable', function (sequelize) {
            return {
              attributes: ['*', [sequelize.fn('YEAR', sequelize.col('createdAt')), 'creationYear']],
              group: ['creationYear', 'title'],
              having: { creationYear: { [Op.gt]: 2002 } },
            };
          }],
          expectation: 'SELECT *, YEAR("createdAt") AS "creationYear" FROM "myTable" GROUP BY "creationYear", "title" HAVING "creationYear" > 2002;',
          context: QueryGenerator,
          needsSequelize: true,
        }, {
          title: 'Combination of sequelize.fn, sequelize.col and { in: ... }',
          arguments: ['myTable', function (sequelize) {
            return {
              where: sequelize.and(
                { archived: null },
                sequelize.where(sequelize.fn('COALESCE', sequelize.col('place_type_codename'), sequelize.col('announcement_type_codename')), { [Op.in]: ['Lost', 'Found'] }),
              ),
            };
          }],
          expectation: 'SELECT * FROM "myTable" WHERE ("myTable"."archived" IS NULL AND COALESCE("place_type_codename", "announcement_type_codename") IN (\'Lost\', \'Found\'));',
          context: QueryGenerator,
          needsSequelize: true,
        }, {
          arguments: ['myTable', { limit: 10 }],
          expectation: 'SELECT * FROM "myTable" LIMIT 10;',
          context: QueryGenerator,
        }, {
          arguments: ['myTable', { limit: 10, offset: 2 }],
          expectation: 'SELECT * FROM "myTable" LIMIT 10 OFFSET 2;',
          context: QueryGenerator,
        }, {
          title: 'uses default limit if only offset is specified',
          arguments: ['myTable', { offset: 2 }],
          expectation: 'SELECT * FROM "myTable" LIMIT NULL OFFSET 2;',
          context: QueryGenerator,
        }, {
          title: 'uses limit 0',
          arguments: ['myTable', { limit: 0 }],
          expectation: 'SELECT * FROM "myTable" LIMIT 0;',
          context: QueryGenerator,
        }, {
          title: 'uses offset 0',
          arguments: ['myTable', { offset: 0 }],
          expectation: 'SELECT * FROM "myTable";',
          context: QueryGenerator,
        }, {
          title: 'multiple where arguments',
          arguments: ['myTable', { where: { boat: 'canoe', weather: 'cold' } }],
          expectation: 'SELECT * FROM "myTable" WHERE "myTable"."boat" = \'canoe\' AND "myTable"."weather" = \'cold\';',
          context: QueryGenerator,
        }, {
          title: 'no where arguments (object)',
          arguments: ['myTable', { where: {} }],
          expectation: 'SELECT * FROM "myTable";',
          context: QueryGenerator,
        }, {
          title: 'no where arguments (string)',
          arguments: ['myTable', { where: [''] }],
          expectation: 'SELECT * FROM "myTable" WHERE 1=1;',
          context: QueryGenerator,
        }, {
          title: 'no where arguments (null)',
          arguments: ['myTable', { where: null }],
          expectation: 'SELECT * FROM "myTable";',
          context: QueryGenerator,
        }, {
          title: 'buffer as where argument',
          arguments: ['myTable', { where: { field: Buffer.from('Sequelize') } }],
          expectation: 'SELECT * FROM "myTable" WHERE "myTable"."field" = X\'53657175656c697a65\';',
          context: QueryGenerator,
        }, {
          title: 'use != if ne !== null',
          arguments: ['myTable', { where: { field: { [Op.ne]: 0 } } }],
          expectation: 'SELECT * FROM "myTable" WHERE "myTable"."field" != 0;',
          context: QueryGenerator,
        }, {
          title: 'use IS NOT if ne === null',
          arguments: ['myTable', { where: { field: { [Op.ne]: null } } }],
          expectation: 'SELECT * FROM "myTable" WHERE "myTable"."field" IS NOT NULL;',
          context: QueryGenerator,
        }, {
          title: 'use IS NOT if not === BOOLEAN',
          arguments: ['myTable', { where: { field: { [Op.not]: true } } }],
          expectation: 'SELECT * FROM "myTable" WHERE "myTable"."field" IS NOT true;',
          context: QueryGenerator,
        }, {
          title: 'use != if not !== BOOLEAN',
          arguments: ['myTable', { where: { field: { [Op.not]: 3 } } }],
          expectation: 'SELECT * FROM "myTable" WHERE "myTable"."field" != 3;',
          context: QueryGenerator,
        }, {
          title: 'Regular Expression in where clause',
          arguments: ['myTable', { where: { field: { [Op.regexp]: '^[h|a|t]' } } }],
          expectation: 'SELECT * FROM "myTable" WHERE "myTable"."field" REGEXP \'^[h|a|t]\';',
          context: QueryGenerator,
        }, {
          title: 'Regular Expression negation in where clause',
          arguments: ['myTable', { where: { field: { [Op.notRegexp]: '^[h|a|t]' } } }],
          expectation: 'SELECT * FROM "myTable" WHERE "myTable"."field" NOT REGEXP \'^[h|a|t]\';',
          context: QueryGenerator,
        }, {
          title: 'Empty having',
          arguments: ['myTable', function () {
            return {
              having: {},
            };
          }],
          expectation: 'SELECT * FROM "myTable";',
          context: QueryGenerator,
          needsSequelize: true,
        }, {
          title: 'Having in subquery',
          arguments: ['myTable', function () {
            return {
              subQuery: true,
              tableAs: 'test',
              having: { creationYear: { [Op.gt]: 2002 } },
            };
          }],
          expectation: 'SELECT "test".* FROM (SELECT * FROM "myTable" AS "test" HAVING "creationYear" > 2002) AS "test";',
          context: QueryGenerator,
          needsSequelize: true,
        }, {
          title: 'Contains fields with "." characters.',
          arguments: ['myTable', {
            attributes: ['foo.bar.baz'],
            model: {
              rawAttributes: {
                'foo.bar.baz': {},
              },
            },
          }],
          expectation: 'SELECT "foo.bar.baz" FROM "myTable";',
          context: QueryGenerator,
        },

        // Variants when quoteIdentifiers is false
        {
          arguments: ['myTable'],
          expectation: 'SELECT * FROM myTable;',
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', { attributes: ['id', 'name'] }],
          expectation: 'SELECT id, name FROM myTable;',
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', { where: { id: 2 } }],
          expectation: 'SELECT * FROM myTable WHERE myTable.id = 2;',
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', { where: { name: 'foo' } }],
          expectation: 'SELECT * FROM myTable WHERE myTable.name = \'foo\';',
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', { where: { name: 'foo\';DROP TABLE myTable;' } }],
          expectation: 'SELECT * FROM myTable WHERE myTable.name = \'foo\'\';DROP TABLE myTable;\';',
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', { where: 2 }],
          expectation: 'SELECT * FROM myTable WHERE myTable.id = 2;',
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['foo', { attributes: [['count(*)', 'count']] }],
          expectation: 'SELECT count(*) AS count FROM foo;',
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', { order: ['id'] }],
          expectation: 'SELECT * FROM myTable ORDER BY id;',
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', { order: ['id', 'DESC'] }],
          expectation: 'SELECT * FROM myTable ORDER BY id, DESC;',
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', { order: ['myTable.id'] }],
          expectation: 'SELECT * FROM myTable ORDER BY myTable.id;',
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', { order: [['myTable.id', 'DESC']] }],
          expectation: 'SELECT * FROM myTable ORDER BY myTable.id DESC;',
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', { order: [['id', 'DESC']] }, function (sequelize) {
            return sequelize.define('myTable', {});
          }],
          expectation: 'SELECT * FROM myTable AS myTable ORDER BY myTable.id DESC;',
          context: { options: { quoteIdentifiers: false } },
          needsSequelize: true,
        }, {
          arguments: ['myTable', { order: [['id', 'DESC'], ['name']] }, function (sequelize) {
            return sequelize.define('myTable', {});
          }],
          expectation: 'SELECT * FROM myTable AS myTable ORDER BY myTable.id DESC, myTable.name;',
          context: { options: { quoteIdentifiers: false } },
          needsSequelize: true,
        }, {
          title: 'functions can take functions as arguments',
          arguments: ['myTable', function (sequelize) {
            return {
              order: [[sequelize.fn('f1', sequelize.fn('f2', sequelize.col('id'))), 'DESC']],
            };
          }],
          expectation: 'SELECT * FROM myTable ORDER BY f1(f2(id)) DESC;',
          context: { options: { quoteIdentifiers: false } },
          needsSequelize: true,
        }, {
          title: 'functions can take all types as arguments',
          arguments: ['myTable', function (sequelize) {
            return {
              order: [
                [sequelize.fn('f1', sequelize.col('myTable.id')), 'DESC'],
                [sequelize.fn('f2', 12, 'lalala', new Date(Date.UTC(2011, 2, 27, 10, 1, 55))), 'ASC'],
              ],
            };
          }],
          expectation: `SELECT * FROM myTable ORDER BY f1(myTable.id) DESC, f2(12, 'lalala', '2011-03-27 10:01:55.000') ASC;`,
          context: { options: { quoteIdentifiers: false } },
          needsSequelize: true,
        }, {
          title: 'sequelize.where with .fn as attribute and default comparator',
          arguments: ['myTable', function (sequelize) {
            return {
              where: sequelize.and(
                sequelize.where(sequelize.fn('LOWER', sequelize.col('user.name')), 'jan'),
                { type: 1 },
              ),
            };
          }],
          expectation: 'SELECT * FROM myTable WHERE (LOWER(user.name) = \'jan\' AND myTable.type = 1);',
          context: { options: { quoteIdentifiers: false } },
          needsSequelize: true,
        }, {
          title: 'sequelize.where with .fn as attribute and LIKE comparator',
          arguments: ['myTable', function (sequelize) {
            return {
              where: sequelize.and(
                sequelize.where(sequelize.fn('LOWER', sequelize.col('user.name')), 'LIKE', '%t%'),
                { type: 1 },
              ),
            };
          }],
          expectation: 'SELECT * FROM myTable WHERE (LOWER(user.name) LIKE \'%t%\' AND myTable.type = 1);',
          context: { options: { quoteIdentifiers: false } },
          needsSequelize: true,
        }, {
          title: 'single string argument should be quoted',
          arguments: ['myTable', { group: 'name' }],
          expectation: 'SELECT * FROM myTable GROUP BY name;',
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', { group: ['name'] }],
          expectation: 'SELECT * FROM myTable GROUP BY name;',
          context: { options: { quoteIdentifiers: false } },
        }, {
          title: 'functions work for group by',
          arguments: ['myTable', function (sequelize) {
            return {
              group: [sequelize.fn('YEAR', sequelize.col('createdAt'))],
            };
          }],
          expectation: 'SELECT * FROM myTable GROUP BY YEAR(createdAt);',
          context: { options: { quoteIdentifiers: false } },
          needsSequelize: true,
        }, {
          title: 'It is possible to mix sequelize.fn and string arguments to group by',
          arguments: ['myTable', function (sequelize) {
            return {
              group: [sequelize.fn('YEAR', sequelize.col('createdAt')), 'title'],
            };
          }],
          expectation: 'SELECT * FROM myTable GROUP BY YEAR(createdAt), title;',
          context: { options: { quoteIdentifiers: false } },
          needsSequelize: true,
        }, {
          arguments: ['myTable', { group: 'name', order: [['id', 'DESC']] }],
          expectation: 'SELECT * FROM myTable GROUP BY name ORDER BY id DESC;',
          context: { options: { quoteIdentifiers: false } },
        }, {
          title: 'HAVING clause works with where-like hash',
          arguments: ['myTable', function (sequelize) {
            return {
              attributes: ['*', [sequelize.fn('YEAR', sequelize.col('createdAt')), 'creationYear']],
              group: ['creationYear', 'title'],
              having: { creationYear: { [Op.gt]: 2002 } },
            };
          }],
          expectation: 'SELECT *, YEAR(createdAt) AS creationYear FROM myTable GROUP BY creationYear, title HAVING creationYear > 2002;',
          context: { options: { quoteIdentifiers: false } },
          needsSequelize: true,
        }, {
          title: 'Combination of sequelize.fn, sequelize.col and { in: ... }',
          arguments: ['myTable', function (sequelize) {
            return {
              where: sequelize.and(
                { archived: null },
                sequelize.where(sequelize.fn('COALESCE', sequelize.col('place_type_codename'), sequelize.col('announcement_type_codename')), { [Op.in]: ['Lost', 'Found'] }),
              ),
            };
          }],
          expectation: 'SELECT * FROM myTable WHERE (myTable.archived IS NULL AND COALESCE(place_type_codename, announcement_type_codename) IN (\'Lost\', \'Found\'));',
          context: { options: { quoteIdentifiers: false } },
          needsSequelize: true,
        }, {
          arguments: ['myTable', { limit: 10 }],
          expectation: 'SELECT * FROM myTable LIMIT 10;',
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', { limit: 10, offset: 2 }],
          expectation: 'SELECT * FROM myTable LIMIT 10 OFFSET 2;',
          context: { options: { quoteIdentifiers: false } },
        }, {
          title: 'uses default limit if only offset is specified',
          arguments: ['myTable', { offset: 2 }],
          expectation: 'SELECT * FROM myTable LIMIT NULL OFFSET 2;',
          context: { options: { quoteIdentifiers: false } },
        }, {
          title: 'uses limit 0',
          arguments: ['myTable', { limit: 0 }],
          expectation: 'SELECT * FROM myTable LIMIT 0;',
          context: { options: { quoteIdentifiers: false } },
        }, {
          title: 'uses offset 0',
          arguments: ['myTable', { offset: 0 }],
          expectation: 'SELECT * FROM myTable;',
          context: { options: { quoteIdentifiers: false } },
        }, {
          title: 'multiple where arguments',
          arguments: ['myTable', { where: { boat: 'canoe', weather: 'cold' } }],
          expectation: 'SELECT * FROM myTable WHERE myTable.boat = \'canoe\' AND myTable.weather = \'cold\';',
          context: { options: { quoteIdentifiers: false } },
        }, {
          title: 'no where arguments (object)',
          arguments: ['myTable', { where: {} }],
          expectation: 'SELECT * FROM myTable;',
          context: { options: { quoteIdentifiers: false } },
        }, {
          title: 'no where arguments (string)',
          arguments: ['myTable', { where: [''] }],
          expectation: 'SELECT * FROM myTable WHERE 1=1;',
          context: { options: { quoteIdentifiers: false } },
        }, {
          title: 'no where arguments (null)',
          arguments: ['myTable', { where: null }],
          expectation: 'SELECT * FROM myTable;',
          context: { options: { quoteIdentifiers: false } },
        }, {
          title: 'buffer as where argument',
          arguments: ['myTable', { where: { field: Buffer.from('Sequelize') } }],
          expectation: 'SELECT * FROM myTable WHERE myTable.field = X\'53657175656c697a65\';',
          context: { options: { quoteIdentifiers: false } },
        }, {
          title: 'use != if ne !== null',
          arguments: ['myTable', { where: { field: { [Op.ne]: 0 } } }],
          expectation: 'SELECT * FROM myTable WHERE myTable.field != 0;',
          context: { options: { quoteIdentifiers: false } },
        }, {
          title: 'use IS NOT if ne === null',
          arguments: ['myTable', { where: { field: { [Op.ne]: null } } }],
          expectation: 'SELECT * FROM myTable WHERE myTable.field IS NOT NULL;',
          context: { options: { quoteIdentifiers: false } },
        }, {
          title: 'use IS NOT if not === BOOLEAN',
          arguments: ['myTable', { where: { field: { [Op.not]: true } } }],
          expectation: 'SELECT * FROM myTable WHERE myTable.field IS NOT true;',
          context: { options: { quoteIdentifiers: false } },
        }, {
          title: 'use != if not !== BOOLEAN',
          arguments: ['myTable', { where: { field: { [Op.not]: 3 } } }],
          expectation: 'SELECT * FROM myTable WHERE myTable.field != 3;',
          context: { options: { quoteIdentifiers: false } },
        }, {
          title: 'Regular Expression in where clause',
          arguments: ['myTable', { where: { field: { [Op.regexp]: '^[h|a|t]' } } }],
          expectation: 'SELECT * FROM myTable WHERE myTable.field REGEXP \'^[h|a|t]\';',
          context: { options: { quoteIdentifiers: false } },
        }, {
          title: 'Regular Expression negation in where clause',
          arguments: ['myTable', { where: { field: { [Op.notRegexp]: '^[h|a|t]' } } }],
          expectation: 'SELECT * FROM myTable WHERE myTable.field NOT REGEXP \'^[h|a|t]\';',
          context: { options: { quoteIdentifiers: false } },
        }, {
          title: 'Empty having',
          arguments: ['myTable', function () {
            return {
              having: {},
            };
          }],
          expectation: 'SELECT * FROM myTable;',
          context: { options: { quoteIdentifiers: false } },
          needsSequelize: true,
        }, {
          title: 'Having in subquery',
          arguments: ['myTable', function () {
            return {
              subQuery: true,
              tableAs: 'test',
              having: { creationYear: { [Op.gt]: 2002 } },
            };
          }],
          expectation: 'SELECT test.* FROM (SELECT * FROM myTable AS test HAVING creationYear > 2002) AS test;',
          context: { options: { quoteIdentifiers: false } },
          needsSequelize: true,
        }, {
          title: 'Contains fields with "." characters.',
          arguments: ['myTable', {
            attributes: ['foo.bar.baz'],
            model: {
              rawAttributes: {
                'foo.bar.baz': {},
              },
            },
          }],
          expectation: 'SELECT "foo.bar.baz" FROM myTable;',
          context: { options: { quoteIdentifiers: false } },
        },
      ],

      insertQuery: [
        {
          arguments: ['myTable', { name: 'foo' }],
          expectation: {
            query: 'INSERT INTO "myTable" ("name") VALUES ($sequelize_1);',
            bind: { sequelize_1: 'foo' },
          },
        }, {
          arguments: ['myTable', { name: 'foo\';DROP TABLE myTable;' }],
          expectation: {
            query: 'INSERT INTO "myTable" ("name") VALUES ($sequelize_1);',
            bind: { sequelize_1: 'foo\';DROP TABLE myTable;' },
          },
        }, {
          arguments: ['myTable', { name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55)) }],
          expectation: {
            query: 'INSERT INTO "myTable" ("name","birthday") VALUES ($sequelize_1,$sequelize_2);',
            bind: { sequelize_1: 'foo', sequelize_2: new Date(Date.UTC(2011, 2, 27, 10, 1, 55)) },
          },
        }, {
          arguments: ['myTable', { name: 'foo', foo: 1 }],
          expectation: {
            query: 'INSERT INTO "myTable" ("name","foo") VALUES ($sequelize_1,$sequelize_2);',
            bind: { sequelize_1: 'foo', sequelize_2: 1 },
          },
        }, {
          arguments: ['myTable', { data: Buffer.from('Sequelize') }],
          expectation: {
            query: 'INSERT INTO "myTable" ("data") VALUES ($sequelize_1);',
            bind: { sequelize_1: Buffer.from('Sequelize') },
          },
        }, {
          arguments: ['myTable', { name: 'foo', foo: 1, nullValue: null }],
          expectation: {
            query: 'INSERT INTO "myTable" ("name","foo","nullValue") VALUES ($sequelize_1,$sequelize_2,$sequelize_3);',
            bind: { sequelize_1: 'foo', sequelize_2: 1, sequelize_3: null },
          },
        }, {
          arguments: ['myTable', { name: 'foo', foo: 1, nullValue: null }],
          expectation: {
            query: 'INSERT INTO "myTable" ("name","foo","nullValue") VALUES ($sequelize_1,$sequelize_2,$sequelize_3);',
            bind: { sequelize_1: 'foo', sequelize_2: 1, sequelize_3: null },
          },
          context: { options: { omitNull: false } },
        }, {
          arguments: ['myTable', { name: 'foo', foo: 1, nullValue: null }],
          expectation: {
            query: 'INSERT INTO "myTable" ("name","foo") VALUES ($sequelize_1,$sequelize_2);',
            bind: { sequelize_1: 'foo', sequelize_2: 1 },
          },
          context: { options: { omitNull: true } },
        }, {
          arguments: ['myTable', { name: 'foo', foo: 1, nullValue: undefined }],
          expectation: {
            query: 'INSERT INTO "myTable" ("name","foo") VALUES ($sequelize_1,$sequelize_2);',
            bind: { sequelize_1: 'foo', sequelize_2: 1 },
          },
          context: { options: { omitNull: true } },
        }, {
          arguments: ['myTable', { foo: false }],
          expectation: {
            query: 'INSERT INTO "myTable" ("foo") VALUES ($sequelize_1);',
            bind: { sequelize_1: false },
          },
        }, {
          arguments: ['myTable', { foo: true }],
          expectation: {
            query: 'INSERT INTO "myTable" ("foo") VALUES ($sequelize_1);',
            bind: { sequelize_1: true },
          },
        }, {
          arguments: ['myTable', function (sequelize) {
            return {
              foo: sequelize.fn('NOW'),
            };
          }],
          expectation: {
            query: 'INSERT INTO "myTable" ("foo") VALUES (NOW());',
            bind: {},
          },
          needsSequelize: true,
        },

        // Variants when quoteIdentifiers is false
        {
          arguments: ['myTable', { name: 'foo' }],
          expectation: {
            query: 'INSERT INTO myTable (name) VALUES ($sequelize_1);',
            bind: { sequelize_1: 'foo' },
          },
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', { name: 'foo\';DROP TABLE myTable;' }],
          expectation: {
            query: 'INSERT INTO myTable (name) VALUES ($sequelize_1);',
            bind: { sequelize_1: 'foo\';DROP TABLE myTable;' },
          },
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', { name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55)) }],
          expectation: {
            query: 'INSERT INTO myTable (name,birthday) VALUES ($sequelize_1,$sequelize_2);',
            bind: { sequelize_1: 'foo', sequelize_2: new Date(Date.UTC(2011, 2, 27, 10, 1, 55)) },
          },
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', { name: 'foo', foo: 1 }],
          expectation: {
            query: 'INSERT INTO myTable (name,foo) VALUES ($sequelize_1,$sequelize_2);',
            bind: { sequelize_1: 'foo', sequelize_2: 1 },
          },
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', { data: Buffer.from('Sequelize') }],
          expectation: {
            query: 'INSERT INTO myTable (data) VALUES ($sequelize_1);',
            bind: { sequelize_1: Buffer.from('Sequelize') },
          },
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', { name: 'foo', foo: 1, nullValue: null }],
          expectation: {
            query: 'INSERT INTO myTable (name,foo,nullValue) VALUES ($sequelize_1,$sequelize_2,$sequelize_3);',
            bind: { sequelize_1: 'foo', sequelize_2: 1, sequelize_3: null },
          },
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', { name: 'foo', foo: 1, nullValue: null }],
          expectation: {
            query: 'INSERT INTO myTable (name,foo,nullValue) VALUES ($sequelize_1,$sequelize_2,$sequelize_3);',
            bind: { sequelize_1: 'foo', sequelize_2: 1, sequelize_3: null },
          },
          context: { options: { omitNull: false, quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', { name: 'foo', foo: 1, nullValue: null }],
          expectation: {
            query: 'INSERT INTO myTable (name,foo) VALUES ($sequelize_1,$sequelize_2);',
            bind: { sequelize_1: 'foo', sequelize_2: 1 },
          },
          context: { options: { omitNull: true, quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', { name: 'foo', foo: 1, nullValue: undefined }],
          expectation: {
            query: 'INSERT INTO myTable (name,foo) VALUES ($sequelize_1,$sequelize_2);',
            bind: { sequelize_1: 'foo', sequelize_2: 1 },
          },
          context: { options: { omitNull: true, quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', { foo: false }],
          expectation: {
            query: 'INSERT INTO myTable (foo) VALUES ($sequelize_1);',
            bind: { sequelize_1: false },
          },
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', { foo: true }],
          expectation: {
            query: 'INSERT INTO myTable (foo) VALUES ($sequelize_1);',
            bind: { sequelize_1: true },
          },
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', function (sequelize) {
            return {
              foo: sequelize.fn('NOW'),
            };
          }],
          expectation: {
            query: 'INSERT INTO myTable (foo) VALUES (NOW());',
            bind: {},
          },
          needsSequelize: true,
          context: { options: { quoteIdentifiers: false } },
        },
      ],

      bulkInsertQuery: [
        {
          arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }]],
          expectation: 'INSERT INTO "myTable" ("name") VALUES (\'foo\'),(\'bar\');',
        }, {
          arguments: ['myTable', [{ name: 'foo\';DROP TABLE myTable;' }, { name: 'bar' }]],
          expectation: 'INSERT INTO "myTable" ("name") VALUES (\'foo\'\';DROP TABLE myTable;\'),(\'bar\');',
        }, {
          arguments: ['myTable', [{ name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55)) }, { name: 'bar', birthday: new Date(Date.UTC(2012, 2, 27, 10, 1, 55)) }]],
          expectation: `INSERT INTO "myTable" ("name","birthday") VALUES ('foo','2011-03-27 10:01:55.000'),('bar','2012-03-27 10:01:55.000');`,
        }, {
          arguments: ['myTable', [{ name: 'foo', foo: 1 }, { name: 'bar', foo: 2 }]],
          expectation: 'INSERT INTO "myTable" ("name","foo") VALUES (\'foo\',1),(\'bar\',2);',
        }, {
          arguments: ['myTable', [{ name: 'foo', foo: 1, nullValue: null }, { name: 'bar', nullValue: null }]],
          expectation: 'INSERT INTO "myTable" ("name","foo","nullValue") VALUES (\'foo\',1,NULL),(\'bar\',NULL,NULL);',
        }, {
          arguments: ['myTable', [{ name: 'foo', foo: 1, nullValue: null }, { name: 'bar', foo: 2, nullValue: null }]],
          expectation: 'INSERT INTO "myTable" ("name","foo","nullValue") VALUES (\'foo\',1,NULL),(\'bar\',2,NULL);',
          context: { options: { omitNull: false } },
        }, {
          arguments: ['myTable', [{ name: 'foo', foo: 1, nullValue: null }, { name: 'bar', foo: 2, nullValue: null }]],
          expectation: 'INSERT INTO "myTable" ("name","foo","nullValue") VALUES (\'foo\',1,NULL),(\'bar\',2,NULL);',
          context: { options: { omitNull: true } }, // Note: We don't honour this because it makes little sense when some rows may have nulls and others not
        }, {
          arguments: ['myTable', [{ name: 'foo', foo: 1, nullValue: undefined }, { name: 'bar', foo: 2, undefinedValue: undefined }]],
          expectation: 'INSERT INTO "myTable" ("name","foo","nullValue","undefinedValue") VALUES (\'foo\',1,NULL,NULL),(\'bar\',2,NULL,NULL);',
          context: { options: { omitNull: true } }, // Note: As above
        }, {
          arguments: ['myTable', [{ name: 'foo', value: true }, { name: 'bar', value: false }]],
          expectation: 'INSERT INTO "myTable" ("name","value") VALUES (\'foo\',true),(\'bar\',false);',
        }, {
          arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }], { ignoreDuplicates: true }],
          expectation: 'INSERT IGNORE INTO "myTable" ("name") VALUES (\'foo\'),(\'bar\');',
        },

        // Variants when quoteIdentifiers is false
        {
          arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }]],
          expectation: 'INSERT INTO myTable (name) VALUES (\'foo\'),(\'bar\');',
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', [{ name: 'foo\';DROP TABLE myTable;' }, { name: 'bar' }]],
          expectation: 'INSERT INTO myTable (name) VALUES (\'foo\'\';DROP TABLE myTable;\'),(\'bar\');',
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', [{ name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55)) }, { name: 'bar', birthday: new Date(Date.UTC(2012, 2, 27, 10, 1, 55)) }]],
          expectation: `INSERT INTO myTable (name,birthday) VALUES ('foo','2011-03-27 10:01:55.000'),('bar','2012-03-27 10:01:55.000');`,
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', [{ name: 'foo', foo: 1 }, { name: 'bar', foo: 2 }]],
          expectation: 'INSERT INTO myTable (name,foo) VALUES (\'foo\',1),(\'bar\',2);',
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', [{ name: 'foo', foo: 1, nullValue: null }, { name: 'bar', nullValue: null }]],
          expectation: 'INSERT INTO myTable (name,foo,nullValue) VALUES (\'foo\',1,NULL),(\'bar\',NULL,NULL);',
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', [{ name: 'foo', foo: 1, nullValue: null }, { name: 'bar', foo: 2, nullValue: null }]],
          expectation: 'INSERT INTO myTable (name,foo,nullValue) VALUES (\'foo\',1,NULL),(\'bar\',2,NULL);',
          context: { options: { omitNull: false, quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', [{ name: 'foo', foo: 1, nullValue: null }, { name: 'bar', foo: 2, nullValue: null }]],
          expectation: 'INSERT INTO myTable (name,foo,nullValue) VALUES (\'foo\',1,NULL),(\'bar\',2,NULL);',
          context: { options: { omitNull: true, quoteIdentifiers: false } }, // Note: We don't honour this because it makes little sense when some rows may have nulls and others not
        }, {
          arguments: ['myTable', [{ name: 'foo', foo: 1, nullValue: undefined }, { name: 'bar', foo: 2, undefinedValue: undefined }]],
          expectation: 'INSERT INTO myTable (name,foo,nullValue,undefinedValue) VALUES (\'foo\',1,NULL,NULL),(\'bar\',2,NULL,NULL);',
          context: { options: { omitNull: true, quoteIdentifiers: false } }, // Note: As above
        }, {
          arguments: ['myTable', [{ name: 'foo', value: true }, { name: 'bar', value: false }]],
          expectation: 'INSERT INTO myTable (name,value) VALUES (\'foo\',true),(\'bar\',false);',
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', [{ name: 'foo' }, { name: 'bar' }], { ignoreDuplicates: true }],
          expectation: 'INSERT IGNORE INTO myTable (name) VALUES (\'foo\'),(\'bar\');',
          context: { options: { quoteIdentifiers: false } },
        },
      ],

      updateQuery: [
        {
          arguments: ['myTable', { name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55)) }, { id: 2 }],
          expectation: {
            query: 'UPDATE "myTable" SET "name"=$sequelize_1,"birthday"=$sequelize_2 WHERE "id" = $sequelize_3',
            bind: { sequelize_1: 'foo', sequelize_2: new Date(Date.UTC(2011, 2, 27, 10, 1, 55)), sequelize_3: 2 },
          },

        }, {
          arguments: ['myTable', { name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55)) }, { id: 2 }],
          expectation: {
            query: 'UPDATE "myTable" SET "name"=$sequelize_1,"birthday"=$sequelize_2 WHERE "id" = $sequelize_3',
            bind: { sequelize_1: 'foo', sequelize_2: new Date(Date.UTC(2011, 2, 27, 10, 1, 55)), sequelize_3: 2 },
          },
        }, {
          arguments: ['myTable', { bar: 2 }, { name: 'foo' }],
          expectation: {
            query: 'UPDATE "myTable" SET "bar"=$sequelize_1 WHERE "name" = $sequelize_2',
            bind: { sequelize_1: 2, sequelize_2: 'foo' },
          },
        }, {
          arguments: ['myTable', { name: 'foo\';DROP TABLE myTable;' }, { name: 'foo' }],
          expectation: {
            query: 'UPDATE "myTable" SET "name"=$sequelize_1 WHERE "name" = $sequelize_2',
            bind: { sequelize_1: 'foo\';DROP TABLE myTable;', sequelize_2: 'foo' },
          },
        }, {
          arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }],
          expectation: {
            query: 'UPDATE "myTable" SET "bar"=$sequelize_1,"nullValue"=$sequelize_2 WHERE "name" = $sequelize_3',
            bind: { sequelize_1: 2, sequelize_2: null, sequelize_3: 'foo' },
          },
        }, {
          arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }],
          expectation: {
            query: 'UPDATE "myTable" SET "bar"=$sequelize_1,"nullValue"=$sequelize_2 WHERE "name" = $sequelize_3',
            bind: { sequelize_1: 2, sequelize_2: null, sequelize_3: 'foo' },
          },
          context: { options: { omitNull: false } },
        }, {
          arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }],
          expectation: {
            query: 'UPDATE "myTable" SET "bar"=$sequelize_1 WHERE "name" = $sequelize_2',
            bind: { sequelize_1: 2, sequelize_2: 'foo' },
          },
          context: { options: { omitNull: true } },
        }, {
          arguments: ['myTable', { bar: false }, { name: 'foo' }],
          expectation: {
            query: 'UPDATE "myTable" SET "bar"=$sequelize_1 WHERE "name" = $sequelize_2',
            bind: { sequelize_1: false, sequelize_2: 'foo' },
          },
        }, {
          arguments: ['myTable', { bar: true }, { name: 'foo' }],
          expectation: {
            query: 'UPDATE "myTable" SET "bar"=$sequelize_1 WHERE "name" = $sequelize_2',
            bind: { sequelize_1: true, sequelize_2: 'foo' },
          },
        }, {
          arguments: ['myTable', function (sequelize) {
            return {
              bar: sequelize.fn('NOW'),
            };
          }, { name: 'foo' }],
          expectation: {
            query: 'UPDATE "myTable" SET "bar"=NOW() WHERE "name" = $sequelize_1',
            bind: { sequelize_1: 'foo' },
          },
          needsSequelize: true,
        }, {
          arguments: ['myTable', function (sequelize) {
            return {
              bar: sequelize.col('foo'),
            };
          }, { name: 'foo' }],
          expectation: {
            query: 'UPDATE "myTable" SET "bar"="foo" WHERE "name" = $sequelize_1',
            bind: { sequelize_1: 'foo' },
          },
          needsSequelize: true,
        },

        // Variants when quoteIdentifiers is false
        {
          arguments: ['myTable', { name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55)) }, { id: 2 }],
          expectation: {
            query: 'UPDATE myTable SET name=$sequelize_1,birthday=$sequelize_2 WHERE id = $sequelize_3',
            bind: { sequelize_1: 'foo', sequelize_2: new Date(Date.UTC(2011, 2, 27, 10, 1, 55)), sequelize_3: 2 },
          },
          context: { options: { quoteIdentifiers: false } },

        }, {
          arguments: ['myTable', { name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55)) }, { id: 2 }],
          expectation: {
            query: 'UPDATE myTable SET name=$sequelize_1,birthday=$sequelize_2 WHERE id = $sequelize_3',
            bind: { sequelize_1: 'foo', sequelize_2: new Date(Date.UTC(2011, 2, 27, 10, 1, 55)), sequelize_3: 2 },
          },
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', { bar: 2 }, { name: 'foo' }],
          expectation: {
            query: 'UPDATE myTable SET bar=$sequelize_1 WHERE name = $sequelize_2',
            bind: { sequelize_1: 2, sequelize_2: 'foo' },
          },
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', { name: 'foo\';DROP TABLE myTable;' }, { name: 'foo' }],
          expectation: {
            query: 'UPDATE myTable SET name=$sequelize_1 WHERE name = $sequelize_2',
            bind: { sequelize_1: 'foo\';DROP TABLE myTable;', sequelize_2: 'foo' },
          },
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }],
          expectation: {
            query: 'UPDATE myTable SET bar=$sequelize_1,nullValue=$sequelize_2 WHERE name = $sequelize_3',
            bind: { sequelize_1: 2, sequelize_2: null, sequelize_3: 'foo' },
          },
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }],
          expectation: {
            query: 'UPDATE myTable SET bar=$sequelize_1,nullValue=$sequelize_2 WHERE name = $sequelize_3',
            bind: { sequelize_1: 2, sequelize_2: null, sequelize_3: 'foo' },
          },
          context: { options: { omitNull: false, quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', { bar: 2, nullValue: null }, { name: 'foo' }],
          expectation: {
            query: 'UPDATE myTable SET bar=$sequelize_1 WHERE name = $sequelize_2',
            bind: { sequelize_1: 2,  sequelize_2: 'foo' },
          },
          context: { options: { omitNull: true, quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', { bar: false }, { name: 'foo' }],
          expectation: {
            query: 'UPDATE myTable SET bar=$sequelize_1 WHERE name = $sequelize_2',
            bind: { sequelize_1: false, sequelize_2: 'foo' },
          },
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', { bar: true }, { name: 'foo' }],
          expectation: {
            query: 'UPDATE myTable SET bar=$sequelize_1 WHERE name = $sequelize_2',
            bind: { sequelize_1: true, sequelize_2: 'foo' },
          },
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', function (sequelize) {
            return {
              bar: sequelize.fn('NOW'),
            };
          }, { name: 'foo' }],
          expectation: {
            query: 'UPDATE myTable SET bar=NOW() WHERE name = $sequelize_1',
            bind: { sequelize_1: 'foo' },
          },
          needsSequelize: true,
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: ['myTable', function (sequelize) {
            return {
              bar: sequelize.col('foo'),
            };
          }, { name: 'foo' }],
          expectation: {
            query: 'UPDATE myTable SET bar=foo WHERE name = $sequelize_1',
            bind: { sequelize_1: 'foo' },
          },
          needsSequelize: true,
          context: { options: { quoteIdentifiers: false } },
        },
      ],

      getForeignKeyQuery: [
        {
          arguments: ['User', 'email'],
          expectation: 'SELECT CONSTRAINT_NAME as constraint_name,CONSTRAINT_NAME as constraintName,CONSTRAINT_SCHEMA as constraintSchema,CONSTRAINT_SCHEMA as constraintCatalog,TABLE_NAME as tableName,TABLE_SCHEMA as tableSchema,TABLE_SCHEMA as tableCatalog,COLUMN_NAME as columnName,REFERENCED_TABLE_SCHEMA as referencedTableSchema,REFERENCED_TABLE_SCHEMA as referencedTableCatalog,REFERENCED_TABLE_NAME as referencedTableName,REFERENCED_COLUMN_NAME as referencedColumnName FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE (REFERENCED_TABLE_NAME = \'User\' AND REFERENCED_COLUMN_NAME = \'email\') OR (TABLE_NAME = \'User\' AND COLUMN_NAME = \'email\' AND REFERENCED_TABLE_NAME IS NOT NULL)',
        },

        // Variants when quoteIdentifiers is false
        {
          arguments: ['User', 'email'],
          expectation: 'SELECT CONSTRAINT_NAME as constraint_name,CONSTRAINT_NAME as constraintName,CONSTRAINT_SCHEMA as constraintSchema,CONSTRAINT_SCHEMA as constraintCatalog,TABLE_NAME as tableName,TABLE_SCHEMA as tableSchema,TABLE_SCHEMA as tableCatalog,COLUMN_NAME as columnName,REFERENCED_TABLE_SCHEMA as referencedTableSchema,REFERENCED_TABLE_SCHEMA as referencedTableCatalog,REFERENCED_TABLE_NAME as referencedTableName,REFERENCED_COLUMN_NAME as referencedColumnName FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE (REFERENCED_TABLE_NAME = \'User\' AND REFERENCED_COLUMN_NAME = \'email\') OR (TABLE_NAME = \'User\' AND COLUMN_NAME = \'email\' AND REFERENCED_TABLE_NAME IS NOT NULL)',
          context: { options: { quoteIdentifiers: false } },

        },
      ],

      selectFromTableFragment: [
        {
          arguments: [{}, null, ['*'], '"Project"'],
          expectation: 'SELECT * FROM "Project"',
        }, {
          arguments: [
            { indexHints: [{ type: IndexHints.USE, values: ['index_project_on_name'] }] },
            null,
            ['*'],
            '"Project"',
          ],
          expectation: 'SELECT * FROM "Project" USE INDEX ("index_project_on_name")',
        }, {
          arguments: [
            { indexHints: [{ type: IndexHints.FORCE, values: ['index_project_on_name'] }] },
            null,
            ['*'],
            '"Project"',
          ],
          expectation: 'SELECT * FROM "Project" FORCE INDEX ("index_project_on_name")',
        }, {
          arguments: [
            { indexHints: [{ type: IndexHints.IGNORE, values: ['index_project_on_name'] }] },
            null,
            ['*'],
            '"Project"',
          ],
          expectation: 'SELECT * FROM "Project" IGNORE INDEX ("index_project_on_name")',
        }, {
          arguments: [
            { indexHints: [{ type: IndexHints.USE, values: ['index_project_on_name', 'index_project_on_name_and_foo'] }] },
            null,
            ['*'],
            '"Project"',
          ],
          expectation: 'SELECT * FROM "Project" USE INDEX ("index_project_on_name","index_project_on_name_and_foo")',
        }, {
          arguments: [
            { indexHints: [{ type: 'FOO', values: ['index_project_on_name'] }] },
            null,
            ['*'],
            '"Project"',
          ],
          expectation: 'SELECT * FROM "Project"',
        },

        // Variants when quoteIdentifiers is false
        {
          arguments: [{}, null, ['*'], 'Project'],
          expectation: 'SELECT * FROM Project',
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: [
            { indexHints: [{ type: IndexHints.USE, values: ['index_project_on_name'] }] },
            null,
            ['*'],
            'Project',
          ],
          expectation: 'SELECT * FROM Project USE INDEX (index_project_on_name)',
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: [
            { indexHints: [{ type: IndexHints.FORCE, values: ['index_project_on_name'] }] },
            null,
            ['*'],
            'Project',
          ],
          expectation: 'SELECT * FROM Project FORCE INDEX (index_project_on_name)',
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: [
            { indexHints: [{ type: IndexHints.IGNORE, values: ['index_project_on_name'] }] },
            null,
            ['*'],
            'Project',
          ],
          expectation: 'SELECT * FROM Project IGNORE INDEX (index_project_on_name)',
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: [
            { indexHints: [{ type: IndexHints.USE, values: ['index_project_on_name', 'index_project_on_name_and_foo'] }] },
            null,
            ['*'],
            'Project',
          ],
          expectation: 'SELECT * FROM Project USE INDEX (index_project_on_name,index_project_on_name_and_foo)',
          context: { options: { quoteIdentifiers: false } },
        }, {
          arguments: [
            { indexHints: [{ type: 'FOO', values: ['index_project_on_name'] }] },
            null,
            ['*'],
            'Project',
          ],
          expectation: 'SELECT * FROM Project',
          context: { options: { quoteIdentifiers: false } },
        },
      ],
    };

    _.each(suites, (tests, suiteTitle) => {
      describe(suiteTitle, () => {
        beforeEach(function () {
          this.queryGenerator = new QueryGenerator({
            sequelize: this.sequelize,
            dialect: this.sequelize.dialect,
          });
        });

        for (const test of tests) {
          const query = test.expectation.query || test.expectation;
          const title = test.title || `SNOWFLAKE correctly returns ${query} for ${JSON.stringify(test.arguments)}`;
          it(title, function () {
            if (test.needsSequelize) {
              if (typeof test.arguments[1] === 'function') {
                test.arguments[1] = test.arguments[1](this.sequelize);
              }

              if (typeof test.arguments[2] === 'function') {
                test.arguments[2] = test.arguments[2](this.sequelize);
              }
            }

            // Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly
            this.queryGenerator.options = { ...this.queryGenerator.options, ...test.context && test.context.options };

            const conditions = this.queryGenerator[suiteTitle](...test.arguments);
            expect(conditions).to.deep.equal(test.expectation);
          });
        }
      });
    });
  });
}