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

Commit 33ff3ad5 by Ruben Bridgewater

Remove unnecessary indentation

1 parent 073f1777
var Association = function() {
var Association = function () {};
};
module.exports = Association;
\ No newline at end of file
......@@ -7,701 +7,699 @@ var Utils = require('./../utils')
, CounterCache = require('../plugins/counter-cache')
, util = require('util');
module.exports = (function() {
var BelongsToMany = function(source, target, options) {
Association.call(this);
this.associationType = 'BelongsToMany';
this.source = source;
this.target = target;
this.targetAssociation = null;
this.options = options || {};
this.sequelize = source.modelManager.sequelize;
this.through = options.through;
this.scope = options.scope;
this.isMultiAssociation = true;
this.isSelfAssociation = this.source === this.target;
this.doubleLinked = false;
this.as = this.options.as;
this.combinedTableName = Utils.combineTableNames(
this.source.tableName,
this.isSelfAssociation ? (this.as || this.target.tableName) : this.target.tableName
);
if (this.through === undefined || this.through === true || this.through === null) {
throw new Error('belongsToMany must be given a through option, either a string or a model');
var BelongsToMany = function(source, target, options) {
Association.call(this);
this.associationType = 'BelongsToMany';
this.source = source;
this.target = target;
this.targetAssociation = null;
this.options = options || {};
this.sequelize = source.modelManager.sequelize;
this.through = options.through;
this.scope = options.scope;
this.isMultiAssociation = true;
this.isSelfAssociation = this.source === this.target;
this.doubleLinked = false;
this.as = this.options.as;
this.combinedTableName = Utils.combineTableNames(
this.source.tableName,
this.isSelfAssociation ? (this.as || this.target.tableName) : this.target.tableName
);
if (this.through === undefined || this.through === true || this.through === null) {
throw new Error('belongsToMany must be given a through option, either a string or a model');
}
if (!this.through.model) {
this.through = {
model: this.through
};
}
/*
* If self association, this is the target association - Unless we find a pairing association
*/
if (this.isSelfAssociation) {
if (!this.as) {
throw new Error('\'as\' must be defined for many-to-many self-associations');
}
if (!this.through.model) {
this.through = {
model: this.through
};
this.targetAssociation = this;
}
/*
* Default/generated foreign/other keys
*/
if (Utils._.isObject(this.options.foreignKey)) {
this.foreignKeyAttribute = this.options.foreignKey;
this.foreignKey = this.foreignKeyAttribute.name || this.foreignKeyAttribute.fieldName;
} else {
if (!this.options.foreignKey) {
this.foreignKeyDefault = true;
}
/*
* If self association, this is the target association - Unless we find a pairing association
*/
if (this.isSelfAssociation) {
if (!this.as) {
throw new Error('\'as\' must be defined for many-to-many self-associations');
}
this.targetAssociation = this;
this.foreignKeyAttribute = {};
this.foreignKey = this.options.foreignKey || Utils._.camelizeIf(
[
Utils._.underscoredIf(this.source.options.name.singular, this.source.options.underscored),
this.source.primaryKeyAttribute
].join('_'),
!this.source.options.underscored
);
}
if (Utils._.isObject(this.options.otherKey)) {
this.otherKeyAttribute = this.options.otherKey;
this.otherKey = this.otherKeyAttribute.name || this.otherKeyAttribute.fieldName;
} else {
if (!this.options.otherKey) {
this.otherKeyDefault = true;
}
/*
* Default/generated foreign/other keys
*/
if (Utils._.isObject(this.options.foreignKey)) {
this.foreignKeyAttribute = this.options.foreignKey;
this.foreignKey = this.foreignKeyAttribute.name || this.foreignKeyAttribute.fieldName;
} else {
if (!this.options.foreignKey) {
this.foreignKeyDefault = true;
}
this.otherKeyAttribute = {};
this.otherKey = this.options.otherKey || Utils._.camelizeIf(
[
Utils._.underscoredIf(
this.isSelfAssociation ?
Utils.singularize(this.as) :
this.target.options.name.singular,
this.target.options.underscored
),
this.target.primaryKeyAttribute
].join('_'),
!this.target.options.underscored
);
}
this.foreignKeyAttribute = {};
this.foreignKey = this.options.foreignKey || Utils._.camelizeIf(
[
Utils._.underscoredIf(this.source.options.name.singular, this.source.options.underscored),
this.source.primaryKeyAttribute
].join('_'),
!this.source.options.underscored
);
}
/*
* Find paired association (if exists)
*/
_.each(this.target.associations, function(association) {
if (association.associationType !== 'BelongsToMany') return;
if (association.target !== this.source) return;
if (Utils._.isObject(this.options.otherKey)) {
this.otherKeyAttribute = this.options.otherKey;
this.otherKey = this.otherKeyAttribute.name || this.otherKeyAttribute.fieldName;
if (this.options.through.model === association.options.through.model) {
this.paired = association;
}
}, this);
if (typeof this.through.model === 'string') {
if (!this.sequelize.isDefined(this.through.model)) {
this.through.model = this.sequelize.define(this.through.model, {}, _.extend(this.options, {
tableName: this.through.model,
indexes: {}, //we dont want indexes here (as referenced in #2416)
paranoid: false // A paranoid join table does not make sense
}));
} else {
if (!this.options.otherKey) {
this.otherKeyDefault = true;
}
this.otherKeyAttribute = {};
this.otherKey = this.options.otherKey || Utils._.camelizeIf(
[
Utils._.underscoredIf(
this.isSelfAssociation ?
Utils.singularize(this.as) :
this.target.options.name.singular,
this.target.options.underscored
),
this.target.primaryKeyAttribute
].join('_'),
!this.target.options.underscored
);
this.through.model = this.sequelize.model(this.through.model);
}
}
/*
* Find paired association (if exists)
*/
_.each(this.target.associations, function(association) {
if (association.associationType !== 'BelongsToMany') return;
if (association.target !== this.source) return;
if (this.options.through.model === association.options.through.model) {
this.paired = association;
}
}, this);
if (typeof this.through.model === 'string') {
if (!this.sequelize.isDefined(this.through.model)) {
this.through.model = this.sequelize.define(this.through.model, {}, _.extend(this.options, {
tableName: this.through.model,
indexes: {}, //we dont want indexes here (as referenced in #2416)
paranoid: false // A paranoid join table does not make sense
}));
} else {
this.through.model = this.sequelize.model(this.through.model);
}
if (this.paired) {
if (this.otherKeyDefault) {
this.otherKey = this.paired.foreignKey;
}
if (this.paired) {
if (this.otherKeyDefault) {
this.otherKey = this.paired.foreignKey;
}
if (this.paired.otherKeyDefault) {
// If paired otherKey was inferred we should make sure to clean it up before adding a new one that matches the foreignKey
if (this.paired.otherKey !== this.foreignKey) {
delete this.through.model.rawAttributes[this.paired.otherKey];
}
this.paired.otherKey = this.foreignKey;
this.paired.foreignIdentifier = this.foreignKey;
delete this.paired.foreignIdentifierField;
if (this.paired.otherKeyDefault) {
// If paired otherKey was inferred we should make sure to clean it up before adding a new one that matches the foreignKey
if (this.paired.otherKey !== this.foreignKey) {
delete this.through.model.rawAttributes[this.paired.otherKey];
}
this.paired.otherKey = this.foreignKey;
this.paired.foreignIdentifier = this.foreignKey;
delete this.paired.foreignIdentifierField;
}
}
if (this.through) {
this.throughModel = this.through.model;
}
if (this.through) {
this.throughModel = this.through.model;
}
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 (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)
};
}
if (Utils._.isPlainObject(this.as)) {
this.options.name = this.as;
this.as = this.as.plural;
} else {
this.as = this.target.options.name.plural;
this.options.name = this.target.options.name;
}
this.associationAccessor = this.as;
// Get singular and plural names, trying to uppercase the first letter, unless the model forbids it
var plural = Utils.uppercaseFirst(this.options.name.plural)
, singular = Utils.uppercaseFirst(this.options.name.singular);
this.accessors = {
get: 'get' + plural,
set: 'set' + plural,
addMultiple: 'add' + plural,
add: 'add' + singular,
create: 'create' + singular,
remove: 'remove' + singular,
removeMultiple: 'remove' + plural,
hasSingle: 'has' + singular,
hasAll: 'has' + plural
};
if (this.options.counterCache) {
new CounterCache(this, this.options.counterCache !== true ? this.options.counterCache : {});
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;
// Get singular and plural names, trying to uppercase the first letter, unless the model forbids it
var plural = Utils.uppercaseFirst(this.options.name.plural)
, singular = Utils.uppercaseFirst(this.options.name.singular);
this.accessors = {
get: 'get' + plural,
set: 'set' + plural,
addMultiple: 'add' + plural,
add: 'add' + singular,
create: 'create' + singular,
remove: 'remove' + singular,
removeMultiple: 'remove' + plural,
hasSingle: 'has' + singular,
hasAll: 'has' + plural
};
util.inherits(BelongsToMany, Association);
if (this.options.counterCache) {
new CounterCache(this, this.options.counterCache !== true ? this.options.counterCache : {});
}
};
// the id is in the target table
// or in an extra table which connects two tables
BelongsToMany.prototype.injectAttributes = function() {
var self = this;
util.inherits(BelongsToMany, Association);
this.identifier = this.foreignKey;
this.foreignIdentifier = this.otherKey;
// the id is in the target table
// or in an extra table which connects two tables
BelongsToMany.prototype.injectAttributes = function() {
var self = this;
// remove any PKs previously defined by sequelize
Utils._.each(this.through.model.rawAttributes, function(attribute, attributeName) {
if (attribute.primaryKey === true && attribute._autoGenerated === true) {
delete self.through.model.rawAttributes[attributeName];
self.primaryKeyDeleted = true;
}
});
this.identifier = this.foreignKey;
this.foreignIdentifier = this.otherKey;
var sourceKey = this.source.rawAttributes[this.source.primaryKeyAttribute]
, sourceKeyType = sourceKey.type
, sourceKeyField = sourceKey.field || this.source.primaryKeyAttribute
, targetKey = this.target.rawAttributes[this.target.primaryKeyAttribute]
, targetKeyType = targetKey.type
, targetKeyField = targetKey.field || this.target.primaryKeyAttribute
, sourceAttribute = Utils._.defaults(this.foreignKeyAttribute, { type: sourceKeyType })
, targetAttribute = Utils._.defaults(this.otherKeyAttribute, { type: targetKeyType });
if (this.primaryKeyDeleted === true) {
targetAttribute.primaryKey = sourceAttribute.primaryKey = true;
} else if (this.through.unique !== false) {
var uniqueKey = [this.through.model.tableName, this.identifier, this.foreignIdentifier, 'unique'].join('_');
targetAttribute.unique = sourceAttribute.unique = uniqueKey;
}
if (!this.through.model.rawAttributes[this.identifier]) {
this.through.model.rawAttributes[this.identifier] = {
_autoGenerated: true
};
}
if (!this.through.model.rawAttributes[this.foreignIdentifier]) {
this.through.model.rawAttributes[this.foreignIdentifier] = {
_autoGenerated: true
};
// remove any PKs previously defined by sequelize
Utils._.each(this.through.model.rawAttributes, function(attribute, attributeName) {
if (attribute.primaryKey === true && attribute._autoGenerated === true) {
delete self.through.model.rawAttributes[attributeName];
self.primaryKeyDeleted = true;
}
});
var sourceKey = this.source.rawAttributes[this.source.primaryKeyAttribute]
, sourceKeyType = sourceKey.type
, sourceKeyField = sourceKey.field || this.source.primaryKeyAttribute
, targetKey = this.target.rawAttributes[this.target.primaryKeyAttribute]
, targetKeyType = targetKey.type
, targetKeyField = targetKey.field || this.target.primaryKeyAttribute
, sourceAttribute = Utils._.defaults(this.foreignKeyAttribute, { type: sourceKeyType })
, targetAttribute = Utils._.defaults(this.otherKeyAttribute, { type: targetKeyType });
if (this.primaryKeyDeleted === true) {
targetAttribute.primaryKey = sourceAttribute.primaryKey = true;
} else if (this.through.unique !== false) {
var uniqueKey = [this.through.model.tableName, this.identifier, this.foreignIdentifier, 'unique'].join('_');
targetAttribute.unique = sourceAttribute.unique = uniqueKey;
}
if (!this.through.model.rawAttributes[this.identifier]) {
this.through.model.rawAttributes[this.identifier] = {
_autoGenerated: true
};
}
if (this.options.constraints !== false) {
sourceAttribute.references = {
model: this.source.getTableName(),
key: sourceKeyField
};
// For the source attribute the passed option is the priority
sourceAttribute.onDelete = this.options.onDelete || this.through.model.rawAttributes[this.identifier].onDelete;
sourceAttribute.onUpdate = this.options.onUpdate || this.through.model.rawAttributes[this.identifier].onUpdate;
if (!this.through.model.rawAttributes[this.foreignIdentifier]) {
this.through.model.rawAttributes[this.foreignIdentifier] = {
_autoGenerated: true
};
}
if (!sourceAttribute.onDelete) sourceAttribute.onDelete = 'CASCADE';
if (!sourceAttribute.onUpdate) sourceAttribute.onUpdate = 'CASCADE';
if (this.options.constraints !== false) {
sourceAttribute.references = {
model: this.source.getTableName(),
key: sourceKeyField
};
// For the source attribute the passed option is the priority
sourceAttribute.onDelete = this.options.onDelete || this.through.model.rawAttributes[this.identifier].onDelete;
sourceAttribute.onUpdate = this.options.onUpdate || this.through.model.rawAttributes[this.identifier].onUpdate;
targetAttribute.references = {
model: this.target.getTableName(),
key: targetKeyField
};
// But the for target attribute the previously defined option is the priority (since it could've been set by another belongsToMany call)
targetAttribute.onDelete = this.through.model.rawAttributes[this.foreignIdentifier].onDelete || this.options.onDelete;
targetAttribute.onUpdate = this.through.model.rawAttributes[this.foreignIdentifier].onUpdate || this.options.onUpdate;
if (!sourceAttribute.onDelete) sourceAttribute.onDelete = 'CASCADE';
if (!sourceAttribute.onUpdate) sourceAttribute.onUpdate = 'CASCADE';
if (!targetAttribute.onDelete) targetAttribute.onDelete = 'CASCADE';
if (!targetAttribute.onUpdate) targetAttribute.onUpdate = 'CASCADE';
}
targetAttribute.references = {
model: this.target.getTableName(),
key: targetKeyField
};
// But the for target attribute the previously defined option is the priority (since it could've been set by another belongsToMany call)
targetAttribute.onDelete = this.through.model.rawAttributes[this.foreignIdentifier].onDelete || this.options.onDelete;
targetAttribute.onUpdate = this.through.model.rawAttributes[this.foreignIdentifier].onUpdate || this.options.onUpdate;
this.through.model.rawAttributes[this.identifier] = Utils._.extend(this.through.model.rawAttributes[this.identifier], sourceAttribute);
this.through.model.rawAttributes[this.foreignIdentifier] = Utils._.extend(this.through.model.rawAttributes[this.foreignIdentifier], targetAttribute);
if (!targetAttribute.onDelete) targetAttribute.onDelete = 'CASCADE';
if (!targetAttribute.onUpdate) targetAttribute.onUpdate = 'CASCADE';
}
this.identifierField = this.through.model.rawAttributes[this.identifier].field || this.identifier;
this.foreignIdentifierField = this.through.model.rawAttributes[this.foreignIdentifier].field || this.foreignIdentifier;
this.through.model.rawAttributes[this.identifier] = Utils._.extend(this.through.model.rawAttributes[this.identifier], sourceAttribute);
this.through.model.rawAttributes[this.foreignIdentifier] = Utils._.extend(this.through.model.rawAttributes[this.foreignIdentifier], targetAttribute);
if (this.paired && !this.paired.foreignIdentifierField) {
this.paired.foreignIdentifierField = this.through.model.rawAttributes[this.paired.foreignIdentifier].field || this.paired.foreignIdentifier;
}
this.identifierField = this.through.model.rawAttributes[this.identifier].field || this.identifier;
this.foreignIdentifierField = this.through.model.rawAttributes[this.foreignIdentifier].field || this.foreignIdentifier;
this.through.model.init(this.through.model.modelManager);
if (this.paired && !this.paired.foreignIdentifierField) {
this.paired.foreignIdentifierField = this.through.model.rawAttributes[this.paired.foreignIdentifier].field || this.paired.foreignIdentifier;
}
Helpers.checkNamingCollision(this);
this.through.model.init(this.through.model.modelManager);
return this;
};
Helpers.checkNamingCollision(this);
BelongsToMany.prototype.injectGetter = function(obj) {
var association = this;
return this;
};
obj[this.accessors.get] = function(options) {
options = association.target.__optClone(options) || {};
BelongsToMany.prototype.injectGetter = function(obj) {
var association = this;
var instance = this
, through = association.through
, scopeWhere
, throughWhere;
obj[this.accessors.get] = function(options) {
options = association.target.__optClone(options) || {};
if (association.scope) {
scopeWhere = _.clone(association.scope);
}
var instance = this
, through = association.through
, scopeWhere
, throughWhere;
options.where = {
$and: [
scopeWhere,
options.where
]
};
if (association.scope) {
scopeWhere = _.clone(association.scope);
}
if (Object(through.model) === through.model) {
throughWhere = {};
throughWhere[association.identifier] = instance.get(association.source.primaryKeyAttribute);
options.where = {
$and: [
scopeWhere,
options.where
]
};
if (through && through.scope) {
Object.keys(through.scope).forEach(function (attribute) {
throughWhere[attribute] = through.scope[attribute];
}.bind(this));
}
if (Object(through.model) === through.model) {
throughWhere = {};
throughWhere[association.identifier] = instance.get(association.source.primaryKeyAttribute);
options.include = options.include || [];
options.include.push({
model: through.model,
as: through.model.name,
attributes: options.joinTableAttributes,
association: {
isSingleAssociation: true,
source: association.target,
target: association.source,
identifier: association.foreignIdentifier,
identifierField: association.foreignIdentifierField
},
required: true,
where: throughWhere,
_pseudo: true
});
if (through && through.scope) {
Object.keys(through.scope).forEach(function (attribute) {
throughWhere[attribute] = through.scope[attribute];
}.bind(this));
}
var model = association.target;
if (options.hasOwnProperty('scope')) {
if (!options.scope) {
model = model.unscoped();
} else {
model = model.scope(options.scope);
}
}
options.include = options.include || [];
options.include.push({
model: through.model,
as: through.model.name,
attributes: options.joinTableAttributes,
association: {
isSingleAssociation: true,
source: association.target,
target: association.source,
identifier: association.foreignIdentifier,
identifierField: association.foreignIdentifierField
},
required: true,
where: throughWhere,
_pseudo: true
});
}
return model.findAll(options);
};
var model = association.target;
if (options.hasOwnProperty('scope')) {
if (!options.scope) {
model = model.unscoped();
} else {
model = model.scope(options.scope);
}
}
obj[this.accessors.hasSingle] = obj[this.accessors.hasAll] = function(instances, options) {
var where = {};
return model.findAll(options);
};
if (!Array.isArray(instances)) {
instances = [instances];
}
obj[this.accessors.hasSingle] = obj[this.accessors.hasAll] = function(instances, options) {
var where = {};
options = options || {};
options.scope = false;
if (!Array.isArray(instances)) {
instances = [instances];
}
_.defaults(options, {
raw: true
});
options = options || {};
options.scope = false;
where.$or = instances.map(function (instance) {
if (instance instanceof association.target.Instance) {
return instance.where();
} else {
var $where = {};
$where[association.target.primaryKeyAttribute] = instance;
return $where;
}
});
_.defaults(options, {
raw: true
});
options.where = {
$and: [
where,
options.where
]
};
where.$or = instances.map(function (instance) {
if (instance instanceof association.target.Instance) {
return instance.where();
} else {
var $where = {};
$where[association.target.primaryKeyAttribute] = instance;
return $where;
}
});
return this[association.accessors.get](options).then(function(associatedObjects) {
return associatedObjects.length === instances.length;
});
options.where = {
$and: [
where,
options.where
]
};
return this;
return this[association.accessors.get](options).then(function(associatedObjects) {
return associatedObjects.length === instances.length;
});
};
BelongsToMany.prototype.injectSetter = function(obj) {
var association = this
, primaryKeyAttribute = association.target.primaryKeyAttribute;
obj[this.accessors.set] = function(newAssociatedObjects, options) {
options = options || {};
var instance = this;
return instance[association.accessors.get]({
scope: false,
transaction: options.transaction,
logging: options.logging
}).then(function(oldAssociatedObjects) {
var foreignIdentifier = association.foreignIdentifier
, sourceKeys = Object.keys(association.source.primaryKeys)
, targetKeys = Object.keys(association.target.primaryKeys)
, obsoleteAssociations = []
, changedAssociations = []
, defaultAttributes = options
, promises = []
, unassociatedObjects;
if (newAssociatedObjects === null) {
newAssociatedObjects = [];
} else {
if (!Array.isArray(newAssociatedObjects)) {
newAssociatedObjects = [newAssociatedObjects];
}
newAssociatedObjects = newAssociatedObjects.map(function(newAssociatedObject) {
if (!(newAssociatedObject instanceof association.target.Instance)) {
var tmpInstance = {};
tmpInstance[primaryKeyAttribute] = newAssociatedObject;
return association.target.build(tmpInstance, {
isNewRecord: false
});
}
return newAssociatedObject;
});
return this;
};
BelongsToMany.prototype.injectSetter = function(obj) {
var association = this
, primaryKeyAttribute = association.target.primaryKeyAttribute;
obj[this.accessors.set] = function(newAssociatedObjects, options) {
options = options || {};
var instance = this;
return instance[association.accessors.get]({
scope: false,
transaction: options.transaction,
logging: options.logging
}).then(function(oldAssociatedObjects) {
var foreignIdentifier = association.foreignIdentifier
, sourceKeys = Object.keys(association.source.primaryKeys)
, targetKeys = Object.keys(association.target.primaryKeys)
, obsoleteAssociations = []
, changedAssociations = []
, defaultAttributes = options
, promises = []
, unassociatedObjects;
if (newAssociatedObjects === null) {
newAssociatedObjects = [];
} else {
if (!Array.isArray(newAssociatedObjects)) {
newAssociatedObjects = [newAssociatedObjects];
}
newAssociatedObjects = newAssociatedObjects.map(function(newAssociatedObject) {
if (!(newAssociatedObject instanceof association.target.Instance)) {
var tmpInstance = {};
tmpInstance[primaryKeyAttribute] = newAssociatedObject;
return association.target.build(tmpInstance, {
isNewRecord: false
});
}
return newAssociatedObject;
});
}
if (options.remove) {
oldAssociatedObjects = newAssociatedObjects;
newAssociatedObjects = [];
}
if (options.remove) {
oldAssociatedObjects = newAssociatedObjects;
newAssociatedObjects = [];
}
// Don't try to insert the transaction as an attribute in the through table
defaultAttributes = Utils._.omit(defaultAttributes, ['transaction', 'hooks', 'individualHooks', 'ignoreDuplicates', 'validate', 'fields', 'logging']);
// Don't try to insert the transaction as an attribute in the through table
defaultAttributes = Utils._.omit(defaultAttributes, ['transaction', 'hooks', 'individualHooks', 'ignoreDuplicates', 'validate', 'fields', 'logging']);
unassociatedObjects = newAssociatedObjects.filter(function(obj) {
return !Utils._.find(oldAssociatedObjects, function(old) {
return (!!obj[foreignIdentifier] ? obj[foreignIdentifier] === old[foreignIdentifier] : (!!obj[targetKeys[0]] ? obj[targetKeys[0]] === old[targetKeys[0]] : obj.id === old.id));
});
unassociatedObjects = newAssociatedObjects.filter(function(obj) {
return !Utils._.find(oldAssociatedObjects, function(old) {
return (!!obj[foreignIdentifier] ? obj[foreignIdentifier] === old[foreignIdentifier] : (!!obj[targetKeys[0]] ? obj[targetKeys[0]] === old[targetKeys[0]] : obj.id === old.id));
});
});
oldAssociatedObjects.forEach(function(old) {
var newObj = Utils._.find(newAssociatedObjects, function(obj) {
return (!!obj[foreignIdentifier] ? obj[foreignIdentifier] === old[foreignIdentifier] : (!!obj[targetKeys[0]] ? obj[targetKeys[0]] === old[targetKeys[0]] : obj.id === old.id));
});
oldAssociatedObjects.forEach(function(old) {
var newObj = Utils._.find(newAssociatedObjects, function(obj) {
return (!!obj[foreignIdentifier] ? obj[foreignIdentifier] === old[foreignIdentifier] : (!!obj[targetKeys[0]] ? obj[targetKeys[0]] === old[targetKeys[0]] : obj.id === old.id));
});
if (!newObj) {
obsoleteAssociations.push(old);
} else {
var throughAttributes = newObj[association.through.model.name];
// Quick-fix for subtle bug when using existing objects that might have the through model attached (not as an attribute object)
if (throughAttributes instanceof association.through.model.Instance) {
throughAttributes = {};
}
var changedAssociation = {
where: {},
attributes: Utils._.defaults({}, throughAttributes, defaultAttributes)
};
changedAssociation.where[association.identifier] = instance[sourceKeys[0]] || instance.id;
changedAssociation.where[foreignIdentifier] = newObj[targetKeys[0]] || newObj.id;
if (Object.keys(changedAssociation.attributes).length) {
changedAssociations.push(changedAssociation);
}
if (!newObj) {
obsoleteAssociations.push(old);
} else {
var throughAttributes = newObj[association.through.model.name];
// Quick-fix for subtle bug when using existing objects that might have the through model attached (not as an attribute object)
if (throughAttributes instanceof association.through.model.Instance) {
throughAttributes = {};
}
});
if (obsoleteAssociations.length > 0) {
var foreignIds = obsoleteAssociations.map(function(associatedObject) {
return ((targetKeys.length === 1) ? associatedObject[targetKeys[0]] : associatedObject.id);
});
var changedAssociation = {
where: {},
attributes: Utils._.defaults({}, throughAttributes, defaultAttributes)
};
var where = {};
where[association.identifier] = ((sourceKeys.length === 1) ? instance[sourceKeys[0]] : instance.id);
where[foreignIdentifier] = foreignIds;
changedAssociation.where[association.identifier] = instance[sourceKeys[0]] || instance.id;
changedAssociation.where[foreignIdentifier] = newObj[targetKeys[0]] || newObj.id;
promises.push(association.through.model.destroy(Utils._.extend(options, {
where: where
})));
if (Object.keys(changedAssociation.attributes).length) {
changedAssociations.push(changedAssociation);
}
}
});
if (unassociatedObjects.length > 0) {
var bulk = unassociatedObjects.map(function(unassociatedObject) {
var attributes = {};
if (obsoleteAssociations.length > 0) {
var foreignIds = obsoleteAssociations.map(function(associatedObject) {
return ((targetKeys.length === 1) ? associatedObject[targetKeys[0]] : associatedObject.id);
});
attributes[association.identifier] = ((sourceKeys.length === 1) ? instance[sourceKeys[0]] : instance.id);
attributes[foreignIdentifier] = ((targetKeys.length === 1) ? unassociatedObject[targetKeys[0]] : unassociatedObject.id);
var where = {};
where[association.identifier] = ((sourceKeys.length === 1) ? instance[sourceKeys[0]] : instance.id);
where[foreignIdentifier] = foreignIds;
attributes = Utils._.defaults(attributes, unassociatedObject[association.through.model.name], defaultAttributes);
promises.push(association.through.model.destroy(Utils._.extend(options, {
where: where
})));
}
if (association.through.scope) {
Object.keys(association.through.scope).forEach(function (attribute) {
attributes[attribute] = association.through.scope[attribute];
});
}
if (unassociatedObjects.length > 0) {
var bulk = unassociatedObjects.map(function(unassociatedObject) {
var attributes = {};
return attributes;
}.bind(this));
attributes[association.identifier] = ((sourceKeys.length === 1) ? instance[sourceKeys[0]] : instance.id);
attributes[foreignIdentifier] = ((targetKeys.length === 1) ? unassociatedObject[targetKeys[0]] : unassociatedObject.id);
promises.push(association.through.model.bulkCreate(bulk, options));
}
attributes = Utils._.defaults(attributes, unassociatedObject[association.through.model.name], defaultAttributes);
if (changedAssociations.length > 0) {
changedAssociations.forEach(function(assoc) {
promises.push(association.through.model.update(assoc.attributes, Utils._.extend(options, {
where: assoc.where
})));
});
}
if (association.through.scope) {
Object.keys(association.through.scope).forEach(function (attribute) {
attributes[attribute] = association.through.scope[attribute];
});
}
return Utils.Promise.all(promises);
});
};
return attributes;
}.bind(this));
obj[this.accessors.addMultiple] = obj[this.accessors.add] = function(newInstance, additionalAttributes) {
// If newInstance is null or undefined, no-op
if (!newInstance) return Utils.Promise.resolve();
promises.push(association.through.model.bulkCreate(bulk, options));
}
if (association.through && association.through.scope) {
_.assign(additionalAttributes, association.through.scope);
if (changedAssociations.length > 0) {
changedAssociations.forEach(function(assoc) {
promises.push(association.through.model.update(assoc.attributes, Utils._.extend(options, {
where: assoc.where
})));
});
}
var instance = this
, primaryKeyAttribute = association.target.primaryKeyAttribute
, options = additionalAttributes = additionalAttributes || {};
return Utils.Promise.all(promises);
});
};
if (Array.isArray(newInstance)) {
var newInstances = newInstance.map(function(newInstance) {
if (!(newInstance instanceof association.target.Instance)) {
var tmpInstance = {};
tmpInstance[primaryKeyAttribute] = newInstance;
return association.target.build(tmpInstance, {
isNewRecord: false
});
}
return newInstance;
});
obj[this.accessors.addMultiple] = obj[this.accessors.add] = function(newInstance, additionalAttributes) {
// If newInstance is null or undefined, no-op
if (!newInstance) return Utils.Promise.resolve();
var foreignIdentifier = association.foreignIdentifier
, sourceKeys = Object.keys(association.source.primaryKeys)
, targetKeys = Object.keys(association.target.primaryKeys)
, obsoleteAssociations = []
, changedAssociations = []
, defaultAttributes = additionalAttributes
, promises = []
, oldAssociations = []
, unassociatedObjects;
if (association.through && association.through.scope) {
_.assign(additionalAttributes, association.through.scope);
}
// Don't try to insert the transaction as an attribute in the through table
defaultAttributes = Utils._.omit(defaultAttributes, ['transaction', 'hooks', 'individualHooks', 'ignoreDuplicates', 'validate', 'fields', 'logging']);
var instance = this
, primaryKeyAttribute = association.target.primaryKeyAttribute
, options = additionalAttributes = additionalAttributes || {};
unassociatedObjects = newInstances.filter(function(obj) {
return !Utils._.find(oldAssociations, function(old) {
return (!!obj[foreignIdentifier] ? obj[foreignIdentifier] === old[foreignIdentifier] : (!!obj[targetKeys[0]] ? obj[targetKeys[0]] === old[targetKeys[0]] : obj.id === old.id));
if (Array.isArray(newInstance)) {
var newInstances = newInstance.map(function(newInstance) {
if (!(newInstance instanceof association.target.Instance)) {
var tmpInstance = {};
tmpInstance[primaryKeyAttribute] = newInstance;
return association.target.build(tmpInstance, {
isNewRecord: false
});
}
return newInstance;
});
var foreignIdentifier = association.foreignIdentifier
, sourceKeys = Object.keys(association.source.primaryKeys)
, targetKeys = Object.keys(association.target.primaryKeys)
, obsoleteAssociations = []
, changedAssociations = []
, defaultAttributes = additionalAttributes
, promises = []
, oldAssociations = []
, unassociatedObjects;
// Don't try to insert the transaction as an attribute in the through table
defaultAttributes = Utils._.omit(defaultAttributes, ['transaction', 'hooks', 'individualHooks', 'ignoreDuplicates', 'validate', 'fields', 'logging']);
unassociatedObjects = newInstances.filter(function(obj) {
return !Utils._.find(oldAssociations, function(old) {
return (!!obj[foreignIdentifier] ? obj[foreignIdentifier] === old[foreignIdentifier] : (!!obj[targetKeys[0]] ? obj[targetKeys[0]] === old[targetKeys[0]] : obj.id === old.id));
});
});
oldAssociations.forEach(function(old) {
var newObj = Utils._.find(newInstances, function(obj) {
return (!!obj[foreignIdentifier] ? obj[foreignIdentifier] === old[foreignIdentifier] : (!!obj[targetKeys[0]] ? obj[targetKeys[0]] === old[targetKeys[0]] : obj.id === old.id));
});
oldAssociations.forEach(function(old) {
var newObj = Utils._.find(newInstances, function(obj) {
return (!!obj[foreignIdentifier] ? obj[foreignIdentifier] === old[foreignIdentifier] : (!!obj[targetKeys[0]] ? obj[targetKeys[0]] === old[targetKeys[0]] : obj.id === old.id));
});
if (!newObj) {
obsoleteAssociations.push(old);
} else if (Object(association.through.model) === association.through.model) {
var throughAttributes = newObj[association.through.model.name];
// Quick-fix for subtle bug when using existing objects that might have the through model attached (not as an attribute object)
if (throughAttributes instanceof association.through.model.Instance) {
throughAttributes = {};
}
var changedAssociation = {
where: {},
attributes: Utils._.defaults({}, throughAttributes, defaultAttributes)
};
changedAssociation.where[association.identifier] = instance[sourceKeys[0]] || instance.id;
changedAssociation.where[foreignIdentifier] = newObj[targetKeys[0]] || newObj.id;
if (Object.keys(changedAssociation.attributes).length) {
changedAssociations.push(changedAssociation);
}
if (!newObj) {
obsoleteAssociations.push(old);
} else if (Object(association.through.model) === association.through.model) {
var throughAttributes = newObj[association.through.model.name];
// Quick-fix for subtle bug when using existing objects that might have the through model attached (not as an attribute object)
if (throughAttributes instanceof association.through.model.Instance) {
throughAttributes = {};
}
});
if (obsoleteAssociations.length > 0) {
var foreignIds = obsoleteAssociations.map(function(associatedObject) {
return ((targetKeys.length === 1) ? associatedObject[targetKeys[0]] : associatedObject.id);
});
var changedAssociation = {
where: {},
attributes: Utils._.defaults({}, throughAttributes, defaultAttributes)
};
var where = {};
where[association.identifier] = ((sourceKeys.length === 1) ? instance[sourceKeys[0]] : instance.id);
where[association.foreignIdentifier] = foreignIds;
changedAssociation.where[association.identifier] = instance[sourceKeys[0]] || instance.id;
changedAssociation.where[foreignIdentifier] = newObj[targetKeys[0]] || newObj.id;
promises.push(association.through.model.destroy(Utils._.extend(options, {
where: where
})));
if (Object.keys(changedAssociation.attributes).length) {
changedAssociations.push(changedAssociation);
}
}
});
if (unassociatedObjects.length > 0) {
var bulk = unassociatedObjects.map(function(unassociatedObject) {
var attributes = {};
if (obsoleteAssociations.length > 0) {
var foreignIds = obsoleteAssociations.map(function(associatedObject) {
return ((targetKeys.length === 1) ? associatedObject[targetKeys[0]] : associatedObject.id);
});
attributes[association.identifier] = ((sourceKeys.length === 1) ? instance[sourceKeys[0]] : instance.id);
attributes[association.foreignIdentifier] = ((targetKeys.length === 1) ? unassociatedObject[targetKeys[0]] : unassociatedObject.id);
var where = {};
where[association.identifier] = ((sourceKeys.length === 1) ? instance[sourceKeys[0]] : instance.id);
where[association.foreignIdentifier] = foreignIds;
if (Object(association.through.model) === association.through.model) {
attributes = Utils._.defaults(attributes, unassociatedObject[association.through.model.name], defaultAttributes);
}
promises.push(association.through.model.destroy(Utils._.extend(options, {
where: where
})));
}
if (association.through.scope) {
_.assign(attributes, association.through.scope);
}
if (unassociatedObjects.length > 0) {
var bulk = unassociatedObjects.map(function(unassociatedObject) {
var attributes = {};
return attributes;
}.bind(this));
attributes[association.identifier] = ((sourceKeys.length === 1) ? instance[sourceKeys[0]] : instance.id);
attributes[association.foreignIdentifier] = ((targetKeys.length === 1) ? unassociatedObject[targetKeys[0]] : unassociatedObject.id);
promises.push(association.through.model.bulkCreate(bulk, options));
}
if (Object(association.through.model) === association.through.model) {
attributes = Utils._.defaults(attributes, unassociatedObject[association.through.model.name], defaultAttributes);
}
if (changedAssociations.length > 0) {
changedAssociations.forEach(function(assoc) {
promises.push(association.through.model.update(assoc.attributes, Utils._.extend(options, {
where: assoc.where
})));
});
}
if (association.through.scope) {
_.assign(attributes, association.through.scope);
}
return Utils.Promise.all(promises);
} else {
if (!(newInstance instanceof association.target.Instance)) {
var tmpInstance = {};
tmpInstance[primaryKeyAttribute] = newInstance;
newInstance = association.target.build(tmpInstance, {
isNewRecord: false
});
}
return attributes;
}.bind(this));
return instance[association.accessors.get]({
scope: false,
where: newInstance.where(),
transaction: (additionalAttributes || {}).transaction,
logging: options.logging
}).then(function(currentAssociatedObjects) {
promises.push(association.through.model.bulkCreate(bulk, options));
}
var attributes = {}
, foreignIdentifier = association.foreignIdentifier;
if (changedAssociations.length > 0) {
changedAssociations.forEach(function(assoc) {
promises.push(association.through.model.update(assoc.attributes, Utils._.extend(options, {
where: assoc.where
})));
});
}
return Utils.Promise.all(promises);
} else {
if (!(newInstance instanceof association.target.Instance)) {
var tmpInstance = {};
tmpInstance[primaryKeyAttribute] = newInstance;
newInstance = association.target.build(tmpInstance, {
isNewRecord: false
});
}
return instance[association.accessors.get]({
scope: false,
where: newInstance.where(),
transaction: (additionalAttributes || {}).transaction,
logging: options.logging
}).then(function(currentAssociatedObjects) {
var sourceKeys = Object.keys(association.source.primaryKeys);
var targetKeys = Object.keys(association.target.primaryKeys);
var attributes = {}
, foreignIdentifier = association.foreignIdentifier;
// Don't try to insert the transaction as an attribute in the through table
additionalAttributes = Utils._.omit(additionalAttributes, ['transaction', 'hooks', 'individualHooks', 'ignoreDuplicates', 'validate', 'fields', 'logging']);
var sourceKeys = Object.keys(association.source.primaryKeys);
var targetKeys = Object.keys(association.target.primaryKeys);
attributes[association.identifier] = ((sourceKeys.length === 1) ? instance[sourceKeys[0]] : instance.id);
attributes[foreignIdentifier] = ((targetKeys.length === 1) ? newInstance[targetKeys[0]] : newInstance.id);
if (!!currentAssociatedObjects.length) {
var where = attributes;
attributes = Utils._.defaults({}, newInstance[association.through.model.name], additionalAttributes);
if (Object.keys(attributes).length) {
return association.through.model.update(attributes, Utils._.extend(options, {
where: where
}));
} else {
return Utils.Promise.resolve();
}
} else {
attributes = Utils._.defaults(attributes, newInstance[association.through.model.name], additionalAttributes);
if (association.through.scope) {
_.assign(attributes, association.through.scope);
}
// Don't try to insert the transaction as an attribute in the through table
additionalAttributes = Utils._.omit(additionalAttributes, ['transaction', 'hooks', 'individualHooks', 'ignoreDuplicates', 'validate', 'fields', 'logging']);
return association.through.model.create(attributes, options);
attributes[association.identifier] = ((sourceKeys.length === 1) ? instance[sourceKeys[0]] : instance.id);
attributes[foreignIdentifier] = ((targetKeys.length === 1) ? newInstance[targetKeys[0]] : newInstance.id);
if (!!currentAssociatedObjects.length) {
var where = attributes;
attributes = Utils._.defaults({}, newInstance[association.through.model.name], additionalAttributes);
if (Object.keys(attributes).length) {
return association.through.model.update(attributes, Utils._.extend(options, {
where: where
}));
} else {
return Utils.Promise.resolve();
}
} else {
attributes = Utils._.defaults(attributes, newInstance[association.through.model.name], additionalAttributes);
if (association.through.scope) {
_.assign(attributes, association.through.scope);
}
});
}
};
obj[this.accessors.removeMultiple] = obj[this.accessors.remove] = function(oldAssociatedObject, options) {
options = options || {};
options.remove = true;
return this[association.accessors.set](oldAssociatedObject, options);
};
return association.through.model.create(attributes, options);
}
});
}
};
return this;
obj[this.accessors.removeMultiple] = obj[this.accessors.remove] = function(oldAssociatedObject, options) {
options = options || {};
options.remove = true;
return this[association.accessors.set](oldAssociatedObject, options);
};
BelongsToMany.prototype.injectCreator = function(obj) {
var association = this;
return this;
};
obj[this.accessors.create] = function(values, options) {
var instance = this;
options = options || {};
BelongsToMany.prototype.injectCreator = function(obj) {
var association = this;
if (Array.isArray(options)) {
options = {
fields: options
};
}
obj[this.accessors.create] = function(values, options) {
var instance = this;
options = options || {};
if (values === undefined) {
values = {};
}
if (Array.isArray(options)) {
options = {
fields: options
};
}
if (association.scope) {
Object.keys(association.scope).forEach(function (attribute) {
values[attribute] = association.scope[attribute];
if (options.fields) options.fields.push(attribute);
});
}
if (values === undefined) {
values = {};
}
// Create the related model instance
return association.target.create(values, options).then(function(newAssociatedObject) {
return instance[association.accessors.add](newAssociatedObject, _.omit(options, ['fields'])).return(newAssociatedObject);
if (association.scope) {
Object.keys(association.scope).forEach(function (attribute) {
values[attribute] = association.scope[attribute];
if (options.fields) options.fields.push(attribute);
});
};
}
return this;
// Create the related model instance
return association.target.create(values, options).then(function(newAssociatedObject) {
return instance[association.accessors.add](newAssociatedObject, _.omit(options, ['fields'])).return(newAssociatedObject);
});
};
return BelongsToMany;
})();
return this;
};
module.exports = BelongsToMany;
......@@ -6,176 +6,174 @@ var Utils = require('./../utils')
, Association = require('./base')
, util = require('util');
module.exports = (function() {
var BelongsTo = function(source, target, options) {
Association.call(this);
this.associationType = 'BelongsTo';
this.source = source;
this.target = target;
this.options = options;
this.scope = options.scope;
this.isSingleAssociation = true;
this.isSelfAssociation = (this.source === this.target);
this.as = this.options.as;
if (Utils._.isObject(this.options.foreignKey)) {
this.foreignKeyAttribute = this.options.foreignKey;
this.foreignKey = this.foreignKeyAttribute.name || this.foreignKeyAttribute.fieldName;
} else {
this.foreignKeyAttribute = {};
this.foreignKey = this.options.foreignKey;
}
if (this.as) {
this.isAliased = true;
this.options.name = {
singular: this.as
};
} else {
this.as = this.target.options.name.singular;
this.options.name = this.target.options.name;
}
if (!this.options.foreignKey) {
this.options.foreignKey = Utils._.camelizeIf(
[
Utils._.underscoredIf(this.as, this.source.options.underscored),
this.target.primaryKeyAttribute
].join('_'),
!this.source.options.underscored
);
}
var BelongsTo = function(source, target, options) {
Association.call(this);
this.associationType = 'BelongsTo';
this.source = source;
this.target = target;
this.options = options;
this.scope = options.scope;
this.isSingleAssociation = true;
this.isSelfAssociation = (this.source === this.target);
this.as = this.options.as;
if (Utils._.isObject(this.options.foreignKey)) {
this.foreignKeyAttribute = this.options.foreignKey;
this.foreignKey = this.foreignKeyAttribute.name || this.foreignKeyAttribute.fieldName;
} else {
this.foreignKeyAttribute = {};
this.foreignKey = this.options.foreignKey;
}
if (this.as) {
this.isAliased = true;
this.options.name = {
singular: this.as
};
} else {
this.as = this.target.options.name.singular;
this.options.name = this.target.options.name;
}
this.identifier = this.foreignKey || Utils._.camelizeIf(
if (!this.options.foreignKey) {
this.options.foreignKey = Utils._.camelizeIf(
[
Utils._.underscoredIf(this.options.name.singular, this.target.options.underscored),
Utils._.underscoredIf(this.as, this.source.options.underscored),
this.target.primaryKeyAttribute
].join('_'),
!this.target.options.underscored
!this.source.options.underscored
);
this.targetIdentifier = this.target.primaryKeyAttribute;
this.associationAccessor = this.as;
this.options.useHooks = options.useHooks;
// Get singular name, trying to uppercase the first letter, unless the model forbids it
var singular = Utils.uppercaseFirst(this.options.name.singular);
this.accessors = {
get: 'get' + singular,
set: 'set' + singular,
create: 'create' + singular
};
}
this.identifier = this.foreignKey || Utils._.camelizeIf(
[
Utils._.underscoredIf(this.options.name.singular, this.target.options.underscored),
this.target.primaryKeyAttribute
].join('_'),
!this.target.options.underscored
);
this.targetIdentifier = this.target.primaryKeyAttribute;
this.associationAccessor = this.as;
this.options.useHooks = options.useHooks;
// Get singular name, trying to uppercase the first letter, unless the model forbids it
var singular = Utils.uppercaseFirst(this.options.name.singular);
this.accessors = {
get: 'get' + singular,
set: 'set' + singular,
create: 'create' + singular
};
};
util.inherits(BelongsTo, Association);
util.inherits(BelongsTo, Association);
// the id is in the source table
BelongsTo.prototype.injectAttributes = function() {
var newAttributes = {};
// the id is in the source table
BelongsTo.prototype.injectAttributes = function() {
var newAttributes = {};
newAttributes[this.identifier] = Utils._.defaults(this.foreignKeyAttribute, { type: this.options.keyType || this.target.rawAttributes[this.targetIdentifier].type });
if (this.options.constraints !== false) {
this.options.onDelete = this.options.onDelete || 'SET NULL';
this.options.onUpdate = this.options.onUpdate || 'CASCADE';
}
Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.target, this.source, this.options);
Utils.mergeDefaults(this.source.rawAttributes, newAttributes);
newAttributes[this.identifier] = Utils._.defaults(this.foreignKeyAttribute, { type: this.options.keyType || this.target.rawAttributes[this.targetIdentifier].type });
if (this.options.constraints !== false) {
this.options.onDelete = this.options.onDelete || 'SET NULL';
this.options.onUpdate = this.options.onUpdate || 'CASCADE';
}
Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.target, this.source, this.options);
Utils.mergeDefaults(this.source.rawAttributes, newAttributes);
this.identifierField = this.source.rawAttributes[this.identifier].field || this.identifier;
this.identifierField = this.source.rawAttributes[this.identifier].field || this.identifier;
this.source.refreshAttributes();
this.source.refreshAttributes();
Helpers.checkNamingCollision(this);
Helpers.checkNamingCollision(this);
return this;
};
return this;
};
// Add getAssociation method to the prototype of the model instance
BelongsTo.prototype.injectGetter = function(instancePrototype) {
var association = this;
// Add getAssociation method to the prototype of the model instance
BelongsTo.prototype.injectGetter = function(instancePrototype) {
var association = this;
instancePrototype[this.accessors.get] = function(options) {
var where = {};
where[association.targetIdentifier] = this.get(association.identifier);
instancePrototype[this.accessors.get] = function(options) {
var where = {};
where[association.targetIdentifier] = this.get(association.identifier);
options = association.target.__optClone(options) || {};
options = association.target.__optClone(options) || {};
options.where = {
$and: [
options.where,
where
]
};
options.where = {
$and: [
options.where,
where
]
};
if (options.limit === undefined) options.limit = null;
if (options.limit === undefined) options.limit = null;
var model = association.target;
if (options.hasOwnProperty('scope')) {
if (!options.scope) {
model = model.unscoped();
} else {
model = model.scope(options.scope);
}
var model = association.target;
if (options.hasOwnProperty('scope')) {
if (!options.scope) {
model = model.unscoped();
} else {
model = model.scope(options.scope);
}
}
return model.find(options);
};
return this;
return model.find(options);
};
// Add setAssociaton method to the prototype of the model instance
BelongsTo.prototype.injectSetter = function(instancePrototype) {
var association = this;
return this;
};
instancePrototype[this.accessors.set] = function(associatedInstance, options) {
options = options || {};
// Add setAssociaton method to the prototype of the model instance
BelongsTo.prototype.injectSetter = function(instancePrototype) {
var association = this;
var value = associatedInstance;
if (associatedInstance instanceof association.target.Instance) {
value = associatedInstance[association.targetIdentifier];
}
instancePrototype[this.accessors.set] = function(associatedInstance, options) {
options = options || {};
this.set(association.identifier, value);
var value = associatedInstance;
if (associatedInstance instanceof association.target.Instance) {
value = associatedInstance[association.targetIdentifier];
}
if (options.save === false) return;
this.set(association.identifier, value);
options = Utils._.extend({
fields: [association.identifier],
allowNull: [association.identifier],
association: true
}, options);
if (options.save === false) return;
options = Utils._.extend({
fields: [association.identifier],
allowNull: [association.identifier],
association: true
}, options);
// passes the changed field to save, so only that field get updated.
return this.save(options);
};
return this;
// passes the changed field to save, so only that field get updated.
return this.save(options);
};
// Add createAssociation method to the prototype of the model instance
BelongsTo.prototype.injectCreator = function(instancePrototype) {
var association = this;
return this;
};
instancePrototype[this.accessors.create] = function(values, fieldsOrOptions) {
var instance = this
, options = {};
// Add createAssociation method to the prototype of the model instance
BelongsTo.prototype.injectCreator = function(instancePrototype) {
var association = this;
if ((fieldsOrOptions || {}).transaction instanceof Transaction) {
options.transaction = fieldsOrOptions.transaction;
}
options.logging = (fieldsOrOptions || {}).logging;
instancePrototype[this.accessors.create] = function(values, fieldsOrOptions) {
var instance = this
, options = {};
return association.target.create(values, fieldsOrOptions).then(function(newAssociatedObject) {
return instance[association.accessors.set](newAssociatedObject, options);
});
};
if ((fieldsOrOptions || {}).transaction instanceof Transaction) {
options.transaction = fieldsOrOptions.transaction;
}
options.logging = (fieldsOrOptions || {}).logging;
return this;
return association.target.create(values, fieldsOrOptions).then(function(newAssociatedObject) {
return instance[association.accessors.set](newAssociatedObject, options);
});
};
return BelongsTo;
})();
return this;
};
module.exports = BelongsTo;
......@@ -3,135 +3,133 @@
var Utils = require('./../utils')
, _ = require('lodash');
module.exports = (function() {
var HasManySingleLinked = function(association, instance) {
this.association = association;
this.instance = instance;
this.target = this.association.target;
this.source = this.association.source;
var HasManySingleLinked = function(association, instance) {
this.association = association;
this.instance = instance;
this.target = this.association.target;
this.source = this.association.source;
};
HasManySingleLinked.prototype.injectGetter = function(options) {
var scopeWhere = this.association.scope ? {} : null;
if (this.association.scope) {
Object.keys(this.association.scope).forEach(function (attribute) {
scopeWhere[attribute] = this.association.scope[attribute];
}.bind(this));
}
options.where = {
$and: [
new Utils.where(
this.target.rawAttributes[this.association.identifier],
this.instance[this.source.primaryKeyAttribute]
),
scopeWhere,
options.where
]
};
HasManySingleLinked.prototype.injectGetter = function(options) {
var scopeWhere = this.association.scope ? {} : null;
var model = this.association.target;
if (options.hasOwnProperty('scope')) {
if (!options.scope) {
model = model.unscoped();
} else {
model = model.scope(options.scope);
}
}
return model.all(options);
};
HasManySingleLinked.prototype.injectSetter = function(oldAssociations, newAssociations, defaultAttributes) {
var self = this
, primaryKeys
, primaryKey
, updateWhere
, associationKeys = Object.keys((oldAssociations[0] || newAssociations[0] || {Model: {primaryKeys: {}}}).Model.primaryKeys || {})
, associationKey = (associationKeys.length === 1) ? associationKeys[0] : 'id'
, options = defaultAttributes
, promises = []
, obsoleteAssociations = oldAssociations.filter(function(old) {
return !Utils._.find(newAssociations, function(obj) {
return obj[associationKey] === old[associationKey];
});
})
, unassociatedObjects = newAssociations.filter(function(obj) {
return !Utils._.find(oldAssociations, function(old) {
return obj[associationKey] === old[associationKey];
});
})
, update;
if (obsoleteAssociations.length > 0) {
// clear the old associations
var obsoleteIds = obsoleteAssociations.map(function(associatedObject) {
associatedObject[self.association.identifier] = (newAssociations.length < 1 ? null : self.instance.id);
return associatedObject[associationKey];
});
update = {};
update[self.association.identifier] = null;
primaryKeys = Object.keys(this.association.target.primaryKeys);
primaryKey = primaryKeys.length === 1 ? primaryKeys[0] : 'id';
updateWhere = {};
updateWhere[primaryKey] = obsoleteIds;
promises.push(this.association.target.unscoped().update(
update,
Utils._.extend(options, {
allowNull: [self.association.identifier],
where: updateWhere
})
));
}
if (unassociatedObjects.length > 0) {
// For the self.instance
var pkeys = Object.keys(self.instance.Model.primaryKeys)
, pkey = pkeys.length === 1 ? pkeys[0] : 'id';
primaryKeys = Object.keys(this.association.target.primaryKeys);
primaryKey = primaryKeys.length === 1 ? primaryKeys[0] : 'id';
updateWhere = {};
// set the new associations
var unassociatedIds = unassociatedObjects.map(function(associatedObject) {
associatedObject[self.association.identifier] = self.instance[pkey] || self.instance.id;
return associatedObject[associationKey];
});
update = {};
update[self.association.identifier] = (newAssociations.length < 1 ? null : self.instance[pkey] || self.instance.id);
if (this.association.scope) {
Object.keys(this.association.scope).forEach(function (attribute) {
scopeWhere[attribute] = this.association.scope[attribute];
}.bind(this));
_.assign(update, this.association.scope);
}
options.where = {
$and: [
new Utils.where(
this.target.rawAttributes[this.association.identifier],
this.instance[this.source.primaryKeyAttribute]
),
scopeWhere,
options.where
]
};
var model = this.association.target;
if (options.hasOwnProperty('scope')) {
if (!options.scope) {
model = model.unscoped();
} else {
model = model.scope(options.scope);
}
}
updateWhere[primaryKey] = unassociatedIds;
return model.all(options);
};
promises.push(this.association.target.unscoped().update(
update,
Utils._.extend(options, {
allowNull: [self.association.identifier],
where: updateWhere
})
));
}
HasManySingleLinked.prototype.injectSetter = function(oldAssociations, newAssociations, defaultAttributes) {
var self = this
, primaryKeys
, primaryKey
, updateWhere
, associationKeys = Object.keys((oldAssociations[0] || newAssociations[0] || {Model: {primaryKeys: {}}}).Model.primaryKeys || {})
, associationKey = (associationKeys.length === 1) ? associationKeys[0] : 'id'
, options = defaultAttributes
, promises = []
, obsoleteAssociations = oldAssociations.filter(function(old) {
return !Utils._.find(newAssociations, function(obj) {
return obj[associationKey] === old[associationKey];
});
})
, unassociatedObjects = newAssociations.filter(function(obj) {
return !Utils._.find(oldAssociations, function(old) {
return obj[associationKey] === old[associationKey];
});
})
, update;
if (obsoleteAssociations.length > 0) {
// clear the old associations
var obsoleteIds = obsoleteAssociations.map(function(associatedObject) {
associatedObject[self.association.identifier] = (newAssociations.length < 1 ? null : self.instance.id);
return associatedObject[associationKey];
});
update = {};
update[self.association.identifier] = null;
primaryKeys = Object.keys(this.association.target.primaryKeys);
primaryKey = primaryKeys.length === 1 ? primaryKeys[0] : 'id';
updateWhere = {};
updateWhere[primaryKey] = obsoleteIds;
promises.push(this.association.target.unscoped().update(
update,
Utils._.extend(options, {
allowNull: [self.association.identifier],
where: updateWhere
})
));
}
return Utils.Promise.all(promises);
};
if (unassociatedObjects.length > 0) {
// For the self.instance
var pkeys = Object.keys(self.instance.Model.primaryKeys)
, pkey = pkeys.length === 1 ? pkeys[0] : 'id';
primaryKeys = Object.keys(this.association.target.primaryKeys);
primaryKey = primaryKeys.length === 1 ? primaryKeys[0] : 'id';
updateWhere = {};
// set the new associations
var unassociatedIds = unassociatedObjects.map(function(associatedObject) {
associatedObject[self.association.identifier] = self.instance[pkey] || self.instance.id;
return associatedObject[associationKey];
});
update = {};
update[self.association.identifier] = (newAssociations.length < 1 ? null : self.instance[pkey] || self.instance.id);
if (this.association.scope) {
_.assign(update, this.association.scope);
}
updateWhere[primaryKey] = unassociatedIds;
promises.push(this.association.target.unscoped().update(
update,
Utils._.extend(options, {
allowNull: [self.association.identifier],
where: updateWhere
})
));
}
HasManySingleLinked.prototype.injectAdder = function(newAssociation, options) {
newAssociation.set(this.association.identifier, this.instance.get(this.instance.Model.primaryKeyAttribute));
if (this.association.scope) {
Object.keys(this.association.scope).forEach(function (attribute) {
newAssociation.set(attribute, this.association.scope[attribute]);
}.bind(this));
}
return Utils.Promise.all(promises);
};
HasManySingleLinked.prototype.injectAdder = function(newAssociation, options) {
newAssociation.set(this.association.identifier, this.instance.get(this.instance.Model.primaryKeyAttribute));
if (this.association.scope) {
Object.keys(this.association.scope).forEach(function (attribute) {
newAssociation.set(attribute, this.association.scope[attribute]);
}.bind(this));
}
return newAssociation.save(options);
};
return newAssociation.save(options);
};
return HasManySingleLinked;
})();
module.exports = HasManySingleLinked;
......@@ -8,282 +8,308 @@ var Utils = require('./../utils')
, util = require('util')
, HasManySingleLinked = require('./has-many-single-linked');
module.exports = (function() {
var HasMany = function(source, target, options) {
Association.call(this);
this.associationType = 'HasMany';
this.source = source;
this.target = target;
this.targetAssociation = null;
this.options = options || {};
this.sequelize = source.modelManager.sequelize;
this.through = options.through;
this.scope = options.scope;
this.isMultiAssociation = true;
this.isSelfAssociation = this.source === this.target;
this.as = this.options.as;
if (this.options.through) {
throw new Error('N:M associations are not supported with hasMany. Use belongsToMany instead');
}
if (Utils._.isObject(this.options.foreignKey)) {
this.foreignKeyAttribute = this.options.foreignKey;
this.foreignKey = this.foreignKeyAttribute.name || this.foreignKeyAttribute.fieldName;
var HasMany = function(source, target, options) {
Association.call(this);
this.associationType = 'HasMany';
this.source = source;
this.target = target;
this.targetAssociation = null;
this.options = options || {};
this.sequelize = source.modelManager.sequelize;
this.through = options.through;
this.scope = options.scope;
this.isMultiAssociation = true;
this.isSelfAssociation = this.source === this.target;
this.as = this.options.as;
if (this.options.through) {
throw new Error('N:M associations are not supported with hasMany. Use belongsToMany instead');
}
if (Utils._.isObject(this.options.foreignKey)) {
this.foreignKeyAttribute = this.options.foreignKey;
this.foreignKey = this.foreignKeyAttribute.name || this.foreignKeyAttribute.fieldName;
} else {
this.foreignKeyAttribute = {};
this.foreignKey = this.options.foreignKey;
}
/*
* If self association, this is the target association
*/
if (this.isSelfAssociation) {
this.targetAssociation = this;
}
if (this.as) {
this.isAliased = true;
if (Utils._.isPlainObject(this.as)) {
this.options.name = this.as;
this.as = this.as.plural;
} else {
this.foreignKeyAttribute = {};
this.foreignKey = this.options.foreignKey;
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;
// Get singular and plural names, trying to uppercase the first letter, unless the model forbids it
var plural = Utils.uppercaseFirst(this.options.name.plural)
, singular = Utils.uppercaseFirst(this.options.name.singular);
this.accessors = {
get: 'get' + plural,
set: 'set' + plural,
addMultiple: 'add' + plural,
add: 'add' + singular,
create: 'create' + singular,
remove: 'remove' + singular,
removeMultiple: 'remove' + plural,
hasSingle: 'has' + singular,
hasAll: 'has' + plural
};
/*
* If self association, this is the target association
*/
if (this.isSelfAssociation) {
this.targetAssociation = this;
}
if (this.options.counterCache) {
new CounterCache(this, this.options.counterCache !== true ? this.options.counterCache : {});
}
};
if (this.as) {
this.isAliased = true;
util.inherits(HasMany, Association);
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;
}
// the id is in the target table
// or in an extra table which connects two tables
HasMany.prototype.injectAttributes = function() {
this.identifier = this.foreignKey || Utils._.camelizeIf(
[
Utils._.underscoredIf(this.source.options.name.singular, this.source.options.underscored),
this.source.primaryKeyAttribute
].join('_'),
!this.source.options.underscored
);
this.associationAccessor = this.as;
// Get singular and plural names, trying to uppercase the first letter, unless the model forbids it
var plural = Utils.uppercaseFirst(this.options.name.plural)
, singular = Utils.uppercaseFirst(this.options.name.singular);
this.accessors = {
get: 'get' + plural,
set: 'set' + plural,
addMultiple: 'add' + plural,
add: 'add' + singular,
create: 'create' + singular,
remove: 'remove' + singular,
removeMultiple: 'remove' + plural,
hasSingle: 'has' + singular,
hasAll: 'has' + plural
};
var newAttributes = {};
var constraintOptions = _.clone(this.options); // Create a new options object for use with addForeignKeyConstraints, to avoid polluting this.options in case it is later used for a n:m
newAttributes[this.identifier] = _.defaults(this.foreignKeyAttribute, { type: this.options.keyType || this.source.rawAttributes[this.source.primaryKeyAttribute].type });
if (this.options.counterCache) {
new CounterCache(this, this.options.counterCache !== true ? this.options.counterCache : {});
}
};
if (this.options.constraints !== false) {
constraintOptions.onDelete = constraintOptions.onDelete || 'SET NULL';
constraintOptions.onUpdate = constraintOptions.onUpdate || 'CASCADE';
}
Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.source, this.target, constraintOptions);
Utils.mergeDefaults(this.target.rawAttributes, newAttributes);
util.inherits(HasMany, Association);
// the id is in the target table
// or in an extra table which connects two tables
HasMany.prototype.injectAttributes = function() {
this.identifier = this.foreignKey || Utils._.camelizeIf(
[
Utils._.underscoredIf(this.source.options.name.singular, this.source.options.underscored),
this.source.primaryKeyAttribute
].join('_'),
!this.source.options.underscored
);
var newAttributes = {};
var constraintOptions = _.clone(this.options); // Create a new options object for use with addForeignKeyConstraints, to avoid polluting this.options in case it is later used for a n:m
newAttributes[this.identifier] = _.defaults(this.foreignKeyAttribute, { type: this.options.keyType || this.source.rawAttributes[this.source.primaryKeyAttribute].type });
if (this.options.constraints !== false) {
constraintOptions.onDelete = constraintOptions.onDelete || 'SET NULL';
constraintOptions.onUpdate = constraintOptions.onUpdate || 'CASCADE';
}
Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.source, this.target, constraintOptions);
Utils.mergeDefaults(this.target.rawAttributes, newAttributes);
this.identifierField = this.target.rawAttributes[this.identifier].field || this.identifier;
this.identifierField = this.target.rawAttributes[this.identifier].field || this.identifier;
this.target.refreshAttributes();
this.source.refreshAttributes();
this.target.refreshAttributes();
this.source.refreshAttributes();
Helpers.checkNamingCollision(this);
Helpers.checkNamingCollision(this);
return this;
};
return this;
};
HasMany.prototype.injectGetter = function(obj) {
var association = this;
obj[this.accessors.get] = function(options) {
var scopeWhere = association.scope ? {} : null
, Model = association.target;
HasMany.prototype.injectGetter = function(obj) {
var association = this;
options = association.target.__optClone(options) || {};
obj[this.accessors.get] = function(options) {
var scopeWhere = association.scope ? {} : null
, Model = association.target;
if (association.scope) {
_.assign(scopeWhere, association.scope);
}
options = association.target.__optClone(options) || {};
options.where = {
$and: [
new Utils.where(
association.target.rawAttributes[association.identifier],
this.get(association.source.primaryKeyAttribute, {raw: true})
),
scopeWhere,
options.where
]
};
if (association.scope) {
_.assign(scopeWhere, association.scope);
if (options.hasOwnProperty('scope')) {
if (!options.scope) {
Model = Model.unscoped();
} else {
Model = Model.scope(options.scope);
}
}
options.where = {
$and: [
new Utils.where(
association.target.rawAttributes[association.identifier],
this.get(association.source.primaryKeyAttribute, {raw: true})
),
scopeWhere,
options.where
]
};
return Model.all(options);
};
if (options.hasOwnProperty('scope')) {
if (!options.scope) {
Model = Model.unscoped();
} else {
Model = Model.scope(options.scope);
}
obj[this.accessors.hasSingle] = obj[this.accessors.hasAll] = function(instances, options) {
var where = {};
if (!Array.isArray(instances)) {
instances = [instances];
}
options = options || {};
options.scope = false;
where.$or = instances.map(function (instance) {
if (instance instanceof association.target.Instance) {
return instance.where();
} else {
var _where = {};
_where[association.target.primaryKeyAttribute] = instance;
return _where;
}
});
return Model.all(options);
options.where = {
$and: [
where,
options.where
]
};
obj[this.accessors.hasSingle] = obj[this.accessors.hasAll] = function(instances, options) {
var where = {};
return this[association.accessors.get](
options,
{ raw: true }
).then(function(associatedObjects) {
return associatedObjects.length === instances.length;
});
};
if (!Array.isArray(instances)) {
instances = [instances];
}
return this;
};
options = options || {};
options.scope = false;
HasMany.prototype.injectSetter = function(obj) {
var association = this
, primaryKeyAttribute = association.target.primaryKeyAttribute;
where.$or = instances.map(function (instance) {
if (instance instanceof association.target.Instance) {
return instance.where();
} else {
var _where = {};
_where[association.target.primaryKeyAttribute] = instance;
return _where;
obj[this.accessors.set] = function(newAssociatedObjects, additionalAttributes) {
additionalAttributes = additionalAttributes || {};
if (newAssociatedObjects === null) {
newAssociatedObjects = [];
} else {
newAssociatedObjects = newAssociatedObjects.map(function(newAssociatedObject) {
if (!(newAssociatedObject instanceof association.target.Instance)) {
var tmpInstance = {};
tmpInstance[primaryKeyAttribute] = newAssociatedObject;
return association.target.build(tmpInstance, {
isNewRecord: false
});
}
return newAssociatedObject;
});
}
options.where = {
$and: [
where,
options.where
]
};
return this[association.accessors.get](
options,
{ raw: true }
).then(function(associatedObjects) {
return associatedObjects.length === instances.length;
});
};
var instance = this;
return this;
return instance[association.accessors.get]({
scope: false,
transaction: (additionalAttributes || {}).transaction,
logging: (additionalAttributes || {}).logging
}).then(function(oldAssociatedObjects) {
return new HasManySingleLinked(association, instance).injectSetter(oldAssociatedObjects, newAssociatedObjects, additionalAttributes);
});
};
HasMany.prototype.injectSetter = function(obj) {
var association = this
obj[this.accessors.addMultiple] = obj[this.accessors.add] = function(newInstance, options) {
// If newInstance is null or undefined, no-op
if (!newInstance) return Utils.Promise.resolve();
var instance = this
, primaryKeyAttribute = association.target.primaryKeyAttribute;
obj[this.accessors.set] = function(newAssociatedObjects, additionalAttributes) {
additionalAttributes = additionalAttributes || {};
options = options || {};
if (newAssociatedObjects === null) {
newAssociatedObjects = [];
} else {
newAssociatedObjects = newAssociatedObjects.map(function(newAssociatedObject) {
if (!(newAssociatedObject instanceof association.target.Instance)) {
var tmpInstance = {};
tmpInstance[primaryKeyAttribute] = newAssociatedObject;
return association.target.build(tmpInstance, {
isNewRecord: false
});
}
return newAssociatedObject;
if (Array.isArray(newInstance)) {
var newInstances = newInstance.map(function(newInstance) {
if (!(newInstance instanceof association.target.Instance)) {
var tmpInstance = {};
tmpInstance[primaryKeyAttribute] = newInstance;
return association.target.build(tmpInstance, {
isNewRecord: false
});
}
return newInstance;
});
return new HasManySingleLinked(association, this).injectSetter([], newInstances, options);
} else {
if (!(newInstance instanceof association.target.Instance)) {
var values = {};
values[primaryKeyAttribute] = newInstance;
newInstance = association.target.build(values, {
isNewRecord: false
});
}
var instance = this;
return instance[association.accessors.get]({
where: newInstance.where(),
scope: false,
transaction: (additionalAttributes || {}).transaction,
logging: (additionalAttributes || {}).logging
}).then(function(oldAssociatedObjects) {
return new HasManySingleLinked(association, instance).injectSetter(oldAssociatedObjects, newAssociatedObjects, additionalAttributes);
});
};
obj[this.accessors.addMultiple] = obj[this.accessors.add] = function(newInstance, options) {
// If newInstance is null or undefined, no-op
if (!newInstance) return Utils.Promise.resolve();
var instance = this
, primaryKeyAttribute = association.target.primaryKeyAttribute;
options = options || {};
if (Array.isArray(newInstance)) {
var newInstances = newInstance.map(function(newInstance) {
if (!(newInstance instanceof association.target.Instance)) {
var tmpInstance = {};
tmpInstance[primaryKeyAttribute] = newInstance;
return association.target.build(tmpInstance, {
isNewRecord: false
transaction: options.transaction,
logging: options.logging
}).bind(this).then(function(currentAssociatedObjects) {
if (currentAssociatedObjects.length === 0) {
newInstance.set(association.identifier, instance.get(instance.Model.primaryKeyAttribute));
if (association.scope) {
Object.keys(association.scope).forEach(function (attribute) {
newInstance.set(attribute, association.scope[attribute]);
});
}
return newInstance;
});
return new HasManySingleLinked(association, this).injectSetter([], newInstances, options);
} else {
if (!(newInstance instanceof association.target.Instance)) {
var values = {};
values[primaryKeyAttribute] = newInstance;
newInstance = association.target.build(values, {
isNewRecord: false
});
return newInstance.save(options);
} else {
return Utils.Promise.resolve(currentAssociatedObjects[0]);
}
});
}
};
return instance[association.accessors.get]({
where: newInstance.where(),
scope: false,
transaction: options.transaction,
logging: options.logging
}).bind(this).then(function(currentAssociatedObjects) {
if (currentAssociatedObjects.length === 0) {
newInstance.set(association.identifier, instance.get(instance.Model.primaryKeyAttribute));
if (association.scope) {
Object.keys(association.scope).forEach(function (attribute) {
newInstance.set(attribute, association.scope[attribute]);
});
}
return newInstance.save(options);
} else {
return Utils.Promise.resolve(currentAssociatedObjects[0]);
}
obj[this.accessors.remove] = function(oldAssociatedObject, options) {
var instance = this;
return instance[association.accessors.get]({
scope: false
}, options).then(function(currentAssociatedObjects) {
var newAssociations = [];
if (!(oldAssociatedObject instanceof association.target.Instance)) {
var tmpInstance = {};
tmpInstance[primaryKeyAttribute] = oldAssociatedObject;
oldAssociatedObject = association.target.build(tmpInstance, {
isNewRecord: false
});
}
};
obj[this.accessors.remove] = function(oldAssociatedObject, options) {
var instance = this;
return instance[association.accessors.get]({
scope: false
}, options).then(function(currentAssociatedObjects) {
var newAssociations = [];
currentAssociatedObjects.forEach(function(association) {
if (!Utils._.isEqual(oldAssociatedObject.where(), association.where())) {
newAssociations.push(association);
}
});
return instance[association.accessors.set](newAssociations, options);
});
};
obj[this.accessors.removeMultiple] = function(oldAssociatedObjects, options) {
var instance = this;
return instance[association.accessors.get]({
scope: false
}, options).then(function(currentAssociatedObjects) {
var newAssociations = [];
// Ensure the oldAssociatedObjects array is an array of target instances
oldAssociatedObjects = oldAssociatedObjects.map(function(oldAssociatedObject) {
if (!(oldAssociatedObject instanceof association.target.Instance)) {
var tmpInstance = {};
tmpInstance[primaryKeyAttribute] = oldAssociatedObject;
......@@ -291,88 +317,60 @@ module.exports = (function() {
isNewRecord: false
});
}
currentAssociatedObjects.forEach(function(association) {
if (!Utils._.isEqual(oldAssociatedObject.where(), association.where())) {
newAssociations.push(association);
}
});
return instance[association.accessors.set](newAssociations, options);
return oldAssociatedObject;
});
};
obj[this.accessors.removeMultiple] = function(oldAssociatedObjects, options) {
var instance = this;
return instance[association.accessors.get]({
scope: false
}, options).then(function(currentAssociatedObjects) {
var newAssociations = [];
// Ensure the oldAssociatedObjects array is an array of target instances
oldAssociatedObjects = oldAssociatedObjects.map(function(oldAssociatedObject) {
if (!(oldAssociatedObject instanceof association.target.Instance)) {
var tmpInstance = {};
tmpInstance[primaryKeyAttribute] = oldAssociatedObject;
oldAssociatedObject = association.target.build(tmpInstance, {
isNewRecord: false
});
}
return oldAssociatedObject;
});
currentAssociatedObjects.forEach(function(association) {
// Determine is this is an association we want to remove
var obj = Utils._.find(oldAssociatedObjects, function(oldAssociatedObject) {
return Utils._.isEqual(oldAssociatedObject.where(), association.where());
});
currentAssociatedObjects.forEach(function(association) {
// This is not an association we want to remove. Add it back
// to the set of associations we will associate our instance with
if (!obj) {
newAssociations.push(association);
}
// Determine is this is an association we want to remove
var obj = Utils._.find(oldAssociatedObjects, function(oldAssociatedObject) {
return Utils._.isEqual(oldAssociatedObject.where(), association.where());
});
return instance[association.accessors.set](newAssociations, options);
// This is not an association we want to remove. Add it back
// to the set of associations we will associate our instance with
if (!obj) {
newAssociations.push(association);
}
});
};
return this;
return instance[association.accessors.set](newAssociations, options);
});
};
HasMany.prototype.injectCreator = function(obj) {
var association = this;
return this;
};
obj[this.accessors.create] = function(values, options) {
var instance = this;
options = options || {};
HasMany.prototype.injectCreator = function(obj) {
var association = this;
if (Array.isArray(options)) {
options = {
fields: options
};
}
obj[this.accessors.create] = function(values, options) {
var instance = this;
options = options || {};
if (values === undefined) {
values = {};
}
if (Array.isArray(options)) {
options = {
fields: options
};
}
if (association.scope) {
Object.keys(association.scope).forEach(function (attribute) {
values[attribute] = association.scope[attribute];
if (options.fields) options.fields.push(attribute);
});
}
if (values === undefined) {
values = {};
}
values[association.identifier] = instance.get(association.source.primaryKeyAttribute);
if (options.fields) options.fields.push(association.identifier);
return association.target.create(values, options);
};
if (association.scope) {
Object.keys(association.scope).forEach(function (attribute) {
values[attribute] = association.scope[attribute];
if (options.fields) options.fields.push(attribute);
});
}
return this;
values[association.identifier] = instance.get(association.source.primaryKeyAttribute);
if (options.fields) options.fields.push(association.identifier);
return association.target.create(values, options);
};
return HasMany;
})();
return this;
};
module.exports = HasMany;
......@@ -5,178 +5,176 @@ var Utils = require('./../utils')
, Association = require('./base')
, util = require('util');
module.exports = (function() {
var HasOne = function(srcModel, targetModel, options) {
Association.call(this);
this.associationType = 'HasOne';
this.source = srcModel;
this.target = targetModel;
this.options = options;
this.isSingleAssociation = true;
this.isSelfAssociation = (this.source === this.target);
this.as = this.options.as;
if (Utils._.isObject(this.options.foreignKey)) {
this.foreignKeyAttribute = this.options.foreignKey;
this.foreignKey = this.foreignKeyAttribute.name || this.foreignKeyAttribute.fieldName;
} else {
this.foreignKeyAttribute = {};
this.foreignKey = this.options.foreignKey;
}
if (this.as) {
this.isAliased = true;
this.options.name = {
singular: this.as
};
} else {
this.as = this.target.options.name.singular;
this.options.name = this.target.options.name;
}
if (!this.options.foreignKey) {
this.options.foreignKey = Utils._.camelizeIf(
[
Utils._.underscoredIf(Utils.singularize(this.source.name), this.target.options.underscored),
this.source.primaryKeyAttribute
].join('_'),
!this.source.options.underscored
);
}
var HasOne = function(srcModel, targetModel, options) {
Association.call(this);
this.associationType = 'HasOne';
this.source = srcModel;
this.target = targetModel;
this.options = options;
this.isSingleAssociation = true;
this.isSelfAssociation = (this.source === this.target);
this.as = this.options.as;
if (Utils._.isObject(this.options.foreignKey)) {
this.foreignKeyAttribute = this.options.foreignKey;
this.foreignKey = this.foreignKeyAttribute.name || this.foreignKeyAttribute.fieldName;
} else {
this.foreignKeyAttribute = {};
this.foreignKey = this.options.foreignKey;
}
if (this.as) {
this.isAliased = true;
this.options.name = {
singular: this.as
};
} else {
this.as = this.target.options.name.singular;
this.options.name = this.target.options.name;
}
this.identifier = this.foreignKey || Utils._.camelizeIf(
if (!this.options.foreignKey) {
this.options.foreignKey = Utils._.camelizeIf(
[
Utils._.underscoredIf(this.source.options.name.singular, this.source.options.underscored),
Utils._.underscoredIf(Utils.singularize(this.source.name), this.target.options.underscored),
this.source.primaryKeyAttribute
].join('_'),
!this.source.options.underscored
);
this.sourceIdentifier = this.source.primaryKeyAttribute;
this.associationAccessor = this.as;
this.options.useHooks = options.useHooks;
// Get singular name, trying to uppercase the first letter, unless the model forbids it
var singular = Utils.uppercaseFirst(this.options.name.singular);
this.accessors = {
get: 'get' + singular,
set: 'set' + singular,
create: 'create' + singular
};
}
this.identifier = this.foreignKey || Utils._.camelizeIf(
[
Utils._.underscoredIf(this.source.options.name.singular, this.source.options.underscored),
this.source.primaryKeyAttribute
].join('_'),
!this.source.options.underscored
);
this.sourceIdentifier = this.source.primaryKeyAttribute;
this.associationAccessor = this.as;
this.options.useHooks = options.useHooks;
// Get singular name, trying to uppercase the first letter, unless the model forbids it
var singular = Utils.uppercaseFirst(this.options.name.singular);
this.accessors = {
get: 'get' + singular,
set: 'set' + singular,
create: 'create' + singular
};
};
util.inherits(HasOne, Association);
util.inherits(HasOne, Association);
// the id is in the target table
HasOne.prototype.injectAttributes = function() {
var newAttributes = {}
, keyType = this.source.rawAttributes[this.sourceIdentifier].type;
// the id is in the target table
HasOne.prototype.injectAttributes = function() {
var newAttributes = {}
, keyType = this.source.rawAttributes[this.sourceIdentifier].type;
newAttributes[this.identifier] = Utils._.defaults(this.foreignKeyAttribute, { type: this.options.keyType || keyType });
Utils.mergeDefaults(this.target.rawAttributes, newAttributes);
newAttributes[this.identifier] = Utils._.defaults(this.foreignKeyAttribute, { type: this.options.keyType || keyType });
Utils.mergeDefaults(this.target.rawAttributes, newAttributes);
this.identifierField = this.target.rawAttributes[this.identifier].field || this.identifier;
this.identifierField = this.target.rawAttributes[this.identifier].field || this.identifier;
if (this.options.constraints !== false) {
this.options.onDelete = this.options.onDelete || 'SET NULL';
this.options.onUpdate = this.options.onUpdate || 'CASCADE';
}
Helpers.addForeignKeyConstraints(this.target.rawAttributes[this.identifier], this.source, this.target, this.options);
if (this.options.constraints !== false) {
this.options.onDelete = this.options.onDelete || 'SET NULL';
this.options.onUpdate = this.options.onUpdate || 'CASCADE';
}
Helpers.addForeignKeyConstraints(this.target.rawAttributes[this.identifier], this.source, this.target, this.options);
// Sync attributes and setters/getters to Model prototype
this.target.refreshAttributes();
// Sync attributes and setters/getters to Model prototype
this.target.refreshAttributes();
Helpers.checkNamingCollision(this);
Helpers.checkNamingCollision(this);
return this;
};
return this;
};
HasOne.prototype.injectGetter = function(instancePrototype) {
var association = this;
HasOne.prototype.injectGetter = function(instancePrototype) {
var association = this;
instancePrototype[this.accessors.get] = function(options) {
var where = {};
where[association.identifier] = this.get(association.sourceIdentifier);
instancePrototype[this.accessors.get] = function(options) {
var where = {};
where[association.identifier] = this.get(association.sourceIdentifier);
options = association.target.__optClone(options) || {};
options = association.target.__optClone(options) || {};
options.where = {
$and: [
options.where,
where
]
};
options.where = {
$and: [
options.where,
where
]
};
if (options.limit === undefined) options.limit = null;
if (options.limit === undefined) options.limit = null;
var model = association.target;
if (options.hasOwnProperty('scope')) {
if (!options.scope) {
model = model.unscoped();
} else {
model = model.scope(options.scope);
}
var model = association.target;
if (options.hasOwnProperty('scope')) {
if (!options.scope) {
model = model.unscoped();
} else {
model = model.scope(options.scope);
}
}
return model.find(options);
};
return this;
return model.find(options);
};
HasOne.prototype.injectSetter = function(instancePrototype) {
var association = this;
instancePrototype[this.accessors.set] = function(associatedInstance, options) {
var instance = this;
options = options || {};
options.scope = false;
return instance[association.accessors.get](options).then(function(oldInstance) {
if (oldInstance) {
oldInstance[association.identifier] = null;
return oldInstance.save(Utils._.extend({}, options, {
fields: [association.identifier],
allowNull: [association.identifier],
association: true
}));
}
}).then(function() {
if (associatedInstance) {
if (!(associatedInstance instanceof association.target.Instance)) {
var tmpInstance = {};
tmpInstance[association.target.primaryKeyAttribute] = associatedInstance;
associatedInstance = association.target.build(tmpInstance, {
isNewRecord: false
});
}
associatedInstance.set(association.identifier, instance.get(association.sourceIdentifier));
return associatedInstance.save(options);
return this;
};
HasOne.prototype.injectSetter = function(instancePrototype) {
var association = this;
instancePrototype[this.accessors.set] = function(associatedInstance, options) {
var instance = this;
options = options || {};
options.scope = false;
return instance[association.accessors.get](options).then(function(oldInstance) {
if (oldInstance) {
oldInstance[association.identifier] = null;
return oldInstance.save(Utils._.extend({}, options, {
fields: [association.identifier],
allowNull: [association.identifier],
association: true
}));
}
}).then(function() {
if (associatedInstance) {
if (!(associatedInstance instanceof association.target.Instance)) {
var tmpInstance = {};
tmpInstance[association.target.primaryKeyAttribute] = associatedInstance;
associatedInstance = association.target.build(tmpInstance, {
isNewRecord: false
});
}
return null;
});
};
return this;
associatedInstance.set(association.identifier, instance.get(association.sourceIdentifier));
return associatedInstance.save(options);
}
return null;
});
};
HasOne.prototype.injectCreator = function(instancePrototype) {
var association = this;
return this;
};
instancePrototype[this.accessors.create] = function(values, options) {
var instance = this;
values = values || {};
options = options || {};
HasOne.prototype.injectCreator = function(instancePrototype) {
var association = this;
values[association.identifier] = instance.get(association.sourceIdentifier);
if (options.fields) options.fields.push(association.identifier);
return association.target.create(values, options);
};
instancePrototype[this.accessors.create] = function(values, options) {
var instance = this;
values = values || {};
options = options || {};
return this;
values[association.identifier] = instance.get(association.sourceIdentifier);
if (options.fields) options.fields.push(association.identifier);
return association.target.create(values, options);
};
return HasOne;
})();
return this;
};
module.exports = HasOne;
......@@ -2,48 +2,50 @@
var Utils = require('./../utils');
module.exports = {
checkNamingCollision: function (association) {
if (association.source.rawAttributes.hasOwnProperty(association.as)) {
throw new Error(
'Naming collision between attribute \'' + association.as +
'\' and association \'' + association.as + '\' on model ' + association.source.name +
'. To remedy this, change either foreignKey or as in your association definition'
);
}
},
addForeignKeyConstraints: function(newAttribute, source, target, options) {
// FK constraints are opt-in: users must either set `foreignKeyConstraints`
// on the association, or request an `onDelete` or `onUpdate` behaviour
if (options.foreignKeyConstraint || options.onDelete || options.onUpdate) {
// Find primary keys: composite keys not supported with this approach
var primaryKeys = Utils._.chain(source.rawAttributes).keys()
.filter(function(key) { return source.rawAttributes[key].primaryKey; })
.map(function(key) { return source.rawAttributes[key].field || key; }).value();
if (primaryKeys.length === 1) {
if (!!source.options.schema) {
newAttribute.references = {
model: source.modelManager.sequelize.queryInterface.QueryGenerator.addSchema({
tableName: source.tableName,
options: {
schema: source.options.schema,
schemaDelimiter: source.options.schemaDelimiter
}
})
};
} else {
newAttribute.references = { model: source.tableName };
}
newAttribute.references.key = primaryKeys[0];
newAttribute.onDelete = options.onDelete;
newAttribute.onUpdate = options.onUpdate;
function checkNamingCollision (association) {
if (association.source.rawAttributes.hasOwnProperty(association.as)) {
throw new Error(
'Naming collision between attribute \'' + association.as +
'\' and association \'' + association.as + '\' on model ' + association.source.name +
'. To remedy this, change either foreignKey or as in your association definition'
);
}
}
function addForeignKeyConstraints (newAttribute, source, target, options) {
// FK constraints are opt-in: users must either set `foreignKeyConstraints`
// on the association, or request an `onDelete` or `onUpdate` behaviour
if (options.foreignKeyConstraint || options.onDelete || options.onUpdate) {
// Find primary keys: composite keys not supported with this approach
var primaryKeys = Utils._.chain(source.rawAttributes).keys()
.filter(function(key) { return source.rawAttributes[key].primaryKey; })
.map(function(key) { return source.rawAttributes[key].field || key; }).value();
if (primaryKeys.length === 1) {
if (!!source.options.schema) {
newAttribute.references = {
model: source.modelManager.sequelize.queryInterface.QueryGenerator.addSchema({
tableName: source.tableName,
options: {
schema: source.options.schema,
schemaDelimiter: source.options.schemaDelimiter
}
})
};
} else {
newAttribute.references = { model: source.tableName };
}
newAttribute.references.key = primaryKeys[0];
newAttribute.onDelete = options.onDelete;
newAttribute.onUpdate = options.onUpdate;
}
}
}
module.exports = {
checkNamingCollision: checkNamingCollision,
addForeignKeyConstraints: addForeignKeyConstraints
};
This diff could not be displayed because it is too large.
......@@ -4,574 +4,571 @@ var Utils = require('../../utils')
, Dot = require('dottie')
, QueryTypes = require('../../query-types');
module.exports = (function() {
var AbstractQuery = function(database, sequelize, options) {};
/**
* Execute the passed sql query.
*
* Examples:
*
* query.run('SELECT 1')
*
* @param {String} sql - The SQL query which should be executed.
* @api public
*/
AbstractQuery.prototype.run = function() {
throw new Error('The run method wasn\'t overwritten!');
};
/**
* Check the logging option of the instance and print deprecation warnings.
*
* @return {void}
*/
AbstractQuery.prototype.checkLoggingOption = function() {
if (this.options.logging === true) {
console.log('DEPRECATION WARNING: The logging-option should be either a function or false. Default: console.log');
this.options.logging = console.log;
var AbstractQuery = function(database, sequelize, options) {};
/**
The function takes the result of the query execution and groups
the associated data by the callee.
Example:
groupJoinData([
{
some: 'data',
id: 1,
association: { foo: 'bar', id: 1 }
}, {
some: 'data',
id: 1,
association: { foo: 'bar', id: 2 }
}, {
some: 'data',
id: 1,
association: { foo: 'bar', id: 3 }
}
])
Result:
Something like this:
[
{
some: 'data',
id: 1,
association: [
{ foo: 'bar', id: 1 },
{ foo: 'bar', id: 2 },
{ foo: 'bar', id: 3 }
]
}
]
*/
/*
* Assumptions
* ID is not necessarily the first field
* All fields for a level is grouped in the same set (i.e. Panel.id, Task.id, Panel.title is not possible)
* Parent keys will be seen before any include/child keys
* Previous set won't necessarily be parent set (one parent could have two children, one child would then be previous set for the other)
*/
/*
* Author (MH) comment: This code is an unreadable mess, but its performant.
* groupJoinData is a performance critical function so we prioritize perf over readability.
*/
var groupJoinData = function(rows, includeOptions, options) {
if (!rows.length) {
return [];
}
var
// Generic looping
i
, length
, $i
, $length
// Row specific looping
, rowsI
, rowsLength = rows.length
, row
// Key specific looping
, keys
, key
, keyI
, keyLength
, prevKey
, values
, topValues
, topExists
, checkExisting = options.checkExisting
// If we don't have to deduplicate we can pre-allocate the resulting array
, results = checkExisting ? [] : new Array(rowsLength)
, resultMap = {}
, includeMap = {}
, itemHash
, parentHash
, topHash
// Result variables for the respective functions
, $keyPrefix
, $keyPrefixString
, $prevKeyPrefixString
, $prevKeyPrefix
, $lastKeyPrefix
, $current
, $parent
// Map each key to an include option
, previousPiece
, buildIncludeMap = function (piece) {
if ($current.includeMap[piece]) {
includeMap[key] = $current = $current.includeMap[piece];
if (previousPiece) {
previousPiece = previousPiece+'.'+piece;
} else {
previousPiece = piece;
}
includeMap[previousPiece] = $current;
}
}
if (this.options.logging === console.log) {
// using just console.log will break in node < 0.6
this.options.logging = function(s) { console.log(s); };
// Calcuate the last item in the array prefix ('Results' for 'User.Results.id')
, lastKeyPrefixMemo = {}
, lastKeyPrefix = function (key) {
if (!lastKeyPrefixMemo[key]) {
var prefix = keyPrefix(key)
, length = prefix.length;
lastKeyPrefixMemo[key] = !length ? '' : prefix[length - 1];
}
return lastKeyPrefixMemo[key];
}
};
/**
* Get the attributes of an insert query, which contains the just inserted id.
*
* @return {String} The field name.
*/
AbstractQuery.prototype.getInsertIdField = function() {
return 'insertId';
};
/**
* Iterate over all known tables and search their names inside the sql query.
* This method will also check association aliases ('as' option).
*
* @param {String} attribute An attribute of a SQL query. (?)
* @return {String} The found tableName / alias.
*/
AbstractQuery.prototype.findTableNameInAttribute = function(attribute) {
if (!this.options.include) {
return null;
// Calculate the string prefix of a key ('User.Results' for 'User.Results.id')
, keyPrefixStringMemo = {}
, keyPrefixString = function (key, memo) {
if (!memo[key]) {
memo[key] = key.substr(0, key.lastIndexOf('.'));
}
return memo[key];
}
if (!this.options.includeNames) {
this.options.includeNames = this.options.include.map(function(include) {
return include.as;
});
// Removes the prefix from a key ('id' for 'User.Results.id')
, removeKeyPrefixMemo = {}
, removeKeyPrefix = function (key) {
if (!removeKeyPrefixMemo[key]) {
var index = key.lastIndexOf('.');
removeKeyPrefixMemo[key] = key.substr(index === -1 ? 0 : index + 1);
}
return removeKeyPrefixMemo[key];
}
var tableNames = this.options.includeNames.filter(function(include) {
return attribute.indexOf(include + '.') === 0;
});
if (tableNames.length === 1) {
return tableNames[0];
} else {
return null;
// Calculates the array prefix of a key (['User', 'Results'] for 'User.Results.id')
, keyPrefixMemo = {}
, keyPrefix = function (key) {
// We use a double memo and keyPrefixString so that different keys with the same prefix will receive the same array instead of differnet arrays with equal values
if (!keyPrefixMemo[key]) {
var prefixString = keyPrefixString(key, keyPrefixStringMemo);
if (!keyPrefixMemo[prefixString]) {
keyPrefixMemo[prefixString] = prefixString ? prefixString.split('.') : [];
}
keyPrefixMemo[key] = keyPrefixMemo[prefixString];
}
return keyPrefixMemo[key];
}
};
AbstractQuery.prototype.isRawQuery = function () {
return this.options.type === QueryTypes.RAW;
};
, primaryKeyAttributes
, prefix;
AbstractQuery.prototype.isVersionQuery = function () {
return this.options.type === QueryTypes.VERSION;
};
for (rowsI = 0; rowsI < rowsLength; rowsI++) {
row = rows[rowsI];
AbstractQuery.prototype.isUpsertQuery = function () {
return this.options.type === QueryTypes.UPSERT;
};
// Keys are the same for all rows, so only need to compute them on the first row
if (rowsI === 0) {
keys = Object.keys(row);
keyLength = keys.length;
}
AbstractQuery.prototype.isInsertQuery = function(results, metaData) {
var result = true;
if (checkExisting) {
topExists = false;
if (this.options.type === QueryTypes.INSERT) {
return true;
// Compute top level hash key (this is usually just the primary key values)
$length = includeOptions.model.primaryKeyAttributes.length;
if ($length === 1) {
topHash = row[includeOptions.model.primaryKeyAttributes[0]];
} else {
topHash = '';
for ($i = 0; $i < $length; $i++) {
topHash += row[includeOptions.model.primaryKeyAttributes[$i]];
}
}
}
// is insert query if sql contains insert into
result = result && (this.sql.toLowerCase().indexOf('insert into') === 0);
topValues = values = {};
$prevKeyPrefix = undefined;
for (keyI = 0; keyI < keyLength; keyI++) {
key = keys[keyI];
// The string prefix isn't actualy needed
// We use it so keyPrefix for different keys will resolve to the same array if they have the same prefix
// TODO: Find a better way?
$keyPrefixString = keyPrefixString(key, keyPrefixStringMemo);
$keyPrefix = keyPrefix(key);
// On the first row we compute the includeMap
if (rowsI === 0 && includeMap[key] === undefined) {
if (!$keyPrefix.length) {
includeMap[key] = includeMap[''] = includeOptions;
} else {
$current = includeOptions;
previousPiece = undefined;
$keyPrefix.forEach(buildIncludeMap);
}
}
// is insert query if no results are passed or if the result has the inserted id
result = result && (!results || results.hasOwnProperty(this.getInsertIdField()));
// End of key set
if ($prevKeyPrefix !== undefined && $prevKeyPrefix !== $keyPrefix) {
if (checkExisting) {
// Compute hash key for this set instance
// TODO: Optimize
length = $prevKeyPrefix.length;
$parent = null;
parentHash = null;
if (length) {
for (i = 0; i < length; i++) {
prefix = $parent ? $parent+'.'+$prevKeyPrefix[i] : $prevKeyPrefix[i];
primaryKeyAttributes = includeMap[prefix].model.primaryKeyAttributes;
$length = primaryKeyAttributes.length;
if ($length === 1) {
itemHash = prefix+row[prefix+'.'+primaryKeyAttributes[0]];
} else {
itemHash = prefix;
for ($i = 0; $i < $length; $i++) {
itemHash += row[prefix+'.'+primaryKeyAttributes[$i]];
}
}
if (!parentHash) {
parentHash = topHash;
}
// is insert query if no metadata are passed or if the metadata has the inserted id
result = result && (!metaData || metaData.hasOwnProperty(this.getInsertIdField()));
itemHash = parentHash + itemHash;
$parent = prefix;
if (i < length - 1) {
parentHash = itemHash;
}
}
} else {
itemHash = topHash;
}
return result;
};
if (itemHash === topHash) {
if (!resultMap[itemHash]) {
resultMap[itemHash] = values;
} else {
topExists = true;
}
} else {
if (!resultMap[itemHash]) {
$parent = resultMap[parentHash];
$lastKeyPrefix = lastKeyPrefix(prevKey);
AbstractQuery.prototype.handleInsertQuery = function(results, metaData) {
if (this.instance) {
// add the inserted row id to the instance
var autoIncrementField = this.model.autoIncrementField
, id = null;
if (includeMap[prevKey].association.isSingleAssociation) {
$parent[$lastKeyPrefix] = resultMap[itemHash] = values;
} else {
if (!$parent[$lastKeyPrefix]) {
$parent[$lastKeyPrefix] = [];
}
$parent[$lastKeyPrefix].push(resultMap[itemHash] = values);
}
}
}
id = id || (results && results[this.getInsertIdField()]);
id = id || (metaData && metaData[this.getInsertIdField()]);
// Reset values
values = {};
} else {
// If checkExisting is false it's because there's only 1:1 associations in this query
// However we still need to map onto the appropriate parent
// For 1:1 we map forward, initializing the value object on the parent to be filled in the next iterations of the loop
$current = topValues;
length = $keyPrefix.length;
if (length) {
for (i = 0; i < length; i++) {
if (i === length -1) {
values = $current[$keyPrefix[i]] = {};
}
$current = $current[$keyPrefix[i]];
}
}
}
}
this.instance[autoIncrementField] = id;
// End of iteration, set value and set prev values (for next iteration)
values[removeKeyPrefix(key)] = row[key];
prevKey = key;
$prevKeyPrefix = $keyPrefix;
$prevKeyPrefixString = $keyPrefixString;
}
};
AbstractQuery.prototype.isShowTablesQuery = function() {
return this.options.type === QueryTypes.SHOWTABLES;
};
AbstractQuery.prototype.handleShowTablesQuery = function(results) {
return Utils._.flatten(results.map(function(resultSet) {
return Utils._.values(resultSet);
}));
};
if (checkExisting) {
length = $prevKeyPrefix.length;
$parent = null;
parentHash = null;
if (length) {
for (i = 0; i < length; i++) {
prefix = $parent ? $parent+'.'+$prevKeyPrefix[i] : $prevKeyPrefix[i];
primaryKeyAttributes = includeMap[prefix].model.primaryKeyAttributes;
$length = primaryKeyAttributes.length;
if ($length === 1) {
itemHash = prefix+row[prefix+'.'+primaryKeyAttributes[0]];
} else {
itemHash = prefix;
for ($i = 0; $i < $length; $i++) {
itemHash += row[prefix+'.'+primaryKeyAttributes[$i]];
}
}
if (!parentHash) {
parentHash = topHash;
}
AbstractQuery.prototype.isShowIndexesQuery = function () {
return this.options.type === QueryTypes.SHOWINDEXES;
};
itemHash = parentHash + itemHash;
$parent = prefix;
if (i < length - 1) {
parentHash = itemHash;
}
}
} else {
itemHash = topHash;
}
AbstractQuery.prototype.isDescribeQuery = function () {
return this.options.type === QueryTypes.DESCRIBE;
};
if (itemHash === topHash) {
if (!resultMap[itemHash]) {
resultMap[itemHash] = values;
} else {
topExists = true;
}
} else {
if (!resultMap[itemHash]) {
$parent = resultMap[parentHash];
$lastKeyPrefix = lastKeyPrefix(prevKey);
AbstractQuery.prototype.isSelectQuery = function() {
return this.options.type === QueryTypes.SELECT;
};
if (includeMap[prevKey].association.isSingleAssociation) {
$parent[$lastKeyPrefix] = resultMap[itemHash] = values;
} else {
if (!$parent[$lastKeyPrefix]) {
$parent[$lastKeyPrefix] = [];
}
$parent[$lastKeyPrefix].push(resultMap[itemHash] = values);
}
}
}
if (!topExists) {
results.push(topValues);
}
} else {
results[rowsI] = topValues;
}
}
return results;
};
/**
* Execute the passed sql query.
*
* Examples:
*
* query.run('SELECT 1')
*
* @param {String} sql - The SQL query which should be executed.
* @api public
*/
AbstractQuery.prototype.run = function() {
throw new Error('The run method wasn\'t overwritten!');
};
/**
* Check the logging option of the instance and print deprecation warnings.
*
* @return {void}
*/
AbstractQuery.prototype.checkLoggingOption = function() {
if (this.options.logging === true) {
console.log('DEPRECATION WARNING: The logging-option should be either a function or false. Default: console.log');
this.options.logging = console.log;
}
if (this.options.logging === console.log) {
// using just console.log will break in node < 0.6
this.options.logging = function(s) { console.log(s); };
}
};
/**
* Get the attributes of an insert query, which contains the just inserted id.
*
* @return {String} The field name.
*/
AbstractQuery.prototype.getInsertIdField = function() {
return 'insertId';
};
/**
* Iterate over all known tables and search their names inside the sql query.
* This method will also check association aliases ('as' option).
*
* @param {String} attribute An attribute of a SQL query. (?)
* @return {String} The found tableName / alias.
*/
AbstractQuery.prototype.findTableNameInAttribute = function(attribute) {
if (!this.options.include) {
return null;
}
if (!this.options.includeNames) {
this.options.includeNames = this.options.include.map(function(include) {
return include.as;
});
}
AbstractQuery.prototype.isBulkUpdateQuery = function() {
return this.options.type === QueryTypes.BULKUPDATE;
};
var tableNames = this.options.includeNames.filter(function(include) {
return attribute.indexOf(include + '.') === 0;
});
AbstractQuery.prototype.isBulkDeleteQuery = function() {
return this.options.type === QueryTypes.BULKDELETE;
};
if (tableNames.length === 1) {
return tableNames[0];
} else {
return null;
}
};
AbstractQuery.prototype.isForeignKeysQuery = function() {
return this.options.type === QueryTypes.FOREIGNKEYS;
};
AbstractQuery.prototype.isRawQuery = function () {
return this.options.type === QueryTypes.RAW;
};
AbstractQuery.prototype.isUpdateQuery = function() {
return this.options.type === QueryTypes.UPDATE;
};
AbstractQuery.prototype.isVersionQuery = function () {
return this.options.type === QueryTypes.VERSION;
};
AbstractQuery.prototype.handleSelectQuery = function(results) {
var result = null;
AbstractQuery.prototype.isUpsertQuery = function () {
return this.options.type === QueryTypes.UPSERT;
};
// Raw queries
if (this.options.raw) {
result = results.map(function(result) {
var o = {};
AbstractQuery.prototype.isInsertQuery = function(results, metaData) {
var result = true;
for (var key in result) {
if (result.hasOwnProperty(key)) {
o[key] = result[key];
}
}
if (this.options.type === QueryTypes.INSERT) {
return true;
}
if (this.options.nest) {
o = Dot.transform(o);
}
// is insert query if sql contains insert into
result = result && (this.sql.toLowerCase().indexOf('insert into') === 0);
return o;
}, this);
// Queries with include
} else if (this.options.hasJoin === true) {
results = groupJoinData(results, {
model: this.model,
includeMap: this.options.includeMap,
includeNames: this.options.includeNames
}, {
checkExisting: this.options.hasMultiAssociation
});
result = this.model.bulkBuild(results, {
isNewRecord: false,
include: this.options.include,
includeNames: this.options.includeNames,
includeMap: this.options.includeMap,
includeValidated: true,
attributes: this.options.originalAttributes || this.options.attributes,
raw: true
});
// Regular queries
} else {
result = this.model.bulkBuild(results, {
isNewRecord: false,
raw: true,
attributes: this.options.attributes
});
}
// is insert query if no results are passed or if the result has the inserted id
result = result && (!results || results.hasOwnProperty(this.getInsertIdField()));
// return the first real model instance if options.plain is set (e.g. Model.find)
if (this.options.plain) {
result = (result.length === 0) ? null : result[0];
}
// is insert query if no metadata are passed or if the metadata has the inserted id
result = result && (!metaData || metaData.hasOwnProperty(this.getInsertIdField()));
return result;
};
return result;
};
AbstractQuery.prototype.isShowOrDescribeQuery = function() {
var result = false;
AbstractQuery.prototype.handleInsertQuery = function(results, metaData) {
if (this.instance) {
// add the inserted row id to the instance
var autoIncrementField = this.model.autoIncrementField
, id = null;
result = result || (this.sql.toLowerCase().indexOf('show') === 0);
result = result || (this.sql.toLowerCase().indexOf('describe') === 0);
id = id || (results && results[this.getInsertIdField()]);
id = id || (metaData && metaData[this.getInsertIdField()]);
return result;
};
this.instance[autoIncrementField] = id;
}
};
AbstractQuery.prototype.isCallQuery = function() {
var result = false;
AbstractQuery.prototype.isShowTablesQuery = function() {
return this.options.type === QueryTypes.SHOWTABLES;
};
result = result || (this.sql.toLowerCase().indexOf('call') === 0);
AbstractQuery.prototype.handleShowTablesQuery = function(results) {
return Utils._.flatten(results.map(function(resultSet) {
return Utils._.values(resultSet);
}));
};
return result;
};
AbstractQuery.prototype.isShowIndexesQuery = function () {
return this.options.type === QueryTypes.SHOWINDEXES;
};
AbstractQuery.prototype.isDescribeQuery = function () {
return this.options.type === QueryTypes.DESCRIBE;
};
/**
The function takes the result of the query execution and groups
the associated data by the callee.
AbstractQuery.prototype.isSelectQuery = function() {
return this.options.type === QueryTypes.SELECT;
};
Example:
groupJoinData([
{
some: 'data',
id: 1,
association: { foo: 'bar', id: 1 }
}, {
some: 'data',
id: 1,
association: { foo: 'bar', id: 2 }
}, {
some: 'data',
id: 1,
association: { foo: 'bar', id: 3 }
}
])
Result:
Something like this:
[
{
some: 'data',
id: 1,
association: [
{ foo: 'bar', id: 1 },
{ foo: 'bar', id: 2 },
{ foo: 'bar', id: 3 }
]
}
]
*/
/*
* Assumptions
* ID is not necessarily the first field
* All fields for a level is grouped in the same set (i.e. Panel.id, Task.id, Panel.title is not possible)
* Parent keys will be seen before any include/child keys
* Previous set won't necessarily be parent set (one parent could have two children, one child would then be previous set for the other)
*/
/*
* Author (MH) comment: This code is an unreadable mess, but its performant.
* groupJoinData is a performance critical function so we prioritize perf over readability.
*/
var groupJoinData = function(rows, includeOptions, options) {
if (!rows.length) {
return [];
}
AbstractQuery.prototype.isBulkUpdateQuery = function() {
return this.options.type === QueryTypes.BULKUPDATE;
};
var
// Generic looping
i
, length
, $i
, $length
// Row specific looping
, rowsI
, rowsLength = rows.length
, row
// Key specific looping
, keys
, key
, keyI
, keyLength
, prevKey
, values
, topValues
, topExists
, checkExisting = options.checkExisting
// If we don't have to deduplicate we can pre-allocate the resulting array
, results = checkExisting ? [] : new Array(rowsLength)
, resultMap = {}
, includeMap = {}
, itemHash
, parentHash
, topHash
// Result variables for the respective functions
, $keyPrefix
, $keyPrefixString
, $prevKeyPrefixString
, $prevKeyPrefix
, $lastKeyPrefix
, $current
, $parent
// Map each key to an include option
, previousPiece
, buildIncludeMap = function (piece) {
if ($current.includeMap[piece]) {
includeMap[key] = $current = $current.includeMap[piece];
if (previousPiece) {
previousPiece = previousPiece+'.'+piece;
} else {
previousPiece = piece;
}
includeMap[previousPiece] = $current;
}
}
// Calcuate the last item in the array prefix ('Results' for 'User.Results.id')
, lastKeyPrefixMemo = {}
, lastKeyPrefix = function (key) {
if (!lastKeyPrefixMemo[key]) {
var prefix = keyPrefix(key)
, length = prefix.length;
lastKeyPrefixMemo[key] = !length ? '' : prefix[length - 1];
}
return lastKeyPrefixMemo[key];
}
// Calculate the string prefix of a key ('User.Results' for 'User.Results.id')
, keyPrefixStringMemo = {}
, keyPrefixString = function (key, memo) {
if (!memo[key]) {
memo[key] = key.substr(0, key.lastIndexOf('.'));
}
return memo[key];
}
// Removes the prefix from a key ('id' for 'User.Results.id')
, removeKeyPrefixMemo = {}
, removeKeyPrefix = function (key) {
if (!removeKeyPrefixMemo[key]) {
var index = key.lastIndexOf('.');
removeKeyPrefixMemo[key] = key.substr(index === -1 ? 0 : index + 1);
}
return removeKeyPrefixMemo[key];
}
// Calculates the array prefix of a key (['User', 'Results'] for 'User.Results.id')
, keyPrefixMemo = {}
, keyPrefix = function (key) {
// We use a double memo and keyPrefixString so that different keys with the same prefix will receive the same array instead of differnet arrays with equal values
if (!keyPrefixMemo[key]) {
var prefixString = keyPrefixString(key, keyPrefixStringMemo);
if (!keyPrefixMemo[prefixString]) {
keyPrefixMemo[prefixString] = prefixString ? prefixString.split('.') : [];
}
keyPrefixMemo[key] = keyPrefixMemo[prefixString];
}
return keyPrefixMemo[key];
}
, primaryKeyAttributes
, prefix;
AbstractQuery.prototype.isBulkDeleteQuery = function() {
return this.options.type === QueryTypes.BULKDELETE;
};
for (rowsI = 0; rowsI < rowsLength; rowsI++) {
row = rows[rowsI];
AbstractQuery.prototype.isForeignKeysQuery = function() {
return this.options.type === QueryTypes.FOREIGNKEYS;
};
// Keys are the same for all rows, so only need to compute them on the first row
if (rowsI === 0) {
keys = Object.keys(row);
keyLength = keys.length;
}
AbstractQuery.prototype.isUpdateQuery = function() {
return this.options.type === QueryTypes.UPDATE;
};
if (checkExisting) {
topExists = false;
AbstractQuery.prototype.handleSelectQuery = function(results) {
var result = null;
// Compute top level hash key (this is usually just the primary key values)
$length = includeOptions.model.primaryKeyAttributes.length;
if ($length === 1) {
topHash = row[includeOptions.model.primaryKeyAttributes[0]];
} else {
topHash = '';
for ($i = 0; $i < $length; $i++) {
topHash += row[includeOptions.model.primaryKeyAttributes[$i]];
}
// Raw queries
if (this.options.raw) {
result = results.map(function(result) {
var o = {};
for (var key in result) {
if (result.hasOwnProperty(key)) {
o[key] = result[key];
}
}
topValues = values = {};
$prevKeyPrefix = undefined;
for (keyI = 0; keyI < keyLength; keyI++) {
key = keys[keyI];
// The string prefix isn't actualy needed
// We use it so keyPrefix for different keys will resolve to the same array if they have the same prefix
// TODO: Find a better way?
$keyPrefixString = keyPrefixString(key, keyPrefixStringMemo);
$keyPrefix = keyPrefix(key);
// On the first row we compute the includeMap
if (rowsI === 0 && includeMap[key] === undefined) {
if (!$keyPrefix.length) {
includeMap[key] = includeMap[''] = includeOptions;
} else {
$current = includeOptions;
previousPiece = undefined;
$keyPrefix.forEach(buildIncludeMap);
}
}
if (this.options.nest) {
o = Dot.transform(o);
}
// End of key set
if ($prevKeyPrefix !== undefined && $prevKeyPrefix !== $keyPrefix) {
if (checkExisting) {
// Compute hash key for this set instance
// TODO: Optimize
length = $prevKeyPrefix.length;
$parent = null;
parentHash = null;
if (length) {
for (i = 0; i < length; i++) {
prefix = $parent ? $parent+'.'+$prevKeyPrefix[i] : $prevKeyPrefix[i];
primaryKeyAttributes = includeMap[prefix].model.primaryKeyAttributes;
$length = primaryKeyAttributes.length;
if ($length === 1) {
itemHash = prefix+row[prefix+'.'+primaryKeyAttributes[0]];
} else {
itemHash = prefix;
for ($i = 0; $i < $length; $i++) {
itemHash += row[prefix+'.'+primaryKeyAttributes[$i]];
}
}
if (!parentHash) {
parentHash = topHash;
}
return o;
}, this);
// Queries with include
} else if (this.options.hasJoin === true) {
results = groupJoinData(results, {
model: this.model,
includeMap: this.options.includeMap,
includeNames: this.options.includeNames
}, {
checkExisting: this.options.hasMultiAssociation
});
itemHash = parentHash + itemHash;
$parent = prefix;
if (i < length - 1) {
parentHash = itemHash;
}
}
} else {
itemHash = topHash;
}
result = this.model.bulkBuild(results, {
isNewRecord: false,
include: this.options.include,
includeNames: this.options.includeNames,
includeMap: this.options.includeMap,
includeValidated: true,
attributes: this.options.originalAttributes || this.options.attributes,
raw: true
});
// Regular queries
} else {
result = this.model.bulkBuild(results, {
isNewRecord: false,
raw: true,
attributes: this.options.attributes
});
}
if (itemHash === topHash) {
if (!resultMap[itemHash]) {
resultMap[itemHash] = values;
} else {
topExists = true;
}
} else {
if (!resultMap[itemHash]) {
$parent = resultMap[parentHash];
$lastKeyPrefix = lastKeyPrefix(prevKey);
if (includeMap[prevKey].association.isSingleAssociation) {
$parent[$lastKeyPrefix] = resultMap[itemHash] = values;
} else {
if (!$parent[$lastKeyPrefix]) {
$parent[$lastKeyPrefix] = [];
}
$parent[$lastKeyPrefix].push(resultMap[itemHash] = values);
}
}
}
// return the first real model instance if options.plain is set (e.g. Model.find)
if (this.options.plain) {
result = (result.length === 0) ? null : result[0];
}
// Reset values
values = {};
} else {
// If checkExisting is false it's because there's only 1:1 associations in this query
// However we still need to map onto the appropriate parent
// For 1:1 we map forward, initializing the value object on the parent to be filled in the next iterations of the loop
$current = topValues;
length = $keyPrefix.length;
if (length) {
for (i = 0; i < length; i++) {
if (i === length -1) {
values = $current[$keyPrefix[i]] = {};
}
$current = $current[$keyPrefix[i]];
}
}
}
}
return result;
};
// End of iteration, set value and set prev values (for next iteration)
values[removeKeyPrefix(key)] = row[key];
prevKey = key;
$prevKeyPrefix = $keyPrefix;
$prevKeyPrefixString = $keyPrefixString;
}
AbstractQuery.prototype.isShowOrDescribeQuery = function() {
var result = false;
if (checkExisting) {
length = $prevKeyPrefix.length;
$parent = null;
parentHash = null;
if (length) {
for (i = 0; i < length; i++) {
prefix = $parent ? $parent+'.'+$prevKeyPrefix[i] : $prevKeyPrefix[i];
primaryKeyAttributes = includeMap[prefix].model.primaryKeyAttributes;
$length = primaryKeyAttributes.length;
if ($length === 1) {
itemHash = prefix+row[prefix+'.'+primaryKeyAttributes[0]];
} else {
itemHash = prefix;
for ($i = 0; $i < $length; $i++) {
itemHash += row[prefix+'.'+primaryKeyAttributes[$i]];
}
}
if (!parentHash) {
parentHash = topHash;
}
result = result || (this.sql.toLowerCase().indexOf('show') === 0);
result = result || (this.sql.toLowerCase().indexOf('describe') === 0);
itemHash = parentHash + itemHash;
$parent = prefix;
if (i < length - 1) {
parentHash = itemHash;
}
}
} else {
itemHash = topHash;
}
return result;
};
if (itemHash === topHash) {
if (!resultMap[itemHash]) {
resultMap[itemHash] = values;
} else {
topExists = true;
}
} else {
if (!resultMap[itemHash]) {
$parent = resultMap[parentHash];
$lastKeyPrefix = lastKeyPrefix(prevKey);
AbstractQuery.prototype.isCallQuery = function() {
var result = false;
if (includeMap[prevKey].association.isSingleAssociation) {
$parent[$lastKeyPrefix] = resultMap[itemHash] = values;
} else {
if (!$parent[$lastKeyPrefix]) {
$parent[$lastKeyPrefix] = [];
}
$parent[$lastKeyPrefix].push(resultMap[itemHash] = values);
}
}
}
if (!topExists) {
results.push(topValues);
}
} else {
results[rowsI] = topValues;
}
}
result = result || (this.sql.toLowerCase().indexOf('call') === 0);
return results;
};
return result;
};
AbstractQuery.$groupJoinData = groupJoinData;
AbstractQuery.$groupJoinData = groupJoinData;
return AbstractQuery;
})();
module.exports = AbstractQuery;
......@@ -6,606 +6,604 @@ var Utils = require('../../utils')
, Model = require('../../model')
, AbstractQueryGenerator = require('../abstract/query-generator');
module.exports = (function() {
var QueryGenerator = {
options: {},
dialect: 'mssql',
createSchema: function(schema) {
return [
'IF NOT EXISTS (SELECT schema_name',
'FROM information_schema.schemata',
'WHERE schema_name =', wrapSingleQuote(schema), ')',
'BEGIN',
"EXEC sp_executesql N'CREATE SCHEMA",
this.quoteIdentifier(schema),
";'",
'END;'
].join(' ');
},
showSchemasQuery: function() {
return [
'SELECT "name" as "schema_name" FROM sys.schemas as s',
'WHERE "s"."name" NOT IN (',
"'INFORMATION_SCHEMA', 'dbo', 'guest', 'sys', 'archive'",
')', 'AND', '"s"."name" NOT LIKE', "'db_%'"
].join(' ');
},
versionQuery: function() {
return "SELECT @@VERSION as 'version'";
},
createTableQuery: function(tableName, attributes, options) {
var query = "IF OBJECT_ID('<%= table %>', 'U') IS NULL CREATE TABLE <%= table %> (<%= attributes %>)"
, primaryKeys = []
, foreignKeys = {}
, attrStr = []
, self = this;
for (var attr in attributes) {
if (attributes.hasOwnProperty(attr)) {
var dataType = attributes[attr]
, match;
if (Utils._.includes(dataType, 'PRIMARY KEY')) {
primaryKeys.push(attr);
if (Utils._.includes(dataType, 'REFERENCES')) {
// MSSQL doesn't support inline REFERENCES declarations: move to the end
match = dataType.match(/^(.+) (REFERENCES.*)$/);
attrStr.push(this.quoteIdentifier(attr) + ' ' + match[1].replace(/PRIMARY KEY/, ''));
foreignKeys[attr] = match[2];
} else {
attrStr.push(this.quoteIdentifier(attr) + ' ' + dataType.replace(/PRIMARY KEY/, ''));
}
} else if (Utils._.includes(dataType, 'REFERENCES')) {
// MSSQL doesn't support inline REFERENCES declarations: move to the end
/* istanbul ignore next */
var throwMethodUndefined = function(methodName) {
throw new Error('The method "' + methodName + '" is not defined! Please add it to your sql dialect.');
};
var QueryGenerator = {
options: {},
dialect: 'mssql',
createSchema: function(schema) {
return [
'IF NOT EXISTS (SELECT schema_name',
'FROM information_schema.schemata',
'WHERE schema_name =', wrapSingleQuote(schema), ')',
'BEGIN',
"EXEC sp_executesql N'CREATE SCHEMA",
this.quoteIdentifier(schema),
";'",
'END;'
].join(' ');
},
showSchemasQuery: function() {
return [
'SELECT "name" as "schema_name" FROM sys.schemas as s',
'WHERE "s"."name" NOT IN (',
"'INFORMATION_SCHEMA', 'dbo', 'guest', 'sys', 'archive'",
')', 'AND', '"s"."name" NOT LIKE', "'db_%'"
].join(' ');
},
versionQuery: function() {
return "SELECT @@VERSION as 'version'";
},
createTableQuery: function(tableName, attributes, options) {
var query = "IF OBJECT_ID('<%= table %>', 'U') IS NULL CREATE TABLE <%= table %> (<%= attributes %>)"
, primaryKeys = []
, foreignKeys = {}
, attrStr = []
, self = this;
for (var attr in attributes) {
if (attributes.hasOwnProperty(attr)) {
var dataType = attributes[attr]
, match;
if (Utils._.includes(dataType, 'PRIMARY KEY')) {
primaryKeys.push(attr);
if (Utils._.includes(dataType, 'REFERENCES')) {
// MSSQL doesn't support inline REFERENCES declarations: move to the end
match = dataType.match(/^(.+) (REFERENCES.*)$/);
attrStr.push(this.quoteIdentifier(attr) + ' ' + match[1]);
attrStr.push(this.quoteIdentifier(attr) + ' ' + match[1].replace(/PRIMARY KEY/, ''));
foreignKeys[attr] = match[2];
} else {
attrStr.push(this.quoteIdentifier(attr) + ' ' + dataType);
attrStr.push(this.quoteIdentifier(attr) + ' ' + dataType.replace(/PRIMARY KEY/, ''));
}
} else if (Utils._.includes(dataType, 'REFERENCES')) {
// MSSQL doesn't support inline REFERENCES declarations: move to the end
match = dataType.match(/^(.+) (REFERENCES.*)$/);
attrStr.push(this.quoteIdentifier(attr) + ' ' + match[1]);
foreignKeys[attr] = match[2];
} else {
attrStr.push(this.quoteIdentifier(attr) + ' ' + dataType);
}
}
}
var values = {
table: this.quoteTable(tableName),
attributes: attrStr.join(', '),
}
, pkString = primaryKeys.map(function(pk) { return this.quoteIdentifier(pk); }.bind(this)).join(', ');
if (!!options.uniqueKeys) {
Utils._.each(options.uniqueKeys, function(columns, indexName) {
if (!Utils._.isString(indexName)) {
indexName = 'uniq_' + tableName + '_' + columns.fields.join('_');
}
values.attributes += ', CONSTRAINT ' + self.quoteIdentifier(indexName) + ' UNIQUE (' + Utils._.map(columns.fields, self.quoteIdentifier).join(', ') + ')';
});
}
if (pkString.length > 0) {
values.attributes += ', PRIMARY KEY (' + pkString + ')';
}
var values = {
table: this.quoteTable(tableName),
attributes: attrStr.join(', '),
}
, pkString = primaryKeys.map(function(pk) { return this.quoteIdentifier(pk); }.bind(this)).join(', ');
for (var fkey in foreignKeys) {
if (foreignKeys.hasOwnProperty(fkey)) {
values.attributes += ', FOREIGN KEY (' + this.quoteIdentifier(fkey) + ') ' + foreignKeys[fkey];
if (!!options.uniqueKeys) {
Utils._.each(options.uniqueKeys, function(columns, indexName) {
if (!Utils._.isString(indexName)) {
indexName = 'uniq_' + tableName + '_' + columns.fields.join('_');
}
}
return Utils._.template(query)(values).trim() + ';';
},
describeTableQuery: function(tableName, schema) {
var sql = [
'SELECT',
"c.COLUMN_NAME AS 'Name',",
"c.DATA_TYPE AS 'Type',",
"c.IS_NULLABLE as 'IsNull',",
"COLUMN_DEFAULT AS 'Default'",
'FROM',
'INFORMATION_SCHEMA.TABLES t',
'INNER JOIN',
'INFORMATION_SCHEMA.COLUMNS c ON t.TABLE_NAME = c.TABLE_NAME',
'WHERE t.TABLE_NAME =', wrapSingleQuote(tableName)
].join(' ');
if (schema) {
sql += 'AND t.TABLE_SCHEMA =' + wrapSingleQuote(schema);
}
return sql;
},
renameTableQuery: function(before, after) {
var query = 'EXEC sp_rename <%= before %>, <%= after %>;';
return Utils._.template(query)({
before: this.quoteTable(before),
after: this.quoteTable(after)
values.attributes += ', CONSTRAINT ' + self.quoteIdentifier(indexName) + ' UNIQUE (' + Utils._.map(columns.fields, self.quoteIdentifier).join(', ') + ')';
});
},
}
showTablesQuery: function () {
return 'SELECT TABLE_NAME, TABLE_SCHEMA FROM INFORMATION_SCHEMA.TABLES;';
},
if (pkString.length > 0) {
values.attributes += ', PRIMARY KEY (' + pkString + ')';
}
dropTableQuery: function(tableName) {
var query = "IF OBJECT_ID('<%= table %>', 'U') IS NOT NULL DROP TABLE <%= table %>";
var values = {
table: this.quoteTable(tableName)
};
for (var fkey in foreignKeys) {
if (foreignKeys.hasOwnProperty(fkey)) {
values.attributes += ', FOREIGN KEY (' + this.quoteIdentifier(fkey) + ') ' + foreignKeys[fkey];
}
}
return Utils._.template(query)(values).trim() + ';';
},
addColumnQuery: function(table, key, dataType) {
// FIXME: attributeToSQL SHOULD be using attributes in addColumnQuery
// but instead we need to pass the key along as the field here
dataType.field = key;
var query = 'ALTER TABLE <%= table %> ADD <%= attribute %>;'
, attribute = Utils._.template('<%= key %> <%= definition %>')({
key: this.quoteIdentifier(key),
definition: this.attributeToSQL(dataType, {
context: 'addColumn'
})
});
return Utils._.template(query)({
table: this.quoteTable(table),
attribute: attribute
});
},
return Utils._.template(query)(values).trim() + ';';
},
describeTableQuery: function(tableName, schema) {
var sql = [
'SELECT',
"c.COLUMN_NAME AS 'Name',",
"c.DATA_TYPE AS 'Type',",
"c.IS_NULLABLE as 'IsNull',",
"COLUMN_DEFAULT AS 'Default'",
'FROM',
'INFORMATION_SCHEMA.TABLES t',
'INNER JOIN',
'INFORMATION_SCHEMA.COLUMNS c ON t.TABLE_NAME = c.TABLE_NAME',
'WHERE t.TABLE_NAME =', wrapSingleQuote(tableName)
].join(' ');
if (schema) {
sql += 'AND t.TABLE_SCHEMA =' + wrapSingleQuote(schema);
}
removeColumnQuery: function(tableName, attributeName) {
var query = 'ALTER TABLE <%= tableName %> DROP <%= attributeName %>;';
return Utils._.template(query)({
tableName: this.quoteTable(tableName),
attributeName: this.quoteIdentifier(attributeName)
return sql;
},
renameTableQuery: function(before, after) {
var query = 'EXEC sp_rename <%= before %>, <%= after %>;';
return Utils._.template(query)({
before: this.quoteTable(before),
after: this.quoteTable(after)
});
},
showTablesQuery: function () {
return 'SELECT TABLE_NAME, TABLE_SCHEMA FROM INFORMATION_SCHEMA.TABLES;';
},
dropTableQuery: function(tableName) {
var query = "IF OBJECT_ID('<%= table %>', 'U') IS NOT NULL DROP TABLE <%= table %>";
var values = {
table: this.quoteTable(tableName)
};
return Utils._.template(query)(values).trim() + ';';
},
addColumnQuery: function(table, key, dataType) {
// FIXME: attributeToSQL SHOULD be using attributes in addColumnQuery
// but instead we need to pass the key along as the field here
dataType.field = key;
var query = 'ALTER TABLE <%= table %> ADD <%= attribute %>;'
, attribute = Utils._.template('<%= key %> <%= definition %>')({
key: this.quoteIdentifier(key),
definition: this.attributeToSQL(dataType, {
context: 'addColumn'
})
});
},
changeColumnQuery: function(tableName, attributes) {
var query = 'ALTER TABLE <%= tableName %> ALTER COLUMN <%= attributes %>;';
var attrString = [];
return Utils._.template(query)({
table: this.quoteTable(table),
attribute: attribute
});
},
removeColumnQuery: function(tableName, attributeName) {
var query = 'ALTER TABLE <%= tableName %> DROP <%= attributeName %>;';
return Utils._.template(query)({
tableName: this.quoteTable(tableName),
attributeName: this.quoteIdentifier(attributeName)
});
},
changeColumnQuery: function(tableName, attributes) {
var query = 'ALTER TABLE <%= tableName %> ALTER COLUMN <%= attributes %>;';
var attrString = [];
for (var attrName in attributes) {
var definition = attributes[attrName];
attrString.push(Utils._.template('<%= attrName %> <%= definition %>')({
attrName: this.quoteIdentifier(attrName),
definition: definition
}));
}
for (var attrName in attributes) {
var definition = attributes[attrName];
return Utils._.template(query)({
tableName: this.quoteTable(tableName),
attributes: attrString.join(', ')
});
},
renameColumnQuery: function(tableName, attrBefore, attributes) {
var query = "EXEC sp_rename '<%= tableName %>.<%= before %>', '<%= after %>', 'COLUMN';"
, newName = Object.keys(attributes)[0];
return Utils._.template(query)({
tableName: this.quoteTable(tableName),
before: attrBefore,
after: newName
});
},
bulkInsertQuery: function(tableName, attrValueHashes, options, attributes) {
var query = 'INSERT INTO <%= table %> (<%= attributes %>)<%= output %> VALUES <%= tuples %>;'
, emptyQuery = 'INSERT INTO <%= table %><%= output %> DEFAULT VALUES'
, tuples = []
, allAttributes = []
, needIdentityInsertWrapper = false
, allQueries = []
, outputFragment;
if (options.returning) {
outputFragment = ' OUTPUT INSERTED.*';
}
attrString.push(Utils._.template('<%= attrName %> <%= definition %>')({
attrName: this.quoteIdentifier(attrName),
definition: definition
}));
Utils._.forEach(attrValueHashes, function(attrValueHash) {
// special case for empty objects with primary keys
var fields = Object.keys(attrValueHash);
if (fields.length === 1 && attributes[fields[0]].autoIncrement && attrValueHash[fields[0]] === null) {
allQueries.push(emptyQuery);
return;
}
return Utils._.template(query)({
tableName: this.quoteTable(tableName),
attributes: attrString.join(', ')
});
},
// normal case
Utils._.forOwn(attrValueHash, function(value, key) {
if (value !== null && attributes[key].autoIncrement) {
needIdentityInsertWrapper = true;
}
renameColumnQuery: function(tableName, attrBefore, attributes) {
var query = "EXEC sp_rename '<%= tableName %>.<%= before %>', '<%= after %>', 'COLUMN';"
, newName = Object.keys(attributes)[0];
if (allAttributes.indexOf(key) === -1) {
if (value === null && attributes[key].autoIncrement)
return;
return Utils._.template(query)({
tableName: this.quoteTable(tableName),
before: attrBefore,
after: newName
allAttributes.push(key);
}
});
},
bulkInsertQuery: function(tableName, attrValueHashes, options, attributes) {
var query = 'INSERT INTO <%= table %> (<%= attributes %>)<%= output %> VALUES <%= tuples %>;'
, emptyQuery = 'INSERT INTO <%= table %><%= output %> DEFAULT VALUES'
, tuples = []
, allAttributes = []
, needIdentityInsertWrapper = false
, allQueries = []
, outputFragment;
if (options.returning) {
outputFragment = ' OUTPUT INSERTED.*';
}
});
if (allAttributes.length > 0) {
Utils._.forEach(attrValueHashes, function(attrValueHash) {
// special case for empty objects with primary keys
var fields = Object.keys(attrValueHash);
if (fields.length === 1 && attributes[fields[0]].autoIncrement && attrValueHash[fields[0]] === null) {
allQueries.push(emptyQuery);
return;
}
// normal case
Utils._.forOwn(attrValueHash, function(value, key) {
if (value !== null && attributes[key].autoIncrement) {
needIdentityInsertWrapper = true;
}
tuples.push('(' +
allAttributes.map(function(key) {
return this.escape(attrValueHash[key]);
}.bind(this)).join(',') +
')');
}.bind(this));
allQueries.push(query);
}
if (allAttributes.indexOf(key) === -1) {
if (value === null && attributes[key].autoIncrement)
return;
var replacements = {
table: this.quoteTable(tableName),
attributes: allAttributes.map(function(attr) {
return this.quoteIdentifier(attr);
}.bind(this)).join(','),
tuples: tuples,
output: outputFragment
};
var generatedQuery = Utils._.template(allQueries.join(';'))(replacements);
if (needIdentityInsertWrapper) {
generatedQuery = [
'SET IDENTITY_INSERT', this.quoteTable(tableName), 'ON;',
generatedQuery,
'SET IDENTITY_INSERT', this.quoteTable(tableName), 'OFF;',
].join(' ');
}
allAttributes.push(key);
}
});
});
return generatedQuery;
},
if (allAttributes.length > 0) {
Utils._.forEach(attrValueHashes, function(attrValueHash) {
tuples.push('(' +
allAttributes.map(function(key) {
return this.escape(attrValueHash[key]);
}.bind(this)).join(',') +
')');
}.bind(this));
deleteQuery: function(tableName, where, options) {
options = options || {};
allQueries.push(query);
}
var table = this.quoteTable(tableName);
if (options.truncate === true) {
// Truncate does not allow LIMIT and WHERE
return 'TRUNCATE TABLE ' + table;
}
var replacements = {
table: this.quoteTable(tableName),
attributes: allAttributes.map(function(attr) {
return this.quoteIdentifier(attr);
}.bind(this)).join(','),
tuples: tuples,
output: outputFragment
};
where = this.getWhereConditions(where);
var limit = ''
, query = 'DELETE<%= limit %> FROM <%= table %><%= where %>; ' +
'SELECT @@ROWCOUNT AS AFFECTEDROWS;';
var generatedQuery = Utils._.template(allQueries.join(';'))(replacements);
if (needIdentityInsertWrapper) {
generatedQuery = [
'SET IDENTITY_INSERT', this.quoteTable(tableName), 'ON;',
generatedQuery,
'SET IDENTITY_INSERT', this.quoteTable(tableName), 'OFF;',
].join(' ');
}
if (Utils._.isUndefined(options.limit)) {
options.limit = 1;
}
return generatedQuery;
},
if (!!options.limit) {
limit = ' TOP(' + this.escape(options.limit) + ')';
}
deleteQuery: function(tableName, where, options) {
options = options || {};
var replacements = {
limit: limit,
table: table,
where: where,
};
var table = this.quoteTable(tableName);
if (options.truncate === true) {
// Truncate does not allow LIMIT and WHERE
return 'TRUNCATE TABLE ' + table;
}
if (replacements.where) {
replacements.where = ' WHERE ' + replacements.where;
}
where = this.getWhereConditions(where);
var limit = ''
, query = 'DELETE<%= limit %> FROM <%= table %><%= where %>; ' +
'SELECT @@ROWCOUNT AS AFFECTEDROWS;';
return Utils._.template(query)(replacements);
},
if (Utils._.isUndefined(options.limit)) {
options.limit = 1;
}
showIndexesQuery: function(tableName) {
var sql = "EXEC sys.sp_helpindex @objname = N'<%= tableName %>';";
return Utils._.template(sql)({
tableName: this.quoteTable(tableName)
});
},
if (!!options.limit) {
limit = ' TOP(' + this.escape(options.limit) + ')';
}
removeIndexQuery: function(tableName, indexNameOrAttributes) {
var sql = 'DROP INDEX <%= indexName %> ON <%= tableName %>'
, indexName = indexNameOrAttributes;
var replacements = {
limit: limit,
table: table,
where: where,
};
if (typeof indexName !== 'string') {
indexName = Utils.inflection.underscore(tableName + '_' + indexNameOrAttributes.join('_'));
}
if (replacements.where) {
replacements.where = ' WHERE ' + replacements.where;
}
var values = {
tableName: this.quoteIdentifiers(tableName),
indexName: indexName
};
return Utils._.template(query)(replacements);
},
return Utils._.template(sql)(values);
},
showIndexesQuery: function(tableName) {
var sql = "EXEC sys.sp_helpindex @objname = N'<%= tableName %>';";
return Utils._.template(sql)({
tableName: this.quoteTable(tableName)
});
},
attributeToSQL: function(attribute) {
if (!Utils._.isPlainObject(attribute)) {
attribute = {
type: attribute
};
}
removeIndexQuery: function(tableName, indexNameOrAttributes) {
var sql = 'DROP INDEX <%= indexName %> ON <%= tableName %>'
, indexName = indexNameOrAttributes;
// handle self referential constraints
if (attribute.references) {
attribute = Utils.formatReferences(attribute);
if (typeof indexName !== 'string') {
indexName = Utils.inflection.underscore(tableName + '_' + indexNameOrAttributes.join('_'));
if (attribute.Model && attribute.Model.tableName === attribute.references.model) {
this.sequelize.log('MSSQL does not support self referencial constraints, '
+ 'we will remove it but we recommend restructuring your query');
attribute.onDelete = '';
attribute.onUpdate = '';
}
}
var values = {
tableName: this.quoteIdentifiers(tableName),
indexName: indexName
};
var template;
return Utils._.template(sql)(values);
},
if (attribute.type instanceof DataTypes.ENUM) {
if (attribute.type.values && !attribute.values) attribute.values = attribute.type.values;
attributeToSQL: function(attribute) {
if (!Utils._.isPlainObject(attribute)) {
attribute = {
type: attribute
};
}
// enums are a special case
template = 'VARCHAR(10) NULL' /* + (attribute.allowNull ? 'NULL' : 'NOT NULL') */;
template += ' CHECK (' + attribute.field + ' IN(' + Utils._.map(attribute.values, function(value) {
return this.escape(value);
}.bind(this)).join(', ') + '))';
return template;
} else {
template = attribute.type.toString();
}
// handle self referential constraints
if (attribute.references) {
attribute = Utils.formatReferences(attribute);
if (attribute.allowNull === false) {
template += ' NOT NULL';
} else if (!attribute.primaryKey && !Utils.defaultValueSchemable(attribute.defaultValue)) {
template += ' NULL';
}
if (attribute.Model && attribute.Model.tableName === attribute.references.model) {
this.sequelize.log('MSSQL does not support self referencial constraints, '
+ 'we will remove it but we recommend restructuring your query');
attribute.onDelete = '';
attribute.onUpdate = '';
}
}
if (attribute.autoIncrement) {
template += ' IDENTITY(1,1)';
}
// Blobs/texts cannot have a defaultValue
if (attribute.type !== 'TEXT' && attribute.type._binary !== true &&
Utils.defaultValueSchemable(attribute.defaultValue)) {
template += ' DEFAULT ' + this.escape(attribute.defaultValue);
}
var template;
if (attribute.unique === true) {
template += ' UNIQUE';
}
if (attribute.primaryKey) {
template += ' PRIMARY KEY';
}
if (attribute.type instanceof DataTypes.ENUM) {
if (attribute.type.values && !attribute.values) attribute.values = attribute.type.values;
if (attribute.references) {
template += ' REFERENCES ' + this.quoteTable(attribute.references.model);
// enums are a special case
template = 'VARCHAR(10) NULL' /* + (attribute.allowNull ? 'NULL' : 'NOT NULL') */;
template += ' CHECK (' + attribute.field + ' IN(' + Utils._.map(attribute.values, function(value) {
return this.escape(value);
}.bind(this)).join(', ') + '))';
return template;
if (attribute.references.key) {
template += ' (' + this.quoteIdentifier(attribute.references.key) + ')';
} else {
template = attribute.type.toString();
template += ' (' + this.quoteIdentifier('id') + ')';
}
if (attribute.allowNull === false) {
template += ' NOT NULL';
} else if (!attribute.primaryKey && !Utils.defaultValueSchemable(attribute.defaultValue)) {
template += ' NULL';
if (attribute.onDelete) {
template += ' ON DELETE ' + attribute.onDelete.toUpperCase();
}
if (attribute.autoIncrement) {
template += ' IDENTITY(1,1)';
if (attribute.onUpdate) {
template += ' ON UPDATE ' + attribute.onUpdate.toUpperCase();
}
}
// Blobs/texts cannot have a defaultValue
if (attribute.type !== 'TEXT' && attribute.type._binary !== true &&
Utils.defaultValueSchemable(attribute.defaultValue)) {
template += ' DEFAULT ' + this.escape(attribute.defaultValue);
}
return template;
},
if (attribute.unique === true) {
template += ' UNIQUE';
}
attributesToSQL: function(attributes, options) {
var result = {}
, key
, attribute
, existingConstraints = [];
if (attribute.primaryKey) {
template += ' PRIMARY KEY';
}
for (key in attributes) {
attribute = attributes[key];
if (attribute.references) {
template += ' REFERENCES ' + this.quoteTable(attribute.references.model);
attribute = Utils.formatReferences(attributes[key]);
if (attribute.references.key) {
template += ' (' + this.quoteIdentifier(attribute.references.key) + ')';
if (existingConstraints.indexOf(attribute.references.model.toString()) !== -1) {
// no cascading constraints to a table more than once
attribute.onDelete = '';
attribute.onUpdate = '';
} else {
template += ' (' + this.quoteIdentifier('id') + ')';
}
existingConstraints.push(attribute.references.model.toString());
if (attribute.onDelete) {
template += ' ON DELETE ' + attribute.onDelete.toUpperCase();
// NOTE: this really just disables cascading updates for all
// definitions. Can be made more robust to support the
// few cases where MSSQL actually supports them
attribute.onUpdate = '';
}
if (attribute.onUpdate) {
template += ' ON UPDATE ' + attribute.onUpdate.toUpperCase();
}
}
return template;
},
attributesToSQL: function(attributes, options) {
var result = {}
, key
, attribute
, existingConstraints = [];
for (key in attributes) {
attribute = attributes[key];
if (attribute.references) {
attribute = Utils.formatReferences(attributes[key]);
if (key && !attribute.field) attribute.field = key;
result[attribute.field || key] = this.attributeToSQL(attribute, options);
}
if (existingConstraints.indexOf(attribute.references.model.toString()) !== -1) {
// no cascading constraints to a table more than once
attribute.onDelete = '';
attribute.onUpdate = '';
} else {
existingConstraints.push(attribute.references.model.toString());
return result;
},
// NOTE: this really just disables cascading updates for all
// definitions. Can be made more robust to support the
// few cases where MSSQL actually supports them
attribute.onUpdate = '';
}
findAutoIncrementField: function(factory) {
var fields = [];
for (var name in factory.attributes) {
if (factory.attributes.hasOwnProperty(name)) {
var definition = factory.attributes[name];
if (definition && definition.autoIncrement) {
fields.push(name);
}
if (key && !attribute.field) attribute.field = key;
result[attribute.field || key] = this.attributeToSQL(attribute, options);
}
}
return result;
},
findAutoIncrementField: function(factory) {
var fields = [];
for (var name in factory.attributes) {
if (factory.attributes.hasOwnProperty(name)) {
var definition = factory.attributes[name];
return fields;
},
createTrigger: function() {
throwMethodUndefined('createTrigger');
},
dropTrigger: function() {
throwMethodUndefined('dropTrigger');
},
renameTrigger: function() {
throwMethodUndefined('renameTrigger');
},
createFunction: function() {
throwMethodUndefined('createFunction');
},
dropFunction: function() {
throwMethodUndefined('dropFunction');
},
renameFunction: function() {
throwMethodUndefined('renameFunction');
},
quoteIdentifier: function(identifier, force) {
if (identifier === '*') return identifier;
return '[' + identifier.replace(/[\[\]']+/g,'') + ']';
},
getForeignKeysQuery: function(table, databaseName) {
var tableName = table.tableName || table;
var sql = [
'SELECT',
'constraint_name = C.CONSTRAINT_NAME',
'FROM',
'INFORMATION_SCHEMA.TABLE_CONSTRAINTS C',
"WHERE C.CONSTRAINT_TYPE = 'FOREIGN KEY'",
'AND C.TABLE_NAME =', wrapSingleQuote(tableName)
].join(' ');
if (table.schema) {
sql += ' AND C.TABLE_SCHEMA =' + wrapSingleQuote(table.schema);
}
if (definition && definition.autoIncrement) {
fields.push(name);
}
}
}
return sql;
},
return fields;
},
createTrigger: function() {
throwMethodUndefined('createTrigger');
},
dropTrigger: function() {
throwMethodUndefined('dropTrigger');
},
renameTrigger: function() {
throwMethodUndefined('renameTrigger');
},
createFunction: function() {
throwMethodUndefined('createFunction');
},
dropFunction: function() {
throwMethodUndefined('dropFunction');
},
renameFunction: function() {
throwMethodUndefined('renameFunction');
},
quoteIdentifier: function(identifier, force) {
if (identifier === '*') return identifier;
return '[' + identifier.replace(/[\[\]']+/g,'') + ']';
},
getForeignKeysQuery: function(table, databaseName) {
var tableName = table.tableName || table;
var sql = [
'SELECT',
'constraint_name = C.CONSTRAINT_NAME',
'FROM',
'INFORMATION_SCHEMA.TABLE_CONSTRAINTS C',
"WHERE C.CONSTRAINT_TYPE = 'FOREIGN KEY'",
'AND C.TABLE_NAME =', wrapSingleQuote(tableName)
].join(' ');
dropForeignKeyQuery: function(tableName, foreignKey) {
return Utils._.template('ALTER TABLE <%= table %> DROP <%= key %>')({
table: this.quoteTable(tableName),
key: this.quoteIdentifier(foreignKey)
});
},
if (table.schema) {
sql += ' AND C.TABLE_SCHEMA =' + wrapSingleQuote(table.schema);
}
setAutocommitQuery: function(value) {
return '';
// return 'SET IMPLICIT_TRANSACTIONS ' + (!!value ? 'OFF' : 'ON') + ';';
},
return sql;
},
setIsolationLevelQuery: function(value, options) {
if (options.parent) {
return;
}
dropForeignKeyQuery: function(tableName, foreignKey) {
return Utils._.template('ALTER TABLE <%= table %> DROP <%= key %>')({
table: this.quoteTable(tableName),
key: this.quoteIdentifier(foreignKey)
});
},
return 'SET TRANSACTION ISOLATION LEVEL ' + value + ';';
},
setAutocommitQuery: function(value) {
return '';
// return 'SET IMPLICIT_TRANSACTIONS ' + (!!value ? 'OFF' : 'ON') + ';';
},
startTransactionQuery: function(transaction, options) {
if (options.parent) {
return 'SAVE TRANSACTION ' + this.quoteIdentifier(transaction.name) + ';';
}
setIsolationLevelQuery: function(value, options) {
if (options.parent) {
return;
}
return 'BEGIN TRANSACTION;';
},
return 'SET TRANSACTION ISOLATION LEVEL ' + value + ';';
},
commitTransactionQuery: function(options) {
if (options.parent) {
return;
}
startTransactionQuery: function(transaction, options) {
if (options.parent) {
return 'SAVE TRANSACTION ' + this.quoteIdentifier(transaction.name) + ';';
}
return 'COMMIT TRANSACTION;';
},
return 'BEGIN TRANSACTION;';
},
rollbackTransactionQuery: function(transaction, options) {
if (options.parent) {
return 'ROLLBACK TRANSACTION ' + this.quoteIdentifier(transaction.name) + ';';
}
commitTransactionQuery: function(options) {
if (options.parent) {
return;
return 'ROLLBACK TRANSACTION;';
},
addLimitAndOffset: function(options, model) {
var fragment = '';
var offset = options.offset || 0
, isSubQuery = options.hasIncludeWhere || options.hasIncludeRequired || options.hasMultiAssociation;
// FIXME: This is ripped from selectQuery to determine whether there is already
// an ORDER BY added for a subquery. Should be refactored so we dont' need
// the duplication. Also consider moving this logic inside the options.order
// check, so that we aren't compiling this twice for every invocation.
var mainQueryOrder = [];
var subQueryOrder = [];
if (options.order) {
if (Array.isArray(options.order)) {
options.order.forEach(function(t) {
if (!Array.isArray(t)) {
if (isSubQuery && !(t instanceof Model) && !(t.model instanceof Model)) {
subQueryOrder.push(this.quote(t, model));
}
} else {
if (isSubQuery && !(t[0] instanceof Model) && !(t[0].model instanceof Model)) {
subQueryOrder.push(this.quote(t, model));
}
mainQueryOrder.push(this.quote(t, model));
}
}.bind(this));
} else {
mainQueryOrder.push(options.order);
}
}
return 'COMMIT TRANSACTION;';
},
rollbackTransactionQuery: function(transaction, options) {
if (options.parent) {
return 'ROLLBACK TRANSACTION ' + this.quoteIdentifier(transaction.name) + ';';
if (options.limit || options.offset) {
if (!options.order || (options.include && !subQueryOrder.length)) {
fragment += (options.order && !isSubQuery) ? ', ' : ' ORDER BY ';
fragment += this.quoteIdentifier(model.primaryKeyAttribute);
}
return 'ROLLBACK TRANSACTION;';
},
addLimitAndOffset: function(options, model) {
var fragment = '';
var offset = options.offset || 0
, isSubQuery = options.hasIncludeWhere || options.hasIncludeRequired || options.hasMultiAssociation;
// FIXME: This is ripped from selectQuery to determine whether there is already
// an ORDER BY added for a subquery. Should be refactored so we dont' need
// the duplication. Also consider moving this logic inside the options.order
// check, so that we aren't compiling this twice for every invocation.
var mainQueryOrder = [];
var subQueryOrder = [];
if (options.order) {
if (Array.isArray(options.order)) {
options.order.forEach(function(t) {
if (!Array.isArray(t)) {
if (isSubQuery && !(t instanceof Model) && !(t.model instanceof Model)) {
subQueryOrder.push(this.quote(t, model));
}
} else {
if (isSubQuery && !(t[0] instanceof Model) && !(t[0].model instanceof Model)) {
subQueryOrder.push(this.quote(t, model));
}
mainQueryOrder.push(this.quote(t, model));
}
}.bind(this));
} else {
mainQueryOrder.push(options.order);
}
if (options.offset || options.limit) {
fragment += ' OFFSET ' + offset + ' ROWS';
}
if (options.limit || options.offset) {
if (!options.order || (options.include && !subQueryOrder.length)) {
fragment += (options.order && !isSubQuery) ? ', ' : ' ORDER BY ';
fragment += this.quoteIdentifier(model.primaryKeyAttribute);
}
if (options.offset || options.limit) {
fragment += ' OFFSET ' + offset + ' ROWS';
}
if (options.limit) {
fragment += ' FETCH NEXT ' + options.limit + ' ROWS ONLY';
}
if (options.limit) {
fragment += ' FETCH NEXT ' + options.limit + ' ROWS ONLY';
}
return fragment;
},
booleanValue: function(value) {
return !!value ? 1 : 0;
}
};
// private methods
function wrapSingleQuote(identifier){
return Utils.addTicks(identifier, "'");
return fragment;
},
booleanValue: function(value) {
return !!value ? 1 : 0;
}
};
/* istanbul ignore next */
var throwMethodUndefined = function(methodName) {
throw new Error('The method "' + methodName + '" is not defined! Please add it to your sql dialect.');
};
// private methods
function wrapSingleQuote(identifier){
return Utils.addTicks(identifier, "'");
}
return Utils._.extend(Utils._.clone(AbstractQueryGenerator), QueryGenerator);
})();
module.exports = Utils._.extend(Utils._.clone(AbstractQueryGenerator), QueryGenerator);
......@@ -4,284 +4,282 @@ var Utils = require('../../utils')
, AbstractQuery = require('../abstract/query')
, sequelizeErrors = require('../../errors.js');
module.exports = (function() {
var Query = function(connection, sequelize, options) {
this.connection = connection;
this.instance = options.instance;
this.model = options.model;
this.sequelize = sequelize;
this.options = Utils._.extend({
logging: console.log,
plain: false,
raw: false
}, options || {});
this.checkLoggingOption();
};
Utils.inherit(Query, AbstractQuery);
Query.prototype.getInsertIdField = function() {
return 'id';
};
Query.prototype.run = function(sql) {
var self = this;
this.sql = sql;
this.sequelize.log('Executing (' + (this.connection.uuid || 'default') + '): ' + this.sql, this.options);
var promise = new Utils.Promise(function(resolve, reject) {
// TRANSACTION SUPPORT
if (Utils._.contains(self.sql, 'BEGIN TRANSACTION')) {
self.connection.beginTransaction(function(err) {
if (!!err) {
reject(self.formatError(err));
} else {
resolve(self.formatResults());
}
} /* name, isolation_level */);
} else if (Utils._.contains(self.sql, 'COMMIT TRANSACTION')) {
self.connection.commitTransaction(function(err) {
if (!!err) {
reject(self.formatError(err));
} else {
resolve(self.formatResults());
}
});
} else if (Utils._.contains(self.sql, 'ROLLBACK TRANSACTION')) {
self.connection.rollbackTransaction(function(err) {
if (!!err) {
reject(self.formatError(err));
} else {
resolve(self.formatResults());
}
});
} else {
// QUERY SUPPORT
var results = [];
var request = new self.connection.lib.Request(self.sql, function(err) {
if (err) {
err.sql = sql;
reject(self.formatError(err));
} else {
resolve(self.formatResults(results));
}
});
request.on('row', function(columns) {
var row = {};
columns.forEach(function(column) {
row[column.metadata.colName] = column.value;
});
var Query = function(connection, sequelize, options) {
this.connection = connection;
this.instance = options.instance;
this.model = options.model;
this.sequelize = sequelize;
this.options = Utils._.extend({
logging: console.log,
plain: false,
raw: false
}, options || {});
this.checkLoggingOption();
};
Utils.inherit(Query, AbstractQuery);
Query.prototype.getInsertIdField = function() {
return 'id';
};
Query.prototype.run = function(sql) {
var self = this;
this.sql = sql;
this.sequelize.log('Executing (' + (this.connection.uuid || 'default') + '): ' + this.sql, this.options);
var promise = new Utils.Promise(function(resolve, reject) {
// TRANSACTION SUPPORT
if (Utils._.contains(self.sql, 'BEGIN TRANSACTION')) {
self.connection.beginTransaction(function(err) {
if (!!err) {
reject(self.formatError(err));
} else {
resolve(self.formatResults());
}
} /* name, isolation_level */);
} else if (Utils._.contains(self.sql, 'COMMIT TRANSACTION')) {
self.connection.commitTransaction(function(err) {
if (!!err) {
reject(self.formatError(err));
} else {
resolve(self.formatResults());
}
});
} else if (Utils._.contains(self.sql, 'ROLLBACK TRANSACTION')) {
self.connection.rollbackTransaction(function(err) {
if (!!err) {
reject(self.formatError(err));
} else {
resolve(self.formatResults());
}
});
} else {
// QUERY SUPPORT
var results = [];
var request = new self.connection.lib.Request(self.sql, function(err) {
if (err) {
err.sql = sql;
reject(self.formatError(err));
} else {
resolve(self.formatResults(results));
}
});
results.push(row);
request.on('row', function(columns) {
var row = {};
columns.forEach(function(column) {
row[column.metadata.colName] = column.value;
});
self.connection.execSql(request);
}
});
results.push(row);
});
return promise;
};
/**
* High level function that handles the results of a query execution.
*
*
* Example:
* query.formatResults([
* {
* id: 1, // this is from the main table
* attr2: 'snafu', // this is from the main table
* Tasks.id: 1, // this is from the associated table
* Tasks.title: 'task' // this is from the associated table
* }
* ])
*
* @param {Array} data - The result of the query execution.
*/
Query.prototype.formatResults = function(data) {
var result = this.instance;
if (this.isInsertQuery(data)) {
this.handleInsertQuery(data);
if (!this.instance) {
if (this.options.plain) {
// NOTE: super contrived. This just passes the newly added query-interface
// test returning only the PK. There isn't a way in MSSQL to identify
// that a given return value is the PK, and we have no schema information
// because there was no calling Model.
var record = data[0];
result = record[Object.keys(record)[0]];
} else {
result = data;
}
self.connection.execSql(request);
}
});
return promise;
};
/**
* High level function that handles the results of a query execution.
*
*
* Example:
* query.formatResults([
* {
* id: 1, // this is from the main table
* attr2: 'snafu', // this is from the main table
* Tasks.id: 1, // this is from the associated table
* Tasks.title: 'task' // this is from the associated table
* }
* ])
*
* @param {Array} data - The result of the query execution.
*/
Query.prototype.formatResults = function(data) {
var result = this.instance;
if (this.isInsertQuery(data)) {
this.handleInsertQuery(data);
if (!this.instance) {
if (this.options.plain) {
// NOTE: super contrived. This just passes the newly added query-interface
// test returning only the PK. There isn't a way in MSSQL to identify
// that a given return value is the PK, and we have no schema information
// because there was no calling Model.
var record = data[0];
result = record[Object.keys(record)[0]];
} else {
result = data;
}
}
if (this.isShowTablesQuery()) {
result = this.handleShowTablesQuery(data);
} else if (this.isDescribeQuery()) {
result = {};
data.forEach(function(_result) {
if (_result.Default)
_result.Default = _result.Default.replace("('",'').replace("')",'').replace(/'/g,''); /* jshint ignore: line */
result[_result.Name] = {
type: _result.Type.toUpperCase(),
allowNull: (_result.IsNull === 'YES' ? true : false),
defaultValue: _result.Default
};
});
} else if (this.isShowIndexesQuery()) {
result = this.handleShowIndexesQuery(data);
} else if (this.isSelectQuery()) {
result = this.handleSelectQuery(data);
} else if (this.isCallQuery()) {
result = data[0];
} else if (this.isBulkUpdateQuery()) {
result = data.length;
} else if (this.isBulkDeleteQuery()){
result = data[0] && data[0].AFFECTEDROWS;
} else if (this.isVersionQuery()) {
result = data[0].version;
} else if (this.isForeignKeysQuery()) {
result = data;
} else if (this.isRawQuery()) {
// MSSQL returns row data and metadata (affected rows etc) in a single object - let's standarize it, sorta
result = [data, data];
}
return result;
};
Query.prototype.handleShowTablesQuery = function(results) {
return results.map(function(resultSet) {
return {
tableName: resultSet.TABLE_NAME,
schema: resultSet.TABLE_SCHEMA
}
if (this.isShowTablesQuery()) {
result = this.handleShowTablesQuery(data);
} else if (this.isDescribeQuery()) {
result = {};
data.forEach(function(_result) {
if (_result.Default)
_result.Default = _result.Default.replace("('",'').replace("')",'').replace(/'/g,''); /* jshint ignore: line */
result[_result.Name] = {
type: _result.Type.toUpperCase(),
allowNull: (_result.IsNull === 'YES' ? true : false),
defaultValue: _result.Default
};
});
};
Query.prototype.formatError = function (err) {
var match;
match = err.message.match(/Violation of UNIQUE KEY constraint '((.|\s)*)'. Cannot insert duplicate key in object '.*'. The duplicate key value is \((.*)\)./);
match = match || err.message.match(/Cannot insert duplicate key row in object .* with unique index '(.*)'/);
if (match && match.length > 1) {
var fields = {}
, message = 'Validation error'
, uniqueKey = this.model.uniqueKeys[match[1]];
if (!!uniqueKey.msg) message = uniqueKey.msg;
if (!!match[2]) {
var values = match[2].split(',').map(Function.prototype.call, String.prototype.trim);
if (!!uniqueKey) {
fields = Utils._.zipObject(uniqueKey.fields, values);
} else {
fields[match[1]] = match[2];
}
} else if (this.isShowIndexesQuery()) {
result = this.handleShowIndexesQuery(data);
} else if (this.isSelectQuery()) {
result = this.handleSelectQuery(data);
} else if (this.isCallQuery()) {
result = data[0];
} else if (this.isBulkUpdateQuery()) {
result = data.length;
} else if (this.isBulkDeleteQuery()){
result = data[0] && data[0].AFFECTEDROWS;
} else if (this.isVersionQuery()) {
result = data[0].version;
} else if (this.isForeignKeysQuery()) {
result = data;
} else if (this.isRawQuery()) {
// MSSQL returns row data and metadata (affected rows etc) in a single object - let's standarize it, sorta
result = [data, data];
}
return result;
};
Query.prototype.handleShowTablesQuery = function(results) {
return results.map(function(resultSet) {
return {
tableName: resultSet.TABLE_NAME,
schema: resultSet.TABLE_SCHEMA
};
});
};
Query.prototype.formatError = function (err) {
var match;
match = err.message.match(/Violation of UNIQUE KEY constraint '((.|\s)*)'. Cannot insert duplicate key in object '.*'. The duplicate key value is \((.*)\)./);
match = match || err.message.match(/Cannot insert duplicate key row in object .* with unique index '(.*)'/);
if (match && match.length > 1) {
var fields = {}
, message = 'Validation error'
, uniqueKey = this.model.uniqueKeys[match[1]];
if (!!uniqueKey.msg) message = uniqueKey.msg;
if (!!match[2]) {
var values = match[2].split(',').map(Function.prototype.call, String.prototype.trim);
if (!!uniqueKey) {
fields = Utils._.zipObject(uniqueKey.fields, values);
} else {
fields[match[1]] = match[2];
}
}
var errors = [];
Utils._.forOwn(fields, function(value, field) {
errors.push(new sequelizeErrors.ValidationErrorItem(
field + ' must be unique', 'unique violation', field, value));
});
var errors = [];
Utils._.forOwn(fields, function(value, field) {
errors.push(new sequelizeErrors.ValidationErrorItem(
field + ' must be unique', 'unique violation', field, value));
});
return new sequelizeErrors.UniqueConstraintError({
message: message,
errors: errors,
parent: err,
fields: fields
});
}
return new sequelizeErrors.UniqueConstraintError({
message: message,
errors: errors,
parent: err,
fields: fields
});
}
match = err.message.match(/Failed on step '(.*)'.Could not create constraint. See previous errors./);
match = err.message.match(/The DELETE statement conflicted with the REFERENCE constraint "(.*)". The conflict occurred in database "(.*)", table "(.*)", column '(.*)'./);
if (match && match.length > 0) {
return new sequelizeErrors.ForeignKeyConstraintError({
fields: null,
index: match[1],
parent: err
});
}
match = err.message.match(/Failed on step '(.*)'.Could not create constraint. See previous errors./);
match = err.message.match(/The DELETE statement conflicted with the REFERENCE constraint "(.*)". The conflict occurred in database "(.*)", table "(.*)", column '(.*)'./);
if (match && match.length > 0) {
return new sequelizeErrors.ForeignKeyConstraintError({
fields: null,
index: match[1],
parent: err
});
}
return new sequelizeErrors.DatabaseError(err);
};
return new sequelizeErrors.DatabaseError(err);
};
Query.prototype.isShowOrDescribeQuery = function() {
var result = false;
Query.prototype.isShowOrDescribeQuery = function() {
var result = false;
result = result || (this.sql.toLowerCase().indexOf("select c.column_name as 'name', c.data_type as 'type', c.is_nullable as 'isnull'") === 0); /* jshint ignore: line */
result = result || (this.sql.toLowerCase().indexOf('select tablename = t.name, name = ind.name,') === 0);
result = result || (this.sql.toLowerCase().indexOf('exec sys.sp_helpindex @objname') === 0);
result = result || (this.sql.toLowerCase().indexOf("select c.column_name as 'name', c.data_type as 'type', c.is_nullable as 'isnull'") === 0); /* jshint ignore: line */
result = result || (this.sql.toLowerCase().indexOf('select tablename = t.name, name = ind.name,') === 0);
result = result || (this.sql.toLowerCase().indexOf('exec sys.sp_helpindex @objname') === 0);
return result;
};
return result;
};
Query.prototype.isShowIndexesQuery = function () {
return this.sql.toLowerCase().indexOf('exec sys.sp_helpindex @objname') === 0;
};
Query.prototype.isShowIndexesQuery = function () {
return this.sql.toLowerCase().indexOf('exec sys.sp_helpindex @objname') === 0;
};
Query.prototype.handleShowIndexesQuery = function (data) {
// Group by index name, and collect all fields
data = Utils._.foldl(data, function (acc, item) {
if (!(item.index_name in acc)) {
acc[item.index_name] = item;
item.fields = [];
}
Query.prototype.handleShowIndexesQuery = function (data) {
// Group by index name, and collect all fields
data = Utils._.foldl(data, function (acc, item) {
if (!(item.index_name in acc)) {
acc[item.index_name] = item;
item.fields = [];
Utils._.forEach(item.index_keys.split(','), function(column) {
var columnName = column.trim();
if (columnName.indexOf('(-)') !== -1) {
columnName = columnName.replace('(-)','');
}
Utils._.forEach(item.index_keys.split(','), function(column) {
var columnName = column.trim();
if (columnName.indexOf('(-)') !== -1) {
columnName = columnName.replace('(-)','');
}
acc[item.index_name].fields.push({
attribute: columnName,
length: undefined,
order: (column.indexOf('(-)') !== -1 ? 'DESC' : 'ASC'),
collate: undefined
});
acc[item.index_name].fields.push({
attribute: columnName,
length: undefined,
order: (column.indexOf('(-)') !== -1 ? 'DESC' : 'ASC'),
collate: undefined
});
delete item.index_keys;
return acc;
}, {});
return Utils._.map(data, function(item) {
return {
primary: (item.index_name.toLowerCase().indexOf('pk') === 0),
fields: item.fields,
name: item.index_name,
tableName: undefined,
unique: (item.index_description.toLowerCase().indexOf('unique') !== -1),
type: undefined,
};
});
};
Query.prototype.handleInsertQuery = function(results, metaData) {
if (this.instance) {
// add the inserted row id to the instance
var autoIncrementField = this.model.autoIncrementField
, autoIncrementFieldAlias = null
, id = null;
if (this.model.rawAttributes.hasOwnProperty(autoIncrementField) &&
this.model.rawAttributes[autoIncrementField].field !== undefined)
autoIncrementFieldAlias = this.model.rawAttributes[autoIncrementField].field ;
id = id || (results && results[0][this.getInsertIdField()]);
id = id || (metaData && metaData[this.getInsertIdField()]);
id = id || (results && results[0][autoIncrementField]);
id = id || (autoIncrementFieldAlias && results && results[0][autoIncrementFieldAlias]);
this.instance[autoIncrementField] = id;
}
};
return Query;
})();
delete item.index_keys;
return acc;
}, {});
return Utils._.map(data, function(item) {
return {
primary: (item.index_name.toLowerCase().indexOf('pk') === 0),
fields: item.fields,
name: item.index_name,
tableName: undefined,
unique: (item.index_description.toLowerCase().indexOf('unique') !== -1),
type: undefined,
};
});
};
Query.prototype.handleInsertQuery = function(results, metaData) {
if (this.instance) {
// add the inserted row id to the instance
var autoIncrementField = this.model.autoIncrementField
, autoIncrementFieldAlias = null
, id = null;
if (this.model.rawAttributes.hasOwnProperty(autoIncrementField) &&
this.model.rawAttributes[autoIncrementField].field !== undefined)
autoIncrementFieldAlias = this.model.rawAttributes[autoIncrementField].field ;
id = id || (results && results[0][this.getInsertIdField()]);
id = id || (metaData && metaData[this.getInsertIdField()]);
id = id || (results && results[0][autoIncrementField]);
id = id || (autoIncrementFieldAlias && results && results[0][autoIncrementFieldAlias]);
this.instance[autoIncrementField] = id;
}
};
module.exports = Query;
......@@ -3,376 +3,374 @@
var Utils = require('../../utils')
, DataTypes = require('../../data-types');
module.exports = (function() {
var QueryGenerator = {
dialect: 'mysql',
createSchema: function() {
var query = 'SHOW TABLES';
return Utils._.template(query)({});
},
showSchemasQuery: function() {
return 'SHOW TABLES';
},
versionQuery: function() {
return 'SELECT VERSION() as `version`';
},
createTableQuery: function(tableName, attributes, options) {
options = Utils._.extend({
engine: 'InnoDB',
charset: null
}, options || {});
var self = this;
var query = 'CREATE TABLE IF NOT EXISTS <%= table %> (<%= attributes%>) ENGINE=<%= engine %><%= comment %><%= charset %><%= collation %><%= initialAutoIncrement %>'
, primaryKeys = []
, foreignKeys = {}
, attrStr = [];
for (var attr in attributes) {
if (attributes.hasOwnProperty(attr)) {
var dataType = attributes[attr]
, match;
if (Utils._.includes(dataType, 'PRIMARY KEY')) {
primaryKeys.push(attr);
if (Utils._.includes(dataType, 'REFERENCES')) {
// MySQL doesn't support inline REFERENCES declarations: move to the end
match = dataType.match(/^(.+) (REFERENCES.*)$/);
attrStr.push(this.quoteIdentifier(attr) + ' ' + match[1].replace(/PRIMARY KEY/, ''));
foreignKeys[attr] = match[2];
} else {
attrStr.push(this.quoteIdentifier(attr) + ' ' + dataType.replace(/PRIMARY KEY/, ''));
}
} else if (Utils._.includes(dataType, 'REFERENCES')) {
// MySQL doesn't support inline REFERENCES declarations: move to the end
var QueryGenerator = {
dialect: 'mysql',
createSchema: function() {
var query = 'SHOW TABLES';
return Utils._.template(query)({});
},
showSchemasQuery: function() {
return 'SHOW TABLES';
},
versionQuery: function() {
return 'SELECT VERSION() as `version`';
},
createTableQuery: function(tableName, attributes, options) {
options = Utils._.extend({
engine: 'InnoDB',
charset: null
}, options || {});
var self = this;
var query = 'CREATE TABLE IF NOT EXISTS <%= table %> (<%= attributes%>) ENGINE=<%= engine %><%= comment %><%= charset %><%= collation %><%= initialAutoIncrement %>'
, primaryKeys = []
, foreignKeys = {}
, attrStr = [];
for (var attr in attributes) {
if (attributes.hasOwnProperty(attr)) {
var dataType = attributes[attr]
, match;
if (Utils._.includes(dataType, 'PRIMARY KEY')) {
primaryKeys.push(attr);
if (Utils._.includes(dataType, 'REFERENCES')) {
// MySQL doesn't support inline REFERENCES declarations: move to the end
match = dataType.match(/^(.+) (REFERENCES.*)$/);
attrStr.push(this.quoteIdentifier(attr) + ' ' + match[1]);
attrStr.push(this.quoteIdentifier(attr) + ' ' + match[1].replace(/PRIMARY KEY/, ''));
foreignKeys[attr] = match[2];
} else {
attrStr.push(this.quoteIdentifier(attr) + ' ' + dataType);
attrStr.push(this.quoteIdentifier(attr) + ' ' + dataType.replace(/PRIMARY KEY/, ''));
}
} else if (Utils._.includes(dataType, 'REFERENCES')) {
// MySQL doesn't support inline REFERENCES declarations: move to the end
match = dataType.match(/^(.+) (REFERENCES.*)$/);
attrStr.push(this.quoteIdentifier(attr) + ' ' + match[1]);
foreignKeys[attr] = match[2];
} else {
attrStr.push(this.quoteIdentifier(attr) + ' ' + dataType);
}
}
}
var values = {
table: this.quoteTable(tableName),
attributes: attrStr.join(', '),
comment: options.comment && Utils._.isString(options.comment) ? ' COMMENT ' + this.escape(options.comment) : '',
engine: options.engine,
charset: (options.charset ? ' DEFAULT CHARSET=' + options.charset : ''),
collation: (options.collate ? ' COLLATE ' + options.collate : ''),
initialAutoIncrement: (options.initialAutoIncrement ? ' AUTO_INCREMENT=' + options.initialAutoIncrement : '')
}
, pkString = primaryKeys.map(function(pk) { return this.quoteIdentifier(pk); }.bind(this)).join(', ');
if (!!options.uniqueKeys) {
Utils._.each(options.uniqueKeys, function(columns, indexName) {
if (!columns.singleField) { // If it's a single field its handled in column def, not as an index
if (!Utils._.isString(indexName)) {
indexName = 'uniq_' + tableName + '_' + columns.fields.join('_');
}
values.attributes += ', UNIQUE ' + self.quoteIdentifier(indexName) + ' (' + Utils._.map(columns.fields, self.quoteIdentifier).join(', ') + ')';
var values = {
table: this.quoteTable(tableName),
attributes: attrStr.join(', '),
comment: options.comment && Utils._.isString(options.comment) ? ' COMMENT ' + this.escape(options.comment) : '',
engine: options.engine,
charset: (options.charset ? ' DEFAULT CHARSET=' + options.charset : ''),
collation: (options.collate ? ' COLLATE ' + options.collate : ''),
initialAutoIncrement: (options.initialAutoIncrement ? ' AUTO_INCREMENT=' + options.initialAutoIncrement : '')
}
, pkString = primaryKeys.map(function(pk) { return this.quoteIdentifier(pk); }.bind(this)).join(', ');
if (!!options.uniqueKeys) {
Utils._.each(options.uniqueKeys, function(columns, indexName) {
if (!columns.singleField) { // If it's a single field its handled in column def, not as an index
if (!Utils._.isString(indexName)) {
indexName = 'uniq_' + tableName + '_' + columns.fields.join('_');
}
});
}
values.attributes += ', UNIQUE ' + self.quoteIdentifier(indexName) + ' (' + Utils._.map(columns.fields, self.quoteIdentifier).join(', ') + ')';
}
});
}
if (pkString.length > 0) {
values.attributes += ', PRIMARY KEY (' + pkString + ')';
}
if (pkString.length > 0) {
values.attributes += ', PRIMARY KEY (' + pkString + ')';
}
for (var fkey in foreignKeys) {
if (foreignKeys.hasOwnProperty(fkey)) {
values.attributes += ', FOREIGN KEY (' + this.quoteIdentifier(fkey) + ') ' + foreignKeys[fkey];
}
for (var fkey in foreignKeys) {
if (foreignKeys.hasOwnProperty(fkey)) {
values.attributes += ', FOREIGN KEY (' + this.quoteIdentifier(fkey) + ') ' + foreignKeys[fkey];
}
}
return Utils._.template(query)(values).trim() + ';';
},
showTablesQuery: function() {
return 'SHOW TABLES;';
},
addColumnQuery: function(table, key, dataType) {
var query = 'ALTER TABLE <%= table %> ADD <%= attribute %>;'
, attribute = Utils._.template('<%= key %> <%= definition %>')({
key: this.quoteIdentifier(key),
definition: this.attributeToSQL(dataType, {
context: 'addColumn'
})
});
return Utils._.template(query)({
table: this.quoteTable(table),
attribute: attribute
});
},
removeColumnQuery: function(tableName, attributeName) {
var query = 'ALTER TABLE <%= tableName %> DROP <%= attributeName %>;';
return Utils._.template(query)({
tableName: this.quoteTable(tableName),
attributeName: this.quoteIdentifier(attributeName)
});
},
return Utils._.template(query)(values).trim() + ';';
},
changeColumnQuery: function(tableName, attributes) {
var query = 'ALTER TABLE <%= tableName %> CHANGE <%= attributes %>;';
var attrString = [];
showTablesQuery: function() {
return 'SHOW TABLES;';
},
for (var attrName in attributes) {
var definition = attributes[attrName];
addColumnQuery: function(table, key, dataType) {
var query = 'ALTER TABLE <%= table %> ADD <%= attribute %>;'
, attribute = Utils._.template('<%= key %> <%= definition %>')({
key: this.quoteIdentifier(key),
definition: this.attributeToSQL(dataType, {
context: 'addColumn'
})
});
attrString.push(Utils._.template('`<%= attrName %>` `<%= attrName %>` <%= definition %>')({
attrName: attrName,
definition: definition
}));
}
return Utils._.template(query)({
table: this.quoteTable(table),
attribute: attribute
});
},
removeColumnQuery: function(tableName, attributeName) {
var query = 'ALTER TABLE <%= tableName %> DROP <%= attributeName %>;';
return Utils._.template(query)({
tableName: this.quoteTable(tableName),
attributeName: this.quoteIdentifier(attributeName)
});
},
changeColumnQuery: function(tableName, attributes) {
var query = 'ALTER TABLE <%= tableName %> CHANGE <%= attributes %>;';
var attrString = [];
for (var attrName in attributes) {
var definition = attributes[attrName];
attrString.push(Utils._.template('`<%= attrName %>` `<%= attrName %>` <%= definition %>')({
attrName: attrName,
definition: definition
}));
}
return Utils._.template(query)({ tableName: this.quoteTable(tableName), attributes: attrString.join(', ') });
},
return Utils._.template(query)({ tableName: this.quoteTable(tableName), attributes: attrString.join(', ') });
},
renameColumnQuery: function(tableName, attrBefore, attributes) {
var query = 'ALTER TABLE <%= tableName %> CHANGE <%= attributes %>;';
var attrString = [];
renameColumnQuery: function(tableName, attrBefore, attributes) {
var query = 'ALTER TABLE <%= tableName %> CHANGE <%= attributes %>;';
var attrString = [];
for (var attrName in attributes) {
var definition = attributes[attrName];
for (var attrName in attributes) {
var definition = attributes[attrName];
attrString.push(Utils._.template('`<%= before %>` `<%= after %>` <%= definition %>')({
before: attrBefore,
after: attrName,
definition: definition
}));
}
attrString.push(Utils._.template('`<%= before %>` `<%= after %>` <%= definition %>')({
before: attrBefore,
after: attrName,
definition: definition
}));
}
return Utils._.template(query)({ tableName: this.quoteTable(tableName), attributes: attrString.join(', ') });
},
return Utils._.template(query)({ tableName: this.quoteTable(tableName), attributes: attrString.join(', ') });
},
upsertQuery: function (tableName, insertValues, updateValues, where, rawAttributes, options) {
options.onDuplicate = 'UPDATE ';
upsertQuery: function (tableName, insertValues, updateValues, where, rawAttributes, options) {
options.onDuplicate = 'UPDATE ';
options.onDuplicate += Object.keys(updateValues).map(function (key) {
key = this.quoteIdentifier(key);
return key + '=VALUES(' + key +')';
}, this).join(', ');
options.onDuplicate += Object.keys(updateValues).map(function (key) {
key = this.quoteIdentifier(key);
return key + '=VALUES(' + key +')';
}, this).join(', ');
return this.insertQuery(tableName, insertValues, rawAttributes, options);
},
return this.insertQuery(tableName, insertValues, rawAttributes, options);
},
bulkInsertQuery: function(tableName, attrValueHashes, options) {
var query = 'INSERT<%= ignoreDuplicates %> INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %><%= onDuplicateKeyUpdate %>;'
, tuples = []
, allAttributes = []
, onDuplicateKeyUpdate = '';
bulkInsertQuery: function(tableName, attrValueHashes, options) {
var query = 'INSERT<%= ignoreDuplicates %> INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %><%= onDuplicateKeyUpdate %>;'
, tuples = []
, allAttributes = []
, onDuplicateKeyUpdate = '';
Utils._.forEach(attrValueHashes, function(attrValueHash) {
Utils._.forOwn(attrValueHash, function(value, key) {
if (allAttributes.indexOf(key) === -1) allAttributes.push(key);
});
Utils._.forEach(attrValueHashes, function(attrValueHash) {
Utils._.forOwn(attrValueHash, function(value, key) {
if (allAttributes.indexOf(key) === -1) allAttributes.push(key);
});
});
Utils._.forEach(attrValueHashes, function(attrValueHash) {
tuples.push('(' +
allAttributes.map(function(key) {
return this.escape(attrValueHash[key]);
}.bind(this)).join(',') +
')');
}.bind(this));
if (options && options.updateOnDuplicate) {
onDuplicateKeyUpdate += ' ON DUPLICATE KEY UPDATE ' + options.updateOnDuplicate.map(function(attr) {
var key = this.quoteIdentifier(attr);
return key + '=VALUES(' + key + ')';
}.bind(this)).join(',');
}
Utils._.forEach(attrValueHashes, function(attrValueHash) {
tuples.push('(' +
allAttributes.map(function(key) {
return this.escape(attrValueHash[key]);
}.bind(this)).join(',') +
')');
}.bind(this));
if (options && options.updateOnDuplicate) {
onDuplicateKeyUpdate += ' ON DUPLICATE KEY UPDATE ' + options.updateOnDuplicate.map(function(attr) {
var key = this.quoteIdentifier(attr);
return key + '=VALUES(' + key + ')';
}.bind(this)).join(',');
}
var replacements = {
ignoreDuplicates: options && options.ignoreDuplicates ? ' IGNORE' : '',
table: this.quoteTable(tableName),
attributes: allAttributes.map(function(attr) {
return this.quoteIdentifier(attr);
}.bind(this)).join(','),
tuples: tuples,
onDuplicateKeyUpdate: onDuplicateKeyUpdate
};
var replacements = {
ignoreDuplicates: options && options.ignoreDuplicates ? ' IGNORE' : '',
table: this.quoteTable(tableName),
attributes: allAttributes.map(function(attr) {
return this.quoteIdentifier(attr);
}.bind(this)).join(','),
tuples: tuples,
onDuplicateKeyUpdate: onDuplicateKeyUpdate
};
return Utils._.template(query)(replacements);
},
deleteQuery: function(tableName, where, options) {
options = options || {};
var table = this.quoteTable(tableName);
if (options.truncate === true) {
// Truncate does not allow LIMIT and WHERE
return 'TRUNCATE ' + table;
}
return Utils._.template(query)(replacements);
},
where = this.getWhereConditions(where);
var limit = '';
deleteQuery: function(tableName, where, options) {
options = options || {};
if (Utils._.isUndefined(options.limit)) {
options.limit = 1;
}
var table = this.quoteTable(tableName);
if (options.truncate === true) {
// Truncate does not allow LIMIT and WHERE
return 'TRUNCATE ' + table;
}
if (!!options.limit) {
limit = ' LIMIT ' + this.escape(options.limit);
}
where = this.getWhereConditions(where);
var limit = '';
var query = 'DELETE FROM ' + table;
if (where) query += ' WHERE ' + where;
query += limit;
if (Utils._.isUndefined(options.limit)) {
options.limit = 1;
}
return query;
},
if (!!options.limit) {
limit = ' LIMIT ' + this.escape(options.limit);
}
showIndexesQuery: function(tableName, options) {
var sql = 'SHOW INDEX FROM <%= tableName %><%= options %>';
return Utils._.template(sql)({
tableName: this.quoteTable(tableName),
options: (options || {}).database ? ' FROM `' + options.database + '`' : ''
});
},
var query = 'DELETE FROM ' + table;
if (where) query += ' WHERE ' + where;
query += limit;
removeIndexQuery: function(tableName, indexNameOrAttributes) {
var sql = 'DROP INDEX <%= indexName %> ON <%= tableName %>'
, indexName = indexNameOrAttributes;
return query;
},
if (typeof indexName !== 'string') {
indexName = Utils.inflection.underscore(tableName + '_' + indexNameOrAttributes.join('_'));
}
showIndexesQuery: function(tableName, options) {
var sql = 'SHOW INDEX FROM <%= tableName %><%= options %>';
return Utils._.template(sql)({
tableName: this.quoteTable(tableName),
options: (options || {}).database ? ' FROM `' + options.database + '`' : ''
});
},
return Utils._.template(sql)({ tableName: this.quoteIdentifiers(tableName), indexName: indexName });
},
removeIndexQuery: function(tableName, indexNameOrAttributes) {
var sql = 'DROP INDEX <%= indexName %> ON <%= tableName %>'
, indexName = indexNameOrAttributes;
attributeToSQL: function(attribute) {
if (!Utils._.isPlainObject(attribute)) {
attribute = {
type: attribute
};
}
if (typeof indexName !== 'string') {
indexName = Utils.inflection.underscore(tableName + '_' + indexNameOrAttributes.join('_'));
}
var template;
return Utils._.template(sql)({ tableName: this.quoteIdentifiers(tableName), indexName: indexName });
},
if (attribute.type instanceof DataTypes.ENUM) {
if (attribute.type.values && !attribute.values) attribute.values = attribute.type.values;
template = 'ENUM(' + Utils._.map(attribute.values, function(value) {
return this.escape(value);
}.bind(this)).join(', ') + ')';
} else {
template = attribute.type.toString();
}
attributeToSQL: function(attribute) {
if (!Utils._.isPlainObject(attribute)) {
attribute = {
type: attribute
};
}
if (attribute.allowNull === false) {
template += ' NOT NULL';
}
var template;
if (attribute.autoIncrement) {
template += ' auto_increment';
}
if (attribute.type instanceof DataTypes.ENUM) {
if (attribute.type.values && !attribute.values) attribute.values = attribute.type.values;
template = 'ENUM(' + Utils._.map(attribute.values, function(value) {
return this.escape(value);
}.bind(this)).join(', ') + ')';
} else {
template = attribute.type.toString();
}
// Blobs/texts cannot have a defaultValue
if (attribute.type !== 'TEXT' && attribute.type._binary !== true && Utils.defaultValueSchemable(attribute.defaultValue)) {
template += ' DEFAULT ' + this.escape(attribute.defaultValue);
}
if (attribute.allowNull === false) {
template += ' NOT NULL';
}
if (attribute.unique === true) {
template += ' UNIQUE';
}
if (attribute.autoIncrement) {
template += ' auto_increment';
}
if (attribute.primaryKey) {
template += ' PRIMARY KEY';
}
// Blobs/texts cannot have a defaultValue
if (attribute.type !== 'TEXT' && attribute.type._binary !== true && Utils.defaultValueSchemable(attribute.defaultValue)) {
template += ' DEFAULT ' + this.escape(attribute.defaultValue);
}
if (attribute.after) {
template += ' AFTER ' + this.quoteIdentifier(attribute.after);
}
if (attribute.unique === true) {
template += ' UNIQUE';
}
if (attribute.references) {
attribute = Utils.formatReferences(attribute);
template += ' REFERENCES ' + this.quoteTable(attribute.references.model);
if (attribute.primaryKey) {
template += ' PRIMARY KEY';
if (attribute.references.key) {
template += ' (' + this.quoteIdentifier(attribute.references.key) + ')';
} else {
template += ' (' + this.quoteIdentifier('id') + ')';
}
if (attribute.after) {
template += ' AFTER ' + this.quoteIdentifier(attribute.after);
if (attribute.onDelete) {
template += ' ON DELETE ' + attribute.onDelete.toUpperCase();
}
if (attribute.references) {
attribute = Utils.formatReferences(attribute);
template += ' REFERENCES ' + this.quoteTable(attribute.references.model);
if (attribute.references.key) {
template += ' (' + this.quoteIdentifier(attribute.references.key) + ')';
} else {
template += ' (' + this.quoteIdentifier('id') + ')';
}
if (attribute.onDelete) {
template += ' ON DELETE ' + attribute.onDelete.toUpperCase();
}
if (attribute.onUpdate) {
template += ' ON UPDATE ' + attribute.onUpdate.toUpperCase();
}
if (attribute.onUpdate) {
template += ' ON UPDATE ' + attribute.onUpdate.toUpperCase();
}
}
return template;
},
return template;
},
attributesToSQL: function(attributes, options) {
var result = {}
, key
, attribute;
attributesToSQL: function(attributes, options) {
var result = {}
, key
, attribute;
for (key in attributes) {
attribute = attributes[key];
result[attribute.field || key] = this.attributeToSQL(attribute, options);
}
for (key in attributes) {
attribute = attributes[key];
result[attribute.field || key] = this.attributeToSQL(attribute, options);
}
return result;
},
return result;
},
findAutoIncrementField: function(factory) {
var fields = [];
findAutoIncrementField: function(factory) {
var fields = [];
for (var name in factory.attributes) {
if (factory.attributes.hasOwnProperty(name)) {
var definition = factory.attributes[name];
for (var name in factory.attributes) {
if (factory.attributes.hasOwnProperty(name)) {
var definition = factory.attributes[name];
if (definition && definition.autoIncrement) {
fields.push(name);
}
if (definition && definition.autoIncrement) {
fields.push(name);
}
}
return fields;
},
quoteIdentifier: function(identifier) {
if (identifier === '*') return identifier;
return Utils.addTicks(identifier, '`');
},
/**
* Generates an SQL query that returns all foreign keys of a table.
*
* @param {String} tableName The name of the table.
* @param {String} schemaName The name of the schema.
* @return {String} The generated sql query.
*/
getForeignKeysQuery: function(tableName, schemaName) {
return "SELECT CONSTRAINT_NAME as constraint_name FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE where TABLE_NAME = '" + tableName + /* jshint ignore: line */
"' AND CONSTRAINT_NAME!='PRIMARY' AND CONSTRAINT_SCHEMA='" + schemaName + "' AND REFERENCED_TABLE_NAME IS NOT NULL;"; /* jshint ignore: line */
},
/**
* Generates an SQL query that removes a foreign key from a table.
*
* @param {String} tableName The name of the table.
* @param {String} foreignKey The name of the foreign key constraint.
* @return {String} The generated sql query.
*/
dropForeignKeyQuery: function(tableName, foreignKey) {
return 'ALTER TABLE ' + this.quoteTable(tableName) + ' DROP FOREIGN KEY ' + this.quoteIdentifier(foreignKey) + ';';
}
};
return Utils._.extend(Utils._.clone(require('../abstract/query-generator')), QueryGenerator);
})();
return fields;
},
quoteIdentifier: function(identifier) {
if (identifier === '*') return identifier;
return Utils.addTicks(identifier, '`');
},
/**
* Generates an SQL query that returns all foreign keys of a table.
*
* @param {String} tableName The name of the table.
* @param {String} schemaName The name of the schema.
* @return {String} The generated sql query.
*/
getForeignKeysQuery: function(tableName, schemaName) {
return "SELECT CONSTRAINT_NAME as constraint_name FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE where TABLE_NAME = '" + tableName + /* jshint ignore: line */
"' AND CONSTRAINT_NAME!='PRIMARY' AND CONSTRAINT_SCHEMA='" + schemaName + "' AND REFERENCED_TABLE_NAME IS NOT NULL;"; /* jshint ignore: line */
},
/**
* Generates an SQL query that removes a foreign key from a table.
*
* @param {String} tableName The name of the table.
* @param {String} foreignKey The name of the foreign key constraint.
* @return {String} The generated sql query.
*/
dropForeignKeyQuery: function(tableName, foreignKey) {
return 'ALTER TABLE ' + this.quoteTable(tableName) + ' DROP FOREIGN KEY ' + this.quoteIdentifier(foreignKey) + ';';
}
};
module.exports = Utils._.extend(Utils._.clone(require('../abstract/query-generator')), QueryGenerator);
......@@ -5,189 +5,187 @@ var Utils = require('../../utils')
, uuid = require('node-uuid')
, sequelizeErrors = require('../../errors.js');
module.exports = (function() {
var Query = function(connection, sequelize, options) {
this.connection = connection;
this.instance = options.instance;
this.model = options.model;
this.sequelize = sequelize;
this.uuid = uuid.v4();
this.options = Utils._.extend({
logging: console.log,
plain: false,
raw: false
}, options || {});
this.checkLoggingOption();
};
Utils.inherit(Query, AbstractQuery);
Query.prototype.run = function(sql) {
var self = this;
this.sql = sql;
this.sequelize.log('Executing (' + (this.connection.uuid || 'default') + '): ' + this.sql, this.options);
var promise = new Utils.Promise(function(resolve, reject) {
self.connection.query(self.sql, function(err, results) {
if (err) {
err.sql = sql;
reject(self.formatError(err));
} else {
resolve(self.formatResults(results));
}
}).setMaxListeners(100);
});
return promise;
};
/**
* High level function that handles the results of a query execution.
*
*
* Example:
* query.formatResults([
* {
* id: 1, // this is from the main table
* attr2: 'snafu', // this is from the main table
* Tasks.id: 1, // this is from the associated table
* Tasks.title: 'task' // this is from the associated table
* }
* ])
*
* @param {Array} data - The result of the query execution.
*/
Query.prototype.formatResults = function(data) {
var result = this.instance;
if (this.isInsertQuery(data)) {
this.handleInsertQuery(data);
if (!this.instance) {
result = data[this.getInsertIdField()];
var Query = function(connection, sequelize, options) {
this.connection = connection;
this.instance = options.instance;
this.model = options.model;
this.sequelize = sequelize;
this.uuid = uuid.v4();
this.options = Utils._.extend({
logging: console.log,
plain: false,
raw: false
}, options || {});
this.checkLoggingOption();
};
Utils.inherit(Query, AbstractQuery);
Query.prototype.run = function(sql) {
var self = this;
this.sql = sql;
this.sequelize.log('Executing (' + (this.connection.uuid || 'default') + '): ' + this.sql, this.options);
var promise = new Utils.Promise(function(resolve, reject) {
self.connection.query(self.sql, function(err, results) {
if (err) {
err.sql = sql;
reject(self.formatError(err));
} else {
resolve(self.formatResults(results));
}
}).setMaxListeners(100);
});
return promise;
};
/**
* High level function that handles the results of a query execution.
*
*
* Example:
* query.formatResults([
* {
* id: 1, // this is from the main table
* attr2: 'snafu', // this is from the main table
* Tasks.id: 1, // this is from the associated table
* Tasks.title: 'task' // this is from the associated table
* }
* ])
*
* @param {Array} data - The result of the query execution.
*/
Query.prototype.formatResults = function(data) {
var result = this.instance;
if (this.isInsertQuery(data)) {
this.handleInsertQuery(data);
if (!this.instance) {
result = data[this.getInsertIdField()];
}
}
if (this.isSelectQuery()) {
result = this.handleSelectQuery(data);
} else if (this.isShowTablesQuery()) {
result = this.handleShowTablesQuery(data);
} else if (this.isDescribeQuery()) {
result = {};
data.forEach(function(_result) {
result[_result.Field] = {
type: _result.Type.toUpperCase(),
allowNull: (_result.Null === 'YES'),
defaultValue: _result.Default
};
});
} else if (this.isShowIndexesQuery()) {
result = this.handleShowIndexesQuery(data);
} else if (this.isCallQuery()) {
result = data[0];
} else if (this.isBulkUpdateQuery() || this.isBulkDeleteQuery() || this.isUpsertQuery()) {
result = data.affectedRows;
} else if (this.isVersionQuery()) {
result = data[0].version;
} else if (this.isForeignKeysQuery()) {
result = data;
} else if (this.isRawQuery()) {
// MySQL returns row data and metadata (affected rows etc) in a single object - let's standarize it, sorta
result = [data, data];
}
return result;
};
Query.prototype.formatError = function (err) {
var match;
switch (err.errno || err.code) {
case 1062:
match = err.message.match(/Duplicate entry '(.*)' for key '?((.|\s)*?)'?$/);
var values = match ? match[1].split('-') : undefined
, fields = {}
, message = 'Validation error'
, uniqueKey = this.model && this.model.uniqueKeys[match[2]];
if (!!uniqueKey) {
if (!!uniqueKey.msg) message = uniqueKey.msg;
fields = Utils._.zipObject(uniqueKey.fields, values);
} else {
fields[match[2]] = match[1];
}
if (this.isSelectQuery()) {
result = this.handleSelectQuery(data);
} else if (this.isShowTablesQuery()) {
result = this.handleShowTablesQuery(data);
} else if (this.isDescribeQuery()) {
result = {};
data.forEach(function(_result) {
result[_result.Field] = {
type: _result.Type.toUpperCase(),
allowNull: (_result.Null === 'YES'),
defaultValue: _result.Default
};
var errors = [];
Utils._.forOwn(fields, function(value, field) {
errors.push(new sequelizeErrors.ValidationErrorItem(
field + ' must be unique', 'unique violation', field, value));
});
} else if (this.isShowIndexesQuery()) {
result = this.handleShowIndexesQuery(data);
} else if (this.isCallQuery()) {
result = data[0];
} else if (this.isBulkUpdateQuery() || this.isBulkDeleteQuery() || this.isUpsertQuery()) {
result = data.affectedRows;
} else if (this.isVersionQuery()) {
result = data[0].version;
} else if (this.isForeignKeysQuery()) {
result = data;
} else if (this.isRawQuery()) {
// MySQL returns row data and metadata (affected rows etc) in a single object - let's standarize it, sorta
result = [data, data];
}
return result;
};
Query.prototype.formatError = function (err) {
var match;
switch (err.errno || err.code) {
case 1062:
match = err.message.match(/Duplicate entry '(.*)' for key '?((.|\s)*?)'?$/);
var values = match ? match[1].split('-') : undefined
, fields = {}
, message = 'Validation error'
, uniqueKey = this.model && this.model.uniqueKeys[match[2]];
if (!!uniqueKey) {
if (!!uniqueKey.msg) message = uniqueKey.msg;
fields = Utils._.zipObject(uniqueKey.fields, values);
} else {
fields[match[2]] = match[1];
}
var errors = [];
Utils._.forOwn(fields, function(value, field) {
errors.push(new sequelizeErrors.ValidationErrorItem(
field + ' must be unique', 'unique violation', field, value));
});
return new sequelizeErrors.UniqueConstraintError({
message: message,
errors: errors,
parent: err,
fields: fields
});
case 1451:
match = err.message.match(/FOREIGN KEY \(`(.*)`\) REFERENCES `(.*)` \(`(.*)`\)(?: ON .*)?\)$/);
return new sequelizeErrors.ForeignKeyConstraintError({
fields: null,
index: match ? match[3] : undefined,
parent: err
});
case 1452:
match = err.message.match(/FOREIGN KEY \(`(.*)`\) REFERENCES `(.*)` \(`(.*)`\)(.*)\)$/);
return new sequelizeErrors.ForeignKeyConstraintError({
fields: null,
index: match ? match[1] : undefined,
parent: err
});
default:
return new sequelizeErrors.DatabaseError(err);
}
};
Query.prototype.handleShowIndexesQuery = function (data) {
// Group by index name, and collect all fields
data = Utils._.foldl(data, function (acc, item) {
if (!(item.Key_name in acc)) {
acc[item.Key_name] = item;
item.fields = [];
}
return new sequelizeErrors.UniqueConstraintError({
message: message,
errors: errors,
parent: err,
fields: fields
});
acc[item.Key_name].fields[item.Seq_in_index - 1] = {
attribute: item.Column_name,
length: item.Sub_part || undefined,
order: item.Collation === 'A' ? 'ASC' : undefined
};
delete item.column_name;
return acc;
}, {});
return Utils._.map(data, function(item) {
return {
primary: item.Key_name === 'PRIMARY',
fields: item.fields,
name: item.Key_name,
tableName: item.Table,
unique: (item.Non_unique !== 1),
type: item.Index_type,
};
});
};
case 1451:
match = err.message.match(/FOREIGN KEY \(`(.*)`\) REFERENCES `(.*)` \(`(.*)`\)(?: ON .*)?\)$/);
return new sequelizeErrors.ForeignKeyConstraintError({
fields: null,
index: match ? match[3] : undefined,
parent: err
});
case 1452:
match = err.message.match(/FOREIGN KEY \(`(.*)`\) REFERENCES `(.*)` \(`(.*)`\)(.*)\)$/);
return new sequelizeErrors.ForeignKeyConstraintError({
fields: null,
index: match ? match[1] : undefined,
parent: err
});
default:
return new sequelizeErrors.DatabaseError(err);
}
};
Query.prototype.handleShowIndexesQuery = function (data) {
// Group by index name, and collect all fields
data = Utils._.foldl(data, function (acc, item) {
if (!(item.Key_name in acc)) {
acc[item.Key_name] = item;
item.fields = [];
}
return Query;
})();
acc[item.Key_name].fields[item.Seq_in_index - 1] = {
attribute: item.Column_name,
length: item.Sub_part || undefined,
order: item.Collation === 'A' ? 'ASC' : undefined
};
delete item.column_name;
return acc;
}, {});
return Utils._.map(data, function(item) {
return {
primary: item.Key_name === 'PRIMARY',
fields: item.fields,
name: item.Key_name,
tableName: item.Table,
unique: (item.Non_unique !== 1),
type: item.Index_type,
};
});
};
module.exports = Query;
......@@ -2,15 +2,17 @@
var hstore = require('pg-hstore')({sanitize : true});
module.exports = {
stringify: function(data) {
if(data === null) return null;
function stringify (data) {
if (data === null) return null;
return hstore.stringify(data);
},
parse: function(value) {
if(value === null) return null;
}
function parse (value) {
if (value === null) return null;
return hstore.parse(value);
}
}
module.exports = {
stringify: stringify,
parse: parse
};
......@@ -10,935 +10,933 @@ var Utils = require('../../utils')
, AbstractQueryGenerator = require('../abstract/query-generator')
, primaryKeys = {};
module.exports = (function() {
var QueryGenerator = {
options: {},
dialect: 'postgres',
var QueryGenerator = {
options: {},
dialect: 'postgres',
createSchema: function(schema) {
var query = 'CREATE SCHEMA <%= schema%>;';
return Utils._.template(query)({schema: schema});
},
createSchema: function(schema) {
var query = 'CREATE SCHEMA <%= schema%>;';
return Utils._.template(query)({schema: schema});
},
dropSchema: function(schema) {
var query = 'DROP SCHEMA <%= schema%> CASCADE;';
return Utils._.template(query)({schema: schema});
},
dropSchema: function(schema) {
var query = 'DROP SCHEMA <%= schema%> CASCADE;';
return Utils._.template(query)({schema: schema});
},
showSchemasQuery: function() {
return "SELECT schema_name FROM information_schema.schemata WHERE schema_name <> 'information_schema' AND schema_name != 'public' AND schema_name !~ E'^pg_';";
},
showSchemasQuery: function() {
return "SELECT schema_name FROM information_schema.schemata WHERE schema_name <> 'information_schema' AND schema_name != 'public' AND schema_name !~ E'^pg_';";
},
versionQuery: function() {
return 'SELECT VERSION() as "version"';
},
versionQuery: function() {
return 'SELECT VERSION() as "version"';
},
createTableQuery: function(tableName, attributes, options) {
var self = this;
createTableQuery: function(tableName, attributes, options) {
var self = this;
options = Utils._.extend({
}, options || {});
options = Utils._.extend({
}, options || {});
primaryKeys[tableName] = [];
primaryKeys[tableName] = [];
var query = 'CREATE TABLE IF NOT EXISTS <%= table %> (<%= attributes%>)<%= comments %>'
, comments = ''
, attrStr = []
, i;
var query = 'CREATE TABLE IF NOT EXISTS <%= table %> (<%= attributes%>)<%= comments %>'
, comments = ''
, attrStr = []
, i;
if (options.comment && Utils._.isString(options.comment)) {
comments += '; COMMENT ON TABLE <%= table %> IS ' + this.escape(options.comment);
}
for (var attr in attributes) {
if ((i = attributes[attr].indexOf('COMMENT')) !== -1) {
// Move comment to a seperate query
comments += '; ' + attributes[attr].substring(i);
attributes[attr] = attributes[attr].substring(0, i);
}
var dataType = this.pgDataTypeMapping(tableName, attr, attributes[attr]);
attrStr.push(this.quoteIdentifier(attr) + ' ' + dataType);
}
var values = {
table: this.quoteTable(tableName),
attributes: attrStr.join(', '),
comments: Utils._.template(comments)({ table: this.quoteTable(tableName)})
};
if (options.comment && Utils._.isString(options.comment)) {
comments += '; COMMENT ON TABLE <%= table %> IS ' + this.escape(options.comment);
}
if (!!options.uniqueKeys) {
Utils._.each(options.uniqueKeys, function(columns) {
if (!columns.singleField) { // If it's a single field its handled in column def, not as an index
values.attributes += ', UNIQUE (' + columns.fields.map(function(f) { return self.quoteIdentifiers(f); }).join(', ') + ')';
}
});
for (var attr in attributes) {
if ((i = attributes[attr].indexOf('COMMENT')) !== -1) {
// Move comment to a seperate query
comments += '; ' + attributes[attr].substring(i);
attributes[attr] = attributes[attr].substring(0, i);
}
var pks = primaryKeys[tableName].map(function(pk) {
return this.quoteIdentifier(pk);
}.bind(this)).join(',');
if (pks.length > 0) {
values.attributes += ', PRIMARY KEY (' + pks + ')';
}
var dataType = this.pgDataTypeMapping(tableName, attr, attributes[attr]);
attrStr.push(this.quoteIdentifier(attr) + ' ' + dataType);
}
return Utils._.template(query)(values).trim() + ';';
},
var values = {
table: this.quoteTable(tableName),
attributes: attrStr.join(', '),
comments: Utils._.template(comments)({ table: this.quoteTable(tableName)})
};
dropTableQuery: function(tableName, options) {
options = options || {};
var query = 'DROP TABLE IF EXISTS <%= table %><%= cascade %>;';
return Utils._.template(query)({
table: this.quoteTable(tableName),
cascade: options.cascade ? ' CASCADE' : ''
if (!!options.uniqueKeys) {
Utils._.each(options.uniqueKeys, function(columns) {
if (!columns.singleField) { // If it's a single field its handled in column def, not as an index
values.attributes += ', UNIQUE (' + columns.fields.map(function(f) { return self.quoteIdentifiers(f); }).join(', ') + ')';
}
});
},
}
showTablesQuery: function() {
return "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public';";
},
var pks = primaryKeys[tableName].map(function(pk) {
return this.quoteIdentifier(pk);
}.bind(this)).join(',');
describeTableQuery: function(tableName, schema) {
if (!schema) {
schema = 'public';
}
if (pks.length > 0) {
values.attributes += ', PRIMARY KEY (' + pks + ')';
}
var query = 'SELECT c.column_name as "Field", c.column_default as "Default", c.is_nullable as "Null", ' +
"CASE WHEN c.udt_name = 'hstore' " +
'THEN c.udt_name ELSE c.data_type END as "Type", (SELECT array_agg(e.enumlabel) ' +
'FROM pg_catalog.pg_type t JOIN pg_catalog.pg_enum e ON t.oid=e.enumtypid WHERE t.typname=c.udt_name) AS "special" ' +
'FROM information_schema.columns c WHERE table_name = <%= table %> AND table_schema = <%= schema %>';
return Utils._.template(query)(values).trim() + ';';
},
dropTableQuery: function(tableName, options) {
options = options || {};
var query = 'DROP TABLE IF EXISTS <%= table %><%= cascade %>;';
return Utils._.template(query)({
table: this.quoteTable(tableName),
cascade: options.cascade ? ' CASCADE' : ''
});
},
showTablesQuery: function() {
return "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public';";
},
describeTableQuery: function(tableName, schema) {
if (!schema) {
schema = 'public';
}
return Utils._.template(query)({
table: this.escape(tableName),
schema: this.escape(schema)
});
},
var query = 'SELECT c.column_name as "Field", c.column_default as "Default", c.is_nullable as "Null", ' +
"CASE WHEN c.udt_name = 'hstore' " +
'THEN c.udt_name ELSE c.data_type END as "Type", (SELECT array_agg(e.enumlabel) ' +
'FROM pg_catalog.pg_type t JOIN pg_catalog.pg_enum e ON t.oid=e.enumtypid WHERE t.typname=c.udt_name) AS "special" ' +
'FROM information_schema.columns c WHERE table_name = <%= table %> AND table_schema = <%= schema %>';
return Utils._.template(query)({
table: this.escape(tableName),
schema: this.escape(schema)
});
},
// A recursive parser for nested where conditions
parseConditionObject: function(_conditions, path) {
var self = this;
path = path || [];
return Utils._.reduce(_conditions, function (r, v, k) { // result, key, value
if (Utils._.isObject(v)) {
r = r.concat(self.parseConditionObject(v, path.concat(k))); // Recursively parse objects
} else {
r.push({ path: path.concat(k), value: v });
}
return r;
}, []);
},
handleSequelizeMethod: function (smth, tableName, factory, options, prepend) {
var _ = Utils._;
if (smth instanceof Utils.json) {
// Parse nested object
if (smth.conditions) {
var conditions = _.map(this.parseConditionObject(smth.conditions), function generateSql(condition) {
return util.format("%s#>>'{%s}' = '%s'",
_.first(condition.path),
_.rest(condition.path).join(','),
condition.value);
});
// A recursive parser for nested where conditions
parseConditionObject: function(_conditions, path) {
var self = this;
return conditions.join(' and ');
} else if (smth.path) {
var str;
path = path || [];
return Utils._.reduce(_conditions, function (r, v, k) { // result, key, value
if (Utils._.isObject(v)) {
r = r.concat(self.parseConditionObject(v, path.concat(k))); // Recursively parse objects
// Allow specifying conditions using the postgres json syntax
if (_.any(['->', '->>', '#>'], _.partial(_.contains, smth.path))) {
str = smth.path;
} else {
r.push({ path: path.concat(k), value: v });
// Also support json dot notation
var path = smth.path.split('.');
str = util.format("%s#>>'{%s}'",
_.first(path),
_.rest(path).join(','));
}
return r;
}, []);
},
handleSequelizeMethod: function (smth, tableName, factory, options, prepend) {
var _ = Utils._;
if (smth instanceof Utils.json) {
// Parse nested object
if (smth.conditions) {
var conditions = _.map(this.parseConditionObject(smth.conditions), function generateSql(condition) {
return util.format("%s#>>'{%s}' = '%s'",
_.first(condition.path),
_.rest(condition.path).join(','),
condition.value);
});
return conditions.join(' and ');
} else if (smth.path) {
var str;
// Allow specifying conditions using the postgres json syntax
if (_.any(['->', '->>', '#>'], _.partial(_.contains, smth.path))) {
str = smth.path;
} else {
// Also support json dot notation
var path = smth.path.split('.');
str = util.format("%s#>>'{%s}'",
_.first(path),
_.rest(path).join(','));
}
if (smth.value) {
str += util.format(' = %s', this.escape(smth.value));
}
return str;
if (smth.value) {
str += util.format(' = %s', this.escape(smth.value));
}
} else {
return AbstractQueryGenerator.handleSequelizeMethod.call(this, smth, tableName, factory, options, prepend);
}
},
addColumnQuery: function(table, key, dataType) {
var query = 'ALTER TABLE <%= table %> ADD COLUMN <%= attribute %>;'
, dbDataType = this.attributeToSQL(dataType, {context: 'addColumn'})
, attribute;
if (dataType.type && dataType.type instanceof DataTypes.ENUM || dataType instanceof DataTypes.ENUM) {
query = this.pgEnum(table, key, dataType) + query;
return str;
}
} else {
return AbstractQueryGenerator.handleSequelizeMethod.call(this, smth, tableName, factory, options, prepend);
}
},
attribute = Utils._.template('<%= key %> <%= definition %>')({
key: this.quoteIdentifier(key),
definition: this.pgDataTypeMapping(table, key, dbDataType)
});
return Utils._.template(query)({
table: this.quoteTable(table),
attribute: attribute
});
},
removeColumnQuery: function(tableName, attributeName) {
var query = 'ALTER TABLE <%= tableName %> DROP COLUMN <%= attributeName %>;';
return Utils._.template(query)({
tableName: this.quoteTable(tableName),
attributeName: this.quoteIdentifier(attributeName)
});
},
changeColumnQuery: function(tableName, attributes) {
var query = 'ALTER TABLE <%= tableName %> ALTER COLUMN <%= query %>;'
, sql = [];
for (var attributeName in attributes) {
var definition = this.pgDataTypeMapping(tableName, attributeName, attributes[attributeName]);
var attrSql = '';
if (definition.indexOf('NOT NULL') > 0) {
attrSql += Utils._.template(query)({
tableName: this.quoteTable(tableName),
query: this.quoteIdentifier(attributeName) + ' SET NOT NULL'
});
definition = definition.replace('NOT NULL', '').trim();
} else {
attrSql += Utils._.template(query)({
tableName: this.quoteTable(tableName),
query: this.quoteIdentifier(attributeName) + ' DROP NOT NULL'
});
}
if (definition.indexOf('DEFAULT') > 0) {
attrSql += Utils._.template(query)({
tableName: this.quoteTable(tableName),
query: this.quoteIdentifier(attributeName) + ' SET DEFAULT ' + definition.match(/DEFAULT ([^;]+)/)[1]
});
definition = definition.replace(/(DEFAULT[^;]+)/, '').trim();
} else {
attrSql += Utils._.template(query)({
tableName: this.quoteTable(tableName),
query: this.quoteIdentifier(attributeName) + ' DROP DEFAULT'
});
}
addColumnQuery: function(table, key, dataType) {
var query = 'ALTER TABLE <%= table %> ADD COLUMN <%= attribute %>;'
, dbDataType = this.attributeToSQL(dataType, {context: 'addColumn'})
, attribute;
if (attributes[attributeName].match(/^ENUM\(/)) {
query = this.pgEnum(tableName, attributeName, attributes[attributeName]) + query;
definition = definition.replace(/^ENUM\(.+\)/, this.quoteIdentifier('enum_' + tableName + '_' + attributeName));
definition += ' USING (' + this.quoteIdentifier(attributeName) + '::' + this.quoteIdentifier(definition) + ')';
}
if (dataType.type && dataType.type instanceof DataTypes.ENUM || dataType instanceof DataTypes.ENUM) {
query = this.pgEnum(table, key, dataType) + query;
}
if (definition.match(/UNIQUE;*$/)) {
definition = definition.replace(/UNIQUE;*$/, '');
attribute = Utils._.template('<%= key %> <%= definition %>')({
key: this.quoteIdentifier(key),
definition: this.pgDataTypeMapping(table, key, dbDataType)
});
return Utils._.template(query)({
table: this.quoteTable(table),
attribute: attribute
});
},
removeColumnQuery: function(tableName, attributeName) {
var query = 'ALTER TABLE <%= tableName %> DROP COLUMN <%= attributeName %>;';
return Utils._.template(query)({
tableName: this.quoteTable(tableName),
attributeName: this.quoteIdentifier(attributeName)
});
},
changeColumnQuery: function(tableName, attributes) {
var query = 'ALTER TABLE <%= tableName %> ALTER COLUMN <%= query %>;'
, sql = [];
for (var attributeName in attributes) {
var definition = this.pgDataTypeMapping(tableName, attributeName, attributes[attributeName]);
var attrSql = '';
if (definition.indexOf('NOT NULL') > 0) {
attrSql += Utils._.template(query)({
tableName: this.quoteTable(tableName),
query: this.quoteIdentifier(attributeName) + ' SET NOT NULL'
});
attrSql += Utils._.template(query.replace('ALTER COLUMN', ''))({
tableName: this.quoteTable(tableName),
query: 'ADD CONSTRAINT ' + this.quoteIdentifier(attributeName + '_unique_idx') + ' UNIQUE (' + this.quoteIdentifier(attributeName) + ')'
});
}
definition = definition.replace('NOT NULL', '').trim();
} else {
attrSql += Utils._.template(query)({
tableName: this.quoteTable(tableName),
query: this.quoteIdentifier(attributeName) + ' DROP NOT NULL'
});
}
if (definition.indexOf('DEFAULT') > 0) {
attrSql += Utils._.template(query)({
tableName: this.quoteTable(tableName),
query: this.quoteIdentifier(attributeName) + ' TYPE ' + definition
query: this.quoteIdentifier(attributeName) + ' SET DEFAULT ' + definition.match(/DEFAULT ([^;]+)/)[1]
});
sql.push(attrSql);
definition = definition.replace(/(DEFAULT[^;]+)/, '').trim();
} else {
attrSql += Utils._.template(query)({
tableName: this.quoteTable(tableName),
query: this.quoteIdentifier(attributeName) + ' DROP DEFAULT'
});
}
return sql.join('');
},
if (attributes[attributeName].match(/^ENUM\(/)) {
query = this.pgEnum(tableName, attributeName, attributes[attributeName]) + query;
definition = definition.replace(/^ENUM\(.+\)/, this.quoteIdentifier('enum_' + tableName + '_' + attributeName));
definition += ' USING (' + this.quoteIdentifier(attributeName) + '::' + this.quoteIdentifier(definition) + ')';
}
renameColumnQuery: function(tableName, attrBefore, attributes) {
var query = 'ALTER TABLE <%= tableName %> RENAME COLUMN <%= attributes %>;';
var attrString = [];
if (definition.match(/UNIQUE;*$/)) {
definition = definition.replace(/UNIQUE;*$/, '');
for (var attributeName in attributes) {
attrString.push(Utils._.template('<%= before %> TO <%= after %>')({
before: this.quoteIdentifier(attrBefore),
after: this.quoteIdentifier(attributeName)
}));
attrSql += Utils._.template(query.replace('ALTER COLUMN', ''))({
tableName: this.quoteTable(tableName),
query: 'ADD CONSTRAINT ' + this.quoteIdentifier(attributeName + '_unique_idx') + ' UNIQUE (' + this.quoteIdentifier(attributeName) + ')'
});
}
return Utils._.template(query)({
attrSql += Utils._.template(query)({
tableName: this.quoteTable(tableName),
attributes: attrString.join(', ')
query: this.quoteIdentifier(attributeName) + ' TYPE ' + definition
});
},
fn: function(fnName, tableName, body, returns, language) {
fnName = fnName || 'testfunc';
language = language || 'plpgsql';
returns = returns || 'SETOF ' + this.quoteTable(tableName);
var query = 'CREATE OR REPLACE FUNCTION pg_temp.<%= fnName %>() RETURNS <%= returns %> AS $func$ BEGIN <%= body %> END; $func$ LANGUAGE <%= language %>; SELECT * FROM pg_temp.<%= fnName %>();';
sql.push(attrSql);
}
return Utils._.template(query)({
fnName: fnName,
returns: returns,
language: language,
body: body
});
},
return sql.join('');
},
exceptionFn: function(fnName, tableName, main, then, when, returns, language) {
when = when || 'unique_violation';
renameColumnQuery: function(tableName, attrBefore, attributes) {
var query = 'ALTER TABLE <%= tableName %> RENAME COLUMN <%= attributes %>;';
var attrString = [];
var body = '<%= main %> EXCEPTION WHEN <%= when %> THEN <%= then %>;';
body = Utils._.template(body)({
main: main,
when: when,
then: then
});
for (var attributeName in attributes) {
attrString.push(Utils._.template('<%= before %> TO <%= after %>')({
before: this.quoteIdentifier(attrBefore),
after: this.quoteIdentifier(attributeName)
}));
}
return this.fn(fnName, tableName, body, returns, language);
},
// http://www.maori.geek.nz/post/postgres_upsert_update_or_insert_in_ger_using_knex_js
upsertQuery: function (tableName, insertValues, updateValues, where, rawAttributes, options) {
var insert = this.insertQuery(tableName, insertValues, rawAttributes, options);
var update = this.updateQuery(tableName, updateValues, where, options, rawAttributes);
// The numbers here are selected to match the number of affected rows returned by MySQL
return this.exceptionFn(
'sequelize_upsert',
tableName,
insert + ' RETURN 1;',
update + '; RETURN 2',
'unique_violation',
'integer'
);
},
bulkInsertQuery: function(tableName, attrValueHashes, options, modelAttributes) {
options = options || {};
var query = 'INSERT INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %>'
, tuples = []
, serials = []
, allAttributes = [];
if (options.returning) {
query += ' RETURNING *';
}
return Utils._.template(query)({
tableName: this.quoteTable(tableName),
attributes: attrString.join(', ')
});
},
fn: function(fnName, tableName, body, returns, language) {
fnName = fnName || 'testfunc';
language = language || 'plpgsql';
returns = returns || 'SETOF ' + this.quoteTable(tableName);
var query = 'CREATE OR REPLACE FUNCTION pg_temp.<%= fnName %>() RETURNS <%= returns %> AS $func$ BEGIN <%= body %> END; $func$ LANGUAGE <%= language %>; SELECT * FROM pg_temp.<%= fnName %>();';
return Utils._.template(query)({
fnName: fnName,
returns: returns,
language: language,
body: body
});
},
exceptionFn: function(fnName, tableName, main, then, when, returns, language) {
when = when || 'unique_violation';
var body = '<%= main %> EXCEPTION WHEN <%= when %> THEN <%= then %>;';
body = Utils._.template(body)({
main: main,
when: when,
then: then
});
return this.fn(fnName, tableName, body, returns, language);
},
// http://www.maori.geek.nz/post/postgres_upsert_update_or_insert_in_ger_using_knex_js
upsertQuery: function (tableName, insertValues, updateValues, where, rawAttributes, options) {
var insert = this.insertQuery(tableName, insertValues, rawAttributes, options);
var update = this.updateQuery(tableName, updateValues, where, options, rawAttributes);
// The numbers here are selected to match the number of affected rows returned by MySQL
return this.exceptionFn(
'sequelize_upsert',
tableName,
insert + ' RETURN 1;',
update + '; RETURN 2',
'unique_violation',
'integer'
);
},
bulkInsertQuery: function(tableName, attrValueHashes, options, modelAttributes) {
options = options || {};
var query = 'INSERT INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %>'
, tuples = []
, serials = []
, allAttributes = [];
if (options.returning) {
query += ' RETURNING *';
}
Utils._.forEach(attrValueHashes, function(attrValueHash) {
Utils._.forOwn(attrValueHash, function(value, key) {
if (allAttributes.indexOf(key) === -1) {
allAttributes.push(key);
}
Utils._.forEach(attrValueHashes, function(attrValueHash) {
Utils._.forOwn(attrValueHash, function(value, key) {
if (allAttributes.indexOf(key) === -1) {
allAttributes.push(key);
}
if (modelAttributes && modelAttributes[key] && modelAttributes[key].autoIncrement === true) {
serials.push(key);
}
});
if (modelAttributes && modelAttributes[key] && modelAttributes[key].autoIncrement === true) {
serials.push(key);
}
});
});
Utils._.forEach(attrValueHashes, function(attrValueHash) {
tuples.push('(' +
allAttributes.map(function(key) {
if (serials.indexOf(key) !== -1) {
return attrValueHash[key] || 'DEFAULT';
}
return this.escape(attrValueHash[key], modelAttributes && modelAttributes[key]);
}.bind(this)).join(',') +
')');
}.bind(this));
var replacements = {
table: this.quoteTable(tableName)
, attributes: allAttributes.map(function(attr) {
return this.quoteIdentifier(attr);
}.bind(this)).join(',')
, tuples: tuples.join(',')
};
query = query + ';';
Utils._.forEach(attrValueHashes, function(attrValueHash) {
tuples.push('(' +
allAttributes.map(function(key) {
if (serials.indexOf(key) !== -1) {
return attrValueHash[key] || 'DEFAULT';
}
return this.escape(attrValueHash[key], modelAttributes && modelAttributes[key]);
}.bind(this)).join(',') +
')');
}.bind(this));
return Utils._.template(query)(replacements);
},
var replacements = {
table: this.quoteTable(tableName)
, attributes: allAttributes.map(function(attr) {
return this.quoteIdentifier(attr);
}.bind(this)).join(',')
, tuples: tuples.join(',')
};
deleteQuery: function(tableName, where, options, model) {
var query;
query = query + ';';
options = options || {};
return Utils._.template(query)(replacements);
},
tableName = Utils.removeTicks(this.quoteTable(tableName), '"');
deleteQuery: function(tableName, where, options, model) {
var query;
if (options.truncate === true) {
query = 'TRUNCATE ' + QueryGenerator.quoteIdentifier(tableName);
options = options || {};
if (options.cascade) {
query += ' CASCADE';
}
tableName = Utils.removeTicks(this.quoteTable(tableName), '"');
return query;
}
if (options.truncate === true) {
query = 'TRUNCATE ' + QueryGenerator.quoteIdentifier(tableName);
if (Utils._.isUndefined(options.limit)) {
options.limit = 1;
if (options.cascade) {
query += ' CASCADE';
}
primaryKeys[tableName] = primaryKeys[tableName] || [];
if (!!model && primaryKeys[tableName].length < 1) {
primaryKeys[tableName] = Object.keys(model.primaryKeys);
}
return query;
}
if (options.limit) {
query = 'DELETE FROM <%= table %> WHERE <%= primaryKeys %> IN (SELECT <%= primaryKeysSelection %> FROM <%= table %><%= where %><%= limit %>)';
} else {
query = 'DELETE FROM <%= table %><%= where %>';
}
if (Utils._.isUndefined(options.limit)) {
options.limit = 1;
}
var pks;
if (primaryKeys[tableName] && primaryKeys[tableName].length > 0) {
pks = primaryKeys[tableName].map(function(pk) {
return this.quoteIdentifier(pk);
}.bind(this)).join(',');
} else {
pks = this.quoteIdentifier('id');
}
primaryKeys[tableName] = primaryKeys[tableName] || [];
var replacements = {
table: this.quoteIdentifiers(tableName),
where: this.getWhereConditions(where),
limit: !!options.limit ? ' LIMIT ' + this.escape(options.limit) : '',
primaryKeys: primaryKeys[tableName].length > 1 ? '(' + pks + ')' : pks,
primaryKeysSelection: pks
};
if (!!model && primaryKeys[tableName].length < 1) {
primaryKeys[tableName] = Object.keys(model.primaryKeys);
}
if (replacements.where) {
replacements.where = ' WHERE ' + replacements.where;
}
if (options.limit) {
query = 'DELETE FROM <%= table %> WHERE <%= primaryKeys %> IN (SELECT <%= primaryKeysSelection %> FROM <%= table %><%= where %><%= limit %>)';
} else {
query = 'DELETE FROM <%= table %><%= where %>';
}
return Utils._.template(query)(replacements);
},
var pks;
if (primaryKeys[tableName] && primaryKeys[tableName].length > 0) {
pks = primaryKeys[tableName].map(function(pk) {
return this.quoteIdentifier(pk);
}.bind(this)).join(',');
} else {
pks = this.quoteIdentifier('id');
}
showIndexesQuery: function(tableName) {
if (!Utils._.isString(tableName)) {
tableName = tableName.tableName;
}
var replacements = {
table: this.quoteIdentifiers(tableName),
where: this.getWhereConditions(where),
limit: !!options.limit ? ' LIMIT ' + this.escape(options.limit) : '',
primaryKeys: primaryKeys[tableName].length > 1 ? '(' + pks + ')' : pks,
primaryKeysSelection: pks
};
// This is ARCANE!
var query = 'SELECT i.relname AS name, ix.indisprimary AS primary, ix.indisunique AS unique, ix.indkey AS indkey, ' +
'array_agg(a.attnum) as column_indexes, array_agg(a.attname) AS column_names, pg_get_indexdef(ix.indexrelid) ' +
'AS definition FROM pg_class t, pg_class i, pg_index ix, pg_attribute a ' +
'WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND a.attrelid = t.oid AND a.attnum = ANY(ix.indkey) AND '+
"t.relkind = 'r' and t.relname = '<%= tableName %>' GROUP BY i.relname, ix.indexrelid, ix.indisprimary, ix.indisunique, ix.indkey ORDER BY i.relname;";
if (replacements.where) {
replacements.where = ' WHERE ' + replacements.where;
}
return Utils._.template(query)({ tableName: tableName });
},
return Utils._.template(query)(replacements);
},
removeIndexQuery: function(tableName, indexNameOrAttributes) {
var sql = 'DROP INDEX IF EXISTS <%= indexName %>'
, indexName = indexNameOrAttributes;
showIndexesQuery: function(tableName) {
if (!Utils._.isString(tableName)) {
tableName = tableName.tableName;
}
if (typeof indexName !== 'string') {
indexName = Utils.inflection.underscore(tableName + '_' + indexNameOrAttributes.join('_'));
}
// This is ARCANE!
var query = 'SELECT i.relname AS name, ix.indisprimary AS primary, ix.indisunique AS unique, ix.indkey AS indkey, ' +
'array_agg(a.attnum) as column_indexes, array_agg(a.attname) AS column_names, pg_get_indexdef(ix.indexrelid) ' +
'AS definition FROM pg_class t, pg_class i, pg_index ix, pg_attribute a ' +
'WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND a.attrelid = t.oid AND a.attnum = ANY(ix.indkey) AND '+
"t.relkind = 'r' and t.relname = '<%= tableName %>' GROUP BY i.relname, ix.indexrelid, ix.indisprimary, ix.indisunique, ix.indkey ORDER BY i.relname;";
return Utils._.template(sql)({
tableName: this.quoteIdentifiers(tableName),
indexName: this.quoteIdentifiers(indexName)
});
},
return Utils._.template(query)({ tableName: tableName });
},
addLimitAndOffset: function(options) {
var fragment = '';
if (options.limit) fragment += ' LIMIT ' + options.limit;
if (options.offset) fragment += ' OFFSET ' + options.offset;
removeIndexQuery: function(tableName, indexNameOrAttributes) {
var sql = 'DROP INDEX IF EXISTS <%= indexName %>'
, indexName = indexNameOrAttributes;
return fragment;
},
if (typeof indexName !== 'string') {
indexName = Utils.inflection.underscore(tableName + '_' + indexNameOrAttributes.join('_'));
}
attributeToSQL: function(attribute) {
if (!Utils._.isPlainObject(attribute)) {
attribute = {
type: attribute
};
}
return Utils._.template(sql)({
tableName: this.quoteIdentifiers(tableName),
indexName: this.quoteIdentifiers(indexName)
});
},
var template = '<%= type %>'
, replacements = {};
addLimitAndOffset: function(options) {
var fragment = '';
if (options.limit) fragment += ' LIMIT ' + options.limit;
if (options.offset) fragment += ' OFFSET ' + options.offset;
if (attribute.type instanceof DataTypes.ENUM) {
if (attribute.type.values && !attribute.values) attribute.values = attribute.type.values;
return fragment;
},
if (Array.isArray(attribute.values) && (attribute.values.length > 0)) {
replacements.type = 'ENUM(' + Utils._.map(attribute.values, function(value) {
return this.escape(value);
}.bind(this)).join(', ') + ')';
} else {
throw new Error("Values for ENUM haven't been defined.");
}
}
attributeToSQL: function(attribute) {
if (!Utils._.isPlainObject(attribute)) {
attribute = {
type: attribute
};
}
if (!replacements.type) {
replacements.type = attribute.type;
}
var template = '<%= type %>'
, replacements = {};
if (attribute.hasOwnProperty('allowNull') && (!attribute.allowNull)) {
template += ' NOT NULL';
}
if (attribute.type instanceof DataTypes.ENUM) {
if (attribute.type.values && !attribute.values) attribute.values = attribute.type.values;
if (attribute.autoIncrement) {
template += ' SERIAL';
if (Array.isArray(attribute.values) && (attribute.values.length > 0)) {
replacements.type = 'ENUM(' + Utils._.map(attribute.values, function(value) {
return this.escape(value);
}.bind(this)).join(', ') + ')';
} else {
throw new Error("Values for ENUM haven't been defined.");
}
}
if (Utils.defaultValueSchemable(attribute.defaultValue)) {
template += ' DEFAULT <%= defaultValue %>';
replacements.defaultValue = this.escape(attribute.defaultValue, attribute);
}
if (!replacements.type) {
replacements.type = attribute.type;
}
if (attribute.unique === true) {
template += ' UNIQUE';
}
if (attribute.hasOwnProperty('allowNull') && (!attribute.allowNull)) {
template += ' NOT NULL';
}
if (attribute.primaryKey) {
template += ' PRIMARY KEY';
}
if (attribute.autoIncrement) {
template += ' SERIAL';
}
if (attribute.references) {
attribute = Utils.formatReferences(attribute);
template += ' REFERENCES <%= referencesTable %> (<%= referencesKey %>)';
replacements.referencesTable = this.quoteTable(attribute.references.model);
if (Utils.defaultValueSchemable(attribute.defaultValue)) {
template += ' DEFAULT <%= defaultValue %>';
replacements.defaultValue = this.escape(attribute.defaultValue, attribute);
}
if (attribute.references.key) {
replacements.referencesKey = this.quoteIdentifiers(attribute.references.key);
} else {
replacements.referencesKey = this.quoteIdentifier('id');
}
if (attribute.unique === true) {
template += ' UNIQUE';
}
if (attribute.onDelete) {
template += ' ON DELETE <%= onDeleteAction %>';
replacements.onDeleteAction = attribute.onDelete.toUpperCase();
}
if (attribute.primaryKey) {
template += ' PRIMARY KEY';
}
if (attribute.onUpdate) {
template += ' ON UPDATE <%= onUpdateAction %>';
replacements.onUpdateAction = attribute.onUpdate.toUpperCase();
}
if (attribute.references) {
attribute = Utils.formatReferences(attribute);
template += ' REFERENCES <%= referencesTable %> (<%= referencesKey %>)';
replacements.referencesTable = this.quoteTable(attribute.references.model);
if (attribute.references.deferrable) {
template += ' <%= deferrable %>';
replacements.deferrable = attribute.references.deferrable.toString(this);
}
if (attribute.references.key) {
replacements.referencesKey = this.quoteIdentifiers(attribute.references.key);
} else {
replacements.referencesKey = this.quoteIdentifier('id');
}
return Utils._.template(template)(replacements);
},
deferConstraintsQuery: function (options) {
return options.deferrable.toString(this);
},
if (attribute.onDelete) {
template += ' ON DELETE <%= onDeleteAction %>';
replacements.onDeleteAction = attribute.onDelete.toUpperCase();
}
setConstraintQuery: function (columns, type) {
var columnFragment = 'ALL';
if (attribute.onUpdate) {
template += ' ON UPDATE <%= onUpdateAction %>';
replacements.onUpdateAction = attribute.onUpdate.toUpperCase();
}
if (columns) {
columnFragment = columns.map(function (column) {
return this.quoteIdentifier(column);
}.bind(this)).join(', ');
if (attribute.references.deferrable) {
template += ' <%= deferrable %>';
replacements.deferrable = attribute.references.deferrable.toString(this);
}
}
return 'SET CONSTRAINTS ' + columnFragment + ' ' + type;
},
return Utils._.template(template)(replacements);
},
setDeferredQuery: function (columns) {
return this.setConstraintQuery(columns, 'DEFERRED');
},
deferConstraintsQuery: function (options) {
return options.deferrable.toString(this);
},
setImmediateQuery: function (columns) {
return this.setConstraintQuery(columns, 'IMMEDIATE');
},
setConstraintQuery: function (columns, type) {
var columnFragment = 'ALL';
attributesToSQL: function(attributes, options) {
var result = {}
, key
, attribute;
if (columns) {
columnFragment = columns.map(function (column) {
return this.quoteIdentifier(column);
}.bind(this)).join(', ');
}
for (key in attributes) {
attribute = attributes[key];
result[attribute.field || key] = this.attributeToSQL(attribute, options);
}
return 'SET CONSTRAINTS ' + columnFragment + ' ' + type;
},
return result;
},
setDeferredQuery: function (columns) {
return this.setConstraintQuery(columns, 'DEFERRED');
},
findAutoIncrementField: function(factory) {
var fields = [];
setImmediateQuery: function (columns) {
return this.setConstraintQuery(columns, 'IMMEDIATE');
},
for (var name in factory.attributes) {
var definition = factory.attributes[name];
attributesToSQL: function(attributes, options) {
var result = {}
, key
, attribute;
if (definition && definition.autoIncrement) {
fields.push(name);
}
}
for (key in attributes) {
attribute = attributes[key];
result[attribute.field || key] = this.attributeToSQL(attribute, options);
}
return fields;
},
createTrigger: function(tableName, triggerName, eventType, fireOnSpec, functionName, functionParams, optionsArray) {
var sql = [
'CREATE <%= constraintVal %>TRIGGER <%= triggerName %>'
, '<%= eventType %> <%= eventSpec %>'
, 'ON <%= tableName %>'
, '<%= optionsSpec %>'
, 'EXECUTE PROCEDURE <%= functionName %>(<%= paramList %>);'
].join('\n\t');
return Utils._.template(sql)({
constraintVal: this.triggerEventTypeIsConstraint(eventType),
triggerName: triggerName,
eventType: this.decodeTriggerEventType(eventType),
eventSpec: this.expandTriggerEventSpec(fireOnSpec),
tableName: tableName,
optionsSpec: this.expandOptions(optionsArray),
functionName: functionName,
paramList: this.expandFunctionParamList(functionParams)
});
},
return result;
},
dropTrigger: function(tableName, triggerName) {
var sql = 'DROP TRIGGER <%= triggerName %> ON <%= tableName %> RESTRICT;';
return Utils._.template(sql)({
triggerName: triggerName,
tableName: tableName
});
},
renameTrigger: function(tableName, oldTriggerName, newTriggerName) {
var sql = 'ALTER TRIGGER <%= oldTriggerName %> ON <%= tableName %> RENAME TO <%= newTriggerName%>;';
return Utils._.template(sql)({
tableName: tableName,
oldTriggerName: oldTriggerName,
newTriggerName: newTriggerName
});
},
createFunction: function(functionName, params, returnType, language, body, options) {
var sql = ['CREATE FUNCTION <%= functionName %>(<%= paramList %>)'
, 'RETURNS <%= returnType %> AS $func$'
, 'BEGIN'
, '\t<%= body %>'
, 'END;'
, "$func$ language '<%= language %>'<%= options %>;"
].join('\n');
return Utils._.template(sql)({
functionName: functionName,
paramList: this.expandFunctionParamList(params),
returnType: returnType,
body: body.replace('\n', '\n\t'),
language: language,
options: this.expandOptions(options)
});
},
dropFunction: function(functionName, params) {
// RESTRICT is (currently, as of 9.2) default but we'll be explicit
var sql = 'DROP FUNCTION <%= functionName %>(<%= paramList %>) RESTRICT;';
return Utils._.template(sql)({
functionName: functionName,
paramList: this.expandFunctionParamList(params)
});
},
renameFunction: function(oldFunctionName, params, newFunctionName) {
var sql = 'ALTER FUNCTION <%= oldFunctionName %>(<%= paramList %>) RENAME TO <%= newFunctionName %>;';
return Utils._.template(sql)({
oldFunctionName: oldFunctionName,
paramList: this.expandFunctionParamList(params),
newFunctionName: newFunctionName
});
},
databaseConnectionUri: function(config) {
var template = '<%= protocol %>://<%= user %>:<%= password %>@<%= host %><% if(port) { %>:<%= port %><% } %>/<%= database %><% if(ssl) { %>?ssl=<%= ssl %><% } %>';
return Utils._.template(template)({
user: config.username,
password: config.password,
database: config.database,
host: config.host,
port: config.port,
protocol: config.protocol,
ssl: config.ssl
});
},
findAutoIncrementField: function(factory) {
var fields = [];
pgEscapeAndQuote: function(val) {
return this.quoteIdentifier(Utils.removeTicks(this.escape(val), "'"));
},
for (var name in factory.attributes) {
var definition = factory.attributes[name];
expandFunctionParamList: function expandFunctionParamList(params) {
if (Utils._.isUndefined(params) || !Utils._.isArray(params)) {
throw new Error('expandFunctionParamList: function parameters array required, including an empty one for no arguments');
if (definition && definition.autoIncrement) {
fields.push(name);
}
}
var paramList = Utils._.each(params, function expandParam(curParam) {
var paramDef = [];
if (Utils._.has(curParam, 'type')) {
if (Utils._.has(curParam, 'direction')) { paramDef.push(curParam.direction); }
if (Utils._.has(curParam, 'name')) { paramDef.push(curParam.name); }
paramDef.push(curParam.type);
} else {
throw new Error('createFunction called with a parameter with no type');
}
return paramDef.join(' ');
});
return paramList.join(', ');
},
expandOptions: function expandOptions(options) {
return Utils._.isUndefined(options) || Utils._.isEmpty(options) ?
'' : '\n\t' + options.join('\n\t');
},
decodeTriggerEventType: function decodeTriggerEventType(eventSpecifier) {
var EVENT_DECODER = {
'after': 'AFTER',
'before': 'BEFORE',
'instead_of': 'INSTEAD OF',
'after_constraint': 'AFTER'
};
return fields;
},
createTrigger: function(tableName, triggerName, eventType, fireOnSpec, functionName, functionParams, optionsArray) {
var sql = [
'CREATE <%= constraintVal %>TRIGGER <%= triggerName %>'
, '<%= eventType %> <%= eventSpec %>'
, 'ON <%= tableName %>'
, '<%= optionsSpec %>'
, 'EXECUTE PROCEDURE <%= functionName %>(<%= paramList %>);'
].join('\n\t');
return Utils._.template(sql)({
constraintVal: this.triggerEventTypeIsConstraint(eventType),
triggerName: triggerName,
eventType: this.decodeTriggerEventType(eventType),
eventSpec: this.expandTriggerEventSpec(fireOnSpec),
tableName: tableName,
optionsSpec: this.expandOptions(optionsArray),
functionName: functionName,
paramList: this.expandFunctionParamList(functionParams)
});
},
dropTrigger: function(tableName, triggerName) {
var sql = 'DROP TRIGGER <%= triggerName %> ON <%= tableName %> RESTRICT;';
return Utils._.template(sql)({
triggerName: triggerName,
tableName: tableName
});
},
renameTrigger: function(tableName, oldTriggerName, newTriggerName) {
var sql = 'ALTER TRIGGER <%= oldTriggerName %> ON <%= tableName %> RENAME TO <%= newTriggerName%>;';
return Utils._.template(sql)({
tableName: tableName,
oldTriggerName: oldTriggerName,
newTriggerName: newTriggerName
});
},
createFunction: function(functionName, params, returnType, language, body, options) {
var sql = ['CREATE FUNCTION <%= functionName %>(<%= paramList %>)'
, 'RETURNS <%= returnType %> AS $func$'
, 'BEGIN'
, '\t<%= body %>'
, 'END;'
, "$func$ language '<%= language %>'<%= options %>;"
].join('\n');
return Utils._.template(sql)({
functionName: functionName,
paramList: this.expandFunctionParamList(params),
returnType: returnType,
body: body.replace('\n', '\n\t'),
language: language,
options: this.expandOptions(options)
});
},
dropFunction: function(functionName, params) {
// RESTRICT is (currently, as of 9.2) default but we'll be explicit
var sql = 'DROP FUNCTION <%= functionName %>(<%= paramList %>) RESTRICT;';
return Utils._.template(sql)({
functionName: functionName,
paramList: this.expandFunctionParamList(params)
});
},
renameFunction: function(oldFunctionName, params, newFunctionName) {
var sql = 'ALTER FUNCTION <%= oldFunctionName %>(<%= paramList %>) RENAME TO <%= newFunctionName %>;';
return Utils._.template(sql)({
oldFunctionName: oldFunctionName,
paramList: this.expandFunctionParamList(params),
newFunctionName: newFunctionName
});
},
databaseConnectionUri: function(config) {
var template = '<%= protocol %>://<%= user %>:<%= password %>@<%= host %><% if(port) { %>:<%= port %><% } %>/<%= database %><% if(ssl) { %>?ssl=<%= ssl %><% } %>';
return Utils._.template(template)({
user: config.username,
password: config.password,
database: config.database,
host: config.host,
port: config.port,
protocol: config.protocol,
ssl: config.ssl
});
},
pgEscapeAndQuote: function(val) {
return this.quoteIdentifier(Utils.removeTicks(this.escape(val), "'"));
},
expandFunctionParamList: function expandFunctionParamList(params) {
if (Utils._.isUndefined(params) || !Utils._.isArray(params)) {
throw new Error('expandFunctionParamList: function parameters array required, including an empty one for no arguments');
}
if (!Utils._.has(EVENT_DECODER, eventSpecifier)) {
throw new Error('Invalid trigger event specified: ' + eventSpecifier);
}
var paramList = Utils._.each(params, function expandParam(curParam) {
var paramDef = [];
if (Utils._.has(curParam, 'type')) {
if (Utils._.has(curParam, 'direction')) { paramDef.push(curParam.direction); }
if (Utils._.has(curParam, 'name')) { paramDef.push(curParam.name); }
paramDef.push(curParam.type);
} else {
throw new Error('createFunction called with a parameter with no type');
}
return paramDef.join(' ');
});
return paramList.join(', ');
},
expandOptions: function expandOptions(options) {
return Utils._.isUndefined(options) || Utils._.isEmpty(options) ?
'' : '\n\t' + options.join('\n\t');
},
decodeTriggerEventType: function decodeTriggerEventType(eventSpecifier) {
var EVENT_DECODER = {
'after': 'AFTER',
'before': 'BEFORE',
'instead_of': 'INSTEAD OF',
'after_constraint': 'AFTER'
};
if (!Utils._.has(EVENT_DECODER, eventSpecifier)) {
throw new Error('Invalid trigger event specified: ' + eventSpecifier);
}
return EVENT_DECODER[eventSpecifier];
},
return EVENT_DECODER[eventSpecifier];
},
triggerEventTypeIsConstraint: function triggerEventTypeIsConstraint(eventSpecifier) {
return eventSpecifier === 'after_constrain' ? 'CONSTRAINT ' : '';
},
triggerEventTypeIsConstraint: function triggerEventTypeIsConstraint(eventSpecifier) {
return eventSpecifier === 'after_constrain' ? 'CONSTRAINT ' : '';
},
expandTriggerEventSpec: function expandTriggerEventSpec(fireOnSpec) {
if (Utils._.isEmpty(fireOnSpec)) {
throw new Error('no table change events specified to trigger on');
}
expandTriggerEventSpec: function expandTriggerEventSpec(fireOnSpec) {
if (Utils._.isEmpty(fireOnSpec)) {
throw new Error('no table change events specified to trigger on');
}
return Utils._.map(fireOnSpec, function parseTriggerEventSpec(fireValue, fireKey) {
var EVENT_MAP = {
'insert': 'INSERT',
'update': 'UPDATE',
'delete': 'DELETE',
'truncate': 'TRUNCATE'
};
return Utils._.map(fireOnSpec, function parseTriggerEventSpec(fireValue, fireKey) {
var EVENT_MAP = {
'insert': 'INSERT',
'update': 'UPDATE',
'delete': 'DELETE',
'truncate': 'TRUNCATE'
};
if (!Utils._.has(EVENT_MAP, fireKey)) {
throw new Error('parseTriggerEventSpec: undefined trigger event ' + fireKey);
}
if (!Utils._.has(EVENT_MAP, fireKey)) {
throw new Error('parseTriggerEventSpec: undefined trigger event ' + fireKey);
}
var eventSpec = EVENT_MAP[fireKey];
if (eventSpec === 'UPDATE') {
if (Utils._.isArray(fireValue) && fireValue.length > 0) {
eventSpec += ' OF ' + fireValue.join(', ');
}
var eventSpec = EVENT_MAP[fireKey];
if (eventSpec === 'UPDATE') {
if (Utils._.isArray(fireValue) && fireValue.length > 0) {
eventSpec += ' OF ' + fireValue.join(', ');
}
return eventSpec;
}).join(' OR ');
},
pgListEnums: function(tableName, attrName, options) {
if (arguments.length === 1) {
options = tableName;
tableName = null;
}
var enumName = '';
return eventSpec;
}).join(' OR ');
},
if (!!tableName && !!attrName) {
enumName = ' AND t.typname=' + this.escape('enum_' + tableName + '_' + attrName) + ' ';
}
pgListEnums: function(tableName, attrName, options) {
if (arguments.length === 1) {
options = tableName;
tableName = null;
}
var query = 'SELECT t.typname enum_name, array_agg(e.enumlabel) enum_value FROM pg_type t ' +
'JOIN pg_enum e ON t.oid = e.enumtypid ' +
'JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace ' +
"WHERE n.nspname = 'public' " + enumName + ' GROUP BY 1';
var enumName = '';
return query;
},
if (!!tableName && !!attrName) {
enumName = ' AND t.typname=' + this.escape('enum_' + tableName + '_' + attrName) + ' ';
}
pgEnum: function(tableName, attr, dataType, options) {
var enumName = this.pgEscapeAndQuote('enum_' + tableName + '_' + attr)
, values;
var query = 'SELECT t.typname enum_name, array_agg(e.enumlabel) enum_value FROM pg_type t ' +
'JOIN pg_enum e ON t.oid = e.enumtypid ' +
'JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace ' +
"WHERE n.nspname = 'public' " + enumName + ' GROUP BY 1';
if (dataType.values) {
values = "ENUM('" + dataType.values.join("', '") + "')";
} else {
values = dataType.toString().match(/^ENUM\(.+\)/)[0];
}
return query;
},
var sql = 'CREATE TYPE ' + enumName + ' AS ' + values + '; ';
if (!!options && options.force === true) {
sql = this.pgEnumDrop(tableName, attr) + sql;
}
return sql;
},
pgEnum: function(tableName, attr, dataType, options) {
var enumName = this.pgEscapeAndQuote('enum_' + tableName + '_' + attr)
, values;
pgEnumAdd: function(tableName, attr, value, options) {
var enumName = this.pgEscapeAndQuote('enum_' + tableName + '_' + attr);
var sql = 'ALTER TYPE ' + enumName + ' ADD VALUE ' + this.escape(value);
if (dataType.values) {
values = "ENUM('" + dataType.values.join("', '") + "')";
} else {
values = dataType.toString().match(/^ENUM\(.+\)/)[0];
}
if (!!options.before) {
sql += ' BEFORE ' + this.escape(options.before);
}
else if (!!options.after) {
sql += ' AFTER ' + this.escape(options.after);
}
var sql = 'CREATE TYPE ' + enumName + ' AS ' + values + '; ';
if (!!options && options.force === true) {
sql = this.pgEnumDrop(tableName, attr) + sql;
}
return sql;
},
return sql;
},
pgEnumAdd: function(tableName, attr, value, options) {
var enumName = this.pgEscapeAndQuote('enum_' + tableName + '_' + attr);
var sql = 'ALTER TYPE ' + enumName + ' ADD VALUE ' + this.escape(value);
pgEnumDrop: function(tableName, attr, enumName) {
enumName = enumName || this.pgEscapeAndQuote('enum_' + tableName + '_' + attr);
return 'DROP TYPE IF EXISTS ' + enumName + '; ';
},
if (!!options.before) {
sql += ' BEFORE ' + this.escape(options.before);
}
else if (!!options.after) {
sql += ' AFTER ' + this.escape(options.after);
}
fromArray: function(text) {
text = text.replace(/^{/, '').replace(/}$/, '');
var matches = text.match(/("(?:\\.|[^"\\\\])*"|[^,]*)(?:\s*,\s*|\s*$)/ig);
return sql;
},
if (matches.length < 1) {
return [];
}
pgEnumDrop: function(tableName, attr, enumName) {
enumName = enumName || this.pgEscapeAndQuote('enum_' + tableName + '_' + attr);
return 'DROP TYPE IF EXISTS ' + enumName + '; ';
},
matches = matches.map(function(m) {
return m.replace(/",$/, '').replace(/,$/, '').replace(/(^"|"$)/, '');
});
fromArray: function(text) {
text = text.replace(/^{/, '').replace(/}$/, '');
var matches = text.match(/("(?:\\.|[^"\\\\])*"|[^,]*)(?:\s*,\s*|\s*$)/ig);
return matches.slice(0, -1);
},
if (matches.length < 1) {
return [];
}
padInt: function(i) {
return (i < 10) ? '0' + i.toString() : i.toString();
},
matches = matches.map(function(m) {
return m.replace(/",$/, '').replace(/,$/, '').replace(/(^"|"$)/, '');
});
pgDataTypeMapping: function(tableName, attr, dataType) {
return this.dataTypeMapping(tableName, attr, dataType);
},
return matches.slice(0, -1);
},
dataTypeMapping: function(tableName, attr, dataType) {
if (Utils._.includes(dataType, 'PRIMARY KEY')) {
primaryKeys[tableName].push(attr);
dataType = dataType.replace(/PRIMARY KEY/, '');
}
padInt: function(i) {
return (i < 10) ? '0' + i.toString() : i.toString();
},
if (Utils._.includes(dataType, 'SERIAL')) {
if (Utils._.includes(dataType, 'BIGINT')) {
dataType = dataType.replace(/SERIAL/, 'BIGSERIAL');
dataType = dataType.replace(/BIGINT/, '');
} else {
dataType = dataType.replace(/INTEGER/, '');
}
dataType = dataType.replace(/NOT NULL/, '');
}
pgDataTypeMapping: function(tableName, attr, dataType) {
return this.dataTypeMapping(tableName, attr, dataType);
},
if (dataType.match(/^ENUM\(/)) {
dataType = dataType.replace(/^ENUM\(.+\)/, this.pgEscapeAndQuote('enum_' + tableName + '_' + attr));
}
dataTypeMapping: function(tableName, attr, dataType) {
if (Utils._.includes(dataType, 'PRIMARY KEY')) {
primaryKeys[tableName].push(attr);
dataType = dataType.replace(/PRIMARY KEY/, '');
}
return dataType;
},
quoteIdentifier: function(identifier, force) {
if (identifier === '*') return identifier;
if (!force && this.options && this.options.quoteIdentifiers === false) { // default is `true`
// In Postgres, if tables or attributes are created double-quoted,
// they are also case sensitive. If they contain any uppercase
// characters, they must always be double-quoted. This makes it
// impossible to write queries in portable SQL if tables are created in
// this way. Hence, we strip quotes if we don't want case sensitivity.
return Utils.removeTicks(identifier, '"');
if (Utils._.includes(dataType, 'SERIAL')) {
if (Utils._.includes(dataType, 'BIGINT')) {
dataType = dataType.replace(/SERIAL/, 'BIGSERIAL');
dataType = dataType.replace(/BIGINT/, '');
} else {
return Utils.addTicks(identifier, '"');
}
},
/*
Escape a value (e.g. a string, number or date)
*/
escape: function(value, field) {
if (value && value._isSequelizeMethod) {
return this.handleSequelizeMethod(value);
dataType = dataType.replace(/INTEGER/, '');
}
dataType = dataType.replace(/NOT NULL/, '');
}
if (Utils._.isObject(value) && field && (field.type instanceof DataTypes.HSTORE || DataTypes.ARRAY.is(field.type, DataTypes.HSTORE))) {
if (field.type instanceof DataTypes.HSTORE){
return "'" + hstore.stringify(value) + "'";
} else if (DataTypes.ARRAY.is(field.type, DataTypes.HSTORE)) {
return 'ARRAY[' + Utils._.map(value, function(v){return "'" + hstore.stringify(v) + "'::hstore";}).join(',') + ']::HSTORE[]';
}
} else if(Utils._.isArray(value) && field && (field.type instanceof DataTypes.RANGE || DataTypes.ARRAY.is(field.type, DataTypes.RANGE))) {
if(field.type instanceof DataTypes.RANGE) { // escape single value
return "'" + range.stringify(value) + "'";
}
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();
}
} else if (field && (field.type instanceof DataTypes.JSON || field.type instanceof DataTypes.JSONB)) {
value = JSON.stringify(value);
} else if (Array.isArray(value) && field && DataTypes.ARRAY.is(field.type, DataTypes.JSON)) {
return 'ARRAY[' + value.map(function (v) {
return SqlString.escape(JSON.stringify(v), false, this.options.timezone, this.dialect, field);
}, this).join(',') + ']::JSON[]';
}
if (dataType.match(/^ENUM\(/)) {
dataType = dataType.replace(/^ENUM\(.+\)/, this.pgEscapeAndQuote('enum_' + tableName + '_' + attr));
}
return SqlString.escape(value, false, this.options.timezone, this.dialect, field);
},
/**
* Generates an SQL query that returns all foreign keys of a table.
*
* @param {String} tableName The name of the table.
* @param {String} schemaName The name of the schema.
* @return {String} The generated sql query.
*/
getForeignKeysQuery: function(tableName, schemaName) {
return 'SELECT conname as constraint_name, pg_catalog.pg_get_constraintdef(r.oid, true) as condef FROM pg_catalog.pg_constraint r ' +
"WHERE r.conrelid = (SELECT oid FROM pg_class WHERE relname = '" + tableName + "' LIMIT 1) AND r.contype = 'f' ORDER BY 1;";
},
/**
* Generates an SQL query that removes a foreign key from a table.
*
* @param {String} tableName The name of the table.
* @param {String} foreignKey The name of the foreign key constraint.
* @return {String} The generated sql query.
*/
dropForeignKeyQuery: function(tableName, foreignKey) {
return 'ALTER TABLE ' + this.quoteTable(tableName) + ' DROP CONSTRAINT ' + this.quoteIdentifier(foreignKey) + ';';
},
setAutocommitQuery: function(value, options) {
if (options.parent) {
return;
}
return dataType;
},
quoteIdentifier: function(identifier, force) {
if (identifier === '*') return identifier;
if (!force && this.options && this.options.quoteIdentifiers === false) { // default is `true`
// In Postgres, if tables or attributes are created double-quoted,
// they are also case sensitive. If they contain any uppercase
// characters, they must always be double-quoted. This makes it
// impossible to write queries in portable SQL if tables are created in
// this way. Hence, we strip quotes if we don't want case sensitivity.
return Utils.removeTicks(identifier, '"');
} else {
return Utils.addTicks(identifier, '"');
}
},
/*
Escape a value (e.g. a string, number or date)
*/
escape: function(value, field) {
if (value && value._isSequelizeMethod) {
return this.handleSequelizeMethod(value);
}
// POSTGRES does not support setting AUTOCOMMIT = OFF
if (!value) {
return;
}
if (Utils._.isObject(value) && field && (field.type instanceof DataTypes.HSTORE || DataTypes.ARRAY.is(field.type, DataTypes.HSTORE))) {
if (field.type instanceof DataTypes.HSTORE){
return "'" + hstore.stringify(value) + "'";
} else if (DataTypes.ARRAY.is(field.type, DataTypes.HSTORE)) {
return 'ARRAY[' + Utils._.map(value, function(v){return "'" + hstore.stringify(v) + "'::hstore";}).join(',') + ']::HSTORE[]';
}
} else if(Utils._.isArray(value) && field && (field.type instanceof DataTypes.RANGE || DataTypes.ARRAY.is(field.type, DataTypes.RANGE))) {
if(field.type instanceof DataTypes.RANGE) { // escape single value
return "'" + range.stringify(value) + "'";
}
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();
}
} else if (field && (field.type instanceof DataTypes.JSON || field.type instanceof DataTypes.JSONB)) {
value = JSON.stringify(value);
} else if (Array.isArray(value) && field && DataTypes.ARRAY.is(field.type, DataTypes.JSON)) {
return 'ARRAY[' + value.map(function (v) {
return SqlString.escape(JSON.stringify(v), false, this.options.timezone, this.dialect, field);
}, this).join(',') + ']::JSON[]';
}
return AbstractQueryGenerator.setAutocommitQuery.call(this, value, options);
return SqlString.escape(value, false, this.options.timezone, this.dialect, field);
},
/**
* Generates an SQL query that returns all foreign keys of a table.
*
* @param {String} tableName The name of the table.
* @param {String} schemaName The name of the schema.
* @return {String} The generated sql query.
*/
getForeignKeysQuery: function(tableName, schemaName) {
return 'SELECT conname as constraint_name, pg_catalog.pg_get_constraintdef(r.oid, true) as condef FROM pg_catalog.pg_constraint r ' +
"WHERE r.conrelid = (SELECT oid FROM pg_class WHERE relname = '" + tableName + "' LIMIT 1) AND r.contype = 'f' ORDER BY 1;";
},
/**
* Generates an SQL query that removes a foreign key from a table.
*
* @param {String} tableName The name of the table.
* @param {String} foreignKey The name of the foreign key constraint.
* @return {String} The generated sql query.
*/
dropForeignKeyQuery: function(tableName, foreignKey) {
return 'ALTER TABLE ' + this.quoteTable(tableName) + ' DROP CONSTRAINT ' + this.quoteIdentifier(foreignKey) + ';';
},
setAutocommitQuery: function(value, options) {
if (options.parent) {
return;
}
};
return Utils._.extend(Utils._.clone(AbstractQueryGenerator), QueryGenerator);
})();
// POSTGRES does not support setting AUTOCOMMIT = OFF
if (!value) {
return;
}
return AbstractQueryGenerator.setAutocommitQuery.call(this, value, options);
}
};
module.exports = Utils._.extend(Utils._.clone(AbstractQueryGenerator), QueryGenerator);
......@@ -66,212 +66,236 @@ function dialectSpecificFieldDatatypeMap (options, prefix) {
return fields;
}
module.exports = (function() {
var Query = function(client, sequelize, options) {
this.client = client;
this.sequelize = sequelize;
this.instance = options.instance;
this.model = options.model;
this.options = Utils._.extend({
logging: console.log,
plain: false,
raw: false
}, options || {});
this.checkLoggingOption();
};
Utils.inherit(Query, AbstractQuery);
Query.prototype.parseDialectSpecificFields = parseDialectSpecificFields;
Query.prototype.run = function(sql) {
this.sql = sql;
var self = this
, receivedError = false
, query = this.client.query(sql)
, rows = [];
this.sequelize.log('Executing (' + (this.client.uuid || 'default') + '): ' + this.sql, this.options);
var promise = new Promise(function(resolve, reject) {
query.on('row', function(row) {
rows.push(row);
});
var Query = function(client, sequelize, options) {
this.client = client;
this.sequelize = sequelize;
this.instance = options.instance;
this.model = options.model;
this.options = Utils._.extend({
logging: console.log,
plain: false,
raw: false
}, options || {});
this.checkLoggingOption();
};
Utils.inherit(Query, AbstractQuery);
query.on('error', function(err) {
receivedError = true;
err.sql = sql;
reject(self.formatError(err));
});
Query.prototype.parseDialectSpecificFields = parseDialectSpecificFields;
query.on('end', function(result) {
if (receivedError) {
return;
}
Query.prototype.run = function(sql) {
this.sql = sql;
resolve([rows, sql, result]);
});
}).spread(function(rows, sql, result) {
var results = rows
, isTableNameQuery = (sql.indexOf('SELECT table_name FROM information_schema.tables') === 0)
, isRelNameQuery = (sql.indexOf('SELECT relname FROM pg_class WHERE oid IN') === 0)
, dialectSpecificFields
, isDialectSpecificField = Utils._.memoize(function (key) { return dialectSpecificFields.hasOwnProperty(key); });
if (isTableNameQuery || isRelNameQuery) {
if (isRelNameQuery) {
results = rows.map(function(row) {
return {
name: row.relname,
tableName: row.relname.split('_')[0]
};
});
} else {
results = rows.map(function(row) { return Utils._.values(row); });
}
return results;
var self = this
, receivedError = false
, query = this.client.query(sql)
, rows = [];
this.sequelize.log('Executing (' + (this.client.uuid || 'default') + '): ' + this.sql, this.options);
var promise = new Promise(function(resolve, reject) {
query.on('row', function(row) {
rows.push(row);
});
query.on('error', function(err) {
receivedError = true;
err.sql = sql;
reject(self.formatError(err));
});
query.on('end', function(result) {
if (receivedError) {
return;
}
if (rows[0] && rows[0].sequelize_caught_exception !== undefined) {
if (rows[0].sequelize_caught_exception !== null) {
throw new self.formatError({
code: '23505',
detail: rows[0].sequelize_caught_exception
});
} else {
rows = rows.map(function (row) {
delete row.sequelize_caught_exception;
return row;
});
}
resolve([rows, sql, result]);
});
}).spread(function(rows, sql, result) {
var results = rows
, isTableNameQuery = (sql.indexOf('SELECT table_name FROM information_schema.tables') === 0)
, isRelNameQuery = (sql.indexOf('SELECT relname FROM pg_class WHERE oid IN') === 0)
, dialectSpecificFields
, isDialectSpecificField = Utils._.memoize(function (key) { return dialectSpecificFields.hasOwnProperty(key); });
if (isTableNameQuery || isRelNameQuery) {
if (isRelNameQuery) {
results = rows.map(function(row) {
return {
name: row.relname,
tableName: row.relname.split('_')[0]
};
});
} else {
results = rows.map(function(row) { return Utils._.values(row); });
}
return results;
}
if (self.isShowIndexesQuery()) {
results.forEach(function (result) {
var attributes = /ON .*? (?:USING .*?\s)?\((.*)\)/gi.exec(result.definition)[1].split(',')
, field
, attribute
, columns;
// Map column index in table to column name
columns = Utils._.zipObject(
result.column_indexes,
self.sequelize.queryInterface.QueryGenerator.fromArray(result.column_names)
);
delete result.column_indexes;
delete result.column_names;
// Indkey is the order of attributes in the index, specified by a string of attribute indexes
result.fields = result.indkey.split(' ').map(function (indKey, index) {
field = columns[indKey];
attribute = attributes[index];
return {
attribute: field,
collate: attribute.match(/COLLATE "(.*?)"/) ? /COLLATE "(.*?)"/.exec(attribute)[1] : undefined,
order: attribute.indexOf('DESC') !== -1 ? 'DESC' : attribute.indexOf('ASC') !== -1 ? 'ASC': undefined,
length: undefined
};
});
delete result.columns;
if (rows[0] && rows[0].sequelize_caught_exception !== undefined) {
if (rows[0].sequelize_caught_exception !== null) {
throw new self.formatError({
code: '23505',
detail: rows[0].sequelize_caught_exception
});
return results;
} else if (self.isForeignKeysQuery()) {
result = [];
rows.forEach(function(row) {
var defParts;
if (row.condef !== undefined && (defParts = row.condef.match(/FOREIGN KEY \((.+)\) REFERENCES (.+)\((.+)\)( ON (UPDATE|DELETE) (CASCADE|RESTRICT))?( ON (UPDATE|DELETE) (CASCADE|RESTRICT))?/))) {
row.id = row.constraint_name;
row.table = defParts[2];
row.from = defParts[1];
row.to = defParts[3];
var i;
for (i=5;i<=8;i+=3) {
if (/(UPDATE|DELETE)/.test(defParts[i])) {
row['on_'+defParts[i].toLowerCase()] = defParts[i+1];
}
} else {
rows = rows.map(function (row) {
delete row.sequelize_caught_exception;
return row;
});
}
}
if (self.isShowIndexesQuery()) {
results.forEach(function (result) {
var attributes = /ON .*? (?:USING .*?\s)?\((.*)\)/gi.exec(result.definition)[1].split(',')
, field
, attribute
, columns;
// Map column index in table to column name
columns = Utils._.zipObject(
result.column_indexes,
self.sequelize.queryInterface.QueryGenerator.fromArray(result.column_names)
);
delete result.column_indexes;
delete result.column_names;
// Indkey is the order of attributes in the index, specified by a string of attribute indexes
result.fields = result.indkey.split(' ').map(function (indKey, index) {
field = columns[indKey];
attribute = attributes[index];
return {
attribute: field,
collate: attribute.match(/COLLATE "(.*?)"/) ? /COLLATE "(.*?)"/.exec(attribute)[1] : undefined,
order: attribute.indexOf('DESC') !== -1 ? 'DESC' : attribute.indexOf('ASC') !== -1 ? 'ASC': undefined,
length: undefined
};
});
delete result.columns;
});
return results;
} else if (self.isForeignKeysQuery()) {
result = [];
rows.forEach(function(row) {
var defParts;
if (row.condef !== undefined && (defParts = row.condef.match(/FOREIGN KEY \((.+)\) REFERENCES (.+)\((.+)\)( ON (UPDATE|DELETE) (CASCADE|RESTRICT))?( ON (UPDATE|DELETE) (CASCADE|RESTRICT))?/))) {
row.id = row.constraint_name;
row.table = defParts[2];
row.from = defParts[1];
row.to = defParts[3];
var i;
for (i=5;i<=8;i+=3) {
if (/(UPDATE|DELETE)/.test(defParts[i])) {
row['on_'+defParts[i].toLowerCase()] = defParts[i+1];
}
}
result.push(row);
});
return result;
} else if (self.isSelectQuery()) {
// Postgres will treat tables as case-insensitive, so fix the case
// of the returned values to match attributes
if (self.options.raw === false && self.sequelize.options.quoteIdentifiers === false) {
var attrsMap = Utils._.reduce(self.model.attributes, function(m, v, k) { m[k.toLowerCase()] = k; return m; }, {});
rows.forEach(function(row) {
Utils._.keys(row).forEach(function(key) {
var targetAttr = attrsMap[key];
if (typeof targetAttr === 'string' && targetAttr !== key) {
row[targetAttr] = row[key];
delete row[key];
}
});
});
}
if (!!self.model && rows.length > 0) {
dialectSpecificFields = dialectSpecificFieldDatatypeMap({
callee: self.model,
include: self.options.include,
types: dialectSpecificTypes
result.push(row);
});
return result;
} else if (self.isSelectQuery()) {
// Postgres will treat tables as case-insensitive, so fix the case
// of the returned values to match attributes
if (self.options.raw === false && self.sequelize.options.quoteIdentifiers === false) {
var attrsMap = Utils._.reduce(self.model.attributes, function(m, v, k) { m[k.toLowerCase()] = k; return m; }, {});
rows.forEach(function(row) {
Utils._.keys(row).forEach(function(key) {
var targetAttr = attrsMap[key];
if (typeof targetAttr === 'string' && targetAttr !== key) {
row[targetAttr] = row[key];
delete row[key];
}
});
});
}
// check whether custom fields exist in responses model
if (!Utils._.isEmpty(dialectSpecificFields)) {
rows.forEach(function (row, rowId, rows) {
Utils._.each(row, function (value, key) {
if (isDialectSpecificField(key))
rows[rowId][key] =
parseDialectSpecificFields[dialectSpecificFields[key].type](value, { dataType: dialectSpecificFields[key].dataType });
});
if (!!self.model && rows.length > 0) {
dialectSpecificFields = dialectSpecificFieldDatatypeMap({
callee: self.model,
include: self.options.include,
types: dialectSpecificTypes
});
// check whether custom fields exist in responses model
if (!Utils._.isEmpty(dialectSpecificFields)) {
rows.forEach(function (row, rowId, rows) {
Utils._.each(row, function (value, key) {
if (isDialectSpecificField(key))
rows[rowId][key] =
parseDialectSpecificFields[dialectSpecificFields[key].type](value, { dataType: dialectSpecificFields[key].dataType });
});
}
});
}
}
return self.handleSelectQuery(rows);
} else if (QueryTypes.DESCRIBE === self.options.type) {
result = {};
return self.handleSelectQuery(rows);
} else if (QueryTypes.DESCRIBE === self.options.type) {
result = {};
rows.forEach(function(_result) {
result[_result.Field] = {
type: _result.Type.toUpperCase(),
allowNull: (_result.Null === 'YES'),
defaultValue: _result.Default,
special: (!!_result.special ? self.sequelize.queryInterface.QueryGenerator.fromArray(_result.special) : [])
};
rows.forEach(function(_result) {
result[_result.Field] = {
type: _result.Type.toUpperCase(),
allowNull: (_result.Null === 'YES'),
defaultValue: _result.Default,
special: (!!_result.special ? self.sequelize.queryInterface.QueryGenerator.fromArray(_result.special) : [])
};
if (result[_result.Field].type === 'BOOLEAN') {
result[_result.Field].defaultValue = { 'false': false, 'true': true }[result[_result.Field].defaultValue];
if (result[_result.Field].type === 'BOOLEAN') {
result[_result.Field].defaultValue = { 'false': false, 'true': true }[result[_result.Field].defaultValue];
if (result[_result.Field].defaultValue === undefined) {
result[_result.Field].defaultValue = null;
}
if (result[_result.Field].defaultValue === undefined) {
result[_result.Field].defaultValue = null;
}
}
if (typeof result[_result.Field].defaultValue === 'string') {
result[_result.Field].defaultValue = result[_result.Field].defaultValue.replace(/'/g, '');
if (typeof result[_result.Field].defaultValue === 'string') {
result[_result.Field].defaultValue = result[_result.Field].defaultValue.replace(/'/g, '');
if (result[_result.Field].defaultValue.indexOf('::') > -1) {
var split = result[_result.Field].defaultValue.split('::');
if (split[1].toLowerCase() !== 'regclass)') {
result[_result.Field].defaultValue = split[0];
}
if (result[_result.Field].defaultValue.indexOf('::') > -1) {
var split = result[_result.Field].defaultValue.split('::');
if (split[1].toLowerCase() !== 'regclass)') {
result[_result.Field].defaultValue = split[0];
}
}
}
});
return result;
} else if (self.isShowOrDescribeQuery()) {
return results;
} else if (QueryTypes.BULKUPDATE === self.options.type) {
if (!self.options.returning) {
return parseInt(result.rowCount, 10);
}
if (!!self.model && rows.length > 0) {
dialectSpecificFields = dialectSpecificFieldDatatypeMap({
callee: self.model,
types: dialectSpecificTypes
});
return result;
} else if (self.isShowOrDescribeQuery()) {
return results;
} else if (QueryTypes.BULKUPDATE === self.options.type) {
if (!self.options.returning) {
return parseInt(result.rowCount, 10);
// check whether custom fields exist in responses model
if (!Utils._.isEmpty(dialectSpecificFields)) {
rows.forEach(function (row, rowId, rows) {
Utils._.each(row, function (value, key) {
if (isDialectSpecificField(key))
rows[rowId][key] =
parseDialectSpecificFields[dialectSpecificFields[key].type](value, { dataType: dialectSpecificFields[key].dataType });
});
});
}
}
if (!!self.model && rows.length > 0) {
return self.handleSelectQuery(rows);
} else if (QueryTypes.BULKDELETE === self.options.type) {
return parseInt(result.rowCount, 10);
} else if (self.isUpsertQuery()) {
return rows[0].sequelize_upsert;
} else if (self.isInsertQuery() || self.isUpdateQuery()) {
if (self.instance && self.instance.dataValues) {
if (self.model) {
dialectSpecificFields = dialectSpecificFieldDatatypeMap({
callee: self.model,
types: dialectSpecificTypes
......@@ -279,156 +303,130 @@ module.exports = (function() {
// check whether custom fields exist in responses model
if (!Utils._.isEmpty(dialectSpecificFields)) {
rows.forEach(function (row, rowId, rows) {
Utils._.each(row, function (value, key) {
if (isDialectSpecificField(key))
rows[rowId][key] =
parseDialectSpecificFields[dialectSpecificFields[key].type](value, { dataType: dialectSpecificFields[key].dataType });
});
Utils._.each(rows[0], function (value, key) {
if (isDialectSpecificField(key))
rows[0][key] =
parseDialectSpecificFields[dialectSpecificFields[key].type](value, { dataType: dialectSpecificFields[key].dataType });
});
}
}
return self.handleSelectQuery(rows);
} else if (QueryTypes.BULKDELETE === self.options.type) {
return parseInt(result.rowCount, 10);
} else if (self.isUpsertQuery()) {
return rows[0].sequelize_upsert;
} else if (self.isInsertQuery() || self.isUpdateQuery()) {
if (self.instance && self.instance.dataValues) {
if (self.model) {
dialectSpecificFields = dialectSpecificFieldDatatypeMap({
callee: self.model,
types: dialectSpecificTypes
});
// check whether custom fields exist in responses model
if (!Utils._.isEmpty(dialectSpecificFields)) {
Utils._.each(rows[0], function (value, key) {
if (isDialectSpecificField(key))
rows[0][key] =
parseDialectSpecificFields[dialectSpecificFields[key].type](value, { dataType: dialectSpecificFields[key].dataType });
});
}
}
for (var key in rows[0]) {
if (rows[0].hasOwnProperty(key)) {
var record = rows[0][key];
for (var key in rows[0]) {
if (rows[0].hasOwnProperty(key)) {
var record = rows[0][key];
var attr = Utils._.find(self.model.rawAttributes, function (attribute) {
return attribute.fieldName === key || attribute.field === key;
});
var attr = Utils._.find(self.model.rawAttributes, function (attribute) {
return attribute.fieldName === key || attribute.field === key;
});
self.instance.dataValues[attr && attr.fieldName || key] = record;
}
self.instance.dataValues[attr && attr.fieldName || key] = record;
}
}
return self.instance || (rows && ((self.options.plain && rows[0]) || rows)) || undefined;
} else if (self.isVersionQuery()) {
return results[0].version;
} else if (self.isRawQuery()) {
return [rows, result];
} else {
return results;
}
});
return promise;
};
Query.prototype.formatError = function (err) {
var match
, table
, index
, fields
, errors
, message;
var code = err.code || err.sqlState
, errMessage = err.message || err.messagePrimary
, errDetail = err.detail || err.messageDetail;
switch (code) {
case '23503':
index = errMessage.match(/violates foreign key constraint \"(.+?)\"/);
index = index ? index[1] : undefined;
table = errMessage.match(/on table \"(.+?)\"/);
table = table ? table[1] : undefined;
return new sequelizeErrors.ForeignKeyConstraintError({
fields: null,
index: index,
table: table,
parent: err
});
case '23505':
// there are multiple different formats of error messages for this error code
// this regex should check at least two
match = errDetail.match(/Key \((.*?)\)=\((.*?)\)/);
if (match) {
fields = Utils._.zipObject(match[1].split(', '), match[2].split(', '));
errors = [];
message = 'Validation error';
Utils._.forOwn(fields, function(value, field) {
errors.push(new sequelizeErrors.ValidationErrorItem(
field + ' must be unique', 'unique violation', field, value));
});
if (this.model && this.model.uniqueKeys) {
Utils._.forOwn(this.model.uniqueKeys, function(constraint) {
if (Utils._.isEqual(constraint.fields, Object.keys(fields)) && !!constraint.msg) {
message = constraint.msg;
return false;
}
});
}
return self.instance || (rows && ((self.options.plain && rows[0]) || rows)) || undefined;
} else if (self.isVersionQuery()) {
return results[0].version;
} else if (self.isRawQuery()) {
return [rows, result];
} else {
return results;
}
});
return new sequelizeErrors.UniqueConstraintError({
message: message,
errors: errors,
parent: err,
fields: fields
});
} else {
return new sequelizeErrors.UniqueConstraintError({
message: errMessage,
parent: err
});
}
return promise;
};
break;
case '23P01':
match = errDetail.match(/Key \((.*?)\)=\((.*?)\)/);
Query.prototype.formatError = function (err) {
var match
, table
, index
, fields
, errors
, message;
var code = err.code || err.sqlState
, errMessage = err.message || err.messagePrimary
, errDetail = err.detail || err.messageDetail;
switch (code) {
case '23503':
index = errMessage.match(/violates foreign key constraint \"(.+?)\"/);
index = index ? index[1] : undefined;
table = errMessage.match(/on table \"(.+?)\"/);
table = table ? table[1] : undefined;
return new sequelizeErrors.ForeignKeyConstraintError({
fields: null,
index: index,
table: table,
parent: err
});
case '23505':
// there are multiple different formats of error messages for this error code
// this regex should check at least two
match = errDetail.match(/Key \((.*?)\)=\((.*?)\)/);
if (match) {
fields = Utils._.zipObject(match[1].split(', '), match[2].split(', '));
errors = [];
message = 'Validation error';
Utils._.forOwn(fields, function(value, field) {
errors.push(new sequelizeErrors.ValidationErrorItem(
field + ' must be unique', 'unique violation', field, value));
});
if (match) {
fields = Utils._.zipObject(match[1].split(', '), match[2].split(', '));
if (this.model && this.model.uniqueKeys) {
Utils._.forOwn(this.model.uniqueKeys, function(constraint) {
if (Utils._.isEqual(constraint.fields, Object.keys(fields)) && !!constraint.msg) {
message = constraint.msg;
return false;
}
});
}
message = 'Exclusion constraint error';
return new sequelizeErrors.ExclusionConstraintError({
return new sequelizeErrors.UniqueConstraintError({
message: message,
constraint: err.constraint,
fields: fields,
table: err.table,
errors: errors,
parent: err,
fields: fields
});
} else {
return new sequelizeErrors.UniqueConstraintError({
message: errMessage,
parent: err
});
}
default:
return new sequelizeErrors.DatabaseError(err);
}
};
break;
case '23P01':
match = errDetail.match(/Key \((.*?)\)=\((.*?)\)/);
if (match) {
fields = Utils._.zipObject(match[1].split(', '), match[2].split(', '));
}
message = 'Exclusion constraint error';
return new sequelizeErrors.ExclusionConstraintError({
message: message,
constraint: err.constraint,
fields: fields,
table: err.table,
parent: err
});
default:
return new sequelizeErrors.DatabaseError(err);
}
};
Query.prototype.isForeignKeysQuery = function() {
return /SELECT conname as constraint_name, pg_catalog\.pg_get_constraintdef\(r\.oid, true\) as condef FROM pg_catalog\.pg_constraint r WHERE r\.conrelid = \(SELECT oid FROM pg_class WHERE relname = '.*' LIMIT 1\) AND r\.contype = 'f' ORDER BY 1;/.test(this.sql);
};
Query.prototype.isForeignKeysQuery = function() {
return /SELECT conname as constraint_name, pg_catalog\.pg_get_constraintdef\(r\.oid, true\) as condef FROM pg_catalog\.pg_constraint r WHERE r\.conrelid = \(SELECT oid FROM pg_class WHERE relname = '.*' LIMIT 1\) AND r\.contype = 'f' ORDER BY 1;/.test(this.sql);
};
Query.prototype.getInsertIdField = function() {
return 'id';
};
Query.prototype.getInsertIdField = function() {
return 'id';
};
return Query;
})();
module.exports = Query;
......@@ -3,60 +3,64 @@
var Utils = require('../../utils'),
moment = require('moment');
module.exports = {
stringify: function (data) {
if (data === null) return null;
function stringify (data) {
if (data === null) return null;
if (!Utils._.isArray(data) || data.length !== 2) return '';
if (!Utils._.isArray(data) || data.length !== 2) return '';
if (Utils._.any(data, Utils._.isNull)) return '';
if (Utils._.any(data, Utils._.isNull)) return '';
if (data.hasOwnProperty('inclusive')) {
if (!data.inclusive) data.inclusive = [false, false];
else if (data.inclusive === true) data.inclusive = [true, true];
} else {
data.inclusive = [false, false];
}
if (data.hasOwnProperty('inclusive')) {
if (!data.inclusive) data.inclusive = [false, false];
else if (data.inclusive === true) data.inclusive = [true, true];
} else {
data.inclusive = [false, false];
Utils._.each(data, function (value, index) {
if (Utils._.isObject(value)) {
if (value.hasOwnProperty('inclusive')) data.inclusive[index] = !!value.inclusive;
if (value.hasOwnProperty('value')) data[index] = value.value;
}
});
return (data.inclusive[0] ? '[' : '(') + JSON.stringify(data[0]) + ',' + JSON.stringify(data[1]) + (data.inclusive[1] ? ']' : ')');
}
function parse (value, AttributeType) {
if (value === null) return null;
if(typeof AttributeType === 'function') AttributeType = new AttributeType();
AttributeType = AttributeType || ''; // if attribute is not defined, assign empty string in order to prevent
// AttributeType.toString() to fail with uncaught exception later in the code
var result = value
.substring(1, value.length - 1)
.split(',', 2);
Utils._.each(data, function (value, index) {
if (Utils._.isObject(value)) {
if (value.hasOwnProperty('inclusive')) data.inclusive[index] = !!value.inclusive;
if (value.hasOwnProperty('value')) data[index] = value.value;
if (result.length !== 2) return value;
result = result
.map(function (value) {
switch (AttributeType.toString()) {
case 'int4range':
return parseInt(value, 10);
case 'numrange':
return parseFloat(value);
case 'daterange':
case 'tsrange':
case 'tstzrange':
return moment(value).toDate();
}
return value;
});
return (data.inclusive[0] ? '[' : '(') + JSON.stringify(data[0]) + ',' + JSON.stringify(data[1]) + (data.inclusive[1] ? ']' : ')');
},
parse: function (value, AttributeType) {
if (value === null) return null;
if(typeof AttributeType === 'function') AttributeType = new AttributeType();
AttributeType = AttributeType || ''; // if attribute is not defined, assign empty string in order to prevent
// AttributeType.toString() to fail with uncaught exception later in the code
var result = value
.substring(1, value.length - 1)
.split(',', 2);
if (result.length !== 2) return value;
result = result
.map(function (value) {
switch (AttributeType.toString()) {
case 'int4range':
return parseInt(value, 10);
case 'numrange':
return parseFloat(value);
case 'daterange':
case 'tsrange':
case 'tstzrange':
return moment(value).toDate();
}
return value;
});
result.inclusive = [(value[0] === '['), (value[value.length - 1] === ']')];
return result;
}
result.inclusive = [(value[0] === '['), (value[value.length - 1] === ']')];
return result;
}
module.exports = {
stringify: stringify,
parse: parse
};
......@@ -11,433 +11,431 @@ var MySqlQueryGenerator = Utils._.extend(
Utils._.clone(require('../mysql/query-generator'))
);
module.exports = (function() {
var QueryGenerator = {
options: {},
dialect: 'sqlite',
createSchema: function() {
var query = "SELECT name FROM `sqlite_master` WHERE type='table' and name!='sqlite_sequence';";
return Utils._.template(query)({});
},
showSchemasQuery: function() {
return "SELECT name FROM `sqlite_master` WHERE type='table' and name!='sqlite_sequence';";
},
versionQuery: function() {
return 'SELECT sqlite_version() as `version`';
},
createTableQuery: function(tableName, attributes, options) {
options = options || {};
var query = 'CREATE TABLE IF NOT EXISTS <%= table %> (<%= attributes%>)'
, primaryKeys = []
, needsMultiplePrimaryKeys = (Utils._.values(attributes).filter(function(definition) {
return Utils._.includes(definition, 'PRIMARY KEY');
}).length > 1)
, attrStr = [];
for (var attr in attributes) {
if (attributes.hasOwnProperty(attr)) {
var dataType = attributes[attr];
var containsAutoIncrement = Utils._.includes(dataType, 'AUTOINCREMENT');
if (containsAutoIncrement) {
dataType = dataType.replace(/BIGINT/, 'INTEGER');
}
var QueryGenerator = {
options: {},
dialect: 'sqlite',
createSchema: function() {
var query = "SELECT name FROM `sqlite_master` WHERE type='table' and name!='sqlite_sequence';";
return Utils._.template(query)({});
},
showSchemasQuery: function() {
return "SELECT name FROM `sqlite_master` WHERE type='table' and name!='sqlite_sequence';";
},
versionQuery: function() {
return 'SELECT sqlite_version() as `version`';
},
createTableQuery: function(tableName, attributes, options) {
options = options || {};
var query = 'CREATE TABLE IF NOT EXISTS <%= table %> (<%= attributes%>)'
, primaryKeys = []
, needsMultiplePrimaryKeys = (Utils._.values(attributes).filter(function(definition) {
return Utils._.includes(definition, 'PRIMARY KEY');
}).length > 1)
, attrStr = [];
for (var attr in attributes) {
if (attributes.hasOwnProperty(attr)) {
var dataType = attributes[attr];
var containsAutoIncrement = Utils._.includes(dataType, 'AUTOINCREMENT');
if (containsAutoIncrement) {
dataType = dataType.replace(/BIGINT/, 'INTEGER');
}
var dataTypeString = dataType;
if (Utils._.includes(dataType, 'PRIMARY KEY')) {
if (Utils._.includes(dataType, 'INTEGER')) { // Only INTEGER is allowed for primary key, see https://github.com/sequelize/sequelize/issues/969 (no lenght, unsigned etc)
dataTypeString = containsAutoIncrement ? 'INTEGER PRIMARY KEY AUTOINCREMENT' : 'INTEGER PRIMARY KEY';
}
var dataTypeString = dataType;
if (Utils._.includes(dataType, 'PRIMARY KEY')) {
if (Utils._.includes(dataType, 'INTEGER')) { // Only INTEGER is allowed for primary key, see https://github.com/sequelize/sequelize/issues/969 (no lenght, unsigned etc)
dataTypeString = containsAutoIncrement ? 'INTEGER PRIMARY KEY AUTOINCREMENT' : 'INTEGER PRIMARY KEY';
}
if (needsMultiplePrimaryKeys) {
primaryKeys.push(attr);
dataTypeString = dataType.replace(/PRIMARY KEY/, 'NOT NULL');
}
if (needsMultiplePrimaryKeys) {
primaryKeys.push(attr);
dataTypeString = dataType.replace(/PRIMARY KEY/, 'NOT NULL');
}
attrStr.push(this.quoteIdentifier(attr) + ' ' + dataTypeString);
}
attrStr.push(this.quoteIdentifier(attr) + ' ' + dataTypeString);
}
}
var values = {
table: this.quoteTable(tableName),
attributes: attrStr.join(', '),
charset: (options.charset ? 'DEFAULT CHARSET=' + options.charset : '')
}
, pkString = primaryKeys.map(function(pk) { return this.quoteIdentifier(pk); }.bind(this)).join(', ');
var values = {
table: this.quoteTable(tableName),
attributes: attrStr.join(', '),
charset: (options.charset ? 'DEFAULT CHARSET=' + options.charset : '')
}
, pkString = primaryKeys.map(function(pk) { return this.quoteIdentifier(pk); }.bind(this)).join(', ');
if (!!options.uniqueKeys) {
Utils._.each(options.uniqueKeys, function(columns) {
if (!columns.singleField) { // If it's a single field its handled in column def, not as an index
values.attributes += ', UNIQUE (' + columns.fields.join(', ') + ')';
}
});
}
if (!!options.uniqueKeys) {
Utils._.each(options.uniqueKeys, function(columns) {
if (!columns.singleField) { // If it's a single field its handled in column def, not as an index
values.attributes += ', UNIQUE (' + columns.fields.join(', ') + ')';
}
});
}
if (pkString.length > 0) {
values.attributes += ', PRIMARY KEY (' + pkString + ')';
}
if (pkString.length > 0) {
values.attributes += ', PRIMARY KEY (' + pkString + ')';
}
var sql = Utils._.template(query)(values).trim() + ';';
return this.replaceBooleanDefaults(sql);
},
booleanValue: function(value){
return !!value ? 1 : 0;
},
addLimitAndOffset: function(options){
var fragment = '';
if (options.offset && !options.limit) {
fragment += ' LIMIT ' + options.offset + ', ' + 10000000000000;
} else if (options.limit) {
if (options.offset) {
fragment += ' LIMIT ' + options.offset + ', ' + options.limit;
} else {
fragment += ' LIMIT ' + options.limit;
}
var sql = Utils._.template(query)(values).trim() + ';';
return this.replaceBooleanDefaults(sql);
},
booleanValue: function(value){
return !!value ? 1 : 0;
},
addLimitAndOffset: function(options){
var fragment = '';
if (options.offset && !options.limit) {
fragment += ' LIMIT ' + options.offset + ', ' + 10000000000000;
} else if (options.limit) {
if (options.offset) {
fragment += ' LIMIT ' + options.offset + ', ' + options.limit;
} else {
fragment += ' LIMIT ' + options.limit;
}
}
return fragment;
},
return fragment;
},
addColumnQuery: function(table, key, dataType) {
var query = 'ALTER TABLE <%= table %> ADD <%= attribute %>;'
, attributes = {};
addColumnQuery: function(table, key, dataType) {
var query = 'ALTER TABLE <%= table %> ADD <%= attribute %>;'
, attributes = {};
attributes[key] = dataType;
var fields = this.attributesToSQL(attributes, {
context: 'addColumn'
});
var attribute = Utils._.template('<%= key %> <%= definition %>')({
key: this.quoteIdentifier(key),
definition: fields[key]
});
var sql = Utils._.template(query)({
table: this.quoteTable(table),
attribute: attribute
attributes[key] = dataType;
var fields = this.attributesToSQL(attributes, {
context: 'addColumn'
});
var attribute = Utils._.template('<%= key %> <%= definition %>')({
key: this.quoteIdentifier(key),
definition: fields[key]
});
return this.replaceBooleanDefaults(sql);
},
var sql = Utils._.template(query)({
table: this.quoteTable(table),
attribute: attribute
});
showTablesQuery: function() {
return "SELECT name FROM `sqlite_master` WHERE type='table' and name!='sqlite_sequence';";
},
return this.replaceBooleanDefaults(sql);
},
upsertQuery: function (tableName, insertValues, updateValues, where, rawAttributes, options) {
options.ignore = true;
var sql = this.insertQuery(tableName, insertValues, rawAttributes, options) + ' ' + this.updateQuery(tableName, updateValues, where, options, rawAttributes);
showTablesQuery: function() {
return "SELECT name FROM `sqlite_master` WHERE type='table' and name!='sqlite_sequence';";
},
return sql;
},
upsertQuery: function (tableName, insertValues, updateValues, where, rawAttributes, options) {
options.ignore = true;
var sql = this.insertQuery(tableName, insertValues, rawAttributes, options) + ' ' + this.updateQuery(tableName, updateValues, where, options, rawAttributes);
bulkInsertQuery: function(tableName, attrValueHashes, options) {
var query = 'INSERT<%= ignoreDuplicates %> INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %>;'
, tuples = []
, allAttributes = [];
return sql;
},
Utils._.forEach(attrValueHashes, function(attrValueHash) {
Utils._.forOwn(attrValueHash, function(value, key) {
if (allAttributes.indexOf(key) === -1) allAttributes.push(key);
});
});
Utils._.forEach(attrValueHashes, function(attrValueHash) {
tuples.push('(' +
allAttributes.map(function (key) {
return this.escape(attrValueHash[key]);
}.bind(this)).join(',') +
')');
}.bind(this));
var replacements = {
ignoreDuplicates: options && options.ignoreDuplicates ? ' OR IGNORE' : '',
table: this.quoteTable(tableName),
attributes: allAttributes.map(function(attr) {
return this.quoteIdentifier(attr);
}.bind(this)).join(','),
tuples: tuples
};
bulkInsertQuery: function(tableName, attrValueHashes, options) {
var query = 'INSERT<%= ignoreDuplicates %> INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %>;'
, tuples = []
, allAttributes = [];
return Utils._.template(query)(replacements);
},
Utils._.forEach(attrValueHashes, function(attrValueHash) {
Utils._.forOwn(attrValueHash, function(value, key) {
if (allAttributes.indexOf(key) === -1) allAttributes.push(key);
});
});
Utils._.forEach(attrValueHashes, function(attrValueHash) {
tuples.push('(' +
allAttributes.map(function (key) {
return this.escape(attrValueHash[key]);
}.bind(this)).join(',') +
')');
}.bind(this));
var replacements = {
ignoreDuplicates: options && options.ignoreDuplicates ? ' OR IGNORE' : '',
table: this.quoteTable(tableName),
attributes: allAttributes.map(function(attr) {
return this.quoteIdentifier(attr);
}.bind(this)).join(','),
tuples: tuples
};
return Utils._.template(query)(replacements);
},
updateQuery: function(tableName, attrValueHash, where, options) {
options = options || {};
_.defaults(options, this.options);
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, options.omitNull, options);
var query = 'UPDATE <%= table %> SET <%= values %> <%= where %>'
, values = [];
for (var key in attrValueHash) {
var value = attrValueHash[key];
values.push(this.quoteIdentifier(key) + '=' + this.escape(value));
}
updateQuery: function(tableName, attrValueHash, where, options) {
options = options || {};
_.defaults(options, this.options);
var replacements = {
table: this.quoteTable(tableName),
values: values.join(','),
where: this.whereQuery(where)
};
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, options.omitNull, options);
return Utils._.template(query)(replacements).trim();
},
var query = 'UPDATE <%= table %> SET <%= values %> <%= where %>'
, values = [];
deleteQuery: function(tableName, where, options) {
options = options || {};
for (var key in attrValueHash) {
var value = attrValueHash[key];
values.push(this.quoteIdentifier(key) + '=' + this.escape(value));
}
var query = 'DELETE FROM <%= table %><%= where %>';
var replacements = {
table: this.quoteTable(tableName),
where: this.getWhereConditions(where)
};
var replacements = {
table: this.quoteTable(tableName),
values: values.join(','),
where: this.whereQuery(where)
};
if (replacements.where) {
replacements.where = ' WHERE ' + replacements.where;
}
return Utils._.template(query)(replacements).trim();
},
return Utils._.template(query)(replacements);
},
deleteQuery: function(tableName, where, options) {
options = options || {};
attributesToSQL: function(attributes) {
var result = {};
var query = 'DELETE FROM <%= table %><%= where %>';
var replacements = {
table: this.quoteTable(tableName),
where: this.getWhereConditions(where)
};
for (var name in attributes) {
var dataType = attributes[name];
var fieldName = dataType.field || name;
if (replacements.where) {
replacements.where = ' WHERE ' + replacements.where;
}
if (Utils._.isObject(dataType)) {
var template = '<%= type %>'
, replacements = { type: dataType.type };
return Utils._.template(query)(replacements);
},
if (dataType.type instanceof DataTypes.ENUM) {
replacements.type = 'TEXT';
attributesToSQL: function(attributes) {
var result = {};
if (!(Array.isArray(dataType.values) && (dataType.values.length > 0))) {
throw new Error("Values for ENUM haven't been defined.");
}
}
for (var name in attributes) {
var dataType = attributes[name];
var fieldName = dataType.field || name;
if (dataType.hasOwnProperty('allowNull') && !dataType.allowNull) {
template += ' NOT NULL';
}
if (Utils._.isObject(dataType)) {
var template = '<%= type %>'
, replacements = { type: dataType.type };
if (Utils.defaultValueSchemable(dataType.defaultValue)) {
// TODO thoroughly check that DataTypes.NOW will properly
// get populated on all databases as DEFAULT value
// i.e. mysql requires: DEFAULT CURRENT_TIMESTAMP
template += ' DEFAULT <%= defaultValue %>';
replacements.defaultValue = this.escape(dataType.defaultValue);
}
if (dataType.type instanceof DataTypes.ENUM) {
replacements.type = 'TEXT';
if (dataType.unique === true) {
template += ' UNIQUE';
}
if (!(Array.isArray(dataType.values) && (dataType.values.length > 0))) {
throw new Error("Values for ENUM haven't been defined.");
}
}
if (dataType.primaryKey) {
template += ' PRIMARY KEY';
if (dataType.hasOwnProperty('allowNull') && !dataType.allowNull) {
template += ' NOT NULL';
if (dataType.autoIncrement) {
template += ' AUTOINCREMENT';
}
}
if (Utils.defaultValueSchemable(dataType.defaultValue)) {
// TODO thoroughly check that DataTypes.NOW will properly
// get populated on all databases as DEFAULT value
// i.e. mysql requires: DEFAULT CURRENT_TIMESTAMP
template += ' DEFAULT <%= defaultValue %>';
replacements.defaultValue = this.escape(dataType.defaultValue);
}
if(dataType.references) {
dataType = Utils.formatReferences(dataType);
template += ' REFERENCES <%= referencesTable %> (<%= referencesKey %>)';
replacements.referencesTable = this.quoteTable(dataType.references.model);
if (dataType.unique === true) {
template += ' UNIQUE';
if(dataType.references.key) {
replacements.referencesKey = this.quoteIdentifier(dataType.references.key);
} else {
replacements.referencesKey = this.quoteIdentifier('id');
}
if (dataType.primaryKey) {
template += ' PRIMARY KEY';
if (dataType.autoIncrement) {
template += ' AUTOINCREMENT';
}
if(dataType.onDelete) {
template += ' ON DELETE <%= onDeleteAction %>';
replacements.onDeleteAction = dataType.onDelete.toUpperCase();
}
if(dataType.references) {
dataType = Utils.formatReferences(dataType);
template += ' REFERENCES <%= referencesTable %> (<%= referencesKey %>)';
replacements.referencesTable = this.quoteTable(dataType.references.model);
if(dataType.references.key) {
replacements.referencesKey = this.quoteIdentifier(dataType.references.key);
} else {
replacements.referencesKey = this.quoteIdentifier('id');
}
if(dataType.onDelete) {
template += ' ON DELETE <%= onDeleteAction %>';
replacements.onDeleteAction = dataType.onDelete.toUpperCase();
}
if(dataType.onUpdate) {
template += ' ON UPDATE <%= onUpdateAction %>';
replacements.onUpdateAction = dataType.onUpdate.toUpperCase();
}
if(dataType.onUpdate) {
template += ' ON UPDATE <%= onUpdateAction %>';
replacements.onUpdateAction = dataType.onUpdate.toUpperCase();
}
result[fieldName] = Utils._.template(template)(replacements);
} else {
result[fieldName] = dataType;
}
}
return result;
},
findAutoIncrementField: function(factory) {
var fields = [];
for (var name in factory.attributes) {
if (factory.attributes.hasOwnProperty(name)) {
var definition = factory.attributes[name];
if (definition && definition.autoIncrement) {
fields.push(name);
}
}
result[fieldName] = Utils._.template(template)(replacements);
} else {
result[fieldName] = dataType;
}
}
return fields;
},
showIndexesQuery: function(tableName) {
var sql = 'PRAGMA INDEX_LIST(<%= tableName %>)';
return Utils._.template(sql)({ tableName: this.quoteTable(tableName) });
},
return result;
},
removeIndexQuery: function(tableName, indexNameOrAttributes) {
var sql = 'DROP INDEX IF EXISTS <%= indexName %>'
, indexName = indexNameOrAttributes;
findAutoIncrementField: function(factory) {
var fields = [];
if (typeof indexName !== 'string') {
indexName = Utils.inflection.underscore(tableName + '_' + indexNameOrAttributes.join('_'));
for (var name in factory.attributes) {
if (factory.attributes.hasOwnProperty(name)) {
var definition = factory.attributes[name];
if (definition && definition.autoIncrement) {
fields.push(name);
}
}
}
return Utils._.template(sql)( { tableName: this.quoteIdentifiers(tableName), indexName: indexName });
},
describeTableQuery: function(tableName, schema, schemaDelimiter) {
var options = {};
options.schema = schema;
options.schemaDelimiter = schemaDelimiter;
options.quoted = false;
return fields;
},
var sql = 'PRAGMA TABLE_INFO(<%= tableName %>);';
return Utils._.template(sql)({ tableName: this.addSchema({tableName: this.quoteIdentifiers(tableName), options: options})});
},
showIndexesQuery: function(tableName) {
var sql = 'PRAGMA INDEX_LIST(<%= tableName %>)';
return Utils._.template(sql)({ tableName: this.quoteTable(tableName) });
},
removeColumnQuery: function(tableName, attributes) {
var backupTableName
, query;
removeIndexQuery: function(tableName, indexNameOrAttributes) {
var sql = 'DROP INDEX IF EXISTS <%= indexName %>'
, indexName = indexNameOrAttributes;
attributes = this.attributesToSQL(attributes);
if (typeof indexName !== 'string') {
indexName = Utils.inflection.underscore(tableName + '_' + indexNameOrAttributes.join('_'));
}
if (typeof tableName === 'object') {
backupTableName = {
tableName: tableName.tableName + '_backup',
schema: tableName.schema
};
} else {
backupTableName = tableName + '_backup';
}
return Utils._.template(sql)( { tableName: this.quoteIdentifiers(tableName), indexName: indexName });
},
query = [
this.createTableQuery(backupTableName, attributes).replace('CREATE TABLE', 'CREATE TEMPORARY TABLE'),
'INSERT INTO <%= backupTableName %> SELECT <%= attributeNames %> FROM <%= tableName %>;',
'DROP TABLE <%= tableName %>;',
this.createTableQuery(tableName, attributes),
'INSERT INTO <%= tableName %> SELECT <%= attributeNames %> FROM <%= backupTableName %>;',
'DROP TABLE <%= backupTableName %>;'
].join('');
return Utils._.template(query)({
tableName: this.quoteTable(tableName),
backupTableName: this.quoteTable(backupTableName),
attributeNames: Utils._.keys(attributes).join(', ')
});
},
describeTableQuery: function(tableName, schema, schemaDelimiter) {
var options = {};
options.schema = schema;
options.schemaDelimiter = schemaDelimiter;
options.quoted = false;
renameColumnQuery: function(tableName, attrNameBefore, attrNameAfter, attributes) {
var backupTableName
, query;
var sql = 'PRAGMA TABLE_INFO(<%= tableName %>);';
return Utils._.template(sql)({ tableName: this.addSchema({tableName: this.quoteIdentifiers(tableName), options: options})});
},
attributes = this.attributesToSQL(attributes);
removeColumnQuery: function(tableName, attributes) {
var backupTableName
, query;
if (typeof tableName === 'object') {
backupTableName = {
tableName: tableName.tableName + '_backup',
schema: tableName.schema
};
} else {
backupTableName = tableName + '_backup';
}
attributes = this.attributesToSQL(attributes);
query = [
this.createTableQuery(backupTableName, attributes).replace('CREATE TABLE', 'CREATE TEMPORARY TABLE'),
'INSERT INTO <%= backupTableName %> SELECT <%= attributeNamesImport %> FROM <%= tableName %>;',
'DROP TABLE <%= tableName %>;',
this.createTableQuery(tableName, attributes),
'INSERT INTO <%= tableName %> SELECT <%= attributeNamesExport %> FROM <%= backupTableName %>;',
'DROP TABLE <%= backupTableName %>;'
].join('');
return Utils._.template(query)({
tableName: this.quoteTable(tableName),
backupTableName: this.quoteTable(backupTableName),
attributeNamesImport: Utils._.keys(attributes).map(function(attr) {
return (attrNameAfter === attr) ? this.quoteIdentifier(attrNameBefore) + ' AS ' + this.quoteIdentifier(attr) : this.quoteIdentifier(attr);
}.bind(this)).join(', '),
attributeNamesExport: Utils._.keys(attributes).map(function(attr) {
return this.quoteIdentifier(attr);
}.bind(this)).join(', ')
});
},
if (typeof tableName === 'object') {
backupTableName = {
tableName: tableName.tableName + '_backup',
schema: tableName.schema
};
} else {
backupTableName = tableName + '_backup';
}
startTransactionQuery: function(transaction, options) {
if (options.parent) {
return 'SAVEPOINT ' + this.quoteIdentifier(transaction.name) + ';';
}
query = [
this.createTableQuery(backupTableName, attributes).replace('CREATE TABLE', 'CREATE TEMPORARY TABLE'),
'INSERT INTO <%= backupTableName %> SELECT <%= attributeNames %> FROM <%= tableName %>;',
'DROP TABLE <%= tableName %>;',
this.createTableQuery(tableName, attributes),
'INSERT INTO <%= tableName %> SELECT <%= attributeNames %> FROM <%= backupTableName %>;',
'DROP TABLE <%= backupTableName %>;'
].join('');
return Utils._.template(query)({
tableName: this.quoteTable(tableName),
backupTableName: this.quoteTable(backupTableName),
attributeNames: Utils._.keys(attributes).join(', ')
});
},
renameColumnQuery: function(tableName, attrNameBefore, attrNameAfter, attributes) {
var backupTableName
, query;
attributes = this.attributesToSQL(attributes);
if (typeof tableName === 'object') {
backupTableName = {
tableName: tableName.tableName + '_backup',
schema: tableName.schema
};
} else {
backupTableName = tableName + '_backup';
}
return 'BEGIN TRANSACTION;';
},
setAutocommitQuery: function() {
return '-- SQLite does not support SET autocommit.';
},
setIsolationLevelQuery: function(value) {
switch (value) {
case Transaction.ISOLATION_LEVELS.REPEATABLE_READ:
return '-- SQLite is not able to choose the isolation level REPEATABLE READ.';
case Transaction.ISOLATION_LEVELS.READ_UNCOMMITTED:
return 'PRAGMA read_uncommitted = ON;';
case Transaction.ISOLATION_LEVELS.READ_COMMITTED:
return 'PRAGMA read_uncommitted = OFF;';
case Transaction.ISOLATION_LEVELS.SERIALIZABLE:
return "-- SQLite's default isolation level is SERIALIZABLE. Nothing to do.";
default:
throw new Error('Unknown isolation level: ' + value);
}
},
replaceBooleanDefaults: function(sql) {
return sql.replace(/DEFAULT '?false'?/g, 'DEFAULT 0').replace(/DEFAULT '?true'?/g, 'DEFAULT 1');
},
quoteIdentifier: function(identifier) {
if (identifier === '*') return identifier;
return Utils.addTicks(identifier, '`');
},
/**
* Generates an SQL query that returns all foreign keys of a table.
*
* @param {String} tableName The name of the table.
* @param {String} schemaName The name of the schema.
* @return {String} The generated sql query.
*/
getForeignKeysQuery: function(tableName, schemaName) {
var sql = 'PRAGMA foreign_key_list(<%= tableName %>)';
return Utils._.template(sql)({ tableName: tableName });
query = [
this.createTableQuery(backupTableName, attributes).replace('CREATE TABLE', 'CREATE TEMPORARY TABLE'),
'INSERT INTO <%= backupTableName %> SELECT <%= attributeNamesImport %> FROM <%= tableName %>;',
'DROP TABLE <%= tableName %>;',
this.createTableQuery(tableName, attributes),
'INSERT INTO <%= tableName %> SELECT <%= attributeNamesExport %> FROM <%= backupTableName %>;',
'DROP TABLE <%= backupTableName %>;'
].join('');
return Utils._.template(query)({
tableName: this.quoteTable(tableName),
backupTableName: this.quoteTable(backupTableName),
attributeNamesImport: Utils._.keys(attributes).map(function(attr) {
return (attrNameAfter === attr) ? this.quoteIdentifier(attrNameBefore) + ' AS ' + this.quoteIdentifier(attr) : this.quoteIdentifier(attr);
}.bind(this)).join(', '),
attributeNamesExport: Utils._.keys(attributes).map(function(attr) {
return this.quoteIdentifier(attr);
}.bind(this)).join(', ')
});
},
startTransactionQuery: function(transaction, options) {
if (options.parent) {
return 'SAVEPOINT ' + this.quoteIdentifier(transaction.name) + ';';
}
};
return Utils._.extend({}, MySqlQueryGenerator, QueryGenerator);
})();
return 'BEGIN TRANSACTION;';
},
setAutocommitQuery: function() {
return '-- SQLite does not support SET autocommit.';
},
setIsolationLevelQuery: function(value) {
switch (value) {
case Transaction.ISOLATION_LEVELS.REPEATABLE_READ:
return '-- SQLite is not able to choose the isolation level REPEATABLE READ.';
case Transaction.ISOLATION_LEVELS.READ_UNCOMMITTED:
return 'PRAGMA read_uncommitted = ON;';
case Transaction.ISOLATION_LEVELS.READ_COMMITTED:
return 'PRAGMA read_uncommitted = OFF;';
case Transaction.ISOLATION_LEVELS.SERIALIZABLE:
return "-- SQLite's default isolation level is SERIALIZABLE. Nothing to do.";
default:
throw new Error('Unknown isolation level: ' + value);
}
},
replaceBooleanDefaults: function(sql) {
return sql.replace(/DEFAULT '?false'?/g, 'DEFAULT 0').replace(/DEFAULT '?true'?/g, 'DEFAULT 1');
},
quoteIdentifier: function(identifier) {
if (identifier === '*') return identifier;
return Utils.addTicks(identifier, '`');
},
/**
* Generates an SQL query that returns all foreign keys of a table.
*
* @param {String} tableName The name of the table.
* @param {String} schemaName The name of the schema.
* @return {String} The generated sql query.
*/
getForeignKeysQuery: function(tableName, schemaName) {
var sql = 'PRAGMA foreign_key_list(<%= tableName %>)';
return Utils._.template(sql)({ tableName: tableName });
}
};
module.exports = Utils._.extend({}, MySqlQueryGenerator, QueryGenerator);
......@@ -9,106 +9,111 @@ var Utils = require('../../utils')
@class QueryInterface
@static
*/
module.exports = {
/**
A wrapper that fixes SQLite's inability to remove columns from existing tables.
It will create a backup of the table, drop the table afterwards and create a
new table with the same name but without the obsolete column.
@method removeColumn
@for QueryInterface
@param {String} tableName The name of the table.
@param {String} attributeName The name of the attribute that we want to remove.
@param {Object} options
@param {Boolean|Function} [options.logging] A function that logs the sql queries, or false for explicitly not logging these queries
@param {CustomEventEmitter} emitter The EventEmitter from outside.
@param {Function} queryAndEmit The function from outside that triggers some events to get triggered.
@since 1.6.0
*/
removeColumn: function(tableName, attributeName, options) {
var self = this;
options = options || {};
return this.describeTable(tableName).then(function(fields) {
delete fields[attributeName];
var sql = self.QueryGenerator.removeColumnQuery(tableName, fields)
, subQueries = sql.split(';').filter(function(q) { return q !== ''; });
return Promise.each(subQueries, function(subQuery) {
return self.sequelize.query(subQuery + ';', { raw: true, logging: options.logging });
});
/**
A wrapper that fixes SQLite's inability to remove columns from existing tables.
It will create a backup of the table, drop the table afterwards and create a
new table with the same name but without the obsolete column.
@method removeColumn
@for QueryInterface
@param {String} tableName The name of the table.
@param {String} attributeName The name of the attribute that we want to remove.
@param {Object} options
@param {Boolean|Function} [options.logging] A function that logs the sql queries, or false for explicitly not logging these queries
@param {CustomEventEmitter} emitter The EventEmitter from outside.
@param {Function} queryAndEmit The function from outside that triggers some events to get triggered.
@since 1.6.0
*/
var removeColumn = function(tableName, attributeName, options) {
var self = this;
options = options || {};
return this.describeTable(tableName).then(function(fields) {
delete fields[attributeName];
var sql = self.QueryGenerator.removeColumnQuery(tableName, fields)
, subQueries = sql.split(';').filter(function(q) { return q !== ''; });
return Promise.each(subQueries, function(subQuery) {
return self.sequelize.query(subQuery + ';', { raw: true, logging: options.logging });
});
},
/**
A wrapper that fixes SQLite's inability to change columns from existing tables.
It will create a backup of the table, drop the table afterwards and create a
new table with the same name but with a modified version of the respective column.
@method changeColumn
@for QueryInterface
@param {String} tableName The name of the table.
@param {Object} attributes An object with the attribute's name as key and it's options as value object.
@param {Object} options
@param {Boolean|Function} [options.logging] A function that logs the sql queries, or false for explicitly not logging these queries
@param {CustomEventEmitter} emitter The EventEmitter from outside.
@param {Function} queryAndEmit The function from outside that triggers some events to get triggered.
@since 1.6.0
*/
changeColumn: function(tableName, attributes, options) {
var attributeName = Utils._.keys(attributes)[0]
, self = this;
options = options || {};
return this.describeTable(tableName).then(function(fields) {
fields[attributeName] = attributes[attributeName];
var sql = self.QueryGenerator.removeColumnQuery(tableName, fields)
, subQueries = sql.split(';').filter(function(q) { return q !== ''; });
return Promise.each(subQueries, function(subQuery) {
return self.sequelize.query(subQuery + ';', { raw: true, logging: options.logging });
});
});
};
/**
A wrapper that fixes SQLite's inability to change columns from existing tables.
It will create a backup of the table, drop the table afterwards and create a
new table with the same name but with a modified version of the respective column.
@method changeColumn
@for QueryInterface
@param {String} tableName The name of the table.
@param {Object} attributes An object with the attribute's name as key and it's options as value object.
@param {Object} options
@param {Boolean|Function} [options.logging] A function that logs the sql queries, or false for explicitly not logging these queries
@param {CustomEventEmitter} emitter The EventEmitter from outside.
@param {Function} queryAndEmit The function from outside that triggers some events to get triggered.
@since 1.6.0
*/
var changeColumn = function(tableName, attributes, options) {
var attributeName = Utils._.keys(attributes)[0]
, self = this;
options = options || {};
return this.describeTable(tableName).then(function(fields) {
fields[attributeName] = attributes[attributeName];
var sql = self.QueryGenerator.removeColumnQuery(tableName, fields)
, subQueries = sql.split(';').filter(function(q) { return q !== ''; });
return Promise.each(subQueries, function(subQuery) {
return self.sequelize.query(subQuery + ';', { raw: true, logging: options.logging });
});
},
/**
A wrapper that fixes SQLite's inability to rename columns from existing tables.
It will create a backup of the table, drop the table afterwards and create a
new table with the same name but with a renamed version of the respective column.
@method renameColumn
@for QueryInterface
@param {String} tableName The name of the table.
@param {String} attrNameBefore The name of the attribute before it was renamed.
@param {String} attrNameAfter The name of the attribute after it was renamed.
@param {Object} options
@param {Boolean|Function} [options.logging] A function that logs the sql queries, or false for explicitly not logging these queries
@param {CustomEventEmitter} emitter The EventEmitter from outside.
@param {Function} queryAndEmit The function from outside that triggers some events to get triggered.
@since 1.6.0
*/
renameColumn: function(tableName, attrNameBefore, attrNameAfter, options) {
var self = this;
options = options || {};
return this.describeTable(tableName).then(function(fields) {
fields[attrNameAfter] = Utils._.clone(fields[attrNameBefore]);
delete fields[attrNameBefore];
var sql = self.QueryGenerator.renameColumnQuery(tableName, attrNameBefore, attrNameAfter, fields)
, subQueries = sql.split(';').filter(function(q) { return q !== ''; });
return Promise.each(subQueries, function(subQuery) {
return self.sequelize.query(subQuery + ';', { raw: true, logging: options.logging });
});
});
};
/**
A wrapper that fixes SQLite's inability to rename columns from existing tables.
It will create a backup of the table, drop the table afterwards and create a
new table with the same name but with a renamed version of the respective column.
@method renameColumn
@for QueryInterface
@param {String} tableName The name of the table.
@param {String} attrNameBefore The name of the attribute before it was renamed.
@param {String} attrNameAfter The name of the attribute after it was renamed.
@param {Object} options
@param {Boolean|Function} [options.logging] A function that logs the sql queries, or false for explicitly not logging these queries
@param {CustomEventEmitter} emitter The EventEmitter from outside.
@param {Function} queryAndEmit The function from outside that triggers some events to get triggered.
@since 1.6.0
*/
var renameColumn = function(tableName, attrNameBefore, attrNameAfter, options) {
var self = this;
options = options || {};
return this.describeTable(tableName).then(function(fields) {
fields[attrNameAfter] = Utils._.clone(fields[attrNameBefore]);
delete fields[attrNameBefore];
var sql = self.QueryGenerator.renameColumnQuery(tableName, attrNameBefore, attrNameAfter, fields)
, subQueries = sql.split(';').filter(function(q) { return q !== ''; });
return Promise.each(subQueries, function(subQuery) {
return self.sequelize.query(subQuery + ';', { raw: true, logging: options.logging });
});
}
});
};
module.exports = {
removeColumn: removeColumn,
changeColumn: changeColumn,
renameColumn: renameColumn
};
\ No newline at end of file
......@@ -5,276 +5,274 @@ var Utils = require('../../utils')
, QueryTypes = require('../../query-types')
, sequelizeErrors = require('../../errors.js');
module.exports = (function() {
var Query = function(database, sequelize, options) {
this.database = database;
this.sequelize = sequelize;
this.instance = options.instance;
this.model = options.model;
this.options = Utils._.extend({
logging: console.log,
plain: false,
raw: false
}, options || {});
this.checkLoggingOption();
};
Utils.inherit(Query, AbstractQuery);
Query.prototype.getInsertIdField = function() {
return 'lastID';
};
Query.prototype.run = function(sql) {
var self = this
, promise;
this.sql = sql;
this.sequelize.log('Executing (' + (this.database.uuid || 'default') + '): ' + this.sql, this.options);
promise = new Utils.Promise(function(resolve) {
var columnTypes = {};
self.database.serialize(function() {
var executeSql = function() {
if (self.sql.indexOf('-- ') === 0) {
return resolve();
} else {
resolve(new Utils.Promise(function(resolve, reject) {
self.database[self.getDatabaseMethod()](self.sql, function(err, results) {
if (err) {
err.sql = self.sql;
reject(self.formatError(err));
} else {
var metaData = this;
metaData.columnTypes = columnTypes;
var result = self.instance;
// add the inserted row id to the instance
if (self.isInsertQuery(results, metaData)) {
self.handleInsertQuery(results, metaData);
if (!self.instance) {
result = metaData[self.getInsertIdField()];
}
var Query = function(database, sequelize, options) {
this.database = database;
this.sequelize = sequelize;
this.instance = options.instance;
this.model = options.model;
this.options = Utils._.extend({
logging: console.log,
plain: false,
raw: false
}, options || {});
this.checkLoggingOption();
};
Utils.inherit(Query, AbstractQuery);
Query.prototype.getInsertIdField = function() {
return 'lastID';
};
Query.prototype.run = function(sql) {
var self = this
, promise;
this.sql = sql;
this.sequelize.log('Executing (' + (this.database.uuid || 'default') + '): ' + this.sql, this.options);
promise = new Utils.Promise(function(resolve) {
var columnTypes = {};
self.database.serialize(function() {
var executeSql = function() {
if (self.sql.indexOf('-- ') === 0) {
return resolve();
} else {
resolve(new Utils.Promise(function(resolve, reject) {
self.database[self.getDatabaseMethod()](self.sql, function(err, results) {
if (err) {
err.sql = self.sql;
reject(self.formatError(err));
} else {
var metaData = this;
metaData.columnTypes = columnTypes;
var result = self.instance;
// add the inserted row id to the instance
if (self.isInsertQuery(results, metaData)) {
self.handleInsertQuery(results, metaData);
if (!self.instance) {
result = metaData[self.getInsertIdField()];
}
}
if (self.sql.indexOf('sqlite_master') !== -1) {
result = results.map(function(resultSet) { return resultSet.name; });
} else if (self.isSelectQuery()) {
if (!self.options.raw) {
results = results.map(function(result) {
for (var name in result) {
if (result.hasOwnProperty(name) && metaData.columnTypes[name]) {
if (metaData.columnTypes[name] === 'DATETIME') {
// we need to convert the timestamps into actual date objects
var val = result[name];
if (val !== null) {
if (val.indexOf('+') === -1) {
// For backwards compat. Dates inserted by sequelize < 2.0dev12 will not have a timestamp set
result[name] = new Date(val + self.sequelize.options.timezone);
} else {
result[name] = new Date(val); // We already have a timezone stored in the string
}
}
} else if (metaData.columnTypes[name].lastIndexOf('BLOB') !== -1) {
if (result[name]) {
result[name] = new Buffer(result[name]);
if (self.sql.indexOf('sqlite_master') !== -1) {
result = results.map(function(resultSet) { return resultSet.name; });
} else if (self.isSelectQuery()) {
if (!self.options.raw) {
results = results.map(function(result) {
for (var name in result) {
if (result.hasOwnProperty(name) && metaData.columnTypes[name]) {
if (metaData.columnTypes[name] === 'DATETIME') {
// we need to convert the timestamps into actual date objects
var val = result[name];
if (val !== null) {
if (val.indexOf('+') === -1) {
// For backwards compat. Dates inserted by sequelize < 2.0dev12 will not have a timestamp set
result[name] = new Date(val + self.sequelize.options.timezone);
} else {
result[name] = new Date(val); // We already have a timezone stored in the string
}
}
} else if (metaData.columnTypes[name].lastIndexOf('BLOB') !== -1) {
if (result[name]) {
result[name] = new Buffer(result[name]);
}
}
}
return result;
});
}
result = self.handleSelectQuery(results);
} else if (self.isShowOrDescribeQuery()) {
result = results;
} else if (self.sql.indexOf('PRAGMA INDEX_LIST') !== -1) {
result = self.handleShowIndexesQuery(results);
} else if (self.sql.indexOf('PRAGMA INDEX_INFO') !== -1) {
result = results;
} else if (self.sql.indexOf('PRAGMA TABLE_INFO') !== -1) {
// this is the sqlite way of getting the metadata of a table
result = {};
results.forEach(function(_result) {
result[_result.name] = {
type: _result.type,
allowNull: (_result.notnull === 0),
defaultValue: _result.dflt_value,
primaryKey : (_result.pk === 1)
};
if (result[_result.name].type === 'TINYINT(1)') {
result[_result.name].defaultValue = { '0': false, '1': true }[result[_result.name].defaultValue];
}
if (result[_result.name].defaultValue === undefined) {
result[_result.name].defaultValue = null;
}
if (typeof result[_result.name].defaultValue === 'string') {
result[_result.name].defaultValue = result[_result.name].defaultValue.replace(/'/g, '');
}
return result;
});
} else if (self.sql.indexOf('PRAGMA foreign_keys;') !== -1) {
result = results[0];
} else if (self.sql.indexOf('PRAGMA foreign_keys') !== -1) {
result = results;
} else if (self.sql.indexOf('PRAGMA foreign_key_list') !== -1) {
result = results;
} else if ([QueryTypes.BULKUPDATE, QueryTypes.BULKDELETE].indexOf(self.options.type) !== -1) {
result = metaData.changes;
} else if (self.options.type === QueryTypes.UPSERT) {
result = undefined;
} else if (self.options.type === QueryTypes.VERSION) {
result = results[0].version;
} else if (self.options.type === QueryTypes.RAW) {
result = [results, metaData];
}
resolve(result);
}
});
}));
}
};
result = self.handleSelectQuery(results);
} else if (self.isShowOrDescribeQuery()) {
result = results;
} else if (self.sql.indexOf('PRAGMA INDEX_LIST') !== -1) {
result = self.handleShowIndexesQuery(results);
} else if (self.sql.indexOf('PRAGMA INDEX_INFO') !== -1) {
result = results;
} else if (self.sql.indexOf('PRAGMA TABLE_INFO') !== -1) {
// this is the sqlite way of getting the metadata of a table
result = {};
results.forEach(function(_result) {
result[_result.name] = {
type: _result.type,
allowNull: (_result.notnull === 0),
defaultValue: _result.dflt_value,
primaryKey : (_result.pk === 1)
};
if (result[_result.name].type === 'TINYINT(1)') {
result[_result.name].defaultValue = { '0': false, '1': true }[result[_result.name].defaultValue];
}
if ((self.getDatabaseMethod() === 'all')) {
var tableNames = [];
if (self.options && self.options.tableNames) {
tableNames = self.options.tableNames;
} else if (/FROM `(.*?)`/i.exec(self.sql)) {
tableNames.push(/FROM `(.*?)`/i.exec(self.sql)[1]);
}
if (result[_result.name].defaultValue === undefined) {
result[_result.name].defaultValue = null;
}
if (!tableNames.length) {
return executeSql();
} else {
return Utils.Promise.map(tableNames, function(tableName) {
if (tableName !== 'sqlite_master') {
return new Utils.Promise(function(resolve) {
// get the column types
self.database.all('PRAGMA table_info(`' + tableName + '`)', function(err, results) {
if (!err) {
for (var i = 0, l = results.length; i < l; i++) {
columnTypes[tableName + '.' + results[i].name] = columnTypes[results[i].name] = results[i].type;
}
if (typeof result[_result.name].defaultValue === 'string') {
result[_result.name].defaultValue = result[_result.name].defaultValue.replace(/'/g, '');
}
resolve();
});
});
} else if (self.sql.indexOf('PRAGMA foreign_keys;') !== -1) {
result = results[0];
} else if (self.sql.indexOf('PRAGMA foreign_keys') !== -1) {
result = results;
} else if (self.sql.indexOf('PRAGMA foreign_key_list') !== -1) {
result = results;
} else if ([QueryTypes.BULKUPDATE, QueryTypes.BULKDELETE].indexOf(self.options.type) !== -1) {
result = metaData.changes;
} else if (self.options.type === QueryTypes.UPSERT) {
result = undefined;
} else if (self.options.type === QueryTypes.VERSION) {
result = results[0].version;
} else if (self.options.type === QueryTypes.RAW) {
result = [results, metaData];
}
resolve(result);
}
}).then(executeSql);
}
} else {
});
}));
}
};
if ((self.getDatabaseMethod() === 'all')) {
var tableNames = [];
if (self.options && self.options.tableNames) {
tableNames = self.options.tableNames;
} else if (/FROM `(.*?)`/i.exec(self.sql)) {
tableNames.push(/FROM `(.*?)`/i.exec(self.sql)[1]);
}
if (!tableNames.length) {
return executeSql();
} else {
return Utils.Promise.map(tableNames, function(tableName) {
if (tableName !== 'sqlite_master') {
return new Utils.Promise(function(resolve) {
// get the column types
self.database.all('PRAGMA table_info(`' + tableName + '`)', function(err, results) {
if (!err) {
for (var i = 0, l = results.length; i < l; i++) {
columnTypes[tableName + '.' + results[i].name] = columnTypes[results[i].name] = results[i].type;
}
}
resolve();
});
});
}
}).then(executeSql);
}
});
} else {
return executeSql();
}
});
});
return promise;
};
return promise;
};
Query.prototype.formatError = function (err) {
var match;
Query.prototype.formatError = function (err) {
var match;
switch (err.code) {
case 'SQLITE_CONSTRAINT':
match = err.message.match(/FOREIGN KEY constraint failed/);
if (match !== null) {
return new sequelizeErrors.ForeignKeyConstraintError({
parent :err
});
}
switch (err.code) {
case 'SQLITE_CONSTRAINT':
match = err.message.match(/FOREIGN KEY constraint failed/);
if (match !== null) {
return new sequelizeErrors.ForeignKeyConstraintError({
parent :err
});
}
var fields = [];
var fields = [];
// Sqlite pre 2.2 behavior - Error: SQLITE_CONSTRAINT: columns x, y are not unique
match = err.message.match(/columns (.*?) are/);
if (match !== null && match.length >= 2) {
fields = match[1].split(', ');
} else {
// Sqlite pre 2.2 behavior - Error: SQLITE_CONSTRAINT: columns x, y are not unique
match = err.message.match(/columns (.*?) are/);
if (match !== null && match.length >= 2) {
fields = match[1].split(', ');
} else {
// Sqlite post 2.2 behavior - Error: SQLITE_CONSTRAINT: UNIQUE constraint failed: table.x, table.y
match = err.message.match(/UNIQUE constraint failed: (.*)/);
if (match !== null && match.length >= 2) {
fields = match[1].split(', ').map(function (columnWithTable) {
return columnWithTable.split('.')[1];
});
}
// Sqlite post 2.2 behavior - Error: SQLITE_CONSTRAINT: UNIQUE constraint failed: table.x, table.y
match = err.message.match(/UNIQUE constraint failed: (.*)/);
if (match !== null && match.length >= 2) {
fields = match[1].split(', ').map(function (columnWithTable) {
return columnWithTable.split('.')[1];
});
}
}
var errors = []
, self = this
, message = 'Validation error';
fields.forEach(function(field) {
errors.push(new sequelizeErrors.ValidationErrorItem(
field + ' must be unique', 'unique violation', field, self.instance && self.instance[field]));
});
var errors = []
, self = this
, message = 'Validation error';
if (this.model) {
Utils._.forOwn(this.model.uniqueKeys, function(constraint) {
if (Utils._.isEqual(constraint.fields, fields) && !!constraint.msg) {
message = constraint.msg;
return false;
}
});
}
fields.forEach(function(field) {
errors.push(new sequelizeErrors.ValidationErrorItem(
field + ' must be unique', 'unique violation', field, self.instance && self.instance[field]));
});
return new sequelizeErrors.UniqueConstraintError({
message: message,
errors: errors,
parent: err,
fields: fields
if (this.model) {
Utils._.forOwn(this.model.uniqueKeys, function(constraint) {
if (Utils._.isEqual(constraint.fields, fields) && !!constraint.msg) {
message = constraint.msg;
return false;
}
});
}
case 'SQLITE_BUSY':
return new sequelizeErrors.TimeoutError(err);
default:
return new sequelizeErrors.DatabaseError(err);
}
};
Query.prototype.handleShowIndexesQuery = function (data) {
var self = this;
// Sqlite returns indexes so the one that was defined last is returned first. Lets reverse that!
return this.sequelize.Promise.map(data.reverse(), function (item) {
item.fields = [];
item.primary = false;
item.unique = !!item.unique;
return self.run('PRAGMA INDEX_INFO(`' + item.name + '`)').then(function (columns) {
columns.forEach(function (column) {
item.fields[column.seqno] = {
attribute: column.name,
length: undefined,
order: undefined,
};
});
return new sequelizeErrors.UniqueConstraintError({
message: message,
errors: errors,
parent: err,
fields: fields
});
return item;
case 'SQLITE_BUSY':
return new sequelizeErrors.TimeoutError(err);
default:
return new sequelizeErrors.DatabaseError(err);
}
};
Query.prototype.handleShowIndexesQuery = function (data) {
var self = this;
// Sqlite returns indexes so the one that was defined last is returned first. Lets reverse that!
return this.sequelize.Promise.map(data.reverse(), function (item) {
item.fields = [];
item.primary = false;
item.unique = !!item.unique;
return self.run('PRAGMA INDEX_INFO(`' + item.name + '`)').then(function (columns) {
columns.forEach(function (column) {
item.fields[column.seqno] = {
attribute: column.name,
length: undefined,
order: undefined,
};
});
return item;
});
};
Query.prototype.getDatabaseMethod = function() {
if (this.isUpsertQuery()) {
return 'exec'; // Needed to run multiple queries in one
} else if (this.isInsertQuery() || this.isUpdateQuery() || this.isBulkUpdateQuery() || (this.sql.toLowerCase().indexOf('CREATE TEMPORARY TABLE'.toLowerCase()) !== -1) || this.options.type === QueryTypes.BULKDELETE) {
return 'run';
} else {
return 'all';
}
};
return Query;
})();
});
};
Query.prototype.getDatabaseMethod = function() {
if (this.isUpsertQuery()) {
return 'exec'; // Needed to run multiple queries in one
} else if (this.isInsertQuery() || this.isUpdateQuery() || this.isBulkUpdateQuery() || (this.sql.toLowerCase().indexOf('CREATE TEMPORARY TABLE'.toLowerCase()) !== -1) || this.options.type === QueryTypes.BULKDELETE) {
return 'run';
} else {
return 'all';
}
};
module.exports = Query;
......@@ -11,1030 +11,1028 @@ var Utils = require('./utils')
, primitives = ['string', 'number', 'boolean']
, defaultsOptions = { raw: true };
module.exports = (function() {
/**
* This class represents an single instance, a database row. You might see it referred to as both Instance and instance. You should not
* instantiate the Instance class directly, instead you access it using the finder and creation methods on the model.
*
* Instance instances operate with the concept of a `dataValues` property, which stores the actual values represented by the instance.
* By default, the values from dataValues can also be accessed directly from the Instance, that is:
* ```js
* instance.field
* // is the same as
* instance.get('field')
* // is the same as
* instance.getDataValue('field')
* ```
* However, if getters and/or setters are defined for `field` they will be invoked, instead of returning the value from `dataValues`.
* Accessing properties directly or using `get` is preferred for regular use, `getDataValue` should only be used for custom getters.
*
* @see {Sequelize#define} for more information about getters and setters
* @class Instance
*/
var Instance = function(values, options) {
this.dataValues = {};
this._previousDataValues = {};
this._changed = {};
this.__options = this.Model.options;
this.options = options || {};
this.hasPrimaryKeys = this.Model.options.hasPrimaryKeys;
this.__eagerlyLoadedAssociations = [];
/**
* Returns true if this instance has not yet been persisted to the database
* @property isNewRecord
* @return {Boolean}
*/
this.isNewRecord = options.isNewRecord;
/**
* Returns the Model the instance was created from.
* @see {Model}
* @property Model
* @return {Model}
*/
initValues.call(this, values, options);
};
// private
var initValues = function(values, options) {
var defaults
, key;
/**
* A reference to the sequelize instance
* @see {Sequelize}
* @property sequelize
* @return {Sequelize}
*/
Object.defineProperty(Instance.prototype, 'sequelize', {
get: function() { return this.Model.modelManager.sequelize; }
});
values = values && _.clone(values) || {};
/**
* Get an object representing the query for this instance, use with `options.where`
*
* @property where
* @return {Object}
*/
Instance.prototype.where = function() {
var where;
if (options.isNewRecord) {
defaults = {};
where = this.Model.primaryKeyAttributes.reduce(function (result, attribute) {
result[attribute] = this.get(attribute, {raw: true});
return result;
}.bind(this), {});
if (this.Model._hasDefaultValues) {
Utils._.each(this.Model._defaultValues, function(valueFn, key) {
if (defaults[key] === undefined) {
defaults[key] = valueFn();
}
});
}
if (_.size(where) === 0) {
return this.__options.whereCollection;
// set id to null if not passed as value, a newly created dao has no id
// removing this breaks bulkCreate
// do after default values since it might have UUID as a default value
if (!defaults.hasOwnProperty(this.Model.primaryKeyAttribute)) {
defaults[this.Model.primaryKeyAttribute] = null;
}
return where;
};
Instance.prototype.toString = function () {
return '[object SequelizeInstance:'+this.Model.name+']';
};
if (this.Model._timestampAttributes.createdAt && defaults[this.Model._timestampAttributes.createdAt]) {
this.dataValues[this.Model._timestampAttributes.createdAt] = Utils.toDefaultValue(defaults[this.Model._timestampAttributes.createdAt]);
delete defaults[this.Model._timestampAttributes.createdAt];
}
/**
* Get the value of the underlying data value
*
* @param {String} key
* @return {any}
*/
Instance.prototype.getDataValue = function(key) {
return this.dataValues[key];
};
if (this.Model._timestampAttributes.updatedAt && defaults[this.Model._timestampAttributes.updatedAt]) {
this.dataValues[this.Model._timestampAttributes.updatedAt] = Utils.toDefaultValue(defaults[this.Model._timestampAttributes.updatedAt]);
delete defaults[this.Model._timestampAttributes.updatedAt];
}
/**
* Update the underlying data value
*
* @param {String} key
* @param {any} value
*/
Instance.prototype.setDataValue = function(key, value) {
var originalValue = this._previousDataValues[key];
if (primitives.indexOf(typeof value) === -1 || value !== originalValue) {
this.changed(key, true);
if (this.Model._timestampAttributes.deletedAt && defaults[this.Model._timestampAttributes.deletedAt]) {
this.dataValues[this.Model._timestampAttributes.deletedAt] = Utils.toDefaultValue(defaults[this.Model._timestampAttributes.deletedAt]);
delete defaults[this.Model._timestampAttributes.deletedAt];
}
this.dataValues[key] = value;
};
if (Object.keys(defaults).length) {
for (key in defaults) {
if (values[key] === undefined) {
this.set(key, Utils.toDefaultValue(defaults[key]), defaultsOptions);
}
}
}
}
this.set(values, options);
};
/**
* This class represents an single instance, a database row. You might see it referred to as both Instance and instance. You should not
* instantiate the Instance class directly, instead you access it using the finder and creation methods on the model.
*
* Instance instances operate with the concept of a `dataValues` property, which stores the actual values represented by the instance.
* By default, the values from dataValues can also be accessed directly from the Instance, that is:
* ```js
* instance.field
* // is the same as
* instance.get('field')
* // is the same as
* instance.getDataValue('field')
* ```
* However, if getters and/or setters are defined for `field` they will be invoked, instead of returning the value from `dataValues`.
* Accessing properties directly or using `get` is preferred for regular use, `getDataValue` should only be used for custom getters.
*
* @see {Sequelize#define} for more information about getters and setters
* @class Instance
*/
var Instance = function(values, options) {
this.dataValues = {};
this._previousDataValues = {};
this._changed = {};
this.__options = this.Model.options;
this.options = options || {};
this.hasPrimaryKeys = this.Model.options.hasPrimaryKeys;
this.__eagerlyLoadedAssociations = [];
/**
* Returns true if this instance has not yet been persisted to the database
* @property isNewRecord
* @return {Boolean}
*/
this.isNewRecord = options.isNewRecord;
/**
* If no key is given, returns all values of the instance, also invoking virtual getters.
*
* If key is given and a field or virtual getter is present for the key it will call that getter - else it will return the value for key.
*
* @param {String} [key]
* @param {Object} [options]
* @param {Boolean} [options.plain=false] If set to true, included instances will be returned as plain objects
* @return {Object|any}
* Returns the Model the instance was created from.
* @see {Model}
* @property Model
* @return {Model}
*/
Instance.prototype.get = function(key, options) {
if (options === undefined && typeof key === 'object') {
options = key;
key = undefined;
}
if (key) {
if (this._customGetters[key]) {
return this._customGetters[key].call(this, key);
}
if (options && options.plain && this.options.include && this.options.includeNames.indexOf(key) !== -1) {
if (Array.isArray(this.dataValues[key])) {
return this.dataValues[key].map(function (instance) {
return instance.get({plain: options.plain});
});
} else if (this.dataValues[key] instanceof Instance) {
return this.dataValues[key].get({plain: options.plain});
} else {
return this.dataValues[key];
}
initValues.call(this, values, options);
};
/**
* A reference to the sequelize instance
* @see {Sequelize}
* @property sequelize
* @return {Sequelize}
*/
Object.defineProperty(Instance.prototype, 'sequelize', {
get: function() { return this.Model.modelManager.sequelize; }
});
/**
* Get an object representing the query for this instance, use with `options.where`
*
* @property where
* @return {Object}
*/
Instance.prototype.where = function() {
var where;
where = this.Model.primaryKeyAttributes.reduce(function (result, attribute) {
result[attribute] = this.get(attribute, {raw: true});
return result;
}.bind(this), {});
if (_.size(where) === 0) {
return this.__options.whereCollection;
}
return where;
};
Instance.prototype.toString = function () {
return '[object SequelizeInstance:'+this.Model.name+']';
};
/**
* Get the value of the underlying data value
*
* @param {String} key
* @return {any}
*/
Instance.prototype.getDataValue = function(key) {
return this.dataValues[key];
};
/**
* Update the underlying data value
*
* @param {String} key
* @param {any} value
*/
Instance.prototype.setDataValue = function(key, value) {
var originalValue = this._previousDataValues[key];
if (primitives.indexOf(typeof value) === -1 || value !== originalValue) {
this.changed(key, true);
}
this.dataValues[key] = value;
};
/**
* If no key is given, returns all values of the instance, also invoking virtual getters.
*
* If key is given and a field or virtual getter is present for the key it will call that getter - else it will return the value for key.
*
* @param {String} [key]
* @param {Object} [options]
* @param {Boolean} [options.plain=false] If set to true, included instances will be returned as plain objects
* @return {Object|any}
*/
Instance.prototype.get = function(key, options) {
if (options === undefined && typeof key === 'object') {
options = key;
key = undefined;
}
if (key) {
if (this._customGetters[key]) {
return this._customGetters[key].call(this, key);
}
if (options && options.plain && this.options.include && this.options.includeNames.indexOf(key) !== -1) {
if (Array.isArray(this.dataValues[key])) {
return this.dataValues[key].map(function (instance) {
return instance.get({plain: options.plain});
});
} else if (this.dataValues[key] instanceof Instance) {
return this.dataValues[key].get({plain: options.plain});
} else {
return this.dataValues[key];
}
return this.dataValues[key];
}
return this.dataValues[key];
}
if (this._hasCustomGetters || (options && options.plain && this.options.include) || (options && options.clone)) {
var values = {}
, _key;
if (this._hasCustomGetters || (options && options.plain && this.options.include) || (options && options.clone)) {
var values = {}
, _key;
if (this._hasCustomGetters) {
for (_key in this._customGetters) {
if (this._customGetters.hasOwnProperty(_key)) {
values[_key] = this.get(_key, options);
}
if (this._hasCustomGetters) {
for (_key in this._customGetters) {
if (this._customGetters.hasOwnProperty(_key)) {
values[_key] = this.get(_key, options);
}
}
}
for (_key in this.dataValues) {
if (!values.hasOwnProperty(_key) && this.dataValues.hasOwnProperty(_key)) {
if (options && options.plain && this.options.include && this.options.includeNames.indexOf(_key) !== -1) {
if (Array.isArray(this.dataValues[_key])) {
values[_key] = this.dataValues[_key].map(function (instance) {
return instance.get({plain: options.plain});
});
} else if (this.dataValues[_key] instanceof Instance) {
values[_key] = this.dataValues[_key].get({plain: options.plain});
} else {
values[_key] = this.dataValues[_key];
}
for (_key in this.dataValues) {
if (!values.hasOwnProperty(_key) && this.dataValues.hasOwnProperty(_key)) {
if (options && options.plain && this.options.include && this.options.includeNames.indexOf(_key) !== -1) {
if (Array.isArray(this.dataValues[_key])) {
values[_key] = this.dataValues[_key].map(function (instance) {
return instance.get({plain: options.plain});
});
} else if (this.dataValues[_key] instanceof Instance) {
values[_key] = this.dataValues[_key].get({plain: options.plain});
} else {
values[_key] = this.dataValues[_key];
}
} else {
values[_key] = this.dataValues[_key];
}
}
return values;
}
return this.dataValues;
};
return values;
}
return this.dataValues;
};
/**
* Set is used to update values on the instance (the sequelize representation of the instance that is, remember that nothing will be persisted before you actually call `save`).
* In its most basic form `set` will update a value stored in the underlying `dataValues` object. However, if a custom setter function is defined for the key, that function
* will be called instead. To bypass the setter, you can pass `raw: true` in the options object.
*
* If set is called with an object, it will loop over the object, and call set recursively for each key, value pair. If you set raw to true, the underlying dataValues will either be
* set directly to the object passed, or used to extend dataValues, if dataValues already contain values.
*
* When set is called, the previous value of the field is stored and sets a changed flag(see `changed`).
*
* Set can also be used to build instances for associations, if you have values for those.
* When using set with associations you need to make sure the property key matches the alias of the association
* while also making sure that the proper include options have been set (from .build() or .find())
*
* If called with a dot.seperated key on a JSON/JSONB attribute it will set the value nested and flag the entire object as changed.
*
* @see {Model#find} for more information about includes
* @param {String|Object} key
* @param {any} value
* @param {Object} [options]
* @param {Boolean} [options.raw=false] If set to true, field and virtual setters will be ignored
* @param {Boolean} [options.reset=false] Clear all previously set data values
* @alias setAttributes
*/
Instance.prototype.set = function(key, value, options) {
var values
, originalValue
, keys
, i
, length;
if (typeof key === 'object' && key !== null) {
values = key;
options = value || {};
if (options.reset) {
this.dataValues = {};
}
/**
* Set is used to update values on the instance (the sequelize representation of the instance that is, remember that nothing will be persisted before you actually call `save`).
* In its most basic form `set` will update a value stored in the underlying `dataValues` object. However, if a custom setter function is defined for the key, that function
* will be called instead. To bypass the setter, you can pass `raw: true` in the options object.
*
* If set is called with an object, it will loop over the object, and call set recursively for each key, value pair. If you set raw to true, the underlying dataValues will either be
* set directly to the object passed, or used to extend dataValues, if dataValues already contain values.
*
* When set is called, the previous value of the field is stored and sets a changed flag(see `changed`).
*
* Set can also be used to build instances for associations, if you have values for those.
* When using set with associations you need to make sure the property key matches the alias of the association
* while also making sure that the proper include options have been set (from .build() or .find())
*
* If called with a dot.seperated key on a JSON/JSONB attribute it will set the value nested and flag the entire object as changed.
*
* @see {Model#find} for more information about includes
* @param {String|Object} key
* @param {any} value
* @param {Object} [options]
* @param {Boolean} [options.raw=false] If set to true, field and virtual setters will be ignored
* @param {Boolean} [options.reset=false] Clear all previously set data values
* @alias setAttributes
*/
Instance.prototype.set = function(key, value, options) {
var values
, originalValue
, keys
, i
, length;
if (typeof key === 'object' && key !== null) {
values = key;
options = value || {};
if (options.reset) {
this.dataValues = {};
// If raw, and we're not dealing with includes or special attributes, just set it straight on the dataValues object
if (options.raw && !(this.options && this.options.include) && !(options && options.attributes) && !this.Model._hasBooleanAttributes && !this.Model._hasDateAttributes) {
if (Object.keys(this.dataValues).length) {
this.dataValues = _.extend(this.dataValues, values);
} else {
this.dataValues = values;
}
// If raw, .changed() shouldn't be true
this._previousDataValues = _.clone(this.dataValues);
} else {
// Loop and call set
// If raw, and we're not dealing with includes or special attributes, just set it straight on the dataValues object
if (options.raw && !(this.options && this.options.include) && !(options && options.attributes) && !this.Model._hasBooleanAttributes && !this.Model._hasDateAttributes) {
if (Object.keys(this.dataValues).length) {
this.dataValues = _.extend(this.dataValues, values);
} else {
this.dataValues = values;
if (options.attributes) {
keys = options.attributes;
if (this.Model._hasVirtualAttributes) {
keys = keys.concat(this.Model._virtualAttributes);
}
// If raw, .changed() shouldn't be true
this._previousDataValues = _.clone(this.dataValues);
} else {
// Loop and call set
if (options.attributes) {
keys = options.attributes;
if (this.Model._hasVirtualAttributes) {
keys = keys.concat(this.Model._virtualAttributes);
}
if (this.options.includeNames) {
keys = keys.concat(this.options.includeNames);
}
if (this.options.includeNames) {
keys = keys.concat(this.options.includeNames);
}
for (i = 0, length = keys.length; i < length; i++) {
if (values[keys[i]] !== undefined) {
this.set(keys[i], values[keys[i]], options);
}
}
} else {
for (key in values) {
this.set(key, values[key], options);
for (i = 0, length = keys.length; i < length; i++) {
if (values[keys[i]] !== undefined) {
this.set(keys[i], values[keys[i]], options);
}
}
if (options.raw) {
// If raw, .changed() shouldn't be true
this._previousDataValues = _.clone(this.dataValues);
} else {
for (key in values) {
this.set(key, values[key], options);
}
}
} else {
if (!options)
options = {};
if (!options.raw) {
originalValue = this.dataValues[key];
if (options.raw) {
// If raw, .changed() shouldn't be true
this._previousDataValues = _.clone(this.dataValues);
}
}
} else {
if (!options)
options = {};
if (!options.raw) {
originalValue = this.dataValues[key];
}
// If not raw, and there's a customer setter
if (!options.raw && this._customSetters[key]) {
this._customSetters[key].call(this, value, key);
} else {
// Check if we have included models, and if this key matches the include model names/aliases
// If not raw, and there's a customer setter
if (!options.raw && this._customSetters[key]) {
this._customSetters[key].call(this, value, key);
} else {
// Check if we have included models, and if this key matches the include model names/aliases
if (this.options && this.options.include && this.options.includeNames.indexOf(key) !== -1 && value) {
// Pass it on to the include handler
this._setInclude(key, value, options);
return;
} else {
// Bunch of stuff we won't do when its raw
if (!options.raw) {
// If attribute is not in model definition, return
if (!this._isAttribute(key)) {
if (key.indexOf('.') > -1 && this.Model._isJsonAttribute(key.split('.')[0])) {
Dottie.set(this.dataValues, key, value);
this.changed(key, true);
}
return;
if (this.options && this.options.include && this.options.includeNames.indexOf(key) !== -1 && value) {
// Pass it on to the include handler
this._setInclude(key, value, options);
return;
} else {
// Bunch of stuff we won't do when its raw
if (!options.raw) {
// If attribute is not in model definition, return
if (!this._isAttribute(key)) {
if (key.indexOf('.') > -1 && this.Model._isJsonAttribute(key.split('.')[0])) {
Dottie.set(this.dataValues, key, value);
this.changed(key, true);
}
return;
}
// If attempting to set primary key and primary key is already defined, return
if (this.Model._hasPrimaryKeys && originalValue && this.Model._isPrimaryKey(key)) {
return;
}
// If attempting to set primary key and primary key is already defined, return
if (this.Model._hasPrimaryKeys && originalValue && this.Model._isPrimaryKey(key)) {
return;
}
// If attempting to set read only attributes, return
if (!this.isNewRecord && this.Model._hasReadOnlyAttributes && this.Model._isReadOnlyAttribute(key)) {
return;
}
// If attempting to set read only attributes, return
if (!this.isNewRecord && this.Model._hasReadOnlyAttributes && this.Model._isReadOnlyAttribute(key)) {
return;
}
// Convert date fields to real date objects
if (this.Model._hasDateAttributes && this.Model._isDateAttribute(key) && value !== null && value !== undefined && !(value instanceof Date) && !value._isSequelizeMethod) {
value = new Date(value);
}
// Convert date fields to real date objects
if (this.Model._hasDateAttributes && this.Model._isDateAttribute(key) && value !== null && value !== undefined && !(value instanceof Date) && !value._isSequelizeMethod) {
value = new Date(value);
}
}
// Convert boolean-ish values to booleans
if (this.Model._hasBooleanAttributes && this.Model._isBooleanAttribute(key) && value !== null && value !== undefined && !value._isSequelizeMethod) {
if (Buffer.isBuffer(value) && value.length === 1) {
// Bit fields are returned as buffers
value = value[0];
}
// Convert boolean-ish values to booleans
if (this.Model._hasBooleanAttributes && this.Model._isBooleanAttribute(key) && value !== null && value !== undefined && !value._isSequelizeMethod) {
if (Buffer.isBuffer(value) && value.length === 1) {
// Bit fields are returned as buffers
value = value[0];
}
if (_.isString(value)) {
// Only take action on valid boolean strings.
value = (value === 'true') ? true : (value === 'false') ? false : value;
if (_.isString(value)) {
// Only take action on valid boolean strings.
value = (value === 'true') ? true : (value === 'false') ? false : value;
} else if (_.isNumber(value)) {
// Only take action on valid boolean integers.
value = (value === 1) ? true : (value === 0) ? false : value;
}
} else if (_.isNumber(value)) {
// Only take action on valid boolean integers.
value = (value === 1) ? true : (value === 0) ? false : value;
}
if (!options.raw && ((primitives.indexOf(typeof value) === -1 && value !== null) || value !== originalValue)) {
this._previousDataValues[key] = originalValue;
this.changed(key, true);
}
this.dataValues[key] = value;
}
if (!options.raw && ((primitives.indexOf(typeof value) === -1 && value !== null) || value !== originalValue)) {
this._previousDataValues[key] = originalValue;
this.changed(key, true);
}
this.dataValues[key] = value;
}
}
return this;
};
Instance.prototype.setAttributes = function(updates) {
return this.set(updates);
};
/**
* If changed is called with a string it will return a boolean indicating whether the value of that key in `dataValues` is different from the value in `_previousDataValues`.
*
* If changed is called without an argument, it will return an array of keys that have changed.
*
* If changed is called without an argument and no keys have changed, it will return `false`.
*
* @param {String} [key]
* @return {Boolean|Array}
*/
Instance.prototype.changed = function(key, value) {
if (key) {
if (value !== undefined) {
this._changed[key] = value;
return this;
}
return this._changed[key] || false;
}
return this;
};
Instance.prototype.setAttributes = function(updates) {
return this.set(updates);
};
/**
* If changed is called with a string it will return a boolean indicating whether the value of that key in `dataValues` is different from the value in `_previousDataValues`.
*
* If changed is called without an argument, it will return an array of keys that have changed.
*
* If changed is called without an argument and no keys have changed, it will return `false`.
*
* @param {String} [key]
* @return {Boolean|Array}
*/
Instance.prototype.changed = function(key, value) {
if (key) {
if (value !== undefined) {
this._changed[key] = value;
return this;
}
return this._changed[key] || false;
}
var changed = Object.keys(this.dataValues).filter(function(key) {
return this.changed(key);
}.bind(this));
return changed.length ? changed : false;
};
/**
* Returns the previous value for key from `_previousDataValues`.
* @param {String} key
* @return {any}
*/
Instance.prototype.previous = function(key) {
return this._previousDataValues[key];
};
Instance.prototype._setInclude = function(key, value, options) {
if (!Array.isArray(value)) value = [value];
if (value[0] instanceof Instance) {
value = value.map(function(instance) {
return instance.dataValues;
});
}
var include = this.options.includeMap[key]
, association = include.association
, self = this
, accessor = key
, childOptions
, primaryKeyAttribute = include.model.primaryKeyAttribute
, isEmpty;
if (!isEmpty) {
childOptions = {
isNewRecord: this.isNewRecord,
include: include.include,
includeNames: include.includeNames,
includeMap: include.includeMap,
includeValidated: true,
raw: options.raw,
attributes: include.originalAttributes
};
}
if (include.originalAttributes === undefined || include.originalAttributes.length) {
if (association.isSingleAssociation) {
if (Array.isArray(value)) {
value = value[0];
}
var changed = Object.keys(this.dataValues).filter(function(key) {
return this.changed(key);
}.bind(this));
return changed.length ? changed : false;
};
/**
* Returns the previous value for key from `_previousDataValues`.
* @param {String} key
* @return {any}
*/
Instance.prototype.previous = function(key) {
return this._previousDataValues[key];
};
Instance.prototype._setInclude = function(key, value, options) {
if (!Array.isArray(value)) value = [value];
if (value[0] instanceof Instance) {
value = value.map(function(instance) {
return instance.dataValues;
});
isEmpty = value && value[primaryKeyAttribute] === null;
self[accessor] = self.dataValues[accessor] = isEmpty ? null : include.model.build(value, childOptions);
} else {
isEmpty = value[0] && value[0][primaryKeyAttribute] === null;
self[accessor] = self.dataValues[accessor] = isEmpty ? [] : include.model.bulkBuild(value, childOptions);
}
}
};
/**
* Validate this instance, and if the validation passes, persist it to the database.
*
* On success, the callback will be called with this instance. On validation error, the callback will be called with an instance of `Sequelize.ValidationError`.
* This error will have a property for each of the fields for which validation failed, with the error message for that field.
*
* @param {Object} [options]
* @param {Object} [options.fields] An optional array of strings, representing database columns. If fields is provided, only those columns will be validated and saved.
* @param {Boolean} [options.silent=false] If true, the updatedAt timestamp will not be updated.
* @param {Boolean} [options.validate=true] If false, validations won't be run.
* @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql.
* @param {Transaction} [options.transaction]
*
* @return {Promise<this|Errors.ValidationError>}
*/
Instance.prototype.save = function(options) {
if (arguments.length > 1) {
throw new Error('The second argument was removed in favor of the options object.');
}
options = _.defaults(options || {}, {
hooks: true,
validate: true
});
var include = this.options.includeMap[key]
, association = include.association
, self = this
, accessor = key
, childOptions
, primaryKeyAttribute = include.model.primaryKeyAttribute
, isEmpty;
if (!isEmpty) {
childOptions = {
isNewRecord: this.isNewRecord,
include: include.include,
includeNames: include.includeNames,
includeMap: include.includeMap,
includeValidated: true,
raw: options.raw,
attributes: include.originalAttributes
};
if (!options.fields) {
if (this.isNewRecord) {
options.fields = Object.keys(this.Model.attributes);
} else {
options.fields = _.intersection(this.changed(), Object.keys(this.Model.attributes));
}
if (include.originalAttributes === undefined || include.originalAttributes.length) {
if (association.isSingleAssociation) {
if (Array.isArray(value)) {
value = value[0];
}
isEmpty = value && value[primaryKeyAttribute] === null;
self[accessor] = self.dataValues[accessor] = isEmpty ? null : include.model.build(value, childOptions);
} else {
isEmpty = value[0] && value[0][primaryKeyAttribute] === null;
self[accessor] = self.dataValues[accessor] = isEmpty ? [] : include.model.bulkBuild(value, childOptions);
}
}
};
options.defaultFields = options.fields;
}
/**
* Validate this instance, and if the validation passes, persist it to the database.
*
* On success, the callback will be called with this instance. On validation error, the callback will be called with an instance of `Sequelize.ValidationError`.
* This error will have a property for each of the fields for which validation failed, with the error message for that field.
*
* @param {Object} [options]
* @param {Object} [options.fields] An optional array of strings, representing database columns. If fields is provided, only those columns will be validated and saved.
* @param {Boolean} [options.silent=false] If true, the updatedAt timestamp will not be updated.
* @param {Boolean} [options.validate=true] If false, validations won't be run.
* @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql.
* @param {Transaction} [options.transaction]
*
* @return {Promise<this|Errors.ValidationError>}
*/
Instance.prototype.save = function(options) {
if (arguments.length > 1) {
throw new Error('The second argument was removed in favor of the options object.');
if (options.returning === undefined) {
if (options.association) {
options.returning = false;
} else if (this.isNewRecord) {
options.returning = true;
}
options = _.defaults(options || {}, {
hooks: true,
validate: true
}
var self = this
, primaryKeyName = this.Model.primaryKeyAttribute
, primaryKeyAttribute = primaryKeyName && this.Model.rawAttributes[primaryKeyName]
, updatedAtAttr = this.Model._timestampAttributes.updatedAt
, createdAtAttr = this.Model._timestampAttributes.createdAt
, hook = self.isNewRecord ? 'Create' : 'Update'
, wasNewRecord = this.isNewRecord;
if (updatedAtAttr && options.fields.indexOf(updatedAtAttr) === -1) {
options.fields.push(updatedAtAttr);
}
if (options.silent === true && !(this.isNewRecord && this.get(updatedAtAttr, {raw: true}))) {
// UpdateAtAttr might have been added as a result of Object.keys(Model.attributes). In that case we have to remove it again
Utils._.remove(options.fields, function(val) {
return val === updatedAtAttr;
});
updatedAtAttr = false;
}
if (!options.fields) {
if (this.isNewRecord) {
options.fields = Object.keys(this.Model.attributes);
} else {
options.fields = _.intersection(this.changed(), Object.keys(this.Model.attributes));
}
options.defaultFields = options.fields;
if (this.isNewRecord === true) {
if (createdAtAttr && options.fields.indexOf(createdAtAttr) === -1) {
options.fields.push(createdAtAttr);
}
if (options.returning === undefined) {
if (options.association) {
options.returning = false;
} else if (this.isNewRecord) {
options.returning = true;
}
if (primaryKeyAttribute && primaryKeyAttribute.defaultValue && options.fields.indexOf(primaryKeyName) < 0) {
options.fields.unshift(primaryKeyName);
}
}
var self = this
, primaryKeyName = this.Model.primaryKeyAttribute
, primaryKeyAttribute = primaryKeyName && this.Model.rawAttributes[primaryKeyName]
, updatedAtAttr = this.Model._timestampAttributes.updatedAt
, createdAtAttr = this.Model._timestampAttributes.createdAt
, hook = self.isNewRecord ? 'Create' : 'Update'
, wasNewRecord = this.isNewRecord;
if (updatedAtAttr && options.fields.indexOf(updatedAtAttr) === -1) {
options.fields.push(updatedAtAttr);
if (this.isNewRecord === false) {
if (primaryKeyName && !this.get(primaryKeyName, {raw: true})) {
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');
}
}
if (options.silent === true && !(this.isNewRecord && this.get(updatedAtAttr, {raw: true}))) {
// UpdateAtAttr might have been added as a result of Object.keys(Model.attributes). In that case we have to remove it again
Utils._.remove(options.fields, function(val) {
return val === updatedAtAttr;
return Promise.bind(this).then(function() {
// Validate
if (options.validate) {
return Promise.bind(this).then(function () {
// hookValidate rejects with errors, validate returns with errors
if (options.hooks) return this.hookValidate(options);
return this.validate(options).then(function (err) {
if (err) throw err;
});
});
updatedAtAttr = false;
}
}).then(function() {
return Promise.bind(this).then(function() {
// Run before hook
if (options.hooks) {
var beforeHookValues = _.pick(this.dataValues, options.fields)
, afterHookValues
, hookChanged
, ignoreChanged = _.difference(this.changed(), options.fields); // In case of update where it's only supposed to update the passed values and the hook values
if (this.isNewRecord === true) {
if (createdAtAttr && options.fields.indexOf(createdAtAttr) === -1) {
options.fields.push(createdAtAttr);
}
if (updatedAtAttr && options.fields.indexOf(updatedAtAttr) !== -1) {
ignoreChanged = _.without(ignoreChanged, updatedAtAttr);
}
if (primaryKeyAttribute && primaryKeyAttribute.defaultValue && options.fields.indexOf(primaryKeyName) < 0) {
options.fields.unshift(primaryKeyName);
}
}
return this.Model.runHooks('before' + hook, this, options).bind(this).then(function() {
if (options.defaultFields && !this.isNewRecord) {
afterHookValues = _.pick(this.dataValues, _.difference(this.changed(), ignoreChanged));
if (this.isNewRecord === false) {
if (primaryKeyName && !this.get(primaryKeyName, {raw: true})) {
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');
}
}
hookChanged = [];
Object.keys(afterHookValues).forEach(function (key) {
if (afterHookValues[key] !== beforeHookValues[key]) {
hookChanged.push(key);
}
});
return Promise.bind(this).then(function() {
// Validate
if (options.validate) {
return Promise.bind(this).then(function () {
// hookValidate rejects with errors, validate returns with errors
if (options.hooks) return this.hookValidate(options);
return this.validate(options).then(function (err) {
if (err) throw err;
});
});
}
}).then(function() {
return Promise.bind(this).then(function() {
// Run before hook
if (options.hooks) {
var beforeHookValues = _.pick(this.dataValues, options.fields)
, afterHookValues
, hookChanged
, ignoreChanged = _.difference(this.changed(), options.fields); // In case of update where it's only supposed to update the passed values and the hook values
if (updatedAtAttr && options.fields.indexOf(updatedAtAttr) !== -1) {
ignoreChanged = _.without(ignoreChanged, updatedAtAttr);
options.fields = _.unique(options.fields.concat(hookChanged));
}
return this.Model.runHooks('before' + hook, this, options).bind(this).then(function() {
if (options.defaultFields && !this.isNewRecord) {
afterHookValues = _.pick(this.dataValues, _.difference(this.changed(), ignoreChanged));
hookChanged = [];
Object.keys(afterHookValues).forEach(function (key) {
if (afterHookValues[key] !== beforeHookValues[key]) {
hookChanged.push(key);
}
});
options.fields = _.unique(options.fields.concat(hookChanged));
}
if (hookChanged) {
if (options.validate) {
// Validate again
if (hookChanged) {
if (options.validate) {
// Validate again
options.skip = _.difference(Object.keys(this.Model.rawAttributes), hookChanged);
return Promise.bind(this).then(function () {
// hookValidate rejects with errors, validate returns with errors
if (options.hooks) return this.hookValidate(options);
options.skip = _.difference(Object.keys(this.Model.rawAttributes), hookChanged);
return Promise.bind(this).then(function () {
// hookValidate rejects with errors, validate returns with errors
if (options.hooks) return this.hookValidate(options);
return this.validate(options).then(function (err) {
if (err) throw err;
});
}).then(function() {
delete options.skip;
return this.validate(options).then(function (err) {
if (err) throw err;
});
}
}).then(function() {
delete options.skip;
});
}
});
}
}).then(function() {
if (!options.fields.length) return this;
if (!this.isNewRecord) return this;
if (!this.options.include || !this.options.include.length) return this;
// Nested creation for BelongsTo relations
return Promise.map(this.options.include.filter(function (include) {
return include.association instanceof BelongsTo;
}), function (include) {
var instance = self.get(include.as);
if (!instance) return Promise.resolve();
return instance.save({
transaction: options.transaction,
logging: options.logging
}).then(function () {
return self[include.association.accessors.set](instance, {save: false});
});
}
});
})
.then(function() {
if (!options.fields.length) return this;
}
}).then(function() {
if (!options.fields.length) return this;
if (!this.isNewRecord) return this;
if (!this.options.include || !this.options.include.length) return this;
// Nested creation for BelongsTo relations
return Promise.map(this.options.include.filter(function (include) {
return include.association instanceof BelongsTo;
}), function (include) {
var instance = self.get(include.as);
if (!instance) return Promise.resolve();
return instance.save({
transaction: options.transaction,
logging: options.logging
}).then(function () {
return self[include.association.accessors.set](instance, {save: false});
});
});
})
.then(function() {
if (!options.fields.length) return this;
var values = Utils.mapValueFieldNames(this.dataValues, options.fields, this.Model)
, query = null
, args = []
, now = Utils.now(this.sequelize.options.dialect);
var values = Utils.mapValueFieldNames(this.dataValues, options.fields, this.Model)
, query = null
, args = []
, now = Utils.now(this.sequelize.options.dialect);
if (updatedAtAttr && !options.silent) {
self.dataValues[updatedAtAttr] = values[self.Model.rawAttributes[updatedAtAttr].field || updatedAtAttr] = self.Model.$getDefaultTimestamp(updatedAtAttr) || now;
}
if (updatedAtAttr && !options.silent) {
self.dataValues[updatedAtAttr] = values[self.Model.rawAttributes[updatedAtAttr].field || updatedAtAttr] = self.Model.$getDefaultTimestamp(updatedAtAttr) || now;
}
if (self.isNewRecord && createdAtAttr && !values[createdAtAttr]) {
self.dataValues[createdAtAttr] = values[self.Model.rawAttributes[createdAtAttr].field || createdAtAttr] = self.Model.$getDefaultTimestamp(createdAtAttr) || now;
}
if (self.isNewRecord && createdAtAttr && !values[createdAtAttr]) {
self.dataValues[createdAtAttr] = values[self.Model.rawAttributes[createdAtAttr].field || createdAtAttr] = self.Model.$getDefaultTimestamp(createdAtAttr) || now;
}
if (self.isNewRecord) {
query = 'insert';
args = [self, self.Model.getTableName(options), values, options];
} else {
var where = this.where();
if (self.isNewRecord) {
query = 'insert';
args = [self, self.Model.getTableName(options), values, options];
} else {
var where = this.where();
where = Utils.mapValueFieldNames(where, Object.keys(where), this.Model);
where = Utils.mapValueFieldNames(where, Object.keys(where), this.Model);
query = 'update';
args = [self, self.Model.getTableName(options), values, where, options];
}
query = 'update';
args = [self, self.Model.getTableName(options), values, where, options];
}
return self.sequelize.getQueryInterface()[query].apply(self.sequelize.getQueryInterface(), args)
.then(function(result) {
// Transfer database generated values (defaults, autoincrement, etc)
Object.keys(self.Model.rawAttributes).forEach(function (attr) {
if (self.Model.rawAttributes[attr].field &&
values[self.Model.rawAttributes[attr].field] !== undefined &&
self.Model.rawAttributes[attr].field !== attr
) {
values[attr] = values[self.Model.rawAttributes[attr].field];
delete values[self.Model.rawAttributes[attr].field];
}
});
values = _.extend(values, result.dataValues);
result.dataValues = _.extend(result.dataValues, values);
return result;
})
.tap(function(result) {
// Run after hook
if (options.hooks) {
return self.Model.runHooks('after' + hook, result, options);
return self.sequelize.getQueryInterface()[query].apply(self.sequelize.getQueryInterface(), args)
.then(function(result) {
// Transfer database generated values (defaults, autoincrement, etc)
Object.keys(self.Model.rawAttributes).forEach(function (attr) {
if (self.Model.rawAttributes[attr].field &&
values[self.Model.rawAttributes[attr].field] !== undefined &&
self.Model.rawAttributes[attr].field !== attr
) {
values[attr] = values[self.Model.rawAttributes[attr].field];
delete values[self.Model.rawAttributes[attr].field];
}
})
.then(function(result) {
options.fields.forEach(function (field) {
result._previousDataValues[field] = result.dataValues[field];
self.changed(field, false);
});
self.isNewRecord = false;
return result;
})
.tap(function() {
if (!wasNewRecord) return;
if (!self.options.include || !self.options.include.length) return;
// Nested creation for HasOne/HasMany/BelongsToMany relations
return Promise.map(self.options.include.filter(function (include) {
return !(include.association instanceof BelongsTo);
}), function (include) {
var instances = self.get(include.as);
if (!instances) return Promise.resolve();
if (!Array.isArray(instances)) instances = [instances];
if (!instances.length) return Promise.resolve();
// Instances will be updated in place so we can safely treat HasOne like a HasMany
return Promise.map(instances, function (instance) {
if (include.association instanceof BelongsToMany) {
return instance.save({transaction: options.transaction, logging: options.logging}).then(function () {
var values = {};
values[include.association.foreignKey] = self.get(self.Model.primaryKeyAttribute, {raw: true});
values[include.association.otherKey] = instance.get(instance.Model.primaryKeyAttribute, {raw: true});
return include.association.throughModel.create(values, {transaction: options.transaction, logging: options.logging});
});
} else {
instance.set(include.association.identifier, self.get(self.Model.primaryKeyAttribute, {raw: true}));
return instance.save({transaction: options.transaction, logging: options.logging});
}
});
});
values = _.extend(values, result.dataValues);
result.dataValues = _.extend(result.dataValues, values);
return result;
})
.tap(function(result) {
// Run after hook
if (options.hooks) {
return self.Model.runHooks('after' + hook, result, options);
}
})
.then(function(result) {
options.fields.forEach(function (field) {
result._previousDataValues[field] = result.dataValues[field];
self.changed(field, false);
});
self.isNewRecord = false;
return result;
})
.tap(function() {
if (!wasNewRecord) return;
if (!self.options.include || !self.options.include.length) return;
// Nested creation for HasOne/HasMany/BelongsToMany relations
return Promise.map(self.options.include.filter(function (include) {
return !(include.association instanceof BelongsTo);
}), function (include) {
var instances = self.get(include.as);
if (!instances) return Promise.resolve();
if (!Array.isArray(instances)) instances = [instances];
if (!instances.length) return Promise.resolve();
// Instances will be updated in place so we can safely treat HasOne like a HasMany
return Promise.map(instances, function (instance) {
if (include.association instanceof BelongsToMany) {
return instance.save({transaction: options.transaction, logging: options.logging}).then(function () {
var values = {};
values[include.association.foreignKey] = self.get(self.Model.primaryKeyAttribute, {raw: true});
values[include.association.otherKey] = instance.get(instance.Model.primaryKeyAttribute, {raw: true});
return include.association.throughModel.create(values, {transaction: options.transaction, logging: options.logging});
});
} else {
instance.set(include.association.identifier, self.get(self.Model.primaryKeyAttribute, {raw: true}));
return instance.save({transaction: options.transaction, logging: options.logging});
}
});
});
});
});
};
/*
* Refresh the current instance in-place, i.e. update the object with current data from the DB and return the same object.
* This is different from doing a `find(Instance.id)`, because that would create and return a new instance. With this method,
* all references to the Instance are updated with the new data and no new objects are created.
*
* @see {Model#find}
* @param {Object} [options] Options that are passed on to `Model.find`
* @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql.
* @return {Promise<this>}
*/
Instance.prototype.reload = function(options) {
var self = this
, where = [
this.sequelize.getQueryInterface().quoteTable(this.Model.name) + '.' + this.sequelize.getQueryInterface().quoteIdentifier(this.Model.primaryKeyField) + '=?',
this.get(this.Model.primaryKeyAttribute, {raw: true})
];
options = _.defaults(options || {}, {
where: where,
limit: 1,
include: this.options.include || null
});
});
});
};
/*
* Refresh the current instance in-place, i.e. update the object with current data from the DB and return the same object.
* This is different from doing a `find(Instance.id)`, because that would create and return a new instance. With this method,
* all references to the Instance are updated with the new data and no new objects are created.
*
* @see {Model#find}
* @param {Object} [options] Options that are passed on to `Model.find`
* @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql.
* @return {Promise<this>}
*/
Instance.prototype.reload = function(options) {
var self = this
, where = [
this.sequelize.getQueryInterface().quoteTable(this.Model.name) + '.' + this.sequelize.getQueryInterface().quoteIdentifier(this.Model.primaryKeyField) + '=?',
this.get(this.Model.primaryKeyAttribute, {raw: true})
];
options = _.defaults(options || {}, {
where: where,
limit: 1,
include: this.options.include || null
});
return this.Model.findOne(options).then(function(reload) {
self.set(reload.dataValues, {raw: true, reset: true});
}).return(self);
};
/*
* Validate the attribute of this instance according to validation rules set in the model definition.
*
* Emits null if and only if validation successful; otherwise an Error instance containing { field name : [error msgs] } entries.
*
* @param {Object} [options] Options that are passed to the validator
* @param {Array} [options.skip] An array of strings. All properties that are in this array will not be validated
* @see {InstanceValidator}
*
* @return {Promise<undefined|Errors.ValidationError>}
*/
Instance.prototype.validate = function(options) {
return new InstanceValidator(this, options).validate();
};
Instance.prototype.hookValidate = function(options) {
return new InstanceValidator(this, options).hookValidate();
};
/**
* This is the same as calling `set` and then calling `save`.
*
* @see {Instance#set}
* @see {Instance#save}
* @param {Object} updates See `set`
* @param {Object} options See `save`
*
* @return {Promise<this>}
* @alias updateAttributes
*/
Instance.prototype.update = function(values, options) {
var changedBefore = this.changed() || []
, sideEffects
, fields;
options = options || {};
if (Array.isArray(options)) options = {fields: options};
this.set(values, {attributes: options.fields});
// Now we need to figure out which fields were actually affected by the setter.
sideEffects = _.without.apply(this, [this.changed() || []].concat(changedBefore));
fields = _.union(Object.keys(values), sideEffects);
if (!options.fields) {
options.fields = _.intersection(fields, this.changed());
options.defaultFields = options.fields;
return this.Model.findOne(options).then(function(reload) {
self.set(reload.dataValues, {raw: true, reset: true});
}).return(self);
};
/*
* Validate the attribute of this instance according to validation rules set in the model definition.
*
* Emits null if and only if validation successful; otherwise an Error instance containing { field name : [error msgs] } entries.
*
* @param {Object} [options] Options that are passed to the validator
* @param {Array} [options.skip] An array of strings. All properties that are in this array will not be validated
* @see {InstanceValidator}
*
* @return {Promise<undefined|Errors.ValidationError>}
*/
Instance.prototype.validate = function(options) {
return new InstanceValidator(this, options).validate();
};
Instance.prototype.hookValidate = function(options) {
return new InstanceValidator(this, options).hookValidate();
};
/**
* This is the same as calling `set` and then calling `save`.
*
* @see {Instance#set}
* @see {Instance#save}
* @param {Object} updates See `set`
* @param {Object} options See `save`
*
* @return {Promise<this>}
* @alias updateAttributes
*/
Instance.prototype.update = function(values, options) {
var changedBefore = this.changed() || []
, sideEffects
, fields;
options = options || {};
if (Array.isArray(options)) options = {fields: options};
this.set(values, {attributes: options.fields});
// Now we need to figure out which fields were actually affected by the setter.
sideEffects = _.without.apply(this, [this.changed() || []].concat(changedBefore));
fields = _.union(Object.keys(values), sideEffects);
if (!options.fields) {
options.fields = _.intersection(fields, this.changed());
options.defaultFields = options.fields;
}
return this.save(options);
};
Instance.prototype.updateAttributes = Instance.prototype.update;
/**
* Destroy the row corresponding to this instance. Depending on your setting for paranoid, the row will either be completely deleted, or have its deletedAt timestamp set to the current time.
*
* @param {Object} [options={}]
* @param {Boolean} [options.force=false] If set to true, paranoid models will actually be deleted
* @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql.
* @param {Transaction} [options.transaction]
*
* @return {Promise<undefined>}
*/
Instance.prototype.destroy = function(options) {
options = Utils._.extend({
hooks: true,
force: false
}, options || {});
return Promise.bind(this).then(function() {
// Run before hook
if (options.hooks) {
return this.Model.runHooks('beforeDestroy', this, options);
}
}).then(function() {
var where;
return this.save(options);
};
Instance.prototype.updateAttributes = Instance.prototype.update;
/**
* Destroy the row corresponding to this instance. Depending on your setting for paranoid, the row will either be completely deleted, or have its deletedAt timestamp set to the current time.
*
* @param {Object} [options={}]
* @param {Boolean} [options.force=false] If set to true, paranoid models will actually be deleted
* @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql.
* @param {Transaction} [options.transaction]
*
* @return {Promise<undefined>}
*/
Instance.prototype.destroy = function(options) {
options = Utils._.extend({
hooks: true,
force: false
}, options || {});
return Promise.bind(this).then(function() {
// Run before hook
if (options.hooks) {
return this.Model.runHooks('beforeDestroy', this, options);
}
}).then(function() {
var where;
if (this.Model._timestampAttributes.deletedAt && options.force === false) {
this.setDataValue(this.Model._timestampAttributes.deletedAt, new Date());
return this.save(_.extend(_.clone(options), {hooks : false}));
} else {
where = {};
var primaryKeys = this.Model.primaryKeyAttributes;
for(var i = 0; i < primaryKeys.length; i++) {
where[this.Model.rawAttributes[primaryKeys[i]].field] = this.get(primaryKeys[i], { raw: true });
}
return this.sequelize.getQueryInterface().delete(this, this.Model.getTableName(options), where, _.defaults(options, { type: QueryTypes.DELETE,limit: null}));
}
}).tap(function() {
// Run after hook
if (options.hooks) {
return this.Model.runHooks('afterDestroy', this, options);
}
}).then(function(result) {
return result;
});
};
/**
* Restore the row corresponding to this instance. Only available for paranoid models.
*
* @param {Object} [options={}]
* @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql.
* @param {Transaction} [options.transaction]
*
* @return {Promise<undefined>}
*/
Instance.prototype.restore = function(options) {
if (!this.Model._timestampAttributes.deletedAt) throw new Error('Model is not paranoid');
options = Utils._.extend({
hooks: true,
force: false
}, options || {});
return Promise.bind(this).then(function() {
// Run before hook
if (options.hooks) {
return this.Model.runHooks('beforeRestore', this, options);
}
}).then(function() {
this.setDataValue(this.Model._timestampAttributes.deletedAt, null);
return this.save(_.extend(_.clone(options), {hooks : false, omitNull : false}));
}).tap(function() {
// Run after hook
if (options.hooks) {
return this.Model.runHooks('afterRestore', this, options);
}
});
};
/**
* Increment the value of one or more columns. This is done in the database, which means it does not use the values currently stored on the Instance. The increment is done using a
* ```sql
* SET column = column + X
* ```
* query. To get the correct value after an increment into the Instance you should do a reload.
*
*```js
* instance.increment('number') // increment number by 1
* instance.increment(['number', 'count'], { by: 2 }) // increment number and count by 2
* instance.increment({ answer: 42, tries: 1}, { by: 2 }) // increment answer by 42, and tries by 1.
* // `by` is ignored, since each column has its own value
* ```
*
* @see {Instance#reload}
* @param {String|Array|Object} fields If a string is provided, that column is incremented by the value of `by` given in options. If an array is provided, the same is true for each column. If and object is provided, each column is incremented by the value given.
* @param {Object} [options]
* @param {Integer} [options.by=1] The number to increment by
* @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql.
* @param {Transaction} [options.transaction]
*
* @return {Promise<this>}
*/
Instance.prototype.increment = function(fields, options) {
var identifier = this.where()
, updatedAtAttr = this.Model._timestampAttributes.updatedAt
, values = {}
, where;
if (identifier) {
for (var attrName in identifier) {
// Field name mapping
var rawAttribute = this.Model.rawAttributes[attrName];
if (rawAttribute.field && rawAttribute.field !== rawAttribute.fieldName) {
identifier[this.Model.rawAttributes[attrName].field] = identifier[attrName];
delete identifier[attrName];
}
if (this.Model._timestampAttributes.deletedAt && options.force === false) {
this.setDataValue(this.Model._timestampAttributes.deletedAt, new Date());
return this.save(_.extend(_.clone(options), {hooks : false}));
} else {
where = {};
var primaryKeys = this.Model.primaryKeyAttributes;
for(var i = 0; i < primaryKeys.length; i++) {
where[this.Model.rawAttributes[primaryKeys[i]].field] = this.get(primaryKeys[i], { raw: true });
}
return this.sequelize.getQueryInterface().delete(this, this.Model.getTableName(options), where, _.defaults(options, { type: QueryTypes.DELETE,limit: null}));
}
options = _.defaults(options || {}, {
by: 1,
attributes: {},
where: {}
});
where = _.extend(options.where || {}, identifier);
if (Utils._.isString(fields)) {
values[fields] = options.by;
} else if (Utils._.isArray(fields)) {
Utils._.each(fields, function(field) {
values[field] = options.by;
});
} else { // Assume fields is key-value pairs
values = fields;
}).tap(function() {
// Run after hook
if (options.hooks) {
return this.Model.runHooks('afterDestroy', this, options);
}
if (updatedAtAttr && !values[updatedAtAttr]) {
options.attributes[updatedAtAttr] = this.Model.$getDefaultTimestamp(updatedAtAttr) || Utils.now(this.sequelize.options.dialect);
}).then(function(result) {
return result;
});
};
/**
* Restore the row corresponding to this instance. Only available for paranoid models.
*
* @param {Object} [options={}]
* @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql.
* @param {Transaction} [options.transaction]
*
* @return {Promise<undefined>}
*/
Instance.prototype.restore = function(options) {
if (!this.Model._timestampAttributes.deletedAt) throw new Error('Model is not paranoid');
options = Utils._.extend({
hooks: true,
force: false
}, options || {});
return Promise.bind(this).then(function() {
// Run before hook
if (options.hooks) {
return this.Model.runHooks('beforeRestore', this, options);
}
Object.keys(values).forEach(function(attr) {
}).then(function() {
this.setDataValue(this.Model._timestampAttributes.deletedAt, null);
return this.save(_.extend(_.clone(options), {hooks : false, omitNull : false}));
}).tap(function() {
// Run after hook
if (options.hooks) {
return this.Model.runHooks('afterRestore', this, options);
}
});
};
/**
* Increment the value of one or more columns. This is done in the database, which means it does not use the values currently stored on the Instance. The increment is done using a
* ```sql
* SET column = column + X
* ```
* query. To get the correct value after an increment into the Instance you should do a reload.
*
*```js
* instance.increment('number') // increment number by 1
* instance.increment(['number', 'count'], { by: 2 }) // increment number and count by 2
* instance.increment({ answer: 42, tries: 1}, { by: 2 }) // increment answer by 42, and tries by 1.
* // `by` is ignored, since each column has its own value
* ```
*
* @see {Instance#reload}
* @param {String|Array|Object} fields If a string is provided, that column is incremented by the value of `by` given in options. If an array is provided, the same is true for each column. If and object is provided, each column is incremented by the value given.
* @param {Object} [options]
* @param {Integer} [options.by=1] The number to increment by
* @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql.
* @param {Transaction} [options.transaction]
*
* @return {Promise<this>}
*/
Instance.prototype.increment = function(fields, options) {
var identifier = this.where()
, updatedAtAttr = this.Model._timestampAttributes.updatedAt
, values = {}
, where;
if (identifier) {
for (var attrName in identifier) {
// Field name mapping
if (this.Model.rawAttributes[attr] && this.Model.rawAttributes[attr].field && this.Model.rawAttributes[attr].field !== attr) {
values[this.Model.rawAttributes[attr].field] = values[attr];
delete values[attr];
var rawAttribute = this.Model.rawAttributes[attrName];
if (rawAttribute.field && rawAttribute.field !== rawAttribute.fieldName) {
identifier[this.Model.rawAttributes[attrName].field] = identifier[attrName];
delete identifier[attrName];
}
}, this);
return this.sequelize.getQueryInterface().increment(this, this.Model.getTableName(options), values, where, options).return(this);
};
/**
* Decrement the value of one or more columns. This is done in the database, which means it does not use the values currently stored on the Instance. The decrement is done using a
* ```sql
* SET column = column - X
* ```
* query. To get the correct value after an decrement into the Instance you should do a reload.
*
* ```js
* instance.decrement('number') // decrement number by 1
* instance.decrement(['number', 'count'], { by: 2 }) // decrement number and count by 2
* instance.decrement({ answer: 42, tries: 1}, { by: 2 }) // decrement answer by 42, and tries by 1.
* // `by` is ignored, since each column has its own value
* ```
*
* @see {Instance#reload}
* @param {String|Array|Object} fields If a string is provided, that column is decremented by the value of `by` given in options. If an array is provided, the same is true for each column. If and object is provided, each column is decremented by the value given
* @param {Object} [options]
* @param {Integer} [options.by=1] The number to decrement by
* @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql.
* @param {Transaction} [options.transaction]
*
* @return {Promise}
*/
Instance.prototype.decrement = function(fields, options) {
options = _.defaults(options || {}, {
by: 1
});
if (!Utils._.isString(fields) && !Utils._.isArray(fields)) { // Assume fields is key-value pairs
Utils._.each(fields, function(value, field) {
fields[field] = -value;
});
}
}
options.by = 0 - options.by;
return this.increment(fields, options);
};
/**
* Check whether all values of this and `other` Instance are the same
*
* @param {Instance} other
* @return {Boolean}
*/
Instance.prototype.equals = function(other) {
var result = true;
Utils._.each(this.dataValues, function(value, key) {
if (Utils._.isDate(value) && Utils._.isDate(other[key])) {
result = result && (value.getTime() === other[key].getTime());
} else {
result = result && (value === other[key]);
}
});
return result;
};
options = _.defaults(options || {}, {
by: 1,
attributes: {},
where: {}
});
/**
* Check if this is eqaul to one of `others` by calling equals
*
* @param {Array} others
* @return {Boolean}
*/
Instance.prototype.equalsOneOf = function(others) {
var self = this;
where = _.extend(options.where || {}, identifier);
return _.any(others, function(other) {
return self.equals(other);
if (Utils._.isString(fields)) {
values[fields] = options.by;
} else if (Utils._.isArray(fields)) {
Utils._.each(fields, function(field) {
values[field] = options.by;
});
};
Instance.prototype.setValidators = function(attribute, validators) {
this.validators[attribute] = validators;
};
} else { // Assume fields is key-value pairs
values = fields;
}
if (updatedAtAttr && !values[updatedAtAttr]) {
options.attributes[updatedAtAttr] = this.Model.$getDefaultTimestamp(updatedAtAttr) || Utils.now(this.sequelize.options.dialect);
}
Object.keys(values).forEach(function(attr) {
// Field name mapping
if (this.Model.rawAttributes[attr] && this.Model.rawAttributes[attr].field && this.Model.rawAttributes[attr].field !== attr) {
values[this.Model.rawAttributes[attr].field] = values[attr];
delete values[attr];
}
}, this);
return this.sequelize.getQueryInterface().increment(this, this.Model.getTableName(options), values, where, options).return(this);
};
/**
* Decrement the value of one or more columns. This is done in the database, which means it does not use the values currently stored on the Instance. The decrement is done using a
* ```sql
* SET column = column - X
* ```
* query. To get the correct value after an decrement into the Instance you should do a reload.
*
* ```js
* instance.decrement('number') // decrement number by 1
* instance.decrement(['number', 'count'], { by: 2 }) // decrement number and count by 2
* instance.decrement({ answer: 42, tries: 1}, { by: 2 }) // decrement answer by 42, and tries by 1.
* // `by` is ignored, since each column has its own value
* ```
*
* @see {Instance#reload}
* @param {String|Array|Object} fields If a string is provided, that column is decremented by the value of `by` given in options. If an array is provided, the same is true for each column. If and object is provided, each column is decremented by the value given
* @param {Object} [options]
* @param {Integer} [options.by=1] The number to decrement by
* @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql.
* @param {Transaction} [options.transaction]
*
* @return {Promise}
*/
Instance.prototype.decrement = function(fields, options) {
options = _.defaults(options || {}, {
by: 1
});
/**
* Convert the instance to a JSON representation. Proxies to calling `get` with no keys. This means get all values gotten from the DB, and apply all custom getters.
*
* @see {Instance#get}
* @return {object}
*/
Instance.prototype.toJSON = function() {
return this.get({
plain: true
if (!Utils._.isString(fields) && !Utils._.isArray(fields)) { // Assume fields is key-value pairs
Utils._.each(fields, function(value, field) {
fields[field] = -value;
});
};
}
// private
var initValues = function(values, options) {
var defaults
, key;
options.by = 0 - options.by;
values = values && _.clone(values) || {};
return this.increment(fields, options);
};
if (options.isNewRecord) {
defaults = {};
/**
* Check whether all values of this and `other` Instance are the same
*
* @param {Instance} other
* @return {Boolean}
*/
Instance.prototype.equals = function(other) {
var result = true;
if (this.Model._hasDefaultValues) {
Utils._.each(this.Model._defaultValues, function(valueFn, key) {
if (defaults[key] === undefined) {
defaults[key] = valueFn();
}
});
}
// set id to null if not passed as value, a newly created dao has no id
// removing this breaks bulkCreate
// do after default values since it might have UUID as a default value
if (!defaults.hasOwnProperty(this.Model.primaryKeyAttribute)) {
defaults[this.Model.primaryKeyAttribute] = null;
}
if (this.Model._timestampAttributes.createdAt && defaults[this.Model._timestampAttributes.createdAt]) {
this.dataValues[this.Model._timestampAttributes.createdAt] = Utils.toDefaultValue(defaults[this.Model._timestampAttributes.createdAt]);
delete defaults[this.Model._timestampAttributes.createdAt];
}
if (this.Model._timestampAttributes.updatedAt && defaults[this.Model._timestampAttributes.updatedAt]) {
this.dataValues[this.Model._timestampAttributes.updatedAt] = Utils.toDefaultValue(defaults[this.Model._timestampAttributes.updatedAt]);
delete defaults[this.Model._timestampAttributes.updatedAt];
}
Utils._.each(this.dataValues, function(value, key) {
if (Utils._.isDate(value) && Utils._.isDate(other[key])) {
result = result && (value.getTime() === other[key].getTime());
} else {
result = result && (value === other[key]);
}
});
if (this.Model._timestampAttributes.deletedAt && defaults[this.Model._timestampAttributes.deletedAt]) {
this.dataValues[this.Model._timestampAttributes.deletedAt] = Utils.toDefaultValue(defaults[this.Model._timestampAttributes.deletedAt]);
delete defaults[this.Model._timestampAttributes.deletedAt];
}
return result;
};
if (Object.keys(defaults).length) {
for (key in defaults) {
if (values[key] === undefined) {
this.set(key, Utils.toDefaultValue(defaults[key]), defaultsOptions);
}
}
}
}
/**
* Check if this is eqaul to one of `others` by calling equals
*
* @param {Array} others
* @return {Boolean}
*/
Instance.prototype.equalsOneOf = function(others) {
var self = this;
this.set(values, options);
};
return _.any(others, function(other) {
return self.equals(other);
});
};
Instance.prototype.setValidators = function(attribute, validators) {
this.validators[attribute] = validators;
};
/**
* Convert the instance to a JSON representation. Proxies to calling `get` with no keys. This means get all values gotten from the DB, and apply all custom getters.
*
* @see {Instance#get}
* @return {object}
*/
Instance.prototype.toJSON = function() {
return this.get({
plain: true
});
};
return Instance;
})();
module.exports = Instance;
......@@ -4,100 +4,98 @@ var Toposort = require('toposort-class')
, Utils = require('./utils')
, _ = require('lodash');
module.exports = (function() {
var ModelManager = function(sequelize) {
this.models = [];
this.sequelize = sequelize;
};
ModelManager.prototype.addModel = function(model) {
this.models.push(model);
this.sequelize.models[model.name] = model;
return model;
};
ModelManager.prototype.removeModel = function(model) {
this.models = this.models.filter(function($model) {
return $model.name !== model.name;
});
var ModelManager = function(sequelize) {
this.models = [];
this.sequelize = sequelize;
};
delete this.sequelize.models[model.name];
};
ModelManager.prototype.addModel = function(model) {
this.models.push(model);
this.sequelize.models[model.name] = model;
ModelManager.prototype.getModel = function(against, options) {
options = _.defaults(options || {}, {
attribute: 'name'
});
return model;
};
var model = this.models.filter(function(model) {
return model[options.attribute] === against;
});
ModelManager.prototype.removeModel = function(model) {
this.models = this.models.filter(function($model) {
return $model.name !== model.name;
});
return !!model ? model[0] : null;
};
delete this.sequelize.models[model.name];
};
ModelManager.prototype.__defineGetter__('all', function() {
return this.models;
ModelManager.prototype.getModel = function(against, options) {
options = _.defaults(options || {}, {
attribute: 'name'
});
/**
* Iterate over Models in an order suitable for e.g. creating tables. Will
* take foreign key constraints into account so that dependencies are visited
* before dependents.
*/
ModelManager.prototype.forEachModel = function(iterator, options) {
var models = {}
, sorter = new Toposort()
, sorted
, dep;
options = _.defaults(options || {}, {
reverse: true
});
var model = this.models.filter(function(model) {
return model[options.attribute] === against;
});
this.models.forEach(function(model) {
var deps = []
, tableName = model.getTableName();
return !!model ? model[0] : null;
};
ModelManager.prototype.__defineGetter__('all', function() {
return this.models;
});
/**
* Iterate over Models in an order suitable for e.g. creating tables. Will
* take foreign key constraints into account so that dependencies are visited
* before dependents.
*/
ModelManager.prototype.forEachModel = function(iterator, options) {
var models = {}
, sorter = new Toposort()
, sorted
, dep;
options = _.defaults(options || {}, {
reverse: true
});
if (_.isObject(tableName)) {
tableName = tableName.schema + '.' + tableName.tableName;
}
this.models.forEach(function(model) {
var deps = []
, tableName = model.getTableName();
models[tableName] = model;
if (_.isObject(tableName)) {
tableName = tableName.schema + '.' + tableName.tableName;
}
for (var attrName in model.rawAttributes) {
if (model.rawAttributes.hasOwnProperty(attrName)) {
var attribute = model.rawAttributes[attrName];
models[tableName] = model;
if (attribute.references) {
attribute = Utils.formatReferences(attribute);
dep = attribute.references.model;
for (var attrName in model.rawAttributes) {
if (model.rawAttributes.hasOwnProperty(attrName)) {
var attribute = model.rawAttributes[attrName];
if (_.isObject(dep)) {
dep = dep.schema + '.' + dep.tableName;
}
if (attribute.references) {
attribute = Utils.formatReferences(attribute);
dep = attribute.references.model;
deps.push(dep);
if (_.isObject(dep)) {
dep = dep.schema + '.' + dep.tableName;
}
deps.push(dep);
}
}
}
deps = deps.filter(function(dep) {
return tableName !== dep;
});
sorter.add(tableName, deps);
deps = deps.filter(function(dep) {
return tableName !== dep;
});
sorted = sorter.sort();
if (options.reverse) {
sorted = sorted.reverse();
}
sorted.forEach(function(name) {
iterator(models[name], name);
});
};
sorter.add(tableName, deps);
});
sorted = sorter.sort();
if (options.reverse) {
sorted = sorted.reverse();
}
sorted.forEach(function(name) {
iterator(models[name], name);
});
};
return ModelManager;
})();
module.exports = ModelManager;
This diff could not be displayed because it is too large.
'use strict';
module.exports = (function() {
var Attribute = function(options) {
var Attribute = function(options) {
if (options.type === undefined) options = {type: options};
this.type = options.type;
};
};
return Attribute;
})();
module.exports = Attribute;
......@@ -5,175 +5,173 @@ var Utils = require('./../utils')
, DataTypes = require('../data-types')
, Promise = require('bluebird');
module.exports = (function() {
var CounterCache = function(association, options) {
this.association = association;
this.source = association.source;
this.target = association.target;
this.options = options || {};
this.sequelize = this.source.modelManager.sequelize;
this.as = this.options.as;
if (association.associationType !== 'HasMany') {
throw new Error('Can only have CounterCache on HasMany association');
}
var CounterCache = function(association, options) {
this.association = association;
this.source = association.source;
this.target = association.target;
this.options = options || {};
this.sequelize = this.source.modelManager.sequelize;
this.as = this.options.as;
if (association.associationType !== 'HasMany') {
throw new Error('Can only have CounterCache on HasMany association');
}
if (this.as) {
this.isAliased = true;
this.columnName = this.as;
} else {
this.as = 'count_' + this.target.options.name.plural;
this.columnName = Utils._.camelizeIf(
this.as,
!this.source.options.underscored
);
}
this.injectAttributes();
this.injectHooks();
};
// Add countAssociation attribute to source model
CounterCache.prototype.injectAttributes = function() {
// Do not try to use a column that's already taken
Helpers.checkNamingCollision(this);
var newAttributes = {};
newAttributes[this.columnName] = {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: Utils._.partial(
Utils.toDefaultValue,
0
)
};
if (this.as) {
this.isAliased = true;
this.columnName = this.as;
} else {
this.as = 'count_' + this.target.options.name.plural;
this.columnName = Utils._.camelizeIf(
this.as,
!this.source.options.underscored
);
}
Utils.mergeDefaults(this.source.rawAttributes, newAttributes);
this.injectAttributes();
this.injectHooks();
};
// Sync attributes and setters/getters to DAO prototype
this.source.refreshAttributes();
};
// Add setAssociaton method to the prototype of the model instance
CounterCache.prototype.injectHooks = function() {
var association = this.association,
counterCacheInstance = this,
CounterUtil,
fullUpdateHook,
atomicHooks,
previousTargetId;
CounterUtil = {
update: function (targetId) {
var query = CounterUtil._targetQuery(targetId);
return association.target.count({ where: query }).then(function (count) {
var newValues = {};
// Add countAssociation attribute to source model
CounterCache.prototype.injectAttributes = function() {
// Do not try to use a column that's already taken
Helpers.checkNamingCollision(this);
query = CounterUtil._sourceQuery(targetId);
var newAttributes = {};
newValues[counterCacheInstance.columnName] = count;
newAttributes[this.columnName] = {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: Utils._.partial(
Utils.toDefaultValue,
0
)
};
return association.source.update(newValues, { where: query });
});
},
increment: function (targetId) {
var query = CounterUtil._sourceQuery(targetId);
Utils.mergeDefaults(this.source.rawAttributes, newAttributes);
return association.source.find({ where: query }).then(function (instance) {
return instance.increment(counterCacheInstance.columnName, { by: 1 });
});
},
decrement: function (targetId) {
var query = CounterUtil._sourceQuery(targetId);
// Sync attributes and setters/getters to DAO prototype
this.source.refreshAttributes();
return association.source.find({ where: query }).then(function (instance) {
return instance.decrement(counterCacheInstance.columnName, { by: 1 });
});
},
// helpers
_targetQuery: function (id) {
var query = {};
query[association.identifier] = id;
return query;
},
_sourceQuery: function (id) {
var query = {};
query[association.source.primaryKeyAttribute] = id;
return query;
}
};
// Add setAssociaton method to the prototype of the model instance
CounterCache.prototype.injectHooks = function() {
var association = this.association,
counterCacheInstance = this,
CounterUtil,
fullUpdateHook,
atomicHooks,
previousTargetId;
CounterUtil = {
update: function (targetId) {
var query = CounterUtil._targetQuery(targetId);
return association.target.count({ where: query }).then(function (count) {
var newValues = {};
query = CounterUtil._sourceQuery(targetId);
newValues[counterCacheInstance.columnName] = count;
return association.source.update(newValues, { where: query });
});
},
increment: function (targetId) {
var query = CounterUtil._sourceQuery(targetId);
return association.source.find({ where: query }).then(function (instance) {
return instance.increment(counterCacheInstance.columnName, { by: 1 });
});
},
decrement: function (targetId) {
var query = CounterUtil._sourceQuery(targetId);
return association.source.find({ where: query }).then(function (instance) {
return instance.decrement(counterCacheInstance.columnName, { by: 1 });
});
},
// helpers
_targetQuery: function (id) {
var query = {};
query[association.identifier] = id;
return query;
},
_sourceQuery: function (id) {
var query = {};
query[association.source.primaryKeyAttribute] = id;
return query;
}
};
fullUpdateHook = function (target) {
var targetId = target.get(association.identifier)
, promises = [];
fullUpdateHook = function (target) {
var targetId = target.get(association.identifier)
, promises = [];
if (targetId) {
promises.push(CounterUtil.update(targetId));
}
if (previousTargetId && previousTargetId !== targetId) {
promises.push(CounterUtil.update(previousTargetId));
}
return Promise.all(promises).return(undefined);
};
atomicHooks = {
create: function (target) {
var targetId = target.get(association.identifier);
if (targetId) {
promises.push(CounterUtil.update(targetId));
return CounterUtil.increment(targetId);
}
},
update: function (target) {
var targetId = target.get(association.identifier)
, promises = [];
if (previousTargetId && previousTargetId !== targetId) {
promises.push(CounterUtil.update(previousTargetId));
if (targetId && !previousTargetId) {
promises.push(CounterUtil.increment(targetId));
}
if (!targetId && previousTargetId) {
promises.push(CounterUtil.decrement(targetId));
}
if (previousTargetId && targetId && previousTargetId !== targetId) {
promises.push(CounterUtil.increment(targetId));
promises.push(CounterUtil.decrement(previousTargetId));
}
return Promise.all(promises);
},
destroy: function (target) {
var targetId = target.get(association.identifier);
return Promise.all(promises).return(undefined);
};
atomicHooks = {
create: function (target) {
var targetId = target.get(association.identifier);
if (targetId) {
return CounterUtil.increment(targetId);
}
},
update: function (target) {
var targetId = target.get(association.identifier)
, promises = [];
if (targetId && !previousTargetId) {
promises.push(CounterUtil.increment(targetId));
}
if (!targetId && previousTargetId) {
promises.push(CounterUtil.decrement(targetId));
}
if (previousTargetId && targetId && previousTargetId !== targetId) {
promises.push(CounterUtil.increment(targetId));
promises.push(CounterUtil.decrement(previousTargetId));
}
return Promise.all(promises);
},
destroy: function (target) {
var targetId = target.get(association.identifier);
if (targetId) {
return CounterUtil.decrement(targetId);
}
if (targetId) {
return CounterUtil.decrement(targetId);
}
};
// previousDataValues are cleared before afterUpdate, so we need to save this here
association.target.addHook('beforeUpdate', function (target) {
previousTargetId = target.previous(association.identifier);
});
if (this.options.atomic === false) {
association.target.addHook('afterCreate', fullUpdateHook);
association.target.addHook('afterUpdate', fullUpdateHook);
association.target.addHook('afterDestroy', fullUpdateHook);
} else {
association.target.addHook('afterCreate', atomicHooks.create);
association.target.addHook('afterUpdate', atomicHooks.update);
association.target.addHook('afterDestroy', atomicHooks.destroy);
}
};
return CounterCache;
})();
// previousDataValues are cleared before afterUpdate, so we need to save this here
association.target.addHook('beforeUpdate', function (target) {
previousTargetId = target.previous(association.identifier);
});
if (this.options.atomic === false) {
association.target.addHook('afterCreate', fullUpdateHook);
association.target.addHook('afterUpdate', fullUpdateHook);
association.target.addHook('afterDestroy', fullUpdateHook);
} else {
association.target.addHook('afterCreate', atomicHooks.create);
association.target.addHook('afterUpdate', atomicHooks.update);
association.target.addHook('afterDestroy', atomicHooks.destroy);
}
};
module.exports = CounterCache;
......@@ -8,176 +8,158 @@ var Utils = require('./utils')
, Promise = require('./promise')
, QueryTypes = require('./query-types');
module.exports = (function() {
/*
* The interface that Sequelize uses to talk to all databases
* @class QueryInterface
**/
var QueryInterface = function(sequelize) {
this.sequelize = sequelize;
this.QueryGenerator = this.sequelize.dialect.QueryGenerator;
};
QueryInterface.prototype.createSchema = function(schema, options) {
options = options || {};
var sql = this.QueryGenerator.createSchema(schema);
return this.sequelize.query(sql, {logging: options.logging});
};
QueryInterface.prototype.dropSchema = function(schema, options) {
options = options || {};
var sql = this.QueryGenerator.dropSchema(schema);
return this.sequelize.query(sql, {logging: options.logging});
};
QueryInterface.prototype.dropAllSchemas = function(options) {
options = options || {};
var self = this;
if (!this.QueryGenerator._dialect.supports.schemas) {
return this.sequelize.drop({ logging: options.logging });
} else {
return this.showAllSchemas(options).map(function(schemaName) {
return self.dropSchema(schemaName, { logging: options.logging });
});
}
};
QueryInterface.prototype.showAllSchemas = function(options) {
var self = this;
options = Utils._.extend({
raw: true,
type: this.sequelize.QueryTypes.SELECT,
logging: false
}, options || {});
var showSchemasSql = self.QueryGenerator.showSchemasQuery();
return this.sequelize.query(showSchemasSql, options).then(function(schemaNames) {
return Utils._.flatten(
Utils._.map(schemaNames, function(value) {
return (!!value.schema_name ? value.schema_name : value);
})
);
/*
* The interface that Sequelize uses to talk to all databases
* @class QueryInterface
**/
var QueryInterface = function(sequelize) {
this.sequelize = sequelize;
this.QueryGenerator = this.sequelize.dialect.QueryGenerator;
};
QueryInterface.prototype.createSchema = function(schema, options) {
options = options || {};
var sql = this.QueryGenerator.createSchema(schema);
return this.sequelize.query(sql, {logging: options.logging});
};
QueryInterface.prototype.dropSchema = function(schema, options) {
options = options || {};
var sql = this.QueryGenerator.dropSchema(schema);
return this.sequelize.query(sql, {logging: options.logging});
};
QueryInterface.prototype.dropAllSchemas = function(options) {
options = options || {};
var self = this;
if (!this.QueryGenerator._dialect.supports.schemas) {
return this.sequelize.drop({ logging: options.logging });
} else {
return this.showAllSchemas(options).map(function(schemaName) {
return self.dropSchema(schemaName, { logging: options.logging });
});
};
}
};
QueryInterface.prototype.databaseVersion = function(options) {
options = options || {};
return this.sequelize.query(this.QueryGenerator.versionQuery(), {
raw: true,
type: QueryTypes.VERSION,
logging: options.logging
});
};
QueryInterface.prototype.showAllSchemas = function(options) {
var self = this;
QueryInterface.prototype.createTable = function(tableName, attributes, options) {
var keys = Object.keys(attributes)
, keyLen = keys.length
, self = this
, sql = ''
, i = 0;
options = Utils._.extend({
raw: true,
type: this.sequelize.QueryTypes.SELECT,
logging: false
}, options || {});
options = options || {};
var showSchemasSql = self.QueryGenerator.showSchemasQuery();
attributes = Utils._.mapValues(attributes, function(attribute) {
if (!Utils._.isPlainObject(attribute)) {
attribute = { type: attribute, allowNull: true };
return this.sequelize.query(showSchemasSql, options).then(function(schemaNames) {
return Utils._.flatten(
Utils._.map(schemaNames, function(value) {
return (!!value.schema_name ? value.schema_name : value);
})
);
});
};
QueryInterface.prototype.databaseVersion = function(options) {
options = options || {};
return this.sequelize.query(this.QueryGenerator.versionQuery(), {
raw: true,
type: QueryTypes.VERSION,
logging: options.logging
});
};
QueryInterface.prototype.createTable = function(tableName, attributes, options) {
var keys = Object.keys(attributes)
, keyLen = keys.length
, self = this
, sql = ''
, i = 0;
options = options || {};
attributes = Utils._.mapValues(attributes, function(attribute) {
if (!Utils._.isPlainObject(attribute)) {
attribute = { type: attribute, allowNull: true };
}
attribute.type = self.sequelize.normalizeDataType(attribute.type);
if (attribute.hasOwnProperty('defaultValue')) {
if (typeof attribute.defaultValue === 'function' && (
attribute.defaultValue === DataTypes.NOW ||
attribute.defaultValue === DataTypes.UUIDV4 ||
attribute.defaultValue === DataTypes.UUIDV4
)) {
attribute.defaultValue = new attribute.defaultValue();
}
}
attribute.type = self.sequelize.normalizeDataType(attribute.type);
if (attribute.type instanceof DataTypes.ENUM) {
// The ENUM is a special case where the type is an object containing the values
attribute.values = attribute.values || attribute.type.values || [];
if (attribute.hasOwnProperty('defaultValue')) {
if (typeof attribute.defaultValue === 'function' && (
attribute.defaultValue === DataTypes.NOW ||
attribute.defaultValue === DataTypes.UUIDV4 ||
attribute.defaultValue === DataTypes.UUIDV4
)) {
attribute.defaultValue = new attribute.defaultValue();
}
if (!attribute.values.length) {
throw new Error('Values for ENUM haven\'t been defined.');
}
}
if (attribute.type instanceof DataTypes.ENUM) {
// The ENUM is a special case where the type is an object containing the values
attribute.values = attribute.values || attribute.type.values || [];
return attribute;
});
if (!attribute.values.length) {
throw new Error('Values for ENUM haven\'t been defined.');
}
}
// Postgres requires a special SQL command for enums
if (self.sequelize.options.dialect === 'postgres') {
var promises = []
// For backwards-compatibility, public schemas don't need to
// explicitly state their schema when creating a new enum type
, getTableName = (!options || !options.schema || options.schema === 'public' ? '' : options.schema + '_') + tableName;
return attribute;
});
for (i = 0; i < keyLen; i++) {
if (attributes[keys[i]].type instanceof DataTypes.ENUM) {
sql = self.QueryGenerator.pgListEnums(getTableName, attributes[keys[i]].field || keys[i], options);
promises.push(self.sequelize.query(sql, { plain: true, raw: true, type: QueryTypes.SELECT, logging: options.logging }));
}
}
// Postgres requires a special SQL command for enums
if (self.sequelize.options.dialect === 'postgres') {
return Promise.all(promises).then(function(results) {
var promises = []
// For backwards-compatibility, public schemas don't need to
// explicitly state their schema when creating a new enum type
, getTableName = (!options || !options.schema || options.schema === 'public' ? '' : options.schema + '_') + tableName;
, instanceTable = self.sequelize.modelManager.models.filter(function(instance) { return instance.tableName === tableName; })
, enumIdx = 0;
instanceTable = instanceTable.length > 0 ? instanceTable[0] : null;
for (i = 0; i < keyLen; i++) {
if (attributes[keys[i]].type instanceof DataTypes.ENUM) {
sql = self.QueryGenerator.pgListEnums(getTableName, attributes[keys[i]].field || keys[i], options);
promises.push(self.sequelize.query(sql, { plain: true, raw: true, type: QueryTypes.SELECT, logging: options.logging }));
}
}
return Promise.all(promises).then(function(results) {
var promises = []
, instanceTable = self.sequelize.modelManager.models.filter(function(instance) { return instance.tableName === tableName; })
, enumIdx = 0;
instanceTable = instanceTable.length > 0 ? instanceTable[0] : null;
for (i = 0; i < keyLen; i++) {
if (attributes[keys[i]].type instanceof DataTypes.ENUM) {
// If the enum type doesn't exist then create it
if (!results[enumIdx]) {
sql = self.QueryGenerator.pgEnum(getTableName, attributes[keys[i]].field || keys[i], attributes[keys[i]], options);
promises.push(self.sequelize.query(sql, { raw: true, logging: options.logging }));
} else if (!!results[enumIdx] && !!instanceTable) {
var enumVals = self.QueryGenerator.fromArray(results[enumIdx].enum_value)
, vals = instanceTable.rawAttributes[keys[i]].values;
vals.forEach(function(value, idx) {
// reset out after/before options since it's for every enum value
options.before = null;
options.after = null;
if (enumVals.indexOf(value) === -1) {
if (!!vals[idx + 1]) {
options.before = vals[idx + 1];
}
else if (!!vals[idx - 1]) {
options.after = vals[idx - 1];
}
promises.push(self.sequelize.query(self.QueryGenerator.pgEnumAdd(getTableName, keys[i], value, options), options));
// If the enum type doesn't exist then create it
if (!results[enumIdx]) {
sql = self.QueryGenerator.pgEnum(getTableName, attributes[keys[i]].field || keys[i], attributes[keys[i]], options);
promises.push(self.sequelize.query(sql, { raw: true, logging: options.logging }));
} else if (!!results[enumIdx] && !!instanceTable) {
var enumVals = self.QueryGenerator.fromArray(results[enumIdx].enum_value)
, vals = instanceTable.rawAttributes[keys[i]].values;
vals.forEach(function(value, idx) {
// reset out after/before options since it's for every enum value
options.before = null;
options.after = null;
if (enumVals.indexOf(value) === -1) {
if (!!vals[idx + 1]) {
options.before = vals[idx + 1];
}
else if (!!vals[idx - 1]) {
options.after = vals[idx - 1];
}
});
enumIdx++;
}
promises.push(self.sequelize.query(self.QueryGenerator.pgEnumAdd(getTableName, keys[i], value, options), options));
}
});
enumIdx++;
}
}
}
if (!tableName.schema && options.schema) {
tableName = self.QueryGenerator.addSchema({
tableName: tableName,
schema: options.schema
});
}
attributes = self.QueryGenerator.attributesToSQL(attributes, {
context: 'createTable'
});
sql = self.QueryGenerator.createTableQuery(tableName, attributes, options);
return Promise.all(promises).then(function() {
return self.sequelize.query(sql, options);
});
});
} else {
if (!tableName.schema && options.schema) {
tableName = self.QueryGenerator.addSchema({
tableName: tableName,
......@@ -190,744 +172,760 @@ module.exports = (function() {
});
sql = self.QueryGenerator.createTableQuery(tableName, attributes, options);
return self.sequelize.query(sql, options);
return Promise.all(promises).then(function() {
return self.sequelize.query(sql, options);
});
});
} else {
if (!tableName.schema && options.schema) {
tableName = self.QueryGenerator.addSchema({
tableName: tableName,
schema: options.schema
});
}
};
QueryInterface.prototype.dropTable = function(tableName, options) {
// if we're forcing we should be cascading unless explicitly stated otherwise
options = options || {};
options.cascade = options.cascade || options.force || false;
attributes = self.QueryGenerator.attributesToSQL(attributes, {
context: 'createTable'
});
sql = self.QueryGenerator.createTableQuery(tableName, attributes, options);
return self.sequelize.query(sql, options);
}
};
var sql = this.QueryGenerator.dropTableQuery(tableName, options)
, self = this;
QueryInterface.prototype.dropTable = function(tableName, options) {
// if we're forcing we should be cascading unless explicitly stated otherwise
options = options || {};
options.cascade = options.cascade || options.force || false;
return this.sequelize.query(sql, options).then(function() {
var promises = [];
var sql = this.QueryGenerator.dropTableQuery(tableName, options)
, self = this;
// Since postgres has a special case for enums, we should drop the related
// enum type within the table and attribute
if (self.sequelize.options.dialect === 'postgres') {
var instanceTable = self.sequelize.modelManager.getModel(tableName, { attribute: 'tableName' });
return this.sequelize.query(sql, options).then(function() {
var promises = [];
// Since postgres has a special case for enums, we should drop the related
// enum type within the table and attribute
if (self.sequelize.options.dialect === 'postgres') {
var instanceTable = self.sequelize.modelManager.getModel(tableName, { attribute: 'tableName' });
if (!!instanceTable) {
var getTableName = (!options || !options.schema || options.schema === 'public' ? '' : options.schema + '_') + tableName;
if (!!instanceTable) {
var getTableName = (!options || !options.schema || options.schema === 'public' ? '' : options.schema + '_') + tableName;
var keys = Object.keys(instanceTable.rawAttributes)
, keyLen = keys.length
, i = 0;
var keys = Object.keys(instanceTable.rawAttributes)
, keyLen = keys.length
, i = 0;
for (i = 0; i < keyLen; i++) {
if (instanceTable.rawAttributes[keys[i]].type instanceof DataTypes.ENUM) {
promises.push(self.sequelize.query(self.QueryGenerator.pgEnumDrop(getTableName, keys[i]), {logging: options.logging, raw: true}));
}
for (i = 0; i < keyLen; i++) {
if (instanceTable.rawAttributes[keys[i]].type instanceof DataTypes.ENUM) {
promises.push(self.sequelize.query(self.QueryGenerator.pgEnumDrop(getTableName, keys[i]), {logging: options.logging, raw: true}));
}
}
}
}
return Promise.all(promises).get(0);
});
};
QueryInterface.prototype.dropAllTables = function(options) {
var self = this;
return Promise.all(promises).get(0);
options = options || {};
var dropAllTables = function(tableNames) {
return Promise.each(tableNames, function(tableName) {
// if tableName is not in the Array of tables names then dont drop it
if (skip.indexOf(tableName.tableName || tableName) === -1) {
return self.dropTable(tableName, { cascade: true, logging: options.logging });
}
});
};
QueryInterface.prototype.dropAllTables = function(options) {
var self = this;
var skip = options.skip || [];
return self.showAllTables().then(function(tableNames) {
if (self.sequelize.options.dialect === 'sqlite') {
return self.sequelize.query('PRAGMA foreign_keys;', options).then(function(result) {
var foreignKeysAreEnabled = result.foreign_keys === 1;
options = options || {};
var dropAllTables = function(tableNames) {
return Promise.each(tableNames, function(tableName) {
// if tableName is not in the Array of tables names then dont drop it
if (skip.indexOf(tableName.tableName || tableName) === -1) {
return self.dropTable(tableName, { cascade: true, logging: options.logging });
if (foreignKeysAreEnabled) {
return self.sequelize.query('PRAGMA foreign_keys = OFF', options).then(function() {
return dropAllTables(tableNames).then(function() {
return self.sequelize.query('PRAGMA foreign_keys = ON', options);
});
});
} else {
return dropAllTables(tableNames);
}
});
};
} else {
return self.getForeignKeysForTables(tableNames).then(function(foreignKeys) {
var promises = [];
var skip = options.skip || [];
return self.showAllTables().then(function(tableNames) {
if (self.sequelize.options.dialect === 'sqlite') {
return self.sequelize.query('PRAGMA foreign_keys;', options).then(function(result) {
var foreignKeysAreEnabled = result.foreign_keys === 1;
if (foreignKeysAreEnabled) {
return self.sequelize.query('PRAGMA foreign_keys = OFF', options).then(function() {
return dropAllTables(tableNames).then(function() {
return self.sequelize.query('PRAGMA foreign_keys = ON', options);
});
});
} else {
return dropAllTables(tableNames);
tableNames.forEach(function(tableName) {
var normalizedTableName = tableName;
if (Utils._.isObject(tableName)) {
normalizedTableName = tableName.schema + '.' + tableName.tableName;
}
});
} else {
return self.getForeignKeysForTables(tableNames).then(function(foreignKeys) {
var promises = [];
tableNames.forEach(function(tableName) {
var normalizedTableName = tableName;
if (Utils._.isObject(tableName)) {
normalizedTableName = tableName.schema + '.' + tableName.tableName;
}
foreignKeys[normalizedTableName].forEach(function(foreignKey) {
var sql = self.QueryGenerator.dropForeignKeyQuery(tableName, foreignKey);
promises.push(self.sequelize.query(sql, options));
});
});
return Promise.all(promises).then(function() {
return dropAllTables(tableNames);
foreignKeys[normalizedTableName].forEach(function(foreignKey) {
var sql = self.QueryGenerator.dropForeignKeyQuery(tableName, foreignKey);
promises.push(self.sequelize.query(sql, options));
});
});
}
});
};
QueryInterface.prototype.dropAllEnums = function(options) {
if (this.sequelize.getDialect() !== 'postgres') {
return Promise.resolve();
return Promise.all(promises).then(function() {
return dropAllTables(tableNames);
});
});
}
});
};
options = options || {};
QueryInterface.prototype.dropAllEnums = function(options) {
if (this.sequelize.getDialect() !== 'postgres') {
return Promise.resolve();
}
var self = this
, sql = this.QueryGenerator.pgListEnums();
options = options || {};
return this.sequelize.query(sql, { plain: false, raw: true, type: QueryTypes.SELECT, logging: options.logging }).map(function(result) {
return self.sequelize.query(
self.QueryGenerator.pgEnumDrop(null, null, self.QueryGenerator.pgEscapeAndQuote(result.enum_name)),
{logging: options.logging, raw: true}
);
});
};
var self = this
, sql = this.QueryGenerator.pgListEnums();
QueryInterface.prototype.renameTable = function(before, after, options) {
options = options || {};
var sql = this.QueryGenerator.renameTableQuery(before, after);
return this.sequelize.query(sql, { logging: options.logging });
};
return this.sequelize.query(sql, { plain: false, raw: true, type: QueryTypes.SELECT, logging: options.logging }).map(function(result) {
return self.sequelize.query(
self.QueryGenerator.pgEnumDrop(null, null, self.QueryGenerator.pgEscapeAndQuote(result.enum_name)),
{logging: options.logging, raw: true}
);
});
};
QueryInterface.prototype.renameTable = function(before, after, options) {
options = options || {};
var sql = this.QueryGenerator.renameTableQuery(before, after);
return this.sequelize.query(sql, { logging: options.logging });
};
QueryInterface.prototype.showAllTables = function(options) {
var self = this;
options = Utils._.extend({
raw: true,
type: QueryTypes.SHOWTABLES
}, options || {});
var showTablesSql = self.QueryGenerator.showTablesQuery();
return self.sequelize.query(showTablesSql, options).then(function(tableNames) {
return Utils._.flatten(tableNames);
});
};
QueryInterface.prototype.describeTable = function(tableName, options) {
var schema = null
, schemaDelimiter = null;
if (typeof options === 'string') {
schema = options;
} else if (typeof options === 'object' && options !== null) {
schema = options.schema || null;
schemaDelimiter = options.schemaDelimiter || null;
}
if (typeof tableName === 'object' && tableName !== null) {
schema = tableName.schema;
tableName = tableName.tableName;
}
var sql = this.QueryGenerator.describeTableQuery(tableName, schema, schemaDelimiter);
return this.sequelize.query(sql, { type: QueryTypes.DESCRIBE, logging: options && options.logging }).then(function(data) {
// If no data is returned from the query, then the table name may be wrong.
// Query generators that use information_schema for retrieving table info will just return an empty result set,
// it will not throw an error like built-ins do (e.g. DESCRIBE on MySql).
if (Utils._.isEmpty(data)) {
return Promise.reject('No description found for "' + tableName + '" table. Check the table name and schema; remember, they _are_ case sensitive.');
} else {
return Promise.resolve(data);
}
});
};
QueryInterface.prototype.addColumn = function(table, key, attribute, options) {
options = options || {};
attribute = this.sequelize.normalizeAttribute(attribute);
return this.sequelize.query(this.QueryGenerator.addColumnQuery(table, key, attribute), { logging: options.logging });
};
QueryInterface.prototype.removeColumn = function(tableName, attributeName, options) {
options = options || {};
if (this.sequelize.options.dialect === 'sqlite') {
// sqlite needs some special treatment as it cannot drop a column
return SQLiteQueryInterface.removeColumn.call(this, tableName, attributeName, {logging: options.logging});
} else {
var sql = this.QueryGenerator.removeColumnQuery(tableName, attributeName);
return this.sequelize.query(sql, {logging: options.logging});
}
};
QueryInterface.prototype.showAllTables = function(options) {
var self = this;
options = Utils._.extend({
raw: true,
type: QueryTypes.SHOWTABLES
}, options || {});
QueryInterface.prototype.changeColumn = function(tableName, attributeName, dataTypeOrOptions, options) {
var attributes = {};
options = options || {};
var showTablesSql = self.QueryGenerator.showTablesQuery();
return self.sequelize.query(showTablesSql, options).then(function(tableNames) {
return Utils._.flatten(tableNames);
});
};
if (Utils._.values(DataTypes).indexOf(dataTypeOrOptions) > -1) {
attributes[attributeName] = { type: dataTypeOrOptions, allowNull: true };
} else {
attributes[attributeName] = dataTypeOrOptions;
}
QueryInterface.prototype.describeTable = function(tableName, options) {
var schema = null
, schemaDelimiter = null;
attributes[attributeName].type = this.sequelize.normalizeDataType(attributes[attributeName].type);
if (typeof options === 'string') {
schema = options;
} else if (typeof options === 'object' && options !== null) {
schema = options.schema || null;
schemaDelimiter = options.schemaDelimiter || null;
}
if (this.sequelize.options.dialect === 'sqlite') {
// sqlite needs some special treatment as it cannot change a column
return SQLiteQueryInterface.changeColumn.call(this, tableName, attributes, {logging: options.logging});
} else {
var query = this.QueryGenerator.attributesToSQL(attributes)
, sql = this.QueryGenerator.changeColumnQuery(tableName, query);
if (typeof tableName === 'object' && tableName !== null) {
schema = tableName.schema;
tableName = tableName.tableName;
}
return this.sequelize.query(sql, {logging: options.logging});
}
};
var sql = this.QueryGenerator.describeTableQuery(tableName, schema, schemaDelimiter);
QueryInterface.prototype.renameColumn = function(tableName, attrNameBefore, attrNameAfter, options) {
options = options || {};
return this.describeTable(tableName, options).then(function(data) {
data = data[attrNameBefore] || {};
return this.sequelize.query(sql, { type: QueryTypes.DESCRIBE, logging: options && options.logging }).then(function(data) {
// If no data is returned from the query, then the table name may be wrong.
// Query generators that use information_schema for retrieving table info will just return an empty result set,
// it will not throw an error like built-ins do (e.g. DESCRIBE on MySql).
if (Utils._.isEmpty(data)) {
return Promise.reject('No description found for "' + tableName + '" table. Check the table name and schema; remember, they _are_ case sensitive.');
} else {
return Promise.resolve(data);
}
});
};
QueryInterface.prototype.addColumn = function(table, key, attribute, options) {
options = options || {};
attribute = this.sequelize.normalizeAttribute(attribute);
return this.sequelize.query(this.QueryGenerator.addColumnQuery(table, key, attribute), { logging: options.logging });
};
var _options = {};
QueryInterface.prototype.removeColumn = function(tableName, attributeName, options) {
options = options || {};
if (this.sequelize.options.dialect === 'sqlite') {
// sqlite needs some special treatment as it cannot drop a column
return SQLiteQueryInterface.removeColumn.call(this, tableName, attributeName, {logging: options.logging});
} else {
var sql = this.QueryGenerator.removeColumnQuery(tableName, attributeName);
return this.sequelize.query(sql, {logging: options.logging});
}
};
QueryInterface.prototype.changeColumn = function(tableName, attributeName, dataTypeOrOptions, options) {
var attributes = {};
options = options || {};
_options[attrNameAfter] = {
attribute: attrNameAfter,
type: data.type,
allowNull: data.allowNull,
defaultValue: data.defaultValue
};
if (Utils._.values(DataTypes).indexOf(dataTypeOrOptions) > -1) {
attributes[attributeName] = { type: dataTypeOrOptions, allowNull: true };
} else {
attributes[attributeName] = dataTypeOrOptions;
// fix: a not-null column cannot have null as default value
if (data.defaultValue === null && !data.allowNull) {
delete _options[attrNameAfter].defaultValue;
}
attributes[attributeName].type = this.sequelize.normalizeDataType(attributes[attributeName].type);
if (this.sequelize.options.dialect === 'sqlite') {
// sqlite needs some special treatment as it cannot change a column
return SQLiteQueryInterface.changeColumn.call(this, tableName, attributes, {logging: options.logging});
// sqlite needs some special treatment as it cannot rename a column
return SQLiteQueryInterface.renameColumn.call(this, tableName, attrNameBefore, attrNameAfter, {logging: options.logging});
} else {
var query = this.QueryGenerator.attributesToSQL(attributes)
, sql = this.QueryGenerator.changeColumnQuery(tableName, query);
var sql = this.QueryGenerator.renameColumnQuery(
tableName,
attrNameBefore,
this.QueryGenerator.attributesToSQL(_options)
);
return this.sequelize.query(sql, {logging: options.logging});
}
};
QueryInterface.prototype.renameColumn = function(tableName, attrNameBefore, attrNameAfter, options) {
options = options || {};
return this.describeTable(tableName, options).then(function(data) {
data = data[attrNameBefore] || {};
var _options = {};
_options[attrNameAfter] = {
attribute: attrNameAfter,
type: data.type,
allowNull: data.allowNull,
defaultValue: data.defaultValue
};
// fix: a not-null column cannot have null as default value
if (data.defaultValue === null && !data.allowNull) {
delete _options[attrNameAfter].defaultValue;
}
if (this.sequelize.options.dialect === 'sqlite') {
// sqlite needs some special treatment as it cannot rename a column
return SQLiteQueryInterface.renameColumn.call(this, tableName, attrNameBefore, attrNameAfter, {logging: options.logging});
} else {
var sql = this.QueryGenerator.renameColumnQuery(
tableName,
attrNameBefore,
this.QueryGenerator.attributesToSQL(_options)
);
return this.sequelize.query(sql, {logging: options.logging});
}.bind(this));
};
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)
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;
}
if (!rawTablename) {
// Map for backwards compat
rawTablename = tableName;
}
options = options || {};
options.fields = attributes;
var sql = this.QueryGenerator.addIndexQuery(tableName, options, rawTablename);
return this.sequelize.query(sql, { logging: options.logging });
};
QueryInterface.prototype.showIndex = function(tableName, options) {
var sql = this.QueryGenerator.showIndexesQuery(tableName, options);
options = options || {};
return this.sequelize.query(sql, {
transaction: options.transaction,
logging: options.logging,
type: QueryTypes.SHOWINDEXES
});
};
QueryInterface.prototype.nameIndexes = function(indexes, rawTablename) {
return this.QueryGenerator.nameIndexes(indexes, rawTablename);
};
QueryInterface.prototype.getForeignKeysForTables = function(tableNames, options) {
var self = this;
options = options || {};
if (tableNames.length === 0) {
return Promise.resolve({});
}
return Promise.map(tableNames, function(tableName) {
return self.sequelize.query(self.QueryGenerator.getForeignKeysQuery(tableName, self.sequelize.config.database), {logging: options.logging}).get(0);
}).then(function(results) {
var result = {};
tableNames.forEach(function(tableName, i) {
if (Utils._.isObject(tableName)) {
tableName = tableName.schema + '.' + tableName.tableName;
}
}.bind(this));
};
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)
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;
}
if (!rawTablename) {
// Map for backwards compat
rawTablename = tableName;
}
options = options || {};
options.fields = attributes;
var sql = this.QueryGenerator.addIndexQuery(tableName, options, rawTablename);
return this.sequelize.query(sql, { logging: options.logging });
};
QueryInterface.prototype.showIndex = function(tableName, options) {
var sql = this.QueryGenerator.showIndexesQuery(tableName, options);
options = options || {};
return this.sequelize.query(sql, {
transaction: options.transaction,
logging: options.logging,
type: QueryTypes.SHOWINDEXES
result[tableName] = Utils._.compact(results[i]).map(function(r) {
return r.constraint_name;
});
});
};
QueryInterface.prototype.nameIndexes = function(indexes, rawTablename) {
return this.QueryGenerator.nameIndexes(indexes, rawTablename);
};
QueryInterface.prototype.getForeignKeysForTables = function(tableNames, options) {
var self = this;
options = options || {};
if (tableNames.length === 0) {
return Promise.resolve({});
}
return Promise.map(tableNames, function(tableName) {
return self.sequelize.query(self.QueryGenerator.getForeignKeysQuery(tableName, self.sequelize.config.database), {logging: options.logging}).get(0);
}).then(function(results) {
var result = {};
tableNames.forEach(function(tableName, i) {
if (Utils._.isObject(tableName)) {
tableName = tableName.schema + '.' + tableName.tableName;
return result;
});
};
QueryInterface.prototype.removeIndex = function(tableName, indexNameOrAttributes, options) {
options = options || {};
var sql = this.QueryGenerator.removeIndexQuery(tableName, indexNameOrAttributes);
return this.sequelize.query(sql, {logging: options.logging});
};
QueryInterface.prototype.insert = function(instance, tableName, values, options) {
var sql = this.QueryGenerator.insertQuery(tableName, values, instance && instance.Model.rawAttributes, options);
options.type = QueryTypes.INSERT;
options.instance = instance;
return this.sequelize.query(sql, options).then(function(result) {
if (instance) result.isNewRecord = false;
return result;
});
};
QueryInterface.prototype.upsert = function(tableName, values, model, options) {
var wheres = []
, where
, indexFields
, indexes = []
, updateValues
, attributes = Object.keys(values);
where = {};
for (var i = 0; i < model.primaryKeyAttributes.length; i++) {
var key = model.primaryKeyAttributes[i];
if(key in values){
where[key] = values[key];
}
}
if (!Utils._.isEmpty(where)) {
wheres.push(where);
}
// Lets combine uniquekeys and indexes into one
indexes = Utils._.map(model.options.uniqueKeys, function (value) {
return value.fields;
});
Utils._.each(model.options.indexes, function (value) {
if (value.unique === true) {
// fields in the index may both the strings or objects with an attribute property - lets sanitize that
indexFields = Utils._.map(value.fields, function (field) {
if (Utils._.isPlainObject(field)) {
return field.attribute;
}
result[tableName] = Utils._.compact(results[i]).map(function(r) {
return r.constraint_name;
});
return field;
});
return result;
});
};
QueryInterface.prototype.removeIndex = function(tableName, indexNameOrAttributes, options) {
options = options || {};
var sql = this.QueryGenerator.removeIndexQuery(tableName, indexNameOrAttributes);
return this.sequelize.query(sql, {logging: options.logging});
};
QueryInterface.prototype.insert = function(instance, tableName, values, options) {
var sql = this.QueryGenerator.insertQuery(tableName, values, instance && instance.Model.rawAttributes, options);
options.type = QueryTypes.INSERT;
options.instance = instance;
return this.sequelize.query(sql, options).then(function(result) {
if (instance) result.isNewRecord = false;
return result;
});
};
QueryInterface.prototype.upsert = function(tableName, values, model, options) {
var wheres = []
, where
, indexFields
, indexes = []
, updateValues
, attributes = Object.keys(values);
where = {};
for (var i = 0; i < model.primaryKeyAttributes.length; i++) {
var key = model.primaryKeyAttributes[i];
if(key in values){
where[key] = values[key];
}
indexes.push(indexFields);
}
});
if (!Utils._.isEmpty(where)) {
indexes.forEach(function (index) {
if (Utils._.intersection(attributes, index).length === index.length) {
where = {};
index.forEach(function (field) {
where[field] = values[field];
});
wheres.push(where);
}
});
// Lets combine uniquekeys and indexes into one
indexes = Utils._.map(model.options.uniqueKeys, function (value) {
return value.fields;
});
where = this.sequelize.or.apply(this.sequelize, wheres);
Utils._.each(model.options.indexes, function (value) {
if (value.unique === true) {
// fields in the index may both the strings or objects with an attribute property - lets sanitize that
indexFields = Utils._.map(value.fields, function (field) {
if (Utils._.isPlainObject(field)) {
return field.attribute;
}
return field;
});
indexes.push(indexFields);
}
});
options.type = QueryTypes.UPSERT;
options.raw = true;
indexes.forEach(function (index) {
if (Utils._.intersection(attributes, index).length === index.length) {
where = {};
index.forEach(function (field) {
where[field] = values[field];
});
wheres.push(where);
}
});
where = this.sequelize.or.apply(this.sequelize, wheres);
options.type = QueryTypes.UPSERT;
options.raw = true;
if (model._timestampAttributes.createdAt) {
// If we are updating an existing row, we shouldn't set createdAt
updateValues = Utils.cloneDeep(values);
delete updateValues[model._timestampAttributes.createdAt];
} else {
updateValues = values;
}
if (model._timestampAttributes.createdAt) {
// If we are updating an existing row, we shouldn't set createdAt
updateValues = Utils.cloneDeep(values);
delete updateValues[model._timestampAttributes.createdAt];
} else {
updateValues = values;
var sql = this.QueryGenerator.upsertQuery(tableName, values, updateValues, where, model.rawAttributes, options);
return this.sequelize.query(sql, options).then(function (rowCount) {
if (rowCount === undefined) {
return rowCount;
}
var sql = this.QueryGenerator.upsertQuery(tableName, values, updateValues, where, model.rawAttributes, options);
return this.sequelize.query(sql, options).then(function (rowCount) {
if (rowCount === undefined) {
return rowCount;
}
// MySQL returns 1 for inserted, 2 for updated http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html. Postgres has been modded to do the same
// MySQL returns 1 for inserted, 2 for updated http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html. Postgres has been modded to do the same
return rowCount === 1;
});
};
return rowCount === 1;
});
};
QueryInterface.prototype.bulkInsert = function(tableName, records, options, attributes) {
options.type = QueryTypes.INSERT;
var sql = this.QueryGenerator.bulkInsertQuery(tableName, records, options, attributes);
return this.sequelize.query(sql, options);
};
QueryInterface.prototype.bulkInsert = function(tableName, records, options, attributes) {
options.type = QueryTypes.INSERT;
var sql = this.QueryGenerator.bulkInsertQuery(tableName, records, options, attributes);
return this.sequelize.query(sql, options);
};
QueryInterface.prototype.update = function(instance, tableName, values, identifier, options) {
var self = this
, restrict = false
, sql = self.QueryGenerator.updateQuery(tableName, values, identifier, options, instance.Model.rawAttributes);
QueryInterface.prototype.update = function(instance, tableName, values, identifier, options) {
var self = this
, restrict = false
, sql = self.QueryGenerator.updateQuery(tableName, values, identifier, options, instance.Model.rawAttributes);
options = options || {};
options.type = QueryTypes.UPDATE;
options = options || {};
options.type = QueryTypes.UPDATE;
// Check for a restrict field
if (!!instance.Model && !!instance.Model.associations) {
var keys = Object.keys(instance.Model.associations)
, length = keys.length;
// Check for a restrict field
if (!!instance.Model && !!instance.Model.associations) {
var keys = Object.keys(instance.Model.associations)
, length = keys.length;
for (var i = 0; i < length; i++) {
if (instance.Model.associations[keys[i]].options && instance.Model.associations[keys[i]].options.onUpdate && instance.Model.associations[keys[i]].options.onUpdate === 'restrict') {
restrict = true;
}
for (var i = 0; i < length; i++) {
if (instance.Model.associations[keys[i]].options && instance.Model.associations[keys[i]].options.onUpdate && instance.Model.associations[keys[i]].options.onUpdate === 'restrict') {
restrict = true;
}
}
}
options.instance = instance;
return this.sequelize.query(sql, options);
};
options.instance = instance;
return this.sequelize.query(sql, options);
};
QueryInterface.prototype.bulkUpdate = function(tableName, values, identifier, options, attributes) {
var sql = this.QueryGenerator.updateQuery(tableName, values, identifier, options, attributes)
, table = Utils._.isObject(tableName) ? tableName : { tableName: tableName }
, model = Utils._.find(this.sequelize.modelManager.models, { tableName: table.tableName });
QueryInterface.prototype.bulkUpdate = function(tableName, values, identifier, options, attributes) {
var sql = this.QueryGenerator.updateQuery(tableName, values, identifier, options, attributes)
, table = Utils._.isObject(tableName) ? tableName : { tableName: tableName }
, model = Utils._.find(this.sequelize.modelManager.models, { tableName: table.tableName });
options = options || {};
options.model = model;
return this.sequelize.query(sql, options);
};
options = options || {};
options.model = model;
return this.sequelize.query(sql, options);
};
QueryInterface.prototype.delete = function(instance, tableName, identifier, options) {
var self = this
, cascades = []
, sql = self.QueryGenerator.deleteQuery(tableName, identifier, null, instance.Model);
QueryInterface.prototype.delete = function(instance, tableName, identifier, options) {
var self = this
, cascades = []
, sql = self.QueryGenerator.deleteQuery(tableName, identifier, null, instance.Model);
options = options || {};
options = options || {};
// Check for a restrict field
if (!!instance.Model && !!instance.Model.associations) {
var keys = Object.keys(instance.Model.associations)
, length = keys.length;
// Check for a restrict field
if (!!instance.Model && !!instance.Model.associations) {
var keys = Object.keys(instance.Model.associations)
, length = keys.length;
for (var i = 0; i < length; i++) {
if (instance.Model.associations[keys[i]].options && instance.Model.associations[keys[i]].options.onDelete) {
if (instance.Model.associations[keys[i]].options.onDelete === 'cascade' && instance.Model.associations[keys[i]].options.useHooks === true) {
cascades.push(instance.Model.associations[keys[i]].accessors.get);
}
for (var i = 0; i < length; i++) {
if (instance.Model.associations[keys[i]].options && instance.Model.associations[keys[i]].options.onDelete) {
if (instance.Model.associations[keys[i]].options.onDelete === 'cascade' && instance.Model.associations[keys[i]].options.useHooks === true) {
cascades.push(instance.Model.associations[keys[i]].accessors.get);
}
}
}
}
return Promise.each(cascades, function (cascade) {
return instance[cascade]({
transaction: options.transaction,
logging: options.logging
}).then(function (instances) {
if (!Array.isArray(instances)) instances = [instances];
return Promise.each(instances, function (instance) {
return instance.destroy({
transaction: options.transaction,
logging: options.logging
});
return Promise.each(cascades, function (cascade) {
return instance[cascade]({
transaction: options.transaction,
logging: options.logging
}).then(function (instances) {
if (!Array.isArray(instances)) instances = [instances];
return Promise.each(instances, function (instance) {
return instance.destroy({
transaction: options.transaction,
logging: options.logging
});
});
}).then(function () {
options.instance = instance;
return self.sequelize.query(sql, options);
});
};
QueryInterface.prototype.bulkDelete = function(tableName, identifier, options, model) {
var sql = this.QueryGenerator.deleteQuery(tableName, identifier, Utils._.defaults(options || {}, {limit: null}), model);
return this.sequelize.query(sql, options);
};
QueryInterface.prototype.select = function(model, tableName, options) {
options = options || {};
options.type = QueryTypes.SELECT;
options.model = model;
return this.sequelize.query(
this.QueryGenerator.selectQuery(tableName, options, model),
options
);
};
QueryInterface.prototype.increment = function(instance, tableName, values, identifier, options) {
var sql = this.QueryGenerator.incrementQuery(tableName, values, identifier, options.attributes);
options = options || {};
options.type = QueryTypes.RAW;
}).then(function () {
options.instance = instance;
return this.sequelize.query(sql, options);
};
return self.sequelize.query(sql, options);
});
};
QueryInterface.prototype.bulkDelete = function(tableName, identifier, options, model) {
var sql = this.QueryGenerator.deleteQuery(tableName, identifier, Utils._.defaults(options || {}, {limit: null}), model);
return this.sequelize.query(sql, options);
};
QueryInterface.prototype.select = function(model, tableName, options) {
options = options || {};
options.type = QueryTypes.SELECT;
options.model = model;
return this.sequelize.query(
this.QueryGenerator.selectQuery(tableName, options, model),
options
);
};
QueryInterface.prototype.increment = function(instance, tableName, values, identifier, options) {
var sql = this.QueryGenerator.incrementQuery(tableName, values, identifier, options.attributes);
options = options || {};
options.type = QueryTypes.RAW;
options.instance = instance;
return this.sequelize.query(sql, options);
};
QueryInterface.prototype.rawSelect = function(tableName, options, attributeSelector, Model) {
if (options.schema) {
tableName = this.QueryGenerator.addSchema({
tableName: tableName,
schema: options.schema
});
}
QueryInterface.prototype.rawSelect = function(tableName, options, attributeSelector, Model) {
if (options.schema) {
tableName = this.QueryGenerator.addSchema({
tableName: tableName,
schema: options.schema
});
}
options = _.defaults(options || {}, {
raw: true,
plain: true,
type: QueryTypes.SELECT
});
options = _.defaults(options || {}, {
raw: true,
plain: true,
type: QueryTypes.SELECT
});
var sql = this.QueryGenerator.selectQuery(tableName, options, Model);
var sql = this.QueryGenerator.selectQuery(tableName, options, Model);
if (attributeSelector === undefined) {
throw new Error('Please pass an attribute selector!');
}
if (attributeSelector === undefined) {
throw new Error('Please pass an attribute selector!');
return this.sequelize.query(sql, options).then(function(data) {
if (!options.plain) {
return data;
}
return this.sequelize.query(sql, options).then(function(data) {
if (!options.plain) {
return data;
}
var result = data ? data[attributeSelector] : null;
var result = data ? data[attributeSelector] : null;
if (options && options.dataType) {
var dataType = options.dataType;
if (options && options.dataType) {
var dataType = options.dataType;
if (dataType instanceof DataTypes.DECIMAL || dataType instanceof DataTypes.FLOAT) {
result = parseFloat(result);
} else if (dataType instanceof DataTypes.INTEGER || dataType instanceof DataTypes.BIGINT) {
result = parseInt(result, 10);
} else if (dataType instanceof DataTypes.DATE) {
if (!Utils._.isNull(result) && !Utils._.isDate(result)) {
result = new Date(result);
}
} else if (dataType instanceof DataTypes.STRING) {
// Nothing to do, result is already a string.
if (dataType instanceof DataTypes.DECIMAL || dataType instanceof DataTypes.FLOAT) {
result = parseFloat(result);
} else if (dataType instanceof DataTypes.INTEGER || dataType instanceof DataTypes.BIGINT) {
result = parseInt(result, 10);
} else if (dataType instanceof DataTypes.DATE) {
if (!Utils._.isNull(result) && !Utils._.isDate(result)) {
result = new Date(result);
}
} else if (dataType instanceof DataTypes.STRING) {
// Nothing to do, result is already a string.
}
return result;
});
};
QueryInterface.prototype.createTrigger = function(tableName, triggerName, timingType, fireOnArray, functionName, functionParams, optionsArray, options) {
var sql = this.QueryGenerator.createTrigger(tableName, triggerName, timingType, fireOnArray, functionName, functionParams, optionsArray);
options = options || {};
if (sql) {
return this.sequelize.query(sql, {logging: options.logging});
} else {
return Promise.resolve();
}
};
QueryInterface.prototype.dropTrigger = function(tableName, triggerName, options) {
var sql = this.QueryGenerator.dropTrigger(tableName, triggerName);
options = options || {};
if (sql) {
return this.sequelize.query(sql, {logging: options.logging});
} else {
return Promise.resolve();
}
};
QueryInterface.prototype.renameTrigger = function(tableName, oldTriggerName, newTriggerName, options) {
var sql = this.QueryGenerator.renameTrigger(tableName, oldTriggerName, newTriggerName);
options = options || {};
return result;
});
};
if (sql) {
return this.sequelize.query(sql, {logging: options.logging});
} else {
return Promise.resolve();
}
};
QueryInterface.prototype.createTrigger = function(tableName, triggerName, timingType, fireOnArray, functionName, functionParams, optionsArray, options) {
var sql = this.QueryGenerator.createTrigger(tableName, triggerName, timingType, fireOnArray, functionName, functionParams, optionsArray);
options = options || {};
if (sql) {
return this.sequelize.query(sql, {logging: options.logging});
} else {
return Promise.resolve();
}
};
QueryInterface.prototype.createFunction = function(functionName, params, returnType, language, body, options) {
var sql = this.QueryGenerator.createFunction(functionName, params, returnType, language, body, options);
options = options || {};
QueryInterface.prototype.dropTrigger = function(tableName, triggerName, options) {
var sql = this.QueryGenerator.dropTrigger(tableName, triggerName);
options = options || {};
if (sql) {
return this.sequelize.query(sql, {logging: options.logging});
} else {
return Promise.resolve();
}
};
if (sql) {
return this.sequelize.query(sql, {logging: options.logging});
} else {
return Promise.resolve();
}
};
QueryInterface.prototype.dropFunction = function(functionName, params, options) {
var sql = this.QueryGenerator.dropFunction(functionName, params);
options = options || {};
QueryInterface.prototype.renameTrigger = function(tableName, oldTriggerName, newTriggerName, options) {
var sql = this.QueryGenerator.renameTrigger(tableName, oldTriggerName, newTriggerName);
options = options || {};
if (sql) {
return this.sequelize.query(sql, {logging: options.logging});
} else {
return Promise.resolve();
}
};
QueryInterface.prototype.renameFunction = function(oldFunctionName, params, newFunctionName, options) {
var sql = this.QueryGenerator.renameFunction(oldFunctionName, params, newFunctionName);
options = options || {};
if (sql) {
return this.sequelize.query(sql, {logging: options.logging});
} else {
return Promise.resolve();
}
};
if (sql) {
return this.sequelize.query(sql, {logging: options.logging});
} else {
return Promise.resolve();
}
};
QueryInterface.prototype.createFunction = function(functionName, params, returnType, language, body, options) {
var sql = this.QueryGenerator.createFunction(functionName, params, returnType, language, body, options);
options = options || {};
// Helper methods useful for querying
if (sql) {
return this.sequelize.query(sql, {logging: options.logging});
} else {
return Promise.resolve();
}
};
/**
* Escape an identifier (e.g. a table or attribute name). If force is true,
* the identifier will be quoted even if the `quoteIdentifiers` option is
* false.
*/
QueryInterface.prototype.quoteIdentifier = function(identifier, force) {
return this.QueryGenerator.quoteIdentifier(identifier, force);
};
QueryInterface.prototype.dropFunction = function(functionName, params, options) {
var sql = this.QueryGenerator.dropFunction(functionName, params);
options = options || {};
QueryInterface.prototype.quoteTable = function(identifier) {
return this.QueryGenerator.quoteTable(identifier);
};
if (sql) {
return this.sequelize.query(sql, {logging: options.logging});
} else {
return Promise.resolve();
}
};
/**
* Split an identifier into .-separated tokens and quote each part.
* If force is true, the identifier will be quoted even if the
* `quoteIdentifiers` option is false.
*/
QueryInterface.prototype.quoteIdentifiers = function(identifiers, force) {
return this.QueryGenerator.quoteIdentifiers(identifiers, force);
};
QueryInterface.prototype.renameFunction = function(oldFunctionName, params, newFunctionName, options) {
var sql = this.QueryGenerator.renameFunction(oldFunctionName, params, newFunctionName);
options = options || {};
/**
* Escape a value (e.g. a string, number or date)
*/
QueryInterface.prototype.escape = function(value) {
return this.QueryGenerator.escape(value);
};
if (sql) {
return this.sequelize.query(sql, {logging: options.logging});
} else {
return Promise.resolve();
}
};
// Helper methods useful for querying
/**
* Escape an identifier (e.g. a table or attribute name). If force is true,
* the identifier will be quoted even if the `quoteIdentifiers` option is
* false.
*/
QueryInterface.prototype.quoteIdentifier = function(identifier, force) {
return this.QueryGenerator.quoteIdentifier(identifier, force);
};
QueryInterface.prototype.quoteTable = function(identifier) {
return this.QueryGenerator.quoteTable(identifier);
};
/**
* Split an identifier into .-separated tokens and quote each part.
* If force is true, the identifier will be quoted even if the
* `quoteIdentifiers` option is false.
*/
QueryInterface.prototype.quoteIdentifiers = function(identifiers, force) {
return this.QueryGenerator.quoteIdentifiers(identifiers, force);
};
/**
* Escape a value (e.g. a string, number or date)
*/
QueryInterface.prototype.escape = function(value) {
return this.QueryGenerator.escape(value);
};
QueryInterface.prototype.setAutocommit = function(transaction, value, options) {
if (!transaction || !(transaction instanceof Transaction)) {
throw new Error('Unable to set autocommit for a transaction without transaction object!');
}
options = Utils._.extend({
parent: options.transaction
}, options || {});
var sql = this.QueryGenerator.setAutocommitQuery(value, options);
if (sql) {
return this.sequelize.query(sql, { transaction: transaction, logging: options.logging });
} else {
return Promise.resolve();
}
};
QueryInterface.prototype.setAutocommit = function(transaction, value, options) {
if (!transaction || !(transaction instanceof Transaction)) {
throw new Error('Unable to set autocommit for a transaction without transaction object!');
}
QueryInterface.prototype.setIsolationLevel = function(transaction, value, options) {
if (!transaction || !(transaction instanceof Transaction)) {
throw new Error('Unable to set isolation level for a transaction without transaction object!');
}
options = Utils._.extend({
parent: options.transaction
}, options || {});
options = Utils._.extend({
parent: options.transaction
}, options || {});
var sql = this.QueryGenerator.setAutocommitQuery(value, options);
if (sql) {
return this.sequelize.query(sql, { transaction: transaction, logging: options.logging });
} else {
return Promise.resolve();
}
};
var sql = this.QueryGenerator.setIsolationLevelQuery(value, options);
QueryInterface.prototype.setIsolationLevel = function(transaction, value, options) {
if (!transaction || !(transaction instanceof Transaction)) {
throw new Error('Unable to set isolation level for a transaction without transaction object!');
}
if (sql) {
return this.sequelize.query(sql, { transaction: transaction, logging: options.logging });
} else {
return Promise.resolve();
}
};
options = Utils._.extend({
parent: options.transaction
}, options || {});
QueryInterface.prototype.startTransaction = function(transaction, options) {
if (!transaction || !(transaction instanceof Transaction)) {
throw new Error('Unable to start a transaction without transaction object!');
}
var sql = this.QueryGenerator.setIsolationLevelQuery(value, options);
options = Utils._.extend({
transaction: transaction,
parent: options.transaction
}, options || {});
if (sql) {
return this.sequelize.query(sql, { transaction: transaction, logging: options.logging });
} else {
return Promise.resolve();
}
};
var sql = this.QueryGenerator.startTransactionQuery(transaction, options);
return this.sequelize.query(sql, options);
};
QueryInterface.prototype.startTransaction = function(transaction, options) {
if (!transaction || !(transaction instanceof Transaction)) {
throw new Error('Unable to start a transaction without transaction object!');
}
QueryInterface.prototype.deferConstraints = function (transaction, options) {
options = Utils._.extend({
transaction: transaction,
parent: options.transaction
}, options || {});
options = Utils._.extend({
transaction: transaction,
parent: options.transaction
}, options || {});
var sql = this.QueryGenerator.deferConstraintsQuery(options);
var sql = this.QueryGenerator.startTransactionQuery(transaction, options);
if (sql) {
return this.sequelize.query(sql, options);
};
QueryInterface.prototype.deferConstraints = function (transaction, options) {
options = Utils._.extend({
transaction: transaction,
parent: options.transaction
}, options || {});
}
var sql = this.QueryGenerator.deferConstraintsQuery(options);
if (sql) {
return this.sequelize.query(sql, options);
}
return Promise.resolve();
};
return Promise.resolve();
};
QueryInterface.prototype.commitTransaction = function(transaction, options) {
if (!transaction || !(transaction instanceof Transaction)) {
throw new Error('Unable to commit a transaction without transaction object!');
}
QueryInterface.prototype.commitTransaction = function(transaction, options) {
if (!transaction || !(transaction instanceof Transaction)) {
throw new Error('Unable to commit a transaction without transaction object!');
}
options = Utils._.extend({
transaction: transaction,
parent: options.transaction
}, options || {});
options = Utils._.extend({
transaction: transaction,
parent: options.transaction
}, options || {});
var sql = this.QueryGenerator.commitTransactionQuery(options);
var sql = this.QueryGenerator.commitTransactionQuery(options);
if (sql) {
return this.sequelize.query(sql, options);
} else {
return Promise.resolve();
}
};
if (sql) {
return this.sequelize.query(sql, options);
} else {
return Promise.resolve();
}
};
QueryInterface.prototype.rollbackTransaction = function(transaction, options) {
if (!transaction || !(transaction instanceof Transaction)) {
throw new Error('Unable to rollback a transaction without transaction object!');
}
QueryInterface.prototype.rollbackTransaction = function(transaction, options) {
if (!transaction || !(transaction instanceof Transaction)) {
throw new Error('Unable to rollback a transaction without transaction object!');
}
options = Utils._.extend({
transaction: transaction,
parent: options.transaction
}, options || {});
options = Utils._.extend({
transaction: transaction,
parent: options.transaction
}, options || {});
var sql = this.QueryGenerator.rollbackTransactionQuery(transaction, options);
return this.sequelize.query(sql, options);
};
var sql = this.QueryGenerator.rollbackTransactionQuery(transaction, options);
return this.sequelize.query(sql, options);
};
return QueryInterface;
})();
module.exports = QueryInterface;
This diff could not be displayed because it is too large.
......@@ -3,7 +3,7 @@
var DataTypes = require('./data-types')
, SqlString = require('./sql-string')
, lodash = require('lodash')
, ParameterValidator = require('./utils/parameter-validator')
, parameterValidator = require('./utils/parameter-validator')
, inflection = require('inflection')
, dottie = require('dottie')
, uuid = require('node-uuid')
......@@ -398,9 +398,7 @@ var Utils = module.exports = {
this.logic = logic;
},
validateParameter: function(value, expectation, options) {
return ParameterValidator.check(value, expectation, options);
},
validateParameter: parameterValidator,
formatReferences: function (obj) {
if (!lodash.isPlainObject(obj.references)) {
......
......@@ -3,7 +3,7 @@
var _ = require('lodash');
var util = require('util');
var validateDeprecation = function(value, expectation, options) {
function validateDeprecation (value, expectation, options) {
if (!options.deprecated) {
return;
}
......@@ -17,9 +17,9 @@ var validateDeprecation = function(value, expectation, options) {
}
return valid;
};
}
var validate = function(value, expectation) {
function validate (value, expectation) {
// the second part of this check is a workaround to deal with an issue that occurs in node-webkit when
// using object literals. https://github.com/sequelize/sequelize/issues/2685
if (value instanceof expectation || Object.prototype.toString.call(value) === Object.prototype.toString.call(expectation.call())) {
......@@ -27,31 +27,31 @@ var validate = function(value, expectation) {
}
throw new Error(util.format('The parameter (value: %s) is no %s.', value, expectation.name));
};
module.exports = {
check: function(value, expectation, options) {
options = _.extend({
deprecated: false,
index: null,
method: null,
optional: false
}, options || {});
if (!value && options.optional) {
return true;
}
if (value === undefined) {
throw new Error('No value has been passed.');
}
if (expectation === undefined) {
throw new Error('No expectation has been passed.');
}
return false
|| validateDeprecation(value, expectation, options)
|| validate(value, expectation, options);
}
function check (value, expectation, options) {
options = _.extend({
deprecated: false,
index: null,
method: null,
optional: false
}, options || {});
if (!value && options.optional) {
return true;
}
if (value === undefined) {
throw new Error('No value has been passed.');
}
};
if (expectation === undefined) {
throw new Error('No expectation has been passed.');
}
return false
|| validateDeprecation(value, expectation, options)
|| validate(value, expectation, options);
}
module.exports = check;
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!