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

Commit 1b98f2a4 by Sascha Depold

Merge branch 'master' into features/mariadb

2 parents d8484e92 19cd2356
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
"waitsFor": false, "waitsFor": false,
"runs": false "runs": false
}, },
"node": true,
"camelcase": true, "camelcase": true,
"curly": true, "curly": true,
"forin": true, "forin": true,
...@@ -18,5 +19,7 @@ ...@@ -18,5 +19,7 @@
"asi": true, "asi": true,
"evil": false, "evil": false,
"laxcomma": true, "laxcomma": true,
"es5": true "es5": true,
"quotmark": false,
"strict": false
} }
\ No newline at end of file
...@@ -13,9 +13,9 @@ teaser: ...@@ -13,9 +13,9 @@ teaser:
test: test:
@if [ "$$GREP" ]; then \ @if [ "$$GREP" ]; then \
make teaser && ./node_modules/mocha/bin/mocha --colors --reporter $(REPORTER) -g "$$GREP" $(TESTS); \ make teaser && ./node_modules/mocha/bin/mocha --colors -t 10000 --reporter $(REPORTER) -g "$$GREP" $(TESTS); \
else \ else \
make teaser && ./node_modules/mocha/bin/mocha --colors --reporter $(REPORTER) $(TESTS); \ make teaser && ./node_modules/mocha/bin/mocha --colors -t 10000 --reporter $(REPORTER) $(TESTS); \
fi fi
......
...@@ -41,6 +41,7 @@ ...@@ -41,6 +41,7 @@
- [BUG] Fix for counts queries with no result [#906](https://github.com/sequelize/sequelize/pull/906). thanks to iamjochem - [BUG] Fix for counts queries with no result [#906](https://github.com/sequelize/sequelize/pull/906). thanks to iamjochem
- [BUG] Allow include when the same table is referenced multiple times using hasMany [#913](https://github.com/sequelize/sequelize/pull/913). thanks to janmeier - [BUG] Allow include when the same table is referenced multiple times using hasMany [#913](https://github.com/sequelize/sequelize/pull/913). thanks to janmeier
- [BUG] Allow definition of defaultValue for the timestamp columns (createdAt, updatedAt, deletedAt) [#930](https://github.com/sequelize/sequelize/pull/930). Thank to durango - [BUG] Allow definition of defaultValue for the timestamp columns (createdAt, updatedAt, deletedAt) [#930](https://github.com/sequelize/sequelize/pull/930). Thank to durango
- [BUG] Don't delete foreign keys of many-to-many associations, if still needed. [#961](https://github.com/sequelize/sequelize/pull/961). thanks to sdepold
- [FEATURE] Validate a model before it gets saved. [#601](https://github.com/sequelize/sequelize/pull/601). thanks to durango - [FEATURE] Validate a model before it gets saved. [#601](https://github.com/sequelize/sequelize/pull/601). thanks to durango
- [FEATURE] Schematics. [#564](https://github.com/sequelize/sequelize/pull/564). thanks to durango - [FEATURE] Schematics. [#564](https://github.com/sequelize/sequelize/pull/564). thanks to durango
- [FEATURE] Foreign key constraints. [#595](https://github.com/sequelize/sequelize/pull/595). thanks to optilude - [FEATURE] Foreign key constraints. [#595](https://github.com/sequelize/sequelize/pull/595). thanks to optilude
...@@ -79,6 +80,8 @@ ...@@ -79,6 +80,8 @@
- [FEATURE] Triggers for Postgres. [#915](https://github.com/sequelize/sequelize/pull/915). Thanks to jonathana. - [FEATURE] Triggers for Postgres. [#915](https://github.com/sequelize/sequelize/pull/915). Thanks to jonathana.
- [FEATURE] Support for join tables. [#877](https://github.com/sequelize/sequelize/pull/877). Thanks to janmeier. - [FEATURE] Support for join tables. [#877](https://github.com/sequelize/sequelize/pull/877). Thanks to janmeier.
- [FEATURE] Support for hooks. [#894](https://github.com/sequelize/sequelize/pull/894). Thanks to durango. - [FEATURE] Support for hooks. [#894](https://github.com/sequelize/sequelize/pull/894). Thanks to durango.
- [FEATURE] Support for literals and casts. [#950](https://github.com/sequelize/sequelize/pull/950). Thanks to durango.
- [FEATURE] Model#findOrBuild. [#960](https://github.com/sequelize/sequelize/pull/960). Thanks to durango.
- [REFACTORING] hasMany now uses a single SQL statement when creating and destroying associations, instead of removing each association seperately [690](https://github.com/sequelize/sequelize/pull/690). Inspired by [#104](https://github.com/sequelize/sequelize/issues/104). janmeier - [REFACTORING] hasMany now uses a single SQL statement when creating and destroying associations, instead of removing each association seperately [690](https://github.com/sequelize/sequelize/pull/690). Inspired by [#104](https://github.com/sequelize/sequelize/issues/104). janmeier
- [REFACTORING] Consistent handling of offset across dialects. Offset is now always applied, and limit is set to max table size of not limit is given [#725](https://github.com/sequelize/sequelize/pull/725). janmeier - [REFACTORING] Consistent handling of offset across dialects. Offset is now always applied, and limit is set to max table size of not limit is given [#725](https://github.com/sequelize/sequelize/pull/725). janmeier
- [REFACTORING] Moved Jasmine to Buster and then Buster to Mocha + Chai. sdepold and durango - [REFACTORING] Moved Jasmine to Buster and then Buster to Mocha + Chai. sdepold and durango
......
...@@ -48,6 +48,7 @@ module.exports = (function() { ...@@ -48,6 +48,7 @@ module.exports = (function() {
HasMany.prototype.injectAttributes = function() { HasMany.prototype.injectAttributes = function() {
var multiAssociation = this.target.associations.hasOwnProperty(this.associationAccessor) var multiAssociation = this.target.associations.hasOwnProperty(this.associationAccessor)
, self = this , self = this
this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.source.tableName, this.source.options.language) + "Id", this.options.underscored) this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.source.tableName, this.source.options.language) + "Id", this.options.underscored)
// is there already a single sided association between the source and the target? // is there already a single sided association between the source and the target?
...@@ -59,9 +60,15 @@ module.exports = (function() { ...@@ -59,9 +60,15 @@ module.exports = (function() {
} else { } else {
this.foreignIdentifier = this.target.associations[this.associationAccessor].identifier this.foreignIdentifier = this.target.associations[this.associationAccessor].identifier
this.target.associations[this.associationAccessor].foreignIdentifier = this.identifier this.target.associations[this.associationAccessor].foreignIdentifier = this.identifier
if (isForeignKeyDeletionAllowedFor.call(this, this.source, this.foreignIdentifier)) {
delete this.source.rawAttributes[this.foreignIdentifier] delete this.source.rawAttributes[this.foreignIdentifier]
}
if (isForeignKeyDeletionAllowedFor.call(this, this.target, this.identifier)) {
delete this.target.associations[this.associationAccessor].source.rawAttributes[this.identifier] delete this.target.associations[this.associationAccessor].source.rawAttributes[this.identifier]
} }
}
// define a new model, which connects the models // define a new model, which connects the models
var combinedTableAttributes = {} var combinedTableAttributes = {}
...@@ -256,5 +263,27 @@ module.exports = (function() { ...@@ -256,5 +263,27 @@ module.exports = (function() {
return this return this
} }
/**
* The method checks if it is ok to delete the previously defined foreign key.
* This is done because we need to keep the foreign key if another association
* is depending on it.
*
* @param {DaoFactory} daoFactory The source or target DaoFactory of this assocation
* @param {[type]} identifier The name of the foreign key identifier
* @return {Boolean} Whether or not the deletion of the foreign key is ok.
*/
var isForeignKeyDeletionAllowedFor = function(daoFactory, identifier) {
var isAllowed = true
, associationNames = Utils._.without(Object.keys(daoFactory.associations), this.associationAccessor)
associationNames.forEach(function(associationName) {
if (daoFactory.associations[associationName].identifier === identifier) {
isAllowed = false
}
})
return isAllowed
}
return HasMany return HasMany
})() })()
...@@ -531,6 +531,39 @@ module.exports = (function() { ...@@ -531,6 +531,39 @@ module.exports = (function() {
return this.build(values).save(fields) return this.build(values).save(fields)
} }
DAOFactory.prototype.findOrInitialize = DAOFactory.prototype.findOrBuild = function (params, defaults) {
var self = this
, defaultKeys = Object.keys(defaults || {})
, defaultLength = defaultKeys.length
return new Utils.CustomEventEmitter(function (emitter) {
self.find({
where: params
}).success(function (instance) {
if (instance === null) {
var i = 0
for (i = 0; i < defaultLength; i++) {
params[defaultKeys[i]] = defaults[defaultKeys[i]]
}
var build = self.build(params)
build.hookValidate({skip: Object.keys(params)}).success(function (instance) {
emitter.emit('success', build, true)
})
.error(function (error) {
emitter.emit('error', error)
})
} else {
emitter.emit('success', instance, false)
}
}).error(function (error) {
emitter.emit('error', error)
})
}).run()
}
DAOFactory.prototype.findOrCreate = function (params, defaults) { DAOFactory.prototype.findOrCreate = function (params, defaults) {
var self = this; var self = this;
...@@ -1080,7 +1113,10 @@ module.exports = (function() { ...@@ -1080,7 +1113,10 @@ module.exports = (function() {
if (include.hasOwnProperty('attributes')) { if (include.hasOwnProperty('attributes')) {
var primaryKeys; var primaryKeys;
if (include.daoFactory.hasPrimaryKeys) { if (include.daoFactory.hasPrimaryKeys) {
primaryKeys = include.daoFactory.primaryKeys primaryKeys = []
for (var field_name in include.daoFactory.primaryKeys) {
primaryKeys.push(field_name)
}
} else { } else {
primaryKeys = ['id'] primaryKeys = ['id']
} }
......
...@@ -135,7 +135,7 @@ module.exports = (function() { ...@@ -135,7 +135,7 @@ module.exports = (function() {
, ciCollation = !!self.daoFactory.options.collate && self.daoFactory.options.collate.match(/_ci$/i) , ciCollation = !!self.daoFactory.options.collate && self.daoFactory.options.collate.match(/_ci$/i)
// Unfortunately for MySQL CI collation we need to map/lowercase values again // Unfortunately for MySQL CI collation we need to map/lowercase values again
if (isEnum && isMySQL && ciCollation) { if (isEnum && isMySQL && ciCollation && (attrName in values)) {
var scopeIndex = (definition.values || []).map(function(d) { return d.toLowerCase() }).indexOf(values[attrName].toLowerCase()) var scopeIndex = (definition.values || []).map(function(d) { return d.toLowerCase() }).indexOf(values[attrName].toLowerCase())
valueOutOfScope = scopeIndex === -1 valueOutOfScope = scopeIndex === -1
......
...@@ -261,20 +261,6 @@ module.exports = (function() { ...@@ -261,20 +261,6 @@ module.exports = (function() {
}, },
/* /*
Globally enable foreign key constraints
*/
enableForeignKeyConstraintsQuery: function() {
throwMethodUndefined('enableForeignKeyConstraintsQuery')
},
/*
Globally disable foreign key constraints
*/
disableForeignKeyConstraintsQuery: function() {
throwMethodUndefined('disableForeignKeyConstraintsQuery')
},
/*
Quote an object based on its type. This is a more general version of quoteIdentifiers Quote an object based on its type. This is a more general version of quoteIdentifiers
Strings: should proxy to quoteIdentifiers Strings: should proxy to quoteIdentifiers
Arrays: First argument should be qouted, second argument should be append without quoting Arrays: First argument should be qouted, second argument should be append without quoting
...@@ -293,7 +279,7 @@ module.exports = (function() { ...@@ -293,7 +279,7 @@ module.exports = (function() {
return this.quoteIdentifiers(obj, force) return this.quoteIdentifiers(obj, force)
} else if (Array.isArray(obj)) { } else if (Array.isArray(obj)) {
return this.quote(obj[0], force) + ' ' + obj[1] return this.quote(obj[0], force) + ' ' + obj[1]
} else if (obj instanceof Utils.fn || obj instanceof Utils.col) { } else if (obj instanceof Utils.fn || obj instanceof Utils.col || obj instanceof Utils.literal || obj instanceof Utils.cast) {
return obj.toString(this) return obj.toString(this)
} else if (Utils._.isObject(obj) && 'raw' in obj) { } else if (Utils._.isObject(obj) && 'raw' in obj) {
return obj.raw return obj.raw
...@@ -363,13 +349,34 @@ module.exports = (function() { ...@@ -363,13 +349,34 @@ module.exports = (function() {
Escape a value (e.g. a string, number or date) Escape a value (e.g. a string, number or date)
*/ */
escape: function(value, field) { escape: function(value, field) {
if (value instanceof Utils.fn || value instanceof Utils.col) { if (value instanceof Utils.fn || value instanceof Utils.col || value instanceof Utils.literal || value instanceof Utils.cast) {
return value.toString(this) return value.toString(this)
} else { } else {
return SqlString.escape(value, false, null, this.dialect, field) return SqlString.escape(value, false, null, 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) {
throwMethodUndefined('getForeignKeysQuery')
},
/**
* 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) {
throwMethodUndefined('dropForeignKeyQuery')
}
} }
var throwMethodUndefined = function(methodName) { var throwMethodUndefined = function(methodName) {
......
...@@ -403,10 +403,10 @@ module.exports = (function() { ...@@ -403,10 +403,10 @@ module.exports = (function() {
}, },
showIndexQuery: function(tableName, options) { showIndexQuery: function(tableName, options) {
var sql = "SHOW INDEX FROM <%= tableName %><%= options %>" var sql = "SHOW INDEX FROM `<%= tableName %>`<%= options %>"
return Utils._.template(sql)({ return Utils._.template(sql)({
tableName: tableName, tableName: tableName,
options: (options || {}).database ? ' FROM ' + options.database : '' options: (options || {}).database ? ' FROM `' + options.database + '`' : ''
}) })
}, },
...@@ -588,22 +588,34 @@ module.exports = (function() { ...@@ -588,22 +588,34 @@ module.exports = (function() {
return fields return fields
}, },
enableForeignKeyConstraintsQuery: function() {
var sql = "SET FOREIGN_KEY_CHECKS = 1;"
return Utils._.template(sql, {})
},
disableForeignKeyConstraintsQuery: function() {
var sql = "SET FOREIGN_KEY_CHECKS = 0;"
return Utils._.template(sql, {})
},
quoteIdentifier: function(identifier, force) { quoteIdentifier: function(identifier, force) {
return Utils.addTicks(identifier, "`") return Utils.addTicks(identifier, "`")
}, },
quoteIdentifiers: function(identifiers, force) { quoteIdentifiers: function(identifiers, force) {
return identifiers.split('.').map(function(v) { return this.quoteIdentifier(v, force) }.bind(this)).join('.') return identifiers.split('.').map(function(v) { return this.quoteIdentifier(v, force) }.bind(this)).join('.')
},
/**
* 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 + "' AND CONSTRAINT_NAME!='PRIMARY' AND CONSTRAINT_SCHEMA='" + schemaName + "' AND REFERENCED_TABLE_NAME IS NOT NULL;"
},
/**
* 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.quoteIdentifier(tableName) + ' DROP FOREIGN KEY ' + this.quoteIdentifier(foreignKey) + ';'
} }
} }
......
...@@ -145,6 +145,7 @@ module.exports = (function() { ...@@ -145,6 +145,7 @@ module.exports = (function() {
// Closes a client correctly even if we have backed up queries // Closes a client correctly even if we have backed up queries
// https://github.com/brianc/node-postgres/pull/346 // https://github.com/brianc/node-postgres/pull/346
this.client.on('drain', this.client.end.bind(this.client)) this.client.on('drain', this.client.end.bind(this.client))
this.client = null
} }
this.isConnecting = false this.isConnecting = false
......
...@@ -177,7 +177,7 @@ module.exports = (function() { ...@@ -177,7 +177,7 @@ module.exports = (function() {
if (definition.indexOf('DEFAULT') > 0) { if (definition.indexOf('DEFAULT') > 0) {
attrSql += Utils._.template(query)({ attrSql += Utils._.template(query)({
tableName: this.quoteIdentifiers(tableName), tableName: this.quoteIdentifiers(tableName),
query: this.quoteIdentifier(attributeName) + ' SET DEFAULT' + definition.match(/DEFAULT ([^;]+)/)[1] query: this.quoteIdentifier(attributeName) + ' SET DEFAULT ' + definition.match(/DEFAULT ([^;]+)/)[1]
}) })
definition = definition.replace(/(DEFAULT[^;]+)/, '').trim() definition = definition.replace(/(DEFAULT[^;]+)/, '').trim()
...@@ -262,7 +262,7 @@ module.exports = (function() { ...@@ -262,7 +262,7 @@ module.exports = (function() {
query += Utils._.template(joinQuery)({ query += Utils._.template(joinQuery)({
table: this.quoteIdentifiers(include.daoFactory.tableName), table: this.quoteIdentifiers(include.daoFactory.tableName),
as: this.quoteIdentifier(include.as), as: this.quoteIdentifier(include.as),
tableLeft: this.quoteIdentifiers((include.association.associationType === 'BelongsTo') ? include.as : tableName), tableLeft: this.quoteIdentifier((include.association.associationType === 'BelongsTo') ? include.as : tableName),
attrLeft: this.quoteIdentifier(((primaryKeysLeft.length !== 1) ? 'id' : primaryKeysLeft[0])), attrLeft: this.quoteIdentifier(((primaryKeysLeft.length !== 1) ? 'id' : primaryKeysLeft[0])),
tableRight: this.quoteIdentifiers((include.association.associationType === 'BelongsTo') ? tableName : include.as), tableRight: this.quoteIdentifiers((include.association.associationType === 'BelongsTo') ? tableName : include.as),
attrRight: this.quoteIdentifier(include.association.identifier) attrRight: this.quoteIdentifier(include.association.identifier)
...@@ -714,14 +714,6 @@ module.exports = (function() { ...@@ -714,14 +714,6 @@ module.exports = (function() {
return fields return fields
}, },
enableForeignKeyConstraintsQuery: function() {
return false // not supported by dialect
},
disableForeignKeyConstraintsQuery: function() {
return false // not supported by dialect
},
createTrigger: function(tableName, triggerName, eventType, fireOnSpec, functionName, functionParams, optionsArray) { createTrigger: function(tableName, triggerName, eventType, fireOnSpec, functionName, functionParams, optionsArray) {
var sql = [ var sql = [
'CREATE <%= constraintVal %>TRIGGER <%= triggerName %>' 'CREATE <%= constraintVal %>TRIGGER <%= triggerName %>'
...@@ -1004,6 +996,28 @@ module.exports = (function() { ...@@ -1004,6 +996,28 @@ module.exports = (function() {
quoteIdentifiers: function(identifiers, force) { quoteIdentifiers: function(identifiers, force) {
return identifiers.split('.').map(function(t) { return this.quoteIdentifier(t, force) }.bind(this)).join('.') return identifiers.split('.').map(function(t) { return this.quoteIdentifier(t, force) }.bind(this)).join('.')
},
/**
* 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.quoteIdentifier(tableName) + ' DROP CONSTRAINT ' + this.quoteIdentifier(foreignKey) + ';'
} }
} }
......
...@@ -377,16 +377,6 @@ module.exports = (function() { ...@@ -377,16 +377,6 @@ module.exports = (function() {
return fields return fields
}, },
enableForeignKeyConstraintsQuery: function() {
var sql = "PRAGMA foreign_keys = ON;"
return Utils._.template(sql, {})
},
disableForeignKeyConstraintsQuery: function() {
var sql = "PRAGMA foreign_keys = OFF;"
return Utils._.template(sql, {})
},
hashToWhereConditions: function(hash) { hashToWhereConditions: function(hash) {
for (var key in hash) { for (var key in hash) {
if (hash.hasOwnProperty(key)) { if (hash.hasOwnProperty(key)) {
...@@ -486,6 +476,17 @@ module.exports = (function() { ...@@ -486,6 +476,17 @@ module.exports = (function() {
quoteIdentifiers: function(identifiers, force) { quoteIdentifiers: function(identifiers, force) {
return identifiers.split('.').map(function(v) { return this.quoteIdentifier(v, force) }.bind(this)).join('.') return identifiers.split('.').map(function(v) { return this.quoteIdentifier(v, force) }.bind(this)).join('.')
},
/**
* 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 "PRAGMA foreign_key_list(\"" + tableName + "\")"
} }
} }
......
...@@ -118,5 +118,60 @@ var QueryInterface = module.exports = { ...@@ -118,5 +118,60 @@ var QueryInterface = module.exports = {
queryAndEmit.call(this, queries.splice(queries.length - 1)[0], methodName, {}, emitter) queryAndEmit.call(this, queries.splice(queries.length - 1)[0], methodName, {}, emitter)
} }
}.bind(this)) }.bind(this))
},
dropAllTables: function() {
var self = this
return new Utils.CustomEventEmitter(function(dropAllTablesEmitter) {
var events = []
, chainer = new Utils.QueryChainer()
, onError = function(err) {
self.emit('dropAllTables', err)
dropAllTablesEmitter.emit('error', err)
}
self
.showAllTables()
.error(onError)
.success(function(tableNames) {
self
.sequelize
.query('PRAGMA foreign_keys;')
.proxy(dropAllTablesEmitter, { events: ['sql'] })
.error(onError)
.success(function(result) {
var foreignKeysAreEnabled = result.foreign_keys === 1
if (foreignKeysAreEnabled) {
var queries = []
queries.push('PRAGMA foreign_keys = OFF')
tableNames.forEach(function(tableName) {
queries.push(self.QueryGenerator.dropTableQuery(tableName).replace(';', ''))
})
queries.push('PRAGMA foreign_keys = ON')
QueryInterface.execMultiQuery.call(self, queries, 'dropAllTables', dropAllTablesEmitter, self.queryAndEmit)
} else {
// add the table removal query to the chainer
tableNames.forEach(function(tableName) {
chainer.add(self, 'dropTable', [ tableName, { cascade: true } ])
})
chainer
.runSerially()
.proxy(dropAllTablesEmitter, { events: ['sql'] })
.error(onError)
.success(function() {
self.emit('dropAllTables', null)
dropAllTablesEmitter.emit('success', null)
})
}
})
})
}).run()
} }
} }
...@@ -139,6 +139,10 @@ module.exports = (function() { ...@@ -139,6 +139,10 @@ module.exports = (function() {
result[_result.name].defaultValue = result[_result.name].defaultValue.replace(/'/g, "") result[_result.name].defaultValue = result[_result.name].defaultValue.replace(/'/g, "")
} }
}) })
} else if (this.sql.indexOf('PRAGMA foreign_keys;') !== -1) {
result = results[0]
} else if (this.sql.indexOf('PRAGMA foreign_keys') !== -1) {
result = results
} }
this.emit('success', result) this.emit('success', result)
......
...@@ -3,20 +3,19 @@ var util = require("util") ...@@ -3,20 +3,19 @@ var util = require("util")
, Promise = require("promise") , Promise = require("promise")
, proxyEventKeys = ['success', 'error', 'sql'] , proxyEventKeys = ['success', 'error', 'sql']
, tick = (typeof setImmediate !== "undefined" ? setImmediate : process.nextTick) , tick = (typeof setImmediate !== "undefined" ? setImmediate : process.nextTick)
, Utils = require('../utils')
var bindToProcess = function(fct) { var bindToProcess = function(fct) {
if (fct) { if (fct && process.domain) {
if (process.domain) { return process.domain.bind(fct)
return process.domain.bind(fct);
}
} }
return fct; return fct
}; };
module.exports = (function() { module.exports = (function() {
var CustomEventEmitter = function(fct) { var CustomEventEmitter = function(fct) {
this.fct = bindToProcess(fct); this.fct = bindToProcess(fct)
} }
util.inherits(CustomEventEmitter, EventEmitter) util.inherits(CustomEventEmitter, EventEmitter)
...@@ -50,29 +49,44 @@ module.exports = (function() { ...@@ -50,29 +49,44 @@ module.exports = (function() {
function(fct) { function(fct) {
fct = bindToProcess(fct); fct = bindToProcess(fct);
this.on('error', function(err) { fct(err, null) }) this.on('error', function(err) { fct(err, null) })
.on('success', function(result) { fct(null, result) }) .on('success', function(arg0, arg1) { fct(null, arg0, arg1) })
return this return this
} }
CustomEventEmitter.prototype.sql = CustomEventEmitter.prototype.sql = function(fct) {
function(fct) {
this.on('sql', bindToProcess(fct)) this.on('sql', bindToProcess(fct))
return this; return this;
} }
CustomEventEmitter.prototype.proxy = function(emitter) { /**
proxyEventKeys.forEach(function (eventKey) { * Proxy every event of this custom event emitter to another one.
*
* @param {CustomEventEmitter} emitter The event emitter that should receive the events.
* @return {void}
*/
CustomEventEmitter.prototype.proxy = function(emitter, options) {
options = Utils._.extend({
events: proxyEventKeys,
skipEvents: []
}, options || {})
options.events = Utils._.difference(options.events, options.skipEvents)
options.events.forEach(function (eventKey) {
this.on(eventKey, function (result) { this.on(eventKey, function (result) {
emitter.emit(eventKey, result) emitter.emit(eventKey, result)
}) })
}.bind(this)) }.bind(this))
return this
} }
CustomEventEmitter.prototype.then = CustomEventEmitter.prototype.then = function(onFulfilled, onRejected) {
function (onFulfilled, onRejected) {
var self = this var self = this
onFulfilled = bindToProcess(onFulfilled) onFulfilled = bindToProcess(onFulfilled)
onRejected = bindToProcess(onRejected) onRejected = bindToProcess(onRejected)
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
self.on('error', reject) self.on('error', reject)
.on('success', resolve); .on('success', resolve);
......
...@@ -223,35 +223,46 @@ module.exports = (function() { ...@@ -223,35 +223,46 @@ module.exports = (function() {
QueryInterface.prototype.dropAllTables = function() { QueryInterface.prototype.dropAllTables = function() {
var self = this var self = this
return new Utils.CustomEventEmitter(function(emitter) { if (this.sequelize.options.dialect === 'sqlite') {
var chainer = new Utils.QueryChainer() // sqlite needs some special treatment as it cannot drop a column
return SQLiteQueryInterface.dropAllTables.call(this)
} else {
return new Utils.CustomEventEmitter(function(dropAllTablesEmitter) {
var events = []
, chainer = new Utils.QueryChainer()
, onError = function(err) {
self.emit('dropAllTables', err)
dropAllTablesEmitter.emit('error', err)
}
self.showAllTables().success(function(tableNames) { self.showAllTables().success(function(tableNames) {
chainer.add(self, 'disableForeignKeyConstraints', []) self.getForeignKeysForTables(tableNames).success(function(foreignKeys) {
tableNames.forEach(function(tableName) { // add the foreign key removal query to the chainer
chainer.add(self, 'dropTable', [tableName, {cascade: true}]) Object.keys(foreignKeys).forEach(function(tableName) {
foreignKeys[tableName].forEach(function(foreignKey) {
var sql = self.QueryGenerator.dropForeignKeyQuery(tableName, foreignKey)
chainer.add(self.sequelize, 'query', [ sql ])
})
}) })
chainer.add(self, 'enableForeignKeyConstraints', []) // add the table removal query to the chainer
tableNames.forEach(function(tableName) {
chainer.add(self, 'dropTable', [ tableName, { cascade: true } ])
})
chainer chainer
.runSerially() .runSerially()
.success(function() { .success(function() {
self.emit('dropAllTables', null) self.emit('dropAllTables', null)
emitter.emit('success', null) dropAllTablesEmitter.emit('success', null)
})
.error(function(err) {
self.emit('dropAllTables', err)
emitter.emit('error', err)
})
}).error(function(err) {
self.emit('dropAllTables', err)
emitter.emit('error', err)
}) })
.error(onError)
}).error(onError)
}).error(onError)
}).run() }).run()
} }
}
QueryInterface.prototype.renameTable = function(before, after) { QueryInterface.prototype.renameTable = function(before, after) {
var sql = this.QueryGenerator.renameTableQuery(before, after) var sql = this.QueryGenerator.renameTableQuery(before, after)
...@@ -263,6 +274,7 @@ module.exports = (function() { ...@@ -263,6 +274,7 @@ module.exports = (function() {
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
var showTablesSql = self.QueryGenerator.showTablesQuery() var showTablesSql = self.QueryGenerator.showTablesQuery()
self.sequelize.query(showTablesSql, null, { raw: true }).success(function(tableNames) { self.sequelize.query(showTablesSql, null, { raw: true }).success(function(tableNames) {
self.emit('showAllTables', null) self.emit('showAllTables', null)
emitter.emit('success', Utils._.flatten(tableNames)) emitter.emit('success', Utils._.flatten(tableNames))
...@@ -397,6 +409,35 @@ module.exports = (function() { ...@@ -397,6 +409,35 @@ module.exports = (function() {
return queryAndEmit.call(this, sql, 'showIndex') return queryAndEmit.call(this, sql, 'showIndex')
} }
QueryInterface.prototype.getForeignKeysForTables = function(tableNames) {
var self = this
return new Utils.CustomEventEmitter(function(emitter) {
if (tableNames.length === 0) {
emitter.emit('success', {})
} else {
var chainer = new Utils.QueryChainer()
tableNames.forEach(function(tableName) {
var sql = self.QueryGenerator.getForeignKeysQuery(tableName, self.sequelize.config.database)
chainer.add(self.sequelize, 'query', [sql])
})
chainer.runSerially().proxy(emitter, {
skipEvents: ['success']
}).success(function(results) {
var result = {}
tableNames.forEach(function(tableName, i) {
result[tableName] = Utils._.compact(results[i]).map(function(r) { return r.constraint_name })
})
emitter.emit('success', result)
})
}
}).run()
}
QueryInterface.prototype.removeIndex = function(tableName, indexNameOrAttributes) { QueryInterface.prototype.removeIndex = function(tableName, indexNameOrAttributes) {
var sql = this.QueryGenerator.removeIndexQuery(tableName, indexNameOrAttributes) var sql = this.QueryGenerator.removeIndexQuery(tableName, indexNameOrAttributes)
return queryAndEmit.call(this, sql, "removeIndex") return queryAndEmit.call(this, sql, "removeIndex")
...@@ -434,13 +475,12 @@ module.exports = (function() { ...@@ -434,13 +475,12 @@ module.exports = (function() {
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer() var chainer = new Utils.QueryChainer()
chainer.add(self, 'enableForeignKeyConstraints', [])
chainer.add(self, 'queryAndEmit', [[sql, dao], 'delete']) chainer.add(self, 'queryAndEmit', [[sql, dao], 'delete'])
chainer.runSerially() chainer.runSerially()
.success(function(results){ .success(function(results){
emitter.query = { sql: sql } emitter.query = { sql: sql }
emitter.emit('success', results[1]) emitter.emit('success', results[0])
emitter.emit('sql', sql) emitter.emit('sql', sql)
}) })
.error(function(err) { .error(function(err) {
...@@ -461,7 +501,6 @@ module.exports = (function() { ...@@ -461,7 +501,6 @@ module.exports = (function() {
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer() var chainer = new Utils.QueryChainer()
chainer.add(self, 'enableForeignKeyConstraints', [])
chainer.add(self, 'queryAndEmit', [sql, 'bulkUpdate']) chainer.add(self, 'queryAndEmit', [sql, 'bulkUpdate'])
return chainer.runSerially() return chainer.runSerially()
...@@ -547,7 +586,6 @@ module.exports = (function() { ...@@ -547,7 +586,6 @@ module.exports = (function() {
var chainer = new Utils.QueryChainer() var chainer = new Utils.QueryChainer()
chainer.add(self, 'enableForeignKeyConstraints', [])
chainer.add(self, 'queryAndEmit', [[sql, dao], 'delete']) chainer.add(self, 'queryAndEmit', [[sql, dao], 'delete'])
chainer.runSerially() chainer.runSerially()
...@@ -578,7 +616,6 @@ module.exports = (function() { ...@@ -578,7 +616,6 @@ module.exports = (function() {
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer() var chainer = new Utils.QueryChainer()
chainer.add(self, 'enableForeignKeyConstraints', [])
chainer.add(self, 'queryAndEmit', [sql, 'bulkDelete', options]) chainer.add(self, 'queryAndEmit', [sql, 'bulkDelete', options])
chainer.runSerially() chainer.runSerially()
...@@ -660,30 +697,6 @@ module.exports = (function() { ...@@ -660,30 +697,6 @@ module.exports = (function() {
}).run() }).run()
} }
QueryInterface.prototype.enableForeignKeyConstraints = function() {
var sql = this.QueryGenerator.enableForeignKeyConstraintsQuery()
if(sql) {
return queryAndEmit.call(this, sql, 'enableForeignKeyConstraints')
} else {
return new Utils.CustomEventEmitter(function(emitter) {
this.emit('enableForeignKeyConstraints', null)
emitter.emit('success')
}).run()
}
}
QueryInterface.prototype.disableForeignKeyConstraints = function() {
var sql = this.QueryGenerator.disableForeignKeyConstraintsQuery()
if(sql){
return queryAndEmit.call(this, sql, 'disableForeignKeyConstraints')
} else {
return new Utils.CustomEventEmitter(function(emitter) {
this.emit('disableForeignKeyConstraints', null)
emitter.emit('success')
}).run()
}
}
QueryInterface.prototype.createTrigger = function(tableName, triggerName, timingType, fireOnArray, QueryInterface.prototype.createTrigger = function(tableName, triggerName, timingType, fireOnArray,
functionName, functionParams, optionsArray) { functionName, functionParams, optionsArray) {
var sql = this.QueryGenerator.createTrigger(tableName, triggerName, timingType, fireOnArray, functionName var sql = this.QueryGenerator.createTrigger(tableName, triggerName, timingType, fireOnArray, functionName
......
...@@ -364,5 +364,13 @@ module.exports = (function() { ...@@ -364,5 +364,13 @@ module.exports = (function() {
return new Utils.col(col) return new Utils.col(col)
} }
Sequelize.prototype.cast = function (val, type) {
return new Utils.cast(val, type)
}
Sequelize.prototype.literal = function (val) {
return new Utils.literal(val)
}
return Sequelize return Sequelize
})() })()
...@@ -492,7 +492,31 @@ var Utils = module.exports = { ...@@ -492,7 +492,31 @@ var Utils = module.exports = {
}, },
col: function (col) { col: function (col) {
this.col = col this.col = col
},
cast: function (val, type) {
this.val = val
this.type = (type || '').trim()
},
literal: function (val) {
this.val = val
}
}
// I know this may seem silly, but this gives us the ability to recognize whether
// or not we should be escaping or if we should trust the user. Basically, it
// keeps things in perspective and organized.
Utils.literal.prototype.toString = function() {
return this.val
}
Utils.cast.prototype.toString = function(queryGenerator) {
if (!this.val instanceof Utils.fn && !this.val instanceof Utils.col && !this.val instanceof Utils.literal) {
this.val = queryGenerator.escape(this.val)
} else {
this.val = this.val.toString(queryGenerator)
} }
return 'CAST(' + this.val + ' AS ' + this.type.toUpperCase() + ')'
} }
Utils.fn.prototype.toString = function(queryGenerator) { Utils.fn.prototype.toString = function(queryGenerator) {
...@@ -504,6 +528,7 @@ Utils.fn.prototype.toString = function(queryGenerator) { ...@@ -504,6 +528,7 @@ Utils.fn.prototype.toString = function(queryGenerator) {
} }
}).join(', ') + ')' }).join(', ') + ')'
} }
Utils.col.prototype.toString = function (queryGenerator) { Utils.col.prototype.toString = function (queryGenerator) {
return queryGenerator.quote(this.col) return queryGenerator.quote(this.col)
} }
......
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
}, },
"devDependencies": { "devDependencies": {
"sqlite3": "~2.1.12", "sqlite3": "~2.1.12",
"mysql": "~2.0.0-alpha8", "mysql": "~2.0.0-alpha9",
"pg": "~2.6.0", "pg": "~2.6.0",
"watchr": "~2.4.3", "watchr": "~2.4.3",
"yuidocjs": "~0.3.36", "yuidocjs": "~0.3.36",
......
...@@ -28,12 +28,9 @@ describe(Support.getTestDialectTeaser("HasMany"), function() { ...@@ -28,12 +28,9 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
describe('hasSingle', function() { describe('hasSingle', function() {
beforeEach(function(done) { beforeEach(function(done) {
var self = this var self = this
this.Article = this.sequelize.define('Article', {
'title': DataTypes.STRING this.Article = this.sequelize.define('Article', { 'title': DataTypes.STRING })
}) this.Label = this.sequelize.define('Label', { 'text': DataTypes.STRING })
this.Label = this.sequelize.define('Label', {
'text': DataTypes.STRING
})
this.Article.hasMany(this.Label) this.Article.hasMany(this.Label)
...@@ -710,6 +707,71 @@ describe(Support.getTestDialectTeaser("HasMany"), function() { ...@@ -710,6 +707,71 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
}) })
}) })
}) })
describe('belongsTo and hasMany at once', function() {
beforeEach(function() {
this.A = this.sequelize.define('a', { name: Sequelize.STRING })
this.B = this.sequelize.define('b', { name: Sequelize.STRING })
})
describe('source belongs to target', function() {
beforeEach(function(done) {
this.A.belongsTo(this.B, { as: 'relation1' })
this.A.hasMany(this.B, { as: 'relation2' })
this.B.hasMany(this.A, { as: 'relation2' })
this.sequelize.sync({ force: true }).success(function() {
done()
})
})
it('correctly uses bId in A', function(done) {
var self = this
var a1 = this.A.build({ name: 'a1' })
, b1 = this.B.build({ name: 'b1' })
a1
.save()
.then(function() { return b1.save() })
.then(function() { return a1.setRelation1(b1) })
.then(function() { return self.A.find({ where: { name: 'a1' } }) })
.done(function(a) {
expect(a.bId).to.be.eq(b1.id)
done()
})
})
})
describe('target belongs to source', function() {
beforeEach(function(done) {
this.B.belongsTo(this.A, { as: 'relation1' })
this.A.hasMany(this.B, { as: 'relation2' })
this.B.hasMany(this.A, { as: 'relation2' })
this.sequelize.sync({ force: true }).success(function() {
done()
})
})
it('correctly uses bId in A', function(done) {
var self = this
var a1 = this.A.build({ name: 'a1' })
, b1 = this.B.build({ name: 'b1' })
a1
.save()
.then(function() { return b1.save() })
.then(function() { return b1.setRelation1(a1) })
.then(function() { return self.B.find({ where: { name: 'b1' } }) })
.done(function(b) {
expect(b.aId).to.be.eq(a1.id)
done()
})
})
})
})
}) })
describe("Foreign key constraints", function() { describe("Foreign key constraints", function() {
......
...@@ -449,7 +449,6 @@ describe(Support.getTestDialectTeaser("DAO"), function () { ...@@ -449,7 +449,6 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
it("should update read only attributes as well (updatedAt)", function(done) { it("should update read only attributes as well (updatedAt)", function(done) {
var self = this var self = this
this.timeout = 2000
this.User.create({ username: 'John Doe' }).complete(function(err, originalUser) { this.User.create({ username: 'John Doe' }).complete(function(err, originalUser) {
var originallyUpdatedAt = originalUser.updatedAt var originallyUpdatedAt = originalUser.updatedAt
...@@ -565,8 +564,6 @@ describe(Support.getTestDialectTeaser("DAO"), function () { ...@@ -565,8 +564,6 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
}) })
describe('save', function() { describe('save', function() {
this.timeout(3000) // for update timestamp
it('only updates fields in passed array', function(done) { it('only updates fields in passed array', function(done) {
var self = this var self = this
, userId = null , userId = null
......
...@@ -9,8 +9,6 @@ chai.Assertion.includeStack = true ...@@ -9,8 +9,6 @@ chai.Assertion.includeStack = true
if (dialect.match(/^mysql|mariadb/)) { if (dialect.match(/^mysql|mariadb/)) {
describe('[MYSQL Specific] Connector Manager', function() { describe('[MYSQL Specific] Connector Manager', function() {
this.timeout(10000)
it('works correctly after being idle', function(done) { it('works correctly after being idle', function(done) {
var User = this.sequelize.define('User', { username: DataTypes.STRING }) var User = this.sequelize.define('User', { username: DataTypes.STRING })
, spy = sinon.spy() , spy = sinon.spy()
......
...@@ -450,10 +450,10 @@ if (dialect.match(/^mysql|mariadb/)) { ...@@ -450,10 +450,10 @@ if (dialect.match(/^mysql|mariadb/)) {
showIndexQuery: [ showIndexQuery: [
{ {
arguments: ['User'], arguments: ['User'],
expectation: 'SHOW INDEX FROM User' expectation: 'SHOW INDEX FROM `User`'
}, { }, {
arguments: ['User', { database: 'sequelize' }], arguments: ['User', { database: 'sequelize' }],
expectation: "SHOW INDEX FROM User FROM sequelize" expectation: "SHOW INDEX FROM `User` FROM `sequelize`"
} }
], ],
......
...@@ -57,7 +57,7 @@ var Support = { ...@@ -57,7 +57,7 @@ var Support = {
}, },
getSequelizeInstance: function(db, user, pass, options) { getSequelizeInstance: function(db, user, pass, options) {
options = options || {}; options = options || {}
options.dialect = options.dialect || this.getTestDialect() options.dialect = options.dialect || this.getTestDialect()
return new Sequelize(db, user, pass, options) return new Sequelize(db, user, pass, options)
}, },
...@@ -141,6 +141,7 @@ before(function(done) { ...@@ -141,6 +141,7 @@ before(function(done) {
beforeEach(function(done) { beforeEach(function(done) {
this.sequelize = sequelize this.sequelize = sequelize
Support.clearDatabase(this.sequelize, function() { Support.clearDatabase(this.sequelize, function() {
done() done()
}) })
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!