不要怂,就是干,撸起袖子干!

Commit 350980c8 by Jan Aagaard Meier

Merge branch 'master' into rewriteAssoc

Conflicts:
	lib/associations/belongs-to-many.js
	lib/associations/has-many.js
	test/unit/associations/belongs-to-many.test.js
2 parents c54d6ca0 52443ede
...@@ -3,8 +3,8 @@ language: node_js ...@@ -3,8 +3,8 @@ language: node_js
node_js: node_js:
- "0.10" - "0.10"
- "0.12" - "0.12"
- "iojs-v1.8" - "iojs-v1"
# - "iojs" # iojs 2.0 is failing because of pg-native and nan - "iojs-v2"
sudo: false sudo: false
......
# Next # 3.2.0
- [FEATURE] Add support for new option `targetKey` in a belongs-to relationship for situations where the target key is not the id field.
- [FEATURE] Add support for keyword `after` in options of a field (useful for migrations), only for MySQL. [#3166](https://github.com/sequelize/sequelize/pull/3166) - [FEATURE] Add support for keyword `after` in options of a field (useful for migrations), only for MySQL. [#3166](https://github.com/sequelize/sequelize/pull/3166)
- [FEATURE] There's a new sequelize.truncate function to truncate all tables defined through the sequelize models [#2671](https://github.com/sequelize/sequelize/pull/2671) - [FEATURE] There's a new sequelize.truncate function to truncate all tables defined through the sequelize models [#2671](https://github.com/sequelize/sequelize/pull/2671)
- [FEATURE] Add support for MySQLs TINYTEXT, MEDIUMTEXT and LONGTEXT. [#3836](https://github.com/sequelize/sequelize/pull/3836) - [FEATURE] Add support for MySQLs TINYTEXT, MEDIUMTEXT and LONGTEXT. [#3836](https://github.com/sequelize/sequelize/pull/3836)
- [FEATURE] Provide warnings if you misuse data types. [#3839](https://github.com/sequelize/sequelize/pull/3839)
- [FIXED] Fix a case where Postgres arrays containing JSONB type was being generated as JSON type.
- [FIXED] Fix a case where `type` in `sequelize.query` was not being set to raw. [#3800](https://github.com/sequelize/sequelize/pull/3800) - [FIXED] Fix a case where `type` in `sequelize.query` was not being set to raw. [#3800](https://github.com/sequelize/sequelize/pull/3800)
- [FIXED] Fix an issue where include all was not being properly expanded for self-references [#3804](https://github.com/sequelize/sequelize/issues/3804) - [FIXED] Fix an issue where include all was not being properly expanded for self-references [#3804](https://github.com/sequelize/sequelize/issues/3804)
- [FIXED] Fix instance.changed regression to not return false negatives for not changed null values [#3812](https://github.com/sequelize/sequelize/issues/3812) - [FIXED] Fix instance.changed regression to not return false negatives for not changed null values [#3812](https://github.com/sequelize/sequelize/issues/3812)
- [FIXED] Fix isEmail validator to allow args: true [#3770](https://github.com/sequelize/sequelize/issues/3770) - [FIXED] Fix isEmail validator to allow args: true [#3770](https://github.com/sequelize/sequelize/issues/3770)
- [FIXED] Fix some occasions where `options.logging` was not used correctly - [FIXED] Fix all occasions where `options.logging` was not used correctly [#3834](https://github.com/sequelize/sequelize/issues/3834)
- [FIXED] Fix `Model#destroy()` to correctly use `options.transaction` - [FIXED] Fix `Model#destroy()` to correctly use `options.transaction`
- [FIXED] Fix `QueryInterface#showIndex()` to correctly pass on `options.transaction` - [FIXED] Fix `QueryInterface#showIndex()` to correctly pass on `options.transaction`
......
...@@ -56,7 +56,7 @@ var sequelize = new Sequelize('mysql://localhost:3306/database', {}) ...@@ -56,7 +56,7 @@ var sequelize = new Sequelize('mysql://localhost:3306/database', {})
| [options.query={}] | Object | Default options for sequelize.query | | [options.query={}] | Object | Default options for sequelize.query |
| [options.set={}] | Object | Default options for sequelize.set | | [options.set={}] | Object | Default options for sequelize.set |
| [options.sync={}] | Object | Default options for sequelize.sync | | [options.sync={}] | Object | Default options for sequelize.sync |
| [options.timezone='+00:00'] | String | The timezone used when converting a date from the database into a javascript date. The timezone is also used to SET TIMEZONE when connecting to the server, to ensure that the result of NOW, CURRENT_TIMESTAMP and other time related functions have in the right timezone. For best cross platform performance use the format +/-HH:MM. | | [options.timezone='+00:00'] | String | The timezone used when converting a date from the database into a JavaScript date. The timezone is also used to SET TIMEZONE when connecting to the server, to ensure that the result of NOW, CURRENT_TIMESTAMP and other time related functions have in the right timezone. For best cross platform performance use the format +/-HH:MM. |
| [options.logging=console.log] | Function | A function that gets executed everytime Sequelize would log something. | | [options.logging=console.log] | Function | A function that gets executed everytime Sequelize would log something. |
| [options.omitNull=false] | Boolean | A flag that defines if null values should be passed to SQL queries or not. | | [options.omitNull=false] | Boolean | A flag that defines if null values should be passed to SQL queries or not. |
| [options.native=false] | Boolean | A flag that defines if native library shall be used or not. Currently only has an effect for postgres | | [options.native=false] | Boolean | A flag that defines if native library shall be used or not. Currently only has an effect for postgres |
...@@ -446,7 +446,7 @@ For more about validation, see http://docs.sequelizejs.com/en/latest/docs/models ...@@ -446,7 +446,7 @@ For more about validation, see http://docs.sequelizejs.com/en/latest/docs/models
| attributes.column | String | DataType | Object | The description of a database column | | attributes.column | String | DataType | Object | The description of a database column |
| attributes.column.type | String | DataType | A string or a data type | | attributes.column.type | String | DataType | A string or a data type |
| [attributes.column.allowNull=true] | Boolean | If false, the column will have a NOT NULL constraint, and a not null validation will be run before an instance is saved. | | [attributes.column.allowNull=true] | Boolean | If false, the column will have a NOT NULL constraint, and a not null validation will be run before an instance is saved. |
| [attributes.column.defaultValue=null] | Any | A literal default value, a javascript function, or an SQL function (see `sequelize.fn`) | | [attributes.column.defaultValue=null] | Any | A literal default value, a JavaScript function, or an SQL function (see `sequelize.fn`) |
| [attributes.column.unique=false] | String | Boolean | If true, the column will get a unique constraint. If a string is provided, the column will be part of a composite unique index. If multiple columns have the same string, they will be part of the same unique index | | [attributes.column.unique=false] | String | Boolean | If true, the column will get a unique constraint. If a string is provided, the column will be part of a composite unique index. If multiple columns have the same string, they will be part of the same unique index |
| [attributes.column.primaryKey=false] | Boolean | | | [attributes.column.primaryKey=false] | Boolean | |
| [attributes.column.field=null] | String | If set, sequelize will map the attribute name to a different name in the database | | [attributes.column.field=null] | String | If set, sequelize will map the attribute name to a different name in the database |
......
...@@ -56,6 +56,17 @@ var User = this.sequelize.define('User', {/* attributes */}) ...@@ -56,6 +56,17 @@ var User = this.sequelize.define('User', {/* attributes */})
User.belongsTo(Company, {foreignKey: 'fk_company'}); // Adds fk_company to User User.belongsTo(Company, {foreignKey: 'fk_company'}); // Adds fk_company to User
``` ```
#### Target keys
By default the target key for a belongsTo relation will be the target primary key. To override this behavior, use the `targetKey` option.
```js
var User = this.sequelize.define('User', {/* attributes */})
, Company = this.sequelize.define('Company', {/* attributes */});
User.belongsTo(Company, {foreignKey: 'fk_companyname', targetKey: 'name'}); // Adds fk_companyname to User
```
### HasOne ### HasOne
...@@ -254,7 +265,7 @@ UPDATE comments SET commentable_id = 42, commentable = 'image' ...@@ -254,7 +265,7 @@ UPDATE comments SET commentable_id = 42, commentable = 'image'
The `getItem` utility function on `Comment` completes the picture - it simply converts the `commentable` string into a call to etiher `getImage` or `getPost`, providing an abstraction over whether a comment belongs to a post or an image. The `getItem` utility function on `Comment` completes the picture - it simply converts the `commentable` string into a call to etiher `getImage` or `getPost`, providing an abstraction over whether a comment belongs to a post or an image.
#### n:m #### n:m
Continuing with the idea of a polymorphic model, consider a tag table - an item can have multiple tags, and a tag can be related to several item Continuing with the idea of a polymorphic model, consider a tag table - an item can have multiple tags, and a tag can be related to several items.
For brevity, the example only shows a Post model, but in reality Tag would be related to several other models. For brevity, the example only shows a Post model, but in reality Tag would be related to several other models.
......
...@@ -273,25 +273,8 @@ migration.removeIndex('Person', ['firstname', 'lastname']) ...@@ -273,25 +273,8 @@ migration.removeIndex('Person', ['firstname', 'lastname'])
``` ```
## Programmatic use ## Programmatic use
Sequelize has a [sister library](https://github.com/sequelize/umzug) for programmatically handling execution and logging of migration tasks.
If you need to interact with the migrator within your code, you can easily achieve that via `sequelize.getMigrator`. You can specify the path to your migrations as well as a pattern which represents the files that contain the migrations.
```js
var migrator = sequelize.getMigrator({
path: process.cwd() + '/database/migrations',
filesFilter: /\.coffee$/
})
```
Once you have a migrator object, you can run its migration with `migrator.migrate`. By default, this will execute all the up methods within your pending migrations. If you want to rollback a migration, just call it like this:
```js
migrator
.migrate({ method: 'down' })
.then(function() {
// The migrations have been executed!
})
```
[0]: http://gulpjs.com/ [0]: http://gulpjs.com/
[1]: https://github.com/sequelize/cli [1]: https://github.com/sequelize/cli
......
...@@ -22,6 +22,24 @@ var BelongsToMany = function(source, target, options) { ...@@ -22,6 +22,24 @@ var BelongsToMany = function(source, target, options) {
this.isSelfAssociation = this.source === this.target; this.isSelfAssociation = this.source === this.target;
this.doubleLinked = false; this.doubleLinked = false;
this.as = this.options.as; this.as = this.options.as;
if (this.as) {
this.isAliased = true;
if (Utils._.isPlainObject(this.as)) {
this.options.name = this.as;
this.as = this.as.plural;
} else {
this.options.name = {
plural: this.as,
singular: Utils.singularize(this.as)
};
}
} else {
this.as = this.target.options.name.plural;
this.options.name = this.target.options.name;
}
this.combinedTableName = Utils.combineTableNames( this.combinedTableName = Utils.combineTableNames(
this.source.tableName, this.source.tableName,
this.isSelfAssociation ? (this.as || this.target.tableName) : this.target.tableName this.isSelfAssociation ? (this.as || this.target.tableName) : this.target.tableName
...@@ -137,23 +155,6 @@ var BelongsToMany = function(source, target, options) { ...@@ -137,23 +155,6 @@ var BelongsToMany = function(source, target, options) {
this.options.tableName = this.combinedName = (this.through.model === Object(this.through.model) ? this.through.model.tableName : this.through.model); this.options.tableName = this.combinedName = (this.through.model === Object(this.through.model) ? this.through.model.tableName : this.through.model);
if (this.as) {
this.isAliased = true;
if (_.isPlainObject(this.as)) {
this.options.name = this.as;
this.as = this.as.plural;
} else {
this.options.name = {
plural: this.as,
singular: Utils.singularize(this.as)
};
}
} else {
this.as = this.target.options.name.plural;
this.options.name = this.target.options.name;
}
this.associationAccessor = this.as; this.associationAccessor = this.as;
// Get singular and plural names, trying to uppercase the first letter, unless the model forbids it // Get singular and plural names, trying to uppercase the first letter, unless the model forbids it
......
...@@ -55,7 +55,7 @@ var BelongsTo = function(source, target, options) { ...@@ -55,7 +55,7 @@ var BelongsTo = function(source, target, options) {
!this.target.options.underscored !this.target.options.underscored
); );
this.targetIdentifier = this.target.primaryKeyAttribute; this.targetIdentifier = this.options.targetKey || this.target.primaryKeyAttribute;
this.associationAccessor = this.as; this.associationAccessor = this.as;
this.options.useHooks = options.useHooks; this.options.useHooks = options.useHooks;
......
...@@ -162,8 +162,9 @@ Mixin.hasOne = singleLinked(HasOne); ...@@ -162,8 +162,9 @@ Mixin.hasOne = singleLinked(HasOne);
* @param {Model} target * @param {Model} target
* @param {object} [options] * @param {object} [options]
* @param {boolean} [options.hooks=false] Set to true to run before-/afterDestroy hooks when an associated model is deleted because of a cascade. For example if `User.hasOne(Profile, {onDelete: 'cascade', hooks:true})`, the before-/afterDestroy hooks for profile will be called when a user is deleted. Otherwise the profile will be deleted without invoking any hooks * @param {boolean} [options.hooks=false] Set to true to run before-/afterDestroy hooks when an associated model is deleted because of a cascade. For example if `User.hasOne(Profile, {onDelete: 'cascade', hooks:true})`, the before-/afterDestroy hooks for profile will be called when a user is deleted. Otherwise the profile will be deleted without invoking any hooks
* @param {string} [options.as] The alias of this model, in singular form. See also the `name` option passed to `sequelize.define`. If you create multiple associations between the same tables, you should provide an alias to be able to distinguish between them. If you provide an alias when creating the assocition, you should provide the same alias when eager loading and when getting assocated models. Defaults to the singularized name of target * @param {string} [options.as] The alias of this model, in singular form. See also the `name` option passed to `sequelize.define`. If you create multiple associations between the same tables, you should provide an alias to be able to distinguish between them. If you provide an alias when creating the association, you should provide the same alias when eager loading and when getting assocated models. Defaults to the singularized name of target
* @param {string|object} [options.foreignKey] The name of the foreign key in the source table or an object representing the type definition for the foreign column (see `Sequelize.define` for syntax). When using an object, you can add a `name` property to set the name of the colum. Defaults to the name of target + primary key of target * @param {string|object} [options.foreignKey] The name of the foreign key in the source table or an object representing the type definition for the foreign column (see `Sequelize.define` for syntax). When using an object, you can add a `name` property to set the name of the column. Defaults to the name of target + primary key of target
* @param {string} [options.targetKey] The name of the field to use as the key for the association in the target table. Defaults to the primary key of the target table
* @param {string} [options.onDelete='SET NULL'] * @param {string} [options.onDelete='SET NULL']
* @param {string} [options.onUpdate='CASCADE'] * @param {string} [options.onUpdate='CASCADE']
* @param {boolean} [options.constraints=true] Should on update and on delete constraints be enabled on the foreign key. * @param {boolean} [options.constraints=true] Should on update and on delete constraints be enabled on the foreign key.
......
'use strict'; 'use strict';
var util = require('util') var util = require('util')
, _ = require('lodash'); , _ = require('lodash')
, warnings = {};
/** /**
* A convenience class holding commonly used data types. The datatypes are used when definining a new model using `Sequelize.define`, like this: * A convenience class holding commonly used data types. The datatypes are used when definining a new model using `Sequelize.define`, like this:
...@@ -38,12 +39,20 @@ var ABSTRACT = function(options) { ...@@ -38,12 +39,20 @@ var ABSTRACT = function(options) {
}; };
ABSTRACT.prototype.dialectTypes = '';
ABSTRACT.prototype.toString = function() { ABSTRACT.prototype.toString = function() {
return this.toSql(); return this.toSql();
}; };
ABSTRACT.prototype.toSql = function() { ABSTRACT.prototype.toSql = function() {
return this.key; return this.key;
}; };
ABSTRACT.prototype.warn = function(text) {
if (!warnings[text]) {
warnings[text] = true;
console.warn('>> WARNING:', text, '\n>> Check:', this.dialectTypes);
}
};
/** /**
* A variable length string. Default length 255 * A variable length string. Default length 255
......
...@@ -1194,7 +1194,7 @@ var QueryGenerator = { ...@@ -1194,7 +1194,7 @@ var QueryGenerator = {
, tableRight = as , tableRight = as
, attrRight = association.associationType !== 'BelongsTo' ? , attrRight = association.associationType !== 'BelongsTo' ?
association.identifierField || association.identifier : association.identifierField || association.identifier :
right.rawAttributes[primaryKeysRight[0]].field || primaryKeysRight[0] right.rawAttributes[association.targetIdentifier || primaryKeysRight[0]].field
, joinOn , joinOn
, subQueryJoinOn; , subQueryJoinOn;
......
...@@ -4,6 +4,8 @@ var BaseTypes = require('../../data-types') ...@@ -4,6 +4,8 @@ var BaseTypes = require('../../data-types')
, util = require('util') , util = require('util')
, _ = require('lodash'); , _ = require('lodash');
BaseTypes.ABSTRACT.prototype.dialectTypes = 'https://msdn.microsoft.com/en-us/library/ms187752%28v=sql.110%29.aspx';
var STRING = function() { var STRING = function() {
if (!(this instanceof STRING)) return new STRING(); if (!(this instanceof STRING)) return new STRING();
BaseTypes.STRING.apply(this, arguments); BaseTypes.STRING.apply(this, arguments);
...@@ -21,8 +23,12 @@ STRING.prototype.toSql = function() { ...@@ -21,8 +23,12 @@ STRING.prototype.toSql = function() {
BaseTypes.TEXT.prototype.toSql = function() { BaseTypes.TEXT.prototype.toSql = function() {
// TEXT is deprecated in mssql and it would normally be saved as a non-unicode string. // TEXT is deprecated in mssql and it would normally be saved as a non-unicode string.
// Using unicode is just future proof // Using unicode is just future proof
if (this._length.toLowerCase() === 'tiny') { if (this._length) {
return 'NVARCHAR(256)'; // tiny text should be 2^8 < 8000 if (this._length.toLowerCase() === 'tiny') { // tiny = 2^8
this.warn('MSSQL does not support TEXT with the `length` = `tiny` option. `NVARCHAR(256)` will be used instead.');
return 'NVARCHAR(256)';
}
this.warn('MSSQL does not support TEXT with the `length` option. `NVARCHAR(MAX)` will be used instead.');
} }
return 'NVARCHAR(MAX)'; return 'NVARCHAR(MAX)';
}; };
...@@ -38,8 +44,12 @@ BOOLEAN.prototype.toSql = function() { ...@@ -38,8 +44,12 @@ BOOLEAN.prototype.toSql = function() {
}; };
BaseTypes.BLOB.prototype.toSql = function() { BaseTypes.BLOB.prototype.toSql = function() {
if (this._length.toLowerCase() === 'tiny') { if (this._length) {
return 'VARBINARY(256)'; // tiny blobs should be 2^8 < 8000 if (this._length.toLowerCase() === 'tiny') { // tiny = 2^8
this.warn('MSSQL does not support BLOB with the `length` = `tiny` option. `VARBINARY(256)` will be used instead.');
return 'VARBINARY(256)';
}
this.warn('MSSQL does not support BLOB with the `length` option. `VARBINARY(MAX)` will be used instead.');
} }
return 'VARBINARY(MAX)'; return 'VARBINARY(MAX)';
}; };
...@@ -78,11 +88,14 @@ var INTEGER = function() { ...@@ -78,11 +88,14 @@ var INTEGER = function() {
if (!(this instanceof INTEGER)) return new INTEGER(); if (!(this instanceof INTEGER)) return new INTEGER();
BaseTypes.INTEGER.apply(this, arguments); BaseTypes.INTEGER.apply(this, arguments);
// MSSQL does not support any parameters for integer // MSSQL does not support any options for integer
this._length = undefined; if (this._length || this.options.length || this._unsigned || this._zerofill) {
this.options.length = undefined; this.warn('MSSQL does not support INTEGER with options. Plain `INTEGER` will be used instead.');
this._unsigned = undefined; this._length = undefined;
this._zerofill = undefined; this.options.length = undefined;
this._unsigned = undefined;
this._zerofill = undefined;
}
}; };
util.inherits(INTEGER, BaseTypes.INTEGER); util.inherits(INTEGER, BaseTypes.INTEGER);
...@@ -90,11 +103,14 @@ var BIGINT = function() { ...@@ -90,11 +103,14 @@ var BIGINT = function() {
if (!(this instanceof BIGINT)) return new BIGINT(); if (!(this instanceof BIGINT)) return new BIGINT();
BaseTypes.BIGINT.apply(this, arguments); BaseTypes.BIGINT.apply(this, arguments);
// MSSQL does not support any parameters for bigint // MSSQL does not support any options for bigint
this._length = undefined; if (this._length || this.options.length || this._unsigned || this._zerofill) {
this.options.length = undefined; this.warn('MSSQL does not support BIGINT with options. Plain `BIGINT` will be used instead.');
this._unsigned = undefined; this._length = undefined;
this._zerofill = undefined; this.options.length = undefined;
this._unsigned = undefined;
this._zerofill = undefined;
}
}; };
util.inherits(BIGINT, BaseTypes.BIGINT); util.inherits(BIGINT, BaseTypes.BIGINT);
...@@ -102,11 +118,14 @@ var REAL = function() { ...@@ -102,11 +118,14 @@ var REAL = function() {
if (!(this instanceof REAL)) return new REAL(); if (!(this instanceof REAL)) return new REAL();
BaseTypes.REAL.apply(this, arguments); BaseTypes.REAL.apply(this, arguments);
// MSSQL does not support any parameters for real // MSSQL does not support any options for real
this._length = undefined; if (this._length || this.options.length || this._unsigned || this._zerofill) {
this.options.length = undefined; this.warn('MSSQL does not support REAL with options. Plain `REAL` will be used instead.');
this._unsigned = undefined; this._length = undefined;
this._zerofill = undefined; this.options.length = undefined;
this._unsigned = undefined;
this._zerofill = undefined;
}
}; };
util.inherits(REAL, BaseTypes.REAL); util.inherits(REAL, BaseTypes.REAL);
...@@ -114,15 +133,23 @@ var FLOAT = function() { ...@@ -114,15 +133,23 @@ var FLOAT = function() {
if (!(this instanceof FLOAT)) return new FLOAT(); if (!(this instanceof FLOAT)) return new FLOAT();
BaseTypes.FLOAT.apply(this, arguments); BaseTypes.FLOAT.apply(this, arguments);
// MSSQL does only support lengths as parameter. // MSSQL does only support lengths as option.
// Values between 1-24 result in 7 digits precision (4 bytes storage size) // Values between 1-24 result in 7 digits precision (4 bytes storage size)
// Values between 25-53 result in 15 digits precision (8 bytes storage size) // Values between 25-53 result in 15 digits precision (8 bytes storage size)
// If decimals are provided remove these and print a warning
if (this._decimals) { if (this._decimals) {
this.warn('MSSQL does not support Float with decimals. Plain `FLOAT` will be used instead.');
this._length = undefined; this._length = undefined;
this.options.length = undefined; this.options.length = undefined;
} }
this._unsigned = undefined; if (this._unsigned) {
this._zerofill = undefined; this.warn('MSSQL does not support Float unsigned. `UNSIGNED` was removed.');
this._unsigned = undefined;
}
if (this._zerofill) {
this.warn('MSSQL does not support Float zerofill. `ZEROFILL` was removed.');
this._zerofill = undefined;
}
}; };
util.inherits(FLOAT, BaseTypes.FLOAT); util.inherits(FLOAT, BaseTypes.FLOAT);
......
...@@ -4,6 +4,8 @@ var BaseTypes = require('../../data-types') ...@@ -4,6 +4,8 @@ var BaseTypes = require('../../data-types')
, util = require('util') , util = require('util')
, _ = require('lodash'); , _ = require('lodash');
BaseTypes.ABSTRACT.prototype.dialectTypes = 'https://dev.mysql.com/doc/refman/5.7/en/data-types.html';
var UUID = function() { var UUID = function() {
if (!(this instanceof UUID)) return new UUID(); if (!(this instanceof UUID)) return new UUID();
BaseTypes.UUID.apply(this, arguments); BaseTypes.UUID.apply(this, arguments);
......
...@@ -4,6 +4,8 @@ var BaseTypes = require('../../data-types') ...@@ -4,6 +4,8 @@ var BaseTypes = require('../../data-types')
, util = require('util') , util = require('util')
, _ = require('lodash'); , _ = require('lodash');
BaseTypes.ABSTRACT.prototype.dialectTypes = 'http://www.postgresql.org/docs/9.4/static/datatype.html';
var STRING = function() { var STRING = function() {
if (!(this instanceof STRING)) return new STRING(); if (!(this instanceof STRING)) return new STRING();
BaseTypes.STRING.apply(this, arguments); BaseTypes.STRING.apply(this, arguments);
...@@ -18,6 +20,10 @@ STRING.prototype.toSql = function() { ...@@ -18,6 +20,10 @@ STRING.prototype.toSql = function() {
}; };
BaseTypes.TEXT.prototype.toSql = function() { BaseTypes.TEXT.prototype.toSql = function() {
if (this._length) {
this.warn('PostgreSQL does not support TEXT with options. Plain `TEXT` will be used instead.');
this._length = undefined;
}
return 'TEXT'; return 'TEXT';
}; };
...@@ -59,10 +65,13 @@ var INTEGER = function() { ...@@ -59,10 +65,13 @@ var INTEGER = function() {
BaseTypes.INTEGER.apply(this, arguments); BaseTypes.INTEGER.apply(this, arguments);
// POSTGRES does not support any parameters for integer // POSTGRES does not support any parameters for integer
this._length = undefined; if (this._length || this.options.length || this._unsigned || this._zerofill) {
this.options.length = undefined; this.warn('PostgreSQL does not support INTEGER with options. Plain `INTEGER` will be used instead.');
this._unsigned = undefined; this._length = undefined;
this._zerofill = undefined; this.options.length = undefined;
this._unsigned = undefined;
this._zerofill = undefined;
}
}; };
util.inherits(INTEGER, BaseTypes.INTEGER); util.inherits(INTEGER, BaseTypes.INTEGER);
...@@ -71,10 +80,13 @@ var BIGINT = function() { ...@@ -71,10 +80,13 @@ var BIGINT = function() {
BaseTypes.BIGINT.apply(this, arguments); BaseTypes.BIGINT.apply(this, arguments);
// POSTGRES does not support any parameters for bigint // POSTGRES does not support any parameters for bigint
this._length = undefined; if (this._length || this.options.length || this._unsigned || this._zerofill) {
this.options.length = undefined; this.warn('PostgreSQL does not support BIGINT with options. Plain `BIGINT` will be used instead.');
this._unsigned = undefined; this._length = undefined;
this._zerofill = undefined; this.options.length = undefined;
this._unsigned = undefined;
this._zerofill = undefined;
}
}; };
util.inherits(BIGINT, BaseTypes.BIGINT); util.inherits(BIGINT, BaseTypes.BIGINT);
...@@ -83,10 +95,13 @@ var REAL = function() { ...@@ -83,10 +95,13 @@ var REAL = function() {
BaseTypes.REAL.apply(this, arguments); BaseTypes.REAL.apply(this, arguments);
// POSTGRES does not support any parameters for real // POSTGRES does not support any parameters for real
this._length = undefined; if (this._length || this.options.length || this._unsigned || this._zerofill) {
this.options.length = undefined; this.warn('PostgreSQL does not support REAL with options. Plain `REAL` will be used instead.');
this._unsigned = undefined; this._length = undefined;
this._zerofill = undefined; this.options.length = undefined;
this._unsigned = undefined;
this._zerofill = undefined;
}
}; };
util.inherits(REAL, BaseTypes.REAL); util.inherits(REAL, BaseTypes.REAL);
...@@ -95,10 +110,13 @@ var DOUBLE = function() { ...@@ -95,10 +110,13 @@ var DOUBLE = function() {
BaseTypes.DOUBLE.apply(this, arguments); BaseTypes.DOUBLE.apply(this, arguments);
// POSTGRES does not support any parameters for double // POSTGRES does not support any parameters for double
this._length = undefined; if (this._length || this.options.length || this._unsigned || this._zerofill) {
this.options.length = undefined; this.warn('PostgreSQL does not support DOUBLE with options. Plain `DOUBLE` will be used instead.');
this._unsigned = undefined; this._length = undefined;
this._zerofill = undefined; this.options.length = undefined;
this._unsigned = undefined;
this._zerofill = undefined;
}
}; };
util.inherits(DOUBLE, BaseTypes.DOUBLE); util.inherits(DOUBLE, BaseTypes.DOUBLE);
...@@ -109,17 +127,29 @@ var FLOAT = function() { ...@@ -109,17 +127,29 @@ var FLOAT = function() {
// POSTGRES does only support lengths as parameter. // POSTGRES does only support lengths as parameter.
// Values between 1-24 result in REAL // Values between 1-24 result in REAL
// Values between 25-53 result in DOUBLE PRECISION // Values between 25-53 result in DOUBLE PRECISION
// If decimals are provided remove these and print a warning
if (this._decimals) { if (this._decimals) {
this.warn('PostgreSQL does not support FLOAT with decimals. Plain `FLOAT` will be used instead.');
this._length = undefined; this._length = undefined;
this.options.length = undefined; this.options.length = undefined;
this._decimals = undefined; this._decimals = undefined;
} }
this._unsigned = undefined; if (this._unsigned) {
this._zerofill = undefined; this.warn('PostgreSQL does not support FLOAT unsigned. `UNSIGNED` was removed.');
this._unsigned = undefined;
}
if (this._zerofill) {
this.warn('PostgreSQL does not support FLOAT zerofill. `ZEROFILL` was removed.');
this._zerofill = undefined;
}
}; };
util.inherits(FLOAT, BaseTypes.FLOAT); util.inherits(FLOAT, BaseTypes.FLOAT);
BaseTypes.BLOB.prototype.toSql = function() { BaseTypes.BLOB.prototype.toSql = function() {
if (this._length) {
this.warn('PostgreSQL does not support BLOB (BYTEA) with options. Plain `BYTEA` will be used instead.');
this._length = undefined;
}
return 'BYTEA'; return 'BYTEA';
}; };
......
...@@ -890,12 +890,13 @@ var QueryGenerator = { ...@@ -890,12 +890,13 @@ var QueryGenerator = {
else if (DataTypes.ARRAY.is(field.type, DataTypes.RANGE)) { // escape array of ranges else if (DataTypes.ARRAY.is(field.type, DataTypes.RANGE)) { // escape array of ranges
return 'ARRAY[' + Utils._.map(value, function(v){return "'" + range.stringify(v) + "'";}).join(',') + ']::' + field.type.toString(); return 'ARRAY[' + Utils._.map(value, function(v){return "'" + range.stringify(v) + "'";}).join(',') + ']::' + field.type.toString();
} }
} else if (field && (field.type instanceof DataTypes.JSON || field.type instanceof DataTypes.JSONB)) { } else if (field && field.type instanceof DataTypes.JSON) {
value = JSON.stringify(value); value = JSON.stringify(value);
} else if (Array.isArray(value) && field && DataTypes.ARRAY.is(field.type, DataTypes.JSON)) { } else if (Array.isArray(value) && field && DataTypes.ARRAY.is(field.type, DataTypes.JSON)) {
var jsonType = field.type.type; // type may be JSON or JSONB
return 'ARRAY[' + value.map(function (v) { return 'ARRAY[' + value.map(function (v) {
return SqlString.escape(JSON.stringify(v), false, this.options.timezone, this.dialect, field); return SqlString.escape(JSON.stringify(v), false, this.options.timezone, this.dialect, field);
}, this).join(',') + ']::JSON[]'; }, this).join(',') + ']::' + jsonType.key + '[]';
} }
return SqlString.escape(value, false, this.options.timezone, this.dialect, field); return SqlString.escape(value, false, this.options.timezone, this.dialect, field);
......
...@@ -4,6 +4,8 @@ var BaseTypes = require('../../data-types') ...@@ -4,6 +4,8 @@ var BaseTypes = require('../../data-types')
, util = require('util') , util = require('util')
, _ = require('lodash'); , _ = require('lodash');
BaseTypes.ABSTRACT.prototype.dialectTypes = 'https://www.sqlite.org/datatype3.html';
var STRING = function() { var STRING = function() {
if (!(this instanceof STRING)) return new STRING(); if (!(this instanceof STRING)) return new STRING();
BaseTypes.STRING.apply(this, arguments); BaseTypes.STRING.apply(this, arguments);
...@@ -19,6 +21,10 @@ STRING.prototype.toSql = function() { ...@@ -19,6 +21,10 @@ STRING.prototype.toSql = function() {
}; };
BaseTypes.TEXT.prototype.toSql = function() { BaseTypes.TEXT.prototype.toSql = function() {
if (this._length) {
this.warn('SQLite does not support TEXT with options. Plain `TEXT` will be used instead.');
this._length = undefined;
}
return 'TEXT'; return 'TEXT';
}; };
......
...@@ -65,7 +65,7 @@ var changeColumn = function(tableName, attributes, options) { ...@@ -65,7 +65,7 @@ var changeColumn = function(tableName, attributes, options) {
, self = this; , self = this;
options = options || {}; options = options || {};
return this.describeTable(tableName).then(function(fields) { return this.describeTable(tableName, options).then(function(fields) {
fields[attributeName] = attributes[attributeName]; fields[attributeName] = attributes[attributeName];
var sql = self.QueryGenerator.removeColumnQuery(tableName, fields) var sql = self.QueryGenerator.removeColumnQuery(tableName, fields)
...@@ -99,7 +99,7 @@ var renameColumn = function(tableName, attrNameBefore, attrNameAfter, options) { ...@@ -99,7 +99,7 @@ var renameColumn = function(tableName, attrNameBefore, attrNameAfter, options) {
var self = this; var self = this;
options = options || {}; options = options || {};
return this.describeTable(tableName).then(function(fields) { return this.describeTable(tableName, options).then(function(fields) {
fields[attrNameAfter] = Utils._.clone(fields[attrNameBefore]); fields[attrNameAfter] = Utils._.clone(fields[attrNameBefore]);
delete fields[attrNameBefore]; delete fields[attrNameBefore];
......
...@@ -538,7 +538,7 @@ Instance.prototype.save = function(options) { ...@@ -538,7 +538,7 @@ Instance.prototype.save = function(options) {
} }
if (this.isNewRecord === false) { if (this.isNewRecord === false) {
if (primaryKeyName && !this.get(primaryKeyName, {raw: true})) { if (primaryKeyName && this.get(primaryKeyName, {raw: true}) === undefined) {
throw new Error('You attempted to save an instance with no primary key, this is not allowed since it would result in a global update'); throw new Error('You attempted to save an instance with no primary key, this is not allowed since it would result in a global update');
} }
} }
...@@ -617,7 +617,7 @@ Instance.prototype.save = function(options) { ...@@ -617,7 +617,7 @@ Instance.prototype.save = function(options) {
transaction: options.transaction, transaction: options.transaction,
logging: options.logging logging: options.logging
}).then(function () { }).then(function () {
return self[include.association.accessors.set](instance, {save: false}); return self[include.association.accessors.set](instance, {save: false, logging: options.logging});
}); });
}); });
}) })
......
...@@ -881,7 +881,7 @@ Model.prototype.sync = function(options) { ...@@ -881,7 +881,7 @@ Model.prototype.sync = function(options) {
}); });
return Promise.map(indexes, function (index) { return Promise.map(indexes, function (index) {
return self.QueryInterface.addIndex(self.getTableName(options), index, self.tableName); return self.QueryInterface.addIndex(self.getTableName(options), _.assign({logging: options.logging}, index), self.tableName);
}); });
}).return(this); }).return(this);
}; };
...@@ -2255,8 +2255,8 @@ Model.prototype.update = function(values, options) { ...@@ -2255,8 +2255,8 @@ Model.prototype.update = function(values, options) {
* *
* @return {Promise} * @return {Promise}
*/ */
Model.prototype.describe = function(schema) { Model.prototype.describe = function(schema, options) {
return this.QueryInterface.describeTable(this.tableName, schema || this.options.schema || undefined); return this.QueryInterface.describeTable(this.tableName, _.assign({schema: schema || this.options.schema || undefined}, options));
}; };
Model.prototype.$getDefaultTimestamp = function(attr) { Model.prototype.$getDefaultTimestamp = function(attr) {
......
...@@ -65,31 +65,31 @@ CounterCache.prototype.injectHooks = function() { ...@@ -65,31 +65,31 @@ CounterCache.prototype.injectHooks = function() {
previousTargetId; previousTargetId;
CounterUtil = { CounterUtil = {
update: function (targetId) { update: function (targetId, options) {
var query = CounterUtil._targetQuery(targetId); var query = CounterUtil._targetQuery(targetId);
return association.target.count({ where: query }).then(function (count) { return association.target.count({ where: query, logging: options && options.logging }).then(function (count) {
var newValues = {}; var newValues = {};
query = CounterUtil._sourceQuery(targetId); query = CounterUtil._sourceQuery(targetId);
newValues[counterCacheInstance.columnName] = count; newValues[counterCacheInstance.columnName] = count;
return association.source.update(newValues, { where: query }); return association.source.update(newValues, { where: query, logging: options && options.logging });
}); });
}, },
increment: function (targetId) { increment: function (targetId, options) {
var query = CounterUtil._sourceQuery(targetId); var query = CounterUtil._sourceQuery(targetId);
return association.source.find({ where: query }).then(function (instance) { return association.source.find({ where: query, logging: options && options.logging }).then(function (instance) {
return instance.increment(counterCacheInstance.columnName, { by: 1 }); return instance.increment(counterCacheInstance.columnName, { by: 1, logging: options && options.logging });
}); });
}, },
decrement: function (targetId) { decrement: function (targetId, options) {
var query = CounterUtil._sourceQuery(targetId); var query = CounterUtil._sourceQuery(targetId);
return association.source.find({ where: query }).then(function (instance) { return association.source.find({ where: query, logging: options && options.logging }).then(function (instance) {
return instance.decrement(counterCacheInstance.columnName, { by: 1 }); return instance.decrement(counterCacheInstance.columnName, { by: 1, logging: options && options.logging });
}); });
}, },
// helpers // helpers
...@@ -109,51 +109,51 @@ CounterCache.prototype.injectHooks = function() { ...@@ -109,51 +109,51 @@ CounterCache.prototype.injectHooks = function() {
} }
}; };
fullUpdateHook = function (target) { fullUpdateHook = function (target, options) {
var targetId = target.get(association.identifier) var targetId = target.get(association.identifier)
, promises = []; , promises = [];
if (targetId) { if (targetId) {
promises.push(CounterUtil.update(targetId)); promises.push(CounterUtil.update(targetId, options));
} }
if (previousTargetId && previousTargetId !== targetId) { if (previousTargetId && previousTargetId !== targetId) {
promises.push(CounterUtil.update(previousTargetId)); promises.push(CounterUtil.update(previousTargetId, options));
} }
return Promise.all(promises).return(undefined); return Promise.all(promises).return(undefined);
}; };
atomicHooks = { atomicHooks = {
create: function (target) { create: function (target, options) {
var targetId = target.get(association.identifier); var targetId = target.get(association.identifier);
if (targetId) { if (targetId) {
return CounterUtil.increment(targetId); return CounterUtil.increment(targetId, options);
} }
}, },
update: function (target) { update: function (target, options) {
var targetId = target.get(association.identifier) var targetId = target.get(association.identifier)
, promises = []; , promises = [];
if (targetId && !previousTargetId) { if (targetId && !previousTargetId) {
promises.push(CounterUtil.increment(targetId)); promises.push(CounterUtil.increment(targetId, options));
} }
if (!targetId && previousTargetId) { if (!targetId && previousTargetId) {
promises.push(CounterUtil.decrement(targetId)); promises.push(CounterUtil.decrement(targetId, options));
} }
if (previousTargetId && targetId && previousTargetId !== targetId) { if (previousTargetId && targetId && previousTargetId !== targetId) {
promises.push(CounterUtil.increment(targetId)); promises.push(CounterUtil.increment(targetId, options));
promises.push(CounterUtil.decrement(previousTargetId)); promises.push(CounterUtil.decrement(previousTargetId, options));
} }
return Promise.all(promises); return Promise.all(promises);
}, },
destroy: function (target) { destroy: function (target, options) {
var targetId = target.get(association.identifier); var targetId = target.get(association.identifier);
if (targetId) { if (targetId) {
return CounterUtil.decrement(targetId); return CounterUtil.decrement(targetId, options);
} }
} }
}; };
......
...@@ -32,11 +32,18 @@ shimCLS(Promise.prototype, 'error', [0]); ...@@ -32,11 +32,18 @@ shimCLS(Promise.prototype, 'error', [0]);
shimCLS(Promise.prototype, 'finally', [0]); shimCLS(Promise.prototype, 'finally', [0]);
// Collections // Collections
shimCLS(Promise, 'map', [1]);
shimCLS(Promise, 'reduce', [1]);
shimCLS(Promise, 'filter', [1]);
shimCLS(Promise, 'each', [1]);
shimCLS(Promise.prototype, 'map', [0]); shimCLS(Promise.prototype, 'map', [0]);
shimCLS(Promise.prototype, 'reduce', [0]); shimCLS(Promise.prototype, 'reduce', [0]);
shimCLS(Promise.prototype, 'filter', [0]); shimCLS(Promise.prototype, 'filter', [0]);
shimCLS(Promise.prototype, 'each', [0]); shimCLS(Promise.prototype, 'each', [0]);
// Promisification
shimCLS(Promise.prototype, 'nodeify', [0]);
// Utility // Utility
shimCLS(Promise.prototype, 'tap', [0]); shimCLS(Promise.prototype, 'tap', [0]);
......
...@@ -243,7 +243,7 @@ QueryInterface.prototype.dropAllTables = function(options) { ...@@ -243,7 +243,7 @@ QueryInterface.prototype.dropAllTables = function(options) {
}; };
var skip = options.skip || []; var skip = options.skip || [];
return self.showAllTables().then(function(tableNames) { return self.showAllTables({logging: options.logging}).then(function(tableNames) {
if (self.sequelize.options.dialect === 'sqlite') { if (self.sequelize.options.dialect === 'sqlite') {
return self.sequelize.query('PRAGMA foreign_keys;', options).then(function(result) { return self.sequelize.query('PRAGMA foreign_keys;', options).then(function(result) {
var foreignKeysAreEnabled = result.foreign_keys === 1; var foreignKeysAreEnabled = result.foreign_keys === 1;
...@@ -259,7 +259,7 @@ QueryInterface.prototype.dropAllTables = function(options) { ...@@ -259,7 +259,7 @@ QueryInterface.prototype.dropAllTables = function(options) {
} }
}); });
} else { } else {
return self.getForeignKeysForTables(tableNames).then(function(foreignKeys) { return self.getForeignKeysForTables(tableNames, {logging: options.logging}).then(function(foreignKeys) {
var promises = []; var promises = [];
tableNames.forEach(function(tableName) { tableNames.forEach(function(tableName) {
...@@ -422,20 +422,12 @@ QueryInterface.prototype.renameColumn = function(tableName, attrNameBefore, attr ...@@ -422,20 +422,12 @@ QueryInterface.prototype.renameColumn = function(tableName, attrNameBefore, attr
}.bind(this)); }.bind(this));
}; };
QueryInterface.prototype.addIndex = function(tableName, _attributes, options, _rawTablename) { QueryInterface.prototype.addIndex = function(tableName, attributes, options, rawTablename) {
var attributes, rawTablename;
// Support for passing tableName, attributes, options or tableName, options (with a fields param which is the attributes) // Support for passing tableName, attributes, options or tableName, options (with a fields param which is the attributes)
if (Array.isArray(_attributes)) { if (!Array.isArray(attributes)) {
attributes = _attributes;
rawTablename = _rawTablename;
} else {
// Support for passing an options object with a fields attribute instead of attributes, options
options = _attributes;
attributes = options.fields;
rawTablename = options; rawTablename = options;
options = attributes;
attributes = options.fields;
} }
if (!rawTablename) { if (!rawTablename) {
......
...@@ -67,7 +67,7 @@ var url = require('url') ...@@ -67,7 +67,7 @@ var url = require('url')
* @param {Object} [options.query={}] Default options for sequelize.query * @param {Object} [options.query={}] Default options for sequelize.query
* @param {Object} [options.set={}] Default options for sequelize.set * @param {Object} [options.set={}] Default options for sequelize.set
* @param {Object} [options.sync={}] Default options for sequelize.sync * @param {Object} [options.sync={}] Default options for sequelize.sync
* @param {String} [options.timezone='+00:00'] The timezone used when converting a date from the database into a javascript date. The timezone is also used to SET TIMEZONE when connecting to the server, to ensure that the result of NOW, CURRENT_TIMESTAMP and other time related functions have in the right timezone. For best cross platform performance use the format +/-HH:MM. * @param {String} [options.timezone='+00:00'] The timezone used when converting a date from the database into a JavaScript date. The timezone is also used to SET TIMEZONE when connecting to the server, to ensure that the result of NOW, CURRENT_TIMESTAMP and other time related functions have in the right timezone. For best cross platform performance use the format +/-HH:MM.
* @param {Function} [options.logging=console.log] A function that gets executed everytime Sequelize would log something. * @param {Function} [options.logging=console.log] A function that gets executed everytime Sequelize would log something.
* @param {Boolean} [options.omitNull=false] A flag that defines if null values should be passed to SQL queries or not. * @param {Boolean} [options.omitNull=false] A flag that defines if null values should be passed to SQL queries or not.
* @param {Boolean} [options.native=false] A flag that defines if native library shall be used or not. Currently only has an effect for postgres * @param {Boolean} [options.native=false] A flag that defines if native library shall be used or not. Currently only has an effect for postgres
...@@ -458,7 +458,7 @@ Sequelize.prototype.getQueryInterface = function() { ...@@ -458,7 +458,7 @@ Sequelize.prototype.getQueryInterface = function() {
* @param {String|DataType|Object} attributes.column The description of a database column * @param {String|DataType|Object} attributes.column The description of a database column
* @param {String|DataType} attributes.column.type A string or a data type * @param {String|DataType} attributes.column.type A string or a data type
* @param {Boolean} [attributes.column.allowNull=true] If false, the column will have a NOT NULL constraint, and a not null validation will be run before an instance is saved. * @param {Boolean} [attributes.column.allowNull=true] If false, the column will have a NOT NULL constraint, and a not null validation will be run before an instance is saved.
* @param {Any} [attributes.column.defaultValue=null] A literal default value, a javascript function, or an SQL function (see `sequelize.fn`) * @param {Any} [attributes.column.defaultValue=null] A literal default value, a JavaScript function, or an SQL function (see `sequelize.fn`)
* @param {String|Boolean} [attributes.column.unique=false] If true, the column will get a unique constraint. If a string is provided, the column will be part of a composite unique index. If multiple columns have the same string, they will be part of the same unique index * @param {String|Boolean} [attributes.column.unique=false] If true, the column will get a unique constraint. If a string is provided, the column will be part of a composite unique index. If multiple columns have the same string, they will be part of the same unique index
* @param {Boolean} [attributes.column.primaryKey=false] * @param {Boolean} [attributes.column.primaryKey=false]
* @param {String} [attributes.column.field=null] If set, sequelize will map the attribute name to a different name in the database * @param {String} [attributes.column.field=null] If set, sequelize will map the attribute name to a different name in the database
...@@ -603,10 +603,6 @@ Sequelize.prototype.import = function(path) { ...@@ -603,10 +603,6 @@ Sequelize.prototype.import = function(path) {
return this.importCache[path]; return this.importCache[path];
}; };
Sequelize.prototype.migrate = function(options) {
return this.getMigrator().migrate(options);
};
/** /**
* Execute a query on the DB, with the posibility to bypass all the sequelize goodness. * Execute a query on the DB, with the posibility to bypass all the sequelize goodness.
* *
...@@ -933,8 +929,8 @@ Sequelize.prototype.drop = function(options) { ...@@ -933,8 +929,8 @@ Sequelize.prototype.drop = function(options) {
* @alias validate * @alias validate
* @return {Promise} * @return {Promise}
*/ */
Sequelize.prototype.authenticate = function() { Sequelize.prototype.authenticate = function(options) {
return this.query('SELECT 1+1 AS result', { raw: true, plain: true }).return().catch(function(err) { return this.query('SELECT 1+1 AS result', Utils._.assign({ raw: true, plain: true }, options)).return().catch(function(err) {
throw new Error(err); throw new Error(err);
}); });
}; };
......
...@@ -95,6 +95,8 @@ var Utils = module.exports = { ...@@ -95,6 +95,8 @@ var Utils = module.exports = {
}, },
cloneDeep: function(obj, fn) { cloneDeep: function(obj, fn) {
return lodash.cloneDeep(obj, function (elem) { return lodash.cloneDeep(obj, function (elem) {
// Preserve special data-types like `fn` across clones. _.get() is used for checking up the prototype chain
if (!!Utils._.get(elem, 'clone') && typeof elem.clone === 'function') {return elem.clone(); }
// Unfortunately, lodash.cloneDeep doesn't preserve Buffer.isBuffer, which we have to rely on for binary data // Unfortunately, lodash.cloneDeep doesn't preserve Buffer.isBuffer, which we have to rely on for binary data
if (Buffer.isBuffer(elem)) { return elem; } if (Buffer.isBuffer(elem)) { return elem; }
...@@ -421,4 +423,8 @@ Utils.fn.prototype._isSequelizeMethod = ...@@ -421,4 +423,8 @@ Utils.fn.prototype._isSequelizeMethod =
Utils.col.prototype._isSequelizeMethod = Utils.col.prototype._isSequelizeMethod =
Utils.json.prototype._isSequelizeMethod = true; Utils.json.prototype._isSequelizeMethod = true;
Utils.fn.prototype.clone = function() {
return new Utils.fn(this.fn, this.args);
};
Utils.Promise = require('./promise'); Utils.Promise = require('./promise');
{ {
"name": "sequelize", "name": "sequelize",
"description": "Multi dialect ORM for Node.JS/io.js", "description": "Multi dialect ORM for Node.JS/io.js",
"version": "3.1.1", "version": "3.2.0",
"author": "Sascha Depold <sascha@depold.com>", "author": "Sascha Depold <sascha@depold.com>",
"contributors": [ "contributors": [
{ {
......
...@@ -600,6 +600,54 @@ describe(Support.getTestDialectTeaser('BelongsTo'), function() { ...@@ -600,6 +600,54 @@ describe(Support.getTestDialectTeaser('BelongsTo'), function() {
expect(User.rawAttributes.GroupPKBTName.type).to.an.instanceof(DataTypes.STRING); expect(User.rawAttributes.GroupPKBTName.type).to.an.instanceof(DataTypes.STRING);
}); });
}); });
it('should support a non-primary key as the association column', function() {
var User = this.sequelize.define('User', { username: DataTypes.STRING })
, Task = this.sequelize.define('Task', { title: DataTypes.STRING });
User.removeAttribute('id');
Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username'});
return this.sequelize.sync({ force: true }).then(function() {
return User.create({ username: 'bob' }).then(function(newUser) {
return Task.create({ title: 'some task' }).then(function(newTask) {
return newTask.setUser(newUser).then(function() {
return Task.findOne({title: 'some task'}).then(function (foundTask) {
return foundTask.getUser().then(function (foundUser) {
expect(foundUser.username).to.equal('bob');
});
});
});
});
});
});
});
it('should support a non-primary key as the association column with a field option', function() {
var User = this.sequelize.define('User', {
username: {
type: DataTypes.STRING,
field: 'the_user_name_field'
}
})
, Task = this.sequelize.define('Task', { title: DataTypes.STRING });
User.removeAttribute('id');
Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username'});
return this.sequelize.sync({ force: true }).then(function() {
return User.create({ username: 'bob' }).then(function(newUser) {
return Task.create({ title: 'some task' }).then(function(newTask) {
return newTask.setUser(newUser).then(function() {
return Task.findOne({title: 'some task'}).then(function (foundTask) {
return foundTask.getUser().then(function (foundUser) {
expect(foundUser.username).to.equal('bob');
});
});
});
});
});
});
});
}); });
describe('Association options', function() { describe('Association options', function() {
......
...@@ -240,6 +240,17 @@ if (current.dialect.supports.transactions) { ...@@ -240,6 +240,17 @@ if (current.dialect.supports.transactions) {
}); });
}); });
it('static map', function () {
var self = this;
return this.sequelize.transaction(function () {
var tid = self.ns.get('transaction').id;
return self.sequelize.Promise.map(self.User.findAll(), function () {
expect(self.ns.get('transaction').id).to.be.ok;
expect(self.ns.get('transaction').id).to.equal(tid);
});
});
});
it('reduce', function () { it('reduce', function () {
var self = this; var self = this;
return this.sequelize.transaction(function () { return this.sequelize.transaction(function () {
...@@ -251,6 +262,17 @@ if (current.dialect.supports.transactions) { ...@@ -251,6 +262,17 @@ if (current.dialect.supports.transactions) {
}); });
}); });
it('static reduce', function () {
var self = this;
return this.sequelize.transaction(function () {
var tid = self.ns.get('transaction').id;
return self.sequelize.Promise.reduce(self.User.findAll(), function () {
expect(self.ns.get('transaction').id).to.be.ok;
expect(self.ns.get('transaction').id).to.equal(tid);
});
});
});
it('filter', function () { it('filter', function () {
var self = this; var self = this;
return this.sequelize.transaction(function () { return this.sequelize.transaction(function () {
...@@ -262,6 +284,17 @@ if (current.dialect.supports.transactions) { ...@@ -262,6 +284,17 @@ if (current.dialect.supports.transactions) {
}); });
}); });
it('static filter', function () {
var self = this;
return this.sequelize.transaction(function () {
var tid = self.ns.get('transaction').id;
return self.sequelize.Promise.filter(self.User.findAll(), function () {
expect(self.ns.get('transaction').id).to.be.ok;
expect(self.ns.get('transaction').id).to.equal(tid);
});
});
});
it('each', function () { it('each', function () {
var self = this; var self = this;
return this.sequelize.transaction(function () { return this.sequelize.transaction(function () {
...@@ -273,6 +306,28 @@ if (current.dialect.supports.transactions) { ...@@ -273,6 +306,28 @@ if (current.dialect.supports.transactions) {
}); });
}); });
it('static each', function () {
var self = this;
return this.sequelize.transaction(function () {
var tid = self.ns.get('transaction').id;
return self.sequelize.Promise.each(self.User.findAll(), function () {
expect(self.ns.get('transaction').id).to.be.ok;
expect(self.ns.get('transaction').id).to.equal(tid);
});
});
});
it('nodeify', function () {
var self = this;
return this.sequelize.transaction(function () {
var tid = self.ns.get('transaction').id;
return self.User.findAll().nodeify(function () {
expect(self.ns.get('transaction').id).to.be.ok;
expect(self.ns.get('transaction').id).to.equal(tid);
});
});
});
it('tap', function () { it('tap', function () {
var self = this; var self = this;
return this.sequelize.transaction(function () { return this.sequelize.transaction(function () {
......
...@@ -21,10 +21,8 @@ describe(Support.getTestDialectTeaser('Configuration'), function() { ...@@ -21,10 +21,8 @@ describe(Support.getTestDialectTeaser('Configuration'), function() {
if (dialect === 'sqlite') { if (dialect === 'sqlite') {
// SQLite doesn't have a breakdown of error codes, so we are unable to discern between the different types of errors. // SQLite doesn't have a breakdown of error codes, so we are unable to discern between the different types of errors.
return expect(seq.query('select 1 as hello')).to.eventually.be.rejectedWith(seq.ConnectionError, 'SQLITE_CANTOPEN: unable to open database file'); return expect(seq.query('select 1 as hello')).to.eventually.be.rejectedWith(seq.ConnectionError, 'SQLITE_CANTOPEN: unable to open database file');
} else if (dialect === 'mssql' || dialect === 'postgres') {
return expect(seq.query('select 1 as hello')).to.eventually.be.rejectedWith([seq.HostNotReachableError, seq.InvalidConnectionError]);
} else { } else {
return expect(seq.query('select 1 as hello')).to.eventually.be.rejectedWith(seq.InvalidConnectionError, 'connect EINVAL'); return expect(seq.query('select 1 as hello')).to.eventually.be.rejectedWith([seq.HostNotReachableError, seq.InvalidConnectionError]);
} }
}); });
......
...@@ -170,6 +170,30 @@ describe(Support.getTestDialectTeaser('Include'), function() { ...@@ -170,6 +170,30 @@ describe(Support.getTestDialectTeaser('Include'), function() {
}); });
}); });
it('should support a belongsTo with the targetKey option', function() {
var User = this.sequelize.define('User', { username: DataTypes.STRING })
, Task = this.sequelize.define('Task', { title: DataTypes.STRING });
User.removeAttribute('id');
Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username'});
return this.sequelize.sync({ force: true }).then(function() {
return User.create({ username: 'bob' }).then(function(newUser) {
return Task.create({ title: 'some task' }).then(function(newTask) {
return newTask.setUser(newUser).then(function() {
return Task.find({
title: 'some task',
include: [ { model: User } ]
})
.then(function (foundTask) {
expect(foundTask).to.be.ok;
expect(foundTask.User.username).to.equal('bob');
});
});
});
});
});
});
it('should support many levels of belongsTo (with a lower level having a where)', function() { it('should support many levels of belongsTo (with a lower level having a where)', function() {
var A = this.sequelize.define('a', {}) var A = this.sequelize.define('a', {})
, B = this.sequelize.define('b', {}) , B = this.sequelize.define('b', {})
......
...@@ -898,6 +898,38 @@ describe(Support.getTestDialectTeaser('Instance'), function() { ...@@ -898,6 +898,38 @@ describe(Support.getTestDialectTeaser('Instance'), function() {
}); });
}); });
it('handles an entry with primaryKey of zero', function() {
var username = 'user'
, newUsername = 'newUser'
, User2 = this.sequelize.define('User2',
{
id: {
type: DataTypes.INTEGER.UNSIGNED,
autoIncrement: false,
primaryKey: true
},
username: { type: DataTypes.STRING }
});
return User2.sync().then(function () {
return User2.create({id: 0, username: username}).then(function (user){
expect(user).to.be.ok;
expect(user.id).to.equal(0);
expect(user.username).to.equal(username);
return User2.findById(0).then(function (user) {
expect(user).to.be.ok;
expect(user.id).to.equal(0);
expect(user.username).to.equal(username);
return user.updateAttributes({username: newUsername}).then(function (user) {
expect(user).to.be.ok;
expect(user.id).to.equal(0);
expect(user.username).to.equal(newUsername);
});
});
});
});
});
it('updates the timestamps', function() { it('updates the timestamps', function() {
var now = Date.now() var now = Date.now()
, user = null , user = null
......
'use strict'; 'use strict';
/* jshint -W030 */ /* jshint -W030 */
/* jshint -W110 */ /* jshint -W110 */
......
...@@ -223,6 +223,31 @@ describe(Support.getTestDialectTeaser('Model'), function() { ...@@ -223,6 +223,31 @@ describe(Support.getTestDialectTeaser('Model'), function() {
expect(user.baz).to.equal('oof'); expect(user.baz).to.equal('oof');
}); });
}); });
it('works with database functions', function() {
return this.User.upsert({ id: 42, username: 'john', foo: this.sequelize.fn('upper', 'mixedCase1')}).bind(this).then(function(created) {
if (dialect === 'sqlite') {
expect(created).to.be.undefined;
} else {
expect(created).to.be.ok;
}
return this.sequelize.Promise.delay(1000).bind(this).then(function() {
return this.User.upsert({ id: 42, username: 'doe', foo: this.sequelize.fn('upper', 'mixedCase2') });
});
}).then(function(created) {
if (dialect === 'sqlite') {
expect(created).to.be.undefined;
} else {
expect(created).not.to.be.ok;
}
return this.User.findById(42);
}).then(function(user) {
expect(user.createdAt).to.be.ok;
expect(user.username).to.equal('doe');
expect(user.foo).to.equal('MIXEDCASE2');
});
});
}); });
} }
}); });
...@@ -39,7 +39,7 @@ describe(Support.getTestDialectTeaser('QueryInterface'), function() { ...@@ -39,7 +39,7 @@ describe(Support.getTestDialectTeaser('QueryInterface'), function() {
count = 0; count = 0;
expect(tableNames).to.have.length(1); expect(tableNames).to.have.length(1);
return self.queryInterface.dropAllTables({logging: log}).then(function() { return self.queryInterface.dropAllTables({logging: log}).then(function() {
expect(count).to.be.equal(1); expect(count).to.be.at.least(1);
count = 0; count = 0;
return self.queryInterface.showAllTables().then(function(tableNames) { return self.queryInterface.showAllTables().then(function(tableNames) {
expect(tableNames).to.be.empty; expect(tableNames).to.be.empty;
...@@ -72,7 +72,7 @@ describe(Support.getTestDialectTeaser('QueryInterface'), function() { ...@@ -72,7 +72,7 @@ describe(Support.getTestDialectTeaser('QueryInterface'), function() {
beforeEach(function() { beforeEach(function() {
var self = this; var self = this;
return this.queryInterface.dropTable('Group', {logging: log}).then(function() { return this.queryInterface.dropTable('Group', {logging: log}).then(function() {
expect(count).to.be.equal(1); expect(count).to.be.at.least(1);
count = 0; count = 0;
return self.queryInterface.createTable('Group', { return self.queryInterface.createTable('Group', {
username: DataTypes.STRING, username: DataTypes.STRING,
...@@ -156,7 +156,7 @@ describe(Support.getTestDialectTeaser('QueryInterface'), function() { ...@@ -156,7 +156,7 @@ describe(Support.getTestDialectTeaser('QueryInterface'), function() {
return Users.sync({ force: true }).then(function() { return Users.sync({ force: true }).then(function() {
return self.queryInterface.describeTable('_Users', {logging: log}).then(function(metadata) { return self.queryInterface.describeTable('_Users', {logging: log}).then(function(metadata) {
expect(count).to.be.equal(1); expect(count).to.be.at.least(1);
count = 0; count = 0;
var username = metadata.username; var username = metadata.username;
......
...@@ -63,5 +63,27 @@ describe(Support.getTestDialectTeaser('belongsToMany'), function() { ...@@ -63,5 +63,27 @@ describe(Support.getTestDialectTeaser('belongsToMany'), function() {
expect(this.destroy).to.have.been.calledOnce; expect(this.destroy).to.have.been.calledOnce;
}); });
}); });
describe('belongsToMany', function () {
it('works with singular and plural name for self-associations', function () {
// Models taken from https://github.com/sequelize/sequelize/issues/3796
var Service = current.define('service', {})
, Instance = Service.Instance;
Service.belongsToMany(Service, {through: 'Supplements', as: 'supplements'});
Service.belongsToMany(Service, {through: 'Supplements', as: {singular: 'supplemented', plural: 'supplemented'}});
expect(Instance.prototype).to.have.property('getSupplements').which.is.a.function;
expect(Instance.prototype).to.have.property('addSupplement').which.is.a.function;
expect(Instance.prototype).to.have.property('addSupplements').which.is.a.function;
expect(Instance.prototype).to.have.property('getSupplemented').which.is.a.function;
expect(Instance.prototype).not.to.have.property('getSupplementeds').which.is.a.function;
expect(Instance.prototype).to.have.property('addSupplemented').which.is.a.function;
expect(Instance.prototype).not.to.have.property('addSupplementeds').which.is.a.function;
});
});
}); });
}); });
...@@ -560,6 +560,13 @@ suite(Support.getTestDialectTeaser('SQL'), function() { ...@@ -560,6 +560,13 @@ suite(Support.getTestDialectTeaser('SQL'), function() {
}); });
}); });
// TODO: Fix Enums and add more tests
// suite('ENUM', function () {
// testsql('ENUM("value 1", "value 2")', DataTypes.ENUM('value 1', 'value 2'), {
// default: 'ENUM'
// });
// });
suite('BLOB', function () { suite('BLOB', function () {
testsql('BLOB', DataTypes.BLOB, { testsql('BLOB', DataTypes.BLOB, {
default: 'BLOB', default: 'BLOB',
......
...@@ -40,6 +40,15 @@ if (current.dialect.supports.JSON) { ...@@ -40,6 +40,15 @@ if (current.dialect.supports.JSON) {
postgres: 'ARRAY[\'{"some":"nested","more":{"nested":true},"answer":42}\',\'43\',\'"joe"\']::JSON[]' postgres: 'ARRAY[\'{"some":"nested","more":{"nested":true},"answer":42}\',\'43\',\'"joe"\']::JSON[]'
}); });
}); });
test('array of JSONB', function () {
expectsql(sql.escape([
{ some: 'nested', more: { nested: true }, answer: 42 },
43,
'joe'
], { type: DataTypes.ARRAY(DataTypes.JSONB)}), {
postgres: 'ARRAY[\'{"some":"nested","more":{"nested":true},"answer":42}\',\'43\',\'"joe"\']::JSONB[]'
});
});
}); });
}); });
}); });
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!