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

Commit ab9a8364 by Sascha Depold

Merge branch 'milestones/2.0.0' of github.com:sequelize/sequelize into milestones/2.0.0

2 parents 5d1a7739 e011a7d2
Showing with 939 additions and 3137 deletions
{ {
"globals": { "globals": {
"jasmine": false,
"spyOn": false, "spyOn": false,
"it": false, "it": false,
"console": false, "console": false,
......
...@@ -3,8 +3,7 @@ before_script: ...@@ -3,8 +3,7 @@ before_script:
- "psql -c 'create database sequelize_test;' -U postgres" - "psql -c 'create database sequelize_test;' -U postgres"
script: script:
- "npm run test-buster-travis" - "make test"
- "npm run test-jasmine"
notifications: notifications:
email: email:
......
REPORTER ?= dot
TESTS = $(shell find ./test/* -name "*.test.js")
DIALECT ?= mysql
# test commands
teaser:
@echo "" && \
node -pe "Array(20 + '$(DIALECT)'.length + 3).join('#')" && \
echo '# Running tests for $(DIALECT) #' && \
node -pe "Array(20 + '$(DIALECT)'.length + 3).join('#')" && \
echo ''
test:
@make teaser && \
./node_modules/mocha/bin/mocha \
--colors \
--reporter $(REPORTER) \
$(TESTS)
sqlite:
@DIALECT=sqlite make test
mysql:
@DIALECT=mysql make test
postgres:
@DIALECT=postgres make test
postgres-native:
@DIALECT=postgres-native make test
# test aliases
pgsql: postgres
postgresn: postgres-native
# test all the dialects \o/
all: sqlite mysql postgres postgres-native
.PHONY: sqlite mysql postgres pgsql postgres-native postgresn all test
\ No newline at end of file
# Sequelize # # Sequelize [![Build Status](https://secure.travis-ci.org/sequelize/sequelize.png)](http://travis-ci.org/sequelize/sequelize) [![Dependency Status](https://david-dm.org/sequelize/sequelize.png)](https://david-dm.org/sequelize/sequelize) [![Dependency Status](https://david-dm.org/sequelize/sequelize.png)](https://david-dm.org/sequelize/sequelize) [![Flattr this](http://api.flattr.com/button/flattr-badge-large.png)](http://flattr.com/thing/1259407/Sequelize) #
The Sequelize library provides easy access to MySQL, SQLite or PostgreSQL databases by mapping database entries to objects and vice versa. To put it in a nutshell... it's an ORM (Object-Relational-Mapper). The library is written entirely in JavaScript and can be used in the Node.JS environment. MySQL, PostgresSQL, and SQLite Object Relational Mapper (ORM) for [node](http://nodejs.org).
<a href="http://flattr.com/thing/1259407/Sequelize" target="_blank">
<img src="http://api.flattr.com/button/flattr-badge-large.png" alt="Flattr this" title="Flattr this" border="0" /></a>
## Important Notes ## ## Important Notes ##
...@@ -35,6 +32,7 @@ changelog of the branch: https://github.com/sequelize/sequelize/blob/milestones/ ...@@ -35,6 +32,7 @@ changelog of the branch: https://github.com/sequelize/sequelize/blob/milestones/
- Asynchronous library - Asynchronous library
- Associations - Associations
- Importing definitions from single files - Importing definitions from single files
- Promises
## Documentation and Updates ## ## Documentation and Updates ##
...@@ -64,7 +62,7 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl ...@@ -64,7 +62,7 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl
- MariaDB support - MariaDB support
- ~~Support for update and delete calls for whole tables without previous loading of instances~~ Implemented in [#569](https://github.com/sequelize/sequelize/pull/569) thanks to @optiltude - ~~Support for update and delete calls for whole tables without previous loading of instances~~ Implemented in [#569](https://github.com/sequelize/sequelize/pull/569) thanks to @optiltude
- Eager loading of nested associations [#388](https://github.com/sdepold/sequelize/issues/388#issuecomment-12019099) - Eager loading of nested associations [#388](https://github.com/sdepold/sequelize/issues/388#issuecomment-12019099)
- Model#delete - ~~Model#delete~~ (renamed to [Model.destroy()](http://sequelizejs.com/documentation#instances-destroy))
- ~~Validate a model before it gets saved.~~ Implemented in [#601](https://github.com/sequelize/sequelize/pull/601), thanks to @durango - ~~Validate a model before it gets saved.~~ Implemented in [#601](https://github.com/sequelize/sequelize/pull/601), thanks to @durango
- Move validation of enum attribute value to validate method - Move validation of enum attribute value to validate method
- BLOB [#99](https://github.com/sequelize/sequelize/issues/99) - BLOB [#99](https://github.com/sequelize/sequelize/issues/99)
...@@ -141,32 +139,27 @@ $ npm install ...@@ -141,32 +139,27 @@ $ npm install
### 4. Run the tests ### ### 4. Run the tests ###
Right now, the test base is split into the `spec` folder (which contains the Right now, the test base is split into the `test` folder (which contains the
lovely [BusterJS](http://busterjs.org) tests) and the `spec-jasmine` folder lovely [Mocha](http://visionmedia.github.io/mocha/) tests).
(which contains the ugly and awkward node-jasmine based tests). A main goal
is to get rid of the jasmine tests!
As you might haven't installed all of the supported SQL dialects, here is how As you might haven't installed all of the supported SQL dialects, here is how
to run the test suites for your development environment: to run the test suites for your development environment:
```console ```console
$ # run all tests at once: $ # run all tests at once:
$ npm test $ make all
$ # run only the jasmine tests (for all dialects):
$ npm run test-jasmine
$ # run all of the buster specs (for all dialects):
$ npm run test-buster
$ # run the buster specs for mysql: $ # run the buster specs for mysql:
$ npm run test-buster-mysql $ make mysql
$ # run the buster specs for sqlite: $ # run the buster specs for sqlite:
$ npm run test-buster-sqlite $ make sqlite
$ # run the buster specs for postgresql: $ # run the buster specs for postgresql:
$ npm run test-buster-postgres $ make pgsql
$ # alternatively you can pass database credentials with $variables when testing
$ DIALECT=dialect SEQ_DB=database SEQ_USER=user SEQ_PW=password make test
``` ```
### 5. That's all ### ### 5. That's all ###
...@@ -235,6 +228,17 @@ for (var key in obj) { ...@@ -235,6 +228,17 @@ for (var key in obj) {
```js ```js
{ {
"globals": {
"spyOn": false,
"it": false,
"console": false,
"describe": false,
"expect": false,
"beforeEach": false,
"waits": false,
"waitsFor": false,
"runs": false
},
"camelcase": true, "camelcase": true,
"curly": true, "curly": true,
"forin": true, "forin": true,
...@@ -246,10 +250,3 @@ for (var key in obj) { ...@@ -246,10 +250,3 @@ for (var key in obj) {
"es5": true "es5": true
} }
``` ```
# Build status
The automated tests we talk about just so much are running on
[Travis public CI](http://travis-ci.org), here is its status:
[![Build Status](https://secure.travis-ci.org/sequelize/sequelize.png)](http://travis-ci.org/sequelize/sequelize)
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
- [DEPENDENCIES] Upgraded validator for IPv6 support. [#603](https://github.com/sequelize/sequelize/pull/603). thanks to durango - [DEPENDENCIES] Upgraded validator for IPv6 support. [#603](https://github.com/sequelize/sequelize/pull/603). thanks to durango
- [DEPENDENCIES] replaced underscore by lodash. [#954](https://github.com/sequelize/sequelize/pull/594). thanks to durango - [DEPENDENCIES] replaced underscore by lodash. [#954](https://github.com/sequelize/sequelize/pull/594). thanks to durango
- [DEPENDENCIES] Upgraded pg to 2.0.0. [#711](https://github.com/sequelize/sequelize/pull/711). thanks to durango - [DEPENDENCIES] Upgraded pg to 2.0.0. [#711](https://github.com/sequelize/sequelize/pull/711). thanks to durango
- [DEPENDENCIES] Upgraded command to 2.0.0 and generic-pool to 2.0.4. thanks to durango
- [DEPENDENCIES] No longer require semver. thanks to durango
- [BUG] Fix string escape with postgresql on raw SQL queries. [#586](https://github.com/sequelize/sequelize/pull/586). thanks to zanamixx - [BUG] Fix string escape with postgresql on raw SQL queries. [#586](https://github.com/sequelize/sequelize/pull/586). thanks to zanamixx
- [BUG] "order by" is now after "group by". [#585](https://github.com/sequelize/sequelize/pull/585). thanks to mekanics - [BUG] "order by" is now after "group by". [#585](https://github.com/sequelize/sequelize/pull/585). thanks to mekanics
- [BUG] Added decimal support for min/max. [#583](https://github.com/sequelize/sequelize/pull/583). thanks to durango - [BUG] Added decimal support for min/max. [#583](https://github.com/sequelize/sequelize/pull/583). thanks to durango
...@@ -21,8 +23,11 @@ ...@@ -21,8 +23,11 @@
- [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] 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] bulkCreate would have problems with a disparate field list [#738](https://github.com/sequelize/sequelize/pull/738). 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 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 - [BUG] Fixed SQL escaping with sqlite and unified escaping [#700](https://github.com/sequelize/sequelize/pull/700). thanks to PiPeep
- [BUG] Fixed Postgres' pools [ff57af63](https://github.com/sequelize/sequelize/commit/ff57af63c2eb395b4828a5984a22984acdc2a5e1)
- [BUG] Fixed BLOB/TEXT columns having a default value declared in MySQL [#793](https://github.com/sequelize/sequelize/pull/793). thanks to durango
- [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
...@@ -42,8 +47,12 @@ ...@@ -42,8 +47,12 @@
- [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] 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 - [FEATURE] Added support for where clauses containing !=, < etc. and support for date ranges [#727](https://github.com/sequelize/sequelize/pull/727). Thanks to durango
- [FEATURE] Added support for model instances being referenced [#761](https://github.com/sequelize/sequelize/pull/761) thanks to sdepold
- [FEATURE] Added support for specifying the path to load a module for a dialect. [#766](https://github.com/sequelize/sequelize/pull/766) thanks to sonnym.
- [FEATURE] Drop index if exists has been added to sqlite [#766](https://github.com/sequelize/sequelize/pull/776) thanks to coderbuzz
- [REFACTORING] hasMany now uses a single SQL statement when creating and destroying associations, instead of removing each association seperately [690](https://github.com/sequelize/sequelize/pull/690). Inspired by [#104](https://github.com/sequelize/sequelize/issues/104). janmeier - [REFACTORING] hasMany now uses a single SQL statement when creating and destroying associations, instead of removing each association seperately [690](https://github.com/sequelize/sequelize/pull/690). Inspired by [#104](https://github.com/sequelize/sequelize/issues/104). janmeier
- [REFACTORING] Consistent handling of offset across dialects. Offset is now always applied, and limit is set to max table size of not limit is given [#725](https://github.com/sequelize/sequelize/pull/725). janmeier - [REFACTORING] Consistent handling of offset across dialects. Offset is now always applied, and limit is set to max table size of not limit is given [#725](https://github.com/sequelize/sequelize/pull/725). janmeier
- [REFACTORING] Moved Jasmine to Buster and then Buster to Mocha + Chai. sdepold and durango
# 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
......
...@@ -21,12 +21,26 @@ module.exports = (function() { ...@@ -21,12 +21,26 @@ module.exports = (function() {
where[self.__factory.connectorDAO.tableName+"."+foreignKey] = {join: self.__factory.target.tableName+".id"} where[self.__factory.connectorDAO.tableName+"."+foreignKey] = {join: self.__factory.target.tableName+".id"}
if (options.where) { if (options.where) {
if (Array.isArray(options.where)) {
smart = Utils.smartWhere([where, options.where], self.__factory.target.daoFactoryManager.sequelize.options.dialect)
smart = Utils.compileSmartWhere.call(self.__factory.target, smart, self.__factory.target.daoFactoryManager.sequelize.options.dialect)
if (smart.length > 0) {
options.where = smart
}
} else {
Utils._.each(options.where, function(value, index) { Utils._.each(options.where, function(value, index) {
delete options.where[index]; delete options.where[index];
smart = Utils.smartWhere(value, self.__factory.target.daoFactoryManager.sequelize.options.dialect)
smart = Utils.compileSmartWhere.call(self.__factory.target, smart)
if (smart.length > 0) {
value = smart
}
options.where[self.__factory.target.tableName+"."+index] = value; options.where[self.__factory.target.tableName+"."+index] = value;
}); });
Utils._.extend(options.where, where) options.where = Utils._.extend(options.where, where)
}
} else { } else {
options.where = where; options.where = where;
} }
......
...@@ -12,8 +12,17 @@ module.exports = (function() { ...@@ -12,8 +12,17 @@ module.exports = (function() {
where[this.__factory.identifier] = this.instance.id where[this.__factory.identifier] = this.instance.id
options.where = options.where ? Utils._.extend(options.where, where) : where if (options.where) {
return this.__factory.target.findAll(options) smart = Utils.smartWhere([where, options.where], this.__factory.target.daoFactoryManager.sequelize.options.dialect)
smart = Utils.compileSmartWhere.call(this.__factory.target, smart)
if (smart.length > 0) {
options.where = smart
}
} else {
options.where = where
}
return this.__factory.target.all(options)
} }
HasManySingleLinked.prototype.injectSetter = function(emitter, oldAssociations, newAssociations) { HasManySingleLinked.prototype.injectSetter = function(emitter, oldAssociations, newAssociations) {
...@@ -34,7 +43,7 @@ module.exports = (function() { ...@@ -34,7 +43,7 @@ module.exports = (function() {
if (obsoleteAssociations.length > 0) { if (obsoleteAssociations.length > 0) {
// clear the old associations // clear the old associations
var obsoleteIds = obsoleteAssociations.map(function(associatedObject) { var obsoleteIds = obsoleteAssociations.map(function(associatedObject) {
associatedObject[self.__factory.identifier] = null associatedObject[self.__factory.identifier] = (newAssociations.length < 1 ? null : self.instance.id)
return associatedObject.id return associatedObject.id
}) })
...@@ -46,12 +55,12 @@ module.exports = (function() { ...@@ -46,12 +55,12 @@ module.exports = (function() {
if (unassociatedObjects.length > 0) { if (unassociatedObjects.length > 0) {
// set the new associations // set the new associations
var unassociatedIds = unassociatedObjects.map(function(associatedObject) { var unassociatedIds = unassociatedObjects.map(function(associatedObject) {
associatedObject[self.__factory.identifier] = self.instance.id associatedObject[self.__factory.identifier] = (newAssociations.length < 1 ? null : self.instance.id)
return associatedObject.id return associatedObject.id
}) })
update = {} update = {}
update[self.__factory.identifier] = self.instance.id update[self.__factory.identifier] = (newAssociations.length < 1 ? null : self.instance.id)
chainer.add(this.__factory.target.update(update, { id: unassociatedIds })) chainer.add(this.__factory.target.update(update, { id: unassociatedIds }))
} }
......
...@@ -56,6 +56,12 @@ module.exports = (function() { ...@@ -56,6 +56,12 @@ module.exports = (function() {
params = {where: where} params = {where: where}
} }
smart = Utils.smartWhere([where, params.where || []], self.target.daoFactoryManager.sequelize.options.dialect)
smart = Utils.compileSmartWhere.call(self.target, smart)
if (smart.length > 0) {
params.where = smart
}
return self.target.find(params) return self.target.find(params)
} }
...@@ -64,17 +70,25 @@ module.exports = (function() { ...@@ -64,17 +70,25 @@ module.exports = (function() {
HasOne.prototype.injectSetter = function(obj) { HasOne.prototype.injectSetter = function(obj) {
var self = this var self = this
, options = self.options || {}
obj[this.accessors.set] = function(associatedObject) { obj[this.accessors.set] = function(associatedObject) {
var instance = this; var instance = this
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
instance[self.accessors.get]().success(function(oldObj) { instance[self.accessors.get]().success(function(oldObj) {
if (oldObj) { if (oldObj) {
oldObj[self.identifier] = null oldObj[self.identifier] = null
oldObj.save() oldObj.save().success(function() {
if (associatedObject) {
associatedObject[self.identifier] = instance.id
associatedObject
.save()
.success(function() { emitter.emit('success', associatedObject) })
.error(function(err) { emitter.emit('error', err) })
} else {
emitter.emit('success', null)
} }
})
} else {
if (associatedObject) { if (associatedObject) {
associatedObject[self.identifier] = instance.id associatedObject[self.identifier] = instance.id
associatedObject associatedObject
...@@ -84,7 +98,7 @@ module.exports = (function() { ...@@ -84,7 +98,7 @@ module.exports = (function() {
} else { } else {
emitter.emit('success', null) emitter.emit('success', null)
} }
}
}) })
}).run() }).run()
} }
......
var Toposort = require('toposort-class') var Toposort = require('toposort-class')
, DaoFactory = require('./dao-factory')
module.exports = (function() { module.exports = (function() {
var DAOFactoryManager = function(sequelize) { var DAOFactoryManager = function(sequelize) {
...@@ -43,12 +44,13 @@ module.exports = (function() { ...@@ -43,12 +44,13 @@ module.exports = (function() {
, sorter = new Toposort() , sorter = new Toposort()
this.daos.forEach(function(dao) { this.daos.forEach(function(dao) {
daos[dao.tableName] = dao
var deps = [] var deps = []
for(var attrName in dao.rawAttributes) { daos[dao.tableName] = dao
if(dao.rawAttributes.hasOwnProperty(attrName)) {
if(dao.rawAttributes[attrName].references) { for (var attrName in dao.rawAttributes) {
if (dao.rawAttributes.hasOwnProperty(attrName)) {
if (dao.rawAttributes[attrName].references) {
deps.push(dao.rawAttributes[attrName].references) deps.push(dao.rawAttributes[attrName].references)
} }
} }
......
...@@ -5,8 +5,6 @@ var Utils = require("./utils") ...@@ -5,8 +5,6 @@ var Utils = require("./utils")
module.exports = (function() { module.exports = (function() {
var DAOFactory = function(name, attributes, options) { var DAOFactory = function(name, attributes, options) {
var self = this
this.options = Utils._.extend({ this.options = Utils._.extend({
timestamps: true, timestamps: true,
instanceMethods: {}, instanceMethods: {},
...@@ -24,18 +22,25 @@ module.exports = (function() { ...@@ -24,18 +22,25 @@ module.exports = (function() {
// error check options // error check options
Utils._.each(options.validate, function(validator, validatorType) { Utils._.each(options.validate, function(validator, validatorType) {
if (Utils._.contains(Utils._.keys(attributes), validatorType)) if (Utils._.contains(Utils._.keys(attributes), validatorType)) {
throw new Error("A model validator function must not have the same name as a field. Model: " + name + ", field/validation name: " + validatorType) throw new Error("A model validator function must not have the same name as a field. Model: " + name + ", field/validation name: " + validatorType)
if (!Utils._.isFunction(validator)) }
if (!Utils._.isFunction(validator)) {
throw new Error("Members of the validate option must be functions. Model: " + name + ", error with validate member " + validatorType) throw new Error("Members of the validate option must be functions. Model: " + name + ", error with validate member " + validatorType)
}
}) })
this.name = name this.name = name
if (!this.options.tableName) { if (!this.options.tableName) {
this.tableName = this.options.freezeTableName ? name : Utils.pluralize(name, this.options.language) this.tableName = this.options.freezeTableName ? name : Utils.pluralize(name, this.options.language)
} else { } else {
this.tableName = this.options.tableName this.tableName = this.options.tableName
} }
attributes = replaceReferencesWithTableNames(attributes)
this.rawAttributes = attributes this.rawAttributes = attributes
this.daoFactoryManager = null // defined in init function this.daoFactoryManager = null // defined in init function
this.associations = {} this.associations = {}
...@@ -59,8 +64,8 @@ module.exports = (function() { ...@@ -59,8 +64,8 @@ module.exports = (function() {
var self = this; var self = this;
this.daoFactoryManager = daoFactoryManager this.daoFactoryManager = daoFactoryManager
this.primaryKeys = {}; this.primaryKeys = {};
Utils._.each(this.attributes, function(dataTypeString, attributeName) { Utils._.each(this.attributes, function(dataTypeString, attributeName) {
if ((attributeName !== 'id') && (dataTypeString.indexOf('PRIMARY KEY') !== -1)) { if ((attributeName !== 'id') && (dataTypeString.indexOf('PRIMARY KEY') !== -1)) {
self.primaryKeys[attributeName] = dataTypeString self.primaryKeys[attributeName] = dataTypeString
...@@ -75,9 +80,11 @@ module.exports = (function() { ...@@ -75,9 +80,11 @@ module.exports = (function() {
findAutoIncrementField.call(this) findAutoIncrementField.call(this)
// DAO prototype // DAO prototype
// WTF ... ?
this.DAO = function() { this.DAO = function() {
DAO.apply(this, arguments); DAO.apply(this, arguments);
}; }
Util.inherits(this.DAO, DAO); Util.inherits(this.DAO, DAO);
this.DAO.prototype.rawAttributes = this.rawAttributes; this.DAO.prototype.rawAttributes = this.rawAttributes;
...@@ -89,47 +96,48 @@ module.exports = (function() { ...@@ -89,47 +96,48 @@ module.exports = (function() {
} }
Utils._.each(['Get', 'Set'], function(type) { Utils._.each(['Get', 'Set'], function(type) {
var prop = type.toLowerCase(), var prop = type.toLowerCase()
opt = prop + 'terMethods', , opt = prop + 'terMethods'
meth = '__define' + type + 'ter__', , meth = '__define' + type + 'ter__'
funcs = Utils._.isObject(self.options[opt]) ? self.options[opt] : {} , funcs = Utils._.isObject(self.options[opt]) ? self.options[opt] : {}
;
Utils._.each(self.rawAttributes, function(attr, name) { Utils._.each(self.rawAttributes, function(attr, name) {
if (attr.hasOwnProperty(prop)) if (attr.hasOwnProperty(prop)) {
funcs[name] = attr[prop] funcs[name] = attr[prop]
}); }
})
Utils._.each(funcs, function(fct, name) { Utils._.each(funcs, function(fct, name) {
if (!Utils._.isFunction(fct)) if (!Utils._.isFunction(fct)) {
throw new Error(type + 'ter for "' + name + '" is not a function.') throw new Error(type + 'ter for "' + name + '" is not a function.')
}
self.DAO.prototype[meth](name, fct); self.DAO.prototype[meth](name, fct)
}) })
}) })
this.DAO.prototype.attributes = Object.keys(this.DAO.prototype.rawAttributes); this.DAO.prototype.attributes = Object.keys(this.DAO.prototype.rawAttributes);
this.DAO.prototype.booleanValues = []
this.DAO.prototype.defaultValues = {}
this.DAO.prototype.validators = {}
this.DAO.prototype.booleanValues = [];
this.DAO.prototype.defaultValues = {};
this.DAO.prototype.validators = {};
Utils._.each(this.rawAttributes, function (definition, name) { Utils._.each(this.rawAttributes, function (definition, name) {
if (((definition === DataTypes.BOOLEAN) || (definition.type === DataTypes.BOOLEAN))) { if (((definition === DataTypes.BOOLEAN) || (definition.type === DataTypes.BOOLEAN))) {
self.DAO.prototype.booleanValues.push(name); self.DAO.prototype.booleanValues.push(name);
} }
if (definition.hasOwnProperty('defaultValue')) { if (definition.hasOwnProperty('defaultValue')) {
self.DAO.prototype.defaultValues[name] = function() { self.DAO.prototype.defaultValues[name] = function() {
return Utils.toDefaultValue(definition.defaultValue); return Utils.toDefaultValue(definition.defaultValue)
} }
} }
if (definition.hasOwnProperty('validate')) { if (definition.hasOwnProperty('validate')) {
self.DAO.prototype.validators[name] = definition.validate; self.DAO.prototype.validators[name] = definition.validate;
} }
}); })
this.DAO.prototype.__factory = this; this.DAO.prototype.__factory = this
this.DAO.prototype.hasDefaultValues = !Utils._.isEmpty(this.DAO.prototype.defaultValues); this.DAO.prototype.hasDefaultValues = !Utils._.isEmpty(this.DAO.prototype.defaultValues)
return this return this
} }
...@@ -432,39 +440,26 @@ module.exports = (function() { ...@@ -432,39 +440,26 @@ module.exports = (function() {
, updatedAtAttr = self.options.underscored ? 'updated_at' : 'updatedAt' , updatedAtAttr = self.options.underscored ? 'updated_at' : 'updatedAt'
, createdAtAttr = self.options.underscored ? 'created_at' : 'createdAt' , createdAtAttr = self.options.underscored ? 'created_at' : 'createdAt'
fields = fields || []
// we will re-create from DAOs, which may have set up default attributes // we will re-create from DAOs, which may have set up default attributes
records = [] records = []
var found = false
if (fields) {
// Always insert updated and created time stamps
if (self.options.timestamps) {
if (fields.indexOf(updatedAtAttr) === -1) {
fields.push(updatedAtAttr)
}
if (fields.indexOf(createdAtAttr) === -1) {
fields.push(createdAtAttr)
}
}
// Build records for the fields we know about
daos.forEach(function(dao) { daos.forEach(function(dao) {
var values = {}; var values = fields.length > 0 ? {} : dao.dataValues
fields.forEach(function(field) { fields.forEach(function(field) {
values[field] = dao.dataValues[field] values[field] = dao.dataValues[field]
}) })
if (self.options.timestamps) { if (self.options.timestamps) {
values[createdAtAttr] = Utils.now()
values[updatedAtAttr] = Utils.now() values[updatedAtAttr] = Utils.now()
} }
records.push(values);
})
} else { records.push(values);
daos.forEach(function(dao) {
records.push(dao.dataValues)
}) })
}
// Validate enums // Validate enums
records.forEach(function(values) { records.forEach(function(values) {
...@@ -520,6 +515,10 @@ module.exports = (function() { ...@@ -520,6 +515,10 @@ module.exports = (function() {
return this.QueryInterface.bulkUpdate(this.tableName, attrValueHash, where) return this.QueryInterface.bulkUpdate(this.tableName, attrValueHash, where)
} }
DAOFactory.prototype.describe = function() {
return this.QueryInterface.describeTable(this.tableName)
}
// private // private
var query = function() { var query = function() {
...@@ -627,6 +626,16 @@ module.exports = (function() { ...@@ -627,6 +626,16 @@ module.exports = (function() {
} }
} }
var replaceReferencesWithTableNames = function(attributes) {
Object.keys(attributes).forEach(function(attrName) {
if (attributes[attrName].references instanceof DAOFactory) {
attributes[attrName].references = attributes[attrName].references.tableName
}
})
return attributes
}
Utils._.extend(DAOFactory.prototype, require("./associations/mixin")) Utils._.extend(DAOFactory.prototype, require("./associations/mixin"))
return DAOFactory return DAOFactory
......
...@@ -399,6 +399,20 @@ module.exports = (function() { ...@@ -399,6 +399,20 @@ module.exports = (function() {
for (key in attrs) { for (key in attrs) {
this.addAttribute(key, attrs[key]) this.addAttribute(key, attrs[key])
} }
// this.addAttributes COMPLETELY destroys the structure of our DAO due to __defineGetter__ resetting the object
// so now we have to rebuild for bulkInserts, bulkUpdates, etc.
var rebuild = {}
// Get the correct map....
Utils._.each(this.attributes, function(key) {
if (this.dataValues.hasOwnProperty(key)) {
rebuild[key] = this.dataValues[key]
}
}.bind(this))
// This allows for aliases, etc.
this.dataValues = Utils._.extend(rebuild, this.dataValues)
} }
return DAO return DAO
......
...@@ -4,11 +4,18 @@ var mysql ...@@ -4,11 +4,18 @@ var mysql
, Utils = require("../../utils") , Utils = require("../../utils")
, without = function(arr, elem) { return arr.filter(function(e) { return e != elem }) } , without = function(arr, elem) { return arr.filter(function(e) { return e != elem }) }
try { mysql = require("mysql") } catch (err) {
console.log("You need to install mysql package manually"); }
module.exports = (function() { module.exports = (function() {
var ConnectorManager = function(sequelize, config) { var ConnectorManager = function(sequelize, config) {
try {
if (config.dialectModulePath) {
mysql = require(config.dialectModulePath)
} else {
mysql = require('mysql')
}
} catch (err) {
console.log('You need to install mysql package manually')
}
this.sequelize = sequelize this.sequelize = sequelize
this.client = null this.client = null
this.config = config || {} this.config = config || {}
...@@ -75,7 +82,9 @@ module.exports = (function() { ...@@ -75,7 +82,9 @@ module.exports = (function() {
read: Pooling.Pool({ read: Pooling.Pool({
name: 'sequelize-read', name: 'sequelize-read',
create: function (done) { create: function (done) {
if (reads >= self.config.replication.read.length) reads = 0; if (reads >= self.config.replication.read.length) {
reads = 0
}
var config = self.config.replication.read[reads++]; var config = self.config.replication.read[reads++];
connect.call(self, function (err, connection) { connect.call(self, function (err, connection) {
...@@ -161,11 +170,12 @@ module.exports = (function() { ...@@ -161,11 +170,12 @@ module.exports = (function() {
query.done(function() { query.done(function() {
self.pendingQueries--; self.pendingQueries--;
if (self.pool) self.pool.release(query.client); if (self.pool) {
else { self.pool.release(query.client);
} else {
if (self.pendingQueries === 0) { if (self.pendingQueries === 0) {
setTimeout(function() { setTimeout(function() {
self.pendingQueries === 0 && self.disconnect.call(self); self.pendingQueries === 0 && self.disconnect.call(self)
}, 100); }, 100);
} }
} }
...@@ -173,16 +183,16 @@ module.exports = (function() { ...@@ -173,16 +183,16 @@ module.exports = (function() {
if (!this.pool) { if (!this.pool) {
query.run(sql); query.run(sql);
} } else {
else {
this.pool.acquire(function(err, client) { this.pool.acquire(function(err, client) {
if (err) return query.emit('error', err); if (err) {
return query.emit('error', err)
}
query.client = client; query.client = client
query.run(sql); query.run(sql)
return; return;
}, undefined, options.type); }, undefined, options.type)
} }
return query; return query;
...@@ -202,8 +212,10 @@ module.exports = (function() { ...@@ -202,8 +212,10 @@ module.exports = (function() {
}; };
ConnectorManager.prototype.disconnect = function() { ConnectorManager.prototype.disconnect = function() {
if (this.client) disconnect.call(this, this.client); if (this.client) {
return; disconnect.call(this, this.client)
}
return
}; };
...@@ -258,12 +270,12 @@ module.exports = (function() { ...@@ -258,12 +270,12 @@ module.exports = (function() {
switch(err.code) { switch(err.code) {
case 'ECONNREFUSED': case 'ECONNREFUSED':
case 'ER_ACCESS_DENIED_ERROR': case 'ER_ACCESS_DENIED_ERROR':
emitter.emit('error', new Error("Failed to authenticate for MySQL. Please double check your settings.")) emitter.emit('error', 'Failed to authenticate for MySQL. Please double check your settings.')
break break
case 'ENOTFOUND': case 'ENOTFOUND':
case 'EHOSTUNREACH': case 'EHOSTUNREACH':
case 'EINVAL': case 'EINVAL':
emitter.emit('error', new Error("Failed to find MySQL server. Please double check your settings.")) emitter.emit('error', 'Failed to find MySQL server. Please double check your settings.')
break break
} }
} }
...@@ -288,7 +300,7 @@ module.exports = (function() { ...@@ -288,7 +300,7 @@ module.exports = (function() {
} }
var validateConnection = function(client) { var validateConnection = function(client) {
return client && client.state != 'disconnected' return client && client.state !== 'disconnected'
} }
var enqueue = function(queueItem, options) { var enqueue = function(queueItem, options) {
...@@ -335,8 +347,6 @@ module.exports = (function() { ...@@ -335,8 +347,6 @@ module.exports = (function() {
} }
var afterQuery = function(queueItem) { var afterQuery = function(queueItem) {
var self = this
dequeue.call(this, queueItem) dequeue.call(this, queueItem)
transferQueuedItems.call(this, this.maxConcurrentQueries - this.activeQueue.length) transferQueuedItems.call(this, this.maxConcurrentQueries - this.activeQueue.length)
disconnectIfNoConnections.call(this) disconnectIfNoConnections.call(this)
......
...@@ -29,9 +29,8 @@ module.exports = (function() { ...@@ -29,9 +29,8 @@ module.exports = (function() {
return Utils._.template(query)({}) return Utils._.template(query)({})
}, },
dropSchema: function() { dropSchema: function(tableName, options) {
var query = "SHOW TABLES" return QueryGenerator.dropTableQuery(tableName, options)
return Utils._.template(query)({})
}, },
showSchemasQuery: function() { showSchemasQuery: function() {
...@@ -521,7 +520,8 @@ module.exports = (function() { ...@@ -521,7 +520,8 @@ module.exports = (function() {
template += " auto_increment" template += " auto_increment"
} }
if ((dataType.defaultValue !== undefined) && (dataType.defaultValue != DataTypes.NOW)) { // Blobs/texts cannot have a defaultValue
if (dataType.type !== "TEXT" && dataType.type._binary !== true && (dataType.defaultValue !== undefined) && (dataType.defaultValue != DataTypes.NOW)) {
template += " DEFAULT " + this.escape(dataType.defaultValue) template += " DEFAULT " + this.escape(dataType.defaultValue)
} }
...@@ -536,7 +536,6 @@ module.exports = (function() { ...@@ -536,7 +536,6 @@ module.exports = (function() {
if(dataType.references) { if(dataType.references) {
template += " REFERENCES " + this.quoteIdentifier(dataType.references) template += " REFERENCES " + this.quoteIdentifier(dataType.references)
if(dataType.referencesKey) { if(dataType.referencesKey) {
template += " (" + this.quoteIdentifier(dataType.referencesKey) + ")" template += " (" + this.quoteIdentifier(dataType.referencesKey) + ")"
} else { } else {
......
...@@ -3,12 +3,15 @@ var Query = require("./query") ...@@ -3,12 +3,15 @@ var Query = require("./query")
module.exports = (function() { module.exports = (function() {
var ConnectorManager = function(sequelize, config) { var ConnectorManager = function(sequelize, config) {
var pgModule = config.dialectModulePath || 'pg'
this.sequelize = sequelize this.sequelize = sequelize
this.client = null this.client = null
this.config = config || {} this.config = config || {}
this.config.port = this.config.port || 5432 this.config.port = this.config.port || 5432
this.pooling = (!!this.config.poolCfg && (this.config.poolCfg.maxConnections > 0)) this.pooling = (!!this.config.pool && (this.config.pool.maxConnections > 0))
this.pg = this.config.native ? require('pg').native : require('pg') this.pg = this.config.native ? require(pgModule).native : require(pgModule)
this.poolIdentifier = null
// Better support for BigInts // Better support for BigInts
// https://github.com/brianc/node-postgres/issues/166#issuecomment-9514935 // https://github.com/brianc/node-postgres/issues/166#issuecomment-9514935
...@@ -16,52 +19,60 @@ module.exports = (function() { ...@@ -16,52 +19,60 @@ module.exports = (function() {
// 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.pool.maxConnections
this.pg.defaults.poolIdleTimeout = this.config.poolCfg.maxIdleTime this.pg.defaults.poolIdleTimeout = this.config.pool.maxIdleTime
} }
this.disconnectTimeoutId = null this.disconnectTimeoutId = null
this.pendingQueries = 0 this.pendingQueries = 0
this.maxConcurrentQueries = (this.config.maxConcurrentQueries || 50) this.maxConcurrentQueries = (this.config.maxConcurrentQueries || 50)
process.on('exit', function() {
this.disconnect()
}.bind(this))
} }
Utils._.extend(ConnectorManager.prototype, require("../connector-manager").prototype) Utils._.extend(ConnectorManager.prototype, require("../connector-manager").prototype)
var isConnecting = false ConnectorManager.prototype.endQuery = function() {
var isConnected = false
ConnectorManager.prototype.query = function(sql, callee, options) {
var self = this var self = this
if (this.client === null) { if (!self.pooling && self.pendingQueries === 0) {
this.connect() setTimeout(function() {
self.pendingQueries === 0 && self.disconnect.call(self)
}, 100)
} }
var query = new Query(this.client, this.sequelize, callee, options || {})
self.pendingQueries += 1
return query.run(sql)
.success(function() { self.endQuery.call(self) })
.error(function() { self.endQuery.call(self) })
} }
ConnectorManager.prototype.endQuery = function() { ConnectorManager.prototype.query = function(sql, callee, options) {
var self = this var self = this
self.pendingQueries -= 1
if (self.pendingQueries == 0) { self.pendingQueries++
setTimeout(function() {
self.pendingQueries == 0 && self.disconnect.call(self) return new Utils.CustomEventEmitter(function(emitter) {
}, 100) self.connect()
} .on('error', function(err) {
emitter.emit('error', err)
})
.on('success', function(done) {
var query = new Query(self.client, self.sequelize, callee, options || {})
done = done || null
query.run(sql, done)
.success(function(results) { emitter.emit('success', results); self.endQuery.call(self) })
.error(function(err) { emitter.emit('error', err); self.endQuery.call(self) })
.on('sql', function(sql) { emitter.emit('sql', sql) })
})
}).run().complete(function() { self.pendingQueries-- })
} }
ConnectorManager.prototype.connect = function() { ConnectorManager.prototype.connect = function(callback) {
var self = this var self = this
var emitter = new (require('events').EventEmitter)() var emitter = new (require('events').EventEmitter)()
// in case database is slow to connect, prevent orphaning the client // in case database is slow to connect, prevent orphaning the client
if (this.isConnecting) { if (this.isConnecting) {
return emitter.emit('success')
return emitter
} }
this.isConnecting = true this.isConnecting = true
...@@ -69,10 +80,14 @@ module.exports = (function() { ...@@ -69,10 +80,14 @@ module.exports = (function() {
var uri = this.sequelize.getQueryInterface().QueryGenerator.databaseConnectionUri(this.config) var uri = this.sequelize.getQueryInterface().QueryGenerator.databaseConnectionUri(this.config)
var connectCallback = function(err, client) { var connectCallback = function(err, client, done) {
self.isConnecting = false self.isConnecting = false
if (!!err) { if (!!err) {
// release the pool immediately, very important.
done && done(err)
if (err.code) {
switch(err.code) { switch(err.code) {
case 'ECONNREFUSED': case 'ECONNREFUSED':
emitter.emit('error', new Error("Failed to authenticate for PostgresSQL. Please double check your settings.")) emitter.emit('error', new Error("Failed to authenticate for PostgresSQL. Please double check your settings."))
...@@ -84,21 +99,25 @@ module.exports = (function() { ...@@ -84,21 +99,25 @@ module.exports = (function() {
break break
default: default:
emitter.emit('error', err) emitter.emit('error', err)
break
}
} }
} else if (client) { } else if (client) {
client.query("SET TIME ZONE 'UTC'") client.query("SET TIME ZONE 'UTC'").on('end', function() {
.on('end', function() {
self.isConnected = true self.isConnected = true
this.client = client self.client = client
emitter.emit('success', done)
}); });
} else { } else {
this.client = null self.client = null
emitter.emit('success', done)
} }
} }
if (this.pooling) { if (this.pooling) {
// acquire client from pool // acquire client from pool
this.pg.connect(uri, connectCallback) this.poolIdentifier = this.pg.pools.getOrCreate(this.sequelize.config)
this.poolIdentifier.connect(connectCallback)
} else { } else {
//create one-off client //create one-off client
this.client = new this.pg.Client(uri) this.client = new this.pg.Client(uri)
...@@ -109,8 +128,14 @@ module.exports = (function() { ...@@ -109,8 +128,14 @@ module.exports = (function() {
} }
ConnectorManager.prototype.disconnect = function() { ConnectorManager.prototype.disconnect = function() {
var self = this if (this.poolIdentifier) {
if (this.client) this.client.end() this.poolIdentifier.destroyAllNow()
}
if (this.client) {
this.client.end()
}
this.client = null this.client = null
this.isConnecting = false this.isConnecting = false
this.isConnected = false this.isConnected = false
......
...@@ -112,7 +112,7 @@ module.exports = (function() { ...@@ -112,7 +112,7 @@ module.exports = (function() {
}, },
describeTableQuery: function(tableName) { describeTableQuery: function(tableName) {
var query = 'SELECT c.column_name as "Field", c.column_default as "Default", c.is_nullable as "Null", c.data_type as "Type", (SELECT array_agg(e.enumlabel) FROM pg_catalog.pg_type t JOIN pg_catalog.pg_enum e ON t.oid=e.enumtypid WHERE t.typname=c.udt_name) AS special FROM information_schema.columns c WHERE table_name = <%= table %>;' var query = 'SELECT c.column_name as "Field", c.column_default as "Default", c.is_nullable as "Null", c.data_type as "Type", (SELECT array_agg(e.enumlabel) FROM pg_catalog.pg_type t JOIN pg_catalog.pg_enum e ON t.oid=e.enumtypid WHERE t.typname=c.udt_name) AS "special" FROM information_schema.columns c WHERE table_name = <%= table %>;'
return Utils._.template(query)({ return Utils._.template(query)({
table: this.escape(tableName) table: this.escape(tableName)
}) })
......
...@@ -18,8 +18,9 @@ module.exports = (function() { ...@@ -18,8 +18,9 @@ module.exports = (function() {
} }
Utils.inherit(Query, AbstractQuery) Utils.inherit(Query, AbstractQuery)
Query.prototype.run = function(sql) { Query.prototype.run = function(sql, done) {
this.sql = sql this.sql = sql
var self = this
if (this.options.logging !== false) { if (this.options.logging !== false) {
this.options.logging('Executing: ' + this.sql) this.options.logging('Executing: ' + this.sql)
...@@ -39,13 +40,14 @@ module.exports = (function() { ...@@ -39,13 +40,14 @@ module.exports = (function() {
}.bind(this)) }.bind(this))
query.on('end', function() { query.on('end', function() {
done && done()
this.emit('sql', this.sql) this.emit('sql', this.sql)
if (receivedError) { if (receivedError) {
return return
} }
onSuccess.call(this, rows) onSuccess.call(this, rows, sql)
}.bind(this)) }.bind(this))
return this return this
...@@ -55,11 +57,11 @@ module.exports = (function() { ...@@ -55,11 +57,11 @@ module.exports = (function() {
return 'id' return 'id'
} }
var onSuccess = function(rows) { var onSuccess = function(rows, sql) {
var results = [] var results = []
, self = this , self = this
, isTableNameQuery = (this.sql.indexOf('SELECT table_name FROM information_schema.tables') === 0) , isTableNameQuery = (sql.indexOf('SELECT table_name FROM information_schema.tables') === 0)
, isRelNameQuery = (this.sql.indexOf('SELECT relname FROM pg_class WHERE oid IN') === 0) , isRelNameQuery = (sql.indexOf('SELECT relname FROM pg_class WHERE oid IN') === 0)
if (isTableNameQuery || isRelNameQuery) { if (isTableNameQuery || isRelNameQuery) {
if (isRelNameQuery) { if (isRelNameQuery) {
......
...@@ -289,7 +289,7 @@ module.exports = (function() { ...@@ -289,7 +289,7 @@ module.exports = (function() {
Escape a value (e.g. a string, number or date) Escape a value (e.g. a string, number or date)
*/ */
escape: function(value) { escape: function(value) {
throwMethodUndefined('quoteIdentifier') throwMethodUndefined('escape')
} }
} }
......
var Utils = require("../../utils") var sqlite3
, sqlite3 = require('sqlite3').verbose() , Utils = require("../../utils")
, Query = require("./query") , Query = require("./query")
module.exports = (function() { module.exports = (function() {
var ConnectorManager = function(sequelize) { var ConnectorManager = function(sequelize, config) {
this.sequelize = sequelize this.sequelize = sequelize
if (config.dialectModulePath) {
sqlite3 = require(config.dialectModulePath).verbose()
} else {
sqlite3 = require('sqlite3').verbose()
}
} }
Utils._.extend(ConnectorManager.prototype, require("../connector-manager").prototype) Utils._.extend(ConnectorManager.prototype, require("../connector-manager").prototype)
...@@ -16,7 +22,7 @@ module.exports = (function() { ...@@ -16,7 +22,7 @@ module.exports = (function() {
this.database = db = new sqlite3.Database(self.sequelize.options.storage || ':memory:', function(err) { this.database = db = new sqlite3.Database(self.sequelize.options.storage || ':memory:', function(err) {
if (err) { if (err) {
if (err.code === "SQLITE_CANTOPEN") { if (err.code === "SQLITE_CANTOPEN") {
emitter.emit('error', new Error("Failed to find SQL server. Please double check your settings.")) emitter.emit('error', 'Failed to find SQL server. Please double check your settings.')
} }
} }
......
...@@ -37,9 +37,8 @@ module.exports = (function() { ...@@ -37,9 +37,8 @@ module.exports = (function() {
return Utils._.template(query)({}) return Utils._.template(query)({})
}, },
dropSchema: function() { dropSchema: function(tableName, options) {
var query = "SELECT name FROM sqlite_master WHERE type='table' and name!='sqlite_sequence';" return this.dropTableQuery(tableName, options)
return Utils._.template(query)({})
}, },
showSchemasQuery: function() { showSchemasQuery: function() {
...@@ -89,6 +88,16 @@ module.exports = (function() { ...@@ -89,6 +88,16 @@ module.exports = (function() {
return this.replaceBooleanDefaults(sql) return this.replaceBooleanDefaults(sql)
}, },
dropTableQuery: function(tableName, options) {
options = options || {}
var query = "DROP TABLE IF EXISTS <%= table %>;"
return Utils._.template(query)({
table: this.quoteIdentifier(tableName)
})
},
addColumnQuery: function() { addColumnQuery: function() {
var sql = MySqlQueryGenerator.addColumnQuery.apply(this, arguments) var sql = MySqlQueryGenerator.addColumnQuery.apply(this, arguments)
return this.replaceBooleanDefaults(sql) return this.replaceBooleanDefaults(sql)
...@@ -134,6 +143,7 @@ module.exports = (function() { ...@@ -134,6 +143,7 @@ module.exports = (function() {
return Utils._.template(query)(replacements) return Utils._.template(query)(replacements)
}, },
selectQuery: function(tableName, options) { selectQuery: function(tableName, options) {
var table = null, var table = null,
joinQuery = "" joinQuery = ""
...@@ -378,7 +388,7 @@ module.exports = (function() { ...@@ -378,7 +388,7 @@ module.exports = (function() {
}, },
removeIndexQuery: function(tableName, indexNameOrAttributes) { removeIndexQuery: function(tableName, indexNameOrAttributes) {
var sql = "DROP INDEX <%= indexName %>" var sql = "DROP INDEX IF EXISTS <%= indexName %>"
, indexName = indexNameOrAttributes , indexName = indexNameOrAttributes
if (typeof indexName !== 'string') { if (typeof indexName !== 'string') {
......
...@@ -54,6 +54,12 @@ module.exports = (function() { ...@@ -54,6 +54,12 @@ module.exports = (function() {
return this return this
} }
CustomEventEmitter.prototype.sql =
function(fct) {
this.on('sql', bindToProcess(fct))
return this;
}
// emit the events on the foreign emitter once events got triggered for me // emit the events on the foreign emitter once events got triggered for me
CustomEventEmitter.prototype.proxy = function(emitter) { CustomEventEmitter.prototype.proxy = function(emitter) {
proxyEventKeys.forEach(function (eventKey) { proxyEventKeys.forEach(function (eventKey) {
......
const fs = require("fs") const fs = require("fs")
, moment = require("moment") , moment = require("moment")
var Utils = require("./utils") var Utils = require(__dirname + "/utils")
, Migration = require("./migration") , Migration = require(__dirname + "/migration")
, DataTypes = require("./data-types") , DataTypes = require(__dirname + "/data-types")
module.exports = (function() { module.exports = (function() {
var Migrator = function(sequelize, options) { var Migrator = function(sequelize, options) {
...@@ -45,7 +45,7 @@ module.exports = (function() { ...@@ -45,7 +45,7 @@ module.exports = (function() {
if (err) { if (err) {
emitter.emit('error', err) emitter.emit('error', err)
} else { } else {
var chainer = new Utils.QueryChainer var chainer = new Utils.QueryChainer()
, from = migrations[0] , from = migrations[0]
if (options.method === 'down') { if (options.method === 'down') {
...@@ -188,7 +188,7 @@ module.exports = (function() { ...@@ -188,7 +188,7 @@ module.exports = (function() {
var self = this; var self = this;
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer; var chainer = new Utils.QueryChainer()
var addMigration = function(filename) { var addMigration = function(filename) {
self.options.logging('Adding migration script at ' + filename) self.options.logging('Adding migration script at ' + filename)
var migration = new Migration(self, filename) var migration = new Migration(self, filename)
......
var Utils = require("./utils") var Utils = require(__dirname + "/utils")
module.exports = (function() { module.exports = (function() {
var QueryChainer = function(emitters) { var QueryChainer = function(emitters) {
......
var Utils = require('./utils') var Utils = require(__dirname + '/utils')
, DataTypes = require('./data-types') , DataTypes = require(__dirname + '/data-types')
, SQLiteQueryInterface = require('./dialects/sqlite/query-interface') , SQLiteQueryInterface = require(__dirname + '/dialects/sqlite/query-interface')
module.exports = (function() { module.exports = (function() {
var QueryInterface = function(sequelize) { var QueryInterface = function(sequelize) {
...@@ -54,7 +54,7 @@ module.exports = (function() { ...@@ -54,7 +54,7 @@ module.exports = (function() {
var showSchemasSql = self.QueryGenerator.showSchemasQuery() var showSchemasSql = self.QueryGenerator.showSchemasQuery()
self.sequelize.query(showSchemasSql, null, { raw: true }).success(function(schemaNames) { self.sequelize.query(showSchemasSql, null, { raw: true }).success(function(schemaNames) {
self.emit('showAllSchemas', null) self.emit('showAllSchemas', null)
emitter.emit('success', Utils._.flatten(Utils._.map(schemaNames, function(value){ return value.schema_name }))) emitter.emit('success', Utils._.flatten(Utils._.map(schemaNames, function(value){ return (!!value.schema_name ? value.schema_name : value) })))
}).error(function(err) { }).error(function(err) {
self.emit('showAllSchemas', err) self.emit('showAllSchemas', err)
emitter.emit('error', err) emitter.emit('error', err)
...@@ -266,23 +266,128 @@ module.exports = (function() { ...@@ -266,23 +266,128 @@ module.exports = (function() {
} }
QueryInterface.prototype.update = function(dao, tableName, values, identifier) { QueryInterface.prototype.update = function(dao, tableName, values, identifier) {
var sql = this.QueryGenerator.updateQuery(tableName, values, identifier) var self = this
return queryAndEmit.call(this, [sql, dao], 'update') , restrict = false
, sql = self.QueryGenerator.updateQuery(tableName, values, identifier)
// Check for a restrict field
if (!!dao.daoFactory && !!dao.daoFactory.associations) {
var keys = Object.keys(dao.daoFactory.associations)
, length = keys.length
for (var i = 0; i < length; i++) {
if (dao.daoFactory.associations[keys[i]].options && dao.daoFactory.associations[keys[i]].options.onUpdate && dao.daoFactory.associations[keys[i]].options.onUpdate === "restrict") {
restrict = true
}
}
}
return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer()
chainer.add(self, 'enableForeignKeyConstraints', [])
chainer.add(self, 'queryAndEmit', [[sql, dao], 'delete'])
chainer.runSerially()
.success(function(results){
emitter.query = { sql: sql }
emitter.emit('success', results[1])
emitter.emit('sql', sql)
})
.error(function(err) {
emitter.query = { sql: sql }
emitter.emit('error', err)
emitter.emit('sql', sql)
})
.on('sql', function(sql) {
emitter.emit('sql', sql)
})
}).run()
} }
QueryInterface.prototype.bulkUpdate = function(tableName, values, identifier) { QueryInterface.prototype.bulkUpdate = function(tableName, values, identifier) {
var sql = this.QueryGenerator.updateQuery(tableName, values, identifier) var self = this
return queryAndEmit.call(this, sql, 'bulkUpdate') , sql = self.QueryGenerator.updateQuery(tableName, values, identifier)
return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer()
chainer.add(self, 'enableForeignKeyConstraints', [])
chainer.add(self, 'queryAndEmit', [sql, 'bulkUpdate'])
return chainer.runSerially()
.success(function(results){
emitter.query = { sql: sql }
emitter.emit('sql', sql)
emitter.emit('success', results[1])
})
.error(function(err) {
emitter.query = { sql: sql }
emitter.emit('sql', sql)
emitter.emit('error', err)
})
}).run()
} }
QueryInterface.prototype.delete = function(dao, tableName, identifier) { QueryInterface.prototype.delete = function(dao, tableName, identifier) {
var sql = this.QueryGenerator.deleteQuery(tableName, identifier) var self = this
return queryAndEmit.call(this, [sql, dao], 'delete') , restrict = false
, sql = self.QueryGenerator.deleteQuery(tableName, identifier)
// Check for a restrict field
if (!!dao.daoFactory && !!dao.daoFactory.associations) {
var keys = Object.keys(dao.daoFactory.associations)
, length = keys.length
for (var i = 0; i < length; i++) {
if (dao.daoFactory.associations[keys[i]].options && dao.daoFactory.associations[keys[i]].options.onDelete && dao.daoFactory.associations[keys[i]].options.onDelete === "restrict") {
restrict = true
}
}
}
return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer()
chainer.add(self, 'enableForeignKeyConstraints', [])
chainer.add(self, 'queryAndEmit', [[sql, dao], 'delete'])
chainer.runSerially()
.success(function(results){
emitter.query = { sql: sql }
emitter.emit('sql', sql)
emitter.emit('success', results[1])
})
.error(function(err) {
emitter.query = { sql: sql }
emitter.emit('sql', sql)
emitter.emit('error', err)
})
}).run()
} }
QueryInterface.prototype.bulkDelete = function(tableName, identifier, options) { QueryInterface.prototype.bulkDelete = function(tableName, identifier, options) {
var sql = this.QueryGenerator.deleteQuery(tableName, identifier, Utils._.defaults(options || {}, {limit: null})) var self = this
return queryAndEmit.call(this, sql, 'bulkDelete') var sql = self.QueryGenerator.deleteQuery(tableName, identifier, Utils._.defaults(options || {}, {limit: null}))
return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer()
chainer.add(self, 'enableForeignKeyConstraints', [])
chainer.add(self, 'queryAndEmit', [sql, 'bulkDelete', options])
chainer.runSerially()
.success(function(results){
emitter.query = { sql: sql }
emitter.emit('sql', sql)
emitter.emit('success', results[1])
})
.error(function(err) {
emitter.query = { sql: sql }
emitter.emit('sql', sql)
emitter.emit('error', err)
})
}).run()
} }
QueryInterface.prototype.select = function(factory, tableName, options, queryOptions) { QueryInterface.prototype.select = function(factory, tableName, options, queryOptions) {
...@@ -385,9 +490,7 @@ module.exports = (function() { ...@@ -385,9 +490,7 @@ module.exports = (function() {
return this.QueryGenerator.escape(value) return this.QueryGenerator.escape(value)
} }
// private var queryAndEmit = QueryInterface.prototype.queryAndEmit = function(sqlOrQueryParams, methodName, options, emitter) {
var queryAndEmit = function(sqlOrQueryParams, methodName, options, emitter) {
options = Utils._.extend({ options = Utils._.extend({
success: function(){}, success: function(){},
error: function(){} error: function(){}
......
...@@ -14,6 +14,7 @@ module.exports = (function() { ...@@ -14,6 +14,7 @@ module.exports = (function() {
@param {String} [password=null] The password which is used to authenticate against the database. @param {String} [password=null] The password which is used to authenticate against the database.
@param {Object} [options={}] An object with options. @param {Object} [options={}] An object with options.
@param {String} [options.dialect='mysql'] The dialect of the relational database. @param {String} [options.dialect='mysql'] The dialect of the relational database.
@param {String} [options.dialectModulePath=null] If specified, load the dialect library from this path.
@param {String} [options.host='localhost'] The host of the relational database. @param {String} [options.host='localhost'] The host of the relational database.
@param {Integer} [options.port=3306] The port of the relational database. @param {Integer} [options.port=3306] The port of the relational database.
@param {String} [options.protocol='tcp'] The protocol of the relational database. @param {String} [options.protocol='tcp'] The protocol of the relational database.
...@@ -68,6 +69,7 @@ module.exports = (function() { ...@@ -68,6 +69,7 @@ module.exports = (function() {
this.options = Utils._.extend({ this.options = Utils._.extend({
dialect: 'mysql', dialect: 'mysql',
dialectModulePath: null,
host: 'localhost', host: 'localhost',
protocol: 'tcp', protocol: 'tcp',
define: {}, define: {},
...@@ -99,6 +101,7 @@ module.exports = (function() { ...@@ -99,6 +101,7 @@ module.exports = (function() {
queue : this.options.queue, queue : this.options.queue,
native : this.options.native, native : this.options.native,
replication: this.options.replication, replication: this.options.replication,
dialectModulePath: this.options.dialectModulePath,
maxConcurrentQueries: this.options.maxConcurrentQueries maxConcurrentQueries: this.options.maxConcurrentQueries
} }
...@@ -188,6 +191,7 @@ module.exports = (function() { ...@@ -188,6 +191,7 @@ module.exports = (function() {
var factory = new DAOFactory(daoName, attributes, options) var factory = new DAOFactory(daoName, attributes, options)
this.daoFactoryManager.addDAO(factory.init(this.daoFactoryManager)) this.daoFactoryManager.addDAO(factory.init(this.daoFactoryManager))
return factory return factory
} }
......
...@@ -9,6 +9,19 @@ SqlString.escapeId = function (val, forbidQualified) { ...@@ -9,6 +9,19 @@ SqlString.escapeId = function (val, forbidQualified) {
}; };
SqlString.escape = function(val, stringifyObjects, timeZone, dialect, field) { SqlString.escape = function(val, stringifyObjects, timeZone, dialect, field) {
if (arguments.length === 1 && typeof arguments[0] === "object") {
val = val.val || val.value || null
stringifyObjects = val.stringifyObjects || val.objects || undefined
timeZone = val.timeZone || val.zone || null
dialect = val.dialect || null
field = val.field || null
}
else if (arguments.length < 3 && typeof arguments[1] === "object") {
timeZone = stringifyObjects.timeZone || stringifyObjects.zone || null
dialect = stringifyObjects.dialect || null
field = stringifyObjects.field || null
}
if (val === undefined || val === null) { if (val === undefined || val === null) {
return 'NULL'; return 'NULL';
} }
...@@ -19,7 +32,8 @@ SqlString.escape = function(val, stringifyObjects, timeZone, dialect, field) { ...@@ -19,7 +32,8 @@ SqlString.escape = function(val, stringifyObjects, timeZone, dialect, field) {
// for us. Postgres actually has a boolean type with true/false literals, // for us. Postgres actually has a boolean type with true/false literals,
// but sequelize doesn't use it yet. // but sequelize doesn't use it yet.
return dialect === 'sqlite' ? +!!val : ('' + !!val); return dialect === 'sqlite' ? +!!val : ('' + !!val);
case 'number': return val+''; case 'number':
return val+'';
} }
if (val instanceof Date) { if (val instanceof Date) {
...@@ -97,7 +111,7 @@ SqlString.dateToString = function(date, timeZone, dialect) { ...@@ -97,7 +111,7 @@ SqlString.dateToString = function(date, timeZone, dialect) {
// TODO: Ideally all dialects would work a bit more like this // TODO: Ideally all dialects would work a bit more like this
if (dialect === "postgres") { if (dialect === "postgres") {
return moment(dt).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') { if (timeZone !== 'local') {
......
var util = require("util") var util = require("util")
, DataTypes = require("./data-types") , DataTypes = require("./data-types")
, SqlString = require("./sql-string") , SqlString = require("./sql-string")
, lodash = require("lodash")
, _string = require('underscore.string')
var Utils = module.exports = { var Utils = module.exports = {
_: (function() { _: (function() {
var _ = require("lodash") var _ = lodash
, _s = require('underscore.string') , _s = _string
_.mixin(_s.exports()) _.mixin(_s.exports())
_.mixin({ _.mixin({
...@@ -39,6 +41,214 @@ var Utils = module.exports = { ...@@ -39,6 +41,214 @@ var Utils = module.exports = {
var timeZone = null; var timeZone = null;
return SqlString.format(arr.shift(), arr, timeZone, dialect) return SqlString.format(arr.shift(), arr, timeZone, dialect)
}, },
// smartWhere can accept an array of {where} objects, or a single {where} object.
// The smartWhere function breaks down the collection of where objects into a more
// centralized object for each column so we can avoid duplicates
// e.g. WHERE username='dan' AND username='dan' becomes WHERE username='dan'
// All of the INs, NOT INs, BETWEENS, etc. are compressed into one key for each column
// This function will hopefully provide more functionality to sequelize in the future.
// tl;dr It's a nice way to dissect a collection of where objects and compress them into one object
smartWhere: function(whereArg, dialect) {
var self = this
, _where = {}
, logic
, type
(Array.isArray(whereArg) ? whereArg : [whereArg]).forEach(function(where) {
// If it's an array we're already good... / it's in a format that can't be broken down further
// e.g. Util.format['SELECT * FROM world WHERE status=?', 'hello']
if (Array.isArray(where)) {
_where._ = where._ || {queries: [], bindings: []}
_where._.queries[_where._.queries.length] = where[0]
if (where.length > 1) {
var values = where.splice(1)
if (dialect === "sqlite") {
values.forEach(function(v, i) {
if (typeof v === "boolean") {
values[i] = (v === true ? 1 : 0)
}
})
}
_where._.bindings = _where._.bindings.concat(values)
}
}
else if (typeof where === "object") {
// First iteration is trying to compress IN and NOT IN as much as possible...
// .. reason being is that WHERE username IN (?) AND username IN (?) != WHERE username IN (?,?)
Object.keys(where).forEach(function(i) {
if (Array.isArray(where[i])) {
where[i] = {
in: where[i]
}
}
})
// Build our smart object
Object.keys(where).forEach(function(i) {
type = typeof where[i]
_where[i] = _where[i] || {}
if (where[i] === null) {
// skip nulls
}
else if (Array.isArray(where[i])) {
_where[i].in = _where[i].in || []
_where[i].in.concat(where[i])
}
else if (type === "object") {
Object.keys(where[i]).forEach(function(ii) {
logic = self.getWhereLogic(ii)
switch(logic) {
case 'IN':
_where[i].in = _where[i].in || []
_where[i].in = _where[i].in.concat(where[i][ii]);
break
case 'NOT':
_where[i].not = _where[i].not || []
_where[i].not = _where[i].not.concat(where[i][ii]);
break
case 'BETWEEN':
_where[i].between = _where[i].between || []
_where[i].between[_where[i].between.length] = [where[i][ii][0], where[i][ii][1]]
break
case 'NOT BETWEEN':
_where[i].nbetween = _where[i].nbetween || []
_where[i].nbetween[_where[i].nbetween.length] = [where[i][ii][0], where[i][ii][1]]
break
case 'JOIN':
_where[i].joined = _where[i].joined || []
_where[i].joined[_where[i].joined.length] = where[i][ii]
break
default:
_where[i].lazy = _where[i].lazy || {conditions: [], bindings: []}
_where[i].lazy.conditions[_where[i].lazy.conditions.length] = logic + ' ?'
_where[i].lazy.bindings = _where[i].lazy.bindings.concat(where[i][ii])
}
})
}
else if (type === "string" || type === "number" || type === "boolean") {
_where[i].lazy = _where[i].lazy || {conditions: [], bindings: []}
if (type === "boolean") {
_where[i].lazy.conditions[_where[i].lazy.conditions.length] = '= ' + SqlString.escape(where[i], false, null, dialect) // sqlite is special
} else {
_where[i].lazy.conditions[_where[i].lazy.conditions.length] = '= ?'
_where[i].lazy.bindings = _where[i].lazy.bindings.concat(where[i])
}
}
})
}
})
return _where
},
// Converts {smart where} object(s) into an array that's friendly for Utils.format()
// NOTE: Must be applied/called from the QueryInterface
compileSmartWhere: function(obj, dialect) {
var self = this
, whereArgs = []
, text = []
, columnName
if (typeof obj !== "object") {
return obj
}
for (var column in obj) {
if (column === "_") {
text[text.length] = obj[column].queries.join(' AND ')
if (obj[column].bindings.length > 0) {
whereArgs = whereArgs.concat(obj[column].bindings)
}
} else {
Object.keys(obj[column]).forEach(function(condition) {
columnName = self.QueryInterface.quoteIdentifiers(column)
switch(condition) {
case 'in':
text[text.length] = columnName + ' IN (' + obj[column][condition].map(function(){ return '?' }) + ')'
whereArgs = whereArgs.concat(obj[column][condition])
break
case 'not':
text[text.length] = columnName + ' NOT IN (' + obj[column][condition].map(function(){ return '?' }) + ')'
whereArgs = whereArgs.concat(obj[column][condition])
break
case 'between':
Object.keys(obj[column][condition]).forEach(function(row) {
text[text.length] = columnName + ' BETWEEN ? AND ?'
whereArgs = whereArgs.concat(obj[column][condition][row][0], obj[column][condition][row][1])
})
break
case 'nbetween':
Object.keys(obj[column][condition]).forEach(function(row) {
text[text.length] = columnName + ' BETWEEN ? AND ?'
whereArgs = whereArgs.concat(obj[column][condition][row][0], obj[column][condition][row][1])
})
break
case 'joined':
Object.keys(obj[column][condition]).forEach(function(row) {
text[text.length] = columnName + ' = ' + self.QueryInterface.quoteIdentifiers(obj[column][condition][row])
})
break
default: // lazy
text = text.concat(obj[column].lazy.conditions.map(function(val){ return columnName + ' ' + val }))
obj[column].lazy.bindings = obj[column].lazy.bindings.map(function(v) { return SqlString.escape(v, false, null, dialect) })
whereArgs = whereArgs.concat(obj[column].lazy.bindings)
}
})
}
}
return lodash.compact([text.join(' AND ')].concat(whereArgs))
},
getWhereLogic: function(logic) {
switch (logic) {
case 'join':
return 'JOIN'
case 'gte':
return '>='
break
case 'gt':
return '>'
break
case 'lte':
return '<='
break
case 'lt':
return '<'
break
case 'eq':
case 'join':
return '='
break
case 'ne':
return '!='
break
case 'between':
case '..':
return 'BETWEEN'
break
case 'nbetween':
case 'notbetween':
case '!..':
return 'NOT BETWEEN'
break
case 'in':
return 'IN'
break
case 'not':
return 'NOT IN'
break
case 'like':
return 'LIKE'
break
case 'nlike':
case 'notlike':
return 'NOT LIKE'
break
default:
return ''
}
},
isHash: function(obj) { isHash: function(obj) {
return Utils._.isObject(obj) && !Array.isArray(obj); return Utils._.isObject(obj) && !Array.isArray(obj);
}, },
...@@ -173,6 +383,6 @@ var Utils = module.exports = { ...@@ -173,6 +383,6 @@ var Utils = module.exports = {
} }
} }
Utils.CustomEventEmitter = require("./emitters/custom-event-emitter") Utils.CustomEventEmitter = require(__dirname + "/emitters/custom-event-emitter")
Utils.QueryChainer = require("./query-chainer") Utils.QueryChainer = require(__dirname + "/query-chainer")
Utils.Lingo = require("lingo") Utils.Lingo = require("lingo")
...@@ -22,6 +22,10 @@ ...@@ -22,6 +22,10 @@
"janzeh@gmail.com", "janzeh@gmail.com",
"jmei@itu.dk" "jmei@itu.dk"
] ]
},
{
"name": "Daniel Durante",
"email": "me@danieldurante.com"
} }
], ],
"repository": { "repository": {
...@@ -32,26 +36,28 @@ ...@@ -32,26 +36,28 @@
"url": "https://github.com/sequelize/sequelize/issues" "url": "https://github.com/sequelize/sequelize/issues"
}, },
"dependencies": { "dependencies": {
"lodash": "~1.2.1", "lodash": "~1.3.1",
"underscore.string": "~2.3.0", "underscore.string": "~2.3.0",
"lingo": "~0.0.5", "lingo": "~0.0.5",
"validator": "1.1.1", "validator": "~1.3.0",
"moment": "~1.7.0", "moment": "~2.1.0",
"commander": "~0.6.0", "commander": "~2.0.0",
"dottie": "0.0.6-1", "dottie": "0.0.8-0",
"toposort-class": "0.1.4", "toposort-class": "~0.2.0",
"generic-pool": "2.0.3", "generic-pool": "2.0.4",
"promise": "~3.0.0" "promise": "~3.2.0"
}, },
"devDependencies": { "devDependencies": {
"jasmine-node": "1.5.0", "sqlite3": "~2.1.12",
"sqlite3": "~2.1.5", "mysql": "~2.0.0-alpha8",
"mysql": "~2.0.0-alpha7", "pg": "~2.2.0",
"pg": "~2.0.0",
"buster": "~0.6.3", "buster": "~0.6.3",
"watchr": "~2.2.0", "watchr": "~2.4.3",
"yuidocjs": "~0.3.36", "yuidocjs": "~0.3.36",
"semver": "~2.0.8" "chai": "~1.7.2",
"mocha": "~1.12.0",
"chai-datetime": "~1.0.0",
"sinon": "~1.7.3"
}, },
"keywords": [ "keywords": [
"mysql", "mysql",
...@@ -61,14 +67,7 @@ ...@@ -61,14 +67,7 @@
], ],
"main": "index", "main": "index",
"scripts": { "scripts": {
"test": "npm run test-jasmine && npm run test-buster", "test": "make all",
"test-jasmine": "jasmine-node spec-jasmine/",
"test-buster": "npm run test-buster-mysql && npm run test-buster-postgres && npm run test-buster-postgres-native && npm run test-buster-sqlite",
"test-buster-travis": "buster-test",
"test-buster-mysql": "DIALECT=mysql buster-test",
"test-buster-postgres": "DIALECT=postgres buster-test",
"test-buster-postgres-native": "DIALECT=postgres-native buster-test",
"test-buster-sqlite": "DIALECT=sqlite buster-test",
"docs": "node_modules/.bin/yuidoc . -o docs" "docs": "node_modules/.bin/yuidoc . -o docs"
}, },
"bin": { "bin": {
......
module.exports = {
up: function(migration, DataTypes) {
migration.createTable('Person', {
name: DataTypes.STRING,
isBetaMember: {
type: DataTypes.BOOLEAN,
defaultValue: false,
allowNull: false
}
})
},
down: function(migration) {
migration.dropTable('Person')
}
}
module.exports = {
up: function() {},
down: function() {}
}
module.exports = {
up: function(migration, DataTypes) {
migration.renameTable('Person', 'User')
},
down: function(migration, DataTypes) {
migration.renameTable('User', 'Person')
}
}
module.exports = {
up: function(migration, DataTypes) {
migration.addColumn('User', 'signature', DataTypes.TEXT)
migration.addColumn('User', 'shopId', { type: DataTypes.INTEGER, allowNull: true })
migration.addColumn('User', 'isAdmin', { type: DataTypes.BOOLEAN, defaultValue: false, allowNull: false })
},
down: function(migration, DataTypes) {
migration.removeColumn('User', 'signature')
migration.removeColumn('User', 'shopId')
migration.removeColumn('User', 'isAdmin')
}
}
module.exports = {
up: function(migration, DataTypes) {
migration.removeColumn('User', 'shopId')
},
down: function(migration, DataTypes) {
migration.addColumn('User', 'shopId', { type: DataTypes.INTEGER, allowNull: true })
}
}
module.exports = {
up: function(migration, DataTypes) {
migration.changeColumn('User', 'signature', {
type: DataTypes.STRING,
allowNull: false,
defaultValue: 'Signature'
})
},
down: function(migration, DataTypes) {}
}
module.exports = {
up: function(migration, DataTypes) {
migration.renameColumn('User', 'signature', 'sig')
},
down: function(migration, DataTypes) {
migration.renameColumn('User', 'sig', 'signature')
}
}
var config = require("../config/config")
, Sequelize = require("../../index")
, sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { pool: config.mysql.pool, logging: false, host: config.mysql.host, port: config.mysql.port })
, Helpers = new (require("../config/helpers"))(sequelize)
describe('BelongsTo', function() {
var User = null
, Task = null
var setup = function() {
User = sequelize.define('User', { username: Sequelize.STRING, enabled: {
type: Sequelize.BOOLEAN,
defaultValue: true
}})
Task = sequelize.define('Task', { title: Sequelize.STRING })
}
beforeEach(function() { Helpers.dropAllTables(); setup() })
afterEach(function() { Helpers.dropAllTables() })
it('adds the foreign key', function() {
Task.belongsTo(User)
expect(Task.attributes['UserId']).toEqual("INTEGER")
})
it("underscores the foreign key", function() {
Task = sequelize.define('Task', { title: Sequelize.STRING }, {underscored: true})
Task.belongsTo(User)
expect(Task.attributes['user_id']).toEqual("INTEGER")
})
it("uses the passed foreign key", function() {
Task.belongsTo(User, {foreignKey: 'person_id'})
expect(Task.attributes['person_id']).toEqual("INTEGER")
})
it("defines getters and setters", function() {
Task.belongsTo(User)
var task = Task.build({title: 'asd'})
expect(task.setUser).toBeDefined()
expect(task.getUser).toBeDefined()
})
it("aliases the getters and setters according to the passed 'as' option", function() {
Task.belongsTo(User, {as: 'Person'})
var task = Task.build({title: 'asd'})
expect(task.setPerson).toBeDefined()
expect(task.getPerson).toBeDefined()
})
it("aliases associations to the same table according to the passed 'as' option", function() {
Task.belongsTo(User, {as: 'Poster'})
Task.belongsTo(User, {as: 'Owner'})
var task = Task.build({title: 'asd'})
expect(task.getPoster).toBeDefined()
expect(task.setPoster).toBeDefined()
expect(task.getOwner).toBeDefined()
expect(task.setOwner).toBeDefined()
})
it("intializes the foreign key with null", function() {
Task.belongsTo(User)
var task = Task.build({title: 'asd'})
expect(task['UserId']).not.toBeDefined();
})
it("sets and gets the correct objects", function() {
Task.belongsTo(User, {as: 'User'})
Helpers.async(function(done) {
User.sync({force: true}).success(function() {
Task.sync({force: true}).success(done)
})
})
Helpers.async(function(done) {
User.create({username: 'asd'}).success(function(u) {
Task.create({title: 'a task'}).success(function(t) {
t.setUser(u).success(function() {
t.getUser().success(function(user) {
expect(user.username).toEqual('asd')
done()
})
})
})
})
})
})
it('extends the id where param with the supplied where params', function() {
Task.belongsTo(User, {as: 'User'})
Helpers.async(function(done) {
User.sync({force: true}).success(function() {
Task.sync({force: true}).success(done)
})
})
Helpers.async(function(done) {
User.create({username: 'asd', enabled: false}).success(function(u) {
Task.create({title: 'a task'}).success(function(t) {
t.setUser(u).success(function() {
t.getUser({where: {enabled: true}}).success(function(user) {
expect(user).toEqual(null)
done()
})
})
})
})
})
})
it("handles self associations", function() {
Helpers.async(function(done) {
var Person = sequelize.define('Person', { name: Sequelize.STRING })
Person.belongsTo(Person, {as: 'Mother', foreignKey: 'MotherId'})
Person.belongsTo(Person, {as: 'Father', foreignKey: 'FatherId'})
Person.sync({force: true}).success(function() {
var p = Person.build()
expect(p.setFather).toBeDefined()
expect(p.setMother).toBeDefined()
done()
})
})
})
it("sets the foreign key in self associations", function() {
var Person = sequelize.define('Person', { name: Sequelize.STRING })
Person.belongsTo(Person, {as: 'Mother'})
expect(Person.associations.MotherPersons.options.foreignKey).toEqual('MotherId')
})
})
var config = require("../config/config")
, Sequelize = require("../../index")
, sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { pool: config.mysql.pool, logging: false, host: config.mysql.host, port: config.mysql.port })
, Helpers = new (require("../config/helpers"))(sequelize)
describe('HasOne', function() {
var User = null
, Task = null
var setup = function() {
User = sequelize.define('User', { username: Sequelize.STRING })
Task = sequelize.define('Task', { title: Sequelize.STRING })
}
beforeEach(function() { Helpers.dropAllTables(); setup() })
afterEach(function() { Helpers.dropAllTables() })
it("adds the foreign key", function() {
User.hasOne(Task)
expect(Task.attributes.UserId).toEqual("INTEGER")
})
it("adds an underscored foreign key", function() {
User = sequelize.define('User', { username: Sequelize.STRING }, {underscored: true})
Task = sequelize.define('Task', { title: Sequelize.STRING })
User.hasOne(Task)
expect(Task.attributes.user_id).toEqual("INTEGER")
})
it("uses the passed foreign key", function() {
User = sequelize.define('User', { username: Sequelize.STRING }, {underscored: true})
Task = sequelize.define('Task', { title: Sequelize.STRING })
User.hasOne(Task, {foreignKey: 'person_id'})
expect(Task.attributes.person_id).toEqual("INTEGER")
})
it("defines the getter and the setter", function() {
User.hasOne(Task)
var u = User.build({username: 'asd'})
expect(u.setTask).toBeDefined()
expect(u.getTask).toBeDefined()
})
it("defined the getter and the setter according to the passed 'as' option", function() {
User.hasOne(Task, {as: 'Work'})
var u = User.build({username: 'asd'})
expect(u.setWork).toBeDefined()
expect(u.getWork).toBeDefined()
})
it("aliases associations to the same table according to the passed 'as' option", function() {
User.hasOne(Task, {as: 'Work'});
User.hasOne(Task, {as: 'Play'});
var u = User.build({username: 'asd'})
expect(u.getWork).toBeDefined()
expect(u.setWork).toBeDefined()
expect(u.getPlay).toBeDefined()
expect(u.setPlay).toBeDefined()
})
it("gets and sets the correct objects", function() {
var user, task;
User.hasOne(Task, {as: 'Task'})
Helpers.async(function(done) {
User.sync({force: true}).success(function() {
Task.sync({force: true}).success(function() {
User.create({username: 'name'}).success(function(_user) {
Task.create({title: 'snafu'}).success(function(_task) {
user = _user
task = _task
done()
})
})
})
})
})
Helpers.async(function(done) {
user.setTask(task).on('success', function() {
user.getTask().on('success', function(task2) {
expect(task.title).toEqual(task2.title)
user.getTask({attributes: ['title']}).on('success', function(task2) {
expect(task2.selectedValues.title).toEqual('snafu')
expect(task2.selectedValues.id).toEqual(null)
done()
})
})
})
})
})
it("unsets unassociated objects", function() {
var user, task1, task2;
User.hasOne(Task, {as: 'Task'})
Helpers.async(function(done) {
User.sync({force: true}).success(function() {
Task.sync({force: true}).success(function() {
User.create({username: 'name'}).success(function(_user) {
Task.create({title: 'snafu'}).success(function(_task1) {
Task.create({title: 'another task'}).success(function(_task2) {
user = _user
task1 = _task1
task2 = _task2
done()
})
})
})
})
})
})
Helpers.async(function(done) {
user.setTask(task1).success(function() {
user.getTask().success(function(_task) {
expect(task1.title).toEqual(_task.title)
user.setTask(task2).success(function() {
user.getTask().success(function(_task2) {
expect(task2.title).toEqual(task2.title)
done()
})
})
})
})
})
})
it("sets self associations", function() {
Helpers.async(function(done) {
var Person = sequelize.define('Person', { name: Sequelize.STRING })
Person.hasOne(Person, {as: 'Mother', foreignKey: 'MotherId'})
Person.hasOne(Person, {as: 'Father', foreignKey: 'FatherId'})
Person.sync({force: true}).success(function() {
var p = Person.build()
expect(p.setFather).toBeDefined()
expect(p.setMother).toBeDefined()
done()
})
})
})
it("automatically sets the foreign key on self associations", function() {
var Person = sequelize.define('Person', { name: Sequelize.STRING })
Person.hasOne(Person, {as: 'Mother'})
expect(Person.associations.MotherPersons.options.foreignKey).toEqual('MotherId')
})
})
module.exports = {
rand: function() {
return parseInt(Math.random() * 999)
},
//make maxIdleTime small so that tests exit promptly
mysql: {
username: "root",
password: null,
database: 'sequelize_test',
host: '127.0.0.1',
port: 3306,
pool: { maxConnections: 5, maxIdleTime: 30}
},
sqlite: {
},
postgres: {
database: 'sequelize_test',
username: "postgres",
port: 5432,
pool: { maxConnections: 5, maxIdleTime: 30}
}
}
var Factories = module.exports = function(helpers) {
this.helpers = helpers
this.sequelize = this.helpers.sequelize
}
Factories.prototype.DAO = function(daoName, options, callback, count) {
count = count || 1
var self = this
, daos = []
this.helpers.async(function(done) {
var DAO = self.sequelize.daoFactoryManager.getDAO(daoName)
var create = function(cb) {
DAO.create(options).on('success', function(dao) {
daos.push(dao)
cb && cb()
}).on('error', function(err) {
console.log(err)
done()
})
}
var cb = function() {
if(--count) {
create(cb)
} else {
done()
callback && callback(daos)
}
}
create(cb)
})
}
Factories.prototype.User = function(options, callback, count) {
this.DAO('User', options, callback, count)
}
Sequelize = require("../../index")
var Helpers = module.exports = function(sequelize) {
this.sequelize = sequelize
this.Factories = new (require("./factories"))(this)
}
Helpers.prototype.sync = function() {
var self = this
this.async(function(done) {
self.sequelize
.sync({force: true})
.success(done)
.failure(function(err) { console.log(err) })
})
}
Helpers.prototype.drop = function() {
var self = this
this.async(function(done) {
self.sequelize
.drop()
.on('success', done)
.on('error', function(err) { console.log(err) })
})
}
Helpers.prototype.dropAllTables = function() {
var self = this
this.async(function(done) {
self.sequelize
.getQueryInterface()
.dropAllTables()
.success(done)
.error(function(err) { console.log(err) })
})
}
Helpers.prototype.async = function(fct) {
var done = false
runs(function() {
fct(function() { return done = true })
})
waitsFor(function(){ return done })
}
var config = require("../config/config")
, Sequelize = require("../../index")
, sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { pool: config.mysql.pool, logging: false, host: config.mysql.host, port: config.mysql.port })
, Helpers = new (require("../config/helpers"))(sequelize)
describe('HasMany', function() {
beforeEach(function() { Helpers.sync() })
afterEach(function() { Helpers.drop() })
//prevent periods from occurring in the table name since they are used to delimit (table.column)
var User = sequelize.define('User' + Math.ceil(Math.random()*10000000), { name: Sequelize.STRING })
, Task = sequelize.define('Task' + Math.ceil(Math.random()*10000000), { name: Sequelize.STRING })
, users = null
, tasks = null
User.hasMany(Task, {as:'Tasks'})
Task.hasMany(User, {as:'Users'})
beforeEach(function() {
Helpers.async(function(_done) {
Helpers.Factories.DAO(User.name, {name: 'User' + Math.random()}, function(_users) {
users = _users; _done()
}, 5)
})
Helpers.async(function(_done) {
Helpers.Factories.DAO(Task.name, {name: 'Task' + Math.random()}, function(_tasks) {
tasks = _tasks; _done()
}, 2)
})
})
describe('addDAO / getDAO', function() {
var user = null
, task = null
beforeEach(function() {
Helpers.async(function(done) {
User.all().on('success', function(_users) {
Task.all().on('success', function(_tasks) {
user = _users[0]
task = _tasks[0]
done()
})
})
})
})
it('should correctly add an association to the dao', function() {
Helpers.async(function(done) {
user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(0)
user.addTask(task).on('success', function() {
user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(1)
done()
})
})
})
})
})
})
describe('removeDAO', function() {
var user = null
, tasks = null
beforeEach(function() {
Helpers.async(function(done) {
User.all().on('success', function(users) {
Task.all().on('success', function(_tasks) {
user = users[0]
tasks = _tasks
done()
})
})
})
})
it("should correctly remove associated objects", function() {
Helpers.async(function(done) {
user.getTasks().on('success', function(__tasks) {
expect(__tasks.length).toEqual(0)
user.setTasks(tasks).on('success', function() {
user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(tasks.length)
user.removeTask(tasks[0]).on('success', function() {
user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(tasks.length - 1)
done()
})
})
})
})
})
})
})
})
})
var config = require("../config/config")
, Sequelize = require("../../index")
, sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { pool: config.mysql.pool, logging: false, host: config.mysql.host, port: config.mysql.port })
, Helpers = new (require("../config/helpers"))(sequelize)
describe('Associations', function() {
beforeEach(function() { Helpers.sync() })
afterEach(function() { Helpers.drop() })
/////////// many-to-many with same prefix ////////////
describe('many-to-many', function() {
describe('where tables have the same prefix', function() {
var Table2 = sequelize.define('wp_table2', {foo: Sequelize.STRING})
, Table1 = sequelize.define('wp_table1', {foo: Sequelize.STRING})
Table1.hasMany(Table2)
Table2.hasMany(Table1)
it("should create a table wp_table1wp_table2s", function() {
Helpers.async(function(done) {
expect(sequelize.daoFactoryManager.getDAO('wp_table1swp_table2s')).toBeDefined()
done()
})
})
})
describe('when join table name is specified', function() {
var Table2 = sequelize.define('ms_table1', {foo: Sequelize.STRING})
, Table1 = sequelize.define('ms_table2', {foo: Sequelize.STRING})
Table1.hasMany(Table2, {joinTableName: 'table1_to_table2'})
Table2.hasMany(Table1, {joinTableName: 'table1_to_table2'})
it("should not use a combined name", function() {
expect(sequelize.daoFactoryManager.getDAO('ms_table1sms_table2s')).toBeUndefined()
})
it("should use the specified name", function() {
expect(sequelize.daoFactoryManager.getDAO('table1_to_table2')).toBeDefined()
})
})
})
})
var config = require("../config/config")
, Sequelize = require("../../index")
, sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { pool: config.mysql.pool, logging: false, host: config.mysql.host, port: config.mysql.port })
, Helpers = new (require("../config/helpers"))(sequelize)
describe('ConnectorManager', function() {
beforeEach(function() {
Helpers.dropAllTables()
})
afterEach(function() {
Helpers.dropAllTables()
})
it('works correctly after being idle', function() {
var User = sequelize.define('User', { username: Sequelize.STRING })
Helpers.async(function(done) {
User.sync({force: true}).on('success', function() {
User.create({username: 'user1'}).on('success', function() {
User.count().on('success', function(count) {
expect(count).toEqual(1)
done()
})
})
})
})
Helpers.async(function(done) {
setTimeout(function() {
User.count().on('success', function(count) {
expect(count).toEqual(1)
done()
})
}, 1000)
})
})
})
var config = require("../config/config")
, Sequelize = require("../../index")
, sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { pool: config.mysql.pool, logging: false, host: config.mysql.host, port: config.mysql.port })
, Helpers = new (require("../config/helpers"))(sequelize)
describe('DAOFactory', function() {
beforeEach(function() { Helpers.sync() })
afterEach(function() { Helpers.drop() })
var User = sequelize.define('User', { age: Sequelize.INTEGER, name: Sequelize.STRING, bio: Sequelize.TEXT })
describe('constructor', function() {
it("handles extended attributes (unique)", function() {
var User = sequelize.define('User' + config.rand(), {
username: { type: Sequelize.STRING, unique: true }
}, { timestamps: false })
expect(User.attributes).toEqual({username:"VARCHAR(255) UNIQUE",id:"INTEGER NOT NULL auto_increment PRIMARY KEY"})
})
it("handles extended attributes (default)", function() {
var User = sequelize.define('User' + config.rand(), {
username: {type: Sequelize.STRING, defaultValue: 'foo'}
}, { timestamps: false })
expect(User.attributes).toEqual({username:"VARCHAR(255) DEFAULT 'foo'",id:"INTEGER NOT NULL auto_increment PRIMARY KEY"})
})
it("handles extended attributes (null)", function() {
var User = sequelize.define('User' + config.rand(), {
username: {type: Sequelize.STRING, allowNull: false}
}, { timestamps: false })
expect(User.attributes).toEqual({username:"VARCHAR(255) NOT NULL",id:"INTEGER NOT NULL auto_increment PRIMARY KEY"})
})
it("handles extended attributes (comment)", function() {
var User = sequelize.define('User' + config.rand(), {
username: {type: Sequelize.STRING, comment: 'This be\'s a comment'}
}, { timestamps: false })
expect(User.attributes).toEqual({username:"VARCHAR(255) COMMENT 'This be\\'s a comment'",id:"INTEGER NOT NULL auto_increment PRIMARY KEY"})
})
it("handles extended attributes (primaryKey)", function() {
var User = sequelize.define('User' + config.rand(), {
username: {type: Sequelize.STRING, primaryKey: true}
}, { timestamps: false })
expect(User.attributes).toEqual({username:"VARCHAR(255) PRIMARY KEY"})
})
it("adds timestamps", function() {
var User1 = sequelize.define('User' + config.rand(), {})
var User2 = sequelize.define('User' + config.rand(), {}, { timestamps: true })
expect(User1.attributes).toEqual({id:"INTEGER NOT NULL auto_increment PRIMARY KEY", updatedAt:"DATETIME NOT NULL", createdAt:"DATETIME NOT NULL"})
expect(User2.attributes).toEqual({id:"INTEGER NOT NULL auto_increment PRIMARY KEY", updatedAt:"DATETIME NOT NULL", createdAt:"DATETIME NOT NULL"})
})
it("adds deletedAt if paranoid", function() {
var User = sequelize.define('User' + config.rand(), {}, { paranoid: true })
expect(User.attributes).toEqual({id:"INTEGER NOT NULL auto_increment PRIMARY KEY", deletedAt:"DATETIME", updatedAt:"DATETIME NOT NULL", createdAt:"DATETIME NOT NULL"})
})
it("underscores timestamps if underscored", function() {
var User = sequelize.define('User' + config.rand(), {}, { paranoid: true, underscored: true })
expect(User.attributes).toEqual({id:"INTEGER NOT NULL auto_increment PRIMARY KEY", deleted_at:"DATETIME", updated_at:"DATETIME NOT NULL", created_at:"DATETIME NOT NULL"})
})
})
describe('primaryKeys', function() {
it("determines the correct primaryKeys", function() {
var User = sequelize.define('User' + config.rand(), {
foo: {type: Sequelize.STRING, primaryKey: true},
bar: Sequelize.STRING
})
expect(User.primaryKeys).toEqual({"foo":"VARCHAR(255) PRIMARY KEY"})
})
})
})
var config = require("../config/config")
, Sequelize = require("../../index")
, sequelize = new Sequelize(config.postgres.database, config.postgres.username, config.postgres.password, {
logging: false,
port: config.postgres.port,
dialect: 'postgres'
})
, Helpers = new (require("../config/helpers"))(sequelize)
describe('HasMany', function() {
beforeEach(function() { Helpers.sync() })
afterEach(function() { Helpers.drop() })
//prevent periods from occurring in the table name since they are used to delimit (table.column)
var User = sequelize.define('User' + Math.ceil(Math.random()*10000000), { name: Sequelize.STRING })
, Task = sequelize.define('Task' + Math.ceil(Math.random()*10000000), { name: Sequelize.STRING })
, users = null
, tasks = null
User.hasMany(Task, {as:'Tasks'})
Task.hasMany(User, {as:'Users'})
beforeEach(function() {
Helpers.async(function(_done) {
Helpers.Factories.DAO(User.name, {name: 'User' + Math.random()}, function(_users) {
users = _users; _done()
}, 5)
})
Helpers.async(function(_done) {
Helpers.Factories.DAO(Task.name, {name: 'Task' + Math.random()}, function(_tasks) {
tasks = _tasks; _done()
}, 2)
})
})
describe('addDAO / getDAO', function() {
var user = null
, task = null
beforeEach(function() {
Helpers.async(function(done) {
User.all().on('success', function(_users) {
Task.all().on('success', function(_tasks) {
user = _users[0]
task = _tasks[0]
done()
})
})
})
})
it('should correctly add an association to the dao', function() {
Helpers.async(function(done) {
user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(0)
user.addTask(task).on('success', function() {
user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(1)
done()
})
})
})
})
})
})
describe('removeDAO', function() {
var user = null
, tasks = null
beforeEach(function() {
Helpers.async(function(done) {
User.all().on('success', function(users) {
Task.all().on('success', function(_tasks) {
user = users[0]
tasks = _tasks
done()
})
})
})
})
it("should correctly remove associated objects", function() {
Helpers.async(function(done) {
user.getTasks().on('success', function(__tasks) {
expect(__tasks.length).toEqual(0)
user.setTasks(tasks).on('success', function() {
user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(tasks.length)
user.removeTask(tasks[0]).on('success', function() {
user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(tasks.length - 1)
done()
})
})
})
})
})
})
})
})
})
var config = require("../config/config")
, Sequelize = require("../../index")
, sequelize = new Sequelize(config.postgres.database, config.postgres.username, config.postgres.password, {
logging: false,
port: config.postgres.port,
dialect: 'postgres'
})
, Helpers = new (require("../config/helpers"))(sequelize)
describe('Associations', function() {
beforeEach(function() { Helpers.sync() })
afterEach(function() { Helpers.drop() })
/////////// many-to-many with same prefix ////////////
describe('many-to-many', function() {
describe('where tables have the same prefix', function() {
var Table2 = sequelize.define('wp_table2', {foo: Sequelize.STRING})
, Table1 = sequelize.define('wp_table1', {foo: Sequelize.STRING})
Table1.hasMany(Table2)
Table2.hasMany(Table1)
it("should create a table wp_table1wp_table2s", function() {
Helpers.async(function(done) {
expect(sequelize.daoFactoryManager.getDAO('wp_table1swp_table2s')).toBeDefined()
done()
})
})
})
describe('when join table name is specified', function() {
var Table2 = sequelize.define('ms_table1', {foo: Sequelize.STRING})
, Table1 = sequelize.define('ms_table2', {foo: Sequelize.STRING})
Table1.hasMany(Table2, {joinTableName: 'table1_to_table2'})
Table2.hasMany(Table1, {joinTableName: 'table1_to_table2'})
it("should not use a combined name", function() {
expect(sequelize.daoFactoryManager.getDAO('ms_table1sms_table2s')).toBeUndefined()
})
it("should use the specified name", function() {
expect(sequelize.daoFactoryManager.getDAO('table1_to_table2')).toBeDefined()
})
})
})
})
var config = require("./config/config")
, Sequelize = require("../index")
, QueryInterface = require("../lib/query-interface")
describe('Sequelize', function() {
var sequelize = null
, Helpers = null
var setup = function(options) {
options = options || {}
if (!options.hasOwnProperty('pool'))
options.pool = config.mysql.pool
if (!options.hasOwnProperty('logging'))
options.logging = false
if (!options.hasOwnProperty('host'))
options.host = config.mysql.host
if (!options.hasOwnProperty('port'))
options.port = config.mysql.port
sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, options)
Helpers = new (require("./config/helpers"))(sequelize)
return options
}
beforeEach(function() { setup() })
afterEach(function() { sequelize = null })
describe('constructor', function() {
it('should pass the global options correctly', function() {
setup({ logging: false, define: { underscored:true } })
var DAO = sequelize.define('dao', {name: Sequelize.STRING})
expect(DAO.options.underscored).toBeTruthy()
})
it('should correctly set the host and the port', function() {
var options = setup({ host: '127.0.0.1', port: 1234 })
expect(sequelize.config.host).toEqual(options.host)
expect(sequelize.config.port).toEqual(options.port)
})
})
describe('define', function() {
it("adds a new dao to the dao manager", function() {
expect(sequelize.daoFactoryManager.all.length).toEqual(0)
sequelize.define('foo', { title: Sequelize.STRING })
expect(sequelize.daoFactoryManager.all.length).toEqual(1)
})
it("overwrites global options", function() {
setup({ define: { collate: 'utf8_general_ci' } })
var DAO = sequelize.define('foo', {bar: Sequelize.STRING}, {collate: 'utf8_bin'})
expect(DAO.options.collate).toEqual('utf8_bin')
})
it("inherits global collate option", function() {
setup({ define: { collate: 'utf8_general_ci' } })
var DAO = sequelize.define('foo', {bar: Sequelize.STRING})
expect(DAO.options.collate).toEqual('utf8_general_ci')
})
it("inherits global classMethods and instanceMethods", function() {
setup({
define: {
classMethods : { globalClassMethod : function() {} },
instanceMethods : { globalInstanceMethod : function() {} }
}
})
var DAO = sequelize.define('foo', {bar: Sequelize.STRING}, {
classMethods : { localClassMethod : function() {} }
})
expect(typeof DAO.options.classMethods.globalClassMethod).toEqual('function')
expect(typeof DAO.options.classMethods.localClassMethod).toEqual('function')
expect(typeof DAO.options.instanceMethods.globalInstanceMethod).toEqual('function')
})
it("uses the passed tableName", function(done) {
var Photo = sequelize.define('Foto', { name: Sequelize.STRING }, { tableName: 'photos' })
Photo.sync({ force: true }).success(function() {
sequelize.getQueryInterface().showAllTables().success(function(tableNames) {
expect(tableNames).toContain('photos')
done()
})
})
})
})
describe('sync', function() {
it("synchronizes all daos", function() {
var Project = sequelize.define('project' + config.rand(), { title: Sequelize.STRING })
var Task = sequelize.define('task' + config.rand(), { title: Sequelize.STRING })
Helpers.async(function(done) {
sequelize.sync().success(function() {
Project.create({title: 'bla'}).success(function() {
Task.create({title: 'bla'}).success(done)
})
})
})
})
})
describe('import', function() {
it("imports a dao definition from a file", function() {
var Project = sequelize.import(__dirname + "/assets/project")
expect(Project).toBeDefined()
})
})
})
var config = require("../config/config")
, Sequelize = require("../../index")
, dbFile = __dirname + '/test.sqlite'
, storages = [':memory:', dbFile]
describe('DAOFactory', function() {
storages.forEach(function(storage) {
describe('with storage "' + storage + '"', function() {
var User = null
, sequelize = null
, Helpers = null
beforeEach(function() {
sequelize = new Sequelize(config.database, config.username, config.password, {
logging: false,
dialect: 'sqlite',
storage: storage
})
Helpers = new (require("../config/helpers"))(sequelize)
User = sequelize.define('User', {
age: Sequelize.INTEGER,
name: Sequelize.STRING,
bio: Sequelize.TEXT
})
Helpers.sync()
})
afterEach(function() {
Helpers.dropAllTables()
if(storage == dbFile) {
Helpers.async(function(done) {
require("fs").unlink(__dirname + '/test.sqlite', done)
})
}
})
describe('create', function() {
it('creates a table entry', function() {
Helpers.async(function(done) {
User
.create({ age: 21, name: 'John Wayne', bio: 'noot noot' })
.success(done)
.error(function(err) { console.log(err) })
})
Helpers.async(function(done) {
User.all().success(function(users) {
var usernames = users.map(function(user) {
return user.name
})
expect(usernames).toEqual(['John Wayne'])
done()
}).error(function(err){ console.log(err) })
})
})
it('should allow the creation of an object with options as attribute', function() {
var Person = sequelize.define('Person', {
name: Sequelize.STRING,
options: Sequelize.TEXT
})
Helpers.async(function(done) {
Person.sync({force: true}).success(done)
})
Helpers.async(function(done) {
var options = JSON.stringify({ foo: 'bar', bar: 'foo' })
Helpers.Factories.DAO('Person', {
name: 'John Doe',
options: options
}, function(people) {
expect(people[0].options).toEqual(options)
done()
})
})
})
it('should allow the creation of an object with a boolean (true) as attribute', function() {
var Person = sequelize.define('Person', {
name: Sequelize.STRING,
has_swag: Sequelize.BOOLEAN
})
Helpers.async(function(done) {
Person.sync({force: true}).success(done)
})
Helpers.async(function(done) {
Helpers.Factories.DAO('Person', {
name: 'John Doe',
has_swag: true
}, function(people) {
expect(people[0].has_swag).toBeTruthy();
done()
})
})
})
it('should allow the creation of an object with a boolean (false) as attribute', function() {
var Person = sequelize.define('Person', {
name: Sequelize.STRING,
has_swag: Sequelize.BOOLEAN
})
Helpers.async(function(done) {
Person.sync({force: true}).success(done)
})
Helpers.async(function(done) {
Helpers.Factories.DAO('Person', {
name: 'John Doe',
has_swag: false
}, function(people) {
expect(people[0].has_swag).toBeFalsy();
done()
})
})
})
})
////////// find //////////////
describe('.find', function() {
beforeEach(function() {
Helpers.Factories.User({name: 'user', bio: 'foobar'}, null, 2)
})
it("finds normal lookups", function() {
Helpers.async(function(done) {
User.find({ where: { name:'user' } }).success(function(user) {
expect(user.name).toEqual('user')
done()
})
})
})
it("should make aliased attributes available", function() {
Helpers.async(function(done) {
User.find({ where: { name:'user' }, attributes: ['id', ['name', 'username']] }).success(function(user) {
expect(user.username).toEqual('user')
done()
})
})
})
})
////////// all //////////////
describe('.all', function() {
beforeEach(function() {
Helpers.Factories.User({name: 'user', bio: 'foobar'}, null, 2)
})
it("should return all users", function() {
Helpers.async(function(done) {
User.all().on('success', function(users) {
done()
expect(users.length).toEqual(2)
}).on('error', function(err) { console.log(err) })
})
})
})
////////// min //////////////
describe('.min', function() {
it("should return the min value", function() {
for(var i = 2; i < 5; i++) Helpers.Factories.User({ age: i })
Helpers.async(function(done) {
User.min('age').on('success', function(min) {
expect(min).toEqual(2); done()
})
})
})
})
////////// max //////////////
describe('.max', function() {
it("should return the max value", function() {
for(var i = 2; i <= 5; i++) Helpers.Factories.User({ age: i })
Helpers.async(function(done) {
User.max('age').on('success', function(min) {
expect(min).toEqual(5); done()
})
})
})
})
})
})
})
// var config = require("./config/config")
// , Sequelize = require("../index")
// , User = null
// , sequelize = new Sequelize(config.database, config.username, config.password, {
// logging: false,
// dialect: dialect
// })
// , Helpers = new (require("./config/helpers"))(sequelize)
// describe('DAO', function() {
// var setup = function() {
// Helpers.async(function(done) {
// User = sequelize.define('User', { username: Sequelize.STRING })
// User.sync({ force: true }).success(done)
// })
// }
// beforeEach(function() { Helpers.dropAllTables(); setup() })
// afterEach(function() { Helpers.dropAllTables() })
// describe('findAll', function() {
// it("can handle dates correctly", function() {
// })
// })
// })
var Utils = require('../lib/utils')
describe('Utils', function() {
describe('removeCommentsFromFunctionString', function() {
it("removes line comments at the start of a line", function() {
var functionWithLineComments = function() {
// noot noot
}
var result = Utils.removeCommentsFromFunctionString(functionWithLineComments.toString())
expect(result).toNotMatch(/.*noot.*/)
})
it("removes lines comments in the middle of a line", function() {
var functionWithLineComments = function() {
alert(1) // noot noot
}
var result = Utils.removeCommentsFromFunctionString(functionWithLineComments.toString())
expect(result).toNotMatch(/.*noot.*/)
})
it("removes range comments", function() {
var s = function() {
alert(1) /*
noot noot
*/
alert(2) /*
foo
*/
}.toString()
var result = Utils.removeCommentsFromFunctionString(s)
expect(result).toNotMatch(/.*noot.*/)
expect(result).toNotMatch(/.*foo.*/)
expect(result).toMatch(/.*alert\(2\).*/)
})
})
describe('argsArePrimaryKeys', function() {
it("doesn't detect primary keys if primareyKeys and values have different lengths", function() {
expect(Utils.argsArePrimaryKeys([1,2,3], [1])).toBeFalsy()
})
it("doesn't detect primary keys if primary keys are hashes or arrays", function() {
expect(Utils.argsArePrimaryKeys([[]], [1])).toBeFalsy()
})
it('detects primary keys if length is correct and data types are matching', function() {
expect(Utils.argsArePrimaryKeys([1,2,3], ["INTEGER", "INTEGER", "INTEGER"])).toBeTruthy()
})
it("detects primary keys if primary keys are dates and lengths are matching", function() {
expect(Utils.argsArePrimaryKeys([new Date()], ['foo'])).toBeTruthy()
})
})
describe('underscore', function() {
describe('underscoredIf', function() {
it('is defined', function() {
expect(Utils._.underscoredIf).toBeDefined()
})
it('underscores if second param is true', function() {
expect(Utils._.underscoredIf('fooBar', true)).toEqual('foo_bar')
})
it("doesn't underscore if second param is false", function() {
expect(Utils._.underscoredIf('fooBar', false)).toEqual('fooBar')
})
})
describe('camelizeIf', function() {
it('is defined', function() {
expect(Utils._.camelizeIf).toBeDefined()
})
it('camelizes if second param is true', function() {
expect(Utils._.camelizeIf('foo_bar', true)).toEqual('fooBar')
})
it("doesn't camelize if second param is false", function() {
expect(Utils._.underscoredIf('fooBar', true)).toEqual('foo_bar')
})
})
})
describe('isHash', function() {
it('doesn\'t match arrays', function() {
expect(Utils.isHash([])).toBeFalsy();
});
it('doesn\'t match null', function() {
expect(Utils.isHash(null)).toBeFalsy();
});
it('matches plain objects', function() {
var values = {
'name': {
'first': 'Foo',
'last': 'Bar'
}
};
expect(Utils.isHash(values)).toBeTruthy();
});
it('matches plain objects with length property/key', function() {
var values = {
'name': {
'first': 'Foo',
'last': 'Bar'
},
'length': 1
};
expect(Utils.isHash(values)).toBeTruthy();
});
});
describe('format', function() {
it('should format where clause correctly when the value is truthy', function() {
var where = ['foo = ?', 1];
expect(Utils.format(where)).toEqual('foo = 1');
});
it('should format where clause correctly when the value is falsy', function() {
var where = ['foo = ?', 0];
expect(Utils.format(where)).toEqual('foo = 0');
});
});
})
module.exports = function(sequelize, DataTypes) {
return sequelize.define('Project' + parseInt(Math.random() * 9999999999999999), {
name: DataTypes.STRING
})
}
\ No newline at end of file
if (typeof require === 'function') {
const buster = require("buster")
, Helpers = require('../buster-helpers')
, Sequelize = require('../../index')
, dialect = Helpers.getTestDialect()
}
buster.spec.expose()
buster.testRunner.timeout = 500
describe(Helpers.getTestDialectTeaser("BelongsTo"), function() {
before(function(done) {
Helpers.initTests({
dialect: dialect,
beforeComplete: function(sequelize) {
this.sequelize = sequelize
}.bind(this),
onComplete: done
})
})
describe('setAssociation', function() {
it('clears the association if null is passed', function(done) {
var User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING })
, Task = this.sequelize.define('TaskXYZ', { title: Sequelize.STRING })
Task.belongsTo(User)
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
task.setUserXYZ(user).success(function() {
task.getUserXYZ().success(function(user) {
expect(user).not.toEqual(null)
task.setUserXYZ(null).success(function() {
task.getUserXYZ().success(function(user) {
expect(user).toEqual(null)
done()
})
})
})
})
})
})
})
})
})
describe("Foreign key constraints", function() {
it("are not enabled by default", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
Task.belongsTo(User)
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
task.setUser(user).success(function() {
user.destroy().success(function() {
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(1)
done()
})
})
})
})
})
})
})
it("can cascade deletes", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
Task.belongsTo(User, {onDelete: 'cascade'})
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
task.setUser(user).success(function() {
user.destroy().success(function() {
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(0)
done()
})
})
})
})
})
})
})
it("can restrict deletes", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
Task.belongsTo(User, {onDelete: 'restrict'})
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
task.setUser(user).success(function() {
user.destroy().error(function() {
// Should fail due to FK restriction
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(1)
done()
})
})
})
})
})
})
})
it("can cascade updates", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
Task.belongsTo(User, {onUpdate: 'cascade'})
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
task.setUser(user).success(function() {
// Changing the id of a DAO requires a little dance since
// the `UPDATE` query generated by `save()` uses `id` in the
// `WHERE` clause
var tableName = user.QueryInterface.QueryGenerator.addSchema(user.__factory)
user.QueryInterface.update(user, tableName, {id: 999}, user.id)
.success(function() {
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(1)
expect(tasks[0].UserId).toEqual(999)
done()
})
})
})
})
})
})
})
it("can restrict updates", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
Task.belongsTo(User, {onUpdate: 'restrict'})
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
task.setUser(user).success(function() {
// Changing the id of a DAO requires a little dance since
// the `UPDATE` query generated by `save()` uses `id` in the
// `WHERE` clause
var tableName = user.QueryInterface.QueryGenerator.addSchema(user.__factory)
user.QueryInterface.update(user, tableName, {id: 999}, user.id)
.error(function() {
// Should fail due to FK restriction
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(1)
done()
})
})
})
})
})
})
})
})
describe("Association options", function() {
it('can specify data type for autogenerated relational keys', function(done) {
var User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING })
, dataTypes = [Sequelize.INTEGER, Sequelize.BIGINT, Sequelize.STRING]
, self = this
dataTypes.forEach(function(dataType) {
var tableName = 'TaskXYZ_' + dataType.toString()
, Task = self.sequelize.define(tableName, { title: Sequelize.STRING })
Task.belongsTo(User, { foreignKey: 'userId', keyType: dataType })
self.sequelize.sync({ force: true }).success(function() {
expect(Task.rawAttributes.userId.type.toString())
.toEqual(dataType.toString())
dataTypes.splice(dataTypes.indexOf(dataType), 1)
if (!dataTypes.length) {
done()
}
})
})
})
})
})
if (typeof require === 'function') {
const buster = require("buster")
, Sequelize = require("../../index")
, Helpers = require('../buster-helpers')
, dialect = Helpers.getTestDialect()
}
buster.spec.expose()
buster.testRunner.timeout = 1500
describe(Helpers.getTestDialectTeaser("HasOne"), function() {
before(function(done) {
var self = this
Helpers.initTests({
dialect: dialect,
beforeComplete: function(sequelize) { self.sequelize = sequelize },
onComplete: done
})
})
describe('setAssociation', function() {
it('//clears the association if null is passed', function(done) {
var User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING })
, Task = this.sequelize.define('TaskXYZ', { title: Sequelize.STRING })
User.hasOne(Task)
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
user.setTaskXYZ(task).success(function() {
user.getTaskXYZ().success(function(task) {
expect(task).not.toEqual(null)
user.setTaskXYZ(null).success(function() {
user.getTaskXYZ().success(function(task) {
expect(task).toEqual(null)
done()
})
})
})
})
})
})
})
})
})
describe("Foreign key constraints", function() {
it("are not enabled by default", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
User.hasOne(Task)
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
user.setTask(task).success(function() {
user.destroy().success(function() {
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(1)
done()
})
})
})
})
})
})
})
it("can cascade deletes", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
User.hasOne(Task, {onDelete: 'cascade'})
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
user.setTask(task).success(function() {
user.destroy().success(function() {
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(0)
done()
})
})
})
})
})
})
})
it("can restrict deletes", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
User.hasOne(Task, {onDelete: 'restrict'})
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
user.setTask(task).success(function() {
user.destroy().error(function() {
// Should fail due to FK restriction
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(1)
done()
})
})
})
})
})
})
})
it("can cascade updates", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
User.hasOne(Task, {onUpdate: 'cascade'})
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
user.setTask(task).success(function() {
// Changing the id of a DAO requires a little dance since
// the `UPDATE` query generated by `save()` uses `id` in the
// `WHERE` clause
var tableName = user.QueryInterface.QueryGenerator.addSchema(user.__factory)
user.QueryInterface.update(user, tableName, {id: 999}, user.id)
.success(function() {
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(1)
expect(tasks[0].UserId).toEqual(999)
done()
})
})
})
})
})
})
})
it("can restrict updates", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
User.hasOne(Task, {onUpdate: 'restrict'})
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
user.setTask(task).success(function() {
// Changing the id of a DAO requires a little dance since
// the `UPDATE` query generated by `save()` uses `id` in the
// `WHERE` clause
var tableName = user.QueryInterface.QueryGenerator.addSchema(user.__factory)
user.QueryInterface.update(user, tableName, {id: 999}, user.id)
.error(function() {
// Should fail due to FK restriction
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(1)
done()
})
})
})
})
})
})
})
})
describe("Association options", function() {
it('can specify data type for autogenerated relational keys', function(done) {
var User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING })
, dataTypes = [Sequelize.INTEGER, Sequelize.BIGINT, Sequelize.STRING]
, self = this
dataTypes.forEach(function(dataType) {
var tableName = 'TaskXYZ_' + dataType.toString()
, Task = self.sequelize.define(tableName, { title: Sequelize.STRING })
User.hasOne(Task, { foreignKey: 'userId', keyType: dataType })
self.sequelize.sync({ force: true }).success(function() {
expect(Task.rawAttributes.userId.type.toString())
.toEqual(dataType.toString())
dataTypes.splice(dataTypes.indexOf(dataType), 1)
if (!dataTypes.length) {
done()
}
})
})
})
})
})
if (typeof require === 'function') {
const buster = require("buster")
, Helpers = require('../buster-helpers')
, Sequelize = require('../../index')
, dialect = Helpers.getTestDialect()
}
buster.spec.expose()
describe(Helpers.getTestDialectTeaser("Mixin"), function() {
before(function(done) {
Helpers.initTests({
dialect: dialect,
beforeComplete: function(sequelize) {
this.sequelize = sequelize
}.bind(this),
onComplete: done
})
})
describe('getAssociation', function() {
it('returns the respective part of the association for 1:1 associations', function() {
var User = this.sequelize.define('User', {})
var Task = this.sequelize.define('Task', {})
User.hasOne(Task)
Task.belongsTo(User)
expect(User.getAssociation(Task).target).toEqual(Task)
})
})
})
var config = module.exports
config["node tests"] = {
environment: "node",
rootPath: "../",
tests: [
"spec/**/*.spec.js"
]
}
var Factories = module.exports = function(helpers) {
this.helpers = helpers
this.sequelize = this.helpers.sequelize
}
Factories.prototype.DAO = function(daoName, options, callback, count) {
count = count || 1
var self = this
, daos = []
this.helpers.async(function(done) {
var DAO = self.sequelize.daoFactoryManager.getDAO(daoName)
var create = function(cb) {
DAO.create(options).on('success', function(dao) {
daos.push(dao)
cb && cb()
}).on('error', function(err) {
console.log(err)
done()
})
}
var cb = function() {
if(--count) {
create(cb)
} else {
done()
callback && callback(daos)
}
}
create(cb)
})
}
Factories.prototype.User = function(options, callback, count) {
this.DAO('User', options, callback, count)
}
Sequelize = require("../../index")
var Helpers = module.exports = function(sequelize) {
this.sequelize = sequelize
this.Factories = new (require("./factories"))(this)
}
Helpers.prototype.sync = function() {
var self = this
this.async(function(done) {
self.sequelize
.sync({force: true})
.success(done)
.failure(function(err) { console.log(err) })
})
}
Helpers.prototype.drop = function() {
var self = this
this.async(function(done) {
self.sequelize
.drop()
.on('success', done)
.on('error', function(err) { console.log(err) })
})
}
Helpers.prototype.dropAllTables = function() {
var self = this
this.async(function(done) {
self.sequelize
.getQueryInterface()
.dropAllTables()
.success(done)
.error(function(err) { console.log(err) })
})
}
Helpers.prototype.async = function(fct) {
var done = false
runs(function() {
fct(function() { return done = true })
})
waitsFor(function(){ return done })
}
if(typeof require === 'function') {
const buster = require("buster")
, semver = require("semver")
, CustomEventEmitter = require("../lib/emitters/custom-event-emitter")
, Helpers = require('./buster-helpers')
, config = require(__dirname + "/config/config")
, dialect = Helpers.getTestDialect()
}
buster.spec.expose()
buster.testRunner.timeout = 1000
var Sequelize = require(__dirname + '/../index')
, noDomains = semver.lt(process.version, '0.8.0')
describe(Helpers.getTestDialectTeaser("Configuration"), function() {
describe('Connections problems should fail with a nice message', function() {
it('when we don\'t have the correct server details', function(done) {
if (noDomains === true) {
console.log('WARNING: Configuration specs requires NodeJS version >= 0.8 for full compatibility')
expect('').toEqual('') // Silence Buster!
return done()
}
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})
, domain = require('domain')
, d = domain.create()
d.on('error', function(err){
var msg = 'Failed to find SQL server. Please double check your settings.'
if (dialect === "postgres" || dialect === "postgres-native") {
msg = 'Failed to find PostgresSQL server. Please double check your settings.'
}
else if (dialect === "mysql") {
msg = 'Failed to find MySQL server. Please double check your settings.'
}
expect(err.message).toEqual(msg)
d.remove(sequelize.query)
done()
})
d.run(function(){
d.add(sequelize.query)
sequelize.query('select 1 as hello')
.success(function(){})
})
})
it('when we don\'t have the correct login information', function(done) {
if (dialect !== "postgres" && dialect !== "postgres-native" && dialect !== "mysql") {
console.log('This dialect doesn\'t support me :(')
expect('').toEqual('') // Silence Buster
return done()
}
if (noDomains === true) {
console.log('WARNING: Configuration specs requires NodeJS version >= 0.8 for full compatibility')
expect('').toEqual('') // Silence Buster!
return done()
}
var sequelize = new Sequelize(config[dialect].database, config[dialect].username, 'fakepass123', {logging: false, host: config[dialect].host, port: 1, dialect: dialect})
, domain = require('domain')
, d = domain.create()
d.on('error', function(err){
var msg = 'Failed to authenticate for SQL. Please double check your settings.'
if (dialect === "postgres" || dialect === "postgres-native") {
msg = 'Failed to authenticate for PostgresSQL. Please double check your settings.'
}
else if (dialect === "mysql") {
msg = 'Failed to authenticate for MySQL. Please double check your settings.'
}
expect(err.message).toEqual(msg)
d.remove(sequelize.query)
done()
})
d.run(function(){
d.add(sequelize.query)
sequelize.query('select 1 as hello')
.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() {
it('should accept username, password, host, port, and database', function() {
var sequelize = new Sequelize('mysql://user:pass@example.com:9821/dbname')
var config = sequelize.config
var options = sequelize.options
expect(options.dialect).toEqual('mysql')
expect(config.database).toEqual('dbname')
expect(config.host).toEqual('example.com')
expect(config.username).toEqual('user')
expect(config.password).toEqual('pass')
expect(config.port).toEqual(9821)
})
it('should work with no authentication options', function() {
var sequelize = new Sequelize('mysql://example.com:9821/dbname')
var config = sequelize.config
expect(config.username).toEqual(undefined)
expect(config.password).toEqual(null)
})
it('should use the default port when no other is specified', function() {
var sequelize = new Sequelize('mysql://example.com/dbname')
var config = sequelize.config
// The default port should be set
expect(config.port).toEqual(3306)
})
})
describe('Intantiation with arguments', function() {
it('should accept two parameters (database, username)', function() {
var sequelize = new Sequelize('dbname', 'root')
var config = sequelize.config
expect(config.database).toEqual('dbname')
expect(config.username).toEqual('root')
})
it('should accept three parameters (database, username, password)', function() {
var sequelize = new Sequelize('dbname', 'root', 'pass')
var config = sequelize.config
expect(config.database).toEqual('dbname')
expect(config.username).toEqual('root')
expect(config.password).toEqual('pass')
})
it('should accept four parameters (database, username, password, options)', function() {
var sequelize = new Sequelize('dbname', 'root', 'pass', { port: 999 })
var config = sequelize.config
expect(config.database).toEqual('dbname')
expect(config.username).toEqual('root')
expect(config.password).toEqual('pass')
expect(config.port).toEqual(999)
})
})
})
if(typeof require === 'function') {
const buster = require("buster")
, Sequelize = require("../index")
, Helpers = require('./buster-helpers')
, dialect = Helpers.getTestDialect()
}
buster.spec.expose()
buster.testRunner.timeout = 1000
describe(Helpers.getTestDialectTeaser("Language Util"), function() {
describe("Plural", function(){
before(function(done) {
Helpers.initTests({
dialect: dialect,
onComplete: function(sequelize) {
this.sequelize = sequelize
this.sequelize.options.language = 'es'
done()
}.bind(this)
})
})
it("should rename tables to their plural form...", function(done){
var table = this.sequelize.define('arbol', {name: Sequelize.STRING})
var table2 = this.sequelize.define('androide', {name: Sequelize.STRING})
expect(table.tableName).toEqual('arboles')
expect(table2.tableName).toEqual('androides')
done()
})
it("should be able to pluralize/singularize associations...", function(done){
var table = this.sequelize.define('arbol', {name: Sequelize.STRING})
var table2 = this.sequelize.define('androide', {name: Sequelize.STRING})
var table3 = this.sequelize.define('hombre', {name: Sequelize.STRING})
table.hasOne(table2)
table2.belongsTo(table)
table3.hasMany(table2)
expect(table.associations.androides.identifier).toEqual('arbolId')
expect(table2.associations.arboles).toBeDefined()
expect(table3.associations.androideshombres).toBeDefined()
done()
})
})
})
if(typeof require === 'function') {
const buster = require("buster")
, Helpers = require('../buster-helpers')
, dialect = Helpers.getTestDialect()
}
buster.spec.expose()
if (dialect.match(/^postgres/)) {
describe('[POSTGRES] hstore', function() {
const hstore = require('../../lib/dialects/postgres/hstore')
describe('stringifyPart', function() {
it("handles undefined values correctly", function() {
expect(hstore.stringifyPart(undefined)).toEqual('NULL')
})
it("handles null values correctly", function() {
expect(hstore.stringifyPart(null)).toEqual('NULL')
})
it("handles boolean values correctly", function() {
expect(hstore.stringifyPart(false)).toEqual('false')
expect(hstore.stringifyPart(true)).toEqual('true')
})
it("handles strings correctly", function() {
expect(hstore.stringifyPart('foo')).toEqual('"foo"')
})
it("handles strings with backslashes correctly", function() {
expect(hstore.stringifyPart("\\'literally\\'")).toEqual('"\\\\\'literally\\\\\'"')
})
it("handles arrays correctly", function() {
expect(hstore.stringifyPart([1,['2'],'"3"'])).toEqual('"[1,[\\"2\\"],\\"\\\\\\"3\\\\\\"\\"]"')
})
it("handles simple objects correctly", function() {
expect(hstore.stringifyPart({ test: 'value' })).toEqual('"{\\"test\\":\\"value\\"}"')
})
it("handles nested objects correctly", function () {
expect(hstore.stringifyPart({ test: { nested: 'value' } })).toEqual('"{\\"test\\":{\\"nested\\":\\"value\\"}}"')
})
it("handles objects correctly", function() {
expect(hstore.stringifyPart({test: {nested: {value: {including: '"string"'}}}})).toEqual('"{\\"test\\":{\\"nested\\":{\\"value\\":{\\"including\\":\\"\\\\\\"string\\\\\\"\\"}}}}"')
})
})
describe('stringify', function() {
it('should handle empty objects correctly', function() {
expect(hstore.stringify({ })).toEqual('')
})
it('should handle null values correctly', function () {
expect(hstore.stringify({ null: null })).toEqual('"null"=>NULL')
})
it('should handle simple objects correctly', function() {
expect(hstore.stringify({ test: 'value' })).toEqual('"test"=>"value"')
})
it('should handle nested objects correctly', function() {
expect(hstore.stringify({ test: { nested: 'value' } })).toEqual('"test"=>"{\\"nested\\":\\"value\\"}"')
})
it('should handle nested arrays correctly', function() {
expect(hstore.stringify({ test: [ 1, '2', [ '"3"' ] ] })).toEqual('"test"=>"[1,\\"2\\",[\\"\\\\\\"3\\\\\\"\\"]]"')
})
it('should handle multiple keys with different types of values', function() {
expect(hstore.stringify({ true: true, false: false, null: null, undefined: undefined, integer: 1, array: [1,'2'], object: { object: 'value' }})).toEqual('"true"=>true,"false"=>false,"null"=>NULL,"undefined"=>NULL,"integer"=>1,"array"=>"[1,\\"2\\"]","object"=>"{\\"object\\":\\"value\\"}"')
})
})
describe('parse', function() {
it('should handle empty objects correctly', function() {
expect(hstore.parse('')).toEqual({ })
})
it('should handle simple objects correctly', function() {
expect(hstore.parse('"test"=>"value"')).toEqual({ test: 'value' })
})
it('should handle nested objects correctly', function() {
expect(hstore.parse('"test"=>"{\\"nested\\":\\"value\\"}"')).toEqual({ test: { nested: 'value' } })
})
it('should handle nested arrays correctly', function() {
expect(hstore.parse('"test"=>"[1,\\"2\\",[\\"\\\\\\"3\\\\\\"\\"]]"')).toEqual({ test: [ 1, '2', [ '"3"' ] ] })
})
it('should handle multiple keys with different types of values', function() {
expect(hstore.parse('"true"=>true,"false"=>false,"null"=>NULL,"undefined"=>NULL,"integer"=>1,"array"=>"[1,\\"2\\"]","object"=>"{\\"object\\":\\"value\\"}"')).toEqual({ true: true, false: false, null: null, undefined: null, integer: 1, array: [1,'2'], object: { object: 'value' }})
})
})
})
}
if (typeof require === 'function') {
const buster = require("buster")
, Helpers = require('./buster-helpers')
, dialect = Helpers.getTestDialect()
, _ = require('lodash')
}
buster.spec.expose()
describe(Helpers.getTestDialectTeaser("Promise"), function () {
before(function (done) {
var self = this
Helpers.initTests({
dialect: dialect,
beforeComplete: function (sequelize, DataTypes) {
self.sequelize = sequelize
self.User = sequelize.define('User', {
username: { type: DataTypes.STRING },
touchedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },
aNumber: { type: DataTypes.INTEGER },
bNumber: { type: DataTypes.INTEGER },
validateTest: {
type: DataTypes.INTEGER,
allowNull: true,
validate: {isInt: true}
},
validateCustom: {
type: DataTypes.STRING,
allowNull: true,
validate: {len: {msg: 'Length failed.', args: [1, 20]}}
},
dateAllowNullTrue: {
type: DataTypes.DATE,
allowNull: true
}
})
self.HistoryLog = sequelize.define('HistoryLog', {
someText: { type: DataTypes.STRING },
aNumber: { type: DataTypes.INTEGER },
aRandomId: { type: DataTypes.INTEGER }
})
self.ParanoidUser = sequelize.define('ParanoidUser', {
username: { type: DataTypes.STRING }
}, {
paranoid: true
})
self.ParanoidUser.hasOne(self.ParanoidUser)
},
onComplete: function () {
self.User.sync({ force: true }).then(function () {
return self.HistoryLog.sync({ force: true })
}).then(function () {
return self.ParanoidUser.sync({force: true })
})
.then(function () {done()}, done)
}
})
})
describe('increment', function () {
before(function (done) {
this.User.create({ id: 1, aNumber: 0, bNumber: 0 }).done(done)
});
it('with array', function (done) {
var self = this;
// Select something
this.User.find(1).then(function (user1) {
return user1.increment(['aNumber'], 2)
}).then(function (user2) {
return self.User.find(1)
}).then(function (user3) {
expect(user3.aNumber).toBe(2);
done();
}, done);
});
it('should still work right with other concurrent updates', function (done) {
var self = this;
// Select something
this.User.find(1).then(function (user1) {
// Select the user again (simulating a concurrent query)
return self.User.find(1).then(function (user2) {
return user2.updateAttributes({
aNumber: user2.aNumber + 1
}).then(function (user3) {
return user1.increment(['aNumber'], 2)
}).then(function (user4) {
return self.User.find(1)
}).then(function (user5) {
expect(user5.aNumber).toBe(3);
done();
}, done)
});
});
});
it('with key value pair', function (done) {
var self = this;
// Select something
this.User.find(1).then(function (user1) {
return user1.increment({ 'aNumber': 1, 'bNumber': 2})
}).then(function () {
return self.User.find(1)
}).then(function (user3) {
expect(user3.aNumber).toBe(1);
expect(user3.bNumber).toBe(2);
done();
}, done);
});
});
describe('decrement', function () {
before(function (done) {
this.User.create({ id: 1, aNumber: 0, bNumber: 0 }).done(done)
});
it('with array', function (done) {
var self = this;
// Select something
this.User.find(1).then(function (user1) {
return user1.decrement(['aNumber'], 2)
}).then(function () {
return self.User.find(1);
}).then(function (user3) {
expect(user3.aNumber).toBe(-2);
done();
}, done);
});
it('with single field', function (done) {
var self = this;
// Select something
this.User.find(1).then(function (user1) {
return user1.decrement(['aNumber'], 2)
}).then(function () {
return self.User.find(1);
}).then(function (user3) {
expect(user3.aNumber).toBe(-2);
done();
}, done);
});
it('should still work right with other concurrent decrements', function (done) {
var self = this;
// Select something
this.User.find(1).then(function (user1) {
var _done = _.after(3, function () {
self.User.find(1).then(function (user2) {
expect(user2.aNumber).toEqual(-6);
done();
})
});
user1.decrement(['aNumber'], 2).done(_done);
user1.decrement(['aNumber'], 2).done(_done);
user1.decrement(['aNumber'], 2).done(_done);
});
});
});
describe('reload', function () {
it("should return a reference to the same DAO instead of creating a new one", function (done) {
this.User.create({ username: 'John Doe' }).then(function (originalUser) {
return originalUser.updateAttributes({ username: 'Doe John' }).then(function () {
return originalUser.reload()
}).then(function (updatedUser) {
expect(originalUser === updatedUser).toBeTrue()
done();
}, done)
})
})
it("should update the values on all references to the DAO", function (done) {
var self = this
this.User.create({ username: 'John Doe' }).then(function (originalUser) {
return self.User.find(originalUser.id).then(function (updater) {
return updater.updateAttributes({ username: 'Doe John' })
}).then(function () {
// We used a different reference when calling updateAttributes, so originalUser is now out of sync
expect(originalUser.username).toEqual('John Doe')
return originalUser.reload()
}).then(function (updatedUser) {
expect(originalUser.username).toEqual('Doe John')
expect(updatedUser.username).toEqual('Doe John')
done();
}, done)
})
})
it("should update the associations as well", function (done) {
var Book = this.sequelize.define('Book', { title: Helpers.Sequelize.STRING })
, Page = this.sequelize.define('Page', { content: Helpers.Sequelize.TEXT })
Book.hasMany(Page)
Page.belongsTo(Book)
this.sequelize.sync({ force: true }).then(function () {
return Book.create({ title: 'A very old book' })
}).then(function (book) {
return Page.create({ content: 'om nom nom' }).then(function (page) {
return book.setPages([ page ]).then(function () {
return Book.find({
where: (dialect === 'postgres' ? '"Books"."id"=' : '`Books`.`id`=') + book.id,
include: [Page]
}).then(function (leBook) {
return page.updateAttributes({ content: 'something totally different' }).then(function (page) {
expect(leBook.pages[0].content).toEqual('om nom nom')
expect(page.content).toEqual('something totally different')
return leBook.reload().then(function (leBook) {
expect(leBook.pages[0].content).toEqual('something totally different')
expect(page.content).toEqual('something totally different')
done()
})
})
})
})
})
}, done)
})
});
describe('complete', function () {
it("gets triggered if an error occurs", function (done) {
this.User.find({ where: "asdasdasd" }).then(null, function (err) {
expect(err).toBeDefined()
expect(err.message).toBeDefined()
done()
})
})
it("gets triggered if everything was ok", function (done) {
this.User.count().then(function (result) {
expect(result).toBeDefined()
done()
})
})
})
describe('save', function () {
it('should fail a validation upon creating', function (done) {
this.User.create({aNumber: 0, validateTest: 'hello'}).then(null, function (err) {
expect(err).toBeDefined()
expect(err).toBeObject()
expect(err.validateTest).toBeArray()
expect(err.validateTest[0]).toBeDefined()
expect(err.validateTest[0].indexOf('Invalid integer')).toBeGreaterThan(-1);
done();
});
})
it('should fail a validation upon building', function (done) {
this.User.build({aNumber: 0, validateCustom: 'aaaaaaaaaaaaaaaaaaaaaaaaaa'}).save()
.then(null, function (err) {
expect(err).toBeDefined()
expect(err).toBeObject()
expect(err.validateCustom).toBeDefined()
expect(err.validateCustom).toBeArray()
expect(err.validateCustom[0]).toBeDefined()
expect(err.validateCustom[0]).toEqual('Length failed.')
done()
})
})
it('should fail a validation when updating', function (done) {
this.User.create({aNumber: 0}).then(function (user) {
return user.updateAttributes({validateTest: 'hello'})
}).then(null, function (err) {
expect(err).toBeDefined()
expect(err).toBeObject()
expect(err.validateTest).toBeDefined()
expect(err.validateTest).toBeArray()
expect(err.validateTest[0]).toBeDefined()
expect(err.validateTest[0].indexOf('Invalid integer')).toBeGreaterThan(-1)
done()
})
})
})
})
if(typeof require === 'function') {
const buster = require("buster")
, CustomEventEmitter = require("../lib/emitters/custom-event-emitter")
, Helpers = require('./buster-helpers')
, dialect = Helpers.getTestDialect()
}
buster.spec.expose()
buster.testRunner.timeout = 1000
describe(Helpers.getTestDialectTeaser("QueryInterface"), function() {
before(function(done) {
Helpers.initTests({
dialect: dialect,
beforeComplete: function(sequelize) {
this.sequelize = sequelize
}.bind(this),
onComplete: function() {
this.interface = this.sequelize.getQueryInterface()
done()
}.bind(this)
})
})
describe('dropAllTables', function() {
it("should drop all tables", function(done) {
this.interface.dropAllTables().complete(function(err) {
expect(err).toBeNull()
this.interface.showAllTables().complete(function(err, tableNames) {
expect(err).toBeNull()
expect(tableNames.length).toEqual(0)
this.interface.createTable('table', { name: Helpers.Sequelize.STRING }).complete(function(err) {
expect(err).toBeNull()
this.interface.showAllTables().complete(function(err, tableNames) {
expect(err).toBeNull()
expect(tableNames.length).toEqual(1)
this.interface.dropAllTables().complete(function(err) {
expect(err).toBeNull()
this.interface.showAllTables().complete(function(err, tableNames) {
expect(err).toBeNull()
expect(tableNames.length).toEqual(0)
done()
})
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
})
})
describe('indexes', function() {
before(function(done) {
this.interface.createTable('User', {
username: Helpers.Sequelize.STRING,
isAdmin: Helpers.Sequelize.BOOLEAN
}).success(done)
})
it('adds, reads and removes an index to the table', function(done) {
this.interface.addIndex('User', ['username', 'isAdmin']).complete(function(err) {
expect(err).toBeNull()
this.interface.showIndex('User').complete(function(err, indexes) {
expect(err).toBeNull()
var indexColumns = Helpers.Sequelize.Utils._.uniq(indexes.map(function(index) { return index.name }))
expect(indexColumns).toEqual(['user_username_is_admin'])
this.interface.removeIndex('User', ['username', 'isAdmin']).complete(function(err) {
expect(err).toBeNull()
this.interface.showIndex('User').complete(function(err, indexes) {
expect(err).toBeNull()
indexColumns = Helpers.Sequelize.Utils._.uniq(indexes.map(function(index) { return index.name }))
expect(indexColumns).toEqual([])
done()
})
}.bind(this))
}.bind(this))
}.bind(this))
})
})
describe('describeTable', function() {
before(function(done) {
this.interface.createTable('User', {
username: Helpers.Sequelize.STRING,
isAdmin: Helpers.Sequelize.BOOLEAN,
enumVals: Helpers.Sequelize.ENUM('hello', 'world')
}).success(done)
})
it('reads the metadata of the table', function(done) {
this.interface.describeTable('User').complete(function(err, metadata) {
expect(err).toBeNull()
var username = metadata.username
var isAdmin = metadata.isAdmin
var enumVals = metadata.enumVals
expect(username.type).toEqual(dialect === 'postgres' ? 'CHARACTER VARYING' : 'VARCHAR(255)')
expect(username.allowNull).toBeTrue()
expect(username.defaultValue).toBeNull()
expect(isAdmin.type).toEqual(dialect === 'postgres' ? 'BOOLEAN' : 'TINYINT(1)')
expect(isAdmin.allowNull).toBeTrue()
expect(isAdmin.defaultValue).toBeNull()
if (dialect === 'postgres') {
expect(enumVals.special).toBeArray();
expect(enumVals.special.length).toEqual(2);
}
done()
})
})
})
})
if(typeof require === 'function') {
const buster = require("buster")
, Helpers = require('./buster-helpers')
, dialect = Helpers.getTestDialect()
}
var qq = function(str) {
if (dialect == 'postgres' || dialect == 'sqlite') {
return '"' + str + '"'
} else if (dialect == 'mysql') {
return '`' + str + '`'
} else {
return str
}
}
buster.spec.expose()
describe(Helpers.getTestDialectTeaser("Sequelize"), function() {
before(function(done) {
Helpers.initTests({
dialect: dialect,
beforeComplete: function(sequelize) { this.sequelize = sequelize }.bind(this),
onComplete: done
})
})
describe('isDefined', function() {
it("returns false if the dao wasn't defined before", function() {
expect(this.sequelize.isDefined('Project')).toBeFalse()
})
it("returns true if the dao was defined before", function() {
this.sequelize.define('Project', {
name: Helpers.Sequelize.STRING
})
expect(this.sequelize.isDefined('Project')).toBeTrue()
})
})
describe('query', function() {
before(function(done) {
this.User = this.sequelize.define('User', {
username: Helpers.Sequelize.STRING
})
this.insertQuery = "INSERT INTO " + qq(this.User.tableName) + " (username, " + qq("createdAt") + ", " + qq("updatedAt") + ") VALUES ('john', '2012-01-01 10:10:10', '2012-01-01 10:10:10')"
this.User.sync().success(done).error(function(err) {
console(err)
done()
})
})
it('executes a query the internal way', function(done) {
this.sequelize.query(this.insertQuery, null, { raw: true })
.complete(function(err, result) {
if (err) {
console.log(err)
}
expect(err).toBeNull()
expect(result).toBeNull()
done()
})
})
it('executes a query if only the sql is passed', function(done) {
this.sequelize.query(this.insertQuery)
.complete(function(err, result) {
if (err) {
console.log(err)
}
expect(err).toBeNull()
expect(result).not.toBeDefined()
done()
})
})
it('executes select queries correctly', function(done) {
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 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) {
this.sequelize.query(this.insertQuery).success(function() {
this.sequelize
.query("select username as " + qq("user.username") + " from " + qq(this.User.tableName) + "")
.complete(function(err, users) {
if (err) {
console.log(err)
}
expect(err).toBeNull()
expect(users.map(function(u){ return u.user })).toEqual([{'username':'john'}])
done()
})
}.bind(this))
})
if (dialect == 'mysql') {
it('executes stored procedures', function(done) {
this.sequelize.query(this.insertQuery).success(function() {
this.sequelize.query('DROP PROCEDURE IF EXISTS foo').success(function() {
this.sequelize.query(
"CREATE PROCEDURE foo()\nSELECT * FROM " + this.User.tableName + ";"
).success(function() {
this.sequelize.query('CALL foo()').success(function(users) {
expect(users.map(function(u){ return u.username })).toEqual(['john'])
done()
})
}.bind(this))
}.bind(this))
}.bind(this))
})
} else {
console.log('FIXME: I want to be supported in this dialect as well :-(')
}
it('uses the passed DAOFactory', function(done) {
this.sequelize.query(this.insertQuery).success(function() {
this.sequelize.query("SELECT * FROM " + qq(this.User.tableName) + ";", this.User).success(function(users) {
expect(users[0].__factory).toEqual(this.User)
done()
}.bind(this))
}.bind(this))
})
it('destructs dot separated attributes when doing a raw query', function(done) {
var tickChar = (dialect === 'postgres') ? '"' : '`'
, sql = "select 1 as " + Helpers.Sequelize.Utils.addTicks('foo.bar.baz', tickChar)
this.sequelize.query(sql, null, { raw: true }).success(function(result) {
expect(result).toEqual([ { foo: { bar: { baz: 1 } } } ])
done()
})
})
it('replaces token with the passed array', function(done) {
this.sequelize.query('select ? as foo, ? as bar', null, { raw: true }, [ 1, 2 ]).success(function(result) {
expect(result).toEqual([{ foo: 1, bar: 2 }])
done()
})
})
})
describe('define', function() {
[
{ type: Helpers.Sequelize.ENUM, values: ['scheduled', 'active', 'finished']},
Helpers.Sequelize.ENUM('scheduled', 'active', 'finished')
].forEach(function(status) {
describe('enum', function() {
before(function(done) {
this.Review = this.sequelize.define('review', { status: status })
this.Review.sync({ force: true }).success(done)
})
it('raises an error if no values are defined', function() {
Helpers.assertException(function() {
this.sequelize.define('omnomnom', {
bla: { type: Helpers.Sequelize.ENUM }
})
}.bind(this), 'Values for ENUM haven\'t been defined.')
})
it('correctly stores values', function(done) {
this.Review.create({ status: 'active' }).success(function(review) {
expect(review.status).toEqual('active')
done()
})
})
it('correctly loads values', function(done) {
this.Review.create({ status: 'active' }).success(function() {
this.Review.findAll().success(function(reviews) {
expect(reviews[0].status).toEqual('active')
done()
})
}.bind(this))
})
it("doesn't save an instance if value is not in the range of enums", function() {
Helpers.assertException(function() {
this.Review.create({ status: 'fnord' })
}.bind(this), 'Value "fnord" for ENUM status is out of allowed scope. Allowed values: scheduled, active, finished')
})
})
})
describe('table', function() {
[
{ id: { type: Helpers.Sequelize.BIGINT } },
{ id: { type: Helpers.Sequelize.STRING, allowNull: true } },
{ id: { type: Helpers.Sequelize.BIGINT, allowNull: false, primaryKey: true, autoIncrement: true } }
].forEach(function(customAttributes) {
it('should be able to override options on the default attributes', function(done) {
var Picture = this.sequelize.define('picture', Helpers.Sequelize.Utils._.cloneDeep(customAttributes))
Picture.sync({ force: true }).success(function() {
Object.keys(customAttributes).forEach(function(attribute) {
Object.keys(customAttributes[attribute]).forEach(function(option) {
var optionValue = customAttributes[attribute][option];
expect(Picture.rawAttributes[attribute][option]).toBe(optionValue)
});
})
done()
})
})
})
})
})
})
...@@ -6,7 +6,7 @@ module.exports = { ...@@ -6,7 +6,7 @@ module.exports = {
pool: { maxConnections: 5, maxIdleTime: 30000}, pool: { maxConnections: 5, maxIdleTime: 30000},
rand: function() { rand: function() {
return parseInt(Math.random() * 999) return parseInt(Math.random() * 999, 10)
}, },
//make maxIdleTime small so that tests exit promptly //make maxIdleTime small so that tests exit promptly
...@@ -26,6 +26,6 @@ module.exports = { ...@@ -26,6 +26,6 @@ module.exports = {
database: 'sequelize_test', database: 'sequelize_test',
username: "postgres", username: "postgres",
port: 5432, port: 5432,
pool: { maxConnections: 5, maxIdleTime: 30} pool: { maxConnections: 5, maxIdleTime: 3000}
} }
} }
var chai = require('chai')
, expect = chai.expect
, config = require(__dirname + "/config/config")
, Support = require(__dirname + '/support')
, dialect = Support.getTestDialect()
, Sequelize = require(__dirname + '/../index')
chai.Assertion.includeStack = true
describe(Support.getTestDialectTeaser("Configuration"), function() {
describe('Connections problems should fail with a nice message', function() {
it("when we don't have the correct server details", function(done) {
// mysql is not properly supported due to the custom pooling system
if (dialect !== "postgres" && dialect !== "postgres-native") {
console.log('This dialect doesn\'t support me :(')
expect(true).to.be.true // Silence Buster
return done()
}
var seq = 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})
seq.query('select 1 as hello').error(function(err) {
expect(err.message).to.match(/Failed to find (.*?) Please double check your settings\./)
done()
})
})
it('when we don\'t have the correct login information', function(done) {
if (dialect !== "postgres" && dialect !== "postgres-native") {
console.log('This dialect doesn\'t support me :(')
expect(true).to.be.true // Silence Buster
return done()
}
var seq = new Sequelize(config[dialect].database, config[dialect].username, 'fakepass123', {logging: false, host: config[dialect].host, port: 1, dialect: dialect})
seq.query('select 1 as hello').error(function(err) {
expect(err.message).to.match(/^Failed to authenticate/)
done()
})
})
it('when we don\'t have a valid dialect.', function(done) {
expect(function() {
new Sequelize(config[dialect].database, config[dialect].username, config[dialect].password, {host: '0.0.0.1', port: config[dialect].port, dialect: undefined})
}).to.throw(Error, 'The dialect undefined is not supported.')
done()
})
})
describe('Instantiation with a URL string', function() {
it('should accept username, password, host, port, and database', function() {
var sequelize = new Sequelize('mysql://user:pass@example.com:9821/dbname')
var config = sequelize.config
var options = sequelize.options
expect(options.dialect).to.equal('mysql')
expect(config.database).to.equal('dbname')
expect(config.host).to.equal('example.com')
expect(config.username).to.equal('user')
expect(config.password).to.equal('pass')
expect(config.port).to.equal('9821')
})
it('should work with no authentication options', function(done) {
var sequelize = new Sequelize('mysql://example.com:9821/dbname')
var config = sequelize.config
expect(config.username).to.not.be.ok
expect(config.password).to.be.null
done()
})
it('should use the default port when no other is specified', function(done) {
var sequelize = new Sequelize('mysql://example.com/dbname')
var config = sequelize.config
// The default port should be set
expect(config.port).to.equal(3306)
done()
})
})
describe('Intantiation with arguments', function() {
it('should accept two parameters (database, username)', function(done) {
var sequelize = new Sequelize('dbname', 'root')
var config = sequelize.config
expect(config.database).to.equal('dbname')
expect(config.username).to.equal('root')
done()
})
it('should accept three parameters (database, username, password)', function(done) {
var sequelize = new Sequelize('dbname', 'root', 'pass')
var config = sequelize.config
expect(config.database).to.equal('dbname')
expect(config.username).to.equal('root')
expect(config.password).to.equal('pass')
done()
})
it('should accept four parameters (database, username, password, options)', function(done) {
var sequelize = new Sequelize('dbname', 'root', 'pass', { port: 999 })
var config = sequelize.config
expect(config.database).to.equal('dbname')
expect(config.username).to.equal('root')
expect(config.password).to.equal('pass')
expect(config.port).to.equal(999)
done()
})
})
})
if(typeof require === 'function') { var chai = require('chai')
const buster = require("buster") , expect = chai.expect
, Sequelize = require("../index") , Sequelize = require(__dirname + '/../index')
, Helpers = require('./buster-helpers') , Support = require(__dirname + '/support')
, dialect = Helpers.getTestDialect() , config = require(__dirname + '/config/config')
}
buster.spec.expose() chai.Assertion.includeStack = true
buster.testRunner.timeout = 1000
describe(Helpers.getTestDialectTeaser("DaoValidator"), function() { describe(Support.getTestDialectTeaser("DaoValidator"), function() {
describe('validations', function() { describe('validations', function() {
before(function(done) {
Helpers.initTests({
dialect: dialect,
onComplete: function(sequelize) {
this.sequelize = sequelize
done()
}.bind(this)
})
}) //- before
var checks = { var checks = {
is: { is: {
spec: { args: ["[a-z]",'i'] }, spec: { args: ["[a-z]",'i'] },
...@@ -116,72 +104,72 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() { ...@@ -116,72 +104,72 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() {
fail: "a", fail: "a",
pass: "0" pass: "0"
} }
, len : { , len: {
spec: { args: [2,4] }, spec: { args: [2,4] },
fail: ["1", "12345"], fail: ["1", "12345"],
pass: ["12", "123", "1234"], pass: ["12", "123", "1234"],
raw: true raw: true
} }
, len: { , len$: {
spec: [2,4], spec: [2,4],
fail: ["1", "12345"], fail: ["1", "12345"],
pass: ["12", "123", "1234"], pass: ["12", "123", "1234"],
raw: true raw: true
} }
, isUUID : { , isUUID: {
spec: { args: 4 }, spec: { args: 4 },
fail: "f47ac10b-58cc-3372-a567-0e02b2c3d479", fail: "f47ac10b-58cc-3372-a567-0e02b2c3d479",
pass: "f47ac10b-58cc-4372-a567-0e02b2c3d479" pass: "f47ac10b-58cc-4372-a567-0e02b2c3d479"
} }
, isDate : { , isDate: {
fail: "not a date", fail: "not a date",
pass: "2011-02-04" pass: "2011-02-04"
} }
, isAfter : { , isAfter: {
spec: { args: "2011-11-05" }, spec: { args: "2011-11-05" },
fail: "2011-11-04", fail: "2011-11-04",
pass: "2011-11-05" pass: "2011-11-05"
} }
, isBefore : { , isBefore: {
spec: { args: "2011-11-05" }, spec: { args: "2011-11-05" },
fail: "2011-11-06", fail: "2011-11-06",
pass: "2011-11-05" pass: "2011-11-05"
} }
, isIn : { , isIn: {
spec: { args: "abcdefghijk" }, spec: { args: "abcdefghijk" },
fail: "ghik", fail: "ghik",
pass: "ghij" pass: "ghij"
} }
, notIn : { , notIn: {
spec: { args: "abcdefghijk" }, spec: { args: "abcdefghijk" },
fail: "ghij", fail: "ghij",
pass: "ghik" pass: "ghik"
} }
, max : { , max: {
spec: { args: 23 }, spec: { args: 23 },
fail: "24", fail: "24",
pass: "23" pass: "23"
} }
, max : { , max$: {
spec: 23, spec: 23,
fail: "24", fail: "24",
pass: "23" pass: "23"
} }
, min : { , min: {
spec: { args: 23 }, spec: { args: 23 },
fail: "22", fail: "22",
pass: "23" pass: "23"
} }
, min : { , min$: {
spec: 23, spec: 23,
fail: "22", fail: "22",
pass: "23" pass: "23"
} }
, isArray : { , isArray: {
fail: 22, fail: 22,
pass: [22] pass: [22]
} }
, isCreditCard : { , isCreditCard: {
fail: "401288888888188f", fail: "401288888888188f",
pass: "4012888888881881" pass: "4012888888881881"
} }
...@@ -189,6 +177,8 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() { ...@@ -189,6 +177,8 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() {
for (var validator in checks) { for (var validator in checks) {
if (checks.hasOwnProperty(validator)) { if (checks.hasOwnProperty(validator)) {
validator = validator.replace(/\$$/, '')
var validatorDetails = checks[validator] var validatorDetails = checks[validator]
if (!validatorDetails.hasOwnProperty("raw")) { if (!validatorDetails.hasOwnProperty("raw")) {
...@@ -214,7 +204,7 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() { ...@@ -214,7 +204,7 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() {
validations[validator].msg = message validations[validator].msg = message
var UserFail = this.sequelize.define('User' + Math.random(), { var UserFail = this.sequelize.define('User' + config.rand(), {
name: { name: {
type: Sequelize.STRING, type: Sequelize.STRING,
validate: validations validate: validations
...@@ -222,20 +212,20 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() { ...@@ -222,20 +212,20 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() {
}) })
var failingUser = UserFail.build({ name : failingValue }) var failingUser = UserFail.build({ name : failingValue })
, errors = undefined; , errors = undefined
failingUser.validate().done( function(err,_errors) { failingUser.validate().done( function(err,_errors) {
expect(_errors).not.toBeNull(); expect(_errors).to.not.be.null
expect(_errors).toEqual({ name : [message] }); expect(_errors).to.deep.eql({ name : [message] })
done(); done()
}); })
}); })
} }
//////////////////////////// ////////////////////////////
// test the success cases // // test the success cases //
//////////////////////////// ////////////////////////////
for (var j = 0; j < validatorDetails.pass.length; j++) { for (var j = 0; j < validatorDetails.pass.length; j++) {
var succeedingValue = validatorDetails.pass[j] var succeedingValue = validatorDetails.pass[j]
...@@ -250,29 +240,83 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() { ...@@ -250,29 +240,83 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() {
validations[validator].msg = validator + "(" + succeedingValue + ")" validations[validator].msg = validator + "(" + succeedingValue + ")"
var UserSuccess = this.sequelize.define('User' + Math.random(), { var UserSuccess = this.sequelize.define('User' + config.rand(), {
name: { name: {
type: Sequelize.STRING, type: Sequelize.STRING,
validate: validations validate: validations
} }
}) })
var successfulUser = UserSuccess.build({ name: succeedingValue }); var successfulUser = UserSuccess.build({ name: succeedingValue })
successfulUser.validate().success( function() { successfulUser.validate().success( function() {
expect(arguments.length).toBe(0); expect(arguments).to.have.length(0)
done(); done()
}).error(function(err) { }).error(function(err) {
expect(err).toBe({}) expect(err).to.be.deep.equal({})
done(); done()
}); })
}) })
} }
} }
} }
describe('#create', function() {
beforeEach(function(done) {
var self = this
var Project = this.sequelize.define('Project', {
name: {
type: Sequelize.STRING,
allowNull: false,
defaultValue: 'unknown',
validate: {
isIn: [['unknown', 'hello', 'test']]
}
}
})
var Task = this.sequelize.define('Task', {
something: Sequelize.INTEGER
})
Project.hasOne(Task)
Task.hasOne(Project)
Project.sync({ force: true }).success(function() {
Task.sync({ force: true }).success(function() {
self.Project = Project
self.Task = Task
done()
})
})
})
it('correctly validates using create method ', function(done) {
var self = this
this.Project.create({}).success(function(project) {
self.Task.create({something: 1}).success(function(task) {
project.setTask(task).success(function(task) {
expect(task.ProjectId).to.not.be.null
task.setProject(project).success(function(project) {
expect(project.ProjectId).to.not.be.null
done()
})
})
})
})
})
it('correctly throws an error using create method ', function(done) {
var self = this
this.Project.create({name: 'nope'}).error(function(err) {
expect(err).to.have.ownProperty('name')
done()
})
})
})
it('correctly validates using custom validation methods', function(done) { it('correctly validates using custom validation methods', function(done) {
var User = this.sequelize.define('User' + Math.random(), { var User = this.sequelize.define('User' + config.rand(), {
name: { name: {
type: Sequelize.STRING, type: Sequelize.STRING,
validate: { validate: {
...@@ -290,22 +334,21 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() { ...@@ -290,22 +334,21 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() {
var failingUser = User.build({ name : "3" }) var failingUser = User.build({ name : "3" })
failingUser.validate().success(function(errors) { failingUser.validate().success(function(errors) {
expect(errors).toEqual({ name: ["name should equal '2'"] }) expect(errors).to.deep.equal({ name: ["name should equal '2'"] })
var successfulUser = User.build({ name : "2" }) var successfulUser = User.build({ name : "2" })
successfulUser.validate().success(function() { successfulUser.validate().success(function() {
expect(arguments.length).toBe(0); expect(arguments).to.have.length(0)
done(); done()
}).error(function(err) { }).error(function(err) {
expect(err).toBe({}); expect(err).to.deep.equal({})
done(); done()
})
}) })
});
}) })
it('skips other validations if allowNull is true and the value is null', function(done) { it('skips other validations if allowNull is true and the value is null', function(done) {
var User = this.sequelize.define('User' + Math.random(), { var User = this.sequelize.define('User' + config.rand(), {
age: { age: {
type: Sequelize.INTEGER, type: Sequelize.INTEGER,
allowNull: true, allowNull: true,
...@@ -319,8 +362,8 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() { ...@@ -319,8 +362,8 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() {
.build({ age: -1 }) .build({ age: -1 })
.validate() .validate()
.success(function(errors) { .success(function(errors) {
expect(errors).not.toBeNull(null) expect(errors).not.to.be.null
expect(errors).toEqual({ age: ['must be positive'] }) expect(errors).to.deep.equal({ age: ['must be positive'] })
User.build({ age: null }).validate().success(function() { User.build({ age: null }).validate().success(function() {
User.build({ age: 1 }).validate().success(function() { User.build({ age: 1 }).validate().success(function() {
...@@ -331,7 +374,7 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() { ...@@ -331,7 +374,7 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() {
}) })
it('validates a model with custom model-wide validation methods', function(done) { it('validates a model with custom model-wide validation methods', function(done) {
var Foo = this.sequelize.define('Foo' + Math.random(), { var Foo = this.sequelize.define('Foo' + config.rand(), {
field1: { field1: {
type: Sequelize.INTEGER, type: Sequelize.INTEGER,
allowNull: true allowNull: true
...@@ -356,38 +399,37 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() { ...@@ -356,38 +399,37 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() {
.build({ field1: null, field2: null }) .build({ field1: null, field2: null })
.validate() .validate()
.success(function(errors) { .success(function(errors) {
expect(errors).not.toBeNull() expect(errors).not.to.be.null
expect(errors).toEqual({ 'xnor': ['xnor failed'] }) expect(errors).to.deep.equal({ 'xnor': ['xnor failed'] })
Foo Foo
.build({ field1: 33, field2: null }) .build({ field1: 33, field2: null })
.validate() .validate()
.success(function(errors) { .success(function(errors) {
expect(errors).not.toBeDefined() expect(errors).not.exist
done() done()
}) })
}) })
}) })
it('validates model with a validator whose arg is an Array successfully twice in a row', function(done){ it('validates model with a validator whose arg is an Array successfully twice in a row', function(done){
var Foo = this.sequelize.define('Foo' + Math.random(), { var Foo = this.sequelize.define('Foo' + config.rand(), {
bar: { bar: {
type: Sequelize.STRING, type: Sequelize.STRING,
validate: { validate: {
isIn: [['a', 'b']] isIn: [['a', 'b']]
} }
} }
}), foo; }), foo
foo = Foo foo = Foo.build({bar:'a'})
.build({bar:'a'});
foo.validate().success(function(errors){ foo.validate().success(function(errors){
expect(errors).not.toBeDefined() expect(errors).not.to.exist
foo.validate().success(function(errors){ foo.validate().success(function(errors){
expect(errors).not.toBeDefined() expect(errors).not.to.exist
done(); done()
}); })
}); })
}); })
}) })
}) })
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!