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

Commit c55da04d by Daniel Durante

Merge branch 'master' into newindex

Conflicts:
	lib/dialects/sqlite/query-generator.js
2 parents 59e1c677 3a94a396
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
# Change these settings to your own preference
indent_style = space
indent_size = 2
# We recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
indent_size = 4
......@@ -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
docs
examples
test
README.md
.watchr.js
changelog.md
Makefile
\ No newline at end of file
......@@ -18,6 +18,7 @@ env:
- DB=mysql DIALECT=postgres
- DB=mysql DIALECT=postgres-native
- DB=mysql DIALECT=sqlite
- DB=mysql DIALECT=mariadb
language: node_js
......
......@@ -13,12 +13,14 @@ 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
mariadb:
@DIALECT=mariadb make test
sqlite:
@DIALECT=sqlite make test
mysql:
......@@ -37,6 +39,6 @@ postgresn: postgres-native
# test all the dialects \o/
all: sqlite mysql postgres postgres-native
all: sqlite mysql postgres postgres-native mariadb
.PHONY: sqlite mysql postgres pgsql postgres-native postgresn all test
# Sequelize [![Build Status](https://secure.travis-ci.org/sequelize/sequelize.png)](http://travis-ci.org/sequelize/sequelize) [![Dependency Status](https://david-dm.org/sequelize/sequelize.png)](https://david-dm.org/sequelize/sequelize) [![Dependency Status](https://david-dm.org/sequelize/sequelize.png)](https://david-dm.org/sequelize/sequelize) [![Flattr this](http://api.flattr.com/button/flattr-badge-large.png)](http://flattr.com/thing/1259407/Sequelize) #
# Sequelize [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/sequelize/sequelize/trend.png)](https://bitdeli.com/free "Bitdeli Badge") [![Build Status](https://secure.travis-ci.org/sequelize/sequelize.png)](http://travis-ci.org/sequelize/sequelize) [![Dependency Status](https://david-dm.org/sequelize/sequelize.png)](https://david-dm.org/sequelize/sequelize) [![Flattr this](http://api.flattr.com/button/flattr-badge-large.png)](http://flattr.com/thing/1259407/Sequelize) #
MySQL, PostgresSQL, and SQLite Object Relational Mapper (ORM) for [node](http://nodejs.org).
MySQL, MariaDB, PostgresSQL, and SQLite Object Relational Mapper (ORM) for [node](http://nodejs.org).
## Important Notes ##
......@@ -61,7 +61,7 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl
- Transactions
- Associations of not yet saved objects: [#864](https://github.com/sequelize/sequelize/issues/864)
- Support for update of tables without primary key
- MariaDB support
- ~~MariaDB support~~
- ~~Support for update and delete calls for whole tables without previous loading of instances~~ Implemented in [#569](https://github.com/sequelize/sequelize/pull/569) thanks to @optiltude
- Eager loading of nested associations [#388](https://github.com/sequelize/sequelize/issues/388)
- ~~Model#delete~~ (renamed to [Model.destroy()](http://sequelizejs.com/documentation#instances-destroy))
......
......@@ -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,10 @@
- [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.
- [FEATURE] Support for MariaDB. [#948](https://github.com/sequelize/sequelize/pull/948). Thanks to reedog117 and janmeier.
- [FEATURE] Filter through associations. [#991](https://github.com/sequelize/sequelize/pull/991). Thanks to snit-ram.
- [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
......
......@@ -48,6 +48,7 @@ module.exports = (function() {
HasMany.prototype.injectAttributes = function() {
var multiAssociation = this.target.associations.hasOwnProperty(this.associationAccessor)
, 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,9 +60,15 @@ module.exports = (function() {
} else {
this.foreignIdentifier = this.target.associations[this.associationAccessor].identifier
this.target.associations[this.associationAccessor].foreignIdentifier = 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
var combinedTableAttributes = {}
......@@ -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
})()
......@@ -186,9 +186,8 @@ module.exports = (function() {
self.DAO.prototype.booleanValues.push(name);
}
if (definition.hasOwnProperty('defaultValue')) {
self.DAO.prototype.defaultValues[name] = function() {
return Utils.toDefaultValue(definition.defaultValue)
}
self.DAO.prototype.defaultValues[name] = Utils._.partial(
Utils.toDefaultValue, definition.defaultValue)
}
if (definition.hasOwnProperty('validate')) {
......@@ -545,6 +544,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;
......@@ -988,7 +1020,7 @@ module.exports = (function() {
DAOFactory.prototype.dataset = function() {
if (!this.__sql) {
this.__sql = sql.setDialect(this.daoFactoryManager.sequelize.options.dialect)
this.__setSqlDialect()
}
var instance = this.__sql.define({ name: this.tableName, columns: [] })
......@@ -1001,6 +1033,11 @@ module.exports = (function() {
return instance
}
DAOFactory.prototype.__setSqlDialect = function() {
var dialect = this.daoFactoryManager.sequelize.options.dialect
this.__sql = sql.setDialect(dialect === 'mariadb' ? 'mysql' : dialect)
}
// private
var paranoidClause = function(options) {
......@@ -1089,7 +1126,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']
}
......
......@@ -131,11 +131,11 @@ module.exports = (function() {
var definition = self.daoFactory.rawAttributes[attrName]
, isHstore = !!definition.type && !!definition.type.type && definition.type.type === DataTypes.HSTORE.type
, isEnum = definition.type && (definition.type.toString() === DataTypes.ENUM.toString())
, isMySQL = self.daoFactory.daoFactoryManager.sequelize.options.dialect === "mysql"
, isMySQL = ['mysql', 'mariadb'].indexOf(self.daoFactory.daoFactoryManager.sequelize.options.dialect) !== -1
, 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
......@@ -422,7 +422,7 @@ module.exports = (function() {
}
DAO.prototype.addAttribute = function(attribute, value) {
if (this.booleanValues.length && this.booleanValues.indexOf(attribute) !== -1 && value !== undefined) { // transform integer 0,1 into boolean
if (this.booleanValues.length && this.booleanValues.indexOf(attribute) !== -1 && value != null) { // transform integer 0,1 into boolean
value = !!value
}
......
......@@ -98,23 +98,6 @@ module.exports = (function() {
},
/*
Returns a query for selecting elements in the table <tableName>.
Options:
- attributes -> An array of attributes (e.g. ['name', 'birthday']). Default: *
- where -> A hash with conditions (e.g. {name: 'foo'})
OR an ID as integer
OR a string with conditions (e.g. 'name="foo"').
If you use a string, you have to escape it on your own.
- order -> e.g. 'id DESC'
- group
- limit -> The maximum count you want to get.
- offset -> An offset value to start from. Only useable with limit!
*/
selectQuery: function(tableName, options) {
throwMethodUndefined('selectQuery')
},
/*
Returns an insert into command. Parameters: table name + hash of attribute-value-pairs.
*/
insertQuery: function(tableName, attrValueHash) {
......@@ -231,21 +214,6 @@ module.exports = (function() {
},
/*
Takes something and transforms it into values of a where condition.
*/
getWhereConditions: function(smth) {
throwMethodUndefined('getWhereConditions')
},
/*
Takes a hash and transforms it into a mysql where condition: {key: value, key2: value2} ==> key=value AND key2=value2
The values are transformed by the relevant datatype.
*/
hashToWhereConditions: function(hash) {
throwMethodUndefined('hashToWhereConditions')
},
/*
This method transforms an array of attribute hashes into equivalent
sql attribute definition.
*/
......@@ -261,20 +229,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
Strings: should proxy to quoteIdentifiers
Arrays: First argument should be qouted, second argument should be append without quoting
......@@ -293,7 +247,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 +317,372 @@ 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)
}
},
/**
* 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')
},
/*
Returns a query for selecting elements in the table <tableName>.
Options:
- attributes -> An array of attributes (e.g. ['name', 'birthday']). Default: *
- where -> A hash with conditions (e.g. {name: 'foo'})
OR an ID as integer
OR a string with conditions (e.g. 'name="foo"').
If you use a string, you have to escape it on your own.
- order -> e.g. 'id DESC'
- group
- limit -> The maximum count you want to get.
- offset -> An offset value to start from. Only useable with limit!
*/
selectQuery: function(tableName, options, factory) {
var table = null,
joinQuery = ""
options = options || {}
options.table = table = Array.isArray(tableName) ? tableName.map(function(t) { return this.quoteIdentifiers(t) }.bind(this)).join(", ") : this.quoteIdentifiers(tableName)
options.attributes = options.attributes && options.attributes.map(function(attr){
if(Array.isArray(attr) && attr.length == 2) {
return [attr[0], this.quoteIdentifier(attr[1])].join(' as ')
} else {
return attr.indexOf(Utils.TICK_CHAR) < 0 && attr.indexOf('"') < 0 ? this.quoteIdentifiers(attr) : attr
}
}.bind(this)).join(", ")
options.attributes = options.attributes || '*'
if (options.include) {
var optAttributes = options.attributes === '*' ? [options.table + '.*'] : [options.attributes]
options.include.forEach(function(include) {
var attributes = include.attributes.map(function(attr) {
return this.quoteIdentifier(include.as) + "." + this.quoteIdentifier(attr) + " AS " + this.quoteIdentifier(include.as + "." + attr)
}.bind(this))
optAttributes = optAttributes.concat(attributes)
var table = include.daoFactory.tableName
, as = include.as
if (!include.association.connectorDAO) {
var primaryKeysLeft = ((include.association.associationType === 'BelongsTo') ? Object.keys(include.association.target.primaryKeys) : Object.keys(include.association.source.primaryKeys))
, tableLeft = ((include.association.associationType === 'BelongsTo') ? include.as : tableName)
, 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)
, tableSource = tableName
, identSource = include.association.identifier
, attrSource = ((!include.association.source.hasPrimaryKeys || primaryKeysSource.length !== 1) ? 'id' : primaryKeysSource[0])
var primaryKeysTarget = Object.keys(include.association.target.primaryKeys)
, tableTarget = include.as
, identTarget = include.association.foreignIdentifier
, attrTarget = ((!include.association.target.hasPrimaryKeys || primaryKeysTarget.length !== 1) ? 'id' : primaryKeysTarget[0])
var tableJunction = include.association.connectorDAO.tableName
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(tableJunction) + " ON " + this.quoteIdentifier(tableSource) + "." + this.quoteIdentifier(attrSource) + " = " + this.quoteIdentifier(tableJunction) + "." + this.quoteIdentifier(identSource)
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableTarget) + "." + this.quoteIdentifier(attrTarget) + " = " + this.quoteIdentifier(tableJunction) + "." + this.quoteIdentifier(identTarget)
}
}.bind(this))
options.attributes = optAttributes.join(', ')
}
var conditionalJoins = this.getConditionalJoins(options, factory),
query;
if (conditionalJoins) {
query = "SELECT " + options.attributes + " FROM ( "
+ "SELECT " + options.table + ".* FROM " + options.table + this.getConditionalJoins(options, factory)
} else {
query = "SELECT " + options.attributes + " FROM " + options.table
query += joinQuery
}
if (options.hasOwnProperty('where')) {
options.where = this.getWhereConditions(options.where, tableName, factory)
query += " WHERE " + options.where
}
if (options.group) {
options.group = Array.isArray(options.group) ? options.group.map(function (t) { return this.quote(t) }.bind(this)).join(', ') : options.group
query += " GROUP BY " + options.group
}
if (options.order) {
options.order = Array.isArray(options.order) ? options.order.map(function (t) { return this.quote(t) }.bind(this)).join(', ') : options.order
query += " ORDER BY " + options.order
}
query = this.addLimitAndOffset(options, query)
if (conditionalJoins) {
query += ") AS " + options.table
query += joinQuery
}
query += ";"
return query
},
addLimitAndOffset: function(options, query){
if (options.offset && !options.limit) {
query += " LIMIT " + options.offset + ", " + 10000000000000;
} else if (options.limit && !(options.include && (options.limit === 1))) {
if (options.offset) {
query += " LIMIT " + options.offset + ", " + options.limit
} else {
query += " LIMIT " + options.limit
}
}
return query;
},
/*
Takes something and transforms it into values of a where condition.
*/
getWhereConditions: function(smth, tableName, factory) {
var result = null
, where = {}
if (Utils.isHash(smth)) {
smth = Utils.prependTableNameToHash(tableName, smth)
result = this.hashToWhereConditions(smth, factory)
} else if (typeof smth === 'number') {
var primaryKeys = !!factory ? Object.keys(factory.primaryKeys) : []
if (primaryKeys.length > 0) {
// Since we're just a number, assume only the first key
primaryKeys = primaryKeys[0]
} else {
primaryKeys = 'id'
}
where[primaryKeys] = smth
smth = Utils.prependTableNameToHash(tableName, where)
result = this.hashToWhereConditions(smth)
} else if (typeof smth === "string") {
result = smth
} else if (Array.isArray(smth)) {
result = Utils.format(smth, this.dialect)
}
return result ? result : '1=1'
},
findAssociation: function(attribute, dao){
var associationToReturn;
Object.keys(dao.associations).forEach(function(key){
if(!dao.associations[key]) return;
var association = dao.associations[key]
, associationName
if (association.associationType === 'BelongsTo') {
associationName = Utils.singularize(association.associationAccessor[0].toLowerCase() + association.associationAccessor.slice(1));
} else {
associationName = association.accessors.get.replace('get', '')
associationName = associationName[0].toLowerCase() + associationName.slice(1);
}
if(associationName === attribute){
associationToReturn = association;
}
});
return associationToReturn;
},
getAssociationFilterDAO: function(filterStr, dao){
var associationParts = filterStr.split('.')
, self = this
associationParts.pop()
associationParts.forEach(function (attribute) {
dao = self.findAssociation(attribute, dao).target;
});
return dao;
},
isAssociationFilter: function(filterStr, dao){
if(!dao){
return false;
}
var pattern = /^[a-z][a-zA-Z0-9]+(\.[a-z][a-zA-Z0-9]+)+$/;
if (!pattern.test(filterStr)) return false;
var associationParts = filterStr.split('.')
, attributePart = associationParts.pop()
, self = this
return associationParts.every(function (attribute) {
var association = self.findAssociation(attribute, dao);
if (!association) return false;
dao = association.target;
return !!dao;
}) && dao.rawAttributes.hasOwnProperty(attributePart);
},
getAssociationFilterColumn: function(filterStr, dao){
var associationParts = filterStr.split('.')
, attributePart = associationParts.pop()
, self = this
associationParts.forEach(function (attribute) {
dao = self.findAssociation(attribute, dao).target;
})
return dao.tableName + '.' + attributePart;
},
getConditionalJoins: function(options, originalDao){
var joins = ''
, self = this
, joinedTables = {}
if (Utils.isHash(options.where)) {
Object.keys(options.where).forEach(function(filterStr){
var associationParts = filterStr.split('.')
, attributePart = associationParts.pop()
, dao = originalDao
if (self.isAssociationFilter(filterStr, dao)) {
associationParts.forEach(function (attribute) {
var association = self.findAssociation(attribute, dao);
if(!joinedTables[association.target.tableName]){
joinedTables[association.target.tableName] = true;
if(association.associationType === 'BelongsTo'){
joins += ' LEFT JOIN ' + self.quoteIdentifiers(association.target.tableName)
joins += ' ON ' + self.quoteIdentifiers(association.source.tableName + '.' + association.identifier)
joins += ' = ' + self.quoteIdentifiers(association.target.tableName + '.' + association.target.autoIncrementField)
} else if (association.connectorDAO){
joinedTables[association.connectorDAO.tableName] = true;
joins += ' LEFT JOIN ' + self.quoteIdentifiers(association.connectorDAO.tableName)
joins += ' ON ' + self.quoteIdentifiers(association.source.tableName + '.' + association.source.autoIncrementField)
joins += ' = ' + self.quoteIdentifiers(association.connectorDAO.tableName + '.' + association.identifier)
joins += ' LEFT JOIN ' + self.quoteIdentifiers(association.target.tableName)
joins += ' ON ' + self.quoteIdentifiers(association.connectorDAO.tableName + '.' + association.foreignIdentifier)
joins += ' = ' + self.quoteIdentifiers(association.target.tableName + '.' + association.target.autoIncrementField)
} else {
joins += ' LEFT JOIN ' + self.quoteIdentifiers(association.target.tableName)
joins += ' ON ' + self.quoteIdentifiers(association.source.tableName + '.' + association.source.autoIncrementField)
joins += ' = ' + self.quoteIdentifiers(association.target.tableName + '.' + association.identifier)
}
}
dao = association.target;
});
}
});
}
return joins;
},
arrayValue: function(value, key, _key){
var _value = null;
if (value.length === 0) { value = [null] }
_value = "(" + value.map(function(v) { return this.escape(v) }.bind(this)).join(',') + ")"
return [_key, _value].join(" IN ")
},
/*
Takes a hash and transforms it into a mysql where condition: {key: value, key2: value2} ==> key=value AND key2=value2
The values are transformed by the relevant datatype.
*/
hashToWhereConditions: function(hash, dao) {
var result = []
for (var key in hash) {
var value = hash[key]
if(this.isAssociationFilter(key, dao)){
key = this.getAssociationFilterColumn(key, dao);
}
//handle qualified key names
var _key = this.quoteIdentifiers(key)
, _value = null
if (Array.isArray(value)) {
result.push(this.arrayValue(value, key, _key, dao))
} else if ((value) && (typeof value == 'object') && !(value instanceof Date)) {
if (!!value.join) {
//using as sentinel for join column => value
_value = this.quoteIdentifiers(value.join)
result.push([_key, _value].join("="))
} else {
for (var logic in value) {
var logicResult = Utils.getWhereLogic(logic)
if (logic === "IN" || logic === "NOT IN") {
var values = Array.isArray(where[i][ii]) ? where[i][ii] : [where[i][ii]]
_where[_where.length] = i + ' ' + logic + ' (' + values.map(function(){ return '?' }).join(',') + ')'
_whereArgs = _whereArgs.concat(values)
}
else if (logicResult === "BETWEEN" || logicResult === "NOT BETWEEN") {
_value = this.escape(value[logic][0])
var _value2 = this.escape(value[logic][1])
result.push(' (' + _key + ' ' + logicResult + ' ' + _value + ' AND ' + _value2 + ') ')
} else {
_value = this.escape(value[logic])
result.push([_key, _value].join(' ' + logicResult + ' '))
}
}
}
} else {
if (typeof value === 'boolean') {
_value = this.booleanValue(value);
} else {
_value = this.escape(value)
}
result.push((_value == 'NULL') ? _key + " IS NULL" : [_key, _value].join("="))
}
}
return result.join(" AND ")
},
booleanValue: function(value){
return value;
}
}
var throwMethodUndefined = function(methodName) {
......
var mariadb
, Pooling = require('generic-pool')
, Query = require("./query")
, Utils = require("../../utils")
, without = function(arr, elem) { return arr.filter(function(e) { return e != elem }) }
module.exports = (function() {
var ConnectorManager = function(sequelize, config) {
try {
if (config.dialectModulePath) {
mariadb = require(config.dialectModulePath)
} else {
mariadb = require('mariasql')
}
} catch (err) {
console.log('You need to install mariasql package manually')
}
this.sequelize = sequelize
this.client = null
this.config = config || {}
this.config.port = this.config.port || 3306
this.disconnectTimeoutId = null
this.queue = []
this.activeQueue = []
this.maxConcurrentQueries = (this.config.maxConcurrentQueries || 50)
this.poolCfg = Utils._.defaults(this.config.pool, {
maxConnections: 10,
minConnections: 0,
maxIdleTime: 1000,
handleDisconnects: false,
validate: function(client) {
return client && client.connected
}
})
this.pendingQueries = 0
this.useReplicaton = !!config.replication
this.useQueue = config.queue !== undefined ? config.queue : true
var self = this
if (this.useReplicaton) {
var reads = 0
, writes = 0
// Init configs with options from config if not present
for (var i in config.replication.read) {
config.replication.read[i] = Utils._.defaults(config.replication.read[i], {
host: this.config.host,
port: this.config.port,
username: this.config.username,
password: this.config.password,
database: this.config.database
})
}
config.replication.write = Utils._.defaults(config.replication.write, {
host: this.config.host,
port: this.config.port,
username: this.config.username,
password: this.config.password,
database: this.config.database
})
// I'll make my own pool, with blackjack and hookers!
this.pool = {
release: function (client) {
if (client.queryType == 'read') {
return this.read.release(client)
} else {
return this.write.release(client)
}
},
acquire: function (callback, priority, queryType) {
if (queryType == 'SELECT') {
this.read.acquire(callback, priority)
} else {
this.write.acquire(callback, priority)
}
},
drain: function () {
this.read.drain()
this.write.drain()
},
read: Pooling.Pool({
name: 'sequelize-read',
create: function (done) {
if (reads >= self.config.replication.read.length) {
reads = 0
}
var config = self.config.replication.read[reads++]
connect.call(self, function (err, connection) {
connection.queryType = 'read'
done(null, connection)
}, config)
},
destroy: function(client) {
disconnect.call(self, client)
},
validate: self.poolCfg.validate,
max: self.poolCfg.maxConnections,
min: self.poolCfg.minConnections,
idleTimeoutMillis: self.poolCfg.maxIdleTime
}),
write: Pooling.Pool({
name: 'sequelize-write',
create: function (done) {
connect.call(self, function (err, connection) {
connection.queryType = 'write'
done(null, connection)
}, self.config.replication.write)
},
destroy: function(client) {
disconnect.call(self, client)
},
validate: self.poolCfg.validate,
max: self.poolCfg.maxConnections,
min: self.poolCfg.minConnections,
idleTimeoutMillis: self.poolCfg.maxIdleTime
})
};
} else if (this.poolCfg) {
//the user has requested pooling, so create our connection pool
this.pool = Pooling.Pool({
name: 'sequelize-mariadb',
create: function (done) {
connect.call(self, done)
},
destroy: function(client) {
disconnect.call(self, client)
},
max: self.poolCfg.maxConnections,
min: self.poolCfg.minConnections,
validate: self.poolCfg.validate,
idleTimeoutMillis: self.poolCfg.maxIdleTime
})
}
process.on('exit', function () {
//be nice & close our connections on exit
if (self.pool) {
self.pool.drain()
} else if (self.client) {
disconnect(self.client)
}
return
})
}
Utils._.extend(ConnectorManager.prototype, require("../connector-manager").prototype)
ConnectorManager.prototype.query = function(sql, callee, options) {
if (!this.isConnected && !this.pool) {
this.connect()
}
if (this.useQueue) {
var queueItem = {
query: new Query(this.client, this.sequelize, callee, options || {}),
client: this.client,
sql: sql
}
enqueue.call(this, queueItem, options)
return queueItem.query
}
var self = this
, query = new Query(this.client, this.sequelize, callee, options || {})
this.pendingQueries++
query.done(function() {
self.pendingQueries--
if (self.pool) {
self.pool.release(query.client)
} else {
if (self.pendingQueries === 0) {
setTimeout(function() {
self.pendingQueries === 0 && self.disconnect.call(self)
}, 100)
}
}
})
if (!this.pool) {
query.run(sql)
} else {
this.pool.acquire(function(err, client) {
if (err) {
return query.emit('error', err)
}
query.client = client
query.run(sql)
return
}, undefined, options.type)
}
return query
}
ConnectorManager.prototype.connect = function() {
var self = this
// in case database is slow to connect, prevent orphaning the client
if (this.isConnecting || this.pool) {
return
}
connect.call(self, function(err, client) {
self.client = client
return
})
return
}
ConnectorManager.prototype.disconnect = function() {
if (this.client) {
disconnect.call(this, this.client)
}
return
}
// private
var disconnect = function(client) {
var self = this
if (!this.useQueue) {
this.client = null
client.end()
return
}
var intervalObj = null
var cleanup = function () {
// make sure to let queued items be finish before calling end
if (self && self.hasQueuedItems) {
return
}
client.end()
if (self && self.client) {
self.client = null
}
clearInterval(intervalObj)
}
intervalObj = setInterval(cleanup, 10)
cleanup()
}
var connect = function(done, config) {
config = config || this.config
var self = this
, client
this.isConnecting = true
var connectionConfig = {
host: config.host,
port: config.port,
user: config.username,
password: config.password,
db: config.database,
metadata: true
}
if (config.dialectOptions) {
Object.keys(config.dialectOptions).forEach(function (key) {
connectionConfig[key] = config.dialectOptions[key];
})
}
client = new mariadb()
client.connect(connectionConfig)
client
.on('error', function (err) {
self.isConnecting = false
done(err)
})
.on('connect', function () {
client.query("SET time_zone = '+0:00'").on('result', function (res) {
res.on('end', function () {
client.setMaxListeners(self.maxConcurrentQueries)
self.isConnecting = false
if (config.pool.handleDisconnects) {
handleDisconnect(self.pool, client)
}
done(null, client)
})
})
})
.on('close', function() {
disconnect.call(self, client)
})
}
var handleDisconnect = function(pool, client) {
client.on('error', function(err) {
if (err.code !== 'PROTOCOL_CONNECTION_LOST') {
throw err
}
pool.destroy(client)
})
}
var enqueue = function(queueItem, options) {
options = options || {}
if (this.activeQueue.length < this.maxConcurrentQueries) {
this.activeQueue.push(queueItem)
if (this.pool) {
var self = this
this.pool.acquire(function(err, client) {
if (err) {
queueItem.query.emit('error', err)
return
}
//we set the client here, asynchronously, when getting a pooled connection
//allowing the ConnectorManager.query method to remain synchronous
queueItem.query.client = client
queueItem.client = client
execQueueItem.call(self, queueItem)
return
}, undefined, options.type)
} else {
execQueueItem.call(this, queueItem)
}
} else {
this.queue.push(queueItem)
}
}
var dequeue = function(queueItem) {
//return the item's connection to the pool
if (this.pool) {
this.pool.release(queueItem.client)
}
this.activeQueue = without(this.activeQueue, queueItem)
}
var transferQueuedItems = function(count) {
for(var i = 0; i < count; i++) {
var queueItem = this.queue.shift()
if (queueItem) {
enqueue.call(this, queueItem)
}
}
}
var afterQuery = function(queueItem) {
dequeue.call(this, queueItem)
transferQueuedItems.call(this, this.maxConcurrentQueries - this.activeQueue.length)
disconnectIfNoConnections.call(this)
}
var execQueueItem = function(queueItem) {
var self = this
queueItem.query
.success(function(){ afterQuery.call(self, queueItem) })
.error(function(){ afterQuery.call(self, queueItem) })
queueItem.query.run(queueItem.sql, queueItem.client)
}
ConnectorManager.prototype.__defineGetter__('hasQueuedItems', function() {
return (this.queue.length > 0) || (this.activeQueue.length > 0) || (this.client && this.client._queue && (this.client._queue.length > 0))
})
// legacy
ConnectorManager.prototype.__defineGetter__('hasNoConnections', function() {
return !this.hasQueuedItems
})
ConnectorManager.prototype.__defineGetter__('isConnected', function() {
return this.client != null
})
var disconnectIfNoConnections = function() {
var self = this
this.disconnectTimeoutId && clearTimeout(this.disconnectTimeoutId)
this.disconnectTimeoutId = setTimeout(function() {
self.isConnected && !self.hasQueuedItems && self.disconnect()
}, 100)
}
return ConnectorManager
})()
var Utils = require("../../utils")
module.exports = (function() {
var QueryGenerator = {
dialect: 'mariadb'
}
// "MariaDB is a drop-in replacement for MySQL." - so thats exactly what we do, drop in the mysql query generator
return Utils._.extend(Utils._.clone(require("../mysql/query-generator")), QueryGenerator)
})()
var Utils = require("../../utils")
, AbstractQuery = require('../abstract/query')
module.exports = (function() {
var Query = function(client, sequelize, callee, options) {
this.client = client
this.callee = callee
this.sequelize = sequelize
this.options = Utils._.extend({
logging: console.log,
plain: false,
raw: false
}, options || {})
this.checkLoggingOption()
}
Utils.inherit(Query, AbstractQuery)
Query.prototype.run = function(sql) {
this.sql = sql
if (this.options.logging !== false) {
this.options.logging('Executing: ' + this.sql)
}
var resultSet = [],
errorDetected = false,
alreadyEnded = false, // This is needed because CALL queries emit 'end' twice...
self = this
this.client.query(this.sql)
.on('result', function(results) {
results
.on('row', function(row, metadata) {
for (var prop in row) {
if (row.hasOwnProperty(prop)) {
if (row[prop] === null) {
continue
}
type = metadata.types[prop]
switch (type) {
case "TINYINT":
case "SMALLINT":
case "INTEGER":
case "MEDIUMINT":
case "BIGINT":
case "YEAR":
row[prop] = parseInt(row[prop], 10)
break
case "DECIMAL":
case "FLOAT":
case "DOUBLE":
row[prop] = parseFloat(row[prop])
break
case "DATE":
case "TIMESTAMP":
case "DATETIME":
row[prop] = new Date(row[prop] + 'Z')
break
case "BIT":
case "BLOB":
case "TINYBLOB":
case "MEDIUMBLOB":
case "LONGBLOB":
if (metadata.charsetNrs[prop] === 63) { // binary
row[prop] = new Buffer(row[prop])
}
break
case "TIME":
case "CHAR":
case "VARCHAR":
case "SET":
case "ENUM":
case "GEOMETRY":
case "NULL":
break
default:
// blank
}
}
}
resultSet.push(row)
})
.on('error', function(err) {
errorDetected = true
self.emit('sql', self.sql)
self.emit('error', err, self.callee)
})
.on('end', function(info) {
if (alreadyEnded || errorDetected) {
return
}
alreadyEnded = true
self.emit('sql', self.sql)
// we need to figure out whether to send the result set
// or info depending upon the type of query
if (/^call/.test(self.sql.toLowerCase())) {
self.emit('success', resultSet)
} else if( /^show/.test(self.sql.toLowerCase()) ||
/^select/.test(self.sql.toLowerCase()) ||
/^describe/.test(self.sql.toLowerCase())) {
self.emit('success', self.formatResults(resultSet))
} else {
self.emit('success', self.formatResults(info))
}
})
})
.on('error', function(err) {
if (errorDetected) {
return
}
errorDetected = true
self.emit('sql', self.sql)
self.emit('error', err, self.callee)
})
.setMaxListeners(100)
return this
}
return Query
})()
var Utils = require("../../utils")
, DataTypes = require("../../data-types")
, SqlString = require("../../sql-string")
, util = require("util")
module.exports = (function() {
......@@ -182,100 +181,6 @@ module.exports = (function() {
return Utils._.template(query)({ tableName: tableName, attributes: attrString.join(', ') })
},
selectQuery: function(tableName, options, factory) {
var table = null,
joinQuery = ""
options = options || {}
options.table = table = Array.isArray(tableName) ? tableName.map(function(t) { return this.quoteIdentifier(t)}.bind(this)).join(", ") : this.quoteIdentifier(tableName)
options.attributes = options.attributes && options.attributes.map(function(attr){
if(Array.isArray(attr) && attr.length == 2) {
return [attr[0], this.quoteIdentifier(attr[1])].join(' as ')
} else {
return attr.indexOf(Utils.TICK_CHAR) < 0 ? this.quoteIdentifiers(attr) : attr
}
}.bind(this)).join(", ")
options.attributes = options.attributes || '*'
if (options.include) {
var optAttributes = options.attributes === '*' ? [options.table + '.*'] : [options.attributes]
options.include.forEach(function(include) {
var attributes = include.attributes.map(function(attr) {
return this.quoteIdentifier(include.as) + "." + this.quoteIdentifier(attr) + " AS " + this.quoteIdentifier(include.as + "." + attr)
}.bind(this))
optAttributes = optAttributes.concat(attributes)
var table = include.daoFactory.tableName
, as = include.as
if (!include.association.connectorDAO) {
var primaryKeysLeft = ((include.association.associationType === 'BelongsTo') ? Object.keys(include.association.target.primaryKeys) : Object.keys(include.association.source.primaryKeys))
, tableLeft = ((include.association.associationType === 'BelongsTo') ? include.as : tableName)
, 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)
, tableSource = tableName
, identSource = include.association.identifier
, attrSource = ((!include.association.source.hasPrimaryKeys || primaryKeysSource.length !== 1) ? 'id' : primaryKeysSource[0])
var primaryKeysTarget = Object.keys(include.association.target.primaryKeys)
, tableTarget = include.as
, identTarget = include.association.foreignIdentifier
, attrTarget = ((!include.association.target.hasPrimaryKeys || primaryKeysTarget.length !== 1) ? 'id' : primaryKeysTarget[0])
var tableJunction = include.association.connectorDAO.tableName
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(tableJunction) + " ON " + this.quoteIdentifier(tableSource) + "." + this.quoteIdentifier(attrSource) + " = " + this.quoteIdentifier(tableJunction) + "." + this.quoteIdentifier(identSource)
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableTarget) + "." + this.quoteIdentifier(attrTarget) + " = " + this.quoteIdentifier(tableJunction) + "." + this.quoteIdentifier(identTarget)
}
}.bind(this))
options.attributes = optAttributes.join(', ')
}
var query = "SELECT " + options.attributes + " FROM " + options.table
query += joinQuery
if (options.hasOwnProperty('where')) {
options.where = this.getWhereConditions(options.where, tableName, factory)
query += " WHERE " + options.where
}
if (options.group) {
options.group = Array.isArray(options.group) ? options.group.map(function (t) { return this.quote(t) }.bind(this)).join(', ') : options.group
query += " GROUP BY " + options.group
}
if (options.order) {
options.order = Array.isArray(options.order) ? options.order.map(function (t) { return this.quote(t) }.bind(this)).join(', ') : options.order
query += " ORDER BY " + options.order
}
if (options.offset && !options.limit) {
/*
* If no limit is defined, our best bet is to use the max number of rows in a table. From the MySQL docs:
* There is a limit of 2^32 (~4.295E+09) rows in a MyISAM table. If you build MySQL with the --with-big-tables option,
* the row limitation is increased to (2^32)^2 (1.844E+19) rows.
*/
query += " LIMIT " + options.offset + ", " + 18440000000000000000;
} else if (options.limit && !(options.include && (options.limit === 1))) {
if (options.offset) {
query += " LIMIT " + options.offset + ", " + options.limit
} else {
query += " LIMIT " + options.limit
}
}
query += ";"
return query
},
insertQuery: function(tableName, attrValueHash) {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull)
......@@ -423,10 +328,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 + '`' : ''
})
},
......@@ -441,83 +346,6 @@ module.exports = (function() {
return Utils._.template(sql)({ tableName: tableName, indexName: indexName })
},
getWhereConditions: function(smth, tableName, factory) {
var result = null
, where = {}
if (Utils.isHash(smth)) {
smth = Utils.prependTableNameToHash(tableName, smth)
result = this.hashToWhereConditions(smth)
} else if (typeof smth === 'number') {
var primaryKeys = !!factory ? Object.keys(factory.primaryKeys) : []
if (primaryKeys.length > 0) {
// Since we're just a number, assume only the first key
primaryKeys = primaryKeys[0]
} else {
primaryKeys = 'id'
}
where[primaryKeys] = smth
smth = Utils.prependTableNameToHash(tableName, where)
result = this.hashToWhereConditions(smth)
} else if (typeof smth === "string") {
result = smth
} else if (Array.isArray(smth)) {
result = Utils.format(smth, this.dialect)
}
return result ? result : '1=1'
},
hashToWhereConditions: function(hash) {
var result = []
for (var key in hash) {
var value = hash[key]
//handle qualified key names
var _key = this.quoteIdentifiers(key)
, _value = null
if (Array.isArray(value)) {
// is value an array?
if (value.length === 0) { value = [null] }
_value = "(" + value.map(function(v) { return this.escape(v) }.bind(this)).join(',') + ")"
result.push([_key, _value].join(" IN "))
} else if ((value) && (typeof value == 'object') && !(value instanceof Date)) {
if (!!value.join) {
//using as sentinel for join column => value
_value = this.quoteIdentifiers(value.join)
result.push([_key, _value].join("="))
} else {
for (var logic in value) {
var logicResult = Utils.getWhereLogic(logic)
if (logic === "IN" || logic === "NOT IN") {
var values = Array.isArray(where[i][ii]) ? where[i][ii] : [where[i][ii]]
_where[_where.length] = i + ' ' + logic + ' (' + values.map(function(){ return '?' }).join(',') + ')'
_whereArgs = _whereArgs.concat(values)
}
else if (logicResult === "BETWEEN" || logicResult === "NOT BETWEEN") {
_value = this.escape(value[logic][0])
var _value2 = this.escape(value[logic][1])
result.push(' (' + _key + ' ' + logicResult + ' ' + _value + ' AND ' + _value2 + ') ')
} else {
_value = this.escape(value[logic])
result.push([_key, _value].join(' ' + logicResult + ' '))
}
}
}
} else {
_value = this.escape(value)
result.push((_value == 'NULL') ? _key + " IS NULL" : [_key, _value].join("="))
}
}
return result.join(" AND ")
},
attributesToSQL: function(attributes) {
var result = {}
......@@ -548,7 +376,7 @@ module.exports = (function() {
}
// Blobs/texts cannot have a defaultValue
if (dataType.type !== "TEXT" && dataType.type._binary !== true && (dataType.defaultValue !== undefined) && (dataType.defaultValue != DataTypes.NOW)) {
if (dataType.type !== "TEXT" && dataType.type._binary !== true && Utils.defaultValueSchemable(dataType.defaultValue)) {
template += " DEFAULT " + this.escape(dataType.defaultValue)
}
......@@ -608,14 +436,17 @@ 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, {})
addLimitAndOffset: function(options, query){
if (options.offset && !options.limit) {
query += " LIMIT " + options.offset + ", " + 18440000000000000000;
} else if (options.limit && !(options.include && (options.limit === 1))) {
if (options.offset) {
query += " LIMIT " + options.offset + ", " + options.limit
} else {
query += " LIMIT " + options.limit
}
}
return query;
},
quoteIdentifier: function(identifier, force) {
......@@ -624,6 +455,28 @@ 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 "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
......
......@@ -164,6 +164,27 @@ module.exports = (function() {
attributes: attrString.join(', ') })
},
arrayValue: function(value, key, _key, factory){
if (value.length === 0) { value = [null] }
var col = null, coltype = null
// Special conditions for searching within an array column type
var _realKey = key.split('.').pop()
if (!!factory && !!factory.rawAttributes[_realKey]) {
col = factory.rawAttributes[_realKey]
coltype = col.type
if(coltype && !(typeof coltype == 'string')) {
coltype = coltype.toString();
}
}
if ( col && ((!!coltype && coltype.match(/\[\]$/) !== null) || (col.toString().match(/\[\]$/) !== null))) {
_value = 'ARRAY[' + value.map(this.escape).join(',') + ']::' + (!!col.type ? col.type : col.toString())
return [_key, _value].join(" && ")
} else {
_value = "(" + value.map(this.escape).join(',') + ")"
return [_key, _value].join(" IN ")
}
},
removeColumnQuery: function(tableName, attributeName) {
var query = "ALTER TABLE <%= tableName %> DROP COLUMN <%= attributeName %>;"
return Utils._.template(query)({
......@@ -197,7 +218,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()
......@@ -250,100 +271,6 @@ module.exports = (function() {
})
},
selectQuery: function(tableName, options, factory) {
var query = "SELECT <%= attributes %> FROM <%= table %>",
table = null
options = options || {}
options.table = table = Array.isArray(tableName) ? tableName.map(function(t) { return this.quoteIdentifiers(t) }.bind(this)).join(", ") : this.quoteIdentifiers(tableName)
options.attributes = options.attributes && options.attributes.map(function(attr) {
if (Array.isArray(attr) && attr.length === 2) {
return [attr[0], this.quoteIdentifier(attr[1])].join(' as ')
} else {
return attr.indexOf('"') < 0 ? this.quoteIdentifiers(attr) : attr
}
}.bind(this)).join(", ")
options.attributes = options.attributes || '*'
if (options.include) {
var optAttributes = options.attributes === '*' ? [options.table + '.*'] : [options.attributes]
options.include.forEach(function(include) {
var attributes = include.attributes.map(function(attr) {
return this.quoteIdentifier(include.as) + "." + this.quoteIdentifier(attr) + " AS " + this.quoteIdentifier(include.as + "." + attr, true)
}.bind(this))
optAttributes = optAttributes.concat(attributes)
var joinQuery = ' LEFT OUTER JOIN <%= table %> AS <%= as %> ON <%= tableLeft %>.<%= attrLeft %> = <%= tableRight %>.<%= attrRight %>'
if (!include.association.connectorDAO) {
var primaryKeysLeft = ((include.association.associationType === 'BelongsTo') ? Object.keys(include.association.target.primaryKeys) : Object.keys(include.association.source.primaryKeys))
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),
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)
})
} else {
var primaryKeysSource = Object.keys(include.association.source.primaryKeys)
, primaryKeysTarget = Object.keys(include.association.target.primaryKeys)
query += Utils._.template(joinQuery)({
table: this.quoteIdentifiers(include.association.connectorDAO.tableName),
as: this.quoteIdentifier(include.association.connectorDAO.tableName),
tableLeft: this.quoteIdentifiers(tableName),
attrLeft: this.quoteIdentifier(((!include.association.source.hasPrimaryKeys || primaryKeysSource.length !== 1) ? 'id' : primaryKeysSource[0])),
tableRight: this.quoteIdentifiers(include.association.connectorDAO.tableName),
attrRight: this.quoteIdentifier(include.association.identifier)
})
query += Utils._.template(joinQuery)({
table: this.quoteIdentifiers(include.daoFactory.tableName),
as: this.quoteIdentifier(include.as),
tableLeft: this.quoteIdentifiers(include.as),
attrLeft: this.quoteIdentifier(((!include.association.target.hasPrimaryKeys || primaryKeysTarget.length !== 1) ? 'id' : primaryKeysTarget[0])),
tableRight: this.quoteIdentifiers(include.association.connectorDAO.tableName),
attrRight: this.quoteIdentifier(include.association.foreignIdentifier)
})
}
}.bind(this))
options.attributes = optAttributes.join(', ')
}
if(options.hasOwnProperty('where')) {
options.where = this.getWhereConditions(options.where, tableName, factory)
query += " WHERE <%= where %>"
}
if(options.group) {
options.group = Array.isArray(options.group) ? options.group.map(function (t) { return this.quote(t) }.bind(this)).join(', ') : options.group
query += " GROUP BY <%= group %>"
}
if(options.order) {
options.order = Array.isArray(options.order) ? options.order.map(function (t) { return this.quote(t) }.bind(this)).join(', ') : options.order
query += " ORDER BY <%= order %>"
}
if (!(options.include && (options.limit === 1))) {
if (options.limit) {
query += " LIMIT <%= limit %>"
}
if (options.offset) {
query += " OFFSET <%= offset %>"
}
}
query += ";"
return Utils._.template(query)(options)
},
insertQuery: function(tableName, attrValueHash, attributes) {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull)
......@@ -542,96 +469,18 @@ module.exports = (function() {
})
},
getWhereConditions: function(smth, tableName, factory) {
var result = null
, where = {}
if (Utils.isHash(smth)) {
smth = Utils.prependTableNameToHash(tableName, smth)
result = this.hashToWhereConditions(smth, factory)
} else if (typeof smth === 'number') {
var primaryKeys = !!factory ? Object.keys(factory.primaryKeys) : []
if (primaryKeys.length > 0) {
// Since we're just a number, assume only the first key
primaryKeys = primaryKeys[0]
} else {
primaryKeys = 'id'
}
where[primaryKeys] = smth
smth = Utils.prependTableNameToHash(tableName, where)
result = this.hashToWhereConditions(smth)
} else if (typeof smth === "string") {
result = smth
}
else if (Array.isArray(smth)) {
result = Utils.format(smth, "postgres")
addLimitAndOffset: function(options, query){
if (!(options.include && (options.limit === 1))) {
if (options.limit) {
query += " LIMIT " + options.limit
}
return result
},
hashToWhereConditions: function(hash, factory) {
var result = []
for (var key in hash) {
var value = hash[key]
//handle qualified key names
var _key = this.quoteIdentifiers(key)
, _value = null
if (Array.isArray(value)) {
if (value.length === 0) { value = [null] }
var col = null, coltype = null
// Special conditions for searching within an array column type
var _realKey = key.split('.').pop()
if (!!factory && !!factory.rawAttributes[_realKey]) {
col = factory.rawAttributes[_realKey]
coltype = col.type
if(coltype && !(typeof coltype == 'string')) {
coltype = coltype.toString();
}
}
if ( col && ((!!coltype && coltype.match(/\[\]$/) !== null) || (col.toString().match(/\[\]$/) !== null))) {
_value = 'ARRAY[' + value.map(this.escape).join(',') + ']::' + (!!col.type ? col.type : col.toString())
result.push([_key, _value].join(" && "))
} else {
_value = "(" + value.map(this.escape).join(',') + ")"
result.push([_key, _value].join(" IN "))
}
}
else if ((value) && (typeof value === "object")) {
if (!!value.join) {
//using as sentinel for join column => value
_value = this.quoteIdentifiers(value.join)
result.push([_key, _value].join("="))
} else {
for (var logic in value) {
var logicResult = Utils.getWhereLogic(logic)
if (logic === "IN" || logic === "NOT IN") {
var values = Array.isArray(where[i][ii]) ? where[i][ii] : [where[i][ii]]
_where[_where.length] = i + ' ' + logic + ' (' + values.map(function(){ return '?' }).join(',') + ')'
_whereArgs = _whereArgs.concat(values)
}
else if (logicResult === "BETWEEN" || logicResult === "NOT BETWEEN") {
_value = this.escape(value[logic][0])
var _value2 = this.escape(value[logic][1])
result.push(' (' + _key + ' ' + logicResult + ' ' + _value + ' AND ' + _value2 + ') ')
} else {
_value = this.escape(value[logic])
result.push([_key, _value].join(' ' + logicResult + ' '))
}
}
}
} else {
_value = this.escape(value)
result.push((_value == 'NULL') ? _key + " IS NULL" : [_key, _value].join("="))
if (options.offset) {
query += " OFFSET " + options.offset
}
}
return result.join(' AND ')
return query;
},
attributesToSQL: function(attributes) {
......@@ -670,7 +519,10 @@ module.exports = (function() {
template += " SERIAL"
}
if (dataType.defaultValue !== undefined) {
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)
}
......@@ -734,14 +586,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 %>'
......@@ -1024,6 +868,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) + ';'
}
}
......
......@@ -55,7 +55,7 @@ module.exports = (function() {
return Utils._.includes(definition, 'PRIMARY KEY')
}).length > 1)
, attrStr = []
, modifierLastIndex = -1
for (var attr in attributes) {
if (attributes.hasOwnProperty(attr)) {
......@@ -65,6 +65,26 @@ module.exports = (function() {
dataType = dataType.replace(/BIGINT/, 'INTEGER')
}
// SQLite thinks that certain modifiers should come before the length declaration,
// whereas other dialects want them after, see http://www.sqlite.org/lang_createtable.html.
// Start by finding the index of the last of the modifiers
['UNSIGNED', 'BINARY', 'ZEROFILL'].forEach(function (modifier) {
var tmpIndex = dataType.indexOf(modifier)
if (tmpIndex > modifierLastIndex) {
modifierLastIndex = tmpIndex + modifier.length
}
})
if (modifierLastIndex) {
// If a modifier was found, and a lenght declaration is given before the modifier, move the length
var length = dataType.match(/\(\s*\d+(\s*,\s*\d)?\s*\)/)
if (length && length.index < modifierLastIndex) {
dataType = dataType.replace(length[0], '')
dataType = Utils._.insert(dataType, modifierLastIndex, length[0])
}
}
if (Utils._.includes(dataType, 'PRIMARY KEY') && needsMultiplePrimaryKeys) {
primaryKeys.push(attr)
attrStr.push(this.quoteIdentifier(attr) + " " + dataType.replace(/PRIMARY KEY/, 'NOT NULL'))
......@@ -95,6 +115,10 @@ module.exports = (function() {
return this.replaceBooleanDefaults(sql)
},
booleanValue: function(value){
return !!value ? 1 : 0;
},
dropTableQuery: function(tableName, options) {
options = options || {}
......@@ -117,6 +141,19 @@ module.exports = (function() {
}
},
addLimitAndOffset: function(options, query){
if (options.offset && !options.limit) {
query += " LIMIT " + options.offset + ", " + 10000000000000;
} else if (options.limit && !(options.include && (options.limit === 1))) {
if (options.offset) {
query += " LIMIT " + options.offset + ", " + options.limit
} else {
query += " LIMIT " + options.limit
}
}
return query;
},
addColumnQuery: function() {
var sql = MySqlQueryGenerator.addColumnQuery.apply(this, arguments)
return this.replaceBooleanDefaults(sql)
......@@ -163,6 +200,7 @@ module.exports = (function() {
return Utils._.template(query)(replacements)
},
<<<<<<< HEAD
selectQuery: function(tableName, options, factory) {
var table = null,
joinQuery = ""
......@@ -331,7 +369,10 @@ module.exports = (function() {
template += " NOT NULL"
}
if (dataType.defaultValue !== undefined) {
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)
}
......@@ -395,32 +436,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)) {
var value = hash[key]
if (typeof value === 'boolean') {
value = !!value ? 1 : 0
}
hash[key] = value
}
}
return hashToWhereConditions.call(this, hash).replace(/\\'/g, "''");
},
showIndexQuery: function(tableName) {
var sql = "PRAGMA INDEX_LIST('<%= tableName %>')"
return Utils._.template(sql, { tableName: tableName })
......@@ -504,6 +519,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,48 @@ 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() {
var args = Array.prototype.slice.call(arguments);
args.unshift(null);
fct.apply(fct, args);
})
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,35 +223,46 @@ module.exports = (function() {
QueryInterface.prototype.dropAllTables = function() {
var self = this
return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer()
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)
}
self.showAllTables().success(function(tableNames) {
chainer.add(self, 'disableForeignKeyConstraints', [])
self.getForeignKeysForTables(tableNames).success(function(foreignKeys) {
tableNames.forEach(function(tableName) {
chainer.add(self, 'dropTable', [tableName, {cascade: true}])
// 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 ])
})
})
chainer.add(self, 'enableForeignKeyConstraints', [])
// add the table removal query to the chainer
tableNames.forEach(function(tableName) {
chainer.add(self, 'dropTable', [ tableName, { cascade: true } ])
})
chainer
.runSerially()
.success(function() {
self.emit('dropAllTables', null)
emitter.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)
dropAllTablesEmitter.emit('success', null)
})
.error(onError)
}).error(onError)
}).error(onError)
}).run()
}
}
QueryInterface.prototype.renameTable = function(before, after) {
var sql = this.QueryGenerator.renameTableQuery(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
......
......@@ -81,6 +81,7 @@ module.exports = (function() {
queue: true,
native: false,
replication: false,
ssl: undefined,
pool: {},
quoteIdentifiers: true,
language: 'en'
......@@ -101,6 +102,7 @@ module.exports = (function() {
protocol: this.options.protocol,
queue : this.options.queue,
native : this.options.native,
ssl : this.options.ssl,
replication: this.options.replication,
dialectModulePath: this.options.dialectModulePath,
maxConcurrentQueries: this.options.maxConcurrentQueries,
......@@ -191,7 +193,7 @@ module.exports = (function() {
attributes[name].validate = attributes[name].validate || {
_checkEnum: function(value) {
var hasValue = value !== undefined
, isMySQL = self.options.dialect === "mysql"
, isMySQL = ['mysql', 'mariadb'].indexOf(self.options.dialect) !== -1
, ciCollation = !!options.collate && options.collate.match(/_ci$/i) !== null
, valueOutOfScope
......@@ -243,11 +245,10 @@ module.exports = (function() {
Sequelize.prototype.import = function(path) {
// is it a relative path?
if (url.parse(path).pathname.indexOf('/') !== 0) {
if (Path.normalize(path).indexOf(path.sep) !== 0) {
// make path relative to the caller
var callerFilename = Utils.stack()[1].getFileName()
, callerMatch = callerFilename.match(/(.+\/).+?$/)
, callerPath = callerMatch[1]
, callerPath = Path.dirname(callerFilename)
path = Path.resolve(callerPath, path)
}
......@@ -363,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
})()
......@@ -373,7 +373,32 @@ var Utils = module.exports = {
},
toDefaultValue: function(value) {
if (lodash.isFunction(value)) {
return value()
} else {
return (value === DataTypes.NOW) ? Utils.now() : value
}
},
/**
* Determine if the default value provided exists and can be described
* in a db schema using the DEFAULT directive.
*
* @param {*} value Any default value.
* @return {boolean} yes / no.
*/
defaultValueSchemable: function(value) {
if (typeof value === 'undefined') {return false}
// TODO this will be schemable when all supported db
// have been normalized for this case
if (value === DataTypes.NOW) {return false}
if (lodash.isFunction(value)) {
return false
}
return true
},
setAttributes: function(hash, identifier, instance, prefix) {
......@@ -492,7 +517,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) {
......@@ -504,6 +553,7 @@ Utils.fn.prototype.toString = function(queryGenerator) {
}
}).join(', ') + ')'
}
Utils.col.prototype.toString = function (queryGenerator) {
return queryGenerator.quote(this.col)
}
......
......@@ -36,7 +36,7 @@
"url": "https://github.com/sequelize/sequelize/issues"
},
"dependencies": {
"lodash": "~2.1.0",
"lodash": "~2.2.0",
"underscore.string": "~2.3.0",
"lingo": "~0.0.5",
"validator": "~1.5.0",
......@@ -46,18 +46,19 @@
"toposort-class": "~0.2.0",
"generic-pool": "2.0.4",
"promise": "~3.2.0",
"sql": "~0.26.0"
"sql": "~0.28.0"
},
"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",
"chai": "~1.8.0",
"mocha": "~1.13.0",
"chai-datetime": "~1.1.1",
"sinon": "~1.7.3"
"sinon": "~1.7.3",
"mariasql": "git://github.com/sequelize/node-mariasql.git"
},
"keywords": [
"mysql",
......
......@@ -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() {
......
/* jshint camelcase: false, expr: true */
var chai = require('chai')
, expect = chai.expect
, Support = require(__dirname + '/../support')
, DataTypes = require(__dirname + "/../../lib/data-types")
chai.Assertion.includeStack = true
describe(Support.getTestDialectTeaser("Multiple Level Filters"), function() {
it('can filter through belongsTo', function(done) {
var User = this.sequelize.define('User', {username: DataTypes.STRING })
, Task = this.sequelize.define('Task', {title: DataTypes.STRING })
, Project = this.sequelize.define('Project', { title: DataTypes.STRING })
Project.belongsTo(User);
User.hasMany(Project)
Task.belongsTo(Project);
Project.hasMany(Task);
this.sequelize.sync({ force: true }).success(function() {
User.bulkCreate([{
username: 'leia'
}, {
username: 'vader'
}]).success(function() {
Project.bulkCreate([{
UserId: 1,
title: 'republic'
},{
title: 'empire'
}]).success(function() {
Task.bulkCreate([{
ProjectId: 1,
title: 'fight empire'
},{
ProjectId: 1,
title: 'stablish republic'
},{
ProjectId: 2,
title: 'destroy rebel alliance'
},{
ProjectId: 2,
title: 'rule everything'
}]).success(function() {
Task.findAll({
where: {
'project.user.username': 'leia'
}
}).success(function(tasks){
try{
expect(tasks.length).to.be.equal(2);
expect(tasks[0].title).to.be.equal('fight empire');
expect(tasks[1].title).to.be.equal('stablish republic');
done();
}catch(e){
done(e);
}
})
});
});
});
})
})
it('avoids duplicated tables in query', function(done) {
var User = this.sequelize.define('User', {username: DataTypes.STRING })
, Task = this.sequelize.define('Task', {title: DataTypes.STRING })
, Project = this.sequelize.define('Project', { title: DataTypes.STRING })
Project.belongsTo(User);
User.hasMany(Project)
Task.belongsTo(Project);
Project.hasMany(Task);
this.sequelize.sync({ force: true }).success(function() {
User.bulkCreate([{
username: 'leia'
}, {
username: 'vader'
}]).success(function() {
Project.bulkCreate([{
UserId: 1,
title: 'republic'
},{
UserId: 2,
title: 'empire'
}]).success(function() {
Task.bulkCreate([{
ProjectId: 1,
title: 'fight empire'
},{
ProjectId: 1,
title: 'stablish republic'
},{
ProjectId: 2,
title: 'destroy rebel alliance'
},{
ProjectId: 2,
title: 'rule everything'
}]).success(function() {
Task.findAll({
where: {
'project.user.username': 'leia',
'project.user.id': 1
}
}).success(function(tasks){
try{
expect(tasks.length).to.be.equal(2);
expect(tasks[0].title).to.be.equal('fight empire');
expect(tasks[1].title).to.be.equal('stablish republic');
done();
}catch(e){
done(e);
}
})
});
});
});
})
})
it('can filter through hasMany', function(done) {
var User = this.sequelize.define('User', {username: DataTypes.STRING })
, Task = this.sequelize.define('Task', {title: DataTypes.STRING })
, Project = this.sequelize.define('Project', { title: DataTypes.STRING })
Project.belongsTo(User);
User.hasMany(Project)
Task.belongsTo(Project);
Project.hasMany(Task);
this.sequelize.sync({ force: true }).success(function() {
User.bulkCreate([{
username: 'leia'
}, {
username: 'vader'
}]).success(function() {
Project.bulkCreate([{
UserId: 1,
title: 'republic'
},{
UserId: 2,
title: 'empire'
}]).success(function() {
Task.bulkCreate([{
ProjectId: 1,
title: 'fight empire'
},{
ProjectId: 1,
title: 'stablish republic'
},{
ProjectId: 2,
title: 'destroy rebel alliance'
},{
ProjectId: 2,
title: 'rule everything'
}]).success(function() {
User.findAll({
where: {
'projects.tasks.title': 'fight empire'
}
}).success(function(users){
try{
expect(users.length).to.be.equal(1);
expect(users[0].username).to.be.equal('leia');
done();
}catch(e){
done(e);
}
})
});
});
});
})
})
it('can filter through hasMany connector', function(done) {
var User = this.sequelize.define('User', {username: DataTypes.STRING })
, Project = this.sequelize.define('Project', { title: DataTypes.STRING })
Project.hasMany(User);
User.hasMany(Project)
this.sequelize.sync({ force: true }).success(function() {
User.bulkCreate([{
username: 'leia'
}, {
username: 'vader'
}]).success(function() {
Project.bulkCreate([{
title: 'republic'
},{
title: 'empire'
}]).success(function() {
User.find(1).success(function(user){
Project.find(1).success(function(project){
user.setProjects([project]).success(function(){
User.find(2).success(function(user){
Project.find(2).success(function(project){
user.setProjects([project]).success(function(){
User.findAll({
where: {
'projects.title': 'republic'
}
}).success(function(users){
try{
expect(users.length).to.be.equal(1);
expect(users[0].username).to.be.equal('leia');
done();
}catch(e){
done(e);
}
})
});
});
});
});
});
});
});
});
})
})
})
......@@ -38,5 +38,17 @@ module.exports = {
maxConnections: process.env.SEQ_PG_POOL_MAX || process.env.SEQ_POOL_MAX || 5,
maxIdleTime: process.env.SEQ_PG_POOL_IDLE || process.env.SEQ_POOL_IDLE || 3000
}
},
mariadb: {
database: process.env.SEQ_MYSQL_DB || process.env.SEQ_DB || 'sequelize_test',
username: process.env.SEQ_MYSQL_USER || process.env.SEQ_USER || "root",
password: process.env.SEQ_MYSQL_PW || process.env.SEQ_PW || null,
host: process.env.SEQ_MYSQL_HOST || process.env.SEQ_HOST || '127.0.0.1',
port: process.env.SEQ_MYSQL_PORT || process.env.SEQ_PORT || 3306,
pool: {
maxConnections: process.env.SEQ_MYSQL_POOL_MAX || process.env.SEQ_POOL_MAX || 1,
maxIdleTime: process.env.SEQ_MYSQL_POOL_IDLE || process.env.SEQ_POOL_IDLE || 3000
}
}
}
/* jshint camelcase: false */
/* jshint expr: true */
var chai = require('chai')
, Sequelize = require('../index')
, expect = chai.expect
......@@ -158,6 +159,27 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
it('should allow me to set a function as default value', function(done) {
var defaultFunction = sinon.stub().returns(5)
var UserTable = this.sequelize.define('UserCol', {
aNumber: {
type: Sequelize.INTEGER,
defaultValue: defaultFunction
}
}, { timestamps: true })
UserTable.sync({ force: true }).success(function() {
UserTable.create().success(function(user) {
UserTable.create().success(function(user2) {
expect(user.aNumber).to.equal(5)
expect(user2.aNumber).to.equal(5)
expect(defaultFunction.callCount).to.equal(2)
done()
})
})
})
})
it('should allow me to override updatedAt, createdAt, and deletedAt fields', function(done) {
var UserTable = this.sequelize.define('UserCol', {
aNumber: Sequelize.INTEGER
......@@ -247,7 +269,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 +350,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 +424,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 +432,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 +460,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 = Support.dialectIsMySQL() ? '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 (Support.dialectIsMySQL()) {
type = this.sequelize.cast(this.sequelize.cast(this.sequelize.literal('1-2'), 'unsigned'), 'signed')
}
this.User.create({
intVal: type
}).on('sql', function (sql) {
if (Support.dialectIsMySQL()) {
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 ' + (Support.dialectIsMySQL() ? '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', (Support.dialectIsMySQL() ? '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 +547,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
})
it('is possible to use functions as default values', function (done) {
var self = this
, userWithDefaults
......@@ -446,8 +600,9 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
done()
}
})
it("casts empty arrays correctly for postgresql insert", function(done) {
if (dialect !== "postgres" && dialect !== "postgresql-native") {
if (dialect !== "postgres") {
expect('').to.equal('')
return done()
}
......@@ -466,7 +621,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
it("casts empty array correct for postgres update", function(done) {
if (dialect !== "postgres" && dialect !== "postgresql-native") {
if (dialect !== "postgres") {
expect('').to.equal('')
return done()
}
......@@ -535,7 +690,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
if (dialect === "sqlite") {
expect(err.message).to.match(/.*SQLITE_CONSTRAINT.*/)
}
else if (dialect === "mysql") {
else if (Support.dialectIsMySQL()) {
expect(err.message).to.match(/.*Duplicate\ entry.*/)
} else {
expect(err.message).to.match(/.*duplicate\ key\ value.*/)
......@@ -559,7 +714,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
UserNull.create({ username: 'foo2', smth: null }).error(function(err) {
expect(err).to.exist
if (dialect === "mysql") {
if (Support.dialectIsMySQL()) {
// We need to allow two different errors for MySQL, see:
// http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_trans_tables
expect(err.message).to.match(/(Column 'smth' cannot be null|Field 'smth' doesn't have a default value)/)
......@@ -577,7 +732,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
if (dialect === "sqlite") {
expect(err.message).to.match(/.*SQLITE_CONSTRAINT.*/)
}
else if (dialect === "mysql") {
else if (Support.dialectIsMySQL()) {
expect(err.message).to.match(/Duplicate entry 'foo' for key 'username'/)
} else {
expect(err.message).to.match(/.*duplicate key value violates unique constraint.*/)
......@@ -698,7 +853,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]
......@@ -1100,6 +1255,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
......@@ -1142,7 +1312,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
})
......@@ -1371,7 +1541,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
it('should be able to handle false/true values just fine...', function(done) {
var User = this.User
, escapeChar = (dialect === "postgres" || dialect === "postgres-native") ? '"' : '`'
, escapeChar = (dialect === "postgres") ? '"' : '`'
User.bulkCreate([
{username: 'boo5', aBool: false},
......@@ -1392,7 +1562,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
it('should be able to handle false/true values through associations as well...', function(done) {
var User = this.User
, escapeChar = (dialect === "postgres" || dialect === "postgres-native") ? '"' : '`'
, escapeChar = (dialect === "postgres") ? '"' : '`'
var Passports = this.sequelize.define('Passports', {
isActive: Sequelize.BOOLEAN
})
......@@ -1437,7 +1607,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 }
......@@ -2088,6 +2258,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
......@@ -3455,12 +3680,12 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
expect(schemas[0]).to.be.instanceof(Array)
// sqlite & MySQL doesn't actually create schemas unless Model.sync() is called
// Postgres supports schemas natively
expect(schemas[0]).to.have.length((dialect === "postgres" || dialect === "postgres-native" ? 2 : 1))
expect(schemas[0]).to.have.length((dialect === "postgres" ? 2 : 1))
done()
})
})
if (dialect === "mysql" || dialect === "sqlite") {
if (Support.dialectIsMySQL() || dialect === "sqlite") {
it("should take schemaDelimiter into account if applicable", function(done){
var UserSpecialUnderscore = this.sequelize.define('UserSpecialUnderscore', {age: Sequelize.INTEGER}, {schema: 'hello', schemaDelimiter: '_'})
var UserSpecialDblUnderscore = this.sequelize.define('UserSpecialDblUnderscore', {age: Sequelize.INTEGER})
......@@ -3494,26 +3719,26 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
UserPublic.schema('special').sync({ force: true }).success(function() {
self.sequelize.queryInterface.describeTable('Publics')
.on('sql', function(sql) {
if (dialect === "sqlite" || dialect === "mysql") {
if (dialect === "sqlite" || Support.dialectIsMySQL()) {
expect(sql).to.not.contain('special')
_done()
}
})
.success(function(table) {
if (dialect === "postgres" || dialect === "postgres-native") {
if (dialect === "postgres") {
expect(table.id.defaultValue).to.not.contain('special')
_done()
}
self.sequelize.queryInterface.describeTable('Publics', 'special')
.on('sql', function(sql) {
if (dialect === "sqlite" || dialect === "mysql") {
if (dialect === "sqlite" || Support.dialectIsMySQL()) {
expect(sql).to.contain('special')
_done()
}
})
.success(function(table) {
if (dialect === "postgres" || dialect === "postgres-native") {
if (dialect === "postgres") {
expect(table.id.defaultValue).to.contain('special')
_done()
}
......@@ -3602,15 +3827,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()
})
})
......@@ -3641,10 +3864,11 @@ 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"\)/)
} else if (dialect === 'mysql') {
} else if (Support.dialectIsMySQL()) {
expect(sql).to.match(/FOREIGN KEY \(`authorId`\) REFERENCES `authors` \(`id`\)/)
} else if (dialect === 'sqlite') {
expect(sql).to.match(/`authorId` INTEGER REFERENCES `authors` \(`id`\)/)
......@@ -3670,10 +3894,11 @@ 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"\)/)
} else if (dialect === 'mysql') {
} else if (Support.dialectIsMySQL()) {
expect(sql).to.match(/FOREIGN KEY \(`authorId`\) REFERENCES `authors` \(`id`\)/)
} else if (dialect === 'sqlite') {
expect(sql).to.match(/`authorId` INTEGER REFERENCES `authors` \(`id`\)/)
......@@ -3699,6 +3924,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 ...
......@@ -3710,8 +3936,10 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
done()
}
}).error(function(err) {
if (dialect === 'mysql') {
if (Support.dialectIsMySQL(true)) {
expect(err.message).to.match(/ER_CANNOT_ADD_FOREIGN|ER_CANT_CREATE_TABLE/)
} else if (dialect === 'mariadb') {
expect(err.message).to.match(/Can\'t create table/)
} else if (dialect === 'sqlite') {
// the parser should not end up here ... see above
expect(1).to.equal(2)
......@@ -3741,7 +3969,9 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
describe("dataset", function() {
it("returns a node-sql instance with the correct dialect", function() {
expect(this.User.dataset().sql.dialectName).to.equal(dialect)
var _dialect = dialect === 'mariadb' ? 'mysql' : dialect
expect(this.User.dataset().sql.dialectName).to.equal(_dialect)
})
it("allows me to generate sql queries", function() {
......@@ -3764,6 +3994,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
var sql = this.User.select("username").toSql()
var sqlMap = {
postgres: 'SELECT username FROM "' + this.User.tableName + '";',
mariadb: 'SELECT username FROM `' + this.User.tableName + '`;',
mysql: 'SELECT username FROM `' + this.User.tableName + '`;',
sqlite: 'SELECT username FROM "' + this.User.tableName + '";'
}
......@@ -3774,6 +4005,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
var sql = this.User.select("username").select("firstName").group("username").toSql()
var sqlMap = {
postgres: 'SELECT username, firstName FROM "' + this.User.tableName + '" GROUP BY username;',
mariadb: 'SELECT username, firstName FROM `' + this.User.tableName + '` GROUP BY username;',
mysql: 'SELECT username, firstName FROM `' + this.User.tableName + '` GROUP BY username;',
sqlite: 'SELECT username, firstName FROM "' + this.User.tableName + '" GROUP BY username;'
}
......
......@@ -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
......@@ -1035,7 +1032,7 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
if (dialect === "postgres" || dialect === "postgres-native") {
expect(sql).to.equal('DELETE FROM "UserDestroys" WHERE "newId" IN (SELECT "newId" FROM "UserDestroys" WHERE "newId"=\'123ABC\' LIMIT 1)')
}
else if (dialect === "mysql") {
else if (Support.dialectIsMySQL()) {
expect(sql).to.equal("DELETE FROM `UserDestroys` WHERE `newId`='123ABC' LIMIT 1")
} else {
expect(sql).to.equal("DELETE FROM `UserDestroys` WHERE `newId`='123ABC'")
......@@ -1117,6 +1114,25 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
})
})
})
it("returns null for null, undefined, and unset boolean values", function(done) {
var Setting = this.sequelize.define('SettingHelper', {
setting_key: DataTypes.STRING,
bool_value: { type: DataTypes.BOOLEAN, allowNull: true },
bool_value2: { type: DataTypes.BOOLEAN, allowNull: true },
bool_value3: { type: DataTypes.BOOLEAN, allowNull: true }
}, { timestamps: false, logging: false })
Setting.sync({ force: true }).success(function() {
Setting.create({ setting_key: 'test', bool_value: null, bool_value2: undefined }).success(function() {
Setting.find({ where: { setting_key: 'test' } }).success(function(setting) {
expect(setting.bool_value).to.equal(null)
expect(setting.bool_value2).to.equal(null)
expect(setting.bool_value3).to.equal(null)
done()
})
})
})
})
})
describe('equals', function() {
......
......@@ -7,7 +7,7 @@ var chai = require('chai')
chai.Assertion.includeStack = true
if (dialect.match(/^mysql/)) {
if (Support.dialectIsMySQL()) {
describe('[MYSQL Specific] Associations', function() {
describe('many-to-many', function() {
describe('where tables have the same prefix', function() {
......
......@@ -7,10 +7,8 @@ var chai = require('chai')
chai.Assertion.includeStack = true
if (dialect.match(/^mysql/)) {
if (Support.dialectIsMySQL()) {
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()
......
......@@ -8,7 +8,7 @@ var chai = require('chai')
chai.Assertion.includeStack = true
if (dialect.match(/^mysql/)) {
if (Support.dialectIsMySQL()) {
describe("[MYSQL Specific] DAOFactory", function () {
describe('constructor', function() {
it("handles extended attributes (unique)", function(done) {
......
......@@ -9,7 +9,7 @@ var chai = require('chai')
chai.Assertion.includeStack = true
if (dialect.match(/^mysql/)) {
if (Support.dialectIsMySQL()) {
describe("[MYSQL Specific] QueryGenerator", function () {
var suites = {
attributesToSQL: [
......@@ -450,10 +450,10 @@ if (dialect.match(/^mysql/)) {
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`"
}
],
......
......@@ -18,7 +18,7 @@ describe(Support.getTestDialectTeaser("QueryGenerators"), function () {
User.sync({ force: true }).success(function() {
var sql = ''
if (dialect === "mysql") {
if (Support.dialectIsMySQL()) {
sql = 'SELECT COLUMN_COMMENT as cmt FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = \'' + self.sequelize.config.database + '\' AND TABLE_NAME = \'Users\' AND COLUMN_NAME = \'username\'';
}
else if (dialect === "postgres" || dialect === "postgres-native") {
......
......@@ -13,7 +13,7 @@ chai.Assertion.includeStack = true
var qq = function(str) {
if (dialect == 'postgres' || dialect == 'sqlite') {
return '"' + str + '"'
} else if (dialect == 'mysql') {
} else if (Support.dialectIsMySQL()) {
return '`' + str + '`'
} else {
return str
......@@ -147,7 +147,7 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () {
})
})
if (dialect == 'mysql') {
if (Support.dialectIsMySQL()) {
it('executes stored procedures', function(done) {
var self = this
self.sequelize.query(this.insertQuery).success(function() {
......
......@@ -91,6 +91,10 @@ if (dialect === 'sqlite') {
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255));"
},
{
arguments: ['myTable', {title: 'VARCHAR(255) BINARY', number: 'INTEGER(5) UNSIGNED'}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR BINARY(255), `number` INTEGER UNSIGNED(5));"
},
{
arguments: ['myTable', {title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)'}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` ENUM(\"A\", \"B\", \"C\"), `name` VARCHAR(255));"
},
......
......@@ -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)
},
......@@ -101,6 +101,19 @@ var Support = {
return envDialect
},
dialectIsMySQL: function(strict) {
var envDialect = process.env.DIALECT || 'mysql'
if (strict === undefined) {
strict = false
}
if (strict) {
return envDialect === 'mysql'
} else {
return ['mysql', 'mariadb'].indexOf(envDialect) !== -1
}
},
getTestDialectTeaser: function(moduleName) {
var dialect = this.getTestDialect()
......@@ -128,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!