"use strict";

/* jshint camelcase: false */
/* jshint expr: true */
var chai      = require('chai')
  , Sequelize = require('../../index')
  , Promise   = Sequelize.Promise
  , expect    = chai.expect
  , Support   = require(__dirname + '/../support')
  , DataTypes = require(__dirname + "/../../lib/data-types")
  , dialect   = Support.getTestDialect()
  , datetime  = require('chai-datetime');

chai.use(datetime);
chai.config.includeStack = true;

describe(Support.getTestDialectTeaser("Model"), function () {
  describe('attributes', function () {
    describe('field', function () {
      beforeEach(function () {
        var queryInterface = this.sequelize.getQueryInterface();

        this.User = this.sequelize.define('user', {
          id: {
            type: DataTypes.INTEGER,
            allowNull: false,
            primaryKey: true,
            autoIncrement: true,
            field: 'userId'
          },
          name: {
            type: DataTypes.STRING,
            field: 'full_name'
          }
        }, {
          tableName: 'users',
          timestamps: false
        });

        this.Task = this.sequelize.define('task', {
          id: {
            type: DataTypes.INTEGER,
            allowNull: false,
            primaryKey: true,
            autoIncrement: true,
            field: 'taskId'
          },
          title: {
            type: DataTypes.STRING,
            field: 'name'
          }
        }, {
          tableName: 'tasks',
          timestamps: false
        });

        this.Comment = this.sequelize.define('comment', {
          id: {
            type: DataTypes.INTEGER,
            allowNull: false,
            primaryKey: true,
            autoIncrement: true,
            field: 'commentId'
          },
          text: {
            type: DataTypes.STRING,
            field: 'comment_text'
          },
		  notes: {
			type: DataTypes.STRING,
			field: 'notes'
		  }
        }, {
          tableName: 'comments',
          timestamps: false
        });

        this.User.hasMany(this.Task, {
          foreignKey: 'user_id'
        });
        this.Task.belongsTo(this.User, {
          foreignKey: 'user_id'
        });
        this.Task.hasMany(this.Comment, {
          foreignKey: 'task_id'
        });
        this.Comment.belongsTo(this.Task, {
          foreignKey: 'task_id'
        });

        return Promise.all([
          queryInterface.createTable('users', {
            userId: {
              type: DataTypes.INTEGER,
              allowNull: false,
              primaryKey: true,
              autoIncrement: true
            },
            full_name: {
              type: DataTypes.STRING
            }
          }),
          queryInterface.createTable('tasks', {
            taskId: {
              type: DataTypes.INTEGER,
              allowNull: false,
              primaryKey: true,
              autoIncrement: true
            },
            user_id: {
              type: DataTypes.INTEGER
            },
            name: {
              type: DataTypes.STRING
            }
          }),
          queryInterface.createTable('comments', {
            commentId: {
              type: DataTypes.INTEGER,
              allowNull: false,
              primaryKey: true,
              autoIncrement: true
            },
            task_id: {
              type: DataTypes.INTEGER
            },
            comment_text: {
              type: DataTypes.STRING
            },
			notes: {
			  type: DataTypes.STRING
			}
          })
        ]);
      });

      it('should create, fetch and update with alternative field names from a simple model', function () {
        var self = this;

        return this.User.create({
          name: 'Foobar'
        }).then(function () {
          return self.User.find({
            limit: 1
          });
        }).then(function (user) {
          expect(user.get('name')).to.equal('Foobar');
          return user.updateAttributes({
            name: 'Barfoo'
          });
        }).then(function () {
          return self.User.find({
            limit: 1
          });
        }).then(function (user) {
          expect(user.get('name')).to.equal('Barfoo');
        });
      });

      it('should make the aliased auto incremented primary key available after create', function () {
        var self = this;
        return this.User.create({
          name: 'Barfoo'
        }).then(function (user) {
          expect(user.get('id')).to.be.ok;
        });
      });

      it('should work with where on includes for find', function () {
        var self = this;

        return this.User.create({
          name: 'Barfoo'
        }).then(function (user) {
          return user.createTask({
            title: 'DatDo'
          });
        }).then(function (task) {
          return task.createComment({
            text: 'Comment'
          });
        }).then(function () {
          return self.Task.find({
            include: [
              {model: self.Comment},
              {model: self.User}
            ],
            where: {title: 'DatDo'}
          });
        }).then(function (task) {
          expect(task.get('title')).to.equal('DatDo');
          expect(task.get('comments')[0].get('text')).to.equal('Comment');
          expect(task.get('user')).to.be.ok;
        });
      });

      it('should work with where on includes for findAll', function () {
        var self = this;

        return this.User.create({
          name: 'Foobar'
        }).then(function (user) {
          return user.createTask({
            title: 'DoDat'
          });
        }).then(function (task) {
          return task.createComment({
            text: 'Comment'
          });
        }).then(function () {
          return self.User.findAll({
            include: [
              {model: self.Task, where: {title: 'DoDat'}, include: [
                {model: self.Comment}
              ]}
            ]
          });
        }).then(function (users) {
          users.forEach(function (user) {
            expect(user.get('name')).to.be.ok;
            expect(user.get('tasks')[0].get('title')).to.equal('DoDat');
            expect(user.get('tasks')[0].get('comments')).to.be.ok;
          });
        });
      });

      it('should work with a simple where', function () {
        var self = this;

        return this.User.create({
          name: 'Foobar'
        }).then(function () {
          return self.User.find({
            where: {
              name: 'Foobar'
            }
          });
        }).then(function (user) {
          expect(user).to.be.ok;
        });
      });

      it('should work with bulkCreate and findAll', function () {
        var self = this;
        return this.User.bulkCreate([{
          name: 'Abc',
        }, {
          name: 'Bcd'
        }, {
          name: 'Cde'
        }]).then(function () {
          return self.User.findAll();
        }).then(function (users) {
          users.forEach(function (user) {
            expect(['Abc', 'Bcd', 'Cde'].indexOf(user.get('name')) !== -1).to.be.true;
          });
        });
      });

      it('should support renaming of sequelize method fields', function () {
        var Test = this.sequelize.define('test', {
          someProperty: Sequelize.VIRTUAL // Since we specify the AS part as a part of the literal string, not with sequelize syntax, we have to tell sequelize about the field
        });

        return this.sequelize.sync({ force: true }).then(function () {
          return Test.create({});
        }).then(function () {
          return Test.findAll({
            attributes: [
              Sequelize.literal('EXISTS(SELECT 1) AS "someProperty"'),
              [Sequelize.literal('EXISTS(SELECT 1)'), 'someProperty2']
            ]
          });
        }).then(function (tests) {
          expect(tests[0].get('someProperty')).to.be.ok;
          expect(tests[0].get('someProperty2')).to.be.ok;
        });
      });

      it('should sync foreign keys with custom field names', function() {
        return this.sequelize.sync({ force: true })
        .then(function() {
          var attrs = this.Task.tableAttributes;
          expect(attrs.user_id.references).to.equal('users');
          expect(attrs.user_id.referencesKey).to.equal('userId');
        }.bind(this));
      });

      it('should find the value of an attribute with a custom field name', function() {
        return this.User.create({ name: 'test user' })
        .then(function() {
          return this.User.find({ where: { name: 'test user' } });
        }.bind(this))
        .then(function(user) {
          expect(user.name).to.equal('test user');
        });
      });

      it('field names that are the same as property names should create, update, and read correctly', function () {
        var self = this;

        return this.Comment.create({
          notes: 'Foobar'
        }).then(function () {
          return self.Comment.find({
            limit: 1
          });
        }).then(function (comment) {
          expect(comment.get('notes')).to.equal('Foobar');
          return comment.updateAttributes({
            notes: 'Barfoo'
          });
        }).then(function () {
          return self.Comment.find({
            limit: 1
          });
        }).then(function (comment) {
          expect(comment.get('notes')).to.equal('Barfoo');
        });
      });

    });

    describe('types', function () {
      describe('VIRTUAL', function () {
        beforeEach(function () {
          this.User = this.sequelize.define('user', {
            storage: Sequelize.STRING,
            field1: {
              type: Sequelize.VIRTUAL,
              set: function (val) {
                this.setDataValue('storage', val);
                this.setDataValue('field1', val);
              },
              get: function () {
                return this.getDataValue('field1');
              }
            },
            field2: {
              type: Sequelize.VIRTUAL,
              get: function () {
                return 42;
              }
            },
            virtualWithDefault: {
              type: Sequelize.VIRTUAL,
              defaultValue: 'cake'
            }
          }, { timestamps: false });

          this.Task = this.sequelize.define('task', {});
          this.Project = this.sequelize.define('project', {});

          this.Task.belongsTo(this.User);
          this.Project.hasMany(this.User);
          this.User.hasMany(this.Project);

          this.sqlAssert = function(sql) {
            expect(sql.indexOf('field1')).to.equal(-1);
            expect(sql.indexOf('field2')).to.equal(-1);
          };

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

        it('should not be ignored in dataValues get', function () {
          var user = this.User.build({
            field1: 'field1_value',
            field2: 'field2_value'
          });

          expect(user.get()).to.deep.equal({ storage: 'field1_value', field1: 'field1_value', virtualWithDefault: 'cake', field2: 42, id: null });
        });

        it('should be ignored in table creation', function () {
          return this.sequelize.getQueryInterface().describeTable(this.User.tableName).then(function (fields) {
            expect(Object.keys(fields).length).to.equal(2);
          });
        });

        it('should be ignored in find, findAll and includes', function () {
          return Promise.all([
            this.User.find().on('sql', this.sqlAssert),
            this.User.findAll().on('sql', this.sqlAssert),
            this.Task.findAll({
              include: [
                this.User
              ]
            }).on('sql', this.sqlAssert),
            this.Project.findAll({
              include: [
                this.User
              ]
            }).on('sql', this.sqlAssert)
          ]);
        });

        it("should allow me to store selected values", function () {
          var Post = this.sequelize.define('Post', {
              text: Sequelize.TEXT,
              someBoolean: {
                type: Sequelize.VIRTUAL
              }
            });

          return this.sequelize.sync({ force: true}).then(function () {
            return Post.bulkCreate([{ text: 'text1' },{ text: 'text2' }]);
          }).then(function () {
            return Post.find({ attributes: ['id','text',Sequelize.literal('EXISTS(SELECT 1) AS "someBoolean"')] });
          }).then(function (post) {
            expect(post.get('someBoolean')).to.be.ok;
            expect(post.get().someBoolean).to.be.ok;
          });
        });

        it('should be ignored in create and updateAttributes', function () {
          return this.User.create({
            field1: 'something'
          }).then(function (user) {
            // We already verified that the virtual is not added to the table definition, so if this succeeds, were good

            expect(user.virtualWithDefault).to.equal('cake');
            expect(user.storage).to.equal('something');
            return user.updateAttributes({
              field1: 'something else'
            });
          }).then(function (user) {
            expect(user.virtualWithDefault).to.equal('cake');
            expect(user.storage).to.equal('something else');
          });
        });

        it('should be ignored in bulkCreate and and bulkUpdate', function () {
          var self = this;
          return this.User.bulkCreate([{
            field1: 'something'
          }]).on('sql', this.sqlAssert).then(function () {
            return self.User.findAll();
          }).then(function (users) {
            expect(users[0].storage).to.equal('something');
          });
        });
      });
    });
  });
});


keep keep keep replace replace replace keep

/* jshint camelcase: false */
/* jshint expr: true */
var chai      = require('chai')
  , Sequelize = require('../../index')
  , Promise   = Sequelize.Promise
  , expect    = chai.expect
  , Support   = require(__dirname + '/../support')
  , DataTypes = require(__dirname + "/../../lib/data-types")
  , dialect   = Support.getTestDialect()
  , datetime  = require('chai-datetime')
  , _         = require('lodash')
  , assert    = require('assert')

chai.use(datetime)
chai.config.includeStack = true

describe(Support.getTestDialectTeaser("DAOFactory"), function () {
  beforeEach(function () {
    return Support.prepareTransactionTest(this.sequelize).bind(this).then(function(sequelize) {
      this.sequelize = sequelize;

      this.User = this.sequelize.define('User', {
        username:     DataTypes.STRING,
        secretValue:  DataTypes.STRING,
        data:         DataTypes.STRING,
        intVal:       DataTypes.INTEGER,
        theDate:      DataTypes.DATE,
        aBool:        DataTypes.BOOLEAN,
        uniqueName:   { type: DataTypes.STRING, unique: true }
      })
      this.Account = this.sequelize.define('Account', {
        accountName: DataTypes.STRING
      });
      this.Student = this.sequelize.define('Student', {
          no:     {type:DataTypes.INTEGER,primaryKey:true},
          name: {type:DataTypes.STRING,allowNull:false},
      })

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

  describe('findOrCreate', function () {
    it("supports transactions", function(done) {
      var self = this;
      this.sequelize.transaction().then(function(t) {
        self.User.findOrCreate({ where: { username: 'Username' }, defaults: { data: 'some data' }}, { transaction: t }).then(function() {
          self.User.count().success(function(count) {
            expect(count).to.equal(0)
            t.commit().success(function() {
              self.User.count().success(function(count) {
                expect(count).to.equal(1)
                done()
              })
            })
          })
        })
      })
    })

    it("supports more than one models per transaction", function(done) {
      var self = this;
      this.sequelize.transaction().then(function(t) {
        self.User.findOrCreate({ where: { username: 'Username'}, defaults: { data: 'some data' }}, { transaction: t }).then(function() {
          self.Account.findOrCreate({ where: { accountName: 'accountName'}}, { transaction: t}).then(function(){
            t.commit().success(function() {
              done()
            })
          })
        })
      })
    })

    it("returns instance if already existent. Single find field.", function(done) {
      var self = this,
        data = {
          username: 'Username'
        };

      this.User.create(data).success(function (user) {
        self.User.findOrCreate({ where: {
          username: user.username
        }}).spread(function (_user, created) {
          expect(_user.id).to.equal(user.id)
          expect(_user.username).to.equal('Username')
          expect(created).to.be.false
          done()
        })
      })
    })

    it("Returns instance if already existent. Multiple find fields.", function(done) {
      var self = this,
        data = {
          username: 'Username',
          data: 'ThisIsData'
        };

      this.User.create(data).success(function (user) {
        self.User.findOrCreate({where: data}).done(function (err, _user, created) {
          expect(_user.id).to.equal(user.id)
          expect(_user.username).to.equal('Username')
          expect(_user.data).to.equal('ThisIsData')
          expect(created).to.be.false
          done()
        })
      })
    })

    it("creates new instance with default value.", function(done) {
      var data = {
          username: 'Username'
        },
        default_values = {
          data: 'ThisIsData'
        };

      this.User.findOrCreate({ where: data, defaults: default_values}).success(function(user, created) {
        expect(user.username).to.equal('Username')
        expect(user.data).to.equal('ThisIsData')
        expect(created).to.be.true
        done()
      })
    })

    it("supports .or() (only using default values)", function (done) {
      this.User.findOrCreate({
        where: Sequelize.or({username: 'Fooobzz'}, {secretValue: 'Yolo'}),
        defaults: {username: 'Fooobzz', secretValue: 'Yolo'}
      }).done(function (err, user, created) {
        expect(err).not.to.be.ok
        expect(user.username).to.equal('Fooobzz')
        expect(user.secretValue).to.equal('Yolo')
        expect(created).to.be.true

        done()
      })
    })

    it("should release transaction when meeting errors", function(done){
        var self = this

        var test = function(times) {
            times = times || 0
            if (times > 10) {
                return true //  stop trying
            }

            /*without name, trigger ValidationError */
            return self.Student.findOrCreate({where:{no:1},defaults:{}})
            .timeout(1000)
            .catch(Promise.TimeoutError,function(e){
                throw new Error(e)
            })
            .then(function(){
                return true
            })
            .catch(Sequelize.ValidationError,function(err){
                return test(times+1)
            })
        }

        test()
        .then(function(){
            done()
        })
        .catch(function(err){
            done(err)
        })
    })

    describe('several concurrent calls', function () {
      it('works with a transaction', function () {
        return this.sequelize.transaction().bind(this).then(function (transaction) {
          return Promise.join(
            this.User.findOrCreate({ where: { uniqueName: 'winner' }}, { transaction: transaction }),
            this.User.findOrCreate({ where: { uniqueName: 'winner' }}, { transaction: transaction }),
            function (first, second) {

              expect(first[0]).to.be.ok;
              expect(first[1]).to.be.ok;
              expect(second[0]).to.be.ok;
              expect(second[1]).not.to.be.ok;

              expect(first[0].id).to.equal(second[0].id);

              return transaction.commit();
            }
          );
        });
      });

      // Creating two concurrent transactions and selecting / inserting from the same table throws sqlite off
      (dialect !== 'sqlite' ? it : it.skip)('works without a transaction', function () {
        return Promise.join(
          this.User.findOrCreate({ where: { uniqueName: 'winner' }}),
          this.User.findOrCreate({ where: { uniqueName: 'winner' }}),
          function (first, second) {
            expect(first[0]).to.be.ok;
            expect(first[1]).to.be.ok;
            expect(second[0]).to.be.ok;
            expect(second[1]).not.to.be.ok;

            expect(first[0].id).to.equal(second[0].id);
          }
        );
      });
    });
  });

  describe('create', function() {
    it('works with non-integer primary keys with a default value', function (done) {
      var User = this.sequelize.define('User', {
        'id': {
          primaryKey: true,
          type: DataTypes.UUID,
          defaultValue: DataTypes.UUIDV4
        },
        'email': {
          type: DataTypes.UUID,
          defaultValue: DataTypes.UUIDV4
        }
      })

      this.sequelize.sync({force: true}).done(function (err) {
        expect(err).not.to.be.ok
        User.create({}).done(function (err, user) {
          expect(err).not.to.be.ok
          expect(user).to.be.ok
          expect(user.id).to.be.ok
          done()
        })
      })
    })

    it('should return an error for a unique constraint error', function (done) {
      var User = this.sequelize.define('User', {
        'email': {
          type: DataTypes.STRING,
          unique: { name: 'email', msg: 'Email is already registered.' },
          validate: {
            notEmpty: true,
            isEmail: true
          }
        }
      })

      this.sequelize.sync({force: true}).done(function (err) {
        expect(err).not.to.be.ok
        User.create({email: 'hello@sequelize.com'}).done(function (err) {
          expect(err).not.to.be.ok
          User.create({email: 'hello@sequelize.com'}).then(function () {
            assert(false)
          }, function (err) {
            expect(err).to.be.ok
            expect(err).to.be.an.instanceof(Error)
            done()
          })
        })
      })
    })

    it('supports transactions', function(done) {
      var self = this;
      this.sequelize.transaction().then(function(t) {
        self.User.create({ username: 'user' }, { transaction: t }).success(function() {
          self.User.count().success(function(count) {
            expect(count).to.equal(0)
            t.commit().success(function() {
              self.User.count().success(function(count) {
                expect(count).to.equal(1)
                done()
              })
            })
          })
        })
      })
    })

    it('is possible to use casting when creating an instance', function (done) {
      var self = this
        , type = Support.dialectIsMySQL() ? 'signed' : 'integer'
        , _done = _.after(2, function() {
          done()
        })

      this.User.create({
        intVal: this.sequelize.cast('1', type)
      }).on('sql', function (sql) {
        expect(sql).to.match(new RegExp('CAST\\(1 AS ' + type.toUpperCase() + '\\)'))
        _done()
      })
      .success(function (user) {
        self.User.find(user.id).success(function (user) {
          expect(user.intVal).to.equal(1)
          _done()
        })
      })
    })

    it('is possible to use casting multiple times mixed in with other utilities', function (done) {
      var self  = this
        , type  = this.sequelize.cast(this.sequelize.cast(this.sequelize.literal('1-2'), 'integer'), 'integer')
        , _done = _.after(2, function() {
          done()
        })

      if (Support.dialectIsMySQL()) {
        type = this.sequelize.cast(this.sequelize.cast(this.sequelize.literal('1-2'), 'unsigned'), 'signed')
      }

      this.User.create({
        intVal: type
      }).on('sql', function (sql) {
        if (Support.dialectIsMySQL()) {
          expect(sql).to.contain('CAST(CAST(1-2 AS UNSIGNED) AS SIGNED)')
        } else {
          expect(sql).to.contain('CAST(CAST(1-2 AS INTEGER) AS INTEGER)')
        }

        _done()
      }).success(function (user) {
        self.User.find(user.id).success(function (user) {
          expect(user.intVal).to.equal(-1)
          _done()
        })
      })
    })

    it('is possible to just use .literal() to bypass escaping', function (done) {
      var self = this

      this.User.create({
        intVal: this.sequelize.literal('CAST(1-2 AS ' + (Support.dialectIsMySQL() ? 'SIGNED' : 'INTEGER') + ')')
      }).success(function (user) {
        self.User.find(user.id).success(function (user) {
          expect(user.intVal).to.equal(-1)
          done()
        })
      })
    })

    it('is possible for .literal() to contain other utility functions', function (done) {
      var self = this

      this.User.create({
        intVal: this.sequelize.literal(this.sequelize.cast('1-2', (Support.dialectIsMySQL() ? 'SIGNED' : 'INTEGER')))
      }).success(function (user) {
        self.User.find(user.id).success(function (user) {
          expect(user.intVal).to.equal(-1)
          done()
        })
      })
    })

    it('is possible to use funtions when creating an instance', function (done) {
      var self = this
      this.User.create({
        secretValue: this.sequelize.fn('upper', 'sequelize')
      }).success(function (user) {
        self.User.find(user.id).success(function (user) {
          expect(user.secretValue).to.equal('SEQUELIZE')
          done()
        })
      })
    })

    it('is possible to use functions as default values', function (done) {
      var self = this
        , userWithDefaults

      if (dialect.indexOf('postgres') === 0) {
        this.sequelize.query('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"').success(function() {
          userWithDefaults = self.sequelize.define('userWithDefaults', {
            uuid: {
              type: 'UUID',
              defaultValue: self.sequelize.fn('uuid_generate_v4')
            }
          })

          userWithDefaults.sync({force: true}).success(function () {
            userWithDefaults.create({}).success(function (user) {
              // uuid validation regex taken from http://stackoverflow.com/a/13653180/800016
              expect(user.uuid).to.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i)
              done()
            })
          })
        })
      } else if (dialect === 'sqlite') {
        // The definition here is a bit hacky. sqlite expects () around the expression for default values, so we call a function without a name
        // to enclose the date function in (). http://www.sqlite.org/syntaxdiagrams.html#column-constraint
        userWithDefaults = self.sequelize.define('userWithDefaults', {
          year: {
            type: Sequelize.STRING,
            defaultValue: self.sequelize.fn('', self.sequelize.fn('date', 'now'))
          }
        })

        userWithDefaults.sync({force: true}).done(function (err) {
          expect(err).not.to.be.ok
          userWithDefaults.create({}).done(function (err, user) {
            expect(err).not.to.be.ok
            userWithDefaults.find(user.id).done(function (err, user) {
              expect(err).not.to.be.ok
              var now = new Date()
                , pad = function (number) {
                  if (number > 9) {
                    return number
                  }
                  return '0' + number
                }

              expect(user.year).to.equal(now.getUTCFullYear() + '-' + pad(now.getUTCMonth() + 1) + '-' + pad(now.getUTCDate()))
              done()
            })
          })
        })
      } else {
        // functions as default values are not supported in mysql, see http://stackoverflow.com/a/270338/800016
        done()
      }
    })

    it("casts empty arrays correctly for postgresql insert", function(done) {
      if (dialect !== "postgres") {
        expect('').to.equal('')
        return done()
      }

      var User = this.sequelize.define('UserWithArray', {
        myvals: { type: Sequelize.ARRAY(Sequelize.INTEGER) },
        mystr: { type: Sequelize.ARRAY(Sequelize.STRING) }
      })

      User.sync({force: true}).success(function() {
        User.create({myvals: [], mystr: []}).on('sql', function(sql){
          expect(sql.indexOf('ARRAY[]::INTEGER[]')).to.be.above(-1)
          expect(sql.indexOf('ARRAY[]::VARCHAR[]')).to.be.above(-1)
          done()
        })
      })
    })

    it("casts empty array correct for postgres update", function(done) {
      if (dialect !== "postgres") {
        expect('').to.equal('')
        return done()
      }

      var User = this.sequelize.define('UserWithArray', {
        myvals: { type: Sequelize.ARRAY(Sequelize.INTEGER) },
        mystr: { type: Sequelize.ARRAY(Sequelize.STRING) }
      })

      User.sync({force: true}).success(function() {
        User.create({myvals: [1,2,3,4], mystr: ["One", "Two", "Three", "Four"]}).on('success', function(user){
         user.myvals = []
          user.mystr = []
          user.save().on('sql', function(sql) {
            expect(sql.indexOf('ARRAY[]::INTEGER[]')).to.be.above(-1)
            expect(sql.indexOf('ARRAY[]::VARCHAR[]')).to.be.above(-1)
            done()
          })
        })
      })
    })

    it("doesn't allow duplicated records with unique:true", function(done) {
      var self = this
        , User = this.sequelize.define('UserWithUniqueUsername', {
            username: { type: Sequelize.STRING, unique: true }
          })

      User.sync({ force: true }).success(function() {
        User.create({ username:'foo' }).success(function() {
          User.create({ username: 'foo' }).catch(self.sequelize.UniqueConstraintError, function(err) {
            done()
          })
        })
      })
    })

    it("raises an error if created object breaks definition contraints", function(done) {
      var UserNull = this.sequelize.define('UserWithNonNullSmth', {
        username: { type: Sequelize.STRING, unique: true },
        smth:     { type: Sequelize.STRING, allowNull: false }
      })

      this.sequelize.options.omitNull = false

      UserNull.sync({ force: true }).success(function() {
        UserNull.create({ username: 'foo2', smth: null }).error(function(err) {
          expect(err).to.exist

          expect(err.get('smth')[0].path).to.equal('smth');
          if (Support.dialectIsMySQL()) {
            // We need to allow two different errors for MySQL, see:
            // http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_trans_tables
            expect(err.get('smth')[0].type).to.match(/notNull Violation/)
          }
          else if (dialect === "sqlite") {
            expect(err.get('smth')[0].type).to.match(/notNull Violation/)
          } else {
            expect(err.get('smth')[0].type).to.match(/notNull Violation/)
          }
          done()
        })
      })
    })
    it("raises an error if created object breaks definition contraints", function(done) {
      var self = this
        , UserNull = this.sequelize.define('UserWithNonNullSmth', {
            username: { type: Sequelize.STRING, unique: true },
            smth:     { type: Sequelize.STRING, allowNull: false }
          })

      this.sequelize.options.omitNull = false

      UserNull.sync({ force: true }).success(function() {
        UserNull.create({ username: 'foo', smth: 'foo' }).success(function() {
          UserNull.create({ username: 'foo', smth: 'bar' }).catch(self.sequelize.UniqueConstraintError, function(err) {
            done()
          })
        })
      })
    })

    it("raises an error if saving an empty string into a column allowing null or URL", function(done) {
      var StringIsNullOrUrl = this.sequelize.define('StringIsNullOrUrl', {
        str: { type: Sequelize.STRING, allowNull: true, validate: { isURL: true } }
      })

      this.sequelize.options.omitNull = false

      StringIsNullOrUrl.sync({ force: true }).success(function() {
        StringIsNullOrUrl.create({ str: null }).success(function(str1) {
          expect(str1.str).to.be.null
          StringIsNullOrUrl.create({ str: 'http://sequelizejs.org' }).success(function(str2) {
            expect(str2.str).to.equal('http://sequelizejs.org')
            StringIsNullOrUrl.create({ str: '' }).error(function(err) {
              expect(err).to.exist
              expect(err.get('str')[0].message).to.match(/Validation isURL failed/)

              done()
            })
          })
        }).error(done)
      })
    })

    it('raises an error if you mess up the datatype', function(done) {
      var self = this
      expect(function() {
        self.sequelize.define('UserBadDataType', {
          activity_date: Sequelize.DATe
        })
      }).to.throw(Error, 'Unrecognized data type for field activity_date')

      expect(function() {
        self.sequelize.define('UserBadDataType', {
          activity_date: {type: Sequelize.DATe}
        })
      }).to.throw(Error, 'Unrecognized data type for field activity_date')
      done()
    })

    it('sets a 64 bit int in bigint', function(done) {
      var User = this.sequelize.define('UserWithBigIntFields', {
        big: Sequelize.BIGINT
      })

      User.sync({ force: true }).success(function() {
        User.create({ big: '9223372036854775807' }).on('success', function(user) {
          expect(user.big).to.be.equal( '9223372036854775807' )
          done()
        })
      })
    })

    it('sets auto increment fields', function(done) {
      var User = this.sequelize.define('UserWithAutoIncrementField', {
        userid: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true, allowNull: false }
      })

      User.sync({ force: true }).success(function() {
        User.create({}).on('success', function(user) {
          expect(user.userid).to.equal(1)

          User.create({}).on('success', function(user) {
            expect(user.userid).to.equal(2)
            done()
          })
        })
      })
    })

    it('allows the usage of options as attribute', function(done) {
      var User = this.sequelize.define('UserWithNameAndOptions', {
        name: Sequelize.STRING,
        options: Sequelize.TEXT
      })

      var options = JSON.stringify({ foo: 'bar', bar: 'foo' })

      User.sync({ force: true }).success(function() {
        User
          .create({ name: 'John Doe', options: options })
          .success(function(user) {
            expect(user.options).to.equal(options)
            done()
          })
      })
    })

    it('allows sql logging', function(done) {
      var User = this.sequelize.define('UserWithUniqueNameAndNonNullSmth', {
        name: {type: Sequelize.STRING, unique: true},
        smth: {type: Sequelize.STRING, allowNull: false}
      })

      User.sync({ force: true }).success(function() {
        User
          .create({ name: 'Fluffy Bunny', smth: 'else' })
          .on('sql', function(sql) {
            expect(sql).to.exist
            expect(sql.toUpperCase().indexOf("INSERT")).to.be.above(-1)
            done()
          })
      })
    })

    it('should only store the values passed in the whitelist', function(done) {
      var self = this
        , data = { username: 'Peter', secretValue: '42' }

      this.User.create(data, { fields: ['username'] }).success(function(user) {
        self.User.find(user.id).success(function(_user) {
          expect(_user.username).to.equal(data.username)
          expect(_user.secretValue).not.to.equal(data.secretValue)
          expect(_user.secretValue).to.equal(null)
          done()
        })
      })
    })

    it('should store all values if no whitelist is specified', function(done) {
      var self = this
        , data = { username: 'Peter', secretValue: '42' }

      this.User.create(data).success(function(user) {
        self.User.find(user.id).success(function(_user) {
          expect(_user.username).to.equal(data.username)
          expect(_user.secretValue).to.equal(data.secretValue)
          done()
        })
      })
    })

    it('can omit autoincremental columns', function(done) {
      var self = this
        , data = { title: 'Iliad' }
        , dataTypes = [Sequelize.INTEGER, Sequelize.BIGINT]
        , chain = new Sequelize.Utils.QueryChainer()
        , chain2 = new Sequelize.Utils.QueryChainer()
        , books = []

      dataTypes.forEach(function(dataType, index) {
        books[index] = self.sequelize.define('Book'+index, {
          id: { type: dataType, primaryKey: true, autoIncrement: true },
          title: Sequelize.TEXT
        })
      })

      books.forEach(function(b) {
        chain.add(b.sync({ force: true }))
      })

      chain.run().success(function() {
        books.forEach(function(b) {
          chain2.add(b.create(data))
        })
        chain2.run().success(function(results) {
          results.forEach(function(book, index) {
            expect(book.title).to.equal(data.title)
            expect(book.author).to.equal(data.author)
            expect(books[index].rawAttributes.id.type.toString())
              .to.equal(dataTypes[index].toString())
          })
          done()
        })
      })
    })

    it('saves data with single quote', function(done) {
      var quote = "single'quote"
        , self  = this

      this.User.create({ data: quote }).success(function(user) {
        expect(user.data).to.equal(quote)

        self.User.find({where: { id: user.id }}).success(function(user) {
          expect(user.data).to.equal(quote)
          done()
        })
      })
    })

    it('saves data with double quote', function(done) {
      var quote = 'double"quote'
        , self  = this

      this.User.create({ data: quote }).success(function(user) {
        expect(user.data).to.equal(quote)

        self.User.find({where: { id: user.id }}).success(function(user) {
          expect(user.data).to.equal(quote)
          done()
        })
      })
    })

    it('saves stringified JSON data', function(done) {
      var json = JSON.stringify({ key: 'value' })
        , self = this

      this.User.create({ data: json }).success(function(user) {
        expect(user.data).to.equal(json)
        self.User.find({where: { id: user.id }}).success(function(user) {
          expect(user.data).to.equal(json)
          done()
        })
      })
    })

    it('stores the current date in createdAt', function(done) {
      this.User.create({ username: 'foo' }).success(function(user) {
        expect(parseInt(+user.createdAt/5000, 10)).to.be.closeTo(parseInt(+new Date()/5000, 10), 1.5)
        done()
      })
    })

    it('allows setting custom IDs', function(done) {
      var self = this
      this.User.create({ id: 42 }).success(function (user) {
        expect(user.id).to.equal(42)

        self.User.find(42).success(function (user) {
          expect(user).to.exist
          done()
        })
      })
    })

    it('should allow blank creates (with timestamps: false)', function (done) {
      var Worker = this.sequelize.define('Worker', {}, {timestamps: false})
      Worker.sync().done(function() {
        Worker.create({}, {fields: []}).done(function (err, worker) {
          expect(err).not.to.be.ok
          expect(worker).to.be.ok
          done()
        })
      })
    })

    it('should allow truly blank creates', function (done) {
      var Worker = this.sequelize.define('Worker', {}, {timestamps: false})
      Worker.sync().done(function() {
        Worker.create({}, {fields: []}).done(function (err, worker) {
          expect(err).not.to.be.ok
          expect(worker).to.be.ok
          done()
        })
      })
    })

    it('should only set passed fields', function (done) {
      var User = this.sequelize.define('User', {
        'email': {
          type: DataTypes.STRING
        },
        'name': {
          type: DataTypes.STRING
        }
      })

      this.sequelize.sync({force: true}).done(function (err) {
        expect(err).not.to.be.ok;

        User.create({
          name: 'Yolo Bear',
          email: 'yolo@bear.com'
        }, {
          fields: ['name']
        }).done(function (err, user) {
          expect(err).not.to.be.ok;
          expect(user.name).to.be.ok;
          expect(user.email).not.to.be.ok;

          User.find(user.id).done(function (err, user) {
            expect(err).not.to.be.ok;
            expect(user.name).to.be.ok;
            expect(user.email).not.to.be.ok;
            done();
          });
        });
      });
    });

    describe('enums', function() {
      it('correctly restores enum values', function(done) {
        var self = this
          , Item = self.sequelize.define('Item', {
          state: { type: Sequelize.ENUM, values: ['available', 'in_cart', 'shipped'] }
        })

        Item.sync({ force: true }).success(function() {
          Item.create({ state: 'available' }).success(function(_item) {
            Item.find({ where: { state: 'available' }}).success(function(item) {
              expect(item.id).to.equal(_item.id)
              done()
            })
          })
        })
      })

      it('allows null values', function(done) {
        var Enum = this.sequelize.define('Enum', {
          state: {
            type: Sequelize.ENUM,
            values: ['happy', 'sad'],
            allowNull: true
          }
        })

        Enum.sync({ force: true }).success(function() {
          Enum.create({state: null}).success(function(_enum) {
            expect(_enum.state).to.be.null
            done()
          })
        })
      })

      describe('when defined via { field: Sequelize.ENUM }', function() {
        it('allows values passed as parameters', function(done) {
          var Enum = this.sequelize.define('Enum', {
            state: Sequelize.ENUM('happy', 'sad')
          })

          Enum.sync({ force: true }).success(function() {
            Enum.create({ state: 'happy' }).success(function() {
              done()
            });
          });
        })

        it('allows values passed as an array', function(done) {
          var Enum = this.sequelize.define('Enum', {
            state: Sequelize.ENUM(['happy', 'sad'])
          })

          Enum.sync({ force: true }).success(function() {
            Enum.create({ state: 'happy' }).success(function() {
              done()
            });
          });
        })
      })

      describe('when defined via { field: { type: Sequelize.ENUM } }', function() {
        it('allows values passed as parameters', function(done) {
          var Enum = this.sequelize.define('Enum', {
            state: {
              type: Sequelize.ENUM('happy', 'sad')
            }
          })

          Enum.sync({ force: true }).success(function() {
            Enum.create({ state: 'happy' }).success(function() {
              done()
            });
          });
        })

        it('allows values passed as an array', function(done) {
          var Enum = this.sequelize.define('Enum', {
            state: {
              type: Sequelize.ENUM(['happy', 'sad'])
            }
          })

          Enum.sync({ force: true }).success(function() {
            Enum.create({ state: 'happy' }).success(function() {
              done()
            });
          });
        })
      })

      describe('can safely sync multiple times', function() {
        it('through the factory', function(done) {
          var Enum = this.sequelize.define('Enum', {
            state: {
              type: Sequelize.ENUM,
              values: ['happy', 'sad'],
              allowNull: true
            }
          })

          Enum.sync({ force: true }).success(function() {
            Enum.sync().success(function() {
              Enum.sync({ force: true }).complete(done)
            })
          })
        })

        it('through sequelize', function(done) {
          var self = this
            , Enum = this.sequelize.define('Enum', {
            state: {
              type: Sequelize.ENUM,
              values: ['happy', 'sad'],
              allowNull: true
            }
          })

          this.sequelize.sync({ force: true }).success(function() {
            self.sequelize.sync().success(function() {
              self.sequelize.sync({ force: true }).complete(done)
            })
          })
        })
      })
    })
  })

  describe('bulkCreate', function() {
    it("supports transactions", function(done) {
      var self = this;
      this.sequelize.transaction().then(function(t) {
        self.User
          .bulkCreate([{ username: 'foo' }, { username: 'bar' }], { transaction: t })
          .success(function() {
            self.User.count().success(function(count1) {
              self.User.count({ transaction: t }).success(function(count2) {
                expect(count1).to.equal(0)
                expect(count2).to.equal(2)
                t.rollback().success(function(){ done() })
              })
            })
          })
      })
    })

    it('properly handles disparate field lists', function(done) {
      var self = this
        , data = [{username: 'Peter', secretValue: '42' },
                  {username: 'Paul'},
                  {username: 'Steve'}]

      this.User.bulkCreate(data).success(function() {
        self.User.findAll({where: {username: 'Paul'}}).success(function(users) {
          expect(users.length).to.equal(1)
          expect(users[0].username).to.equal("Paul")
          expect(users[0].secretValue).to.be.null
          done()
        })
      })
    })

    it('inserts multiple values respecting the white list', function(done) {
      var self = this
        , data = [{ username: 'Peter', secretValue: '42' },
                  { username: 'Paul', secretValue: '23'}]

      this.User.bulkCreate(data, { fields: ['username'] }).success(function() {
        self.User.findAll({order: 'id'}).success(function(users) {
          expect(users.length).to.equal(2)
          expect(users[0].username).to.equal("Peter")
          expect(users[0].secretValue).to.be.null;
          expect(users[1].username).to.equal("Paul")
          expect(users[1].secretValue).to.be.null;
          done()
        })
      })
    })

    it('should store all values if no whitelist is specified', function(done) {
      var self = this
        , data = [{ username: 'Peter', secretValue: '42' },
                  { username: 'Paul', secretValue: '23'}]

      this.User.bulkCreate(data).success(function() {
        self.User.findAll({order: 'id'}).success(function(users) {
          expect(users.length).to.equal(2)
          expect(users[0].username).to.equal("Peter")
          expect(users[0].secretValue).to.equal('42')
          expect(users[1].username).to.equal("Paul")
          expect(users[1].secretValue).to.equal('23')
          done()
        })
      })
    })

    it('saves data with single quote', function(done) {
      var self = this
        , quote = "Single'Quote"
        , data = [{ username: 'Peter', data: quote},
                  { username: 'Paul', data: quote}]

      this.User.bulkCreate(data).success(function() {
        self.User.findAll({order: 'id'}).success(function(users) {
          expect(users.length).to.equal(2)
          expect(users[0].username).to.equal("Peter")
          expect(users[0].data).to.equal(quote)
          expect(users[1].username).to.equal("Paul")
          expect(users[1].data).to.equal(quote)
          done()
        })
      })
    })

    it('saves data with double quote', function(done) {
      var self = this
        , quote = 'Double"Quote'
        , data = [{ username: 'Peter', data: quote},
                  { username: 'Paul', data: quote}]

      this.User.bulkCreate(data).success(function() {
        self.User.findAll({order: 'id'}).success(function(users) {
          expect(users.length).to.equal(2)
          expect(users[0].username).to.equal("Peter")
          expect(users[0].data).to.equal(quote)
          expect(users[1].username).to.equal("Paul")
          expect(users[1].data).to.equal(quote)
          done()
        })
      })
    })

    it('saves stringified JSON data', function(done) {
      var self = this
        , json = JSON.stringify({ key: 'value' })
        , data = [{ username: 'Peter', data: json},
                  { username: 'Paul', data: json}]

      this.User.bulkCreate(data).success(function() {
        self.User.findAll({order: 'id'}).success(function(users) {
          expect(users.length).to.equal(2)
          expect(users[0].username).to.equal("Peter")
          expect(users[0].data).to.equal(json)
          expect(users[1].username).to.equal("Paul")
          expect(users[1].data).to.equal(json)
          done()
        })
      })
    })

    it('properly handles a model with a length column', function (done) {
      var UserWithLength = this.sequelize.define('UserWithLength', {
        length: Sequelize.INTEGER
      })

      UserWithLength.sync({force:true}).success(function() {
        UserWithLength.bulkCreate([{ length: 42}, {length: 11}]).success(function () {
          done()
        })
      })
    })

    it('stores the current date in createdAt', function(done) {
      var self = this
        , data = [{ username: 'Peter'},
                  { username: 'Paul'}]

      this.User.bulkCreate(data).success(function() {
        self.User.findAll({order: 'id'}).success(function(users) {
          expect(users.length).to.equal(2)
          expect(users[0].username).to.equal('Peter')
          expect(parseInt(+users[0].createdAt/5000, 10)).to.be.closeTo(parseInt(+new Date()/5000, 10), 1.5)
          expect(users[1].username).to.equal('Paul')
          expect(parseInt(+users[1].createdAt/5000, 10)).to.be.closeTo(parseInt(+new Date()/5000, 10), 1.5)
          done()
        })
      })
    })

    it('emits an error when validate is set to true', function(done) {
      var Tasks = this.sequelize.define('Task', {
        name: {
          type: Sequelize.STRING,
          allowNull: false,
        },
        code: {
          type: Sequelize.STRING,
          validate: {
            len: [3, 10]
          }
        }
      })

      Tasks.sync({ force: true }).success(function() {
        Tasks.bulkCreate([
          {name: 'foo', code: '123'},
          {code: '1234'},
          {name: 'bar', code: '1'}
        ], { validate: true }).error(function(errors) {
          expect(errors).to.not.be.null
          expect(errors).to.be.an('Array')
          expect(errors).to.have.length(2)
          expect(errors[0].record.code).to.equal('1234')
          expect(errors[0].errors.get('name')[0].type).to.equal('notNull Violation')
          expect(errors[1].record.name).to.equal('bar')
          expect(errors[1].record.code).to.equal('1')
          expect(errors[1].errors.get('code')[0].message).to.equal('Validation len failed')
          done()
        })
      })
    })

    it("doesn't emit an error when validate is set to true but our selectedValues are fine", function(done) {
      var Tasks = this.sequelize.define('Task', {
        name: {
          type: Sequelize.STRING,
          validate: {
            notEmpty: true,
          },
        },
        code: {
          type: Sequelize.STRING,
          validate: {
            len: [3, 10]
          }
        }
      })

      Tasks.sync({ force: true }).success(function() {
        Tasks.bulkCreate([
          {name: 'foo', code: '123'},
          {code: '1234'}
        ], { fields: ['code'], validate: true }).success(function() {
          // we passed!
          done()
        })
      })
    })

    it('should allow blank arrays (return immediatly)', function (done) {
      var Worker = this.sequelize.define('Worker', {})
      Worker.sync().done(function() {
        Worker.bulkCreate([]).done(function (err, workers) {
          expect(err).not.to.be.ok
          expect(workers).to.be.ok
          expect(workers.length).to.equal(0)
          done()
        })
      })
    })

    it('should allow blank creates (with timestamps: false)', function (done) {
      var Worker = this.sequelize.define('Worker', {}, {timestamps: false})
      Worker.sync().done(function() {
        Worker.bulkCreate([{}, {}]).done(function (err, workers) {
          expect(err).not.to.be.ok
          expect(workers).to.be.ok
          done()
        })
      })
    })

    it('should allow autoincremented attributes to be set', function (done) {
      var Worker = this.sequelize.define('Worker', {}, {timestamps: false})
      Worker.sync().done(function() {
        Worker.bulkCreate([
          {id: 5},
          {id: 10}
        ]).done(function (err) {
          expect(err).not.to.be.ok
          Worker.findAll({order: 'id ASC'}).done(function (err, workers) {
            expect(workers[0].id).to.equal(5)
            expect(workers[1].id).to.equal(10)
            done()
          })
        })
      })
    })

    if (Support.getTestDialect() !== 'postgres') {
      it("should support the ignoreDuplicates option", function(done) {
        var self = this
          , data = [{ uniqueName: 'Peter', secretValue: '42' },
                    { uniqueName: 'Paul', secretValue: '23' }]

        this.User.bulkCreate(data, { fields: ['uniqueName', 'secretValue'] }).success(function() {
          data.push({ uniqueName: 'Michael', secretValue: '26' });
          self.User.bulkCreate(data, { fields: ['uniqueName', 'secretValue'], ignoreDuplicates: true }).success(function() {
            self.User.findAll({order: 'id'}).success(function(users) {
              expect(users.length).to.equal(3)
              expect(users[0].uniqueName).to.equal("Peter")
              expect(users[0].secretValue).to.equal("42");
              expect(users[1].uniqueName).to.equal("Paul")
              expect(users[1].secretValue).to.equal("23");
              expect(users[2].uniqueName).to.equal("Michael")
              expect(users[2].secretValue).to.equal("26");
              done()
            });
          });
        })
      })
    } else {
      it("should throw an error when the ignoreDuplicates option is passed", function(done) {
        var self = this
          , data = [{ uniqueName: 'Peter', secretValue: '42' },
                    { uniqueName: 'Paul', secretValue: '23' }]

        this.User.bulkCreate(data, { fields: ['uniqueName', 'secretValue'] }).success(function() {
          data.push({ uniqueName: 'Michael', secretValue: '26' });

          self.User.bulkCreate(data, { fields: ['uniqueName', 'secretValue'], ignoreDuplicates: true }).error(function(err) {
            expect(err).to.exist
            expect(err.message).to.match(/Postgres does not support the \'ignoreDuplicates\' option./)

            done();
          })
        })
      })
    }

    describe('enums', function() {
      it('correctly restores enum values', function(done) {
        var self = this
          , Item = self.sequelize.define('Item', {
          state: { type: Sequelize.ENUM, values: ['available', 'in_cart', 'shipped'] },
          name: Sequelize.STRING
        })

        Item.sync({ force: true }).success(function() {
          Item.bulkCreate([{state: 'in_cart', name: 'A'}, { state: 'available', name: 'B'}]).success(function() {
            Item.find({ where: { state: 'available' }}).success(function(item) {
              expect(item.name).to.equal('B')
              done()
            })
          })
        })
      })
    })
  })
})


Copyright (c) 2014 Sascha Depold
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


var Sequelize = require(__dirname + "/../../index")
  , config    = require(__dirname + "/../../spec/config/config")
  , sequelize = new Sequelize(config.database, config.username, config.password, {logging: false})

var Person  = sequelize.define('Person', { name: Sequelize.STRING })
  , chainer = new Sequelize.Utils.QueryChainer

sequelize.sync({force: true}).on('success', function() {
  var count   = 10,
      queries = []

  for(var i = 0; i < count; i++)
    chainer.add(Person.create({name: 'someone' + (i % 3)}))

  console.log("Begin to save " + count + " items!")

  chainer.run().on('success', function() {
    console.log("finished")
    Person.count().on('success', function(count) {
      console.log("Counted " + count + " elements!")
    })
    Person.count({where: {name: 'someone2'}}).on('success', function(count) {
      console.log("Counted " + count + " elements with name = someone2!")
    })
  })
})


'use strict';

var util = require('util')
  , DataTypes = require('./data-types')
  , SqlString = require('./sql-string')
  , lodash = require('lodash')
  , ParameterValidator = require('./utils/parameter-validator')
  , inflection = require('inflection')
  , uuid = require('node-uuid');

var Utils = module.exports = {
  inflection: inflection,
  _: (function() {
    var _ = lodash;

    _.mixin({
      includes: function(str, needle){
        if (needle === '') return true;
        if (str === null) return false;
        return String(str).indexOf(needle) !== -1;
      },
      camelizeIf: function(string, condition) {
        var result = string;

        if (condition) {
          result = Utils.camelize(string);
        }

        return result;
      },
      underscoredIf: function(string, condition) {
        var result = string;

        if (condition) {
          result = inflection.underscore(string);
        }

        return result;
      },
      /*
       * Returns an array with some falsy values removed. The values null, "", undefined and NaN are considered falsey.
       */
      compactLite: function(array) {
        var index = -1,
          length = array ? array.length : 0,
          result = [];

        while (++index < length) {
          var value = array[index];
          if (typeof value === 'boolean' || value === 0 || value) {
            result.push(value);
          }
        }
        return result;
      }
    });

    return _;
  })(),
  // Same concept as _.merge, but don't overwrite properties that have already been assigned
  mergeDefaults: function (a, b) {
    return this._.merge(a, b, function (objectValue, sourceValue) {
      // If it's an object, let _ handle it this time, we will be called again for each property
      if (!this._.isPlainObject(objectValue) && objectValue !== undefined) {
        return objectValue;
      }
    }, this);
  },
  lowercaseFirst: function (s) {
    return s[0].toLowerCase() + s.slice(1);
  },
  uppercaseFirst: function (s) {
    return s[0].toUpperCase() + s.slice(1);
  },
  spliceStr: function (str, index, count, add) {
    return str.slice(0, index) + add + str.slice(index + count);
  },
  camelize: function(str){
    return str.trim().replace(/[-_\s]+(.)?/g, function(match, c){ return c.toUpperCase(); });
  },
  format: function(arr, dialect) {
    var timeZone = null;
    // Make a clone of the array beacuse format modifies the passed args
    return SqlString.format(arr[0], arr.slice(1), timeZone, dialect);
  },
  formatNamedParameters: function(sql, parameters, dialect) {
    var timeZone = null;
    return SqlString.formatNamedParameters(sql, parameters, timeZone, dialect);
  },
  injectScope: function(scope, merge) {
    var self = this;

    scope = scope || {};

    if (!this.scoped && self.options.defaultScope) {
      self.scopeObj = Utils._.clone(self.options.defaultScope);
      if (!Array.isArray(self.scopeObj.where)) {
        self.scopeObj.where = [self.scopeObj.where];
      }
    } else {
      self.scopeObj = self.scopeObj || {};
    }

    if (Array.isArray(scope.where)) {
      self.scopeObj.where = self.scopeObj.where || [];
      self.scopeObj.where.push(scope.where);
      return true;
    }

    if (typeof scope.order === 'string') {
      self.scopeObj.order = self.scopeObj.order || [];
      self.scopeObj.order[self.scopeObj.order.length] = scope.order;
    }

    // Limit and offset are *always* merged.
    if (!!scope.limit) {
      self.scopeObj.limit = scope.limit;
    }

    if (!!scope.offset) {
      self.scopeObj.offset = scope.offset;
    }

    // Where objects are a mixed variable. Possible values are arrays, strings, and objects
    if (!!scope.where) {
      // Begin building our scopeObj
      self.scopeObj.where = self.scopeObj.where || [];

      // Reset if we're merging!
      if (merge === true && !!scope.where && !!self.scopeObj.where) {
        var scopeKeys = Object.keys(scope.where);
        self.scopeObj.where = self.scopeObj.where.map(function(scopeObj) {
          if (!Array.isArray(scopeObj) && typeof scopeObj === 'object') {
            return lodash.omit.apply(undefined, [scopeObj].concat(scopeKeys));
          } else {
            return scopeObj;
          }
        }).filter(function(scopeObj) {
          return !lodash.isEmpty(scopeObj);
        });
        self.scopeObj.where = self.scopeObj.where.concat(scope.where);
      }

      if (Array.isArray(scope.where)) {
        self.scopeObj.where.push(scope.where);
      }
      else if (typeof scope.where === 'object') {
        Object.keys(scope.where).forEach(function() {
          self.scopeObj.where.push(scope.where);
        });
      } else { // Assume the value is a string
        self.scopeObj.where.push([scope.where]);
      }
    }

    if (!!self.scopeObj.where) {
      self.scopeObj.where = lodash.uniq(self.scopeObj.where);
    }
  },
  // smartWhere can accept an array of {where} objects, or a single {where} object.
  // The smartWhere function breaks down the collection of where objects into a more
  // centralized object for each column so we can avoid duplicates
  // e.g. WHERE username='dan' AND username='dan' becomes WHERE username='dan'
  // All of the INs, NOT INs, BETWEENS, etc. are compressed into one key for each column
  // This function will hopefully provide more functionality to sequelize in the future.
  // tl;dr It's a nice way to dissect a collection of where objects and compress them into one object
  smartWhere: function(whereArg, dialect) {
    var self = this
      , _where = {}
      , logic
      , type;

    (Array.isArray(whereArg) ? whereArg : [whereArg]).forEach(function(where) {
      // If it's an array we're already good... / it's in a format that can't be broken down further
      // e.g. Util.format['SELECT * FROM world WHERE status=?', 'hello']
      if (Array.isArray(where)) {
        _where._ = where._ || {queries: [], bindings: []};
        _where._.queries[_where._.queries.length] = where[0];
        if (where.length > 1) {
          var values = where.splice(1);
          if (dialect === 'sqlite') {
            values.forEach(function(v, i) {
              if (typeof v === 'boolean') {
                values[i] = (v === true ? 1 : 0);
              }
            });
          }
          _where._.bindings = _where._.bindings.concat(values);
        }
      }
      else if (typeof where === 'object') {
        // First iteration is trying to compress IN and NOT IN as much as possible...
        // .. reason being is that WHERE username IN (?) AND username IN (?) != WHERE username IN (?,?)
        Object.keys(where).forEach(function(i) {
          if (Array.isArray(where[i])) {
            where[i] = {
              in : where[i]
            };
          }
        });

        // Build our smart object
        Object.keys(where).forEach(function(i) {
          type = typeof where[i];
          _where[i] = _where[i] || {};

          if (where[i] === null) {
            // skip nulls
          }
          else if (Array.isArray(where[i])) {
            _where[i].in = _where[i]. in || [];
            _where[i]. in .concat(where[i]);
          }
          else if (Utils._.isPlainObject(where[i])) {
            Object.keys(where[i]).forEach(function(ii) {
              logic = self.getWhereLogic(ii, where[i][ii]);

              switch (logic) {
              case 'IN':
                _where[i].in = _where[i]. in || [];
                _where[i].in = _where[i]. in .concat(where[i][ii]);
                break;
              case 'NOT':
                _where[i].not = _where[i].not || [];
                _where[i].not = _where[i].not.concat(where[i][ii]);
                break;
              case 'BETWEEN':
                _where[i].between = _where[i].between || [];
                _where[i].between[_where[i].between.length] = [where[i][ii][0], where[i][ii][1]];
                break;
              case 'NOT BETWEEN':
                _where[i].nbetween = _where[i].nbetween || [];
                _where[i].nbetween[_where[i].nbetween.length] = [where[i][ii][0], where[i][ii][1]];
                break;
              case 'JOIN':
                _where[i].joined = _where[i].joined || [];
                _where[i].joined[_where[i].joined.length] = where[i][ii];
                break;
              default:
                _where[i].lazy = _where[i].lazy || {conditions: [], bindings: []};
                _where[i].lazy.conditions[_where[i].lazy.conditions.length] = logic + ' ?';
                _where[i].lazy.bindings = _where[i].lazy.bindings.concat(where[i][ii]);
              }
            });
          }
          else if (type === 'string' || type === 'number' || type === 'boolean' || Buffer.isBuffer(where[i])) {
            _where[i].lazy = _where[i].lazy || {conditions: [], bindings: []};
            if (type === 'boolean') {
              _where[i].lazy.conditions[_where[i].lazy.conditions.length] = '= ' + SqlString.escape(where[i], false, null, dialect); // sqlite is special
            } else {
              _where[i].lazy.conditions[_where[i].lazy.conditions.length] = '= ?';
              _where[i].lazy.bindings = _where[i].lazy.bindings.concat(where[i]);
            }
          }
        });
      }
    });

    return _where;
  },
  // Converts {smart where} object(s) into an array that's friendly for Utils.format()
  // NOTE: Must be applied/called from the QueryInterface
  compileSmartWhere: function(obj, dialect) {
    var self = this
      , whereArgs = []
      , text = []
      , columnName;

    if (typeof obj !== 'object') {
      return obj;
    }

    for (var column in obj) {
      if (column === '_') {
        text[text.length] = obj[column].queries.join(' AND ');
        if (obj[column].bindings.length > 0) {
          whereArgs = whereArgs.concat(obj[column].bindings);
        }
      } else {
        Object.keys(obj[column]).forEach(function(condition) {
          columnName = self.QueryInterface.quoteIdentifiers(column);
          switch (condition) {
          case 'in':
            text[text.length] = columnName + ' IN (' + obj[column][condition].map(function() { return '?'; }) + ')';
            whereArgs = whereArgs.concat(obj[column][condition]);
            break;
          case 'not':
            text[text.length] = columnName + ' NOT IN (' + obj[column][condition].map(function() { return '?'; }) + ')';
            whereArgs = whereArgs.concat(obj[column][condition]);
            break;
          case 'between':
            Object.keys(obj[column][condition]).forEach(function(row) {
              text[text.length] = columnName + ' BETWEEN ? AND ?';
              whereArgs = whereArgs.concat(obj[column][condition][row][0], obj[column][condition][row][1]);
            });
            break;
          case 'nbetween':
            Object.keys(obj[column][condition]).forEach(function(row) {
              text[text.length] = columnName + ' BETWEEN ? AND ?';
              whereArgs = whereArgs.concat(obj[column][condition][row][0], obj[column][condition][row][1]);
            });
            break;
          case 'joined':
            Object.keys(obj[column][condition]).forEach(function(row) {
              text[text.length] = columnName + ' = ' + self.QueryInterface.quoteIdentifiers(obj[column][condition][row]);
            });
            break;
          default: // lazy
            text = text.concat(obj[column].lazy.conditions.map(function(val) { return columnName + ' ' + val; }));
            whereArgs = whereArgs.concat(obj[column].lazy.bindings);
          }
        });
      }
    }

    return Utils._.compactLite([text.join(' AND ')].concat(whereArgs));
  },
  getWhereLogic: function(logic, val) {
    switch (logic) {
    case 'join':
      return 'JOIN';
    case 'gte':
      return '>=';
    case 'gt':
      return '>';
    case 'lte':
      return '<=';
    case 'lt':
      return '<';
    case 'eq':
      return '=';
    case 'ne':
      return val === null ? 'IS NOT' : '!=';
    case 'between':
    case '..':
      return 'BETWEEN';
    case 'nbetween':
    case 'notbetween':
    case '!..':
      return 'NOT BETWEEN';
    case 'in':
      return 'IN';
    case 'not':
      return 'NOT IN';
    case 'like':
      return 'LIKE';
    case 'nlike':
    case 'notlike':
      return 'NOT LIKE';
    case 'ilike':
      return 'ILIKE';
    case 'nilike':
    case 'notilike':
      return 'NOT ILIKE';
    default:
      return '';
    }
  },
  argsArePrimaryKeys: function(args, primaryKeys) {
    var result = (args.length === Object.keys(primaryKeys).length);
    if (result) {
      Utils._.each(args, function(arg) {
        if (result) {
          if (['number', 'string'].indexOf(typeof arg) !== -1) {
            result = true;
          } else {
            result = (arg instanceof Date) || Buffer.isBuffer(arg);
          }
        }
      });
    }
    return result;
  },
  canTreatArrayAsAnd: function(arr) {
    return arr.reduce(function(treatAsAnd, arg) {
      if (treatAsAnd) {
        return treatAsAnd;
      } else {
        return !(arg instanceof Date) && ((arg instanceof Utils.and) || (arg instanceof Utils.or) || Utils._.isPlainObject(arg));
      }
    }, false);
  },

  combineTableNames: function(tableName1, tableName2) {
    return (tableName1.toLowerCase() < tableName2.toLowerCase()) ? (tableName1 + tableName2) : (tableName2 + tableName1);
  },

  singularize: function(s, language) {
    return inflection.singularize(s);
  },

  pluralize: function(s, language) {
    return inflection.pluralize(s);
  },

  removeCommentsFromFunctionString: function(s) {
    s = s.replace(/\s*(\/\/.*)/g, '');
    s = s.replace(/(\/\*[\n\r\s\S]*?\*\/)/mg, '');

    return s;
  },

  toDefaultValue: function(value) {
    if (lodash.isFunction(value)) {
      return value();
    } else if (value === DataTypes.UUIDV1) {
      return uuid.v1();
    } else if (value === DataTypes.UUIDV4) {
      return uuid.v4();
    } else if (value === DataTypes.NOW) {
      return Utils.now();
    } else {
      return value;
    }
  },

  /**
   * Determine if the default value provided exists and can be described
   * in a db schema using the DEFAULT directive.
   *
   * @param  {*} value Any default value.
   * @return {boolean} yes / no.
   */
  defaultValueSchemable: function(value) {
    if (typeof value === 'undefined') { return false; }

    // TODO this will be schemable when all supported db
    // have been normalized for this case
    if (value === DataTypes.NOW) { return false; }

    if (value === DataTypes.UUIDV1 || value === DataTypes.UUIDV4) { return false; }

    if (lodash.isFunction(value)) {
      return false;
    }

    return true;
  },

  removeNullValuesFromHash: function(hash, omitNull, options) {
    var result = hash;

    options = options || {};
    options.allowNull = options.allowNull || [];

    if (omitNull) {
      var _hash = {};

      Utils._.each(hash, function(val, key) {
        if (options.allowNull.indexOf(key) > -1 || key.match(/Id$/) || ((val !== null) && (val !== undefined))) {
          _hash[key] = val;
        }
      });

      result = _hash;
    }

    return result;
  },

  inherit: function(SubClass, SuperClass) {
    if (SuperClass.constructor === Function) {
      // Normal Inheritance
      SubClass.prototype = new SuperClass();
      SubClass.prototype.constructor = SubClass;
      SubClass.prototype.parent = SuperClass.prototype;
    } else {
      // Pure Virtual Inheritance
      SubClass.prototype = SuperClass;
      SubClass.prototype.constructor = SubClass;
      SubClass.prototype.parent = SuperClass;
    }

    return SubClass;
  },

  stack: function() {
    var orig = Error.prepareStackTrace;
    Error.prepareStackTrace = function(_, stack) { return stack; };
    var err = new Error();
    Error.captureStackTrace(err, this);
    var errStack = err.stack;
    Error.prepareStackTrace = orig;
    return errStack;
  },

  now: function(dialect) {
    var now = new Date();
    if (dialect !== 'postgres') now.setMilliseconds(0);
    return now;
  },

  tick: function(func) {
    var tick = (global.hasOwnProperty('setImmediate') ? global.setImmediate : process.nextTick);
    tick(func);
  },

  // Note: Use the `quoteIdentifier()` and `escape()` methods on the
  // `QueryInterface` instead for more portable code.

  TICK_CHAR: '`',
  addTicks: function(s, tickChar) {
    tickChar = tickChar || Utils.TICK_CHAR;
    return tickChar + Utils.removeTicks(s, tickChar) + tickChar;
  },
  removeTicks: function(s, tickChar) {
    tickChar = tickChar || Utils.TICK_CHAR;
    return s.replace(new RegExp(tickChar, 'g'), '');
  },

  /*
   * Utility functions for representing SQL functions, and columns that should be escaped.
   * Please do not use these functions directly, use Sequelize.fn and Sequelize.col instead.
   */
  fn: function(fn, args) {
    this.fn = fn;
    this.args = args;
  },

  col: function(col) {
    if (arguments.length > 1) {
      col = Array.prototype.slice.call(arguments);
    }
    this.col = col;
  },

  cast: function(val, type) {
    this.val = val;
    this.type = (type || '').trim();
  },

  literal: function(val) {
    this.val = val;
  },

  asIs: function(val) {
    this.val = val;
  },

  and: function(args) {
    this.args = args;
  },

  or: function(args) {
    this.args = args;
  },

  where: function(attribute, logic) {
    this.attribute = attribute;
    this.logic = logic;
  },

  generateUUID: function() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
      var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
      return v.toString(16);
    });
  },

  validateParameter: function(value, expectation, options) {
    return ParameterValidator.check(value, expectation, options);
  }
};

Utils.condition = Utils.where;

Utils.and.prototype._isSequelizeMethod =
Utils.or.prototype._isSequelizeMethod =
Utils.where.prototype._isSequelizeMethod =
Utils.literal.prototype._isSequelizeMethod =
Utils.cast.prototype._isSequelizeMethod =
Utils.fn.prototype._isSequelizeMethod =
Utils.col.prototype._isSequelizeMethod = true;

// I know this may seem silly, but this gives us the ability to recognize whether
// or not we should be escaping or if we should trust the user. Basically, it
// keeps things in perspective and organized.
Utils.literal.prototype.toString = function() {
  return this.val;
};
Utils.asIs.prototype = Utils.literal.prototype;

Utils.cast.prototype.toString = function(queryGenerator) {
  if (!this.val instanceof Utils.fn && !this.val instanceof Utils.col && !this.val instanceof Utils.literal) {
    this.val = queryGenerator.escape(this.val);
  } else {
    this.val = this.val.toString(queryGenerator);
  }

  return 'CAST(' + this.val + ' AS ' + this.type.toUpperCase() + ')';
};

Utils.fn.prototype.toString = function(queryGenerator, parentModel) {
  return this.fn + '(' + this.args.map(function(arg) {
    if (arg._isSequelizeMethod) {
      return arg.toString(queryGenerator, parentModel);
    } else {
      return queryGenerator.escape(arg);
    }
  }).join(', ') + ')';
};

Utils.col.prototype.toString = function(queryGenerator, parentModel) {
  if (Array.isArray(this.col)) {
    if (!parentModel) {
      throw new Error('Cannot call Sequelize.col() with array outside of order / group clause');
    }
  } else if (this.col.indexOf('*') === 0) {
    return '*';
  }
  return queryGenerator.quote(this.col, parentModel);
};

Utils.CustomEventEmitter = require(__dirname + '/emitters/custom-event-emitter');
Utils.Promise = require(__dirname + '/promise');
Utils.QueryChainer = require(__dirname + '/query-chainer');


# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org

root = true

[*]

# Change these settings to your own preference
indent_style = space
indent_size = 2

# We recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false
indent_size = 4


module.exports = {
  up: function(migration, DataTypes, done) {
    migration
        .createTable('trigger_test', {
          name: DataTypes.STRING,
          updated_at: {
            type: DataTypes.DATE,
            defaultValue: DataTypes.NOW,
            allowNull: false
          }
        })
        .complete(done)
  },
  down: function(migration, DataTypes, done) {
    migration.dropTable('trigger_test').complete(done)
  }
}


module.exports = {
  up: function(migration, DataTypes, done) {
    migration.createFunction('get_an_answer', [], 'int', 'plpgsql',
            'RETURN 42;'
    ).complete(done);
  },
  down: function(migration, DataTypes, done) {
    migration.dropFunction('get_an_answer', []).complete(done);
  }
}


describe('several concurrent calls', function () {

keep keep keep replace replace replace replace replace replace replace keep keep keep


module.exports = {
  up: function(migration, DataTypes, done) {
    migration.dropFunction('get_the_answer', []).complete(done);
  },
  down: function(migration, DataTypes, done) {
    migration.createFunction('get_the_answer', 'int', 'plpgsql',
            'RETURN 42;'
    ).complete(done);
  }
}