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

Commit 5d1a7739 by Sascha Depold

Merge branch 'master' into milestones/2.0.0

2 parents 15153227 1134d01d
...@@ -20,6 +20,9 @@ ...@@ -20,6 +20,9 @@
- [BUG] PostgreSQL should now be able to insert empty arrays with typecasting. [#718](https://github.com/sequelize/sequelize/pull/718). thanks to durango - [BUG] PostgreSQL should now be able to insert empty arrays with typecasting. [#718](https://github.com/sequelize/sequelize/pull/718). thanks to durango
- [BUG] Fields should be escaped by quoteIdentifier for max/min functions which allows SQL reserved keywords to be used. [#719](https://github.com/sequelize/sequelize/pull/719). thanks to durango - [BUG] Fields should be escaped by quoteIdentifier for max/min functions which allows SQL reserved keywords to be used. [#719](https://github.com/sequelize/sequelize/pull/719). thanks to durango
- [BUG] Fixed bug when trying to save objects with eagerly loaded attributes [#716](https://github.com/sequelize/sequelize/pull/716). thanks to iamjochen - [BUG] Fixed bug when trying to save objects with eagerly loaded attributes [#716](https://github.com/sequelize/sequelize/pull/716). thanks to iamjochen
- [BUG] Strings for .find() should be fixed. Also added support for string primary keys to be found easily. [#737](https://github.com/sequelize/sequelize/pull/737). thanks to durango
- [BUG] Fixed problems with quoteIdentifiers and {raw: false} option on raw queries [#751](https://github.com/sequelize/sequelize/pull/751). thanks to janmeier
- [BUG] Fixed SQL escaping with sqlite and unified escaping [#700](https://github.com/sequelize/sequelize/pull/700). thanks to PiPeep
- [FEATURE] Validate a model before it gets saved. [#601](https://github.com/sequelize/sequelize/pull/601). thanks to durango - [FEATURE] Validate a model before it gets saved. [#601](https://github.com/sequelize/sequelize/pull/601). thanks to durango
- [FEATURE] Schematics. [#564](https://github.com/sequelize/sequelize/pull/564). thanks to durango - [FEATURE] Schematics. [#564](https://github.com/sequelize/sequelize/pull/564). thanks to durango
- [FEATURE] Foreign key constraints. [#595](https://github.com/sequelize/sequelize/pull/595). thanks to optilude - [FEATURE] Foreign key constraints. [#595](https://github.com/sequelize/sequelize/pull/595). thanks to optilude
...@@ -33,11 +36,14 @@ ...@@ -33,11 +36,14 @@
- [FEATURE] `findOrCreate` now returns an additional flag (`created`), that is true if a model was created, and false if it was found [#648](https://github.com/sequelize/sequelize/pull/648). janmeier - [FEATURE] `findOrCreate` now returns an additional flag (`created`), that is true if a model was created, and false if it was found [#648](https://github.com/sequelize/sequelize/pull/648). janmeier
- [FEATURE] Field and table comments for MySQL and PG. [#523](https://github.com/sequelize/sequelize/pull/523). MySQL by iamjochen. PG by janmeier - [FEATURE] Field and table comments for MySQL and PG. [#523](https://github.com/sequelize/sequelize/pull/523). MySQL by iamjochen. PG by janmeier
- [FEATURE] BigInts can now be used for autoincrement/serial columns. [#673](https://github.com/sequelize/sequelize/pull/673). thanks to sevastos - [FEATURE] BigInts can now be used for autoincrement/serial columns. [#673](https://github.com/sequelize/sequelize/pull/673). thanks to sevastos
- [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
- [FEATURE] Use moment for better postgres timestamp strings. [#710](https://github.com/sequelize/sequelize/pull/710). Thanks to seth-admittedly - [FEATURE] Use moment for better postgres timestamp strings. [#710](https://github.com/sequelize/sequelize/pull/710). Thanks to seth-admittedly
- [FEATURE] Keep milliseconds in timestamps for postgres. [#712](https://github.com/sequelize/sequelize/pull/712). Thanks to seth-admittedly - [FEATURE] Keep milliseconds in timestamps for postgres. [#712](https://github.com/sequelize/sequelize/pull/712). Thanks to seth-admittedly
- [FEATURE] You can now set lingo's language through Sequelize. [#713](https://github.com/sequelize/sequelize/pull/713). Thanks to durango - [FEATURE] You can now set lingo's language through Sequelize. [#713](https://github.com/sequelize/sequelize/pull/713). Thanks to durango
- [FEATURE] Added a `findAndCountAll`, useful for pagination. [#533](https://github.com/sequelize/sequelize/pull/533). Thanks to iamjochen - [FEATURE] Added a `findAndCountAll`, useful for pagination. [#533](https://github.com/sequelize/sequelize/pull/533). Thanks to iamjochen
- [FEATURE] Made explicit migrations possible. [#728](https://github.com/sequelize/sequelize/pull/728). Thanks to freezy
- [FEATURE] Added support for where clauses containing !=, < etc. and support for date ranges [#727](https://github.com/sequelize/sequelize/pull/727). Thanks to durango
- [REFACTORING] hasMany now uses a single SQL statement when creating and destroying associations, instead of removing each association seperately [690](https://github.com/sequelize/sequelize/pull/690). Inspired by [#104](https://github.com/sequelize/sequelize/issues/104). janmeier
- [REFACTORING] Consistent handling of offset across dialects. Offset is now always applied, and limit is set to max table size of not limit is given [#725](https://github.com/sequelize/sequelize/pull/725). janmeier
# v1.6.0 # # v1.6.0 #
- [DEPENDENCIES] upgrade mysql to alpha7. You *MUST* use this version or newer for DATETIMEs to work - [DEPENDENCIES] upgrade mysql to alpha7. You *MUST* use this version or newer for DATETIMEs to work
......
/*jslint node:true */
"use strict";
var Sequelize = require('sequelize');
// initialize database connection
var sequelize = new Sequelize('testsequelize', 'testsequelize', 'testsequelize', {
dialect: 'postgres',
port: 5432,
define: {
freezeTableName: true
}
});
// load models
var models = [
'Trainer',
'Series',
'Video'
];
models.forEach(function(model) {
module.exports[model] = sequelize.import(__dirname + '/' + model);
});
// describe relationships
(function(m) {
m.Series.hasOne(m.Video);
m.Trainer.hasMany(m.Series);
})(module.exports);
// export connection
module.exports.sequelize = sequelize;
\ No newline at end of file
/*jslint node:true */
"use strict";
module.exports = function(sequelize, DataTypes) {
return sequelize.define('Series', {
title: {
type: DataTypes.STRING
},
sub_title: {
type: DataTypes.STRING
},
description: {
type: DataTypes.TEXT
},
// Set FK relationship (hasMany) with `Trainer`
trainer_id: {
type: DataTypes.INTEGER,
references: "Trainer",
referencesKey: 'id'
}
}, {
// don't need timestamp attributes for this model
timestamps: false,
underscored: true
});
};
/*jslint node:true */
"use strict";
module.exports = function(sequelize, DataTypes) {
return sequelize.define('Trainer', {
first_name: {
type: DataTypes.STRING
},
last_name: {
type: DataTypes.STRING
}
}, {
// don't need timestamp attributes for this model
timestamps: false,
underscored: true
});
};
/*jslint node:true */
"use strict";
module.exports = function(sequelize, DataTypes) {
return sequelize.define('Video', {
title: {
type: DataTypes.STRING
},
sequence: {
type: DataTypes.INTEGER
},
description: {
type: DataTypes.TEXT
},
// set relationship (hasOne) with `Series`
series_id: {
type: DataTypes.INTEGER,
references: "Series",
referencesKey: 'id'
}
}, {
// don't need timestamp attributes for this model
timestamps: false,
underscored: true
});
};
Results after syncing the dabatase
----------------------------------
After syncing the database, you'll see in the console the following:
```
Executing: CREATE TABLE IF NOT EXISTS "Trainer" ("first_name" VARCHAR(255), "last_name" VARCHAR(255), "id" SERIAL , PRIMARY KEY ("id"));
Executing: CREATE TABLE IF NOT EXISTS "Series" ("title" VARCHAR(255), "sub_title" VARCHAR(255), "description" TEXT, "trainer_id" INTEGER REFERENCES "Trainer" ("id"), "id" SERIAL , PRIMARY KEY ("id"));
Executing: CREATE TABLE IF NOT EXISTS "Video" ("title" VARCHAR(255), "sequence" INTEGER, "description" TEXT, "series_id" INTEGER REFERENCES "Series" ("id"), "id" SERIAL , PRIMARY KEY ("id"));
```
Notice in the `Video` that `series_id` field has a referential integrity to `Series`:
```
"series_id" INTEGER REFERENCES "Series" ("id")
```
This is the output when describing the table's structure of the Postgres database:
**Trainer** table:
```
testsequelize=> \d+ "Trainer";
Table "public.Trainer"
Column | Type | Modifiers |
------------+------------------------+--------------------------------------------------------+
first_name | character varying(255) | |
last_name | character varying(255) | |
id | integer | not null default nextval('"Trainer_id_seq"'::regclass) |
Indexes:
"Trainer_pkey" PRIMARY KEY, btree (id)
Referenced by:
TABLE ""Series"" CONSTRAINT "Series_trainer_id_fkey" FOREIGN KEY (trainer_id) REFERENCES "Trainer"(id)
Has OIDs: no
```
**Series** table:
```
testsequelize=> \d+ "Series";
Table "public.Series"
Column | Type | Modifiers |
-----------------+------------------------+-------------------------------------------------------+
title | character varying(255) | |
sub_title | character varying(255) | |
description | text | |
trainer_id | integer | |
id | integer | not null default nextval('"Series_id_seq"'::regclass) |
Indexes:
"Series_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
"Series_trainer_id_fkey" FOREIGN KEY (trainer_id) REFERENCES "Trainer"(id)
Referenced by:
TABLE ""Video"" CONSTRAINT "Video_series_id_fkey" FOREIGN KEY (series_id) REFERENCES "Series"(id)
Has OIDs: no
```
**Video** table:
```
testsequelize=> \d+ "Video";
Table "public.Video"
Column | Type | Modifiers |
-------------+------------------------+------------------------------------------------------+
title | character varying(255) | |
sequence | integer | |
description | text | |
series_id | integer | |
id | integer | not null default nextval('"Video_id_seq"'::regclass) |
Indexes:
"Video_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
"Video_series_id_fkey" FOREIGN KEY (series_id) REFERENCES "Series"(id)
Has OIDs: no
```
...@@ -278,6 +278,17 @@ module.exports = (function() { ...@@ -278,6 +278,17 @@ module.exports = (function() {
// whereCollection is used for non-primary key updates // whereCollection is used for non-primary key updates
this.options.whereCollection = options.where || null this.options.whereCollection = options.where || null
} else if (typeof options === "string") {
var where = {}
, keys = Object.keys(primaryKeys)
if (this.primaryKeyCount === 1) {
where[primaryKeys[keys[0]]] = options;
options = where;
} else if (this.primaryKeyCount < 1) {
// Revert to default behavior which is {where: [int]}
options = {where: parseInt(Number(options) || 0, 0)}
}
} }
options.limit = 1 options.limit = 1
......
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() {
...@@ -217,8 +218,14 @@ module.exports = (function() { ...@@ -217,8 +218,14 @@ module.exports = (function() {
query += " ORDER BY " + options.order query += " ORDER BY " + options.order
} }
if (options.offset && !options.limit) {
if (options.limit && !(options.include && (options.limit === 1))) { /*
* 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) { if (options.offset) {
query += " LIMIT " + options.offset + ", " + options.limit query += " LIMIT " + options.offset + ", " + options.limit
} else { } else {
...@@ -547,7 +554,7 @@ module.exports = (function() { ...@@ -547,7 +554,7 @@ module.exports = (function() {
} }
if (dataType.comment && Utils._.isString(dataType.comment) && dataType.comment.length) { if (dataType.comment && Utils._.isString(dataType.comment) && dataType.comment.length) {
template += " COMMENT " + Utils.escape(dataType.comment) template += " COMMENT " + this.escape(dataType.comment)
} }
result[name] = template result[name] = template
...@@ -594,12 +601,7 @@ module.exports = (function() { ...@@ -594,12 +601,7 @@ module.exports = (function() {
}, },
escape: function(value) { escape: function(value) {
if (value instanceof Date) { return SqlString.escape(value, false, null, "mysql")
value = Utils.toSqlDate(value)
} else if (typeof value === 'boolean') {
value = value ? 1 : 0
}
return Utils.escape(value)
} }
} }
......
...@@ -10,6 +10,10 @@ module.exports = (function() { ...@@ -10,6 +10,10 @@ module.exports = (function() {
this.pooling = (!!this.config.poolCfg && (this.config.poolCfg.maxConnections > 0)) this.pooling = (!!this.config.poolCfg && (this.config.poolCfg.maxConnections > 0))
this.pg = this.config.native ? require('pg').native : require('pg') this.pg = this.config.native ? require('pg').native : require('pg')
// Better support for BigInts
// https://github.com/brianc/node-postgres/issues/166#issuecomment-9514935
this.pg.types.setTypeParser(20, String);
// set pooling parameters if specified // set pooling parameters if specified
if (this.pooling) { if (this.pooling) {
this.pg.defaults.poolSize = this.config.poolCfg.maxConnections this.pg.defaults.poolSize = this.config.poolCfg.maxConnections
......
var Utils = require("../../utils") var Utils = require("../../utils")
, util = require("util") , util = require("util")
, DataTypes = require("../../data-types") , DataTypes = require("../../data-types")
, SqlString = require("../../sql-string")
, tables = {} , tables = {}
, primaryKeys = {} , primaryKeys = {}
, moment = require("moment")
module.exports = (function() { module.exports = (function() {
var QueryGenerator = { var QueryGenerator = {
...@@ -293,7 +293,6 @@ module.exports = (function() { ...@@ -293,7 +293,6 @@ module.exports = (function() {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull) attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull)
var query = "INSERT INTO <%= table %> (<%= attributes %>) VALUES (<%= values %>) RETURNING *;" var query = "INSERT INTO <%= table %> (<%= attributes %>) VALUES (<%= values %>) RETURNING *;"
// Remove serials that are null or undefined, which causes an error in PG // Remove serials that are null or undefined, which causes an error in PG
Utils._.forEach(attrValueHash, function(value, key, hash) { Utils._.forEach(attrValueHash, function(value, key, hash) {
if (tables[tableName]) { if (tables[tableName]) {
...@@ -725,10 +724,6 @@ module.exports = (function() { ...@@ -725,10 +724,6 @@ module.exports = (function() {
return (i < 10) ? '0' + i.toString() : i.toString() return (i < 10) ? '0' + i.toString() : i.toString()
}, },
pgSqlDate: function (dt) {
return moment(dt).format("YYYY-MM-DD HH:mm:ss.SSS Z")
},
pgDataTypeMapping: function (tableName, attr, dataType) { pgDataTypeMapping: function (tableName, attr, dataType) {
if (Utils._.includes(dataType, 'PRIMARY KEY')) { if (Utils._.includes(dataType, 'PRIMARY KEY')) {
primaryKeys[tableName].push(attr) primaryKeys[tableName].push(attr)
...@@ -779,33 +774,8 @@ module.exports = (function() { ...@@ -779,33 +774,8 @@ module.exports = (function() {
return identifiers.split('.').map(function(t) { return this.quoteIdentifier(t, force) }.bind(this)).join('.') return identifiers.split('.').map(function(t) { return this.quoteIdentifier(t, force) }.bind(this)).join('.')
}, },
escape: function (val, field) { escape: function(value, field) {
if (val === undefined || val === null) { return SqlString.escape(value, false, null, "postgres", field)
return 'NULL';
}
switch (typeof val) {
case 'boolean':
return (val) ? 'true' : 'false';
case 'number':
return val + '';
case 'object':
if (Array.isArray(val)) {
var ret = 'ARRAY['+ val.map(function(it) { return this.escape(it) }.bind(this)).join(',') + ']'
if (!!field && !!field.type) {
ret += '::' + field.type.replace(/\(\d+\)/g, '')
}
return ret
}
}
if (val instanceof Date) {
val = this.pgSqlDate(val);
}
// http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS
val = val.replace(/'/g, "''");
return "'" + val + "'";
} }
} }
......
...@@ -111,7 +111,7 @@ module.exports = (function() { ...@@ -111,7 +111,7 @@ module.exports = (function() {
} else { } else {
// Postgres will treat tables as case-insensitive, so fix the case // Postgres will treat tables as case-insensitive, so fix the case
// of the returned values to match attributes // of the returned values to match attributes
if(this.sequelize.options.quoteIdentifiers == false) { if(this.options.raw === false && this.sequelize.options.quoteIdentifiers === false) {
var attrsMap = Utils._.reduce(this.callee.attributes, function(m, v, k) { m[k.toLowerCase()] = k; return m}, {}) var attrsMap = Utils._.reduce(this.callee.attributes, function(m, v, k) { m[k.toLowerCase()] = k; return m}, {})
rows.forEach(function(row) { rows.forEach(function(row) {
Utils._.keys(row).forEach(function(key) { Utils._.keys(row).forEach(function(key) {
......
var Utils = require("../../utils") var Utils = require("../../utils")
, DataTypes = require("../../data-types") , DataTypes = require("../../data-types")
, SqlString = require("../../sql-string")
var MySqlQueryGenerator = Utils._.extend( var MySqlQueryGenerator = Utils._.extend(
Utils._.clone(require("../query-generator")), Utils._.clone(require("../query-generator")),
...@@ -188,8 +189,13 @@ module.exports = (function() { ...@@ -188,8 +189,13 @@ module.exports = (function() {
query += " ORDER BY " + options.order query += " ORDER BY " + options.order
} }
if (options.offset && !options.limit) {
if (options.limit && !(options.include && (options.limit === 1))) { /*
* 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) { if (options.offset) {
query += " LIMIT " + options.offset + ", " + options.limit query += " LIMIT " + options.offset + ", " + options.limit
} else { } else {
...@@ -448,19 +454,7 @@ module.exports = (function() { ...@@ -448,19 +454,7 @@ module.exports = (function() {
}, },
escape: function(value) { escape: function(value) {
if (value instanceof Date) { return SqlString.escape(value, false, null, "sqlite")
value = Utils.toSqlDate(value)
}
if (typeof value === 'string') {
return "'" + value.replace(/'/g, "''") + "'";
} else if (typeof value === 'boolean') {
return value ? 1 : 0; // SQLite has no type boolean
} else if (value === null || value === undefined) {
return 'NULL';
} else {
return value;
}
} }
} }
......
var moment = require("moment") var moment = require("moment")
, path = require("path")
, Utils = require("./utils") , Utils = require("./utils")
, DataTypes = require("./data-types") , DataTypes = require("./data-types")
, QueryInterface = require("./query-interface") , QueryInterface = require("./query-interface")
module.exports = (function() { module.exports = (function() {
var Migration = function(migrator, path) { var Migration = function(migrator, p) {
this.migrator = migrator this.migrator = migrator
this.path = path this.path = path.normalize(p)
this.filename = Utils._.last(this.path.split('/')) this.filename = Utils._.last(this.path.split(path.sep))
var parsed = Migration.parseFilename(this.filename) var parsed = Migration.parseFilename(this.filename)
......
...@@ -177,6 +177,56 @@ module.exports = (function() { ...@@ -177,6 +177,56 @@ module.exports = (function() {
}).run() }).run()
} }
/**
* Explicitly executes one or multiple migrations.
*
* @param filename {String|Array} Absolute filename(s) of the migrations script
* @param options {Object} Can contain three functions, before, after and success, which are executed before
* or after each migration respectively, with one parameter, the migration.
*/
Migrator.prototype.exec = function(filename, options) {
var self = this;
return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer;
var addMigration = function(filename) {
self.options.logging('Adding migration script at ' + filename)
var migration = new Migration(self, filename)
chainer.add(migration, 'execute', [{ method: 'up' }], {
before: function(migration) {
if (options && Utils._.isFunction(options.before)) {
options.before.call(self, migration);
}
},
after: function(migration) {
if (options && Utils._.isFunction(options.after)) {
options.after.call(self, migration);
}
},
success: function(migration, callback) {
if (options && Utils._.isFunction(options.success)) {
options.success.call(self, migration);
}
callback();
}
})
}
if (Utils._.isArray(filename)) {
Utils._.each(filename, function(f) {
addMigration(f);
})
} else {
addMigration(filename);
}
chainer
.runSerially({ skipOnError: true })
.success(function() { emitter.emit('success', null) })
.error(function(err) { emitter.emit('error', err) })
}).run()
}
// private // private
var getLastMigrationFromDatabase = function() { var getLastMigrationFromDatabase = function() {
......
...@@ -102,7 +102,11 @@ module.exports = (function() { ...@@ -102,7 +102,11 @@ module.exports = (function() {
maxConcurrentQueries: this.options.maxConcurrentQueries maxConcurrentQueries: this.options.maxConcurrentQueries
} }
var ConnectorManager = require("./dialects/" + this.options.dialect + "/connector-manager") try {
var ConnectorManager = require("./dialects/" + this.options.dialect + "/connector-manager")
} catch(err) {
throw new Error("The dialect " + this.options.dialect + " is not supported.")
}
this.daoFactoryManager = new DAOFactoryManager(this) this.daoFactoryManager = new DAOFactoryManager(this)
this.connectorManager = new ConnectorManager(this, this.config) this.connectorManager = new ConnectorManager(this, this.config)
......
var SqlString = exports; var moment = require("moment")
, SqlString = exports;
SqlString.escapeId = function (val, forbidQualified) { SqlString.escapeId = function (val, forbidQualified) {
if (forbidQualified) { if (forbidQualified) {
...@@ -7,18 +8,22 @@ SqlString.escapeId = function (val, forbidQualified) { ...@@ -7,18 +8,22 @@ SqlString.escapeId = function (val, forbidQualified) {
return '`' + val.replace(/`/g, '``').replace(/\./g, '`.`') + '`'; return '`' + val.replace(/`/g, '``').replace(/\./g, '`.`') + '`';
}; };
SqlString.escape = function(val, stringifyObjects, timeZone, dialect) { SqlString.escape = function(val, stringifyObjects, timeZone, dialect, field) {
if (val === undefined || val === null) { if (val === undefined || val === null) {
return 'NULL'; return 'NULL';
} }
switch (typeof val) { switch (typeof val) {
case 'boolean': return (val) ? 'true' : 'false'; case 'boolean':
// SQLite doesn't have true/false support. MySQL aliases true/false to 1/0
// for us. Postgres actually has a boolean type with true/false literals,
// but sequelize doesn't use it yet.
return dialect === 'sqlite' ? +!!val : ('' + !!val);
case 'number': return val+''; case 'number': return val+'';
} }
if (val instanceof Date) { if (val instanceof Date) {
val = SqlString.dateToString(val, timeZone || "Z"); val = SqlString.dateToString(val, timeZone || "Z", dialect);
} }
if (Buffer.isBuffer(val)) { if (Buffer.isBuffer(val)) {
...@@ -26,7 +31,7 @@ SqlString.escape = function(val, stringifyObjects, timeZone, dialect) { ...@@ -26,7 +31,7 @@ SqlString.escape = function(val, stringifyObjects, timeZone, dialect) {
} }
if (Array.isArray(val)) { if (Array.isArray(val)) {
return SqlString.arrayToList(val, timeZone); return SqlString.arrayToList(val, timeZone, dialect, field);
} }
if (typeof val === 'object') { if (typeof val === 'object') {
...@@ -37,8 +42,9 @@ SqlString.escape = function(val, stringifyObjects, timeZone, dialect) { ...@@ -37,8 +42,9 @@ SqlString.escape = function(val, stringifyObjects, timeZone, dialect) {
} }
} }
if (dialect == "postgres") { if (dialect === 'postgres' || dialect === 'sqlite') {
// http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS // 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 { } else {
val = val.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) { val = val.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) {
...@@ -56,11 +62,22 @@ SqlString.escape = function(val, stringifyObjects, timeZone, dialect) { ...@@ -56,11 +62,22 @@ SqlString.escape = function(val, stringifyObjects, timeZone, dialect) {
return "'"+val+"'"; return "'"+val+"'";
}; };
SqlString.arrayToList = function(array, timeZone) { SqlString.arrayToList = function(array, timeZone, dialect, field) {
return array.map(function(v) { if (dialect === 'postgres') {
if (Array.isArray(v)) return '(' + SqlString.arrayToList(v) + ')'; var ret = 'ARRAY[' + array.map(function(v) {
return SqlString.escape(v, true, timeZone); return SqlString.escape(v, true, timeZone, dialect, field);
}).join(', '); }).join(',') + ']';
if (!!field && !!field.type) {
ret += '::' + field.type.replace(/\(\d+\)/g, '');
}
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(', ');
}
}; };
SqlString.format = function(sql, values, timeZone, dialect) { SqlString.format = function(sql, values, timeZone, dialect) {
...@@ -75,10 +92,15 @@ SqlString.format = function(sql, values, timeZone, dialect) { ...@@ -75,10 +92,15 @@ SqlString.format = function(sql, values, timeZone, dialect) {
}); });
}; };
SqlString.dateToString = function(date, timeZone) { SqlString.dateToString = function(date, timeZone, dialect) {
var dt = new Date(date); var dt = new Date(date);
if (timeZone != 'local') { // TODO: Ideally all dialects would work a bit more like this
if (dialect === "postgres") {
return moment(dt).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));
...@@ -87,14 +109,7 @@ SqlString.dateToString = function(date, timeZone) { ...@@ -87,14 +109,7 @@ SqlString.dateToString = function(date, timeZone) {
} }
} }
var year = dt.getFullYear(); return moment(dt).format("YYYY-MM-DD HH:mm:ss");
var month = zeroPad(dt.getMonth() + 1);
var day = zeroPad(dt.getDate());
var hour = zeroPad(dt.getHours());
var minute = zeroPad(dt.getMinutes());
var second = zeroPad(dt.getSeconds());
return year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second;
}; };
SqlString.bufferToString = function(buffer) { SqlString.bufferToString = function(buffer) {
......
...@@ -42,17 +42,6 @@ var Utils = module.exports = { ...@@ -42,17 +42,6 @@ var Utils = module.exports = {
isHash: function(obj) { isHash: function(obj) {
return Utils._.isObject(obj) && !Array.isArray(obj); return Utils._.isObject(obj) && !Array.isArray(obj);
}, },
pad: function (s) {
return s < 10 ? '0' + s : s
},
toSqlDate: function(date) {
return date.getUTCFullYear() + '-' +
this.pad(date.getUTCMonth()+1) + '-' +
this.pad(date.getUTCDate()) + ' ' +
this.pad(date.getUTCHours()) + ':' +
this.pad(date.getUTCMinutes()) + ':' +
this.pad(date.getUTCSeconds())
},
argsArePrimaryKeys: function(args, primaryKeys) { argsArePrimaryKeys: function(args, primaryKeys) {
var result = (args.length == Object.keys(primaryKeys).length) var result = (args.length == Object.keys(primaryKeys).length)
if (result) { if (result) {
...@@ -181,12 +170,9 @@ var Utils = module.exports = { ...@@ -181,12 +170,9 @@ var Utils = module.exports = {
removeTicks: function(s, tickChar) { removeTicks: function(s, tickChar) {
tickChar = tickChar || Utils.TICK_CHAR tickChar = tickChar || Utils.TICK_CHAR
return s.replace(new RegExp(tickChar, 'g'), "") return s.replace(new RegExp(tickChar, 'g'), "")
},
escape: function(s) {
return SqlString.escape(s, true, "local").replace(/\\"/g, '"')
} }
} }
Utils.CustomEventEmitter = require("./emitters/custom-event-emitter") Utils.CustomEventEmitter = require("./emitters/custom-event-emitter")
Utils.QueryChainer = require("./query-chainer") Utils.QueryChainer = require("./query-chainer")
Utils.Lingo = require("lingo") Utils.Lingo = require("lingo")
\ No newline at end of file
...@@ -50,7 +50,8 @@ ...@@ -50,7 +50,8 @@
"pg": "~2.0.0", "pg": "~2.0.0",
"buster": "~0.6.3", "buster": "~0.6.3",
"watchr": "~2.2.0", "watchr": "~2.2.0",
"yuidocjs": "~0.3.36" "yuidocjs": "~0.3.36",
"semver": "~2.0.8"
}, },
"keywords": [ "keywords": [
"mysql", "mysql",
......
...@@ -238,9 +238,7 @@ describe('DAOFactory', function() { ...@@ -238,9 +238,7 @@ describe('DAOFactory', function() {
}) })
}) })
}) })
/*
// at time of writing (v1.6.0) Sequelize does not seem to support 'offset' on it's own consistently (goes wrong for PostGRES and SQLite)
it("handles offset", function() { it("handles offset", function() {
Helpers.async(function(done) { Helpers.async(function(done) {
User.findAndCountAll({offset: 1}).success(function(info) { User.findAndCountAll({offset: 1}).success(function(info) {
...@@ -251,7 +249,7 @@ describe('DAOFactory', function() { ...@@ -251,7 +249,7 @@ describe('DAOFactory', function() {
}) })
}) })
}) })
*/
it("handles limit", function() { it("handles limit", function() {
Helpers.async(function(done) { Helpers.async(function(done) {
User.findAndCountAll({limit: 1}).success(function(info) { User.findAndCountAll({limit: 1}).success(function(info) {
......
...@@ -178,9 +178,9 @@ describe('QueryGenerator', function() { ...@@ -178,9 +178,9 @@ describe('QueryGenerator', function() {
expectation: "SELECT * FROM `myTable` LIMIT 2, 10;", expectation: "SELECT * FROM `myTable` LIMIT 2, 10;",
context: QueryGenerator context: QueryGenerator
}, { }, {
title: 'ignores offset if no limit was passed', title: 'uses default limit if only offset is specified',
arguments: ['myTable', {offset: 2}], arguments: ['myTable', {offset: 2}],
expectation: "SELECT * FROM `myTable`;", expectation: "SELECT * FROM `myTable` LIMIT 2, 18440000000000000000;",
context: QueryGenerator context: QueryGenerator
}, { }, {
title: 'multiple where arguments', title: 'multiple where arguments',
...@@ -235,10 +235,10 @@ describe('QueryGenerator', function() { ...@@ -235,10 +235,10 @@ describe('QueryGenerator', function() {
context: {options: {omitNull: true}} context: {options: {omitNull: true}}
}, { }, {
arguments: ['myTable', {foo: false}], arguments: ['myTable', {foo: false}],
expectation: "INSERT INTO `myTable` (`foo`) VALUES (0);" expectation: "INSERT INTO `myTable` (`foo`) VALUES (false);"
}, { }, {
arguments: ['myTable', {foo: true}], arguments: ['myTable', {foo: true}],
expectation: "INSERT INTO `myTable` (`foo`) VALUES (1);" expectation: "INSERT INTO `myTable` (`foo`) VALUES (true);"
} }
], ],
...@@ -272,7 +272,7 @@ describe('QueryGenerator', function() { ...@@ -272,7 +272,7 @@ describe('QueryGenerator', function() {
context: {options: {omitNull: true}} // Note: As above context: {options: {omitNull: true}} // Note: As above
}, { }, {
arguments: ['myTable', [{name: "foo", value: true}, {name: 'bar', value: false}]], arguments: ['myTable', [{name: "foo", value: true}, {name: 'bar', value: false}]],
expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('foo',1),('bar',0);" expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('foo',true),('bar',false);"
} }
], ],
...@@ -302,10 +302,10 @@ describe('QueryGenerator', function() { ...@@ -302,10 +302,10 @@ describe('QueryGenerator', function() {
context: {options: {omitNull: true}} context: {options: {omitNull: true}}
}, { }, {
arguments: ['myTable', {bar: false}, {name: 'foo'}], arguments: ['myTable', {bar: false}, {name: 'foo'}],
expectation: "UPDATE `myTable` SET `bar`=0 WHERE `name`='foo'" expectation: "UPDATE `myTable` SET `bar`=false WHERE `name`='foo'"
}, { }, {
arguments: ['myTable', {bar: true}, {name: 'foo'}], arguments: ['myTable', {bar: true}, {name: 'foo'}],
expectation: "UPDATE `myTable` SET `bar`=1 WHERE `name`='foo'" expectation: "UPDATE `myTable` SET `bar`=true WHERE `name`='foo'"
} }
], ],
...@@ -385,11 +385,11 @@ describe('QueryGenerator', function() { ...@@ -385,11 +385,11 @@ describe('QueryGenerator', function() {
}, },
{ {
arguments: [{ maple: false, bacon: true }], arguments: [{ maple: false, bacon: true }],
expectation: "`maple`=0 AND `bacon`=1" expectation: "`maple`=false AND `bacon`=true"
}, },
{ {
arguments: [{ beaver: [false, true] }], arguments: [{ beaver: [false, true] }],
expectation: "`beaver` IN (0,1)" expectation: "`beaver` IN (false,true)"
}, },
{ {
arguments: [{birthday: new Date(Date.UTC(2011, 6, 1, 10, 1, 55))}], arguments: [{birthday: new Date(Date.UTC(2011, 6, 1, 10, 1, 55))}],
......
...@@ -34,7 +34,7 @@ var BusterHelpers = module.exports = { ...@@ -34,7 +34,7 @@ var BusterHelpers = module.exports = {
var sequelizeOptions = { var sequelizeOptions = {
logging: options.logging, logging: options.logging,
dialect: options.dialect, dialect: options.dialect,
port: config[options.dialect].port port: process.env.SEQ_PORT || config[options.dialect].port
} }
if (process.env.DIALECT === 'postgres-native') { if (process.env.DIALECT === 'postgres-native') {
...@@ -42,9 +42,9 @@ var BusterHelpers = module.exports = { ...@@ -42,9 +42,9 @@ var BusterHelpers = module.exports = {
} }
return new Sequelize( return new Sequelize(
config[options.dialect].database, process.env.SEQ_DB || config[options.dialect].database,
config[options.dialect].username, process.env.SEQ_USER || process.env.SEQ_USERNAME || config[options.dialect].username,
config[options.dialect].password, process.env.SEQ_PW || process.env.SEQ_PASSWORD || config[options.dialect].password,
sequelizeOptions sequelizeOptions
) )
}, },
......
if(typeof require === 'function') { if(typeof require === 'function') {
const buster = require("buster") const buster = require("buster")
, semver = require("semver")
, CustomEventEmitter = require("../lib/emitters/custom-event-emitter") , CustomEventEmitter = require("../lib/emitters/custom-event-emitter")
, Helpers = require('./buster-helpers') , Helpers = require('./buster-helpers')
, config = require(__dirname + "/config/config") , config = require(__dirname + "/config/config")
...@@ -10,27 +11,22 @@ buster.spec.expose() ...@@ -10,27 +11,22 @@ buster.spec.expose()
buster.testRunner.timeout = 1000 buster.testRunner.timeout = 1000
var Sequelize = require(__dirname + '/../index') var Sequelize = require(__dirname + '/../index')
, noDomains = semver.lt(process.version, '0.8.0')
describe(Helpers.getTestDialectTeaser("Configuration"), function() { describe(Helpers.getTestDialectTeaser("Configuration"), function() {
describe('Connections problems should fail with a nice message', function() { describe('Connections problems should fail with a nice message', function() {
it('should give us an error for not having the correct server details', function(done) { it('when we don\'t have the correct server details', function(done) {
var domain if (noDomains === true) {
console.log('WARNING: Configuration specs requires NodeJS version >= 0.8 for full compatibility')
try {
domain = require('domain')
} catch (err) {
console.log('WARNING: Configuration specs requires Node version >= 0.8')
expect('').toEqual('') // Silence Buster! expect('').toEqual('') // Silence Buster!
done() return done()
} }
var d = domain.create()
var sequelize = new Sequelize(config[dialect].database, config[dialect].username, config[dialect].password, {storage: '/path/to/no/where/land', logging: false, host: '0.0.0.1', port: config[dialect].port, dialect: dialect}) var sequelize = new Sequelize(config[dialect].database, config[dialect].username, config[dialect].password, {storage: '/path/to/no/where/land', logging: false, host: '0.0.0.1', port: config[dialect].port, dialect: dialect})
d.add(sequelize.query) , domain = require('domain')
, d = domain.create()
d.on('error', function(err){ d.on('error', function(err){
d.remove(sequelize.query)
var msg = 'Failed to find SQL server. Please double check your settings.' var msg = 'Failed to find SQL server. Please double check your settings.'
if (dialect === "postgres" || dialect === "postgres-native") { if (dialect === "postgres" || dialect === "postgres-native") {
msg = 'Failed to find PostgresSQL server. Please double check your settings.' msg = 'Failed to find PostgresSQL server. Please double check your settings.'
...@@ -40,39 +36,35 @@ describe(Helpers.getTestDialectTeaser("Configuration"), function() { ...@@ -40,39 +36,35 @@ describe(Helpers.getTestDialectTeaser("Configuration"), function() {
} }
expect(err.message).toEqual(msg) expect(err.message).toEqual(msg)
d.remove(sequelize.query)
done() done()
}) })
d.run(function(){ d.run(function(){
d.add(sequelize.query)
sequelize.query('select 1 as hello') sequelize.query('select 1 as hello')
.success(function(){}) .success(function(){})
}) })
}) })
it('should give us an error for not having the correct login information', function(done) { it('when we don\'t have the correct login information', function(done) {
if (dialect !== "postgres" && dialect !== "postgres-native" && dialect !== "mysql") { if (dialect !== "postgres" && dialect !== "postgres-native" && dialect !== "mysql") {
// This dialect doesn't support incorrect login information console.log('This dialect doesn\'t support me :(')
expect('').toEqual('') // Silence Buster expect('').toEqual('') // Silence Buster
return done() return done()
} }
var domain if (noDomains === true) {
console.log('WARNING: Configuration specs requires NodeJS version >= 0.8 for full compatibility')
try {
domain = require('domain')
} catch (err) {
console.log('WARNING: Configuration specs requires Node version >= 0.8')
expect('').toEqual('') // Silence Buster! expect('').toEqual('') // Silence Buster!
done() return done()
} }
var d = domain.create()
var sequelize = new Sequelize(config[dialect].database, config[dialect].username, 'fakepass123', {logging: false, host: config[dialect].host, port: 1, dialect: dialect}) var sequelize = new Sequelize(config[dialect].database, config[dialect].username, 'fakepass123', {logging: false, host: config[dialect].host, port: 1, dialect: dialect})
d.add(sequelize.query) , domain = require('domain')
, d = domain.create()
d.on('error', function(err){ d.on('error', function(err){
d.remove(sequelize.query)
var msg = 'Failed to authenticate for SQL. Please double check your settings.' var msg = 'Failed to authenticate for SQL. Please double check your settings.'
if (dialect === "postgres" || dialect === "postgres-native") { if (dialect === "postgres" || dialect === "postgres-native") {
msg = 'Failed to authenticate for PostgresSQL. Please double check your settings.' msg = 'Failed to authenticate for PostgresSQL. Please double check your settings.'
...@@ -82,14 +74,22 @@ describe(Helpers.getTestDialectTeaser("Configuration"), function() { ...@@ -82,14 +74,22 @@ describe(Helpers.getTestDialectTeaser("Configuration"), function() {
} }
expect(err.message).toEqual(msg) expect(err.message).toEqual(msg)
d.remove(sequelize.query)
done() done()
}) })
d.run(function(){ d.run(function(){
d.add(sequelize.query)
sequelize.query('select 1 as hello') sequelize.query('select 1 as hello')
.success(function(){}) .success(function(){})
}) })
}) })
it('when we don\'t have a valid dialect.', function() {
Helpers.assertException(function() {
new Sequelize(config[dialect].database, config[dialect].username, config[dialect].password, {host: '0.0.0.1', port: config[dialect].port, dialect: undefined})
}.bind(this), 'The dialect undefined is not supported.')
})
}) })
describe('Instantiation with a URL string', function() { describe('Instantiation with a URL string', function() {
......
...@@ -14,6 +14,7 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() { ...@@ -14,6 +14,7 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() {
Helpers.initTests({ Helpers.initTests({
dialect: dialect, dialect: dialect,
beforeComplete: function(sequelize, DataTypes) { beforeComplete: function(sequelize, DataTypes) {
this.DataTypes = DataTypes
this.sequelize = sequelize this.sequelize = sequelize
this.User = sequelize.define('User', { this.User = sequelize.define('User', {
username: DataTypes.STRING, username: DataTypes.STRING,
...@@ -1060,11 +1061,36 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() { ...@@ -1060,11 +1061,36 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() {
this.User.create({ this.User.create({
username: 'barfooz' username: 'barfooz'
}).success(function(user) { }).success(function(user) {
this.user = user this.UserPrimary = this.sequelize.define('UserPrimary', {
done() specialKey: {
type: this.DataTypes.STRING,
primaryKey: true
}
})
this.UserPrimary.sync({force: true}).success(function(primary){
this.UserPrimary.create({specialKey: 'a string'}).success(function(){
this.user = user
done()
}.bind(this))
}.bind(this))
}.bind(this)) }.bind(this))
}) })
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).toEqual('a string')
done()
})
})
it('doesn\'t throw an error when entering in a non integer value', function(done) {
this.User.find('a string value').success(function(user) {
expect(user).toBeNull()
done()
})
})
it('returns a single dao', function(done) { it('returns a single dao', function(done) {
this.User.find(this.user.id).success(function(user) { this.User.find(this.user.id).success(function(user) {
expect(Array.isArray(user)).toBeFalsy() expect(Array.isArray(user)).toBeFalsy()
......
...@@ -91,6 +91,22 @@ describe(Helpers.getTestDialectTeaser("Sequelize"), function() { ...@@ -91,6 +91,22 @@ describe(Helpers.getTestDialectTeaser("Sequelize"), function() {
}.bind(this)) }.bind(this))
}) })
it('executes select queries correctly when quoteIdentifiers is false', function(done) {
this.sequelize.options.quoteIdentifiers = false
this.sequelize.query(this.insertQuery).success(function() {
this.sequelize
.query("select * from " + qq(this.User.tableName) + "")
.complete(function(err, users) {
if (err) {
console.log(err)
}
expect(err).toBeNull()
expect(users.map(function(u){ return u.username })).toEqual(['john'])
done()
})
}.bind(this))
})
it('executes select query and parses dot notation results', function(done) { it('executes select query and parses dot notation results', function(done) {
this.sequelize.query(this.insertQuery).success(function() { this.sequelize.query(this.insertQuery).success(function() {
this.sequelize this.sequelize
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!