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

Commit 7b07cd00 by Jan Aagaard Meier

Merge latest master

2 parents 2c3cfabb d11da684
......@@ -10,6 +10,8 @@ notifications:
- sascha@depold.com
hipchat:
- 40e8850aaba9854ac4c9963bd33f8b@253477
irc:
- "chat.freenode.net#sequelizejs"
env:
- DB=mysql DIALECT=mysql
......@@ -22,3 +24,8 @@ language: node_js
node_js:
- "0.8"
- "0.10"
branches:
only:
- master
- milestones/2.0.0
......@@ -32,6 +32,14 @@
- [BUG] Default schemas should now be utilized when describing tables [#812](https://github.com/sequelize/sequelize/pull/812). thanks to durango
- [BUG] Fixed eager loading for many-to-many associations. [#834](https://github.com/sequelize/sequelize/pull/834). thanks to lemon-tree
- [BUG] allowNull: true enums can now be null [#857](https://github.com/sequelize/sequelize/pull/857). thanks to durango
- [BUG] Fixes Postgres' ability to search within arrays. [#879](https://github.com/sequelize/sequelize/pull/879). thanks to durango
- [BUG] Find and finAll would modify the options objects, now the objects are cloned at the start of the method [#884](https://github.com/sequelize/sequelize/pull/884) thanks to janmeier. Improved in [#899](https://github.com/sequelize/sequelize/pull/899) thanks to hackwaly
- [BUG] Add support for typed arrays in SqlString.escape and SqlString.arrayToList [#891](https://github.com/sequelize/sequelize/pull/891). thanks to LJ1102
- [BUG] Postgres requires empty array to be explicitly cast on update [#890](https://github.com/sequelize/sequelize/pull/890). thanks to robraux
- [BUG] Added tests & bugfixes for DAO-Factory.update and array of values in where clause [#880](https://github.com/sequelize/sequelize/pull/880). thanks to domasx2
- [BUG] sqlite no longer leaks a global `db` variable [#900](https://github.com/sequelize/sequelize/pull/900). thanks to xming
- [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
- [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
......@@ -60,8 +68,11 @@
- [FEATURE] Blob support. janmeier
- [FEATURE] We can now define our own custom timestamp columns [#856](https://github.com/sequelize/sequelize/pull/856). thanks to durango
- [FEATURE] Scopes. [#748](https://github.com/sequelize/sequelize/pull/748). durango
- [FEATURE] Model#find() / Model#findAll() is now working with strings. [#855](https://github.com/sequelize/sequelize/pull/855). Thanks to whito.
- [FEATURE] Model#find() / Model#findAll() is now working with strings. [#855](https://github.com/sequelize/sequelize/pull/855). Thanks to whito.
- [FEATURE] Shortcut method for getting a defined model. [#868](https://github.com/sequelize/sequelize/pull/868). Thanks to jwilm.
- [FEATURE] Added Sequelize.fn() and Sequelize.col() to properly call columns and functions within Sequelize. [#882](https://github.com/sequelize/sequelize/pull/882). thanks to janmeier
- [FEATURE] Sequelize.import supports relative paths. [#901](https://github.com/sequelize/sequelize/pull/901). thanks to accerqueira.
- [FEATURE] Sequelize.import can now handle functions. [#911](https://github.com/sequelize/sequelize/pull/911). Thanks to davidrivera.
- [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
......
......@@ -26,8 +26,9 @@ module.exports = (function() {
this.isSelfAssociation ? (this.options.as || this.target.tableName) : this.target.tableName
)
}
this.options.tableName = this.associationAccessor = this.combinedName = combinedTableName
this.options.tableName = this.combinedName = (this.options.joinTableName || combinedTableName)
this.associationAccessor = this.options.as || this.combinedName
var as = (this.options.as || Utils.pluralize(this.target.tableName, this.target.options.language))
this.accessors = {
......
......@@ -324,8 +324,8 @@ module.exports = (function() {
DAOFactory.prototype.findAll = function(options, queryOptions) {
var hasJoin = false
var options = Utils._.clone(options)
options = optClone(options)
if (typeof options === 'object') {
if (options.hasOwnProperty('include')) {
hasJoin = true
......@@ -656,7 +656,7 @@ module.exports = (function() {
*/
DAOFactory.prototype.update = function(attrValueHash, where, options) {
options = options || {}
options.validate = options.validate || true
options.validate = options.validate === undefined ? true : Boolean(options.validate)
if(this.options.timestamps) {
var attr = Utils._.underscoredIf(this.options.updatedAt, this.options.underscored)
......@@ -664,7 +664,10 @@ module.exports = (function() {
}
if (options.validate === true) {
var validate = this.build(attrValueHash).validate()
var build = this.build(attrValueHash)
, attrKeys = Object.keys(attrValueHash)
, validate = build.validate({skip: Object.keys(build.dataValues).filter(function(val) { return attrKeys.indexOf(val) !== -1 })})
if (validate !== null && Object.keys(validate).length > 0) {
return new Utils.CustomEventEmitter(function(emitter) {
emitter.emit('error', validate)
......@@ -806,6 +809,13 @@ module.exports = (function() {
return attributes
}
var optClone = function (options) {
return Utils._.cloneDeep(options, function (elem) {
// The DAOFactories used for include are pass by ref, so don't clone them. Otherwise return undefined, meaning, 'handle this lodash'
return elem instanceof DAOFactory ? elem : undefined
})
}
Utils._.extend(DAOFactory.prototype, require("./associations/mixin"))
return DAOFactory
......
......@@ -36,7 +36,7 @@ var validateModel = function() {
}
var validateAttributes = function() {
var self = this
var self = this
, errors = {}
// for each field and value
......
var Utils = require("../../utils")
module.exports = (function() {
var QueryGenerator = {
addSchema: function(opts) {
......@@ -268,7 +270,35 @@ module.exports = (function() {
Globally disable foreign key constraints
*/
disableForeignKeyConstraintsQuery: function() {
throwMethodUndefined('disableForeignKeyConstraintsQuery')
throwMethodUndefined('disableForeignKeyConstraintsQuery')
},
/*
Quote an object based on its type. This is a more general version of quoteIdentifiers
Strings: should proxy to quoteIdentifiers
Arrays: First argument should be qouted, second argument should be append without quoting
Objects:
* If raw is set, that value should be returned verbatim, without quoting
* If fn is set, the string should start with the value of fn, starting paren, followed by
the values of cols (which is assumed to be an array), quoted and joined with ', ',
unless they are themselves objects
* If direction is set, should be prepended
Currently this function is only used for ordering / grouping columns, but it could
potentially also be used for other places where we want to be able to call SQL functions (e.g. as default values)
*/
quote: function(obj, force) {
if (Utils._.isString(obj)) {
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) {
return obj.toString(this)
} else if (Utils._.isObject(obj) && 'raw' in obj) {
return obj.raw
} else {
throw new Error('Unknown structure passed to order / group: ' + JSON.stringify(obj))
}
},
/*
......
......@@ -228,11 +228,12 @@ module.exports = (function() {
}
if (options.group) {
options.group = Array.isArray(options.group) ? options.group.map(function(t) { return this.quoteIdentifiers(t)}.bind(this)).join(', ') : this.quoteIdentifiers(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
}
......@@ -612,5 +613,5 @@ module.exports = (function() {
}
return Utils._.extend(Utils._.clone(require("../query-generator")), QueryGenerator)
return Utils._.extend(Utils._.clone(require("../abstract/query-generator")), QueryGenerator)
})()
......@@ -19,9 +19,9 @@ module.exports = (function() {
// set pooling parameters if specified
if (this.pooling) {
this.pg.defaults.poolSize = this.config.pool.maxConnections
this.pg.defaults.poolIdleTimeout = this.config.pool.maxIdleTime
this.pg.defaults.reapIntervalMillis = this.config.pool.reapInterval || 1000
this.pg.defaults.poolSize = this.config.pool.maxConnections || 10
this.pg.defaults.poolIdleTimeout = this.config.pool.maxIdleTime || 30000
this.pg.defaults.reapIntervalMillis = this.config.pool.reapInterval || 1000
}
this.disconnectTimeoutId = null
......@@ -142,7 +142,9 @@ module.exports = (function() {
}
if (this.client) {
this.client.end.bind(this.client)
// 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.isConnecting = false
......
......@@ -300,14 +300,12 @@ module.exports = (function() {
}
if(options.group) {
options.group = Array.isArray(options.group) ? options.group.map(function(t) { return this.quoteIdentifiers(t) }.bind(this)).join(', ') : this.quoteIdentifiers(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 = options.order.replace(/([^ ]+)(.*)/, function(m, g1, g2) {
return this.quoteIdentifiers(g1) + g2
}.bind(this))
options.order = Array.isArray(options.order) ? options.order.map(function (t) { return this.quote(t) }.bind(this)).join(', ') : options.order
query += " ORDER BY <%= order %>"
}
......@@ -382,7 +380,7 @@ module.exports = (function() {
return Utils._.template(query)(replacements)
},
updateQuery: function(tableName, attrValueHash, where, options) {
updateQuery: function(tableName, attrValueHash, where, options, attributes) {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull, options)
var query = "UPDATE <%= table %> SET <%= values %> WHERE <%= where %> RETURNING *"
......@@ -390,7 +388,7 @@ module.exports = (function() {
for (var key in attrValueHash) {
var value = attrValueHash[key]
values.push(this.quoteIdentifier(key) + "=" + this.escape(value))
values.push(this.quoteIdentifier(key) + "=" + this.escape(value, (!!attributes && !!attributes[key] ? attributes[key] : undefined)))
}
var replacements = {
......@@ -402,7 +400,7 @@ module.exports = (function() {
return Utils._.template(query)(replacements)
},
deleteQuery: function(tableName, where, options) {
deleteQuery: function(tableName, where, options, factory) {
options = options || {}
if (options.truncate === true) {
......@@ -415,6 +413,10 @@ module.exports = (function() {
primaryKeys[tableName] = primaryKeys[tableName] || [];
if (!!factory && primaryKeys[tableName].length < 1) {
primaryKeys[tableName] = Object.keys(factory.primaryKeys)
}
var query = "DELETE FROM <%= table %> WHERE <%= primaryKeys %> IN (SELECT <%= primaryKeysSelection %> FROM <%= table %> WHERE <%= where %><%= limit %>)"
var pks;
......@@ -526,7 +528,7 @@ module.exports = (function() {
if (Utils.isHash(smth)) {
smth = Utils.prependTableNameToHash(tableName, smth)
result = this.hashToWhereConditions(smth)
result = this.hashToWhereConditions(smth, factory)
} else if (typeof smth === 'number') {
var primaryKeys = !!factory ? Object.keys(factory.primaryKeys) : []
if (primaryKeys.length > 0) {
......@@ -549,7 +551,7 @@ module.exports = (function() {
return result
},
hashToWhereConditions: function(hash) {
hashToWhereConditions: function(hash, factory) {
var result = []
for (var key in hash) {
......@@ -561,9 +563,23 @@ module.exports = (function() {
if (Array.isArray(value)) {
if (value.length === 0) { value = [null] }
_value = "(" + value.map(this.escape).join(',') + ")"
result.push([_key, _value].join(" IN "))
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) {
......@@ -868,5 +884,5 @@ module.exports = (function() {
return returning;
}
return Utils._.extend(Utils._.clone(require("../query-generator")), QueryGenerator)
return Utils._.extend(Utils._.clone(require("../abstract/query-generator")), QueryGenerator)
})()
......@@ -20,34 +20,34 @@ module.exports = (function() {
Query.prototype.run = function(sql) {
this.sql = sql
var self = this
, receivedError = false
, query = this.client.query(sql)
, rows = []
if (this.options.logging !== false) {
this.options.logging('Executing: ' + this.sql)
}
var receivedError = false
, query = this.client.query(sql)
, rows = []
query.on('row', function(row) {
rows.push(row)
})
query.on('error', function(err) {
receivedError = true
this.emit('error', err, this.callee)
}.bind(this))
self.emit('error', err, self.callee)
})
query.on('end', function() {
this.emit('sql', this.sql)
self.emit('sql', self.sql)
if (receivedError) {
return
}
onSuccess.call(this, rows, sql)
}.bind(this))
onSuccess.call(self, rows, sql)
})
return this
}
......
......@@ -18,6 +18,7 @@ module.exports = (function() {
ConnectorManager.prototype.connect = function() {
var emitter = new (require('events').EventEmitter)()
, self = this
, db
this.database = db = new sqlite3.Database(self.sequelize.options.storage || ':memory:', function(err) {
if (err) {
......
......@@ -3,7 +3,7 @@ var Utils = require("../../utils")
, SqlString = require("../../sql-string")
var MySqlQueryGenerator = Utils._.extend(
Utils._.clone(require("../query-generator")),
Utils._.clone(require("../abstract/query-generator")),
Utils._.clone(require("../mysql/query-generator"))
)
......@@ -211,11 +211,12 @@ module.exports = (function() {
}
if (options.group) {
options.group = Array.isArray(options.group) ? options.group.map(function(t) { return this.quoteIdentifiers(t)}.bind(this)).join(', ') : qa(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
}
......
......@@ -417,7 +417,7 @@ module.exports = (function() {
QueryInterface.prototype.update = function(dao, tableName, values, identifier, options) {
var self = this
, restrict = false
, sql = self.QueryGenerator.updateQuery(tableName, values, identifier, options)
, sql = self.QueryGenerator.updateQuery(tableName, values, identifier, options, dao.daoFactory.rawAttributes)
// Check for a restrict field
if (!!dao.daoFactory && !!dao.daoFactory.associations) {
......@@ -481,7 +481,7 @@ module.exports = (function() {
QueryInterface.prototype.delete = function(dao, tableName, identifier) {
var self = this
, restrict = false
, sql = self.QueryGenerator.deleteQuery(tableName, identifier)
, sql = self.QueryGenerator.deleteQuery(tableName, identifier, null, dao.daoFactory)
// Check for a restrict field
if (!!dao.daoFactory && !!dao.daoFactory.associations) {
......@@ -581,10 +581,10 @@ module.exports = (function() {
qry
.success(function(data) {
var result = data[attributeSelector]
var result = data ? data[attributeSelector] : null
if (options && options.parseInt) {
result = parseInt(result)
result = parseInt(result, 10)
}
if (options && options.parseFloat) {
......
var url = require("url")
, Path = require("path")
, Utils = require("./utils")
, DAOFactory = require("./dao-factory")
, DataTypes = require('./data-types')
......@@ -215,8 +216,18 @@ module.exports = (function() {
}
Sequelize.prototype.import = function(path) {
// is it a relative path?
if (url.parse(path).pathname.indexOf('/') !== 0) {
// make path relative to the caller
var callerFilename = Utils.stack()[1].getFileName()
, callerMatch = callerFilename.match(/(.+\/).+?$/)
, callerPath = callerMatch[1]
path = Path.resolve(callerPath, path)
}
if (!this.importCache[path]) {
var defineCall = require(path)
var defineCall = (arguments.length > 1 ? arguments[1] : require(path))
this.importCache[path] = defineCall(this, DataTypes)
}
......@@ -318,5 +329,13 @@ module.exports = (function() {
}).run()
}
Sequelize.prototype.fn = function (fn) {
return new Utils.fn(fn, Array.prototype.slice.call(arguments, 1))
}
Sequelize.prototype.col = function (col) {
return new Utils.col(col)
}
return Sequelize
})()
var moment = require("moment")
, isArrayBufferView
, SqlString = exports;
if (typeof ArrayBufferView === 'function') {
isArrayBufferView = function(object) { return object && (object instanceof ArrayBufferView) }
} else {
var arrayBufferViews = [
Int8Array, Uint8Array, Int16Array, Uint16Array,
Int32Array, Uint32Array, Float32Array, Float64Array
]
isArrayBufferView = function(object) {
for (var i=0; i<8; i++) {
if (object instanceof arrayBufferViews[i]) {
return true
}
}
return false
};
}
SqlString.escapeId = function (val, forbidQualified) {
if (forbidQualified) {
return '`' + val.replace(/`/g, '``') + '`';
return '`' + val.replace(/`/g, '``') + '`'
}
return '`' + val.replace(/`/g, '``').replace(/\./g, '`.`') + '`';
};
return '`' + val.replace(/`/g, '``').replace(/\./g, '`.`') + '`'
}
SqlString.escape = function(val, stringifyObjects, timeZone, dialect, field) {
if (arguments.length === 1 && typeof arguments[0] === "object") {
......@@ -37,29 +56,28 @@ SqlString.escape = function(val, stringifyObjects, timeZone, dialect, field) {
}
if (val instanceof Date) {
val = SqlString.dateToString(val, timeZone || "Z", dialect);
val = SqlString.dateToString(val, timeZone || "Z", dialect)
}
if (Buffer.isBuffer(val)) {
return SqlString.bufferToString(val, dialect);
return SqlString.bufferToString(val, dialect)
}
if (Array.isArray(val)) {
return SqlString.arrayToList(val, timeZone, dialect, field);
if (Array.isArray(val) || isArrayBufferView(val)) {
return SqlString.arrayToList(val, timeZone, dialect, field)
}
if (typeof val === 'object') {
if (stringifyObjects) {
val = val.toString();
val = val.toString()
} else {
return SqlString.objectToValues(val, timeZone);
return SqlString.objectToValues(val, timeZone)
}
}
if (dialect === 'postgres' || dialect === 'sqlite') {
// http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS
// http://stackoverflow.com/q/603572/130598
val = val.replace(/'/g, "''");
val = val.replace(/'/g, "''")
} else {
val = val.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) {
switch(s) {
......@@ -71,115 +89,134 @@ SqlString.escape = function(val, stringifyObjects, timeZone, dialect, field) {
case "\x1a": return "\\Z";
default: return "\\"+s;
}
});
})
}
return "'"+val+"'";
};
return "'"+val+"'"
}
SqlString.arrayToList = function(array, timeZone, dialect, field) {
if (dialect === 'postgres') {
var ret = 'ARRAY[' + array.map(function(v) {
return SqlString.escape(v, true, timeZone, dialect, field);
}).join(',') + ']';
if (array.map) {
var valstr = array.map(function(v) {
return SqlString.escape(v, true, timeZone, dialect, field)
}).join(',')
} else {
var valstr = ""
for (var i = 0; i < array.length; i++) {
valstr += SqlString.escape(array[i], true, timeZone, dialect, field) + ','
}
valstr = valstr.slice(0,-1)
}
var ret = 'ARRAY[' + valstr + ']'
if (!!field && !!field.type) {
ret += '::' + field.type.replace(/\(\d+\)/g, '');
ret += '::' + field.type.replace(/\(\d+\)/g, '')
}
return ret;
return ret
} else {
return array.map(function(v) {
if (Array.isArray(v))
return '(' + SqlString.arrayToList(v, timeZone, dialect) + ')';
return SqlString.escape(v, true, timeZone, dialect);
}).join(', ');
if (array.map) {
return array.map(function(v) {
if (Array.isArray(v)) {
return '(' + SqlString.arrayToList(v, timeZone, dialect) + ')'
}
return SqlString.escape(v, true, timeZone, dialect)
}).join(', ')
} else {
var valstr = ""
for (var i = 0; i < array.length; i++) {
valstr += SqlString.escape(array[i], true, timeZone, dialect) + ', '
}
return valstr.slice(0, -2)
}
}
};
}
SqlString.format = function(sql, values, timeZone, dialect) {
values = [].concat(values);
return sql.replace(/\?/g, function(match) {
if (!values.length) {
return match;
return match
}
return SqlString.escape(values.shift(), false, timeZone, dialect);
});
};
return SqlString.escape(values.shift(), false, timeZone, dialect)
})
}
SqlString.formatNamedParameters = function(sql, values, timeZone, dialect) {
return sql.replace(/\:(\w+)/g, function (value, key) {
if (values.hasOwnProperty(key)) {
return SqlString.escape(values[key], false, timeZone, dialect);
}
else {
throw new Error('Named parameter "' + value + '" has no value in the given object.');
return SqlString.escape(values[key], false, timeZone, dialect)
} else {
throw new Error('Named parameter "' + value + '" has no value in the given object.')
}
});
};
})
}
SqlString.dateToString = function(date, timeZone, dialect) {
var dt = new Date(date);
var dt = new Date(date)
// TODO: Ideally all dialects would work a bit more like this
if (dialect === "postgres") {
return moment(dt).zone('+00:00').format("YYYY-MM-DD HH:mm:ss.SSS Z");
return moment(dt).zone('+00:00').format("YYYY-MM-DD HH:mm:ss.SSS Z")
}
if (timeZone !== 'local') {
var tz = convertTimezone(timeZone);
var tz = convertTimezone(timeZone)
dt.setTime(dt.getTime() + (dt.getTimezoneOffset() * 60000));
dt.setTime(dt.getTime() + (dt.getTimezoneOffset() * 60000))
if (tz !== false) {
dt.setTime(dt.getTime() + (tz * 60000));
dt.setTime(dt.getTime() + (tz * 60000))
}
}
return moment(dt).format("YYYY-MM-DD HH:mm:ss");
};
return moment(dt).format("YYYY-MM-DD HH:mm:ss")
}
SqlString.bufferToString = function(buffer, dialect) {
var hex = '';
var hex = ''
try {
hex = buffer.toString('hex');
hex = buffer.toString('hex')
} catch (err) {
// node v0.4.x does not support hex / throws unknown encoding error
for (var i = 0; i < buffer.length; i++) {
var byte = buffer[i];
hex += zeroPad(byte.toString(16));
var byte = buffer[i]
hex += zeroPad(byte.toString(16))
}
}
if (dialect === 'postgres') {
// bytea hex format http://www.postgresql.org/docs/current/static/datatype-binary.html
return "E'\\\\x" + hex+ "'";
return "E'\\\\x" + hex+ "'"
}
return "X'" + hex+ "'";
};
return "X'" + hex+ "'"
}
SqlString.objectToValues = function(object, timeZone) {
var values = [];
var values = []
for (var key in object) {
var value = object[key];
var value = object[key]
if(typeof value === 'function') {
continue;
}
values.push(this.escapeId(key) + ' = ' + SqlString.escape(value, true, timeZone));
values.push(this.escapeId(key) + ' = ' + SqlString.escape(value, true, timeZone))
}
return values.join(', ');
};
return values.join(', ')
}
function zeroPad(number) {
return (number < 10) ? '0' + number : number;
return (number < 10) ? '0' + number : number
}
function convertTimezone(tz) {
if (tz == "Z") return 0;
if (tz == "Z") {
return 0
}
var m = tz.match(/([\+\-\s])(\d\d):?(\d\d)?/);
var m = tz.match(/([\+\-\s])(\d\d):?(\d\d)?/)
if (m) {
return (m[1] == '-' ? -1 : 1) * (parseInt(m[2], 10) + ((m[3] ? parseInt(m[3], 10) : 0) / 60)) * 60;
return (m[1] == '-' ? -1 : 1) * (parseInt(m[2], 10) + ((m[3] ? parseInt(m[3], 10) : 0) / 60)) * 60
}
return false;
return false
}
......@@ -55,7 +55,8 @@ var Utils = module.exports = {
},
format: function(arr, dialect) {
var timeZone = null;
return SqlString.format(arr.shift(), arr, timeZone, dialect)
// Make a clone of the array beacuse format modifies the passed args
return SqlString.format(arr[0], arr.slice(1), timeZone, dialect)
},
formatNamedParameters: function(sql, parameters, dialect) {
var timeZone = null;
......@@ -451,6 +452,18 @@ var Utils = module.exports = {
return subClass;
},
stack: function() {
var orig = Error.prepareStackTrace;
Error.prepareStackTrace = function(_, stack){ return stack; };
var err = new Error();
Error.captureStackTrace(err, arguments.callee);
var stack = err.stack;
Error.prepareStackTrace = orig;
return stack;
},
now: function(dialect) {
var now = new Date()
if(dialect != "postgres") now.setMilliseconds(0)
......@@ -468,9 +481,33 @@ var Utils = module.exports = {
removeTicks: function(s, tickChar) {
tickChar = tickChar || Utils.TICK_CHAR
return s.replace(new RegExp(tickChar, 'g'), "")
},
/*
* Utility functions for representing SQL functions, and columns that should be escaped.
* Please do not use these functions directly, use Sequelize.fn and Sequelize.col instead.
*/
fn: function (fn, args) {
this.fn = fn
this.args = args
},
col: function (col) {
this.col = col
}
}
Utils.fn.prototype.toString = function(queryGenerator) {
return this.fn + '(' + this.args.map(function (arg) {
if (arg instanceof Utils.fn || arg instanceof Utils.col) {
return arg.toString(queryGenerator)
} else {
return queryGenerator.escape(arg)
}
}).join(', ') + ')'
}
Utils.col.prototype.toString = function (queryGenerator) {
return queryGenerator.quote(this.col)
}
Utils.CustomEventEmitter = require(__dirname + "/emitters/custom-event-emitter")
Utils.QueryChainer = require(__dirname + "/query-chainer")
Utils.Lingo = require("lingo")
......@@ -36,11 +36,11 @@
"url": "https://github.com/sequelize/sequelize/issues"
},
"dependencies": {
"lodash": "~1.3.1",
"lodash": "~2.0.0",
"underscore.string": "~2.3.0",
"lingo": "~0.0.5",
"validator": "~1.5.0",
"moment": "~2.1.0",
"moment": "~2.2.1",
"commander": "~2.0.0",
"dottie": "0.0.8-0",
"toposort-class": "~0.2.0",
......@@ -51,11 +51,11 @@
"devDependencies": {
"sqlite3": "~2.1.12",
"mysql": "~2.0.0-alpha8",
"pg": "~2.3.1",
"pg": "~2.6.0",
"watchr": "~2.4.3",
"yuidocjs": "~0.3.36",
"chai": "~1.7.2",
"mocha": "~1.12.0",
"mocha": "~1.13.0",
"chai-datetime": "~1.1.1",
"sinon": "~1.7.3"
},
......@@ -77,4 +77,4 @@
"node": ">=0.4.6"
},
"license": "MIT"
}
\ No newline at end of file
}
/* jshint camelcase: false */
/* jshint camelcase: false, expr: true */
var chai = require('chai')
, expect = chai.expect
, Support = require(__dirname + '/../support')
......@@ -7,6 +7,19 @@ var chai = require('chai')
chai.Assertion.includeStack = true
describe(Support.getTestDialectTeaser("BelongsTo"), function() {
describe("Model.associations", function () {
it("should store all assocations when associting to the same table multiple times", function () {
var User = this.sequelize.define('User', {})
, Group = this.sequelize.define('Group', {})
Group.belongsTo(User)
Group.belongsTo(User, { foreignKey: 'primaryGroupId', as: 'primaryUsers' })
Group.belongsTo(User, { foreignKey: 'secondaryGroupId', as: 'secondaryUsers' })
expect(Object.keys(Group.associations)).to.deep.equal(['Users', 'primaryUsers', 'secondaryUsers'])
})
})
describe('setAssociation', function() {
it('can set the association with declared primary keys...', function(done) {
var User = this.sequelize.define('UserXYZ', { user_id: {type: DataTypes.INTEGER, primaryKey: true }, username: DataTypes.STRING })
......
/* jshint camelcase: false */
/* jshint camelcase: false, expr: true */
var chai = require('chai')
, expect = chai.expect
, Support = require(__dirname + '/../support')
......@@ -11,6 +11,19 @@ var chai = require('chai')
chai.Assertion.includeStack = true
describe(Support.getTestDialectTeaser("HasMany"), function() {
describe("Model.associations", function () {
it("should store all assocations when associting to the same table multiple times", function () {
var User = this.sequelize.define('User', {})
, Group = this.sequelize.define('Group', {})
Group.hasMany(User)
Group.hasMany(User, { foreignKey: 'primaryGroupId', as: 'primaryUsers' })
Group.hasMany(User, { foreignKey: 'secondaryGroupId', as: 'secondaryUsers' })
expect(Object.keys(Group.associations)).to.deep.equal(['GroupsUsers', 'primaryUsers', 'secondaryUsers'])
})
})
describe('(1:N)', function() {
describe('hasSingle', function() {
beforeEach(function(done) {
......
/* jshint camelcase: false */
/* jshint camelcase: false, expr: true */
var chai = require('chai')
, expect = chai.expect
, Support = require(__dirname + '/../support')
......@@ -7,6 +7,19 @@ var chai = require('chai')
chai.Assertion.includeStack = true
describe(Support.getTestDialectTeaser("HasOne"), function() {
describe("Model.associations", function () {
it("should store all assocations when associting to the same table multiple times", function () {
var User = this.sequelize.define('User', {})
, Group = this.sequelize.define('Group', {})
Group.hasOne(User)
Group.hasOne(User, { foreignKey: 'primaryGroupId', as: 'primaryUsers' })
Group.hasOne(User, { foreignKey: 'secondaryGroupId', as: 'secondaryUsers' })
expect(Object.keys(Group.associations)).to.deep.equal(['Users', 'primaryUsers', 'secondaryUsers'])
})
})
describe('getAssocation', function() {
it('should be able to handle a where object that\'s a first class citizen.', function(done) {
var User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING })
......
......@@ -333,7 +333,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
describe('create', function() {
it("casts empty arrays correctly for postgresql", function(done) {
it("casts empty arrays correctly for postgresql insert", function(done) {
if (dialect !== "postgres" && dialect !== "postgresql-native") {
expect('').to.equal('')
return done()
......@@ -352,6 +352,30 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
})
it("casts empty array correct for postgres update", function(done) {
if (dialect !== "postgres" && dialect !== "postgresql-native") {
expect('').to.equal('')
return done()
}
var User = this.sequelize.define('UserWithArray', {
myvals: { type: Sequelize.ARRAY(Sequelize.INTEGER) },
mystr: { type: Sequelize.ARRAY(Sequelize.STRING) }
})
User.sync({force: true}).success(function() {
User.create({myvals: [1,2,3,4], mystr: ["One", "Two", "Three", "Four"]}).on('success', function(user){
user.myvals = []
user.mystr = []
user.save().on('sql', function(sql) {
expect(sql.indexOf('ARRAY[]::INTEGER[]')).to.be.above(-1)
expect(sql.indexOf('ARRAY[]::VARCHAR[]')).to.be.above(-1)
done()
})
})
})
})
it("doesn't allow duplicated records with unique:true", function(done) {
var User = this.sequelize.define('UserWithUniqueUsername', {
......@@ -1064,6 +1088,27 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
it('should be able to find rows where attribute is in a list of values', function (done) {
this.User.findAll({
where: {
username: ['boo', 'boo2']
}
}).success(function(users){
expect(users).to.have.length(2);
done()
});
})
it('should not break when trying to find rows using an array of primary keys', function (done) {
this.User.findAll({
where: {
id: [1, 2, 3]
}
}).success(function(users){
done();
});
})
it('should be able to find a row using like', function(done) {
this.User.findAll({
where: {
......@@ -1426,14 +1471,14 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
var self = this
this.User.create({username: 'barfooz'}).success(function(user) {
self.UserPrimary = self.sequelize.define('UserPrimary', {
specialKey: {
specialkey: {
type: DataTypes.STRING,
primaryKey: true
}
})
self.UserPrimary.sync({force: true}).success(function() {
self.UserPrimary.create({specialKey: 'a string'}).success(function() {
self.UserPrimary.create({specialkey: 'a string'}).success(function() {
self.user = user
done()
})
......@@ -1441,9 +1486,18 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
it('does not modify the passed arguments', function (done) {
var options = { where: ['specialkey = ?', 'awesome']}
this.UserPrimary.find(options).success(function(user) {
expect(options).to.deep.equal({ where: ['specialkey = ?', 'awesome']})
done()
})
})
it('doesn\'t throw an error when entering in a non integer value for a specified primary field', function(done) {
this.UserPrimary.find('a string').success(function(user) {
expect(user.specialKey).to.equal('a string')
expect(user.specialkey).to.equal('a string')
done()
})
})
......@@ -1800,17 +1854,17 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
it('eager loads with non-id primary keys', function(done) {
var self = this
self.User = self.sequelize.define('UserPKeagerbelong', {
username: {
self.User = self.sequelize.define('UserPKeagerbelong', {
username: {
type: Sequelize.STRING,
primaryKey: true
}
}
})
self.Group = self.sequelize.define('GroupPKeagerbelong', {
name: {
self.Group = self.sequelize.define('GroupPKeagerbelong', {
name: {
type: Sequelize.STRING,
primaryKey: true
}
}
})
self.User.belongsTo(self.Group)
......@@ -1833,6 +1887,24 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
})
it('allows mulitple assocations of the same model with different alias', function (done) {
var self = this
this.Worker.belongsTo(this.Task, { as: 'ToDo' })
this.Worker.belongsTo(this.Task, { as: 'DoTo' })
this.init(function () {
self.Worker.find({
include: [
{ model: self.Task, as: 'ToDo' },
{ model: self.Task, as: 'DoTo' }
]
}).success(function () {
// Just being able to include both shows that this test works, so no assertions needed
done()
})
})
})
})
describe('hasOne', function() {
......@@ -1869,17 +1941,17 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
it('eager loads with non-id primary keys', function(done) {
var self = this
self.User = self.sequelize.define('UserPKeagerone', {
username: {
self.User = self.sequelize.define('UserPKeagerone', {
username: {
type: Sequelize.STRING,
primaryKey: true
}
}
})
self.Group = self.sequelize.define('GroupPKeagerone', {
name: {
self.Group = self.sequelize.define('GroupPKeagerone', {
name: {
type: Sequelize.STRING,
primaryKey: true
}
}
})
self.Group.hasOne(self.User)
......@@ -1954,6 +2026,23 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
done()
})
})
it('allows mulitple assocations of the same model with different alias', function (done) {
var self = this
this.Worker.hasOne(this.Task, { as: 'DoTo' })
this.init(function () {
self.Worker.find({
include: [
{ model: self.Task, as: 'ToDo' },
{ model: self.Task, as: 'DoTo' }
]
}).success(function () {
// Just being able to include both shows that this test works, so no assertions needed
done()
})
})
})
})
})
......@@ -1991,17 +2080,17 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
it('eager loads with non-id primary keys', function(done) {
var self = this
self.User = self.sequelize.define('UserPKeagerone', {
username: {
self.User = self.sequelize.define('UserPKeagerone', {
username: {
type: Sequelize.STRING,
primaryKey: true
}
}
})
self.Group = self.sequelize.define('GroupPKeagerone', {
name: {
self.Group = self.sequelize.define('GroupPKeagerone', {
name: {
type: Sequelize.STRING,
primaryKey: true
}
}
})
self.Group.hasMany(self.User)
self.User.hasMany(self.Group)
......@@ -2023,7 +2112,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
expect(someUser.groupPKeagerones[0].name).to.equal('people')
done()
})
})
})
})
})
})
......@@ -2080,6 +2169,23 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
done()
})
})
it('allows mulitple assocations of the same model with different alias', function (done) {
var self = this
this.Worker.hasMany(this.Task, { as: 'DoTos' })
this.init(function () {
self.Worker.find({
include: [
{ model: self.Task, as: 'ToDos' },
{ model: self.Task, as: 'DoTos' }
]
}).success(function () {
// Just being able to include both shows that this test works, so no assertions needed
done()
})
})
})
})
})
})
......@@ -2458,6 +2564,15 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
it('does not modify the passed arguments', function (done) {
var options = { where: ['username = ?', 'awesome']}
this.User.findAll(options).success(function(user) {
expect(options).to.deep.equal({ where: ['username = ?', 'awesome']})
done()
})
})
it("finds all users matching the passed conditions", function(done) {
this.User.findAll({where: "id != " + this.users[1].id}).success(function(users) {
expect(users.length).to.equal(1)
......@@ -2492,7 +2607,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
it("sorts the results via a date column", function(done) {
var self = this
self.User.create({username: 'user3', data: 'bar', theDate: moment().add('hours', 2).toDate()}).success(function(){
self.User.findAll({ order: 'theDate DESC' }).success(function(users) {
self.User.findAll({ order: [['theDate', 'DESC']] }).success(function(users) {
expect(users[0].id).to.be.above(users[2].id)
done()
})
......@@ -2689,6 +2804,15 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
it('does not modify the passed arguments', function (done) {
var options = { where: ['username = ?', 'user1']}
this.User.count(options).success(function(count) {
expect(options).to.deep.equal({ where: ['username = ?', 'user1']})
done()
})
})
it('allows sql logging', function(done) {
this.User.count().on('sql', function(sql) {
expect(sql).to.exist
......
......@@ -1000,6 +1000,34 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
})
})
it('destroys a record with a primary key of something other than id', function(done) {
var UserDestroy = this.sequelize.define('UserDestroy', {
newId: {
type: DataTypes.STRING,
primaryKey: true
},
email: DataTypes.STRING
})
UserDestroy.sync().success(function() {
UserDestroy.create({newId: '123ABC', email: 'hello'}).success(function() {
UserDestroy.find({where: {email: 'hello'}}).success(function(user) {
user.destroy().on('sql', function(sql) {
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") {
expect(sql).to.equal("DELETE FROM `UserDestroys` WHERE `newId`='123ABC' LIMIT 1")
} else {
expect(sql).to.equal("DELETE FROM `UserDestroys` WHERE `newId`='123ABC'")
}
done()
})
})
})
})
})
it("sets deletedAt property to a specific date when deleting an instance", function(done) {
var self = this
this.ParanoidUser.create({ username: 'fnord' }).success(function() {
......
......@@ -254,6 +254,35 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() {
}
describe('#update', function() {
it('should allow us to update specific columns without tripping the validations', function(done) {
var User = this.sequelize.define('model', {
username: Sequelize.STRING,
email: {
type: Sequelize.STRING,
allowNull: false,
validate: {
isEmail: {
msg: 'You must enter a valid email address'
}
}
}
})
User.sync({ force: true }).success(function() {
User.create({ username: 'bob', email: 'hello@world.com' }).success(function(user) {
User
.update({ username: 'toni' }, { id: user.id })
.error(function(err) { console.log(err) })
.success(function() {
User.find(1).success(function(user) {
expect(user.username).to.equal('toni')
done()
})
})
})
})
})
it('should be able to emit an error upon updating when a validation has failed from an instance', function(done) {
var Model = this.sequelize.define('model', {
name: {
......
......@@ -163,20 +163,77 @@ if (dialect.match(/^mysql/)) {
expectation: "SELECT * FROM `myTable` ORDER BY id DESC;",
context: QueryGenerator
}, {
arguments: ['myTable', {order: ["id"]}],
expectation: "SELECT * FROM `myTable` ORDER BY `id`;",
context: QueryGenerator
}, {
arguments: ['myTable', {order: ["myTable.id"]}],
expectation: "SELECT * FROM `myTable` ORDER BY `myTable`.`id`;",
context: QueryGenerator
}, {
arguments: ['myTable', {order: [["id", 'DESC']]}],
expectation: "SELECT * FROM `myTable` ORDER BY `id` DESC;",
context: QueryGenerator
}, {
title: 'raw arguments are neither quoted nor escaped',
arguments: ['myTable', {order: [[{raw: 'f1(f2(id))'}, 'DESC']]}],
expectation: "SELECT * FROM `myTable` ORDER BY f1(f2(id)) DESC;",
context: QueryGenerator
}, {
title: 'functions can take functions as arguments',
arguments: ['myTable', function (sequelize) {
return {
order: [[sequelize.fn('f1', sequelize.fn('f2', sequelize.col('id'))), 'DESC']]
}
}],
expectation: "SELECT * FROM `myTable` ORDER BY f1(f2(`id`)) DESC;",
context: QueryGenerator,
needsSequelize: true
}, {
title: 'functions can take all types as arguments',
arguments: ['myTable', function (sequelize) {
return {
order: [
[sequelize.fn('f1', sequelize.col('myTable.id')), 'DESC'],
[sequelize.fn('f2', 12, 'lalala', new Date(Date.UTC(2011, 2, 27, 10, 1, 55))), 'ASC']
]
}
}],
expectation: "SELECT * FROM `myTable` ORDER BY f1(`myTable`.`id`) DESC, f2(12, 'lalala', '2011-03-27 10:01:55') ASC;",
context: QueryGenerator,
needsSequelize: true
}, {
title: 'single string argument is not quoted',
arguments: ['myTable', {group: "name"}],
expectation: "SELECT * FROM `myTable` GROUP BY `name`;",
expectation: "SELECT * FROM `myTable` GROUP BY name;",
context: QueryGenerator
}, {
arguments: ['myTable', {group: ["name"]}],
arguments: ['myTable', { group: ["name"] }],
expectation: "SELECT * FROM `myTable` GROUP BY `name`;",
context: QueryGenerator
}, {
arguments: ['myTable', {group: ["name", "title"]}],
expectation: "SELECT * FROM `myTable` GROUP BY `name`, `title`;",
context: QueryGenerator
title: 'functions work for group by',
arguments: ['myTable', function (sequelize) {
return {
group: [sequelize.fn('YEAR', sequelize.col('createdAt'))]
}
}],
expectation: "SELECT * FROM `myTable` GROUP BY YEAR(`createdAt`);",
context: QueryGenerator,
needsSequelize: true
}, {
title: 'It is possible to mix sequelize.fn and string arguments to group by',
arguments: ['myTable', function (sequelize) {
return {
group: [sequelize.fn('YEAR', sequelize.col('createdAt')), 'title']
}
}],
expectation: "SELECT * FROM `myTable` GROUP BY YEAR(`createdAt`), `title`;",
context: QueryGenerator,
needsSequelize: true
}, {
arguments: ['myTable', {group: "name", order: "id DESC"}],
expectation: "SELECT * FROM `myTable` GROUP BY `name` ORDER BY id DESC;",
expectation: "SELECT * FROM `myTable` GROUP BY name ORDER BY id DESC;",
context: QueryGenerator
}, {
arguments: ['myTable', {limit: 10}],
......@@ -423,9 +480,12 @@ if (dialect.match(/^mysql/)) {
describe(suiteTitle, function() {
tests.forEach(function(test) {
var title = test.title || 'MySQL correctly returns ' + test.expectation + ' for ' + util.inspect(test.arguments)
it(title, function(done) {
it(title, function(done) {
// Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly
var context = test.context || {options: {}};
if (test.needsSequelize) {
test.arguments[1] = test.arguments[1](this.sequelize)
}
QueryGenerator.options = context.options
var conditions = QueryGenerator[suiteTitle].apply(QueryGenerator, test.arguments)
expect(conditions).to.deep.equal(test.expectation)
......
......@@ -26,6 +26,28 @@ if (dialect.match(/^postgres/)) {
done()
})
it('should be able to search within an array', function(done) {
this.User.all({where: {email: ['hello', 'world']}}).on('sql', function(sql) {
expect(sql).to.equal('SELECT * FROM "Users" WHERE "Users"."email" && ARRAY[\'hello\',\'world\']::TEXT[];')
done()
})
})
it('should be able to find a record while searching in an array', function(done) {
var self = this
this.User.bulkCreate([
{username: 'bob', email: ['myemail@email.com']},
{username: 'tony', email: ['wrongemail@email.com']}
]).success(function() {
self.User.all({where: {email: ['myemail@email.com']}}).success(function(user) {
expect(user).to.be.instanceof(Array)
expect(user).to.have.length(1)
expect(user[0].username).to.equal('bob')
done()
})
})
})
it('describeTable should tell me that a column is hstore and not USER-DEFINED', function(done) {
this.sequelize.queryInterface.describeTable('Users').success(function(table) {
expect(table.document.type).to.equal('HSTORE')
......
......@@ -17,6 +17,7 @@ if (dialect.match(/^postgres/)) {
this.User = this.sequelize.define('User', {
username: DataTypes.STRING,
email: {type: DataTypes.ARRAY(DataTypes.TEXT)},
numbers: {type: DataTypes.ARRAY(DataTypes.FLOAT)},
document: {type: DataTypes.HSTORE, defaultValue: '"default"=>"value"'}
})
this.User.sync({ force: true }).success(function() {
......@@ -246,13 +247,73 @@ if (dialect.match(/^postgres/)) {
expectation: "SELECT * FROM \"myTable\" WHERE foo='bar';"
}, {
arguments: ['myTable', {order: "id DESC"}],
expectation: "SELECT * FROM \"myTable\" ORDER BY \"id\" DESC;"
}, {
expectation: "SELECT * FROM \"myTable\" ORDER BY id DESC;"
}, {
arguments: ['myTable', {order: ["id"]}],
expectation: 'SELECT * FROM "myTable" ORDER BY "id";',
context: QueryGenerator
}, {
arguments: ['myTable', {order: ["myTable.id"]}],
expectation: 'SELECT * FROM "myTable" ORDER BY "myTable"."id";',
context: QueryGenerator
}, {
arguments: ['myTable', {order: [["id", 'DESC']]}],
expectation: 'SELECT * FROM "myTable" ORDER BY "id" DESC;',
context: QueryGenerator
}, {
title: 'raw arguments are neither quoted nor escaped',
arguments: ['myTable', {order: [[{raw: 'f1(f2(id))'},'DESC']]}],
expectation: 'SELECT * FROM "myTable" ORDER BY f1(f2(id)) DESC;',
context: QueryGenerator
}, {
title: 'functions can take functions as arguments',
arguments: ['myTable', function (sequelize) {
return {
order: [[sequelize.fn('f1', sequelize.fn('f2', sequelize.col('id'))), 'DESC']]
}
}],
expectation: 'SELECT * FROM "myTable" ORDER BY f1(f2("id")) DESC;',
context: QueryGenerator,
needsSequelize: true
}, {
title: 'functions can take all types as arguments',
arguments: ['myTable', function (sequelize) {
return {
order: [
[sequelize.fn('f1', sequelize.col('myTable.id')), 'DESC'],
[sequelize.fn('f2', 12, 'lalala', new Date(Date.UTC(2011, 2, 27, 10, 1, 55))), 'ASC']
]
}
}],
expectation: 'SELECT * FROM "myTable" ORDER BY f1("myTable"."id") DESC, f2(12, \'lalala\', \'2011-03-27 10:01:55.000 +00:00\') ASC;',
context: QueryGenerator,
needsSequelize: true
}, {
title: 'single string argument is not quoted',
arguments: ['myTable', {group: "name"}],
expectation: "SELECT * FROM \"myTable\" GROUP BY \"name\";"
expectation: "SELECT * FROM \"myTable\" GROUP BY name;"
}, {
arguments: ['myTable', {group: ["name"]}],
expectation: "SELECT * FROM \"myTable\" GROUP BY \"name\";"
}, {
title: 'functions work for group by',
arguments: ['myTable', function (sequelize) {
return {
group: [sequelize.fn('YEAR', sequelize.col('createdAt'))]
}
}],
expectation: "SELECT * FROM \"myTable\" GROUP BY YEAR(\"createdAt\");",
needsSequelize: true
},{
title: 'It is possible to mix sequelize.fn and string arguments to group by',
arguments: ['myTable', function (sequelize) {
return {
group: [sequelize.fn('YEAR', sequelize.col('createdAt')), 'title']
}
}],
expectation: "SELECT * FROM \"myTable\" GROUP BY YEAR(\"createdAt\"), \"title\";",
context: QueryGenerator,
needsSequelize: true
}, {
arguments: ['myTable', {group: ["name","title"]}],
expectation: "SELECT * FROM \"myTable\" GROUP BY \"name\", \"title\";"
......@@ -361,6 +422,9 @@ if (dialect.match(/^postgres/)) {
arguments: ['myTable', {data: new Buffer('Sequelize') }],
expectation: "INSERT INTO \"myTable\" (\"data\") VALUES (E'\\\\x53657175656c697a65') RETURNING *;"
}, {
arguments: ['myTable', {name: 'foo', numbers: new Uint8Array([1,2,3])}],
expectation: "INSERT INTO \"myTable\" (\"name\",\"numbers\") VALUES ('foo',ARRAY[1,2,3]) RETURNING *;"
}, {
arguments: ['myTable', {name: 'foo', foo: 1}],
expectation: "INSERT INTO \"myTable\" (\"name\",\"foo\") VALUES ('foo',1) RETURNING *;"
}, {
......@@ -403,6 +467,10 @@ if (dialect.match(/^postgres/)) {
expectation: "INSERT INTO myTable (name,birthday) VALUES ('foo','2011-03-27 10:01:55.000 +00:00') RETURNING *;",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', {name: 'foo', numbers: new Uint8Array([1,2,3])}],
expectation: "INSERT INTO myTable (name,numbers) VALUES ('foo',ARRAY[1,2,3]) RETURNING *;",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', {name: 'foo', foo: 1}],
expectation: "INSERT INTO myTable (name,foo) VALUES ('foo',1) RETURNING *;",
context: {options: {quoteIdentifiers: false}}
......@@ -536,6 +604,9 @@ if (dialect.match(/^postgres/)) {
arguments: ['myTable', {bar: 2}, {name: 'foo'}],
expectation: "UPDATE \"myTable\" SET \"bar\"=2 WHERE \"name\"='foo' RETURNING *"
}, {
arguments: ['myTable', {numbers: new Uint8Array([1,2,3])}, {name: 'foo'}],
expectation: "UPDATE \"myTable\" SET \"numbers\"=ARRAY[1,2,3] WHERE \"name\"='foo' RETURNING *"
}, {
arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {name: 'foo'}],
expectation: "UPDATE \"myTable\" SET \"name\"='foo'';DROP TABLE myTable;' WHERE \"name\"='foo' RETURNING *"
}, {
......@@ -575,6 +646,10 @@ if (dialect.match(/^postgres/)) {
expectation: "UPDATE myTable SET bar=2 WHERE name='foo' RETURNING *",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', {numbers: new Uint8Array([1,2,3])}, {name: 'foo'}],
expectation: "UPDATE myTable SET numbers=ARRAY[1,2,3] WHERE name='foo' RETURNING *",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {name: 'foo'}],
expectation: "UPDATE myTable SET name='foo'';DROP TABLE myTable;' WHERE name='foo' RETURNING *",
context: {options: {quoteIdentifiers: false}}
......@@ -792,6 +867,9 @@ if (dialect.match(/^postgres/)) {
it(title, function(done) {
// Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly
var context = test.context || {options: {}};
if (test.needsSequelize) {
test.arguments[1] = test.arguments[1](this.sequelize)
}
QueryGenerator.options = context.options
var conditions = QueryGenerator[suiteTitle].apply(QueryGenerator, test.arguments)
expect(conditions).to.deep.equal(test.expectation)
......
......@@ -375,8 +375,20 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () {
})
describe('import', function() {
it("imports a dao definition from a file", function(done) {
it("imports a dao definition from a file absolute path", function(done) {
var Project = this.sequelize.import(__dirname + "/assets/project")
expect(Project).to.exist
done()
})
it("imports a dao definition from a function", function(done) {
var Project = this.sequelize.import('Project', function(sequelize, DataTypes) {
return sequelize.define('Project' + parseInt(Math.random() * 9999999999999999), {
name: DataTypes.STRING
})
})
expect(Project).to.exist
done()
})
......
......@@ -104,6 +104,156 @@ if (dialect === 'sqlite') {
}
],
selectQuery: [
{
arguments: ['myTable'],
expectation: "SELECT * FROM `myTable`;",
context: QueryGenerator
}, {
arguments: ['myTable', {attributes: ['id', 'name']}],
expectation: "SELECT `id`, `name` FROM `myTable`;",
context: QueryGenerator
}, {
arguments: ['myTable', {where: {id: 2}}],
expectation: "SELECT * FROM `myTable` WHERE `myTable`.`id`=2;",
context: QueryGenerator
}, {
arguments: ['myTable', {where: {name: 'foo'}}],
expectation: "SELECT * FROM `myTable` WHERE `myTable`.`name`='foo';",
context: QueryGenerator
}, {
arguments: ['myTable', {where: {name: "foo';DROP TABLE myTable;"}}],
expectation: "SELECT * FROM `myTable` WHERE `myTable`.`name`='foo\'\';DROP TABLE myTable;';",
context: QueryGenerator
}, {
arguments: ['myTable', {where: 2}],
expectation: "SELECT * FROM `myTable` WHERE `myTable`.`id`=2;",
context: QueryGenerator
}, {
arguments: ['foo', { attributes: [['count(*)', 'count']] }],
expectation: 'SELECT count(*) as `count` FROM `foo`;',
context: QueryGenerator
}, {
arguments: ['myTable', {where: "foo='bar'"}],
expectation: "SELECT * FROM `myTable` WHERE foo='bar';",
context: QueryGenerator
}, {
arguments: ['myTable', {order: "id DESC"}],
expectation: "SELECT * FROM `myTable` ORDER BY id DESC;",
context: QueryGenerator
}, {
arguments: ['myTable', {order: ["id"]}],
expectation: "SELECT * FROM `myTable` ORDER BY `id`;",
context: QueryGenerator
}, {
arguments: ['myTable', {order: ["myTable.id"]}],
expectation: "SELECT * FROM `myTable` ORDER BY `myTable`.`id`;",
context: QueryGenerator
}, {
arguments: ['myTable', {order: [["id", 'DESC']]}],
expectation: "SELECT * FROM `myTable` ORDER BY `id` DESC;",
context: QueryGenerator
}, {
title: 'raw arguments are neither quoted nor escaped',
arguments: ['myTable', {order: [[{raw: 'f1(f2(id))'}, 'DESC']]}],
expectation: "SELECT * FROM `myTable` ORDER BY f1(f2(id)) DESC;",
context: QueryGenerator
}, {
title: 'functions can take functions as arguments',
arguments: ['myTable', function (sequelize) {
return {
order: [[sequelize.fn('f1', sequelize.fn('f2', sequelize.col('id'))), 'DESC']]
}
}],
expectation: "SELECT * FROM `myTable` ORDER BY f1(f2(`id`)) DESC;",
context: QueryGenerator,
needsSequelize: true
}, {
title: 'functions can take all types as arguments',
arguments: ['myTable', function (sequelize) {
return {
order: [
[sequelize.fn('f1', sequelize.col('myTable.id')), 'DESC'],
[sequelize.fn('f2', 12, 'lalala', new Date(Date.UTC(2011, 2, 27, 10, 1, 55))), 'ASC']
]
}
}],
expectation: "SELECT * FROM `myTable` ORDER BY f1(`myTable`.`id`) DESC, f2(12, 'lalala', '2011-03-27 10:01:55') ASC;",
context: QueryGenerator,
needsSequelize: true
}, {
title: 'single string argument is not quoted',
arguments: ['myTable', {group: "name"}],
expectation: "SELECT * FROM `myTable` GROUP BY name;",
context: QueryGenerator
}, {
arguments: ['myTable', {group: ["name"]}],
expectation: "SELECT * FROM `myTable` GROUP BY `name`;",
context: QueryGenerator
}, {
title: 'functions work for group by',
arguments: ['myTable', function (sequelize) {
return {
group: [sequelize.fn('YEAR', sequelize.col('createdAt'))]
}
}],
expectation: "SELECT * FROM `myTable` GROUP BY YEAR(`createdAt`);",
context: QueryGenerator,
needsSequelize: true
}, {
title: 'It is possible to mix sequelize.fn and string arguments to group by',
arguments: ['myTable', function (sequelize) {
return {
group: [sequelize.fn('YEAR', sequelize.col('createdAt')), 'title']
}
}],
expectation: "SELECT * FROM `myTable` GROUP BY YEAR(`createdAt`), `title`;",
context: QueryGenerator,
needsSequelize: true
}, {
arguments: ['myTable', {group: ["name", "title"]}],
expectation: "SELECT * FROM `myTable` GROUP BY `name`, `title`;",
context: QueryGenerator
}, {
arguments: ['myTable', {group: "name", order: "id DESC"}],
expectation: "SELECT * FROM `myTable` GROUP BY name ORDER BY id DESC;",
context: QueryGenerator
}, {
arguments: ['myTable', {limit: 10}],
expectation: "SELECT * FROM `myTable` LIMIT 10;",
context: QueryGenerator
}, {
arguments: ['myTable', {limit: 10, offset: 2}],
expectation: "SELECT * FROM `myTable` LIMIT 2, 10;",
context: QueryGenerator
}, {
title: 'uses default limit if only offset is specified',
arguments: ['myTable', {offset: 2}],
expectation: "SELECT * FROM `myTable` LIMIT 2, 10000000000000;",
context: QueryGenerator
}, {
title: 'multiple where arguments',
arguments: ['myTable', {where: {boat: 'canoe', weather: 'cold'}}],
expectation: "SELECT * FROM `myTable` WHERE `myTable`.`boat`='canoe' AND `myTable`.`weather`='cold';",
context: QueryGenerator
}, {
title: 'no where arguments (object)',
arguments: ['myTable', {where: {}}],
expectation: "SELECT * FROM `myTable` WHERE 1=1;",
context: QueryGenerator
}, {
title: 'no where arguments (string)',
arguments: ['myTable', {where: ''}],
expectation: "SELECT * FROM `myTable` WHERE 1=1;",
context: QueryGenerator
}, {
title: 'no where arguments (null)',
arguments: ['myTable', {where: null}],
expectation: "SELECT * FROM `myTable` WHERE 1=1;",
context: QueryGenerator
}
],
insertQuery: [
{
arguments: ['myTable', { name: 'foo' }],
......@@ -257,6 +407,9 @@ if (dialect === 'sqlite') {
it(title, function(done) {
// Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly
var context = test.context || {options: {}};
if (test.needsSequelize) {
test.arguments[1] = test.arguments[1](this.sequelize)
}
QueryGenerator.options = context.options
var conditions = QueryGenerator[suiteTitle].apply(QueryGenerator, test.arguments)
expect(conditions).to.deep.equal(test.expectation)
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!