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

Commit 1b98f2a4 by Sascha Depold

Merge branch 'master' into features/mariadb

2 parents d8484e92 19cd2356
......@@ -10,6 +10,7 @@
"waitsFor": false,
"runs": false
},
"node": true,
"camelcase": true,
"curly": true,
"forin": true,
......@@ -18,5 +19,7 @@
"asi": true,
"evil": false,
"laxcomma": true,
"es5": true
"es5": true,
"quotmark": false,
"strict": false
}
\ No newline at end of file
......@@ -13,9 +13,9 @@ teaser:
test:
@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 \
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
......
......@@ -41,6 +41,7 @@
- [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 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] 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
......@@ -79,6 +80,8 @@
- [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 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] 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
......
......@@ -47,7 +47,8 @@ module.exports = (function() {
// or in an extra table which connects two tables
HasMany.prototype.injectAttributes = function() {
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)
// is there already a single sided association between the source and the target?
......@@ -59,8 +60,14 @@ module.exports = (function() {
} else {
this.foreignIdentifier = this.target.associations[this.associationAccessor].identifier
this.target.associations[this.associationAccessor].foreignIdentifier = this.identifier
delete this.source.rawAttributes[this.foreignIdentifier]
delete this.target.associations[this.associationAccessor].source.rawAttributes[this.identifier]
if (isForeignKeyDeletionAllowedFor.call(this, this.source, 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]
}
}
// define a new model, which connects the models
......@@ -256,5 +263,27 @@ module.exports = (function() {
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
})()
......@@ -359,8 +359,8 @@ module.exports = (function() {
// whereCollection is used for non-primary key updates
this.options.whereCollection = optcpy.where || null;
return this.QueryInterface.select(this, [this.getTableName(), joinTableName], optcpy, Utils._.defaults({
type: 'SELECT'
return this.QueryInterface.select(this, [this.getTableName(), joinTableName], optcpy, Utils._.defaults({
type: 'SELECT'
}, queryOptions))
}
......@@ -531,6 +531,39 @@ module.exports = (function() {
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) {
var self = this;
......@@ -1080,7 +1113,10 @@ module.exports = (function() {
if (include.hasOwnProperty('attributes')) {
var primaryKeys;
if (include.daoFactory.hasPrimaryKeys) {
primaryKeys = include.daoFactory.primaryKeys
primaryKeys = []
for (var field_name in include.daoFactory.primaryKeys) {
primaryKeys.push(field_name)
}
} else {
primaryKeys = ['id']
}
......
......@@ -135,7 +135,7 @@ module.exports = (function() {
, ciCollation = !!self.daoFactory.options.collate && self.daoFactory.options.collate.match(/_ci$/i)
// 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())
valueOutOfScope = scopeIndex === -1
......
......@@ -261,27 +261,13 @@ 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
Strings: should proxy to quoteIdentifiers
Arrays: First argument should be qouted, second argument should be append without quoting
Objects:
* If raw is set, that value should be returned verbatim, without quoting
* If fn is set, the string should start with the value of fn, starting paren, followed by
the values of cols (which is assumed to be an array), quoted and joined with ', ',
the values of cols (which is assumed to be an array), quoted and joined with ', ',
unless they are themselves objects
* If direction is set, should be prepended
......@@ -293,7 +279,7 @@ module.exports = (function() {
return this.quoteIdentifiers(obj, force)
} else if (Array.isArray(obj)) {
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)
} else if (Utils._.isObject(obj) && 'raw' in obj) {
return obj.raw
......@@ -363,13 +349,34 @@ module.exports = (function() {
Escape a value (e.g. a string, number or date)
*/
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)
} 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) {
......
......@@ -403,10 +403,10 @@ module.exports = (function() {
},
showIndexQuery: function(tableName, options) {
var sql = "SHOW INDEX FROM <%= tableName %><%= options %>"
var sql = "SHOW INDEX FROM `<%= tableName %>`<%= options %>"
return Utils._.template(sql)({
tableName: tableName,
options: (options || {}).database ? ' FROM ' + options.database : ''
options: (options || {}).database ? ' FROM `' + options.database + '`' : ''
})
},
......@@ -588,22 +588,34 @@ module.exports = (function() {
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) {
return Utils.addTicks(identifier, "`")
},
quoteIdentifiers: function(identifiers, force) {
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() {
// Closes a client correctly even if we have backed up queries
// https://github.com/brianc/node-postgres/pull/346
this.client.on('drain', this.client.end.bind(this.client))
this.client = null
}
this.isConnecting = false
......
......@@ -177,7 +177,7 @@ module.exports = (function() {
if (definition.indexOf('DEFAULT') > 0) {
attrSql += Utils._.template(query)({
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()
......@@ -262,7 +262,7 @@ module.exports = (function() {
query += Utils._.template(joinQuery)({
table: this.quoteIdentifiers(include.daoFactory.tableName),
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])),
tableRight: this.quoteIdentifiers((include.association.associationType === 'BelongsTo') ? tableName : include.as),
attrRight: this.quoteIdentifier(include.association.identifier)
......@@ -714,14 +714,6 @@ module.exports = (function() {
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) {
var sql = [
'CREATE <%= constraintVal %>TRIGGER <%= triggerName %>'
......@@ -1004,6 +996,28 @@ module.exports = (function() {
quoteIdentifiers: function(identifiers, force) {
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) + ';'
}
}
......
......@@ -179,7 +179,7 @@ module.exports = (function() {
, attrLeft = ((primaryKeysLeft.length !== 1) ? 'id' : primaryKeysLeft[0])
, tableRight = ((include.association.associationType === 'BelongsTo') ? tableName : include.as)
, attrRight = include.association.identifier
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableLeft) + "." + this.quoteIdentifier(attrLeft) + " = " + this.quoteIdentifier(tableRight) + "." + this.quoteIdentifier(attrRight)
} else {
var primaryKeysSource = Object.keys(include.association.source.primaryKeys)
......@@ -377,16 +377,6 @@ module.exports = (function() {
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) {
for (var key in hash) {
if (hash.hasOwnProperty(key)) {
......@@ -486,6 +476,17 @@ module.exports = (function() {
quoteIdentifiers: function(identifiers, force) {
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 = {
queryAndEmit.call(this, queries.splice(queries.length - 1)[0], methodName, {}, emitter)
}
}.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() {
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)
......
......@@ -3,20 +3,19 @@ var util = require("util")
, Promise = require("promise")
, proxyEventKeys = ['success', 'error', 'sql']
, tick = (typeof setImmediate !== "undefined" ? setImmediate : process.nextTick)
, Utils = require('../utils')
var bindToProcess = function(fct) {
if (fct) {
if (process.domain) {
return process.domain.bind(fct);
}
if (fct && process.domain) {
return process.domain.bind(fct)
}
return fct;
return fct
};
module.exports = (function() {
var CustomEventEmitter = function(fct) {
this.fct = bindToProcess(fct);
this.fct = bindToProcess(fct)
}
util.inherits(CustomEventEmitter, EventEmitter)
......@@ -50,29 +49,44 @@ module.exports = (function() {
function(fct) {
fct = bindToProcess(fct);
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
}
CustomEventEmitter.prototype.sql =
function(fct) {
CustomEventEmitter.prototype.sql = function(fct) {
this.on('sql', bindToProcess(fct))
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) {
emitter.emit(eventKey, result)
})
}.bind(this))
return this
}
CustomEventEmitter.prototype.then =
function (onFulfilled, onRejected) {
CustomEventEmitter.prototype.then = function(onFulfilled, onRejected) {
var self = this
onFulfilled = bindToProcess(onFulfilled)
onRejected = bindToProcess(onRejected)
return new Promise(function (resolve, reject) {
self.on('error', reject)
.on('success', resolve);
......
......@@ -223,34 +223,45 @@ module.exports = (function() {
QueryInterface.prototype.dropAllTables = function() {
var self = this
return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer()
self.showAllTables().success(function(tableNames) {
chainer.add(self, 'disableForeignKeyConstraints', [])
tableNames.forEach(function(tableName) {
chainer.add(self, 'dropTable', [tableName, {cascade: true}])
})
if (this.sequelize.options.dialect === 'sqlite') {
// 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)
}
chainer.add(self, 'enableForeignKeyConstraints', [])
self.showAllTables().success(function(tableNames) {
self.getForeignKeysForTables(tableNames).success(function(foreignKeys) {
chainer
.runSerially()
.success(function() {
self.emit('dropAllTables', null)
emitter.emit('success', null)
// add the foreign key removal query to the chainer
Object.keys(foreignKeys).forEach(function(tableName) {
foreignKeys[tableName].forEach(function(foreignKey) {
var sql = self.QueryGenerator.dropForeignKeyQuery(tableName, foreignKey)
chainer.add(self.sequelize, 'query', [ sql ])
})
})
.error(function(err) {
self.emit('dropAllTables', err)
emitter.emit('error', err)
// add the table removal query to the chainer
tableNames.forEach(function(tableName) {
chainer.add(self, 'dropTable', [ tableName, { cascade: true } ])
})
}).error(function(err) {
self.emit('dropAllTables', err)
emitter.emit('error', err)
})
}).run()
chainer
.runSerially()
.success(function() {
self.emit('dropAllTables', null)
dropAllTablesEmitter.emit('success', null)
})
.error(onError)
}).error(onError)
}).error(onError)
}).run()
}
}
QueryInterface.prototype.renameTable = function(before, after) {
......@@ -263,6 +274,7 @@ module.exports = (function() {
return new Utils.CustomEventEmitter(function(emitter) {
var showTablesSql = self.QueryGenerator.showTablesQuery()
self.sequelize.query(showTablesSql, null, { raw: true }).success(function(tableNames) {
self.emit('showAllTables', null)
emitter.emit('success', Utils._.flatten(tableNames))
......@@ -397,6 +409,35 @@ module.exports = (function() {
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) {
var sql = this.QueryGenerator.removeIndexQuery(tableName, indexNameOrAttributes)
return queryAndEmit.call(this, sql, "removeIndex")
......@@ -434,13 +475,12 @@ module.exports = (function() {
return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer()
chainer.add(self, 'enableForeignKeyConstraints', [])
chainer.add(self, 'queryAndEmit', [[sql, dao], 'delete'])
chainer.runSerially()
.success(function(results){
emitter.query = { sql: sql }
emitter.emit('success', results[1])
emitter.emit('success', results[0])
emitter.emit('sql', sql)
})
.error(function(err) {
......@@ -461,7 +501,6 @@ module.exports = (function() {
return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer()
chainer.add(self, 'enableForeignKeyConstraints', [])
chainer.add(self, 'queryAndEmit', [sql, 'bulkUpdate'])
return chainer.runSerially()
......@@ -547,7 +586,6 @@ module.exports = (function() {
var chainer = new Utils.QueryChainer()
chainer.add(self, 'enableForeignKeyConstraints', [])
chainer.add(self, 'queryAndEmit', [[sql, dao], 'delete'])
chainer.runSerially()
......@@ -578,7 +616,6 @@ module.exports = (function() {
return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer()
chainer.add(self, 'enableForeignKeyConstraints', [])
chainer.add(self, 'queryAndEmit', [sql, 'bulkDelete', options])
chainer.runSerially()
......@@ -660,30 +697,6 @@ module.exports = (function() {
}).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,
functionName, functionParams, optionsArray) {
var sql = this.QueryGenerator.createTrigger(tableName, triggerName, timingType, fireOnArray, functionName
......
......@@ -364,5 +364,13 @@ module.exports = (function() {
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
})()
......@@ -492,7 +492,31 @@ var Utils = module.exports = {
},
col: function (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) {
......@@ -501,9 +525,10 @@ Utils.fn.prototype.toString = function(queryGenerator) {
return arg.toString(queryGenerator)
} else {
return queryGenerator.escape(arg)
}
}
}).join(', ') + ')'
}
Utils.col.prototype.toString = function (queryGenerator) {
return queryGenerator.quote(this.col)
}
......
......@@ -50,7 +50,7 @@
},
"devDependencies": {
"sqlite3": "~2.1.12",
"mysql": "~2.0.0-alpha8",
"mysql": "~2.0.0-alpha9",
"pg": "~2.6.0",
"watchr": "~2.4.3",
"yuidocjs": "~0.3.36",
......
......@@ -28,12 +28,9 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
describe('hasSingle', function() {
beforeEach(function(done) {
var self = this
this.Article = this.sequelize.define('Article', {
'title': DataTypes.STRING
})
this.Label = this.sequelize.define('Label', {
'text': DataTypes.STRING
})
this.Article = this.sequelize.define('Article', { 'title': DataTypes.STRING })
this.Label = this.sequelize.define('Label', { 'text': DataTypes.STRING })
this.Article.hasMany(this.Label)
......@@ -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() {
......@@ -876,4 +938,4 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
})
})
})
})
\ No newline at end of file
})
......@@ -247,7 +247,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
done()
})
it("stores the the passed values in a special variable", function(done) {
it("stores the passed values in a special variable", function(done) {
var user = this.User.build({ username: 'John Wayne' })
expect(user.selectedValues).to.deep.equal({ username: 'John Wayne' })
done()
......@@ -328,8 +328,63 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
describe('findOrInitialize', function() {
describe('returns an instance if it already exists', function() {
it('with a single find field', function (done) {
var self = this
this.User.create({ username: 'Username' }).success(function (user) {
self.User.findOrInitialize({
username: user.username
}).success(function (_user, initialized) {
expect(_user.id).to.equal(user.id)
expect(_user.username).to.equal('Username')
expect(initialized).to.be.false
done()
})
})
})
it('with multiple find fields', function(done) {
var self = this
this.User.create({ username: 'Username', data: 'data' }).success(function (user) {
self.User.findOrInitialize({
username: user.username,
data: user.data
}).success(function (_user, initialized) {
expect(_user.id).to.equal(user.id)
expect(_user.username).to.equal('Username')
expect(_user.data).to.equal('data')
expect(initialized).to.be.false
done()
})
})
})
it('builds a new instance with default value.', function(done) {
var data = {
username: 'Username'
},
default_values = {
data: 'ThisIsData'
}
this.User.findOrInitialize(data, default_values).success(function(user, initialized) {
expect(user.id).to.be.null
expect(user.username).to.equal('Username')
expect(user.data).to.equal('ThisIsData')
expect(initialized).to.be.true
expect(user.isNewRecord).to.be.true
expect(user.isDirty).to.be.true
done()
})
})
})
})
describe('findOrCreate', function () {
it("Returns instace if already existent. Single find field.", function(done) {
it("Returns instance if already existent. Single find field.", function(done) {
var self = this,
data = {
username: 'Username'
......@@ -347,7 +402,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
it("Returns instace if already existent. Multiple find fields.", function(done) {
it("Returns instance if already existent. Multiple find fields.", function(done) {
var self = this,
data = {
username: 'Username',
......@@ -355,7 +410,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
};
this.User.create(data).success(function (user) {
self.User.findOrCreate(data).success(function (_user, created) {
self.User.findOrCreate(data).done(function (err, _user, created) {
expect(_user.id).to.equal(user.id)
expect(_user.username).to.equal('Username')
expect(_user.data).to.equal('ThisIsData')
......@@ -383,6 +438,82 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
describe('create', function() {
it('is possible to use casting when creating an instance', function (done) {
var self = this
, type = dialect === "mysql" ? 'signed' : 'integer'
, _done = _.after(2, function() {
done()
})
this.User.create({
intVal: this.sequelize.cast('1', type)
}).on('sql', function (sql) {
expect(sql).to.match(new RegExp('CAST\\(1 AS ' + type.toUpperCase() + '\\)'))
_done()
})
.success(function (user) {
self.User.find(user.id).success(function (user) {
expect(user.intVal).to.equal(1)
_done()
})
})
})
it('is possible to use casting multiple times mixed in with other utilities', function (done) {
var self = this
, type = this.sequelize.cast(this.sequelize.cast(this.sequelize.literal('1-2'), 'integer'), 'integer')
, _done = _.after(2, function() {
done()
})
if (dialect === "mysql") {
type = this.sequelize.cast(this.sequelize.cast(this.sequelize.literal('1-2'), 'unsigned'), 'signed')
}
this.User.create({
intVal: type
}).on('sql', function (sql) {
if (dialect === "mysql") {
expect(sql).to.contain('CAST(CAST(1-2 AS UNSIGNED) AS SIGNED)')
} else {
expect(sql).to.contain('CAST(CAST(1-2 AS INTEGER) AS INTEGER)')
}
_done()
}).success(function (user) {
self.User.find(user.id).success(function (user) {
expect(user.intVal).to.equal(-1)
_done()
})
})
})
it('is possible to just use .literal() to bypass escaping', function (done) {
var self = this
this.User.create({
intVal: this.sequelize.literal('CAST(1-2 AS ' + (dialect === "mysql" ? 'SIGNED' : 'INTEGER') + ')')
}).success(function (user) {
self.User.find(user.id).success(function (user) {
expect(user.intVal).to.equal(-1)
done()
})
})
})
it('is possible for .literal() to contain other utility functions', function (done) {
var self = this
this.User.create({
intVal: this.sequelize.literal(this.sequelize.cast('1-2', (dialect === "mysql" ? 'SIGNED' : 'INTEGER')))
}).success(function (user) {
self.User.find(user.id).success(function (user) {
expect(user.intVal).to.equal(-1)
done()
})
})
})
it('is possible to use funtions when creating an instance', function (done) {
var self = this
this.User.create({
......@@ -394,6 +525,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
})
it('is possible to use functions as default values', function (done) {
var self = this
, userWithDefaults
......@@ -446,6 +578,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
done()
}
})
it("casts empty arrays correctly for postgresql insert", function(done) {
if (dialect !== "postgres" && dialect !== "postgresql-native") {
expect('').to.equal('')
......@@ -666,7 +799,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
it('can omitt autoincremental columns', function(done) {
it('can omit autoincremental columns', function(done) {
var self = this
, data = { title: 'Iliad' }
, dataTypes = [Sequelize.INTEGER, Sequelize.BIGINT]
......@@ -1068,6 +1201,21 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
it('updates with casting', function (done) {
var self = this
this.User.create({
username: 'John'
}).success(function(user) {
self.User.update({username: self.sequelize.cast('1', 'char')}, {username: 'John'}).success(function() {
self.User.all().success(function(users) {
expect(users[0].username).to.equal('1')
done()
})
})
})
})
it('updates with function and column value', function (done) {
var self = this
......@@ -1110,7 +1258,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
describe('destroy', function() {
it('deletes a record from the database if dao is not paranoid', function(done) {
var UserDestroy = this.sequelize.define('UserDestory', {
var UserDestroy = this.sequelize.define('UserDestroy', {
name: Sequelize.STRING,
bio: Sequelize.TEXT
})
......@@ -1405,7 +1553,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
it('should be able to retun a record with primaryKey being null for new inserts', function(done) {
it('should be able to return a record with primaryKey being null for new inserts', function(done) {
var Session = this.sequelize.define('Session', {
token: { type: DataTypes.TEXT, allowNull: false },
lastUpdate: { type: DataTypes.DATE, allowNull: false }
......@@ -2056,6 +2204,61 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
it('getting parent data in many to one relationship', function(done) {
var self = this;
var User = self.sequelize.define('User', {
id: {type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true},
username: {type: Sequelize.STRING}
})
var Message = self.sequelize.define('Message', {
id: {type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true},
user_id: {type: Sequelize.INTEGER},
message: {type: Sequelize.STRING}
})
User.hasMany(Message)
Message.belongsTo(User, { foreignKey: 'user_id' })
Message.sync({ force: true }).success(function() {
User.sync({ force: true }).success(function() {
User.create({username: 'test_testerson'}).success(function(user) {
Message.create({user_id: user.id, message: 'hi there!'}).success(function(message) {
Message.create({user_id: user.id, message: 'a second message'}).success(function(message) {
Message.findAll({
where: {user_id: user.id},
attributes: [
'user_id',
'message'
],
include: [{ model: User, as: User.tableName, attributes: ['username'] }]
}).success(function(messages) {
expect(messages.length).to.equal(2);
expect(messages[0].message).to.equal('hi there!');
expect(messages[0].user.username).to.equal('test_testerson');
expect(messages[1].message).to.equal('a second message');
expect(messages[1].user.username).to.equal('test_testerson');
done()
})
})
})
})
})
})
})
it('allows mulitple assocations of the same model with different alias', function (done) {
var self = this
......@@ -3570,15 +3773,13 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
describe('references', function() {
this.timeout(3000)
beforeEach(function(done) {
var self = this
this.sequelize.getQueryInterface().dropTable('posts', { force: true }).success(function() {
self.sequelize.getQueryInterface().dropTable('authors', { force: true }).success(function() {
self.Author = self.sequelize.define('author', { firstName: Sequelize.STRING })
self.Author.sync({ force: true }).success(function() {
self.Author.sync().success(function() {
done()
})
})
......@@ -3609,6 +3810,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
this.Author.hasMany(Post)
Post.belongsTo(this.Author)
// The posts table gets dropped in the before filter.
Post.sync().on('sql', function(sql) {
if (dialect === 'postgres') {
expect(sql).to.match(/"authorId" INTEGER REFERENCES "authors" \("id"\)/)
......@@ -3638,6 +3840,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
this.Author.hasMany(Post)
Post.belongsTo(this.Author)
// The posts table gets dropped in the before filter.
Post.sync().on('sql', function(sql) {
if (dialect === 'postgres') {
expect(sql).to.match(/"authorId" INTEGER REFERENCES "authors" \("id"\)/)
......@@ -3667,6 +3870,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
this.Author.hasMany(Post)
Post.belongsTo(this.Author)
// The posts table gets dropped in the before filter.
Post.sync().success(function() {
if (dialect === 'sqlite') {
// sorry ... but sqlite is too stupid to understand whats going on ...
......
......@@ -449,7 +449,6 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
it("should update read only attributes as well (updatedAt)", function(done) {
var self = this
this.timeout = 2000
this.User.create({ username: 'John Doe' }).complete(function(err, originalUser) {
var originallyUpdatedAt = originalUser.updatedAt
......@@ -565,8 +564,6 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
})
describe('save', function() {
this.timeout(3000) // for update timestamp
it('only updates fields in passed array', function(done) {
var self = this
, userId = null
......
......@@ -9,8 +9,6 @@ chai.Assertion.includeStack = true
if (dialect.match(/^mysql|mariadb/)) {
describe('[MYSQL Specific] Connector Manager', function() {
this.timeout(10000)
it('works correctly after being idle', function(done) {
var User = this.sequelize.define('User', { username: DataTypes.STRING })
, spy = sinon.spy()
......
......@@ -182,8 +182,8 @@ if (dialect.match(/^mysql|mariadb/)) {
}, {
title: 'functions can take functions as arguments',
arguments: ['myTable', function (sequelize) {
return {
order: [[sequelize.fn('f1', sequelize.fn('f2', sequelize.col('id'))), 'DESC']]
return {
order: [[sequelize.fn('f1', sequelize.fn('f2', sequelize.col('id'))), 'DESC']]
}
}],
expectation: "SELECT * FROM `myTable` ORDER BY f1(f2(`id`)) DESC;",
......@@ -194,7 +194,7 @@ if (dialect.match(/^mysql|mariadb/)) {
arguments: ['myTable', function (sequelize) {
return {
order: [
[sequelize.fn('f1', sequelize.col('myTable.id')), 'DESC'],
[sequelize.fn('f1', sequelize.col('myTable.id')), 'DESC'],
[sequelize.fn('f2', 12, 'lalala', new Date(Date.UTC(2011, 2, 27, 10, 1, 55))), 'ASC']
]
}
......@@ -450,10 +450,10 @@ if (dialect.match(/^mysql|mariadb/)) {
showIndexQuery: [
{
arguments: ['User'],
expectation: 'SHOW INDEX FROM User'
expectation: 'SHOW INDEX FROM `User`'
}, {
arguments: ['User', { database: 'sequelize' }],
expectation: "SHOW INDEX FROM User FROM sequelize"
expectation: "SHOW INDEX FROM `User` FROM `sequelize`"
}
],
......@@ -504,7 +504,7 @@ if (dialect.match(/^mysql|mariadb/)) {
describe(suiteTitle, function() {
tests.forEach(function(test) {
var title = test.title || 'MySQL correctly returns ' + test.expectation + ' for ' + util.inspect(test.arguments)
it(title, function(done) {
it(title, function(done) {
// Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly
var context = test.context || {options: {}};
if (test.needsSequelize) {
......
......@@ -57,7 +57,7 @@ var Support = {
},
getSequelizeInstance: function(db, user, pass, options) {
options = options || {};
options = options || {}
options.dialect = options.dialect || this.getTestDialect()
return new Sequelize(db, user, pass, options)
},
......@@ -141,6 +141,7 @@ before(function(done) {
beforeEach(function(done) {
this.sequelize = sequelize
Support.clearDatabase(this.sequelize, function() {
done()
})
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!