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

Commit 1e343008 by Daniel Durante

Merge branch 'master' into sync-logging

2 parents 765c97cb 6a48ec2c
# 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
docs
examples
test
README.md
.watchr.js
changelog.md
Makefile
\ No newline at end of file
...@@ -18,6 +18,7 @@ env: ...@@ -18,6 +18,7 @@ env:
- DB=mysql DIALECT=postgres - DB=mysql DIALECT=postgres
- DB=mysql DIALECT=postgres-native - DB=mysql DIALECT=postgres-native
- DB=mysql DIALECT=sqlite - DB=mysql DIALECT=sqlite
- DB=mysql DIALECT=mariadb
language: node_js language: node_js
......
...@@ -13,12 +13,14 @@ teaser: ...@@ -13,12 +13,14 @@ teaser:
test: test:
@if [ "$$GREP" ]; then \ @if [ "$$GREP" ]; then \
make teaser && ./node_modules/mocha/bin/mocha --colors --reporter $(REPORTER) -g "$$GREP" $(TESTS); \ make teaser && ./node_modules/mocha/bin/mocha --colors -t 10000 --reporter $(REPORTER) -g "$$GREP" $(TESTS); \
else \ else \
make teaser && ./node_modules/mocha/bin/mocha --colors --reporter $(REPORTER) $(TESTS); \ make teaser && ./node_modules/mocha/bin/mocha --colors -t 10000 --reporter $(REPORTER) $(TESTS); \
fi fi
mariadb:
@DIALECT=mariadb make test
sqlite: sqlite:
@DIALECT=sqlite make test @DIALECT=sqlite make test
mysql: mysql:
...@@ -37,6 +39,6 @@ postgresn: postgres-native ...@@ -37,6 +39,6 @@ postgresn: postgres-native
# test all the dialects \o/ # 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 .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 [![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) #
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 ## ## Important Notes ##
...@@ -61,7 +61,7 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl ...@@ -61,7 +61,7 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl
- Transactions - Transactions
- Associations of not yet saved objects: [#864](https://github.com/sequelize/sequelize/issues/864) - Associations of not yet saved objects: [#864](https://github.com/sequelize/sequelize/issues/864)
- Support for update of tables without primary key - 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 - ~~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) - 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)) - ~~Model#delete~~ (renamed to [Model.destroy()](http://sequelizejs.com/documentation#instances-destroy))
......
...@@ -80,6 +80,10 @@ ...@@ -80,6 +80,10 @@
- [FEATURE] Triggers for Postgres. [#915](https://github.com/sequelize/sequelize/pull/915). Thanks to jonathana. - [FEATURE] Triggers for Postgres. [#915](https://github.com/sequelize/sequelize/pull/915). Thanks to jonathana.
- [FEATURE] Support for join tables. [#877](https://github.com/sequelize/sequelize/pull/877). Thanks to janmeier. - [FEATURE] Support for join tables. [#877](https://github.com/sequelize/sequelize/pull/877). Thanks to janmeier.
- [FEATURE] Support for hooks. [#894](https://github.com/sequelize/sequelize/pull/894). Thanks to durango. - [FEATURE] Support for hooks. [#894](https://github.com/sequelize/sequelize/pull/894). Thanks to durango.
- [FEATURE] Support for literals and casts. [#950](https://github.com/sequelize/sequelize/pull/950). Thanks to durango.
- [FEATURE] Model#findOrBuild. [#960](https://github.com/sequelize/sequelize/pull/960). Thanks to durango.
- [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] hasMany now uses a single SQL statement when creating and destroying associations, instead of removing each association seperately [690](https://github.com/sequelize/sequelize/pull/690). Inspired by [#104](https://github.com/sequelize/sequelize/issues/104). janmeier
- [REFACTORING] Consistent handling of offset across dialects. Offset is now always applied, and limit is set to max table size of not limit is given [#725](https://github.com/sequelize/sequelize/pull/725). janmeier - [REFACTORING] Consistent handling of offset across dialects. Offset is now always applied, and limit is set to max table size of not limit is given [#725](https://github.com/sequelize/sequelize/pull/725). janmeier
- [REFACTORING] Moved Jasmine to Buster and then Buster to Mocha + Chai. sdepold and durango - [REFACTORING] Moved Jasmine to Buster and then Buster to Mocha + Chai. sdepold and durango
......
...@@ -172,9 +172,8 @@ module.exports = (function() { ...@@ -172,9 +172,8 @@ module.exports = (function() {
self.DAO.prototype.booleanValues.push(name); self.DAO.prototype.booleanValues.push(name);
} }
if (definition.hasOwnProperty('defaultValue')) { if (definition.hasOwnProperty('defaultValue')) {
self.DAO.prototype.defaultValues[name] = function() { self.DAO.prototype.defaultValues[name] = Utils._.partial(
return Utils.toDefaultValue(definition.defaultValue) Utils.toDefaultValue, definition.defaultValue)
}
} }
if (definition.hasOwnProperty('validate')) { if (definition.hasOwnProperty('validate')) {
...@@ -532,6 +531,39 @@ module.exports = (function() { ...@@ -532,6 +531,39 @@ module.exports = (function() {
return this.build(values).save(fields) return this.build(values).save(fields)
} }
DAOFactory.prototype.findOrInitialize = DAOFactory.prototype.findOrBuild = function (params, defaults) {
var self = this
, defaultKeys = Object.keys(defaults || {})
, defaultLength = defaultKeys.length
return new Utils.CustomEventEmitter(function (emitter) {
self.find({
where: params
}).success(function (instance) {
if (instance === null) {
var i = 0
for (i = 0; i < defaultLength; i++) {
params[defaultKeys[i]] = defaults[defaultKeys[i]]
}
var build = self.build(params)
build.hookValidate({skip: Object.keys(params)}).success(function (instance) {
emitter.emit('success', build, true)
})
.error(function (error) {
emitter.emit('error', error)
})
} else {
emitter.emit('success', instance, false)
}
}).error(function (error) {
emitter.emit('error', error)
})
}).run()
}
DAOFactory.prototype.findOrCreate = function (params, defaults) { DAOFactory.prototype.findOrCreate = function (params, defaults) {
var self = this; var self = this;
...@@ -975,7 +1007,7 @@ module.exports = (function() { ...@@ -975,7 +1007,7 @@ module.exports = (function() {
DAOFactory.prototype.dataset = function() { DAOFactory.prototype.dataset = function() {
if (!this.__sql) { if (!this.__sql) {
this.__sql = sql.setDialect(this.daoFactoryManager.sequelize.options.dialect) this.__setSqlDialect()
} }
var instance = this.__sql.define({ name: this.tableName, columns: [] }) var instance = this.__sql.define({ name: this.tableName, columns: [] })
...@@ -988,6 +1020,11 @@ module.exports = (function() { ...@@ -988,6 +1020,11 @@ module.exports = (function() {
return instance return instance
} }
DAOFactory.prototype.__setSqlDialect = function() {
var dialect = this.daoFactoryManager.sequelize.options.dialect
this.__sql = sql.setDialect(dialect === 'mariadb' ? 'mysql' : dialect)
}
// private // private
var paranoidClause = function(options) { var paranoidClause = function(options) {
...@@ -1076,7 +1113,10 @@ module.exports = (function() { ...@@ -1076,7 +1113,10 @@ module.exports = (function() {
if (include.hasOwnProperty('attributes')) { if (include.hasOwnProperty('attributes')) {
var primaryKeys; var primaryKeys;
if (include.daoFactory.hasPrimaryKeys) { if (include.daoFactory.hasPrimaryKeys) {
primaryKeys = include.daoFactory.primaryKeys primaryKeys = []
for (var field_name in include.daoFactory.primaryKeys) {
primaryKeys.push(field_name)
}
} else { } else {
primaryKeys = ['id'] primaryKeys = ['id']
} }
......
...@@ -131,7 +131,7 @@ module.exports = (function() { ...@@ -131,7 +131,7 @@ module.exports = (function() {
var definition = self.daoFactory.rawAttributes[attrName] var definition = self.daoFactory.rawAttributes[attrName]
, isHstore = !!definition.type && !!definition.type.type && definition.type.type === DataTypes.HSTORE.type , isHstore = !!definition.type && !!definition.type.type && definition.type.type === DataTypes.HSTORE.type
, isEnum = definition.type && (definition.type.toString() === DataTypes.ENUM.toString()) , 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) , ciCollation = !!self.daoFactory.options.collate && self.daoFactory.options.collate.match(/_ci$/i)
// Unfortunately for MySQL CI collation we need to map/lowercase values again // Unfortunately for MySQL CI collation we need to map/lowercase values again
...@@ -409,7 +409,7 @@ module.exports = (function() { ...@@ -409,7 +409,7 @@ module.exports = (function() {
} }
DAO.prototype.addAttribute = function(attribute, value) { 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 value = !!value
} }
......
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") var Utils = require("../../utils")
, DataTypes = require("../../data-types") , DataTypes = require("../../data-types")
, SqlString = require("../../sql-string")
, util = require("util") , util = require("util")
module.exports = (function() { module.exports = (function() {
...@@ -163,100 +162,6 @@ module.exports = (function() { ...@@ -163,100 +162,6 @@ module.exports = (function() {
return Utils._.template(query)({ tableName: tableName, attributes: attrString.join(', ') }) 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) { insertQuery: function(tableName, attrValueHash) {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull) attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull)
...@@ -422,83 +327,6 @@ module.exports = (function() { ...@@ -422,83 +327,6 @@ module.exports = (function() {
return Utils._.template(sql)({ tableName: tableName, indexName: indexName }) 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) { attributesToSQL: function(attributes) {
var result = {} var result = {}
...@@ -529,7 +357,7 @@ module.exports = (function() { ...@@ -529,7 +357,7 @@ module.exports = (function() {
} }
// Blobs/texts cannot have a defaultValue // 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) template += " DEFAULT " + this.escape(dataType.defaultValue)
} }
...@@ -589,6 +417,19 @@ module.exports = (function() { ...@@ -589,6 +417,19 @@ module.exports = (function() {
return fields return fields
}, },
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) { quoteIdentifier: function(identifier, force) {
return Utils.addTicks(identifier, "`") return Utils.addTicks(identifier, "`")
}, },
......
...@@ -55,7 +55,7 @@ module.exports = (function() { ...@@ -55,7 +55,7 @@ module.exports = (function() {
return Utils._.includes(definition, 'PRIMARY KEY') return Utils._.includes(definition, 'PRIMARY KEY')
}).length > 1) }).length > 1)
, attrStr = [] , attrStr = []
, modifierLastIndex = -1
for (var attr in attributes) { for (var attr in attributes) {
if (attributes.hasOwnProperty(attr)) { if (attributes.hasOwnProperty(attr)) {
...@@ -65,6 +65,26 @@ module.exports = (function() { ...@@ -65,6 +65,26 @@ module.exports = (function() {
dataType = dataType.replace(/BIGINT/, 'INTEGER') 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) { if (Utils._.includes(dataType, 'PRIMARY KEY') && needsMultiplePrimaryKeys) {
primaryKeys.push(attr) primaryKeys.push(attr)
attrStr.push(this.quoteIdentifier(attr) + " " + dataType.replace(/PRIMARY KEY/, 'NOT NULL')) attrStr.push(this.quoteIdentifier(attr) + " " + dataType.replace(/PRIMARY KEY/, 'NOT NULL'))
...@@ -89,6 +109,10 @@ module.exports = (function() { ...@@ -89,6 +109,10 @@ module.exports = (function() {
return this.replaceBooleanDefaults(sql) return this.replaceBooleanDefaults(sql)
}, },
booleanValue: function(value){
return !!value ? 1 : 0;
},
dropTableQuery: function(tableName, options) { dropTableQuery: function(tableName, options) {
options = options || {} options = options || {}
...@@ -99,6 +123,19 @@ module.exports = (function() { ...@@ -99,6 +123,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() { addColumnQuery: function() {
var sql = MySqlQueryGenerator.addColumnQuery.apply(this, arguments) var sql = MySqlQueryGenerator.addColumnQuery.apply(this, arguments)
return this.replaceBooleanDefaults(sql) return this.replaceBooleanDefaults(sql)
...@@ -145,100 +182,6 @@ module.exports = (function() { ...@@ -145,100 +182,6 @@ module.exports = (function() {
return Utils._.template(query)(replacements) return Utils._.template(query)(replacements)
}, },
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 SQLite docs:
* A 140 terabytes database can hold no more than approximately 1e+13 rows
*/
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
}
}
query += ";"
return query
},
updateQuery: function(tableName, attrValueHash, where, options) { updateQuery: function(tableName, attrValueHash, where, options) {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull, options) attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull, options)
...@@ -313,7 +256,10 @@ module.exports = (function() { ...@@ -313,7 +256,10 @@ module.exports = (function() {
template += " NOT NULL" 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 %>" template += " DEFAULT <%= defaultValue %>"
replacements.defaultValue = this.escape(dataType.defaultValue) replacements.defaultValue = this.escape(dataType.defaultValue)
} }
...@@ -377,22 +323,6 @@ module.exports = (function() { ...@@ -377,22 +323,6 @@ module.exports = (function() {
return fields return fields
}, },
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) { showIndexQuery: function(tableName) {
var sql = "PRAGMA INDEX_LIST('<%= tableName %>')" var sql = "PRAGMA INDEX_LIST('<%= tableName %>')"
return Utils._.template(sql, { tableName: tableName }) return Utils._.template(sql, { tableName: tableName })
......
...@@ -49,7 +49,11 @@ module.exports = (function() { ...@@ -49,7 +49,11 @@ module.exports = (function() {
function(fct) { function(fct) {
fct = bindToProcess(fct); fct = bindToProcess(fct);
this.on('error', function(err) { fct(err, null) }) this.on('error', function(err) { fct(err, null) })
.on('success', function(result) { fct(null, result) }) .on('success', function() {
var args = Array.prototype.slice.call(arguments);
args.unshift(null);
fct.apply(fct, args);
})
return this return this
} }
......
...@@ -81,6 +81,7 @@ module.exports = (function() { ...@@ -81,6 +81,7 @@ module.exports = (function() {
queue: true, queue: true,
native: false, native: false,
replication: false, replication: false,
ssl: undefined,
pool: {}, pool: {},
quoteIdentifiers: true, quoteIdentifiers: true,
language: 'en' language: 'en'
...@@ -101,6 +102,7 @@ module.exports = (function() { ...@@ -101,6 +102,7 @@ module.exports = (function() {
protocol: this.options.protocol, protocol: this.options.protocol,
queue : this.options.queue, queue : this.options.queue,
native : this.options.native, native : this.options.native,
ssl : this.options.ssl,
replication: this.options.replication, replication: this.options.replication,
dialectModulePath: this.options.dialectModulePath, dialectModulePath: this.options.dialectModulePath,
maxConcurrentQueries: this.options.maxConcurrentQueries, maxConcurrentQueries: this.options.maxConcurrentQueries,
...@@ -191,7 +193,7 @@ module.exports = (function() { ...@@ -191,7 +193,7 @@ module.exports = (function() {
attributes[name].validate = attributes[name].validate || { attributes[name].validate = attributes[name].validate || {
_checkEnum: function(value) { _checkEnum: function(value) {
var hasValue = value !== undefined 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 , ciCollation = !!options.collate && options.collate.match(/_ci$/i) !== null
, valueOutOfScope , valueOutOfScope
...@@ -243,11 +245,10 @@ module.exports = (function() { ...@@ -243,11 +245,10 @@ module.exports = (function() {
Sequelize.prototype.import = function(path) { Sequelize.prototype.import = function(path) {
// is it a relative 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 // make path relative to the caller
var callerFilename = Utils.stack()[1].getFileName() var callerFilename = Utils.stack()[1].getFileName()
, callerMatch = callerFilename.match(/(.+\/).+?$/) , callerPath = Path.dirname(callerFilename)
, callerPath = callerMatch[1]
path = Path.resolve(callerPath, path) path = Path.resolve(callerPath, path)
} }
...@@ -365,5 +366,13 @@ module.exports = (function() { ...@@ -365,5 +366,13 @@ module.exports = (function() {
return new Utils.col(col) return new Utils.col(col)
} }
Sequelize.prototype.cast = function (val, type) {
return new Utils.cast(val, type)
}
Sequelize.prototype.literal = function (val) {
return new Utils.literal(val)
}
return Sequelize return Sequelize
})() })()
...@@ -373,7 +373,32 @@ var Utils = module.exports = { ...@@ -373,7 +373,32 @@ var Utils = module.exports = {
}, },
toDefaultValue: function(value) { toDefaultValue: function(value) {
if (lodash.isFunction(value)) {
return value()
} else {
return (value === DataTypes.NOW) ? Utils.now() : value 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) { setAttributes: function(hash, identifier, instance, prefix) {
...@@ -492,7 +517,31 @@ var Utils = module.exports = { ...@@ -492,7 +517,31 @@ var Utils = module.exports = {
}, },
col: function (col) { col: function (col) {
this.col = col this.col = col
},
cast: function (val, type) {
this.val = val
this.type = (type || '').trim()
},
literal: function (val) {
this.val = val
}
}
// I know this may seem silly, but this gives us the ability to recognize whether
// or not we should be escaping or if we should trust the user. Basically, it
// keeps things in perspective and organized.
Utils.literal.prototype.toString = function() {
return this.val
}
Utils.cast.prototype.toString = function(queryGenerator) {
if (!this.val instanceof Utils.fn && !this.val instanceof Utils.col && !this.val instanceof Utils.literal) {
this.val = queryGenerator.escape(this.val)
} else {
this.val = this.val.toString(queryGenerator)
} }
return 'CAST(' + this.val + ' AS ' + this.type.toUpperCase() + ')'
} }
Utils.fn.prototype.toString = function(queryGenerator) { Utils.fn.prototype.toString = function(queryGenerator) {
...@@ -504,6 +553,7 @@ Utils.fn.prototype.toString = function(queryGenerator) { ...@@ -504,6 +553,7 @@ Utils.fn.prototype.toString = function(queryGenerator) {
} }
}).join(', ') + ')' }).join(', ') + ')'
} }
Utils.col.prototype.toString = function (queryGenerator) { Utils.col.prototype.toString = function (queryGenerator) {
return queryGenerator.quote(this.col) return queryGenerator.quote(this.col)
} }
......
...@@ -57,7 +57,8 @@ ...@@ -57,7 +57,8 @@
"chai": "~1.8.0", "chai": "~1.8.0",
"mocha": "~1.13.0", "mocha": "~1.13.0",
"chai-datetime": "~1.1.1", "chai-datetime": "~1.1.1",
"sinon": "~1.7.3" "sinon": "~1.7.3",
"mariasql": "git://github.com/sequelize/node-mariasql.git"
}, },
"keywords": [ "keywords": [
"mysql", "mysql",
......
/* 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 = { ...@@ -38,5 +38,17 @@ module.exports = {
maxConnections: process.env.SEQ_PG_POOL_MAX || process.env.SEQ_POOL_MAX || 5, 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 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
}
} }
} }
...@@ -449,7 +449,6 @@ describe(Support.getTestDialectTeaser("DAO"), function () { ...@@ -449,7 +449,6 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
it("should update read only attributes as well (updatedAt)", function(done) { it("should update read only attributes as well (updatedAt)", function(done) {
var self = this var self = this
this.timeout = 2000
this.User.create({ username: 'John Doe' }).complete(function(err, originalUser) { this.User.create({ username: 'John Doe' }).complete(function(err, originalUser) {
var originallyUpdatedAt = originalUser.updatedAt var originallyUpdatedAt = originalUser.updatedAt
...@@ -565,8 +564,6 @@ describe(Support.getTestDialectTeaser("DAO"), function () { ...@@ -565,8 +564,6 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
}) })
describe('save', function() { describe('save', function() {
this.timeout(3000) // for update timestamp
it('only updates fields in passed array', function(done) { it('only updates fields in passed array', function(done) {
var self = this var self = this
, userId = null , userId = null
...@@ -1035,7 +1032,7 @@ describe(Support.getTestDialectTeaser("DAO"), function () { ...@@ -1035,7 +1032,7 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
if (dialect === "postgres" || dialect === "postgres-native") { 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)') 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") expect(sql).to.equal("DELETE FROM `UserDestroys` WHERE `newId`='123ABC' LIMIT 1")
} else { } else {
expect(sql).to.equal("DELETE FROM `UserDestroys` WHERE `newId`='123ABC'") expect(sql).to.equal("DELETE FROM `UserDestroys` WHERE `newId`='123ABC'")
...@@ -1117,6 +1114,25 @@ describe(Support.getTestDialectTeaser("DAO"), function () { ...@@ -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() { describe('equals', function() {
......
...@@ -7,7 +7,7 @@ var chai = require('chai') ...@@ -7,7 +7,7 @@ var chai = require('chai')
chai.Assertion.includeStack = true chai.Assertion.includeStack = true
if (dialect.match(/^mysql/)) { if (Support.dialectIsMySQL()) {
describe('[MYSQL Specific] Associations', function() { describe('[MYSQL Specific] Associations', function() {
describe('many-to-many', function() { describe('many-to-many', function() {
describe('where tables have the same prefix', function() { describe('where tables have the same prefix', function() {
......
...@@ -7,10 +7,8 @@ var chai = require('chai') ...@@ -7,10 +7,8 @@ var chai = require('chai')
chai.Assertion.includeStack = true chai.Assertion.includeStack = true
if (dialect.match(/^mysql/)) { if (Support.dialectIsMySQL()) {
describe('[MYSQL Specific] Connector Manager', function() { describe('[MYSQL Specific] Connector Manager', function() {
this.timeout(10000)
it('works correctly after being idle', function(done) { it('works correctly after being idle', function(done) {
var User = this.sequelize.define('User', { username: DataTypes.STRING }) var User = this.sequelize.define('User', { username: DataTypes.STRING })
, spy = sinon.spy() , spy = sinon.spy()
......
...@@ -8,7 +8,7 @@ var chai = require('chai') ...@@ -8,7 +8,7 @@ var chai = require('chai')
chai.Assertion.includeStack = true chai.Assertion.includeStack = true
if (dialect.match(/^mysql/)) { if (Support.dialectIsMySQL()) {
describe("[MYSQL Specific] DAOFactory", function () { describe("[MYSQL Specific] DAOFactory", function () {
describe('constructor', function() { describe('constructor', function() {
it("handles extended attributes (unique)", function(done) { it("handles extended attributes (unique)", function(done) {
......
...@@ -9,7 +9,7 @@ var chai = require('chai') ...@@ -9,7 +9,7 @@ var chai = require('chai')
chai.Assertion.includeStack = true chai.Assertion.includeStack = true
if (dialect.match(/^mysql/)) { if (Support.dialectIsMySQL()) {
describe("[MYSQL Specific] QueryGenerator", function () { describe("[MYSQL Specific] QueryGenerator", function () {
var suites = { var suites = {
attributesToSQL: [ attributesToSQL: [
......
...@@ -18,7 +18,7 @@ describe(Support.getTestDialectTeaser("QueryGenerators"), function () { ...@@ -18,7 +18,7 @@ describe(Support.getTestDialectTeaser("QueryGenerators"), function () {
User.sync({ force: true }).success(function() { User.sync({ force: true }).success(function() {
var sql = '' 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\''; 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") { else if (dialect === "postgres" || dialect === "postgres-native") {
......
...@@ -14,7 +14,7 @@ chai.Assertion.includeStack = true ...@@ -14,7 +14,7 @@ chai.Assertion.includeStack = true
var qq = function(str) { var qq = function(str) {
if (dialect == 'postgres' || dialect == 'sqlite') { if (dialect == 'postgres' || dialect == 'sqlite') {
return '"' + str + '"' return '"' + str + '"'
} else if (dialect == 'mysql') { } else if (Support.dialectIsMySQL()) {
return '`' + str + '`' return '`' + str + '`'
} else { } else {
return str return str
...@@ -148,7 +148,7 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () { ...@@ -148,7 +148,7 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () {
}) })
}) })
if (dialect == 'mysql') { if (Support.dialectIsMySQL()) {
it('executes stored procedures', function(done) { it('executes stored procedures', function(done) {
var self = this var self = this
self.sequelize.query(this.insertQuery).success(function() { self.sequelize.query(this.insertQuery).success(function() {
......
...@@ -91,6 +91,10 @@ if (dialect === 'sqlite') { ...@@ -91,6 +91,10 @@ if (dialect === 'sqlite') {
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255));" 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)'}], 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));" expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` ENUM(\"A\", \"B\", \"C\"), `name` VARCHAR(255));"
}, },
......
...@@ -101,6 +101,19 @@ var Support = { ...@@ -101,6 +101,19 @@ var Support = {
return envDialect 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) { getTestDialectTeaser: function(moduleName) {
var dialect = this.getTestDialect() var dialect = this.getTestDialect()
...@@ -128,6 +141,7 @@ before(function(done) { ...@@ -128,6 +141,7 @@ before(function(done) {
beforeEach(function(done) { beforeEach(function(done) {
this.sequelize = sequelize this.sequelize = sequelize
Support.clearDatabase(this.sequelize, function() { Support.clearDatabase(this.sequelize, function() {
done() done()
}) })
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!