'use strict';

var util = require('util')
  , _ = require('lodash');

/**
 * A convenience class holding commonly used data types. The datatypes are used when definining a new model using `Sequelize.define`, like this:
 * ```js
 * sequelize.define('model', {
 *   column: DataTypes.INTEGER
 * })
 * ```
 * When defining a model you can just as easily pass a string as type, but often using the types defined here is beneficial. For example, using `DataTypes.BLOB`, mean
 * that that column will be returned as an instance of `Buffer` when being fetched by sequelize.
 *
 * Some data types have special properties that can be accessed in order to change the data type. For example, to get an unsigned integer with zerofill you can do `DataTypes.INTEGER.UNSIGNED.ZEROFILL`.
 * The order you access the properties in do not matter, so `DataTypes.INTEGER.ZEROFILL.UNSIGNED` is fine as well. The available properties are listed under each data type.
 *
 * To provide a length for the data type, you can invoke it like a function: `INTEGER(2)`
 *
 * Three of the values provided here (`NOW`, `UUIDV1` and `UUIDV4`) are special default values, that should not be used to define types. Instead they are used as shorthands for
 * defining default values. For example, to get a uuid field with a default value generated following v1 of the UUID standard:
 * ```js
 * sequelize.define('model', {
 *   uuid: {
 *     type: DataTypes.UUID,
 *     defaultValue: DataTypes.UUIDV1,
 *     primaryKey: true
 *   }
 * })
 * ```
 *
 * @class DataTypes
 */

var ABSTRACT = function(options) {

};

ABSTRACT.prototype.toString = function() {
  return this.toSql();
};
ABSTRACT.prototype.toSql = function() {
  return this.key;
};

/**
 * A variable length string. Default length 255
 *
 * Available properties: `BINARY`
 *
 * @property STRING
 */
var STRING = function(length, binary) {
  var options = typeof length === "object" && length || {
    length: length,
    binary: binary
  };

  if (!(this instanceof STRING)) return new STRING(options);

  this.options = options;
  this._binary = options.binary;
  this._length = options.length || 255;
};
util.inherits(STRING, ABSTRACT);

STRING.prototype.key = STRING.key = 'STRING';
STRING.prototype.toSql = function() {
  return 'VARCHAR(' + this._length + ')' + ((this._binary) ? ' BINARY' : '');
};
Object.defineProperty(STRING.prototype, 'BINARY', {
  get: function() {
    this._binary = true;
    this.options.binary = true;
    return this;
  }
});

/**
 * A fixed length string. Default length 255
 *
 * Available properties: `BINARY`
 *
 * @property CHAR
 */
var CHAR = function(length, binary) {
  var options = typeof length === "object" && length || {
    length: length,
    binary: binary
  };

  if (!(this instanceof CHAR)) return new CHAR(options);
  STRING.apply(this, arguments);
};
util.inherits(CHAR, STRING);

CHAR.prototype.key = CHAR.key = 'CHAR';
CHAR.prototype.toSql = function() {
  return 'CHAR(' + this._length + ')' + ((this._binary) ? ' BINARY' : '');
};

/**
 * An unlimited length text column
 * @property TEXT
 */
var TEXT = function(options) {
  if (!(this instanceof TEXT)) return new TEXT(options);
};
util.inherits(TEXT, ABSTRACT);

TEXT.prototype.key = TEXT.key = 'TEXT';

var NUMBER = function(options) {
  this.options = options;
  this._length = options.length;
  this._zerofill = options.zerofill;
  this._decimals = options.decimals;
  this._precision = options.precision;
  this._scale = options.scale;
  this._unsigned = options.unsigned;
};
util.inherits(NUMBER, ABSTRACT);

NUMBER.prototype.key = NUMBER.key = 'NUMBER';
NUMBER.prototype.toSql = function() {
  var result = this.key;
  if (this._length) {
    result += '(' + this._length;
    if (typeof this._decimals === 'number') {
      result += ',' + this._decimals;
    }
    result += ')';
  }
  if (this._unsigned) {
    result += ' UNSIGNED';
  }
  if (this._zerofill) {
    result += ' ZEROFILL';
  }
  return result;
};

Object.defineProperty(NUMBER.prototype, 'UNSIGNED', {
  get: function() {
    this._unsigned = true;
    this.options.unsigned = true;
    return this;
  }
});
Object.defineProperty(NUMBER.prototype, 'ZEROFILL', {
  get: function() {
    this._zerofill = true;
    this.options.zerofill = true;
    return this;
  }
});

/**
 * A 32 bit integer.
 *
 * Available properties: `UNSIGNED`, `ZEROFILL`
 *
 * @property INTEGER
 */
var INTEGER = function(length) {
  var options = typeof length === "object" && length || {
    length: length
  };
  if (!(this instanceof INTEGER)) return new INTEGER(options);
  NUMBER.call(this, options);
};
util.inherits(INTEGER, NUMBER);

INTEGER.prototype.key = INTEGER.key = 'INTEGER';

/**
 * A 64 bit integer.
 *
 * Available properties: `UNSIGNED`, `ZEROFILL`
 *
 * @property BIGINT
 */

var BIGINT = function(length) {
  var options = typeof length === "object" && length || {
    length: length
  };
  if (!(this instanceof BIGINT)) return new BIGINT(options);
  NUMBER.call(this, options);
};
util.inherits(BIGINT, NUMBER);

BIGINT.prototype.key = BIGINT.key = 'BIGINT';

/**
 * Floating point number. Accepts one or two arguments for precision
 *
 * Available properties: `UNSIGNED`, `ZEROFILL`
 *
 * @property FLOAT
 */

var FLOAT = function(length, decimals) {
  var options = typeof length === "object" && length || {
    length: length,
    decimals: decimals
  };
  if (!(this instanceof FLOAT)) return new FLOAT(options);
  NUMBER.call(this, options);
};
util.inherits(FLOAT, NUMBER);

FLOAT.prototype.key = FLOAT.key = 'FLOAT';

/**
 * Decimal number. Accepts one or two arguments for precision
 *
 * Available properties: `UNSIGNED`, `ZEROFILL`
 *
 * @property DECIMAL
 */
var DECIMAL = function(precision, scale) {
  var options = typeof precision === "object" && precision || {
    precision: precision,
    scale: scale
  };
  if (!(this instanceof DECIMAL)) return new DECIMAL(options);
  NUMBER.call(this, options);
};
util.inherits(DECIMAL, NUMBER);

DECIMAL.prototype.key = DECIMAL.key = 'DECIMAL';
DECIMAL.prototype.toSql = function() {
  if (this._precision || this._scale) {
    return 'DECIMAL(' + [this._precision, this._scale].filter(_.identity).join(',') + ')';
  }

  return 'DECIMAL';
};

/**
 * A boolean / tinyint column, depending on dialect
 * @property BOOLEAN
 */
var BOOLEAN = function() {
  if (!(this instanceof BOOLEAN)) return new BOOLEAN();
  ABSTRACT.apply(this, arguments);
};
util.inherits(BOOLEAN, ABSTRACT);

BOOLEAN.prototype.key = BOOLEAN.key = 'BOOLEAN';
BOOLEAN.prototype.toSql = function() {
  return 'TINYINT(1)';
};

/**
 * A time column
 * @property TIME
 */

var TIME = function() {
  if (!(this instanceof TIME)) return new TIME();
  ABSTRACT.apply(this, arguments);
};
util.inherits(TIME, ABSTRACT);

TIME.prototype.key = TIME.key = 'TIME';
TIME.prototype.toSql = function() {
  return 'TIME';
};

/**
 * A datetime column
 * @property DATE
 */
var DATE = function() {
  if (!(this instanceof DATE)) return new DATE();
  ABSTRACT.apply(this, arguments);
};
util.inherits(DATE, ABSTRACT);

DATE.prototype.key = DATE.key = 'DATE';
DATE.prototype.toSql = function() {
  return 'DATETIME';
};

/**
 * A date only column
 * @property DATEONLY
 */

var DATEONLY = function() {
  if (!(this instanceof DATEONLY)) return new DATEONLY();
  ABSTRACT.apply(this, arguments);
};
util.inherits(DATEONLY, ABSTRACT);

DATEONLY.prototype.key = DATEONLY.key = 'DATEONLY';
DATEONLY.prototype.toSql = function() {
  return 'DATE';
};

/**
 * A key / value column. Only available in postgres.
 * @property HSTORE
 */

var HSTORE = function() {
  if (!(this instanceof HSTORE)) return new HSTORE();
  ABSTRACT.apply(this, arguments);
};
util.inherits(HSTORE, ABSTRACT);

HSTORE.prototype.key = HSTORE.key = 'HSTORE';

/**
 * A JSON string column. Only available in postgres.
 * @property JSON
 */
var JSONTYPE = function() {
  if (!(this instanceof JSONTYPE)) return new JSONTYPE();
  ABSTRACT.apply(this, arguments);
};
util.inherits(JSONTYPE, ABSTRACT);

JSONTYPE.prototype.key = JSONTYPE.key = 'JSON';

/**
 * A pre-processed JSON data column. Only available in postgres.
 * @property JSONB
 */
var JSONB = function() {
  if (!(this instanceof JSONB)) return new JSONB();
  ABSTRACT.apply(this, arguments);
};
util.inherits(JSONB, ABSTRACT);

JSONB.prototype.key = JSONB.key = 'JSONB';

/**
 * A default value of the current timestamp
 * @property NOW
 */
var NOW = function() {
  if (!(this instanceof NOW)) return new NOW();
  ABSTRACT.apply(this, arguments);
};
util.inherits(NOW, ABSTRACT);

NOW.prototype.key = NOW.key = 'NOW';

/**
 * Binary storage. Available lengths: `tiny`, `medium`, `long`
 *
 * @property BLOB
 */

var BLOB = function(length) {
  var options = typeof length === "object" && length || {
    length: length
  };
  if (!(this instanceof BLOB)) return new BLOB(options);
  this._length = options.length || '';
};
util.inherits(BLOB, ABSTRACT);

BLOB.prototype.key = BLOB.key = 'BLOB';
BLOB.prototype.toSql = function() {
  switch (this._length.toLowerCase()) {
  case 'tiny':
    return 'TINYBLOB';
  case 'medium':
    return 'MEDIUMBLOB';
  case 'long':
    return 'LONGBLOB';
  default:
    return this.key;
  }
};

/**
 * Range types are data types representing a range of values of some element type (called the range's subtype). Only available in postgres.
 * See {@link http://www.postgresql.org/docs/9.4/static/rangetypes.html|Postgres documentation} for more details
 * @property RANGE
 */

var RANGE = function (subtype) {
  var options = {};
  if(typeof subtype === 'function') { // if subtype passed - instantiate object of this subtype and return new function
    options.subtype = new subtype();
    return RANGE.bind({}, options);
  }
  else if(typeof subtype === 'object' && subtype.hasOwnProperty('subtype'))
    options = subtype;

  if (!(this instanceof RANGE)) return new RANGE(options);
  ABSTRACT.apply(this, arguments);

  this._subtype = options.subtype ? (options.subtype.key || 'INTEGER') : 'INTEGER';
};
util.inherits(RANGE, ABSTRACT);

var pgRangeSubtypes = {
  integer: 'int4range',
  bigint: 'int8range',
  decimal: 'numrange',
  dateonly: 'daterange',
  date: 'tstzrange'
};

RANGE.prototype.key = RANGE.key = 'RANGE';
RANGE.prototype.toSql = function() {
  return pgRangeSubtypes[this._subtype.toLowerCase()] || pgRangeSubtypes['integer'];
};

/**
 * A column storing a unique univeral identifier. Use with `UUIDV1` or `UUIDV4` for default values.
 * @property UUID
 */
var UUID = function() {
  if (!(this instanceof UUID)) return new UUID();
  ABSTRACT.apply(this, arguments);
};
util.inherits(UUID, ABSTRACT);

UUID.prototype.key = UUID.key = 'UUID';

/**
 * A default unique universal identifier generated following the UUID v1 standard
 * @property UUIDV1
 */

var UUIDV1 = function() {
  if (!(this instanceof UUIDV1)) return new UUIDV1();
  ABSTRACT.apply(this, arguments);
};
util.inherits(UUIDV1, ABSTRACT);

UUIDV1.prototype.key = UUIDV1.key = 'UUIDV1';

/**
 * A default unique universal identifier generated following the UUID v2 standard
 * @property UUIDV4
 */

var UUIDV4 = function() {
  if (!(this instanceof UUIDV4)) return new UUIDV4();
  ABSTRACT.apply(this, arguments);
};
util.inherits(UUIDV4, ABSTRACT);

UUIDV4.prototype.key = UUIDV4.key = 'UUIDV4';

/**
 * A virtual value that is not stored in the DB. This could for example be useful if you want to provide a default value in your model
 * that is returned to the user but not stored in the DB.
 *
 * You could also use it to validate a value before permuting and storing it. Checking password length before hashing it for example:
 * ```js
 * sequelize.define('user', {
 *   password_hash: DataTypes.STRING
 *   password: {
 *     type: DataTypes.VIRTUAL,
 *     set: function (val) {
 *        this.setDataValue('password', val);
 *        this.setDataValue('password_hash', this.salt + val);
 *      },
 *      validate: {
 *         isLongEnough: function (val) {
 *           if (val.length < 7) {
 *             throw new Error("Please choose a longer password")
 *          }
 *       }
 *     }
 *   }
 * })
 * ```
 * In the above code the password is stored plainly in the password field so it can be validated, but is never stored in the DB.
 * @property VIRTUAL
 * @alias NONE
 */
var VIRTUAL = function() {
  if (!(this instanceof VIRTUAL)) return new VIRTUAL();
  ABSTRACT.apply(this, arguments);
};
util.inherits(VIRTUAL, ABSTRACT);

VIRTUAL.prototype.key = VIRTUAL.key = 'VIRTUAL';

/**
 * An enumeration. `DataTypes.ENUM('value', 'another value')`.
 *
 * @property ENUM
 */
var ENUM = function(value) {
  var options = typeof value === "object" && !Array.isArray(value) && value || {
    values: Array.prototype.slice.call(arguments).reduce(function(result, element) {
      return result.concat(Array.isArray(element) ? element : [element]);
    }, [])
  };
  if (!(this instanceof ENUM)) return new ENUM(options);
  this.values = options.values;
};
util.inherits(ENUM, ABSTRACT);

ENUM.prototype.key = ENUM.key = 'ENUM';

/**
 * An array of `type`, e.g. `DataTypes.ARRAY(DataTypes.DECIMAL)`. Only available in postgres.
 * @property ARRAY
 */
var ARRAY = function(type) {
  var options = typeof type === "object" && !(type instanceof ABSTRACT) && type || {
    type: type
  };
  if (!(this instanceof ARRAY)) return new ARRAY(options);
  this.type = typeof options.type === "function" ? new options.type() : options.type;
};
util.inherits(ARRAY, ABSTRACT);

ARRAY.prototype.key = ARRAY.key = 'ARRAY';
ARRAY.prototype.toSql = function() {
  return this.type.toSql() + '[]';
};
ARRAY.is = function(obj, type) {
  return obj instanceof ARRAY && obj.type instanceof type;
};

var helpers = {
  BINARY: [STRING, CHAR],
  UNSIGNED: [NUMBER, INTEGER, BIGINT, FLOAT],
  ZEROFILL: [NUMBER, INTEGER, BIGINT, FLOAT],
  PRECISION: [DECIMAL],
  SCALE: [DECIMAL]
};
Object.keys(helpers).forEach(function (helper) {
  helpers[helper].forEach(function (DataType) {
    if (!DataType[helper]) {
      Object.defineProperty(DataType, helper, {
        get: function() {
          var dataType = new DataType();
          if (typeof dataType[helper] === "object") {
            dataType[helper];
            return dataType;
          }
          return dataType[helper].apply(dataType, arguments);
        }
      });
    }
  });
});

module.exports = {
  ABSTRACT: ABSTRACT,
  STRING: STRING,
  CHAR: CHAR,
  TEXT: TEXT,
  NUMBER: NUMBER,
  INTEGER: INTEGER,
  BIGINT: BIGINT,
  FLOAT: FLOAT,
  TIME: TIME,
  DATE: DATE,
  DATEONLY: DATEONLY,
  BOOLEAN: BOOLEAN,
  NOW: NOW,
  BLOB: BLOB,
  DECIMAL: DECIMAL,
  UUID: UUID,
  UUIDV1: UUIDV1,
  UUIDV4: UUIDV4,
  HSTORE: HSTORE,
  JSON: JSONTYPE,
  JSONB: JSONB,
  VIRTUAL: VIRTUAL,
  ARRAY: ARRAY,
  NONE: VIRTUAL,
  ENUM: ENUM,
  RANGE: RANGE
};


'use strict';

var chai = require('chai')
  , expect = chai.expect
  , Support = require(__dirname + '/support')
  , DataTypes = require(__dirname + '/../../lib/data-types')
  , SequelizePromise = require(__dirname + '/../../lib/promise')
  , dialect = Support.getTestDialect()
  , _ = require('lodash')
  , sinon = require('sinon');

chai.config.includeStack = true;

describe(Support.getTestDialectTeaser('Promise'), function() {
  beforeEach(function() {
    return Support.prepareTransactionTest(this.sequelize).bind(this).then(function(sequelize) {
      this.sequelize = sequelize;
      this.User = this.sequelize.define('User', {
        username: { type: DataTypes.STRING },
        touchedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },
        aNumber: { type: DataTypes.INTEGER },
        bNumber: { type: DataTypes.INTEGER },

        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
        }
      });

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

  describe('increment', function() {
    beforeEach(function(done) {
      this.User.create({ id: 1, aNumber: 0, bNumber: 0 }).done(done);
    });

    it('with array', function(done) {
      var self = this;

      this.User
        .find(1)
        .then(function(user) {
          expect(user.id).to.equal(1);
          return user.increment(['aNumber'], { by: 2 });
        })
        .then(function(user) {
          // The following assertion would rock hard, but it's not implemented :(
          // expect(user.aNumber).to.equal(2)
          return self.User.find(1);
        })
        .then(function(user) {
          expect(user.aNumber).to.equal(2);
          done();
        });
    });

    it('should still work right with other concurrent updates', function(done) {
      var self = this;

      // Select something
      this.User
        .find(1)
        .then(function(user1) {
        // Select the user again (simulating a concurrent query)
          return self.User.find(1)
            .then(function(user2) {
              return user2
                .updateAttributes({ aNumber: user2.aNumber + 1 })
                .then(function() { return user1.increment(['aNumber'], { by: 2 }); })
                .then(function() { return self.User.find(1); })
                .then(function(user5) {
                  expect(user5.aNumber).to.equal(3);
                  done();
                });
            });
        });
    });

    it('with key value pair', function(done) {
      var self = this;

      this.User
        .find(1)
        .then(function(user1) {
          return user1.increment({ 'aNumber': 1, 'bNumber': 2});
        })
        .then(function() {
          return self.User.find(1);
        })
        .then(function(user3) {
          expect(user3.aNumber).to.equal(1);
          expect(user3.bNumber).to.equal(2);
          done();
        });
    });
  });

  describe('decrement', function() {
    beforeEach(function(done) {
      this.User.create({ id: 1, aNumber: 0, bNumber: 0 }).done(done);
    });

    it('with array', function(done) {
      var self = this;

      this.User
        .find(1)
        .then(function(user1) {
          return user1.decrement(['aNumber'], { by: 2 });
        })
        .then(function(user2) {
          return self.User.find(1);
        })
        .then(function(user3) {
          expect(user3.aNumber).to.equal(-2);
          done();
        });
    });

    it('with single field', function(done) {
      var self = this;

      this.User
        .find(1)
        .then(function(user1) {
          return user1.decrement(['aNumber'], { by: 2 });
        })
        .then(function(user3) {
          return self.User.find(1);
        })
        .then(function(user3) {
          expect(user3.aNumber).to.equal(-2);
          done();
        });
    });

    it('should still work right with other concurrent decrements', function(done) {
      var self = this;

      this.User
        .find(1)
        .then(function(user1) {
          var _done = _.after(3, function() {
            self.User
              .find(1)
              .then(function(user2) {
                expect(user2.aNumber).to.equal(-6);
                done();
              });
          });

          user1.decrement(['aNumber'], { by: 2 }).done(_done);
          user1.decrement(['aNumber'], { by: 2 }).done(_done);
          user1.decrement(['aNumber'], { by: 2 }).done(_done);
        });
      });
  });

  describe('reload', function() {
    it('should return a reference to the same DAO instead of creating a new one', function(done) {
      this.User
        .create({ username: 'John Doe' })
        .then(function(originalUser) {
          return originalUser
            .updateAttributes({ username: 'Doe John' })
            .then(function() {
              return originalUser.reload();
            })
            .then(function(updatedUser) {
              expect(originalUser === updatedUser).to.be.true;
              done();
            });
        });
    });

    it('should update the values on all references to the DAO', function(done) {
      var self = this;

      this.User
        .create({ username: 'John Doe' })
        .then(function(originalUser) {
          return self.User
            .find(originalUser.id)
            .then(function(updater) {
              return updater.updateAttributes({ username: 'Doe John' });
            })
            .then(function() {
              // We used a different reference when calling updateAttributes, so originalUser is now out of sync
              expect(originalUser.username).to.equal('John Doe');
              return originalUser.reload();
            }).then(function(updatedUser) {
              expect(originalUser.username).to.equal('Doe John');
              expect(updatedUser.username).to.equal('Doe John');
              done();
            });
        });
    });

    it('should update the associations as well', function(done) {
      var Book = this.sequelize.define('Book', { title: DataTypes.STRING })
        , Page = this.sequelize.define('Page', { content: DataTypes.TEXT });

      Book.hasMany(Page);
      Page.belongsTo(Book);

      Book
        .sync({ force: true })
        .then(function() {
          Page
            .sync({ force: true })
            .then(function() {
              return Book.create({ title: 'A very old book' });
            })
            .then(function(book) {
              return Page
                .create({ content: 'om nom nom' })
                .then(function(page) {
                  return book
                    .setPages([page])
                    .then(function() {
                      return Book
                        .find({
                          where: {id: book.id},
                          include: [Page]
                        })
                        .then(function(leBook) {
                          return page
                            .updateAttributes({ content: 'something totally different' })
                            .then(function(page) {
                              expect(leBook.Pages[0].content).to.equal('om nom nom');
                              expect(page.content).to.equal('something totally different');

                              return leBook
                                .reload()
                                .then(function(leBook) {
                                  expect(leBook.Pages[0].content).to.equal('something totally different');
                                  expect(page.content).to.equal('something totally different');
                                  done();
                                });
                            });
                        });
                    });
                });
            }, done);
        });
    });
  });

  describe('complete', function() {
    it('gets triggered if an error occurs', function(done) {
      this.User.find({ where: 'asdasdasd' }).then(null, function(err) {
        expect(err).to.be.ok;
        expect(err.message).to.be.ok;
        done();
      });
    });

    it('gets triggered if everything was ok', function(done) {
      this.User.count().then(function(result) {
        expect(result).to.not.be.undefined;
        done();
      });
    });
  });

  describe('save', function() {
    it('should fail a validation upon creating', function(done) {
      this.User.create({aNumber: 0, validateTest: 'hello'})
        .catch (function(err) {
          expect(err).to.be.ok;
          expect(err).to.be.an('object');
          expect(err.get('validateTest')).to.be.an('array');
          expect(err.get('validateTest')[0]).to.be.ok;
          expect(err.get('validateTest')[0].message).to.equal('Validation isInt failed');
          done();
        });
    });

    it('should fail a validation upon building', function(done) {
      this.User.build({aNumber: 0, validateCustom: 'aaaaaaaaaaaaaaaaaaaaaaaaaa'}).save()
        .catch (function(err) {
          expect(err).to.be.ok;
          expect(err).to.be.an('object');
          expect(err.get('validateCustom')).to.be.ok;
          expect(err.get('validateCustom')).to.be.an('array');
          expect(err.get('validateCustom')[0]).to.be.ok;
          expect(err.get('validateCustom')[0].message).to.equal('Length failed.');
          done();
        });
    });

    it('should fail a validation when updating', function(done) {
      this.User.create({aNumber: 0}).then(function(user) {
        return user.updateAttributes({validateTest: 'hello'});
      }).catch (function(err) {
        expect(err).to.be.ok;
        expect(err).to.be.an('object');
        expect(err.get('validateTest')).to.be.ok;
        expect(err.get('validateTest')).to.be.an('array');
        expect(err.get('validateTest')[0]).to.be.ok;
        expect(err.get('validateTest')[0].message).to.equal('Validation isInt failed');
        done();
      });
    });
  });

  describe('findOrCreate', function() {
    beforeEach(function(done) {
      this.User.create({ id: 1, aNumber: 0, bNumber: 0 }).done(done);
    });

    describe('with spread', function() {
      it('user not created', function(done) {
        this.User
          .findOrCreate({ where: { id: 1}})
          .spread(function(user, created) {
            expect(user.id).to.equal(1);
            expect(created).to.equal(false);
            expect(arguments.length).to.equal(2);
            done();
          });
      });

      it('user created', function(done) {
        this.User
          .findOrCreate({ where: { id: 2}})
          .spread(function(user, created) {
            expect(user.id).to.equal(2);
            expect(created).to.equal(true);
            expect(arguments.length).to.equal(2);
            done();
          });
      });
    });
  });

  describe('backwards compat', function() {
    it('should still work with .complete() when resolving', function(done) {
      var spy = sinon.spy()
        , promise = new SequelizePromise(function(resolve, reject) {
          resolve('abc');
        });

      promise.complete(spy);
      promise.then(function() {
        expect(spy.calledOnce).to.be.true;
        expect(spy.firstCall.args).to.deep.equal([null, 'abc']);
        done();
      });
    });

    it('should still work with .success() when resolving', function(done) {
      var spy = sinon.spy()
        , promise = new SequelizePromise(function(resolve, reject) {
          resolve('yay');
        });

      promise.success(spy);
      promise.then(function() {
        expect(spy.calledOnce).to.be.true;
        expect(spy.firstCall.args).to.deep.equal(['yay']);
        done();
      });
    });

    it('should still work with .on(\'success\') when resolving', function(done) {
      var spy = sinon.spy()
        , promise = new SequelizePromise(function(resolve, reject) {
          resolve('yoohoo');
        });

      promise.on('success', spy);
      promise.then(function() {
        expect(spy.calledOnce).to.be.true;
        expect(spy.firstCall.args).to.deep.equal(['yoohoo']);
        done();
      });
    });

    it('should still work with .done() when resolving multiple results', function(done) {
      var spy = sinon.spy()
        , promise = new SequelizePromise(function(resolve, reject) {
          resolve(SequelizePromise.all(['MyModel', true]));
        });

      promise.spread(spy);
      promise.done(function(err, model, created) {
        expect(model).to.equal('MyModel');
        expect(created).to.be.true;
        expect(spy.calledOnce).to.be.true;
        expect(spy.firstCall.args).to.deep.equal(['MyModel', true]);
        done();
      });
    });

    it('should still work with .complete() after chaining', function(done) {
      var spy = sinon.spy()
        , promise = new SequelizePromise(function(resolve, reject) {
          resolve('Heyo');
        });

      promise.then(function(result) {
        return result + '123';
      }).complete(function(err, result) {
        expect(err).not.to.be.ok;
        expect(result).to.equal('Heyo123');
        done();
      });
    });

    it('should still work with .success() when emitting', function(done) {
      var spy = sinon.spy()
        , promise = new SequelizePromise(function(resolve, reject) {
          // no-op
        });

      promise.success(spy);
      promise.then(function() {
        expect(spy.calledOnce).to.be.true;
        expect(spy.firstCall.args).to.deep.equal(['yay']);
        done();
      });

      promise.emit('success', 'yay');
    });

    it('should still work with .done() when rejecting', function(done) {
      var spy = sinon.spy()
        , promise = new SequelizePromise(function(resolve, reject) {
          reject(new Error('no'));
        });

      promise.done(spy);
      promise.catch(function() {
        expect(spy.calledOnce).to.be.true;
        expect(spy.firstCall.args[0]).to.be.an.instanceof(Error);
        done();
      });
    });

    it('should still work with .error() when throwing', function(done) {
      var spy = sinon.spy()
        , promise = new SequelizePromise(function(resolve, reject) {
          throw new Error('no');
        });

      promise.error(spy);
      promise.catch(function() {
        expect(spy.calledOnce).to.be.true;
        expect(spy.firstCall.args[0]).to.be.an.instanceof(Error);
        done();
      });
    });

    it('should still work with .on(\'error\') when throwing', function(done) {
      var spy = sinon.spy()
        , promise = new SequelizePromise(function(resolve, reject) {
          throw new Error('noway');
        });

      promise.on('error', spy);
      promise.catch(function() {
        expect(spy.calledOnce).to.be.true;
        expect(spy.firstCall.args[0]).to.be.an.instanceof(Error);
        done();
      });
    });

    it('should still work with .error() when emitting', function(done) {
      var spy = sinon.spy()
        , promise = new SequelizePromise(function(resolve, reject) {
          // no-op
        });

      promise.on('error', spy);
      promise.catch(function() {
        expect(spy.calledOnce).to.be.true;
        expect(spy.firstCall.args[0]).to.be.an.instanceof(Error);
        done();
      });

      promise.emit('error', new Error('noway'));
    });

    it('should still support sql events', function(done) {
      var spy = sinon.spy()
        , promise = new SequelizePromise(function(resolve, reject) {
          resolve('yay');
        });

      promise.on('sql', spy);

      promise.emit('sql', 'SQL STATEMENT 1');
      promise.emit('sql', 'SQL STATEMENT 2');

      promise.then(function() {
        expect(spy.calledTwice).to.be.true;
        done();
      });
    });

    describe('proxy', function() {
      it('should correctly work with success listeners', function(done) {
        var emitter = new SequelizePromise(function() {})
          , proxy = new SequelizePromise(function() {})
          , success = sinon.spy();

        emitter.success(success);
        proxy.success(function() {
          process.nextTick(function() {
            expect(success.called).to.be.true;
            done();
          });
        });

        proxy.proxy(emitter);
        proxy.emit('success');
      });

      it('should correctly work with complete/done listeners', function(done) {
        var promise = new SequelizePromise(function() {})
          , proxy = new SequelizePromise(function() {})
          , complete = sinon.spy();

        promise.complete(complete);
        proxy.complete(function() {
          process.nextTick(function() {
            expect(complete.called).to.be.true;
            done();
          });
        });

        proxy.proxy(promise);
        proxy.emit('success');
      });
    });

    describe('when emitting an error event with an array of errors', function() {
      describe('if an error handler is given', function() {
        it('should return the whole array', function(done) {
          var emitter = new SequelizePromise(function() {});
          var errors = [
            [
              new Error('First error'),
              new Error('Second error')
            ], [
              new Error('Third error')
            ]
          ];

          emitter.error(function(err) {
            expect(err).to.equal(errors);

            done();
          });
          emitter.emit('error', errors);
        });
      });
    });
  });
});


Results after syncing the dabatase
----------------------------------

After syncing the database, you'll see in the console the following:

```
Executing: CREATE TABLE IF NOT EXISTS "Trainer" ("first_name" VARCHAR(255), "last_name" VARCHAR(255), "id"   SERIAL , PRIMARY KEY ("id"));
Executing: CREATE TABLE IF NOT EXISTS "Series" ("title" VARCHAR(255), "sub_title" VARCHAR(255), "description" TEXT, "trainer_id" INTEGER REFERENCES "Trainer" ("id"), "id"   SERIAL , PRIMARY KEY ("id"));
Executing: CREATE TABLE IF NOT EXISTS "Video" ("title" VARCHAR(255), "sequence" INTEGER, "description" TEXT, "series_id" INTEGER REFERENCES "Series" ("id"), "id"   SERIAL , PRIMARY KEY ("id"));
```

Notice in the `Video` that `series_id` field has a referential integrity to `Series`:

```
"series_id" INTEGER REFERENCES "Series" ("id")
```

This is the output when describing the table's structure of the Postgres database:

**Trainer** table:
```
testsequelize=> \d+ "Trainer";
                                                        Table "public.Trainer"
   Column   |          Type          |                       Modifiers                        |
------------+------------------------+--------------------------------------------------------+
 first_name | character varying(255) |                                                        |
 last_name  | character varying(255) |                                                        |
 id         | integer                | not null default nextval('"Trainer_id_seq"'::regclass) |
Indexes:
    "Trainer_pkey" PRIMARY KEY, btree (id)
Referenced by:
    TABLE ""Series"" CONSTRAINT "Series_trainer_id_fkey" FOREIGN KEY (trainer_id) REFERENCES "Trainer"(id)
Has OIDs: no
```

**Series** table:
```
testsequelize=> \d+ "Series";
                                                          Table "public.Series"
     Column      |          Type          |                       Modifiers                       |
-----------------+------------------------+-------------------------------------------------------+
 title           | character varying(255) |                                                       |
 sub_title       | character varying(255) |                                                       |
 description     | text                   |                                                       |
 trainer_id      | integer                |                                                       |
 id              | integer                | not null default nextval('"Series_id_seq"'::regclass) |
Indexes:
    "Series_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
    "Series_trainer_id_fkey" FOREIGN KEY (trainer_id) REFERENCES "Trainer"(id)
Referenced by:
    TABLE ""Video"" CONSTRAINT "Video_series_id_fkey" FOREIGN KEY (series_id) REFERENCES "Series"(id)
Has OIDs: no
```

**Video** table:
```
testsequelize=> \d+ "Video";
                                                        Table "public.Video"
   Column    |          Type          |                      Modifiers                       |
-------------+------------------------+------------------------------------------------------+
 title       | character varying(255) |                                                      |
 sequence    | integer                |                                                      |
 description | text                   |                                                      |
 series_id   | integer                |                                                      |
 id          | integer                | not null default nextval('"Video_id_seq"'::regclass) |
Indexes:
    "Video_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
    "Video_series_id_fkey" FOREIGN KEY (series_id) REFERENCES "Series"(id)
Has OIDs: no
```


Sequelize is a promise-based Node.js ORM for Postgres, MySQL, SQLite, MariaDB and MSSQL. It features solid transaction support, relations, read replication and more.

[Installation](http://sequelize.readthedocs.org/en/latest/docs/getting-started/)

## Example usage
```js
var Sequelize = require('sequelize')
  , sequelize = new Sequelize('database', 'username', 'password');

var User = sequelize.define('User', {
  username: Sequelize.STRING,
  birthday: Sequelize.DATE
});

return sequelize.sync().then(function() {
  return User.create({
    username: 'sdepold',
    birthday: new Date(1986, 06, 28)
  }).then(function(sdepold) {
    console.log(sdepold.values)
  });
});
```


if(typeof subtype === 'function') { // if subtype passed - instantiate object of this subtype and return new function

if(typeof Subtype === 'function') { // if subtype passed - instantiate object of this subtype and return new function


'use strict';

var hstore = require("pg-hstore")({sanitize : true});

module.exports = {
  stringify: function(data) {
    if(data === null) return null;

    return hstore.stringify(data);
  },
  parse: function(value) {
    if(value === null) return null;

    return hstore.parse(value);
  }
};