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

Commit aaff7157 by Edgar Veiga

Merge from upstream.

2 parents 87a378d0 03d0fc39
Showing with 1439 additions and 838 deletions
[submodule "doc"]
path = doc
url = git://github.com/sdepold/sequelize-doc.git
...@@ -3,18 +3,22 @@ before_script: ...@@ -3,18 +3,22 @@ before_script:
- "psql -c 'create database sequelize_test;' -U postgres" - "psql -c 'create database sequelize_test;' -U postgres"
script: script:
- "npm test" - "npm run test-buster-travis"
- "npm run test-jasmine"
notifications: notifications:
email: email:
- sascha@depold.com - sascha@depold.com
env: env:
- DB=mysql - DB=mysql DIALECT=mysql
- DB=mysql DIALECT=postgres
- DB=mysql DIALECT=postgres-native
- DB=mysql DIALECT=sqlite
language: node_js language: node_js
node_js: node_js:
- 0.6
- 0.8 - 0.8
- 0.9
...@@ -30,31 +30,33 @@ Also make sure to take a look at the examples in the repository. The website wil ...@@ -30,31 +30,33 @@ Also make sure to take a look at the examples in the repository. The website wil
- [IRC](irc://irc.freenode.net/sequelizejs) - [IRC](irc://irc.freenode.net/sequelizejs)
- [XING](https://www.xing.com/net/priec1b5cx/sequelize) - [XING](https://www.xing.com/net/priec1b5cx/sequelize)
## Collaboration ## ## Collaboration 2.0 ##
I'm glad to get pull request if any functionality is missing or something is buggy. But _please_ ... run the tests before you send me the pull request. I'm glad to get pull request if any functionality is missing or something is buggy. But _please_ ... run the tests before you send me the pull request.
Now if you want to contribute but don't really know where to begin Still interested? Coolio! Here is how to get started:
don't worry, the steps below will guide you to have a sequelize
contributor's environment running in a couple minutes.
### 1. Prepare the environment ### ### 1. Prepare your environment ###
All the following steps consider you already have [npm](http://npmjs.org/) installed in your [node.js version 0.4.6 or higher](https://github.com/sdepold/sequelize/blob/master/package.json#L30) Here comes a little surprise: You need [Node.JS](http://nodejs.org). In order to be
a productive developer, I would recommend the latest v0.8 (or a stable 0.9 if
already out). Also I usually recommend [NVM](https://github.com/creationix/nvm).
#### 1.1 MySQL and other external dependencies #### Once Node.JS is installed on your computer, you will also have access to the lovely
Node Package Manager (NPM).
Contributing to sequelize requires you to have ### 2. Database... Come to me! ###
[MySQL](http://www.mysql.com/) up and running in your local
environment. The reason for that is that we have test cases that runs
against an actual MySQL server and make sure everything is always
working.
That is also one of the reasons your features must come with tests: First class citizen of Sequelize was MySQL. Over time, Sequelize began to
let's make sure sequelize will stay awesome as more features are added become compatible to SQLite and PostgreSQL. In order to provide a fully
as well as that fixed bugs will never come back. featured pull request, you would most likely want to install of them. Give
it a try, it's not that hard.
Well, after installing **MySQL** you also need to create the sequelize test database: If you are too lazy or just don't know how to get this work,
feel free to join the IRC channel (freenode@#sequelizejs).
For MySQL and PostgreSQL you'll need to create a DB called `sequelize_test`.
For MySQL this would look like this:
```console ```console
$ echo "CREATE DATABASE sequelize_test;" | mysql -uroot $ echo "CREATE DATABASE sequelize_test;" | mysql -uroot
...@@ -65,10 +67,11 @@ $ echo "CREATE DATABASE sequelize_test;" | mysql -uroot ...@@ -65,10 +67,11 @@ $ echo "CREATE DATABASE sequelize_test;" | mysql -uroot
tests, but make sure to don't commit your credentials, we don't want tests, but make sure to don't commit your credentials, we don't want
to expose your personal data in sequelize codebase ;) to expose your personal data in sequelize codebase ;)
**AND ONE LAST THING:** Sequelize also supports SQLite. So this should be working **AND ONE LAST THING:** Once `npm install` worked for you (see below), you'll
on your machine as well :) get SQLite tests for free :)
### 2. Install the dependencies ### ### 3. Install the dependencies ###
Just "cd" into sequelize directory and run `npm install`, see an example below: Just "cd" into sequelize directory and run `npm install`, see an example below:
...@@ -77,25 +80,44 @@ $ cd path/to/sequelize ...@@ -77,25 +80,44 @@ $ cd path/to/sequelize
$ npm install $ npm install
``` ```
### 3. Run the tests ### ### 4. Run the tests ###
In order to run the tests you got to run `jasmine-node` against the `spec` directory. Right now, the test base is split into the `spec` folder (which contains the
By the way, [there](https://github.com/sdepold/sequelize/tree/master/spec) is where lovely [BusterJS](http://busterjs.org) tests) and the `spec-jasmine` folder
you will write new tests if that's the case. (which contains the ugly and awkward node-jasmine based tests). A main goal
is to get rid of the jasmine tests!
All you need is to run `./node_modules/.bin/jasmine-node spec/`, As you might haven't installed all of the supported SQL dialects, here is how
although this is kinda long and boring, so we configures a NPM task to run the test suites for your development environment:
and made that less laborious to you :)
```console ```console
$ # run all tests at once:
$ npm test $ npm test
$ # 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:
$ npm run test-buster-mysql
$ # run the buster specs for sqlite:
$ npm run test-buster-sqlite
$ # run the buster specs for postgresql:
$ npm run test-buster-postgres
``` ```
### 4. That's all ### ### 5. That's all ###
Just commit and send pull requests. Just commit and send pull requests.
Happy hacking and thank you for contributing.
Ah and one last thing: If you think you deserve it, feel free to add yourself to the
`package.json`. Also I always look for projects which are using sequelize. If you have
one of them, drop me a line!
Happy hacking and thank you for contributing
# Build status # Build status
......
...@@ -9,9 +9,11 @@ const path = require("path") ...@@ -9,9 +9,11 @@ const path = require("path")
var configPath = process.cwd() + '/config' var configPath = process.cwd() + '/config'
, migrationsPath = process.cwd() + '/migrations' , migrationsPath = process.cwd() + '/migrations'
, packageJsonPath = __dirname + '/../package.json'
, packageJson = JSON.parse(fs.readFileSync(packageJsonPath).toString())
, configFile = configPath + '/config.json' , configFile = configPath + '/config.json'
, configPathExists = path.existsSync(configPath) , configPathExists = fs.existsSync(configPath)
, configFileExists = path.existsSync(configFile) , configFileExists = fs.existsSync(configFile)
var writeConfig = function(config) { var writeConfig = function(config) {
!configPathExists && fs.mkdirSync(configPath) !configPathExists && fs.mkdirSync(configPath)
...@@ -49,14 +51,21 @@ var createMigrationsFolder = function(force) { ...@@ -49,14 +51,21 @@ var createMigrationsFolder = function(force) {
var readConfig = function() { var readConfig = function() {
try { try {
return JSON.parse(fs.readFileSync(configFile)) var config = JSON.parse(fs.readFileSync(configFile))
, env = process.env.NODE_ENV || 'development'
if (config[env]) {
config = config[env]
}
return config
} catch(e) { } catch(e) {
throw new Error('The config.json is not available or contains invalid JSON.') throw new Error('The config.json is not available or contains invalid JSON.')
} }
} }
program program
.version('1.3.0') .version(packageJson.version)
.option('-i, --init', 'Initializes the project. Creates a config/config.json') .option('-i, --init', 'Initializes the project. Creates a config/config.json')
.option('-m, --migrate', 'Runs undone migrations') .option('-m, --migrate', 'Runs undone migrations')
.option('-u, --undo', 'Redo the last migration.') .option('-u, --undo', 'Redo the last migration.')
...@@ -100,10 +109,24 @@ if(program.migrate) { ...@@ -100,10 +109,24 @@ if(program.migrate) {
} else if(program.init) { } else if(program.init) {
if(!configFileExists || !!program.force) { if(!configFileExists || !!program.force) {
writeConfig({ writeConfig({
development: {
username: "root",
password: null,
database: 'database_development',
host: '127.0.0.1'
},
test: {
username: "root", username: "root",
password: null, password: null,
database: 'database', database: 'database_test',
host: '127.0.0.1' host: '127.0.0.1'
},
production: {
username: "root",
password: null,
database: 'database_production',
host: '127.0.0.1'
}
}) })
console.log('Successfully created config.json') console.log('Successfully created config.json')
......
# v1.6.0 #
- [DEPENDENCIES] upgraded most dependencies. most important: mysql was upgraded to 2.0.0-alpha-3
- [REFACTORING] separated tests for dialects
- [BUG] fixed wrong version in sequelize binary
- [BUG] local options have higher priority than global options (thanks to guersam)
- [BUG] fixed where clause when passing an empty array (thanks to kbackowski)
- [FEATURE] added association prefetching for find and findAll
- [FEATURE] it's now possible to use callbacks of async functions inside migrations (thanks to mphilpot)
- [FEATURE] improved comfort of sequelize.query. just pass an sql string to it and wait for the result
- [FEATURE] Migrations now understand NODE_ENV (thanks to gavri)
- [FEATURE] Performance improvements (thanks to Mick-Hansen and janmeier from innofluence)
- [FEATURE] Model.find and Model.findAll can now take a String with an ID. (thanks to ghernandez345)
- [FEATURE] Compatibility for JSON-like strings in Postgres (thanks to aslakhellesoy)
- [FEATURE] honor maxConcurrentQueries option (thanks to dchester)
- [FEATURE] added support for stored procedures (inspired by wuyuntao)
- [FEATURE] added possibility to use pg lib's native api (thanks to denysonique)
- [FEATURE] added possibility to define the attributes of received associations (thanks to joshm)
# v1.5.0 # # v1.5.0 #
- [REFACTORING] use underscore functions for Utils.isHash (thanks to Mick-Hansen/innofluence) - [REFACTORING] use underscore functions for Utils.isHash (thanks to Mick-Hansen/innofluence)
- [REFACTORING] removed the 'failure' event and replaced it with 'error' - [REFACTORING] removed the 'failure' event and replaced it with 'error'
...@@ -5,6 +23,7 @@ ...@@ -5,6 +23,7 @@
- [BUG] obsolete reference attribute for many-to-many associations are removed correctly - [BUG] obsolete reference attribute for many-to-many associations are removed correctly
- [BUG] associations can be cleared via passing null to the set method - [BUG] associations can be cleared via passing null to the set method
- [BUG] "fixed" quota handling (thanks to dgf) - [BUG] "fixed" quota handling (thanks to dgf)
- [BUG] fixed destroy in postgresql (thanks to robraux)
- [FEATURE] added possibility to set protocol and to remove port from postgresql connection uri (thanks to danielschwartz) - [FEATURE] added possibility to set protocol and to remove port from postgresql connection uri (thanks to danielschwartz)
- [FEATURE] added possibility to not use a junction table for many-to-many associations on the same table (thanks to janmeier/innofluence) - [FEATURE] added possibility to not use a junction table for many-to-many associations on the same table (thanks to janmeier/innofluence)
- [FEATURE] results of the `import` method is now cached (thanks to janmeier/innofluence) - [FEATURE] results of the `import` method is now cached (thanks to janmeier/innofluence)
......
Subproject commit 3c0dc39aeada08622898b0c231a86114319a09dd
...@@ -3,13 +3,15 @@ var Utils = require("./../utils") ...@@ -3,13 +3,15 @@ var Utils = require("./../utils")
module.exports = (function() { module.exports = (function() {
var BelongsTo = function(srcDAO, targetDAO, options) { var BelongsTo = function(srcDAO, targetDAO, options) {
this.associationType = 'BelongsTo'
this.source = srcDAO this.source = srcDAO
this.target = targetDAO this.target = targetDAO
this.options = options this.options = options
this.isSelfAssociation = (this.source.tableName == this.target.tableName) this.isSelfAssociation = (this.source.tableName == this.target.tableName)
if(this.isSelfAssociation && !this.options.foreignKey && !!this.options.as) if(this.isSelfAssociation && !this.options.foreignKey && !!this.options.as) {
this.options.foreignKey = Utils._.underscoredIf(Utils.singularize(this.options.as) + "Id", this.source.options.underscored) this.options.foreignKey = Utils._.underscoredIf(Utils.singularize(this.options.as) + "Id", this.source.options.underscored)
}
this.associationAccessor = this.isSelfAssociation this.associationAccessor = this.isSelfAssociation
? Utils.combineTableNames(this.target.tableName, this.options.as || this.target.tableName) ? Utils.combineTableNames(this.target.tableName, this.options.as || this.target.tableName)
...@@ -22,7 +24,11 @@ module.exports = (function() { ...@@ -22,7 +24,11 @@ module.exports = (function() {
this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.target.tableName) + "Id", this.source.options.underscored) this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.target.tableName) + "Id", this.source.options.underscored)
newAttributes[this.identifier] = { type: DataTypes.INTEGER } newAttributes[this.identifier] = { type: DataTypes.INTEGER }
Utils._.extend(this.source.rawAttributes, newAttributes) Utils._.defaults(this.source.rawAttributes, newAttributes)
// Sync attributes to DAO proto each time a new assoc is added
this.target.DAO.prototype.attributes = Object.keys(this.target.DAO.prototype.rawAttributes);
return this return this
} }
...@@ -30,9 +36,18 @@ module.exports = (function() { ...@@ -30,9 +36,18 @@ module.exports = (function() {
var self = this var self = this
, accessor = Utils._.camelize('get_' + (this.options.as || Utils.singularize(this.target.tableName))) , accessor = Utils._.camelize('get_' + (this.options.as || Utils.singularize(this.target.tableName)))
obj[accessor] = function() { obj[accessor] = function(params) {
var id = obj[self.identifier] var id = this[self.identifier]
return self.target.find(id)
if (!Utils._.isUndefined(params)) {
if (!Utils._.isUndefined(params.attributes)) {
params = Utils._.extend({where: {id:id}}, params)
}
} else {
params = id
}
return self.target.find(params)
} }
return this return this
...@@ -43,10 +58,10 @@ module.exports = (function() { ...@@ -43,10 +58,10 @@ module.exports = (function() {
, accessor = Utils._.camelize('set_' + (this.options.as || Utils.singularize(this.target.tableName))) , accessor = Utils._.camelize('set_' + (this.options.as || Utils.singularize(this.target.tableName)))
obj[accessor] = function(associatedObject) { obj[accessor] = function(associatedObject) {
obj[self.identifier] = associatedObject ? associatedObject.id : null this[self.identifier] = associatedObject ? associatedObject.id : null
// passes the changed field to save, so only that field get updated. // passes the changed field to save, so only that field get updated.
return obj.save([ self.identifier ]) return this.save([ self.identifier ])
} }
return this return this
......
...@@ -26,7 +26,7 @@ module.exports = (function() { ...@@ -26,7 +26,7 @@ module.exports = (function() {
options.where[self.__factory.target.tableName+"."+index] = value; options.where[self.__factory.target.tableName+"."+index] = value;
}); });
options.where = options.where ? Utils.merge(options.where, where) : where Utils._.extend(options.where, where)
} else { } else {
options.where = where; options.where = where;
} }
......
...@@ -11,23 +11,22 @@ module.exports = (function() { ...@@ -11,23 +11,22 @@ module.exports = (function() {
where[this.__factory.identifier] = this.instance.id where[this.__factory.identifier] = this.instance.id
options.where = options.where ? Utils.merge(options.where, where) : where options.where = options.where ? Utils._.extend(options.where, where) : where
return this.__factory.target.findAll(options) return this.__factory.target.findAll(options)
} }
HasManySingleLinked.prototype.injectSetter = function(emitter, oldAssociations, newAssociations) { HasManySingleLinked.prototype.injectSetter = function(emitter, oldAssociations, newAssociations) {
var self = this var self = this
, options = this.__factory.options , options = this.__factory.options
, chainer = new Utils.QueryChainer()
// clear the old associations // clear the old associations
oldAssociations.forEach(function(associatedObject) { oldAssociations.forEach(function(associatedObject) {
associatedObject[self.__factory.identifier] = options.omitNull ? '' : null associatedObject[self.__factory.identifier] = null
associatedObject.save() chainer.add(associatedObject.save())
}) })
// set the new one // set the new associations
var chainer = new Utils.QueryChainer()
newAssociations.forEach(function(associatedObject) { newAssociations.forEach(function(associatedObject) {
associatedObject[self.__factory.identifier] = self.instance.id associatedObject[self.__factory.identifier] = self.instance.id
chainer.add(associatedObject.save()) chainer.add(associatedObject.save())
......
...@@ -6,6 +6,7 @@ var HasManySingleLinked = require("./has-many-single-linked") ...@@ -6,6 +6,7 @@ var HasManySingleLinked = require("./has-many-single-linked")
module.exports = (function() { module.exports = (function() {
var HasMany = function(srcDAO, targetDAO, options) { var HasMany = function(srcDAO, targetDAO, options) {
this.associationType = 'HasMany'
this.source = srcDAO this.source = srcDAO
this.target = targetDAO this.target = targetDAO
this.options = options this.options = options
...@@ -53,6 +54,7 @@ module.exports = (function() { ...@@ -53,6 +54,7 @@ module.exports = (function() {
combinedTableAttributes[this.foreignIdentifier] = {type:DataTypes.INTEGER, primaryKey: true} combinedTableAttributes[this.foreignIdentifier] = {type:DataTypes.INTEGER, primaryKey: true}
this.connectorDAO = this.source.daoFactoryManager.sequelize.define(this.combinedName, combinedTableAttributes, this.options) this.connectorDAO = this.source.daoFactoryManager.sequelize.define(this.combinedName, combinedTableAttributes, this.options)
if(!this.isSelfAssociation) { if(!this.isSelfAssociation) {
this.target.associations[this.associationAccessor].connectorDAO = this.connectorDAO this.target.associations[this.associationAccessor].connectorDAO = this.connectorDAO
} }
...@@ -63,9 +65,13 @@ module.exports = (function() { ...@@ -63,9 +65,13 @@ module.exports = (function() {
} else { } else {
var newAttributes = {} var newAttributes = {}
newAttributes[this.identifier] = { type: DataTypes.INTEGER } newAttributes[this.identifier] = { type: DataTypes.INTEGER }
Utils._.extend(this.target.rawAttributes, newAttributes) Utils._.defaults(this.target.rawAttributes, newAttributes)
} }
// Sync attributes to DAO proto each time a new assoc is added
this.target.DAO.prototype.attributes = Object.keys(this.target.DAO.prototype.rawAttributes);
this.source.DAO.prototype.attributes = Object.keys(this.source.DAO.prototype.rawAttributes);
return this return this
} }
......
...@@ -3,13 +3,15 @@ var Utils = require("./../utils") ...@@ -3,13 +3,15 @@ var Utils = require("./../utils")
module.exports = (function() { module.exports = (function() {
var HasOne = function(srcDAO, targetDAO, options) { var HasOne = function(srcDAO, targetDAO, options) {
this.associationType = 'HasOne'
this.source = srcDAO this.source = srcDAO
this.target = targetDAO this.target = targetDAO
this.options = options this.options = options
this.isSelfAssociation = (this.source.tableName == this.target.tableName) this.isSelfAssociation = (this.source.tableName == this.target.tableName)
if(this.isSelfAssociation && !this.options.foreignKey && !!this.options.as) if(this.isSelfAssociation && !this.options.foreignKey && !!this.options.as) {
this.options.foreignKey = Utils._.underscoredIf(Utils.singularize(this.options.as) + "Id", this.options.underscored) this.options.foreignKey = Utils._.underscoredIf(Utils.singularize(this.options.as) + "Id", this.options.underscored)
}
this.associationAccessor = this.isSelfAssociation this.associationAccessor = this.isSelfAssociation
? Utils.combineTableNames(this.target.tableName, this.options.as || this.target.tableName) ? Utils.combineTableNames(this.target.tableName, this.options.as || this.target.tableName)
...@@ -27,7 +29,10 @@ module.exports = (function() { ...@@ -27,7 +29,10 @@ module.exports = (function() {
this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.source.tableName) + "Id", this.options.underscored) this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.source.tableName) + "Id", this.options.underscored)
newAttributes[this.identifier] = { type: DataTypes.INTEGER } newAttributes[this.identifier] = { type: DataTypes.INTEGER }
Utils._.extend(this.target.rawAttributes, newAttributes) Utils._.defaults(this.target.rawAttributes, newAttributes)
// Sync attributes to DAO proto each time a new assoc is added
this.target.DAO.prototype.attributes = Object.keys(this.target.DAO.prototype.rawAttributes);
return this return this
} }
...@@ -35,12 +40,21 @@ module.exports = (function() { ...@@ -35,12 +40,21 @@ module.exports = (function() {
HasOne.prototype.injectGetter = function(obj) { HasOne.prototype.injectGetter = function(obj) {
var self = this var self = this
obj[this.accessors.get] = function() { obj[this.accessors.get] = function(params) {
var id = obj.id var id = this.id
, where = {} , where = {}
where[self.identifier] = id where[self.identifier] = id
return self.target.find({where: where})
if (!Utils._.isUndefined(params)) {
if (!Utils._.isUndefined(params.attributes)) {
params = Utils._.extend({where: where}, params)
}
} else {
params = {where: where}
}
return self.target.find(params)
} }
return this return this
...@@ -51,15 +65,16 @@ module.exports = (function() { ...@@ -51,15 +65,16 @@ module.exports = (function() {
, options = self.options || {} , options = self.options || {}
obj[this.accessors.set] = function(associatedObject) { obj[this.accessors.set] = function(associatedObject) {
var instance = this;
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
obj[self.accessors.get]().success(function(oldObj) { instance[self.accessors.get]().success(function(oldObj) {
if(oldObj) { if(oldObj) {
oldObj[self.identifier] = options.omitNull ? '' : null; oldObj[self.identifier] = null
oldObj.save() oldObj.save()
} }
if(associatedObject) { if(associatedObject) {
associatedObject[self.identifier] = obj.id associatedObject[self.identifier] = instance.id
associatedObject associatedObject
.save() .save()
.success(function() { emitter.emit('success', associatedObject) }) .success(function() { emitter.emit('success', associatedObject) })
......
...@@ -10,21 +10,49 @@ Mixin.hasOne = function(associatedDAO, options) { ...@@ -10,21 +10,49 @@ Mixin.hasOne = function(associatedDAO, options) {
// the id is in the foreign table // the id is in the foreign table
var association = new HasOne(this, associatedDAO, Utils._.extend((options||{}), this.options)) var association = new HasOne(this, associatedDAO, Utils._.extend((options||{}), this.options))
this.associations[association.associationAccessor] = association.injectAttributes() this.associations[association.associationAccessor] = association.injectAttributes()
association.injectGetter(this.DAO.prototype);
association.injectSetter(this.DAO.prototype);
return this return this
} }
Mixin.belongsTo = function(associatedDAO, options) { Mixin.belongsTo = function(associatedDAO, options) {
// the id is in this table // the id is in this table
var association = new BelongsTo(this, associatedDAO, Utils._.extend((options||{}), this.options)) var association = new BelongsTo(this, associatedDAO, Utils._.extend((options||{}), this.options))
this.associations[association.associationAccessor] = association.injectAttributes() this.associations[association.associationAccessor] = association.injectAttributes()
association.injectGetter(this.DAO.prototype);
association.injectSetter(this.DAO.prototype);
return this return this
} }
Mixin.hasMany = function(associatedDAO, options) { Mixin.hasMany = function(associatedDAO, options) {
// the id is in the foreign table or in a connecting table // the id is in the foreign table or in a connecting table
var association = new HasMany(this, associatedDAO, Utils._.extend((options||{}), this.options)) var association = new HasMany(this, associatedDAO, Utils._.extend((options||{}), this.options))
this.associations[association.associationAccessor] = association.injectAttributes() this.associations[association.associationAccessor] = association.injectAttributes()
association.injectGetter(this.DAO.prototype);
association.injectSetter(this.DAO.prototype);
return this return this
} }
Mixin.getAssociation = function(target, options) {
var result = null
for (var associationName in this.associations) {
var association = this.associations[associationName]
if (!result && (association.target === target)) {
result = association
}
}
return result
}
/* example for instance methods: /* example for instance methods:
Mixin.prototype.test = function() { Mixin.prototype.test = function() {
console.log('asd') console.log('asd')
......
...@@ -16,9 +16,12 @@ module.exports = (function() { ...@@ -16,9 +16,12 @@ module.exports = (function() {
}) })
} }
DAOFactoryManager.prototype.getDAO = function(daoName) { DAOFactoryManager.prototype.getDAO = function(daoName, options) {
options = options || {}
options.attribute = options.attribute || 'name'
var dao = this.daos.filter(function(dao) { var dao = this.daos.filter(function(dao) {
return dao.name == daoName return dao[options.attribute] === daoName
}) })
return !!dao ? dao[0] : null return !!dao ? dao[0] : null
......
var Utils = require("./utils") var Utils = require("./utils")
, DAO = require("./dao") , DAO = require("./dao")
, DataTypes = require("./data-types") , DataTypes = require("./data-types")
, Util = require('util')
module.exports = (function() { module.exports = (function() {
var DAOFactory = function(name, attributes, options) { var DAOFactory = function(name, attributes, options) {
...@@ -41,26 +42,65 @@ module.exports = (function() { ...@@ -41,26 +42,65 @@ module.exports = (function() {
get: function() { return this.QueryInterface.QueryGenerator } get: function() { return this.QueryInterface.QueryGenerator }
}) })
Object.defineProperty(DAOFactory.prototype, 'primaryKeyCount', { DAOFactory.prototype.init = function(daoFactoryManager) {
get: function() { return Utils._.keys(this.primaryKeys).length } var self = this;
})
this.daoFactoryManager = daoFactoryManager
Object.defineProperty(DAOFactory.prototype, 'hasPrimaryKeys', { this.primaryKeys = {};
get: function() { return this.primaryKeyCount > 0 } Utils._.each(this.attributes, function(dataTypeString, attributeName) {
if((attributeName != 'id') && (dataTypeString.indexOf('PRIMARY KEY') !== -1)) {
self.primaryKeys[attributeName] = dataTypeString
}
}) })
DAOFactory.prototype.init = function(daoFactoryManager) { this.primaryKeyCount = Utils._.keys(this.primaryKeys).length;
this.daoFactoryManager = daoFactoryManager this.options.hasPrimaryKeys = this.hasPrimaryKeys = this.primaryKeyCount > 0;
addDefaultAttributes.call(this) addDefaultAttributes.call(this)
addOptionalClassMethods.call(this) addOptionalClassMethods.call(this)
findAutoIncrementField.call(this) findAutoIncrementField.call(this)
// DAO prototype
this.DAO = function() {
DAO.apply(this, arguments);
};
Util.inherits(this.DAO, DAO);
this.DAO.prototype.rawAttributes = this.rawAttributes;
if (this.options.instanceMethods) {
Utils._.each(this.options.instanceMethods, function(fct, name) {
self.DAO.prototype[name] = fct
})
}
this.DAO.prototype.attributes = Object.keys(this.DAO.prototype.rawAttributes);
this.DAO.prototype.booleanValues = [];
this.DAO.prototype.defaultValues = {};
this.DAO.prototype.validators = {};
Utils._.each(this.rawAttributes, function (definition, name) {
if(((definition === DataTypes.BOOLEAN) || (definition.type === DataTypes.BOOLEAN))) {
self.DAO.prototype.booleanValues.push(name);
}
if(definition.hasOwnProperty('defaultValue')) {
self.DAO.prototype.defaultValues[name] = function() {
return Utils.toDefaultValue(definition.defaultValue);
}
}
if (definition.hasOwnProperty('validate')) {
self.DAO.prototype.validators[name] = definition.validate;
}
});
this.DAO.prototype.__factory = this;
this.DAO.prototype.hasDefaultValues = !Utils._.isEmpty(this.DAO.prototype.defaultValues);
return this return this
} }
DAOFactory.prototype.sync = function(options) { DAOFactory.prototype.sync = function(options) {
options = Utils.merge(options || {}, this.options) options = Utils._.extend({}, this.options, options || {})
var self = this var self = this
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
...@@ -90,47 +130,84 @@ module.exports = (function() { ...@@ -90,47 +130,84 @@ module.exports = (function() {
} }
DAOFactory.prototype.findAll = function(options) { DAOFactory.prototype.findAll = function(options) {
return this.QueryInterface.select(this, this.tableName, options) var hasJoin = false;
if ((typeof options === 'object') && (options.hasOwnProperty('include'))) {
var includes = options.include
hasJoin = true;
options.include = {}
includes.forEach(function(daoName) {
options.include[daoName] = this.daoFactoryManager.getDAO(daoName)
}.bind(this))
}
return this.QueryInterface.select(this, this.tableName, options, { type: 'SELECT', hasJoin: hasJoin })
} }
//right now, the caller (has-many-double-linked) is in charge of the where clause //right now, the caller (has-many-double-linked) is in charge of the where clause
DAOFactory.prototype.findAllJoin = function(joinTableName, options) { DAOFactory.prototype.findAllJoin = function(joinTableName, options) {
optcpy = Utils._.clone(options) var optcpy = Utils._.clone(options)
optcpy.attributes = optcpy.attributes || [Utils.addTicks(this.tableName)+".*"] optcpy.attributes = optcpy.attributes || [Utils.addTicks(this.tableName)+".*"]
return this.QueryInterface.select(this, [this.tableName, joinTableName], optcpy) return this.QueryInterface.select(this, [this.tableName, joinTableName], optcpy, { type: 'SELECT' })
} }
DAOFactory.prototype.find = function(options) { DAOFactory.prototype.find = function(options) {
if([null, undefined].indexOf(options) > -1) { var hasJoin = false;
// no options defined?
// return an emitter which emits null
if([null, undefined].indexOf(options) !== -1) {
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
setTimeout(function() { emitter.emit('success', null) }, 10) setTimeout(function() { emitter.emit('success', null) }, 10)
}).run() }).run()
} }
var primaryKeys = this.primaryKeys;
// options is not a hash but an id // options is not a hash but an id
if(typeof options == 'number') if(typeof options === 'number') {
options = { where: options } options = { where: options }
else if (Utils.argsArePrimaryKeys(arguments, this.primaryKeys)) { } else if (Utils._.size(primaryKeys) && Utils.argsArePrimaryKeys(arguments, primaryKeys)) {
var where = {} var where = {}
, self = this , self = this
, keys = Utils._.keys(primaryKeys)
Utils._.each(arguments, function(arg, i) { Utils._.each(arguments, function(arg, i) {
var key = Utils._.keys(self.primaryKeys)[i] var key = keys[i]
where[key] = arg where[key] = arg
}) })
options = { where: where } options = { where: where }
} else if ((typeof options === 'string') && (parseInt(options, 10).toString() === options)) {
var parsedId = parseInt(options, 10);
if(!Utils._.isFinite(parsedId)) {
throw new Error('Invalid argument to find(). Must be an id or an options object.')
}
options = { where: parsedId }
} else if ((typeof options === 'object') && (options.hasOwnProperty('include'))) {
var includes = options.include
hasJoin = true;
options.include = {}
includes.forEach(function(daoName) {
options.include[daoName] = this.daoFactoryManager.getDAO(daoName)
}.bind(this))
} }
options.limit = 1 options.limit = 1
return this.QueryInterface.select(this, this.tableName, options, {plain: true}) return this.QueryInterface.select(this, this.tableName, options, { plain: true, type: 'SELECT', hasJoin: hasJoin })
} }
DAOFactory.prototype.count = function(options) { DAOFactory.prototype.count = function(options) {
options = Utils._.extend({ attributes: [] }, options || {}) options = Utils._.extend({ attributes: [] }, options || {})
options.attributes.push(['count(*)', 'count']) options.attributes.push(['count(*)', 'count'])
options.parseInt = true
return this.QueryInterface.rawSelect(this.tableName, options, 'count') return this.QueryInterface.rawSelect(this.tableName, options, 'count')
} }
...@@ -138,52 +215,23 @@ module.exports = (function() { ...@@ -138,52 +215,23 @@ module.exports = (function() {
DAOFactory.prototype.max = function(field, options) { DAOFactory.prototype.max = function(field, options) {
options = Utils._.extend({ attributes: [] }, options || {}) options = Utils._.extend({ attributes: [] }, options || {})
options.attributes.push(['max(' + field + ')', 'max']) options.attributes.push(['max(' + field + ')', 'max'])
options.parseInt = true
return this.QueryInterface.rawSelect(this.tableName, options, 'max') return this.QueryInterface.rawSelect(this.tableName, options, 'max')
} }
DAOFactory.prototype.min = function(field, options) { DAOFactory.prototype.min = function(field, options) {
options = Utils._.extend({ attributes: [] }, options || {}) options = Utils._.extend({ attributes: [] }, options || {})
options.attributes.push(['min(' + field + ')', 'min']) options.attributes.push(['min(' + field + ')', 'min'])
options.parseInt = true
return this.QueryInterface.rawSelect(this.tableName, options, 'min') return this.QueryInterface.rawSelect(this.tableName, options, 'min')
} }
DAOFactory.prototype.build = function(values, options) { DAOFactory.prototype.build = function(values, options) {
var instance = new DAO(values, Utils._.extend(this.options, this.attributes, { hasPrimaryKeys: this.hasPrimaryKeys }))
, self = this
options = options || {} options = options || {}
instance.__factory = this
Utils._.each(this.attributes, function(definition, name) {
//transform integer 0,1 into boolean
if((definition.indexOf(DataTypes.BOOLEAN) !== -1) && (typeof instance[name] === "number")) {
instance[name] = (instance[name] !== 0)
}
//add default attributes
if(typeof instance[name] === 'undefined') {
var value = null
if(self.rawAttributes.hasOwnProperty(name) && self.rawAttributes[name].hasOwnProperty('defaultValue')) {
value = Utils.toDefaultValue(self.rawAttributes[name].defaultValue)
}
instance[name] = value var self = this
instance.addAttribute(name, value) , instance = new this.DAO(values, this.options)
}
// add validation
if (self.rawAttributes.hasOwnProperty(name) && self.rawAttributes[name].hasOwnProperty('validate')) {
instance.setValidators(name, self.rawAttributes[name].validate)
}
})
Utils._.each(this.options.instanceMethods || {}, function(fct, name) { instance[name] = fct })
Utils._.each(this.associations, function(association, associationName) {
association.injectGetter(instance)
association.injectSetter(instance)
})
instance.isNewRecord = options.hasOwnProperty('isNewRecord') ? options.isNewRecord : true instance.isNewRecord = options.hasOwnProperty('isNewRecord') ? options.isNewRecord : true
...@@ -222,25 +270,23 @@ module.exports = (function() { ...@@ -222,25 +270,23 @@ module.exports = (function() {
}).run() }).run()
} }
DAOFactory.prototype.__defineGetter__('primaryKeys', function() {
var result = {}
Utils._.each(this.attributes, function(dataTypeString, attributeName) {
if((attributeName != 'id') && (dataTypeString.indexOf('PRIMARY KEY') > -1))
result[attributeName] = dataTypeString
})
return result
})
// private // private
var query = function() { var query = function() {
var args = Utils._.map(arguments, function(arg, _) { return arg }) var args = Utils._.map(arguments, function(arg, _) { return arg })
, s = this.daoFactoryManager.sequelize , sequelize = this.daoFactoryManager.sequelize
// add this as the second argument // add this as the second argument
if(arguments.length == 1) args.push(this) if (arguments.length === 1) {
return s.query.apply(s, args) args.push(this)
}
// add {} as options
if (args.length === 2) {
args.push({})
}
return sequelize.query.apply(sequelize, args)
} }
var addOptionalClassMethods = function() { var addOptionalClassMethods = function() {
...@@ -275,17 +321,17 @@ module.exports = (function() { ...@@ -275,17 +321,17 @@ module.exports = (function() {
} }
var findAutoIncrementField = function() { var findAutoIncrementField = function() {
var self = this var fields = this.QueryGenerator.findAutoIncrementField(this)
, fields = this.QueryGenerator.findAutoIncrementField(this)
this.autoIncrementField = null this.autoIncrementField = null
fields.forEach(function(field) { fields.forEach(function(field) {
if(self.autoIncrementField) if (this.autoIncrementField) {
throw new Error('Invalid DAO definition. Only one autoincrement field allowed.') throw new Error('Invalid DAO definition. Only one autoincrement field allowed.')
else } else {
self.autoIncrementField = field this.autoIncrementField = field
}) }
}.bind(this))
} }
Utils._.extend(DAOFactory.prototype, require("./associations/mixin")) Utils._.extend(DAOFactory.prototype, require("./associations/mixin"))
......
var Utils = require("./utils") var Utils = require("./utils")
, Mixin = require("./associations/mixin") , Mixin = require("./associations/mixin")
, DataTypes = require("./data-types")
, Validator = require("validator") , Validator = require("validator")
, DataTypes = require("./data-types")
module.exports = (function() { module.exports = (function() {
var DAO = function(values, options) { var DAO = function(values, options) {
var self = this var self = this;
this.__options = options;
this.attributes = [] this.hasPrimaryKeys = options.hasPrimaryKeys;
this.validators = {} // holds validation settings for each attribute this.selectedValues = values;
this.__factory = null // will be set in DAO.build
this.__options = Utils._.extend({
underscored: false,
hasPrimaryKeys: false,
timestamps: true,
paranoid: false
}, options || {})
initAttributes.call(this, values) initAttributes.call(this, values)
if (this.hasDefaultValues) {
Utils._.each(this.defaultValues, function (value, name) {
if(typeof self[name] === 'undefined') {
self.addAttribute(name, value());
}
})
}
if (this.booleanValues.length) {
this.booleanValues.forEach(function (name) {
//transform integer 0,1 into boolean
self[name] = !!self[name];
});
}
} }
Utils._.extend(DAO.prototype, Mixin.prototype) Utils._.extend(DAO.prototype, Mixin.prototype)
...@@ -32,7 +40,7 @@ module.exports = (function() { ...@@ -32,7 +40,7 @@ module.exports = (function() {
Object.defineProperty(DAO.prototype, 'isDeleted', { Object.defineProperty(DAO.prototype, 'isDeleted', {
get: function() { get: function() {
var result = this.__options.timestamps && this.__options.paranoid var result = this.__options.timestamps && this.__options.paranoid
result = result && this[this.__options.underscored ? 'deleted_at' : 'deletedAt'] != null result = result && this[this.__options.underscored ? 'deleted_at' : 'deletedAt'] !== null
return result return result
} }
...@@ -89,29 +97,25 @@ module.exports = (function() { ...@@ -89,29 +97,25 @@ module.exports = (function() {
, updatedAtAttr = this.__options.underscored ? 'updated_at' : 'updatedAt' , updatedAtAttr = this.__options.underscored ? 'updated_at' : 'updatedAt'
, createdAtAttr = this.__options.underscored ? 'created_at' : 'createdAt' , createdAtAttr = this.__options.underscored ? 'created_at' : 'createdAt'
if(fields) { if (fields) {
if(self.__options.timestamps) { if (self.__options.timestamps) {
if(fields.indexOf(updatedAtAttr) === -1) { if (fields.indexOf(updatedAtAttr) === -1) {
fields.push(updatedAtAttr) fields.push(updatedAtAttr)
} }
if(fields.indexOf(createdAtAttr) === -1) { if (fields.indexOf(createdAtAttr) === -1) {
fields.push(createdAtAttr) fields.push(createdAtAttr)
} }
} }
fields.forEach(function(field) { fields.forEach(function(field) {
if(self.values[field] !== undefined) { if (self.values[field] !== undefined) {
values[field] = self.values[field] values[field] = self.values[field]
} }
}) })
} }
if(this.__options.timestamps && this.hasOwnProperty(updatedAtAttr)) { if(this.__options.timestamps && this.hasOwnProperty(updatedAtAttr)) {
var now = new Date() this[updatedAtAttr] = values[updatedAtAttr] = new Date()
this[updatedAtAttr] = now
values[updatedAtAttr] = now
} }
if(this.isNewRecord) { if(this.isNewRecord) {
...@@ -155,7 +159,7 @@ module.exports = (function() { ...@@ -155,7 +159,7 @@ module.exports = (function() {
// is it a validator module function? // is it a validator module function?
else { else {
// extra args // extra args
fn_args = details.hasOwnProperty("args") ? details.args : [] fn_args = details.hasOwnProperty("args") ? details.args : details
if (!Utils._.isArray(fn_args)) if (!Utils._.isArray(fn_args))
fn_args = [fn_args] fn_args = [fn_args]
// error msg // error msg
...@@ -249,7 +253,6 @@ module.exports = (function() { ...@@ -249,7 +253,6 @@ module.exports = (function() {
DAO.prototype.addAttribute = function(attribute, value) { DAO.prototype.addAttribute = function(attribute, value) {
this[attribute] = value this[attribute] = value
this.attributes.push(attribute)
} }
DAO.prototype.setValidators = function(attribute, validators) { DAO.prototype.setValidators = function(attribute, validators) {
...@@ -263,14 +266,16 @@ module.exports = (function() { ...@@ -263,14 +266,16 @@ module.exports = (function() {
// private // private
var initAttributes = function(values) { var initAttributes = function(values) {
var self = this
// add all passed values to the dao and store the attribute names in this.attributes // add all passed values to the dao and store the attribute names in this.attributes
Utils._.map(values, function(value, key) { self.addAttribute(key, value) }) for (var key in values) {
if (values.hasOwnProperty(key)) {
this.addAttribute(key, values[key])
}
}
// set id to null if not passed as value // set id to null if not passed as value
// a newly created dao has no id // a newly created dao has no id
var defaults = this.__options.hasPrimaryKeys ? {} : { id: null } var defaults = this.hasPrimaryKeys ? {} : { id: null }
if(this.__options.timestamps) { if(this.__options.timestamps) {
defaults[this.__options.underscored ? 'created_at' : 'createdAt'] = new Date() defaults[this.__options.underscored ? 'created_at' : 'createdAt'] = new Date()
...@@ -281,15 +286,16 @@ module.exports = (function() { ...@@ -281,15 +286,16 @@ module.exports = (function() {
} }
} }
Utils._.map(defaults, function(value, attr) { if (Utils._.size(defaults)) {
if(!self.hasOwnProperty(attr)) { for (var attr in defaults) {
self.addAttribute(attr, Utils.toDefaultValue(value)) var value = defaults[attr]
if(!this.hasOwnProperty(attr)) {
this.addAttribute(attr, Utils.toDefaultValue(value))
}
}
} }
})
} }
/* Add the instance methods to DAO */
Utils._.extend(DAO.prototype, Mixin.prototype)
return DAO return DAO
})() })()
var Pooling = require('generic-pool') var mysql = require("mysql")
, Pooling = require('generic-pool')
, Query = require("./query") , Query = require("./query")
, 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 }) }
...@@ -13,7 +14,11 @@ module.exports = (function() { ...@@ -13,7 +14,11 @@ module.exports = (function() {
this.activeQueue = [] this.activeQueue = []
this.maxConcurrentQueries = (this.config.maxConcurrentQueries || 50) this.maxConcurrentQueries = (this.config.maxConcurrentQueries || 50)
this.poolCfg = this.config.pool this.poolCfg = this.config.pool
this.pendingQueries = 0;
this.useQueue = config.queue !== undefined ? config.queue : true;
var self = this var self = this
if (this.poolCfg) { if (this.poolCfg) {
//the user has requested pooling, so create our connection pool //the user has requested pooling, so create our connection pool
if (!this.poolCfg.maxConnections) { if (!this.poolCfg.maxConnections) {
...@@ -31,6 +36,7 @@ module.exports = (function() { ...@@ -31,6 +36,7 @@ module.exports = (function() {
idleTimeoutMillis: self.poolCfg.maxIdleTime idleTimeoutMillis: self.poolCfg.maxIdleTime
}) })
} }
process.on('exit', function () { process.on('exit', function () {
//be nice & close our connections on exit //be nice & close our connections on exit
if (self.pool) { if (self.pool) {
...@@ -42,49 +48,80 @@ module.exports = (function() { ...@@ -42,49 +48,80 @@ module.exports = (function() {
return return
}) })
} }
Utils._.extend(ConnectorManager.prototype, require("../connector-manager").prototype)
var isConnecting = false Utils._.extend(ConnectorManager.prototype, require("../connector-manager").prototype);
ConnectorManager.prototype.query = function(sql, callee, options) { var isConnecting = false;
if(!this.isConnected && !this.pool) this.connect() ConnectorManager.prototype.query = function(sql, callee, options) {
if (!this.isConnected && !this.pool) this.connect();
if (this.useQueue) {
var queueItem = { var queueItem = {
query: new Query(this.client, callee, options || {}), query: new Query(this.client, this.sequelize, callee, options || {}),
sql: sql sql: sql
};
enqueue.call(this, queueItem);
return queueItem.query;
} }
enqueue.call(this, queueItem) var self = this, query = new Query(this.client, this.sequelize, callee, options || {});
this.pendingQueries++;
return queueItem.query query.done(function() {
self.pendingQueries--;
if (self.pool) self.pool.release(query.client);
else {
if (self.pendingQueries === 0) {
setTimeout(function() {
self.pendingQueries === 0 && self.disconnect.call(self);
}, 100);
} }
}
});
if (!this.pool) query.run(sql);
else {
this.pool.acquire(function(err, client) {
if (err) return query.emit('error', err);
query.client = client;
query.run(sql);
return;
});
}
return query;
};
ConnectorManager.prototype.connect = function() { ConnectorManager.prototype.connect = function() {
var self = this var self = this;
// 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 || this.pool) { if (this.isConnecting || this.pool) {
return return;
} }
connect.call(self, function(err, client) { connect.call(self, function(err, client) {
self.client = client self.client = client;
return return;
}) });
return return;
} };
ConnectorManager.prototype.disconnect = function() { ConnectorManager.prototype.disconnect = function() {
if (this.client) if (this.client) disconnect.call(this, this.client);
disconnect.call(this, this.client) return;
return };
}
// private // private
var disconnect = function(client) { var disconnect = function(client) {
var self = this var self = this;
if (!this.useQueue) this.client = null;
client.end(function() { client.end(function() {
if (!self.useQueue) return client.destroy();
var intervalObj = null var intervalObj = null
var cleanup = function () { var cleanup = function () {
var retryCt = 0 var retryCt = 0
...@@ -106,18 +143,17 @@ module.exports = (function() { ...@@ -106,18 +143,17 @@ module.exports = (function() {
} }
var connect = function(done) { var connect = function(done) {
var self = this var connection = mysql.createConnection({
var client = require("mysql").createClient({ host: this.config.host,
user: self.config.username, port: this.config.port,
password: self.config.password, user: this.config.username,
host: self.config.host, password: this.config.password,
port: self.config.port, database: this.config.database
database: self.config.database
}) })
client.setMaxListeners(self.maxConcurrentQueries) // client.setMaxListeners(self.maxConcurrentQueries)
self.isConnecting = false this.isConnecting = false
done(null, client)
return done(null, connection)
} }
var enqueue = function(queueItem) { var enqueue = function(queueItem) {
...@@ -157,10 +193,9 @@ module.exports = (function() { ...@@ -157,10 +193,9 @@ module.exports = (function() {
var transferQueuedItems = function(count) { var transferQueuedItems = function(count) {
for(var i = 0; i < count; i++) { for(var i = 0; i < count; i++) {
var queueItem = this.queue[0] var queueItem = this.queue.shift();
if(queueItem) { if(queueItem) {
enqueue.call(this, queueItem) enqueue.call(this, queueItem)
this.queue = without(this.queue, queueItem)
} }
} }
} }
...@@ -208,5 +243,3 @@ module.exports = (function() { ...@@ -208,5 +243,3 @@ module.exports = (function() {
return ConnectorManager return ConnectorManager
})() })()
\
var Utils = require("../../utils") var Utils = require("../../utils")
, AbstractQuery = require('../abstract/query')
module.exports = (function() { module.exports = (function() {
var Query = function(client, callee, options) { var Query = function(client, sequelize, callee, options) {
var self = this
this.client = client this.client = client
this.callee = callee this.callee = callee
this.sequelize = sequelize
this.options = Utils._.extend({ this.options = Utils._.extend({
logging: console.log, logging: console.log,
plain: false, plain: false,
raw: false raw: false
}, options || {}) }, options || {})
if(this.options.logging === true) { this.checkLoggingOption()
console.log('DEPRECATION WARNING: The logging-option should be either a function or false. Default: console.log')
this.options.logging = console.log
}
if(this.options.logging == console.log) {
// using just console.log will break in node < 0.6
this.options.logging = function(s) { console.log(s) }
} }
this.bindClientFunction = function(err) { onFailure.call(self, err) } Utils.inherit(Query, AbstractQuery)
}
Utils._.extend(Query.prototype, require("../query").prototype)
Query.prototype.run = function(sql) { Query.prototype.run = function(sql) {
var self = this
this.sql = sql this.sql = sql
bindClient.call(this) if(this.options.logging !== false) {
if(this.options.logging !== false)
this.options.logging('Executing: ' + this.sql) this.options.logging('Executing: ' + this.sql)
this.client.query(this.sql, function(err, results, fields) {
self.emit('sql', self.sql)
err ? onFailure.call(self, err) : onSuccess.call(self, results, fields)
}).setMaxListeners(100)
return this
}
//private
var bindClient = function() {
this.client.on('error', this.bindClientFunction)
} }
var unbindClient = function() { this.client.query(this.sql, function(err, results, fields) {
this.client.removeListener('error', this.bindClientFunction) this.emit('sql', this.sql)
}
var onSuccess = function(results, fields) {
var result = this.callee
, self = this
// add the inserted row id to the instance
if (this.callee && (this.sql.indexOf('INSERT INTO') == 0) && (results.hasOwnProperty('insertId')))
this.callee[this.callee.__factory.autoIncrementField] = results.insertId
if (this.sql.indexOf('SELECT') == 0) { if (err) {
// transform results into real model instances this.emit('error', err, this.callee)
// return the first real model instance if options.plain is set (e.g. Model.find)
if(this.options.raw) {
result = results
} else { } else {
result = results.map(function(result) { this.emit('success', this.formatResults(results))
return self.callee.build(result, { isNewRecord: false })
})
}
if(this.options.plain)
result = (result.length == 0) ? null : result[0]
} else if(this.sql.indexOf('SHOW TABLES') == 0) {
result = Utils._.flatten(results.map(function(resultSet) {
return Utils._.values(resultSet)
}))
} else if((this.sql.indexOf('SHOW') == 0) || (this.sql.indexOf('DESCRIBE') == 0)) {
result = results
} }
}.bind(this)).setMaxListeners(100)
unbindClient.call(this) return this
this.emit('success', result)
}
var onFailure = function(err) {
unbindClient.call(this)
this.emit('error', err, this.callee)
} }
return Query return Query
......
var Query = require("./query") var Query = require("./query")
, Utils = require("../../utils") , Utils = require("../../utils")
, pg = require("pg")
, without = function(arr, elem) { return arr.filter(function(e) { return e != elem }) }
module.exports = (function() { module.exports = (function() {
var ConnectorManager = function(sequelize, config) { var ConnectorManager = function(sequelize, config) {
this.sequelize = sequelize this.sequelize = sequelize
this.client = null this.client = null
this.config = config || {} this.config = config || {}
this.pooling = (this.config.poolCfg != null && this.config.poolCfg.maxConnections > 0) this.pooling = (!!this.config.poolCfg && (this.config.poolCfg.maxConnections > 0))
this.pg = this.config.native ? require('pg').native : require('pg')
// set pooling parameters if specified // set pooling parameters if specified
if (this.pooling) { if (this.pooling) {
pg.defaults.poolSize = this.config.poolCfg.maxConnections this.pg.defaults.poolSize = this.config.poolCfg.maxConnections
pg.defaults.poolIdleTimeout = this.config.poolCfg.maxIdleTime this.pg.defaults.poolIdleTimeout = this.config.poolCfg.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)
...@@ -26,7 +27,7 @@ module.exports = (function() { ...@@ -26,7 +27,7 @@ module.exports = (function() {
ConnectorManager.prototype.query = function(sql, callee, options) { ConnectorManager.prototype.query = function(sql, callee, options) {
var self = this var self = this
if (this.client == null) this.connect() if (this.client == null) this.connect()
var query = new Query(this.client, callee, options || {}) var query = new Query(this.client, this.sequelize, callee, options || {})
self.pendingQueries += 1 self.pendingQueries += 1
return query.run(sql) return query.run(sql)
.success(function() { self.endQuery.call(self) }) .success(function() { self.endQuery.call(self) })
...@@ -55,7 +56,10 @@ module.exports = (function() { ...@@ -55,7 +56,10 @@ module.exports = (function() {
var connectCallback = function(err, client) { var connectCallback = function(err, client) {
self.isConnecting = false self.isConnecting = false
if (!err && client) {
if (!!err) {
throw err
} 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
...@@ -68,11 +72,10 @@ module.exports = (function() { ...@@ -68,11 +72,10 @@ module.exports = (function() {
if (this.pooling) { if (this.pooling) {
// acquire client from pool // acquire client from pool
pg.connect(uri, connectCallback) this.pg.connect(uri, connectCallback)
} else { } else {
//create one-off client //create one-off client
this.client = new pg.Client(uri) this.client = new this.pg.Client(uri)
this.client.connect(connectCallback) this.client.connect(connectCallback)
} }
} }
......
var Utils = require("../../utils"); var Utils = require("../../utils")
, AbstractQuery = require('../abstract/query')
module.exports = (function() { module.exports = (function() {
var Query = function(client, callee, options) { var Query = function(client, sequelize, callee, options) {
var self = this
this.client = client this.client = client
this.sequelize = sequelize
this.callee = callee this.callee = callee
this.options = Utils._.extend({ this.options = Utils._.extend({
logging: console.log, logging: console.log,
...@@ -12,83 +12,79 @@ module.exports = (function() { ...@@ -12,83 +12,79 @@ module.exports = (function() {
raw: false raw: false
}, options || {}) }, options || {})
if(this.options.logging === true) { this.checkLoggingOption()
console.log('DEPRECATION WARNING: The logging-option should be either a function or false. Default: console.log')
this.options.logging = console.log
}
if(this.options.logging == console.log) {
// using just console.log will break in node < 0.6
this.options.logging = function(s) { console.log(s) }
}
} }
Utils._.extend(Query.prototype, require("../query").prototype) Utils.inherit(Query, AbstractQuery)
Query.prototype.run = function(sql) { Query.prototype.run = function(sql) {
var self = this
this.sql = sql this.sql = sql
if(this.options.logging !== false) { if(this.options.logging !== false) {
this.options.logging('Executing: ' + this.sql) this.options.logging('Executing: ' + this.sql)
} }
var results = []; var receivedError = false
var receivedError = false; , query = this.client.query(sql)
, rows = []
var query = this.client.query(sql)
query.on('row', function(row) { query.on('row', function(row) {
if (self.callee && (self.sql.indexOf('INSERT INTO') == 0 || self.sql.indexOf('UPDATE') == 0)) { rows.push(row)
Utils._.forEach(row, function(value, key) {
self.callee[key] = value
}) })
results.push(self.callee)
query.on('error', function(err) {
receivedError = true
this.emit('error', err, this.callee)
}.bind(this))
query.on('end', function() {
this.emit('sql', this.sql)
if (receivedError) {
return
} }
if (self.sql.indexOf('SELECT table_name FROM information_schema.tables') == 0) { onSuccess.call(this, rows)
results.push(Utils._.values(row)) }.bind(this))
} else if (self.sql.indexOf('SELECT relname FROM pg_class WHERE oid IN') == 0) {
results.push(Utils._.values(row)) return this
} else if (self.sql.indexOf('SELECT') == 0) {
// transform results into real model instances
// return the first real model instance if options.plain is set (e.g. Model.find)
if (self.options.raw) {
results.push(row);
} else {
results.push(self.callee.build(row, { isNewRecord: false }))
} }
} else if((self.sql.indexOf('SHOW') == 0) || (self.sql.indexOf('DESCRIBE') == 0)) {
results.push(row) Query.prototype.getInsertIdField = function() {
return 'id'
} }
});
query.on('end', function() { var onSuccess = function(rows) {
self.emit('sql', self.sql) var results = []
if (receivedError) return; , isTableNameQuery = (this.sql.indexOf('SELECT table_name FROM information_schema.tables') === 0)
, isRelNameQuery = (this.sql.indexOf('SELECT relname FROM pg_class WHERE oid IN') === 0)
if (self.sql.indexOf('SELECT') == 0) { if (isTableNameQuery || isRelNameQuery) {
if (self.options.plain) { return this.emit('success', rows.map(function(row) { return Utils._.values(row) }))
self.emit('success', (results.length == 0) ? null : results[0]) }
} else {
self.emit('success', results) if (this.send('isSelectQuery')) {
this.emit('success', this.send('handleSelectQuery', rows))
} else if (this.send('isShowOrDescribeQuery')) {
this.emit('success', results)
} else if (this.send('isInsertQuery')) {
for (var key in rows[0]) {
if (rows[0].hasOwnProperty(key)) {
this.callee[key] = rows[0][key]
} }
} else if((self.sql.indexOf('SHOW') == 0) || (self.sql.indexOf('DESCRIBE') == 0)) {
self.emit('success', results)
} else if (self.sql.indexOf('INSERT INTO') == 0) {
self.emit('success', results[0])
} else if (self.sql.indexOf('UPDATE') == 0) {
self.emit('success', self.callee)
} else {
self.emit('success', results)
} }
});
query.on('error', function(err) { this.emit('success', this.callee)
receivedError = true } else if (this.send('isUpdateQuery')) {
self.emit('error', err, self.callee) for (var key in rows[0]) {
}); if (rows[0].hasOwnProperty(key)) {
this.callee[key] = rows[0][key]
}
}
return this this.emit('success', this.callee)
} else {
this.emit('success', results)
}
} }
return Query return Query
......
var Utils = require("../utils")
module.exports = (function() {
var Query = function(database, callee, options) {
throw new Error('Constructor was not overwritten!')
}
Utils._.extend(Query.prototype, require("../emitters/custom-event-emitter").prototype)
Query.prototype.run = function(sql) {
throw new Error("The run method wasn't overwritten!")
}
Query.prototype.success = Query.prototype.ok = function(fct) {
this.on('success', fct)
return this
}
Query.prototype.failure = Query.prototype.fail = Query.prototype.error = function(fct) {
this.on('error', fct)
return this
}
return Query
})()
...@@ -10,7 +10,7 @@ module.exports = (function() { ...@@ -10,7 +10,7 @@ module.exports = (function() {
Utils._.extend(ConnectorManager.prototype, require("../connector-manager").prototype) Utils._.extend(ConnectorManager.prototype, require("../connector-manager").prototype)
ConnectorManager.prototype.query = function(sql, callee, options) { ConnectorManager.prototype.query = function(sql, callee, options) {
return new Query(this.database, callee, options).run(sql) return new Query(this.database, this.sequelize, callee, options).run(sql)
} }
return ConnectorManager return ConnectorManager
......
var Utils = require("../../utils") var Utils = require("../../utils")
, util = require("util") var MySqlQueryGenerator = Utils._.extend(
Utils._.clone(require("../query-generator")),
Utils._.clone(require("../mysql/query-generator"))
)
var hashToWhereConditions = MySqlQueryGenerator.hashToWhereConditions
var escape = function(str) { var escape = function(str) {
if (typeof str === 'string') { if (typeof str === 'string') {
...@@ -22,25 +27,41 @@ module.exports = (function() { ...@@ -22,25 +27,41 @@ module.exports = (function() {
var query = "CREATE TABLE IF NOT EXISTS <%= table %> (<%= attributes%>)" var query = "CREATE TABLE IF NOT EXISTS <%= table %> (<%= attributes%>)"
, primaryKeys = [] , primaryKeys = []
, attrStr = Utils._.map(attributes, function(dataType, attr) { , needsMultiplePrimaryKeys = (Utils._.values(attributes).filter(function(definition) {
if (Utils._.includes(dataType, 'PRIMARY KEY')) { return Utils._.includes(definition, 'PRIMARY KEY')
}).length > 1)
, attrStr = []
for (var attr in attributes) {
if (attributes.hasOwnProperty(attr)) {
var dataType = attributes[attr]
if (Utils._.includes(dataType, 'PRIMARY KEY') && needsMultiplePrimaryKeys) {
primaryKeys.push(attr) primaryKeys.push(attr)
return Utils.addTicks(attr) + " " + dataType attrStr.push(Utils.addTicks(attr) + " " + dataType.replace(/PRIMARY KEY/, 'NOT NULL'))
} else { } else {
return Utils.addTicks(attr) + " " + dataType attrStr.push(Utils.addTicks(attr) + " " + dataType)
}
} }
}).join(", ") }
, values = {
var values = {
table: Utils.addTicks(tableName), table: Utils.addTicks(tableName),
attributes: attrStr, attributes: attrStr.join(", "),
charset: (options.charset ? "DEFAULT CHARSET=" + options.charset : "") charset: (options.charset ? "DEFAULT CHARSET=" + options.charset : "")
} }
, pkString = primaryKeys.map(function(pk) { return Utils.addTicks(pk) }).join(", ")
if (pkString.length > 0) {
values.attributes += ", PRIMARY KEY (" + pkString + ")"
}
return Utils._.template(query)(values).trim() + ";" return Utils._.template(query)(values).trim() + ";"
}, },
showTablesQuery: function() { showTablesQuery: function() {
return "SELECT name FROM sqlite_master WHERE type='table';" return "SELECT name FROM sqlite_master WHERE type='table' and name!='sqlite_sequence';"
}, },
insertQuery: function(tableName, attrValueHash) { insertQuery: function(tableName, attrValueHash) {
...@@ -63,12 +84,16 @@ module.exports = (function() { ...@@ -63,12 +84,16 @@ module.exports = (function() {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull) attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull)
var query = "UPDATE <%= table %> SET <%= values %> WHERE <%= where %>" var query = "UPDATE <%= table %> SET <%= values %> WHERE <%= where %>"
, values = []
for (var key in attrValueHash) {
var value = attrValueHash[key]
values.push(Utils.addTicks(key) + "=" + escape((value instanceof Date) ? Utils.toSqlDate(value) : value))
}
var replacements = { var replacements = {
table: Utils.addTicks(tableName), table: Utils.addTicks(tableName),
values: Utils._.map(attrValueHash, function(value, key){ values: values.join(","),
return Utils.addTicks(key) + "=" + escape((value instanceof Date) ? Utils.toSqlDate(value) : value)
}).join(","),
where: MySqlQueryGenerator.getWhereConditions(where) where: MySqlQueryGenerator.getWhereConditions(where)
} }
...@@ -91,45 +116,75 @@ module.exports = (function() { ...@@ -91,45 +116,75 @@ module.exports = (function() {
attributesToSQL: function(attributes) { attributesToSQL: function(attributes) {
var result = {} var result = {}
Utils._.map(attributes, function(dataType, name) { for (var name in attributes) {
var dataType = attributes[name]
if(Utils.isHash(dataType)) { if(Utils.isHash(dataType)) {
var template = "<%= type %>" var template = "<%= type %>"
, replacements = { type: dataType.type } , replacements = { type: dataType.type }
if(dataType.hasOwnProperty('allowNull') && !dataType.allowNull && !dataType.primaryKey) if (dataType.hasOwnProperty('allowNull') && !dataType.allowNull && !dataType.primaryKey) {
template += " NOT NULL" template += " NOT NULL"
}
if(dataType.defaultValue != undefined) { if(dataType.defaultValue != undefined) {
template += " DEFAULT <%= defaultValue %>" template += " DEFAULT <%= defaultValue %>"
replacements.defaultValue = Utils.escape(dataType.defaultValue) replacements.defaultValue = Utils.escape(dataType.defaultValue)
} }
if(dataType.unique) template += " UNIQUE" if (dataType.unique) {
if(dataType.primaryKey) template += " PRIMARY KEY" template += " UNIQUE"
}
if (dataType.primaryKey) {
template += " PRIMARY KEY"
if (dataType.autoIncrement) {
template += ' AUTOINCREMENT'
}
}
result[name] = Utils._.template(template)(replacements) result[name] = Utils._.template(template)(replacements)
} else { } else {
result[name] = dataType result[name] = dataType
} }
}) }
return result return result
}, },
findAutoIncrementField: function(factory) { findAutoIncrementField: function(factory) {
var fields = Utils._.map(factory.attributes, function(definition, name) { var fields = []
var isAutoIncrementField = (definition && (definition.indexOf('INTEGER PRIMARY KEY') == 0))
return isAutoIncrementField ? name : null
})
return Utils._.compact(fields) for (var name in factory.attributes) {
if (factory.attributes.hasOwnProperty(name)) {
var definition = factory.attributes[name]
if (definition && (definition.indexOf('INTEGER PRIMARY KEY AUTOINCREMENT') === 0)) {
fields.push(name)
}
} }
} }
var MySqlQueryGenerator = Utils._.extend( return fields
Utils._.clone(require("../query-generator")), },
Utils._.clone(require("../mysql/query-generator"))
) hashToWhereConditions: function(hash) {
for (var key in hash) {
if (hash.hasOwnProperty(key)) {
var value = hash[key]
if (typeof value === 'boolean') {
value = !!value ? 1 : 0
}
hash[key] = value
}
}
return hashToWhereConditions(hash)
}
}
return Utils._.extend(MySqlQueryGenerator, QueryGenerator) return Utils._.extend(MySqlQueryGenerator, QueryGenerator)
})() })()
var Utils = require("../../utils") var Utils = require("../../utils")
, AbstractQuery = require('../abstract/query')
module.exports = (function() { module.exports = (function() {
var Query = function(database, callee, options) { var Query = function(database, sequelize, callee, options) {
this.database = database this.database = database
this.sequelize = sequelize
this.callee = callee this.callee = callee
this.options = Utils._.extend({ this.options = Utils._.extend({
logging: console.log, logging: console.log,
...@@ -10,17 +12,13 @@ module.exports = (function() { ...@@ -10,17 +12,13 @@ module.exports = (function() {
raw: false raw: false
}, options || {}) }, options || {})
if(this.options.logging === true) { this.checkLoggingOption()
console.log('DEPRECATION WARNING: The logging-option should be either a function or false. Default: console.log')
this.options.logging = console.log
} }
Utils.inherit(Query, AbstractQuery)
if(this.options.logging == console.log) { Query.prototype.getInsertIdField = function() {
// using just console.log will break in node < 0.6 return 'lastID'
this.options.logging = function(s) { console.log(s) }
} }
}
Utils._.extend(Query.prototype, require("../query").prototype)
Query.prototype.run = function(sql) { Query.prototype.run = function(sql) {
var self = this var self = this
...@@ -34,19 +32,17 @@ module.exports = (function() { ...@@ -34,19 +32,17 @@ module.exports = (function() {
var columnTypes = {}; var columnTypes = {};
this.database.serialize(function() { this.database.serialize(function() {
var executeSql = function() { var executeSql = function() {
self.database[databaseMethod](self.sql, function(err, results) { self.database[getDatabaseMethod.call(self)](self.sql, function(err, results) {
//allow clients to listen to sql to do their own logging or whatnot // allow clients to listen to sql to do their own logging or whatnot
self.emit('sql', self.sql) self.emit('sql', self.sql)
this.columnTypes = columnTypes; this.columnTypes = columnTypes;
err ? onFailure.call(self, err) : onSuccess.call(self, results, this) err ? onFailure.call(self, err) : onSuccess.call(self, results, this)
}) })
}; };
var isInsertCommand = (self.sql.toLowerCase().indexOf('insert') == 0) if ((getDatabaseMethod.call(self) === 'all') && /select\s.*?\sfrom\s+([^ ;]+)/i.test(self.sql)) {
, isUpdateCommand = (self.sql.toLowerCase().indexOf('update') == 0)
, databaseMethod = (isInsertCommand || isUpdateCommand) ? 'run' : 'all'
if (databaseMethod === 'all' && /select\s.*?\sfrom\s+([^ ;]+)/i.test(self.sql)) {
var tableName = RegExp.$1; var tableName = RegExp.$1;
if (tableName !== 'sqlite_master') { if (tableName !== 'sqlite_master') {
// get the column types // get the column types
self.database.all("PRAGMA table_info(" + tableName + ")", function(err, results) { self.database.all("PRAGMA table_info(" + tableName + ")", function(err, results) {
...@@ -70,39 +66,43 @@ module.exports = (function() { ...@@ -70,39 +66,43 @@ module.exports = (function() {
//private //private
var getDatabaseMethod = function() {
if (this.send('isInsertQuery') || this.send('isUpdateQuery')) {
return 'run'
} else {
return 'all'
}
}
var onSuccess = function(results, metaData) { var onSuccess = function(results, metaData) {
var result = this.callee var result = this.callee
, self = this , self = this
// add the inserted row id to the instance // add the inserted row id to the instance
if (this.callee && (this.sql.indexOf('INSERT INTO') == 0) && metaData.hasOwnProperty('lastID')) { if (this.send('isInsertQuery', results, metaData)) {
var autoIncrementField = this.callee.__factory.autoIncrementField this.send('handleInsertQuery', results, metaData)
this.callee[autoIncrementField] = metaData.lastID
} }
if (this.sql.indexOf('sqlite_master') != -1) { if (this.sql.indexOf('sqlite_master') != -1) {
result = results.map(function(resultSet){ return resultSet.name }) result = results.map(function(resultSet) { return resultSet.name })
} else if (this.sql.indexOf('SELECT') == 0) { } else if (this.send('isSelectQuery')) {
// transform results into real model instances // we need to convert the timestamps into actual date objects
// return the first real model instance if options.plain is set (e.g. Model.find)
if(this.options.raw) { if(!this.options.raw) {
result = results results = results.map(function(result) {
} else {
result = results.map(function(result) {
for (var name in result) { for (var name in result) {
if (metaData.columnTypes[name] === 'DATETIME') { if (result.hasOwnProperty(name) && (metaData.columnTypes[name] === 'DATETIME')) {
result[name] = new Date(result[name]); result[name] = new Date(result[name]);
} }
} }
return self.callee.build(result, { isNewRecord: false }) return result
}) })
} }
if(this.options.plain) result = this.send('handleSelectQuery', results)
result = (result.length == 0) ? null : result[0] } else if (this.send('isShowOrDescribeQuery')) {
} else if((this.sql.indexOf('SHOW') == 0) || (this.sql.indexOf('DESCRIBE') == 0))
result = results result = results
}
this.emit('success', result) this.emit('success', result)
} }
......
...@@ -121,7 +121,7 @@ module.exports = (function() { ...@@ -121,7 +121,7 @@ module.exports = (function() {
(self.undoneMethods == 0) && callback && callback() (self.undoneMethods == 0) && callback && callback()
}) })
self.queryInterface[_method].apply(self.queryInterface, args) return self.queryInterface[_method].apply(self.queryInterface, args)
} }
})(method) })(method)
} }
......
...@@ -148,7 +148,7 @@ module.exports = (function() { ...@@ -148,7 +148,7 @@ module.exports = (function() {
self.QueryGenerator.attributesToSQL(options) self.QueryGenerator.attributesToSQL(options)
) )
self.sequelize.query(sql).success(function() { self.sequelize.query(sql, null, {}).success(function() {
self.emit('renameColumn', null) self.emit('renameColumn', null)
emitter.emit('success', null) emitter.emit('success', null)
}).error(function(err) { }).error(function(err) {
...@@ -202,24 +202,30 @@ module.exports = (function() { ...@@ -202,24 +202,30 @@ module.exports = (function() {
QueryInterface.prototype.rawSelect = function(tableName, options, attributeSelector) { QueryInterface.prototype.rawSelect = function(tableName, options, attributeSelector) {
var self = this var self = this
if(attributeSelector == undefined) if(attributeSelector == undefined) {
throw new Error('Please pass an attribute selector!') throw new Error('Please pass an attribute selector!')
}
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
var sql = self.QueryGenerator.selectQuery(tableName, options) var sql = self.QueryGenerator.selectQuery(tableName, options)
, qry = self.sequelize.query(sql, null, { plain: true, raw: true, type: 'SELECT' })
qry
.success(function(data) {
var result = data[attributeSelector]
var qry = self.sequelize if (options && options.parseInt) {
.query(sql, null, { plain: true, raw: true }) result = parseInt(result)
}
qry.success(function(data) {
self.emit('rawSelect', null) self.emit('rawSelect', null)
emitter.emit('success', data[attributeSelector]) emitter.emit('success', result)
}) })
.error(function(err) { .error(function(err) {
self.emit('rawSelect', err) self.emit('rawSelect', err)
emitter.emit('error', err) emitter.emit('error', err)
}) })
qry.on('sql', function(sql) { .on('sql', function(sql) {
emitter.emit('sql', sql) emitter.emit('sql', sql)
}) })
}).run() }).run()
...@@ -231,17 +237,25 @@ module.exports = (function() { ...@@ -231,17 +237,25 @@ module.exports = (function() {
var self = this var self = this
options = Utils._.extend({ options = Utils._.extend({
success: function(obj){}, success: function(){},
error: function(err){} error: function(){}
}, options || {}) }, options || {})
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
var query = null var query = null
if(Array.isArray(sqlOrQueryParams)) { if(Array.isArray(sqlOrQueryParams)) {
if (sqlOrQueryParams.length === 1) {
sqlOrQueryParams.push(null)
}
if (sqlOrQueryParams.length === 2) {
sqlOrQueryParams.push({})
}
query = self.sequelize.query.apply(self.sequelize, sqlOrQueryParams) query = self.sequelize.query.apply(self.sequelize, sqlOrQueryParams)
} else { } else {
query = self.sequelize.query(sqlOrQueryParams) query = self.sequelize.query(sqlOrQueryParams, null, {})
} }
// append the query for better testing // append the query for better testing
...@@ -258,7 +272,7 @@ module.exports = (function() { ...@@ -258,7 +272,7 @@ module.exports = (function() {
}) })
query.on('sql', function(sql) { query.on('sql', function(sql) {
emitter.emit('sql', sql) emitter.emit('sql', sql)
}) });
}).run() }).run()
} }
......
...@@ -10,6 +10,22 @@ if(parseFloat(process.version.replace('v', '')) < 0.6) { ...@@ -10,6 +10,22 @@ if(parseFloat(process.version.replace('v', '')) < 0.6) {
} }
module.exports = (function() { module.exports = (function() {
/**
Main constructor of the project.
Params:
- `database`
- `username`
- `password`, optional, default: null
- `options`, optional, default: {}
Examples:
mymodule.write('foo')
mymodule.write('foo', { stream: process.stderr })
*/
var Sequelize = function(database, username, password, options) { var Sequelize = function(database, username, password, options) {
this.options = Utils._.extend({ this.options = Utils._.extend({
dialect: 'mysql', dialect: 'mysql',
...@@ -20,7 +36,9 @@ module.exports = (function() { ...@@ -20,7 +36,9 @@ module.exports = (function() {
query: {}, query: {},
sync: {}, sync: {},
logging: console.log, logging: console.log,
omitNull: false omitNull: false,
queue: true,
native: false
}, options || {}) }, options || {})
if(this.options.logging === true) { if(this.options.logging === true) {
...@@ -35,7 +53,10 @@ module.exports = (function() { ...@@ -35,7 +53,10 @@ module.exports = (function() {
host : this.options.host, host : this.options.host,
port : this.options.port, port : this.options.port,
pool : this.options.pool, pool : this.options.pool,
protocol: this.options.protocol protocol: this.options.protocol,
queue : this.options.queue,
native : this.options.native,
maxConcurrentQueries: this.options.maxConcurrentQueries
} }
var ConnectorManager = require("./dialects/" + this.options.dialect + "/connector-manager") var ConnectorManager = require("./dialects/" + this.options.dialect + "/connector-manager")
...@@ -46,8 +67,14 @@ module.exports = (function() { ...@@ -46,8 +67,14 @@ module.exports = (function() {
this.importCache = {} this.importCache = {}
} }
/**
Reference to Utils
*/
Sequelize.Utils = Utils Sequelize.Utils = Utils
Sequelize.Utils._.map(DataTypes, function(sql, accessor) { Sequelize[accessor] = sql})
for (var dataType in DataTypes) {
Sequelize[dataType] = DataTypes[dataType]
}
Sequelize.prototype.getQueryInterface = function() { Sequelize.prototype.getQueryInterface = function() {
this.queryInterface = this.queryInterface || new QueryInterface(this) this.queryInterface = this.queryInterface || new QueryInterface(this)
...@@ -55,28 +82,40 @@ module.exports = (function() { ...@@ -55,28 +82,40 @@ module.exports = (function() {
} }
Sequelize.prototype.getMigrator = function(options, force) { Sequelize.prototype.getMigrator = function(options, force) {
if(force) if(force) {
this.migrator = new Migrator(this, options) this.migrator = new Migrator(this, options)
else } else {
this.migrator = this.migrator || new Migrator(this, options) this.migrator = this.migrator || new Migrator(this, options)
}
return this.migrator return this.migrator
} }
Sequelize.prototype.define = function(daoName, attributes, options) { Sequelize.prototype.define = function(daoName, attributes, options) {
options = options || {} options = options || {}
var globalOptions = this.options
if(this.options.define)
options = Sequelize.Utils.merge(options, this.options.define) if(globalOptions.define) {
options.omitNull = this.options.omitNull options = Utils._.extend({}, globalOptions.define, options)
Utils._(['classMethods', 'instanceMethods']).each(function(key) {
if(globalOptions.define[key]) {
options[key] = options[key] || {}
Utils._.extend(options[key], globalOptions.define[key])
}
})
}
options.omitNull = globalOptions.omitNull
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
} }
Sequelize.prototype.isDefined = function(daoName) {
var daos = this.daoFactoryManager.daos
return (daos.filter(function(dao) { return dao.name === daoName }).length !== 0)
}
Sequelize.prototype.import = function(path) { Sequelize.prototype.import = function(path) {
if (!this.importCache[path]) { if (!this.importCache[path]) {
var defineCall = require(path) var defineCall = require(path)
...@@ -91,9 +130,18 @@ module.exports = (function() { ...@@ -91,9 +130,18 @@ module.exports = (function() {
} }
Sequelize.prototype.query = function(sql, callee, options) { Sequelize.prototype.query = function(sql, callee, options) {
options = Utils._.extend(Utils._.clone(this.options.query), options || {}) if (arguments.length === 3) {
options = options
} else if (arguments.length === 2) {
options = {}
} else {
options = { raw: true }
}
options = Utils._.extend(Utils._.clone(this.options.query), options)
options = Utils._.extend(options, { options = Utils._.extend(options, {
logging: this.options.hasOwnProperty('logging') ? this.options.logging : console.log logging: this.options.hasOwnProperty('logging') ? this.options.logging : console.log,
type: (sql.toLowerCase().indexOf('select') === 0) ? 'SELECT' : false
}) })
return this.connectorManager.query(sql, callee, options) return this.connectorManager.query(sql, callee, options)
...@@ -103,7 +151,7 @@ module.exports = (function() { ...@@ -103,7 +151,7 @@ module.exports = (function() {
options = options || {} options = options || {}
if(this.options.sync) { if(this.options.sync) {
options = Sequelize.Utils.merge(options, this.options.sync) options = Utils._.extend({}, this.options.sync, options)
} }
var chainer = new Utils.QueryChainer() var chainer = new Utils.QueryChainer()
......
var client = new (require("mysql").Client)() var mysql = require("mysql")
, connection = mysql.createConnection({})
, util = require("util") , util = require("util")
, DataTypes = require("./data-types") , DataTypes = require("./data-types")
...@@ -12,12 +13,20 @@ var Utils = module.exports = { ...@@ -12,12 +13,20 @@ var Utils = module.exports = {
includes: _s.include, includes: _s.include,
camelizeIf: function(string, condition) { camelizeIf: function(string, condition) {
var result = string var result = string
if(condition) result = _.camelize(string)
if(condition) {
result = _.camelize(string)
}
return result return result
}, },
underscoredIf: function(string, condition) { underscoredIf: function(string, condition) {
var result = string var result = string
if(condition) result = _.underscored(string)
if(condition) {
result = _.underscored(string)
}
return result return result
} }
}) })
...@@ -32,16 +41,16 @@ var Utils = module.exports = { ...@@ -32,16 +41,16 @@ var Utils = module.exports = {
return Utils.TICK_CHAR + Utils.removeTicks(s) + Utils.TICK_CHAR return Utils.TICK_CHAR + Utils.removeTicks(s) + Utils.TICK_CHAR
}, },
removeTicks: function(s) { removeTicks: function(s) {
return s.replace("`", "") return s.replace(new RegExp(Utils.TICK_CHAR, 'g'), "")
}, },
escape: function(s) { escape: function(s) {
return client.escape(s).replace(/\\"/g, '"') return connection.escape(s).replace(/\\"/g, '"')
}, },
format: function(arr) { format: function(arr) {
var query = arr[0] var query = arr[0]
, replacements = Utils._.compact(arr.map(function(obj) { return obj != query ? obj : null})) , replacements = Utils._.compact(arr.map(function(obj) { return obj != query ? obj : null}))
return client.format.apply(client, [query, replacements]) return connection.format.apply(connection, [query, replacements])
}, },
isHash: function(obj) { isHash: function(obj) {
return Utils._.isObject(obj) && !Utils._.isArray(obj); return Utils._.isObject(obj) && !Utils._.isArray(obj);
...@@ -58,15 +67,17 @@ var Utils = module.exports = { ...@@ -58,15 +67,17 @@ var Utils = module.exports = {
}, },
argsArePrimaryKeys: function(args, primaryKeys) { argsArePrimaryKeys: function(args, primaryKeys) {
var result = (args.length == Utils._.keys(primaryKeys).length) var result = (args.length == Utils._.keys(primaryKeys).length)
if (result) {
Utils._.each(args, function(arg) { Utils._.each(args, function(arg) {
if(result) { if(result) {
if(['number', 'string'].indexOf(typeof arg) > -1) if(['number', 'string'].indexOf(typeof arg) !== -1)
result = true result = true
else else
result = (arg instanceof Date) result = (arg instanceof Date)
} }
}) })
}
return result return result
}, },
combineTableNames: function(tableName1, tableName2) { combineTableNames: function(tableName1, tableName2) {
...@@ -81,13 +92,6 @@ var Utils = module.exports = { ...@@ -81,13 +92,6 @@ var Utils = module.exports = {
return Utils.Lingo.en.isPlural(s) ? s : Utils.Lingo.en.pluralize(s) return Utils.Lingo.en.isPlural(s) ? s : Utils.Lingo.en.pluralize(s)
}, },
merge: function(a, b){
for(var key in b) {
a[key] = b[key]
}
return a
},
removeCommentsFromFunctionString: function(s) { removeCommentsFromFunctionString: function(s) {
s = s.replace(/\s*(\/\/.*)/g, '') s = s.replace(/\s*(\/\/.*)/g, '')
s = s.replace(/(\/\*[\n\r\s\S]*?\*\/)/mg, '') s = s.replace(/(\/\*[\n\r\s\S]*?\*\/)/mg, '')
...@@ -119,7 +123,7 @@ var Utils = module.exports = { ...@@ -119,7 +123,7 @@ var Utils = module.exports = {
var _hash = {} var _hash = {}
Utils._.each(hash, function(val, key) { Utils._.each(hash, function(val, key) {
if(val !== null && val !== undefined) { if (key.match(/Id$/) || ((val !== null) && (val !== undefined))) {
_hash[key] = val; _hash[key] = val;
} }
}) })
...@@ -128,6 +132,40 @@ var Utils = module.exports = { ...@@ -128,6 +132,40 @@ var Utils = module.exports = {
} }
return result return result
},
prependTableNameToHash: function(tableName, hash) {
if (tableName) {
var _hash = {}
for (var key in hash) {
if (key.indexOf('.') === -1) {
_hash[tableName + '.' + key] = hash[key]
} else {
_hash[key] = hash[key]
}
}
return _hash
} else {
return hash
}
},
inherit: function(subClass, superClass) {
if (superClass.constructor == Function) {
// Normal Inheritance
subClass.prototype = new superClass();
subClass.prototype.constructor = subClass;
subClass.prototype.parent = superClass.prototype;
} else {
// Pure Virtual Inheritance
subClass.prototype = superClass;
subClass.prototype.constructor = subClass;
subClass.prototype.parent = superClass;
}
return subClass;
} }
} }
......
{ {
"name": "sequelize", "name": "sequelize",
"description": "Multi dialect ORM for Node.JS", "description": "Multi dialect ORM for Node.JS",
"version": "1.5.0-beta-2", "version": "1.6.0-beta-1",
"author": "Sascha Depold <sascha@depold.com>", "author": "Sascha Depold <sascha@depold.com>",
"contributors": [ "contributors": [
{ {
...@@ -22,20 +22,21 @@ ...@@ -22,20 +22,21 @@
} }
], ],
"dependencies": { "dependencies": {
"mysql": "0.9.x", "mysql": "~2.0.0-alpha3",
"underscore": "1.2.x", "underscore": "~1.4.0",
"underscore.string": "2.0.x", "underscore.string": "~2.3.0",
"lingo": "0.0.x", "lingo": "~0.0.5",
"validator": "0.3.x", "validator": "0.3.x",
"moment": "1.1.x", "moment": "~1.7.0",
"commander": "~0.6.0", "commander": "~0.6.0",
"generic-pool": "1.0.9" "generic-pool": "1.0.9"
}, },
"devDependencies": { "devDependencies": {
"jasmine-node": "1.0.17", "jasmine-node": "1.0.17",
"sqlite3": "~2.1.5", "sqlite3": "~2.1.5",
"pg": "0.6.x", "pg": "~0.8.6",
"buster": "~0.6.0" "buster": "~0.6.0",
"dox-foundation": "~0.3.0"
}, },
"keywords": [ "keywords": [
"mysql", "mysql",
...@@ -47,7 +48,13 @@ ...@@ -47,7 +48,13 @@
"scripts": { "scripts": {
"test": "npm run test-jasmine && npm run test-buster", "test": "npm run test-jasmine && npm run test-buster",
"test-jasmine": "./node_modules/.bin/jasmine-node spec-jasmine/", "test-jasmine": "./node_modules/.bin/jasmine-node spec-jasmine/",
"test-buster": "./node_modules/.bin/buster-test" "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": "./node_modules/.bin/buster-test",
"test-buster-mysql": "DIALECT=mysql ./node_modules/.bin/buster-test",
"test-buster-postgres": "DIALECT=postgres ./node_modules/.bin/buster-test",
"test-buster-postgres-native": "DIALECT=postgres-native ./node_modules/.bin/buster-test",
"test-buster-sqlite": "DIALECT=sqlite ./node_modules/.bin/buster-test",
"generate-docs": "node_modules/.bin/dox-foundation --source ./lib --title Sequelize"
}, },
"bin": { "bin": {
"sequelize": "bin/sequelize" "sequelize": "bin/sequelize"
......
...@@ -62,7 +62,7 @@ describe('BelongsTo', function() { ...@@ -62,7 +62,7 @@ describe('BelongsTo', function() {
Task.belongsTo(User) Task.belongsTo(User)
var task = Task.build({title: 'asd'}) var task = Task.build({title: 'asd'})
expect(task['UserId']).toBeNull() expect(task['UserId']).not.toBeDefined();
}) })
it("sets and gets the correct objects", function() { it("sets and gets the correct objects", function() {
......
...@@ -87,11 +87,15 @@ describe('HasMany', function() { ...@@ -87,11 +87,15 @@ describe('HasMany', function() {
user.setTasks([task1, task2]).success(function() { user.setTasks([task1, task2]).success(function() {
user.getTasks().success(function(tasks) { user.getTasks().success(function(tasks) {
expect(tasks.length).toEqual(2) expect(tasks.length).toEqual(2)
user.getTasks({attributes: ['title']}).success(function(tasks) {
expect(tasks[0].selectedValues.title).toEqual('task1')
expect(tasks[0].selectedValues.id).toEqual(null)
done() done()
}) })
}) })
}) })
}) })
})
it("should allow selfAssociation to be single linked (only one DAO is created)", function() { it("should allow selfAssociation to be single linked (only one DAO is created)", function() {
var oldLength = sequelize.daoFactoryManager.daos.length; var oldLength = sequelize.daoFactoryManager.daos.length;
......
...@@ -88,11 +88,15 @@ describe('HasOne', function() { ...@@ -88,11 +88,15 @@ describe('HasOne', function() {
user.setTask(task).on('success', function() { user.setTask(task).on('success', function() {
user.getTask().on('success', function(task2) { user.getTask().on('success', function(task2) {
expect(task.title).toEqual(task2.title) 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() done()
}) })
}) })
}) })
}) })
})
it("unsets unassociated objects", function() { it("unsets unassociated objects", function() {
var user, task1, task2; var user, task1, task2;
......
...@@ -31,248 +31,28 @@ describe('DAO', function() { ...@@ -31,248 +31,28 @@ describe('DAO', function() {
beforeEach(function() { Helpers.dropAllTables(); setup() }) beforeEach(function() { Helpers.dropAllTables(); setup() })
afterEach(function() { Helpers.dropAllTables() }) afterEach(function() { Helpers.dropAllTables() })
describe('Validations', function() { describe('Escaping', function() {
var checks = { it('is done properly for special characters', function() {
is : { var User = sequelize.define('User', {
spec : { args: ["[a-z]",'i'] }, bio: Sequelize.TEXT
fail: "0", }, { timestamps: false, logging: false })
pass: "a"
}
, not : {
spec: { args: ["[a-z]",'i'] },
fail: "a",
pass: "0"
}
, isEmail : {
fail: "a",
pass: "abc@abc.com"
}
, isUrl : {
fail: "abc",
pass: "http://abc.com"
}
, isIP : {
fail: "abc",
pass: "129.89.23.1"
}
, isAlpha : {
fail: "012",
pass: "abc"
}
, isAlphanumeric : {
fail: "_abc019",
pass: "abc019"
}
, isNumeric : {
fail: "abc",
pass: "019"
}
, isInt : {
fail: "9.2",
pass: "-9"
}
, isLowercase : {
fail: "AB",
pass: "ab"
}
, isUppercase : {
fail: "ab",
pass: "AB"
}
, isDecimal : {
fail: "a",
pass: "0.2"
}
, isFloat : {
fail: "a",
pass: "9.2"
}
, notNull : {
fail: null,
pass: 0
}
, isNull : {
fail: 0,
pass: null
}
, notEmpty : {
fail: " ",
pass: "a"
}
, equals : {
spec : { args : "bla bla bla" },
fail: "bla",
pass: "bla bla bla"
}
, contains : {
spec : { args : "bla" },
fail: "la",
pass: "0bla23"
}
, notContains : {
spec : { args : "bla" },
fail: "0bla23",
pass: "la"
}
, regex : {
spec : { args: ["[a-z]",'i'] },
fail: "0",
pass: "a"
}
, notRegex : {
spec: { args: ["[a-z]",'i'] },
fail: "a",
pass: "0"
}
, len : {
spec: { args: [2,4] },
fail: ["1", "12345"],
pass: ["12", "123", "1234"],
raw: true
}
, isUUID : {
spec: { args: 4 },
fail: "f47ac10b-58cc-3372-a567-0e02b2c3d479",
pass: "f47ac10b-58cc-4372-a567-0e02b2c3d479"
}
, isDate : {
fail: "not a date",
pass: "2011-02-04"
}
, isAfter : {
spec: { args: "2011-11-05" },
fail: "2011-11-04",
pass: "2011-11-05"
}
, isBefore : {
spec: { args: "2011-11-05" },
fail: "2011-11-06",
pass: "2011-11-05"
}
, isIn : {
spec: { args: "abcdefghijk" },
fail: "ghik",
pass: "ghij"
}
, notIn : {
spec: { args: "abcdefghijk" },
fail: "ghij",
pass: "ghik"
}
, max : {
spec: { args: 23 },
fail: "24",
pass: "23"
}
, min : {
spec: { args: 23 },
fail: "22",
pass: "23"
}
, isArray : {
fail: 22,
pass: [22]
}
, isCreditCard : {
fail: "401288888888188f",
pass: "4012888888881881"
}
};
var User, i;
it('should correctly validate using node-validator methods', function() {
Helpers.async(function(done) { Helpers.async(function(done) {
for (var validator in checks) { User.sync({ force: true }).success(done)
if (checks.hasOwnProperty(validator)) { })
// build spec
var v = {};
v[validator] = checks[validator].hasOwnProperty("spec") ? checks[validator].spec : {};
var check = checks[validator];
// test for failure
if (!check.hasOwnProperty("raw"))
check.fail = new Array(check.fail);
for (i=0; i<check.fail.length; ++i) {
v[validator].msg = validator + "(" + check.fail[i] + ")";
// define user
User = sequelize.define('User' + Math.random(), {
name: {
type: Sequelize.STRING,
validate: v
}
});
var u_fail = User.build({
name : check.fail[i]
});
var errors = u_fail.validate();
expect(errors).toNotBe(null);
expect(errors).toEqual({
name : [v[validator].msg]
});
}
// test for success
if (!check.hasOwnProperty("raw"))
check.pass = new Array(check.pass);
for (i=0; i<check.pass.length; ++i) {
v[validator].msg = validator + "(" + check.pass[i] + ")";
// define user
User = sequelize.define('User' + Math.random(), {
name: {
type: Sequelize.STRING,
validate: v
}
});
var u_success = User.build({
name : check.pass[i]
});
expect(u_success.validate()).toBe(null);
}
}
} // for each check
done();
});
});
it('should correctly validate using custom validation methods', function() {
Helpers.async(function(done) { Helpers.async(function(done) {
User = sequelize.define('User' + Math.random(), { // Ideally we should test more: "\0\n\r\b\t\\\'\"\x1a"
name: { // But this causes sqlite to fail and exits the entire test suite immediately
type: Sequelize.STRING, var bio = dialect + "'\"\n"; // Need to add the dialect here so in case of failure I know what DB it failed for
validate: { User.create({ bio: bio }).success(function(u1) {
customFn: function(val) { User.find(u1.id).success(function(u2) {
if (val !== "2") expect(u2.bio).toEqual(bio)
throw new Error("name should equal '2'") done()
} })
} })
} })
}); })
var u_fail = User.build({
name : "3"
});
var errors = u_fail.validate();
expect(errors).toNotBe(null);
expect(errors).toEqual({
name : ["name should equal '2'"]
});
var u_success = User.build({
name : "2"
});
expect(u_success.validate()).toBe(null);
done();
});
});
}) })
describe('isNewRecord', function() { describe('isNewRecord', function() {
...@@ -449,7 +229,8 @@ describe('DAO', function() { ...@@ -449,7 +229,8 @@ describe('DAO', function() {
it("doesn't update the updatedAt column", function() { it("doesn't update the updatedAt column", function() {
Helpers.async(function(done) { Helpers.async(function(done) {
User2.create({ username: 'john doe' }).success(function(johnDoe) { User2.create({ username: 'john doe' }).success(function(johnDoe) {
expect(johnDoe.updatedAt).toBeNull() // sqlite and mysql return undefined, whereas postgres returns null
expect([undefined, null].indexOf(johnDoe.updatedAt)).not.toBe(-1);
done() done()
}) })
}) })
...@@ -554,60 +335,6 @@ describe('DAO', function() { ...@@ -554,60 +335,6 @@ describe('DAO', function() {
}) })
}) })
describe('toJSON', function() {
it('returns an object containing all values', function() {
var self = this
var User = sequelize.define('User', {
username: Sequelize.STRING, age: Sequelize.INTEGER, isAdmin: Sequelize.BOOLEAN
}, { timestamps: false, logging: false })
Helpers.async(function(done) {
User.sync({ force: true }).success(done)
})
Helpers.async(function(done) {
var user = User.build({ username: 'test.user', age: 99, isAdmin: true })
expect(user.toJSON()).toEqual({ username: 'test.user', age: 99, isAdmin: true, id: null })
done()
})
})
it('returns a response that can be stringified', function() {
var self = this
var User = sequelize.define('User', {
username: Sequelize.STRING, age: Sequelize.INTEGER, isAdmin: Sequelize.BOOLEAN
}, { timestamps: false, logging: false })
Helpers.async(function(done) {
User.sync({ force: true }).success(done)
})
Helpers.async(function(done) {
var user = User.build({ username: 'test.user', age: 99, isAdmin: true })
expect(JSON.stringify(user)).toEqual('{"username":"test.user","age":99,"isAdmin":true,"id":null}')
done()
})
})
it('returns a response that can be stringified and then parsed', function() {
var self = this
var User = sequelize.define('User', {
username: Sequelize.STRING, age: Sequelize.INTEGER, isAdmin: Sequelize.BOOLEAN
}, { timestamps: false, logging: false })
Helpers.async(function(done) {
User.sync({ force: true }).success(done)
})
Helpers.async(function(done) {
var user = User.build({ username: 'test.user', age: 99, isAdmin: true })
expect(JSON.parse(JSON.stringify(user))).toEqual({ username: 'test.user', age: 99, isAdmin: true, id: null })
done()
})
})
})
}) })
}) })
}) })
...@@ -35,44 +35,61 @@ describe('QueryGenerator', function() { ...@@ -35,44 +35,61 @@ describe('QueryGenerator', function() {
selectQuery: [ selectQuery: [
{ {
arguments: ['myTable'], arguments: ['myTable'],
expectation: "SELECT * FROM `myTable`;" expectation: "SELECT * FROM `myTable`;",
context: QueryGenerator
}, { }, {
arguments: ['myTable', {attributes: ['id', 'name']}], arguments: ['myTable', {attributes: ['id', 'name']}],
expectation: "SELECT `id`, `name` FROM `myTable`;" expectation: "SELECT `id`, `name` FROM `myTable`;",
context: QueryGenerator
}, { }, {
arguments: ['myTable', {where: {id: 2}}], arguments: ['myTable', {where: {id: 2}}],
expectation: "SELECT * FROM `myTable` WHERE `id`=2;" expectation: "SELECT * FROM `myTable` WHERE `myTable`.`id`=2;",
context: QueryGenerator
}, { }, {
arguments: ['myTable', {where: {name: 'foo'}}], arguments: ['myTable', {where: {name: 'foo'}}],
expectation: "SELECT * FROM `myTable` WHERE `name`='foo';" expectation: "SELECT * FROM `myTable` WHERE `myTable`.`name`='foo';",
context: QueryGenerator
}, { }, {
arguments: ['myTable', {where: {name: "foo';DROP TABLE myTable;"}}], arguments: ['myTable', {where: {name: "foo';DROP TABLE myTable;"}}],
expectation: "SELECT * FROM `myTable` WHERE `name`='foo\\';DROP TABLE myTable;';" expectation: "SELECT * FROM `myTable` WHERE `myTable`.`name`='foo\\';DROP TABLE myTable;';",
context: QueryGenerator
}, { }, {
arguments: ['myTable', {where: 2}], arguments: ['myTable', {where: 2}],
expectation: "SELECT * FROM `myTable` WHERE `id`=2;" expectation: "SELECT * FROM `myTable` WHERE `myTable`.`id`=2;",
context: QueryGenerator
}, { }, {
arguments: ['foo', { attributes: [['count(*)', 'count']] }], arguments: ['foo', { attributes: [['count(*)', 'count']] }],
expectation: 'SELECT count(*) as `count` FROM `foo`;' expectation: 'SELECT count(*) as `count` FROM `foo`;',
context: QueryGenerator
}, { }, {
arguments: ['myTable', {where: "foo='bar'"}], arguments: ['myTable', {where: "foo='bar'"}],
expectation: "SELECT * FROM `myTable` WHERE foo='bar';" expectation: "SELECT * FROM `myTable` WHERE foo='bar';",
context: QueryGenerator
}, { }, {
arguments: ['myTable', {order: "id DESC"}], arguments: ['myTable', {order: "id DESC"}],
expectation: "SELECT * FROM `myTable` ORDER BY id DESC;" expectation: "SELECT * FROM `myTable` ORDER BY id DESC;",
context: QueryGenerator
}, { }, {
arguments: ['myTable', {group: "name"}], arguments: ['myTable', {group: "name"}],
expectation: "SELECT * FROM `myTable` GROUP BY `name`;" expectation: "SELECT * FROM `myTable` GROUP BY `name`;",
context: QueryGenerator
}, {
arguments: ['myTable', {group: "name", order: "id DESC"}],
expectation: "SELECT * FROM `myTable` GROUP BY `name` ORDER BY id DESC;",
context: QueryGenerator
}, { }, {
arguments: ['myTable', {limit: 10}], arguments: ['myTable', {limit: 10}],
expectation: "SELECT * FROM `myTable` LIMIT 10;" expectation: "SELECT * FROM `myTable` LIMIT 10;",
context: QueryGenerator
}, { }, {
arguments: ['myTable', {limit: 10, offset: 2}], arguments: ['myTable', {limit: 10, offset: 2}],
expectation: "SELECT * FROM `myTable` LIMIT 2, 10;" expectation: "SELECT * FROM `myTable` LIMIT 2, 10;",
context: QueryGenerator
}, { }, {
title: 'ignores offset if no limit was passed', title: 'ignores offset if no limit was passed',
arguments: ['myTable', {offset: 2}], arguments: ['myTable', {offset: 2}],
expectation: "SELECT * FROM `myTable`;" expectation: "SELECT * FROM `myTable`;",
context: QueryGenerator
} }
], ],
...@@ -194,6 +211,10 @@ describe('QueryGenerator', function() { ...@@ -194,6 +211,10 @@ describe('QueryGenerator', function() {
{ {
arguments: [{ id: [1,2,3] }], arguments: [{ id: [1,2,3] }],
expectation: "`id` IN (1,2,3)" expectation: "`id` IN (1,2,3)"
},
{
arguments: [{ id: [] }],
expectation: "`id` IN (NULL)"
} }
] ]
} }
......
...@@ -19,12 +19,21 @@ describe('QueryGenerator', function() { ...@@ -19,12 +19,21 @@ describe('QueryGenerator', function() {
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}], arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}],
expectation: "CREATE TABLE IF NOT EXISTS \"myTable\" (\"title\" VARCHAR(255), \"name\" VARCHAR(255));" expectation: "CREATE TABLE IF NOT EXISTS \"myTable\" (\"title\" VARCHAR(255), \"name\" VARCHAR(255));"
}, },
{
arguments: ['mySchema.myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}],
expectation: "CREATE TABLE IF NOT EXISTS \"mySchema\".\"myTable\" (\"title\" VARCHAR(255), \"name\" VARCHAR(255));"
},
], ],
dropTableQuery: [ dropTableQuery: [
{ {
arguments: ['myTable'], arguments: ['myTable'],
expectation: "DROP TABLE IF EXISTS \"myTable\";" expectation: "DROP TABLE IF EXISTS \"myTable\";"
},
{
arguments: ['mySchema.myTable'],
expectation: "DROP TABLE IF EXISTS \"mySchema\".\"myTable\";"
} }
], ],
...@@ -69,6 +78,12 @@ describe('QueryGenerator', function() { ...@@ -69,6 +78,12 @@ describe('QueryGenerator', function() {
title: 'uses offset even if no limit was passed', title: 'uses offset even if no limit was passed',
arguments: ['myTable', {offset: 2}], arguments: ['myTable', {offset: 2}],
expectation: "SELECT * FROM \"myTable\" OFFSET 2;" expectation: "SELECT * FROM \"myTable\" OFFSET 2;"
}, {
arguments: ['mySchema.myTable'],
expectation: "SELECT * FROM \"mySchema\".\"myTable\";"
}, {
arguments: ['mySchema.myTable', {where: {name: "foo';DROP TABLE mySchema.myTable;"}}],
expectation: "SELECT * FROM \"mySchema\".\"myTable\" WHERE \"name\"='foo'';DROP TABLE mySchema.myTable;';"
} }
], ],
...@@ -100,38 +115,53 @@ describe('QueryGenerator', function() { ...@@ -100,38 +115,53 @@ describe('QueryGenerator', function() {
arguments: ['myTable', {name: 'foo', nullValue: undefined}], arguments: ['myTable', {name: 'foo', nullValue: undefined}],
expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo') RETURNING *;", expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo') RETURNING *;",
context: {options: {omitNull: true}} context: {options: {omitNull: true}}
}, {
arguments: ['mySchema.myTable', {name: 'foo'}],
expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('foo') RETURNING *;"
}, {
arguments: ['mySchema.myTable', {name: JSON.stringify({info: 'Look ma a " quote'})}],
expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('{\"info\":\"Look ma a \\\" quote\"}') RETURNING *;"
}, {
arguments: ['mySchema.myTable', {name: "foo';DROP TABLE mySchema.myTable;"}],
expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('foo'';DROP TABLE mySchema.myTable;') RETURNING *;"
} }
], ],
updateQuery: [ updateQuery: [
{ {
arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {id: 2}], arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {id: 2}],
expectation: "UPDATE \"myTable\" SET \"name\"='foo',\"birthday\"='2011-03-27 10:01:55.0' WHERE \"id\"=2" expectation: "UPDATE \"myTable\" SET \"name\"='foo',\"birthday\"='2011-03-27 10:01:55.0' WHERE \"id\"=2 RETURNING *"
}, { }, {
arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, 2], arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, 2],
expectation: "UPDATE \"myTable\" SET \"name\"='foo',\"birthday\"='2011-03-27 10:01:55.0' WHERE \"id\"=2" expectation: "UPDATE \"myTable\" SET \"name\"='foo',\"birthday\"='2011-03-27 10:01:55.0' WHERE \"id\"=2 RETURNING *"
}, { }, {
arguments: ['myTable', {bar: 2}, {name: 'foo'}], arguments: ['myTable', {bar: 2}, {name: 'foo'}],
expectation: "UPDATE \"myTable\" SET \"bar\"=2 WHERE \"name\"='foo'" expectation: "UPDATE \"myTable\" SET \"bar\"=2 WHERE \"name\"='foo' RETURNING *"
}, { }, {
arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {name: 'foo'}], arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {name: 'foo'}],
expectation: "UPDATE \"myTable\" SET \"name\"='foo'';DROP TABLE myTable;' WHERE \"name\"='foo'" expectation: "UPDATE \"myTable\" SET \"name\"='foo'';DROP TABLE myTable;' WHERE \"name\"='foo' RETURNING *"
}, { }, {
arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}], arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}],
expectation: "UPDATE \"myTable\" SET \"bar\"=2,\"nullValue\"=NULL WHERE \"name\"='foo'" expectation: "UPDATE \"myTable\" SET \"bar\"=2,\"nullValue\"=NULL WHERE \"name\"='foo' RETURNING *"
}, { }, {
arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}], arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}],
expectation: "UPDATE \"myTable\" SET \"bar\"=2,\"nullValue\"=NULL WHERE \"name\"='foo'", expectation: "UPDATE \"myTable\" SET \"bar\"=2,\"nullValue\"=NULL WHERE \"name\"='foo' RETURNING *",
context: {options: {omitNull: false}} context: {options: {omitNull: false}}
}, { }, {
arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}], arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}],
expectation: "UPDATE \"myTable\" SET \"bar\"=2 WHERE \"name\"='foo'", expectation: "UPDATE \"myTable\" SET \"bar\"=2 WHERE \"name\"='foo' RETURNING *",
context: {options: {omitNull: true}} context: {options: {omitNull: true}}
}, { }, {
arguments: ['myTable', {bar: 2, nullValue: undefined}, {name: 'foo'}], arguments: ['myTable', {bar: 2, nullValue: undefined}, {name: 'foo'}],
expectation: "UPDATE \"myTable\" SET \"bar\"=2 WHERE \"name\"='foo'", expectation: "UPDATE \"myTable\" SET \"bar\"=2 WHERE \"name\"='foo' RETURNING *",
context: {options: {omitNull: true}} context: {options: {omitNull: true}}
}, }, {
arguments: ['mySchema.myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {id: 2}],
expectation: "UPDATE \"mySchema\".\"myTable\" SET \"name\"='foo',\"birthday\"='2011-03-27 10:01:55.0' WHERE \"id\"=2 RETURNING *"
}, {
arguments: ['mySchema.myTable', {name: "foo';DROP TABLE mySchema.myTable;"}, {name: 'foo'}],
expectation: "UPDATE \"mySchema\".\"myTable\" SET \"name\"='foo'';DROP TABLE mySchema.myTable;' WHERE \"name\"='foo' RETURNING *"
}
], ],
deleteQuery: [ deleteQuery: [
...@@ -147,6 +177,12 @@ describe('QueryGenerator', function() { ...@@ -147,6 +177,12 @@ describe('QueryGenerator', function() {
}, { }, {
arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {limit: 10}], arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {limit: 10}],
expectation: "DELETE FROM \"myTable\" WHERE \"id\" IN (SELECT \"id\" FROM \"myTable\" WHERE \"name\"='foo'';DROP TABLE myTable;' LIMIT 10)" expectation: "DELETE FROM \"myTable\" WHERE \"id\" IN (SELECT \"id\" FROM \"myTable\" WHERE \"name\"='foo'';DROP TABLE myTable;' LIMIT 10)"
}, {
arguments: ['mySchema.myTable', {name: 'foo'}],
expectation: "DELETE FROM \"mySchema\".\"myTable\" WHERE \"id\" IN (SELECT \"id\" FROM \"mySchema\".\"myTable\" WHERE \"name\"='foo' LIMIT 1)"
}, {
arguments: ['mySchema.myTable', {name: "foo';DROP TABLE mySchema.myTable;"}, {limit: 10}],
expectation: "DELETE FROM \"mySchema\".\"myTable\" WHERE \"id\" IN (SELECT \"id\" FROM \"mySchema\".\"myTable\" WHERE \"name\"='foo'';DROP TABLE mySchema.myTable;' LIMIT 10)"
} }
], ],
...@@ -167,6 +203,9 @@ describe('QueryGenerator', function() { ...@@ -167,6 +203,9 @@ describe('QueryGenerator', function() {
'User', ['username', 'isAdmin'], { indicesType: 'FULLTEXT', indexName: 'bar'} 'User', ['username', 'isAdmin'], { indicesType: 'FULLTEXT', indexName: 'bar'}
], ],
expectation: "CREATE FULLTEXT INDEX \"bar\" ON \"User\" (\"username\", \"isAdmin\")" expectation: "CREATE FULLTEXT INDEX \"bar\" ON \"User\" (\"username\", \"isAdmin\")"
}, {
arguments: ['mySchema.User', ['username', 'isAdmin']],
expectation: 'CREATE INDEX \"user_username_is_admin\" ON \"mySchema\".\"User\" (\"username\", \"isAdmin\")'
} }
], ],
...@@ -188,6 +227,9 @@ describe('QueryGenerator', function() { ...@@ -188,6 +227,9 @@ describe('QueryGenerator', function() {
}, { }, {
arguments: ['User', ['foo', 'bar']], arguments: ['User', ['foo', 'bar']],
expectation: "DROP INDEX IF EXISTS \"user_foo_bar\"" expectation: "DROP INDEX IF EXISTS \"user_foo_bar\""
}, {
arguments: ['User', 'mySchema.user_foo_bar'],
expectation: "DROP INDEX IF EXISTS \"mySchema\".\"user_foo_bar\""
} }
], ],
...@@ -195,6 +237,10 @@ describe('QueryGenerator', function() { ...@@ -195,6 +237,10 @@ describe('QueryGenerator', function() {
{ {
arguments: [{ id: [1,2,3] }], arguments: [{ id: [1,2,3] }],
expectation: "\"id\" IN (1,2,3)" expectation: "\"id\" IN (1,2,3)"
},
{
arguments: [{ id: [] }],
expectation: "\"id\" IN (NULL)"
} }
] ]
} }
......
...@@ -40,6 +40,35 @@ describe('Sequelize', function() { ...@@ -40,6 +40,35 @@ describe('Sequelize', function() {
sequelize.define('foo', { title: Sequelize.STRING }) sequelize.define('foo', { title: Sequelize.STRING })
expect(sequelize.daoFactoryManager.all.length).toEqual(1) 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')
})
}) })
describe('sync', function() { describe('sync', function() {
......
...@@ -2,37 +2,38 @@ if (typeof require === 'function') { ...@@ -2,37 +2,38 @@ if (typeof require === 'function') {
const buster = require("buster") const buster = require("buster")
, Helpers = require('../buster-helpers') , Helpers = require('../buster-helpers')
, Sequelize = require('../../index') , Sequelize = require('../../index')
, dialect = Helpers.getTestDialect()
} }
buster.spec.expose() buster.spec.expose()
buster.testRunner.timeout = 500 buster.testRunner.timeout = 500
describe('BelongsTo', function() { describe("[" + Helpers.getTestDialectTeaser() + "] BelongsTo", function() {
before(function(done) { before(function(done) {
var self = this
Helpers.initTests({ Helpers.initTests({
beforeComplete: function(sequelize) { self.sequelize = sequelize }, beforeComplete: function(sequelize) {
this.sequelize = sequelize
}.bind(this),
onComplete: done onComplete: done
}) })
}) })
describe('setAssociation', function() { describe('setAssociation', function() {
it('clears the association if null is passed', function(done) { it('clears the association if null is passed', function(done) {
var User = this.sequelize.define('User', { username: Sequelize.STRING }) var User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING })
, Task = this.sequelize.define('Task', { title: Sequelize.STRING }) , Task = this.sequelize.define('TaskXYZ', { title: Sequelize.STRING })
Task.belongsTo(User) Task.belongsTo(User)
this.sequelize.sync({ force: true }).success(function() { this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) { User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) { Task.create({ title: 'task' }).success(function(task) {
task.setUser(user).success(function() { task.setUserXYZ(user).success(function() {
task.getUser().success(function(user) { task.getUserXYZ().success(function(user) {
expect(user).not.toEqual(null) expect(user).not.toEqual(null)
task.setUser(null).success(function() { task.setUserXYZ(null).success(function() {
task.getUser().success(function(user) { task.getUserXYZ().success(function(user) {
expect(user).toEqual(null) expect(user).toEqual(null)
done() done()
}) })
......
...@@ -2,16 +2,18 @@ if (typeof require === 'function') { ...@@ -2,16 +2,18 @@ if (typeof require === 'function') {
const buster = require("buster") const buster = require("buster")
, Helpers = require('../buster-helpers') , Helpers = require('../buster-helpers')
, Sequelize = require('../../index') , Sequelize = require('../../index')
, dialect = Helpers.getTestDialect()
} }
buster.spec.expose() buster.spec.expose()
buster.testRunner.timeout = 500 buster.testRunner.timeout = 500
describe('HasMany', function() { describe("[" + Helpers.getTestDialectTeaser() + "] HasMany", function() {
before(function(done) { before(function(done) {
var self = this var self = this
Helpers.initTests({ Helpers.initTests({
dialect: dialect,
beforeComplete: function(sequelize) { self.sequelize = sequelize }, beforeComplete: function(sequelize) { self.sequelize = sequelize },
onComplete: done onComplete: done
}) })
...@@ -154,7 +156,7 @@ describe('HasMany', function() { ...@@ -154,7 +156,7 @@ describe('HasMany', function() {
}) })
it("clears associations when passing null to the set-method with omitNull set to true", function(done) { it("clears associations when passing null to the set-method with omitNull set to true", function(done) {
this.sequelize.options.omitNull = true; this.sequelize.options.omitNull = true
var User = this.sequelize.define('User', { username: Sequelize.STRING }) var User = this.sequelize.define('User', { username: Sequelize.STRING })
, Task = this.sequelize.define('Task', { title: Sequelize.STRING }) , Task = this.sequelize.define('Task', { title: Sequelize.STRING })
...@@ -212,9 +214,9 @@ describe('HasMany', function() { ...@@ -212,9 +214,9 @@ describe('HasMany', function() {
}) })
}) })
it("only get objects that fullfil the options", function(done) { it("only get objects that fulfill the options", function(done) {
this.User.find({where: {username: 'John'}}).success(function (john) { this.User.find({ where: { username: 'John' } }).success(function (john) {
john.getTasks({where: {active: true}, limit: 10, order: 'ID DESC'}).success(function (tasks) { john.getTasks({ where: { active: true }, limit: 10, order: 'id DESC' }).success(function (tasks) {
expect(tasks.length).toEqual(1) expect(tasks.length).toEqual(1)
done(); done();
}) })
...@@ -256,7 +258,7 @@ describe('HasMany', function() { ...@@ -256,7 +258,7 @@ describe('HasMany', function() {
}) })
}) })
it("only get objects that fullfil the options", function(done) { it("only get objects that fulfill the options", function(done) {
this.User.find({where: {username: 'John'}}).success(function (john) { this.User.find({where: {username: 'John'}}).success(function (john) {
john.getTasks({where: {active: true}}).success(function (tasks) { john.getTasks({where: {active: true}}).success(function (tasks) {
expect(tasks.length).toEqual(1) expect(tasks.length).toEqual(1)
......
...@@ -2,16 +2,18 @@ if (typeof require === 'function') { ...@@ -2,16 +2,18 @@ if (typeof require === 'function') {
const buster = require("buster") const buster = require("buster")
, Sequelize = require("../../index") , Sequelize = require("../../index")
, Helpers = require('../buster-helpers') , Helpers = require('../buster-helpers')
, dialect = Helpers.getTestDialect()
} }
buster.spec.expose() buster.spec.expose()
buster.testRunner.timeout = 500 buster.testRunner.timeout = 1500
describe('HasOne', function() { describe("[" + Helpers.getTestDialectTeaser() + "] HasOne", function() {
before(function(done) { before(function(done) {
var self = this var self = this
Helpers.initTests({ Helpers.initTests({
dialect: dialect,
beforeComplete: function(sequelize) { self.sequelize = sequelize }, beforeComplete: function(sequelize) { self.sequelize = sequelize },
onComplete: done onComplete: done
}) })
...@@ -19,20 +21,20 @@ describe('HasOne', function() { ...@@ -19,20 +21,20 @@ describe('HasOne', function() {
describe('setAssociation', function() { describe('setAssociation', function() {
it('clears the association if null is passed', function(done) { it('clears the association if null is passed', function(done) {
var User = this.sequelize.define('User', { username: Sequelize.STRING }) var User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING })
, Task = this.sequelize.define('Task', { title: Sequelize.STRING }) , Task = this.sequelize.define('TaskXYZ', { title: Sequelize.STRING })
User.hasOne(Task) User.hasOne(Task)
this.sequelize.sync({ force: true }).success(function() { this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) { User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) { Task.create({ title: 'task' }).success(function(task) {
user.setTask(task).success(function() { user.setTaskXYZ(task).success(function() {
user.getTask().success(function(task) { user.getTaskXYZ().success(function(task) {
expect(task).not.toEqual(null) expect(task).not.toEqual(null)
user.setTask(null).success(function() { user.setTaskXYZ(null).success(function() {
user.getTask().success(function(task) { user.getTaskXYZ().success(function(task) {
expect(task).toEqual(null) expect(task).toEqual(null)
done() 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)
})
})
})
...@@ -4,6 +4,8 @@ const Sequelize = require(__dirname + "/../index") ...@@ -4,6 +4,8 @@ const Sequelize = require(__dirname + "/../index")
, fs = require('fs') , fs = require('fs')
var BusterHelpers = module.exports = { var BusterHelpers = module.exports = {
Sequelize: Sequelize,
initTests: function(options) { initTests: function(options) {
var sequelize = this.createSequelizeInstance(options) var sequelize = this.createSequelizeInstance(options)
...@@ -19,15 +21,21 @@ var BusterHelpers = module.exports = { ...@@ -19,15 +21,21 @@ var BusterHelpers = module.exports = {
options.dialect = options.dialect || 'mysql' options.dialect = options.dialect || 'mysql'
options.logging = (options.hasOwnProperty('logging') ? options.logging : false) options.logging = (options.hasOwnProperty('logging') ? options.logging : false)
return new Sequelize( var sequelizeOptions = {
config[options.dialect].database,
config[options.dialect].username,
config[options.dialect].password,
{
logging: options.logging, logging: options.logging,
dialect: options.dialect, dialect: options.dialect,
port: config[options.dialect].port port: config[options.dialect].port
} }
if (process.env.DIALECT === 'postgres-native') {
sequelizeOptions.native = true
}
return new Sequelize(
config[options.dialect].database,
config[options.dialect].username,
config[options.dialect].password,
sequelizeOptions
) )
}, },
...@@ -44,7 +52,39 @@ var BusterHelpers = module.exports = { ...@@ -44,7 +52,39 @@ var BusterHelpers = module.exports = {
getSupportedDialects: function() { getSupportedDialects: function() {
return fs.readdirSync(__dirname + '/../lib/dialects').filter(function(file) { return fs.readdirSync(__dirname + '/../lib/dialects').filter(function(file) {
return (file.indexOf('.js') === -1) return ((file.indexOf('.js') === -1) && (file.indexOf('abstract') === -1))
}) })
},
getTestDialect: function() {
var envDialect = process.env.DIALECT || 'mysql'
if (envDialect === 'postgres-native') {
envDialect = 'postgres'
}
if (this.getSupportedDialects().indexOf(envDialect) === -1) {
throw new Error('The dialect you have passed is unknown. Did you really mean: ' + envDialect)
}
return envDialect
},
getTestDialectTeaser: function() {
var dialect = this.getTestDialect()
if (process.env.DIALECT === 'postgres-native') {
dialect = 'postgres-native'
}
return dialect.toUpperCase()
},
checkMatchForDialects: function(dialect, value, expectations) {
if (!!expectations[dialect]) {
expect(value).toMatch(expectations[dialect])
} else {
throw new Error('Undefined expectation for "' + dialect + '"!')
}
} }
} }
if(typeof require === 'function') { if(typeof require === 'function') {
const buster = require("buster") const buster = require("buster")
, dialects = ['sqlite', 'mysql', 'postgres']
, Helpers = require('./buster-helpers') , Helpers = require('./buster-helpers')
, dialect = Helpers.getTestDialect()
} }
buster.spec.expose() buster.spec.expose()
dialects.forEach(function(dialect) { describe("[" + Helpers.getTestDialectTeaser() + "] DAO", function() {
describe('DAO@' + dialect, function() {
before(function(done) { before(function(done) {
var self = this var self = this
...@@ -21,7 +20,7 @@ dialects.forEach(function(dialect) { ...@@ -21,7 +20,7 @@ dialects.forEach(function(dialect) {
aNumber: { type: DataTypes.INTEGER } aNumber: { type: DataTypes.INTEGER }
}) })
}, },
onComplete: function(sequelize) { onComplete: function() {
self.User.sync({ force: true }).success(done) self.User.sync({ force: true }).success(done)
} }
}) })
...@@ -69,5 +68,34 @@ dialects.forEach(function(dialect) { ...@@ -69,5 +68,34 @@ dialects.forEach(function(dialect) {
}) })
}) })
}) })
describe('toJSON', function toJSON() {
before(function(done) {
this.User = this.sequelize.define('UserWithUsernameAndAgeAndIsAdmin', {
username: Helpers.Sequelize.STRING,
age: Helpers.Sequelize.INTEGER,
isAdmin: Helpers.Sequelize.BOOLEAN
}, {
timestamps: false,
logging: true
})
this.User.sync({ force: true }).success(done)
})
it('returns an object containing all values', function() {
var user = this.User.build({ username: 'test.user', age: 99, isAdmin: true })
expect(user.toJSON()).toEqual({ username: 'test.user', age: 99, isAdmin: true, id: null })
})
it('returns a response that can be stringified', function() {
var user = this.User.build({ username: 'test.user', age: 99, isAdmin: true })
expect(JSON.stringify(user)).toEqual('{"username":"test.user","age":99,"isAdmin":true,"id":null}')
})
it('returns a response that can be stringified and then parsed', function() {
var user = this.User.build({ username: 'test.user', age: 99, isAdmin: true })
expect(JSON.parse(JSON.stringify(user))).toEqual({ username: 'test.user', age: 99, isAdmin: true, id: null })
})
}) })
}) })
if(typeof require === 'function') {
const buster = require("buster")
, Sequelize = require("../index")
, Helpers = require('./buster-helpers')
, dialect = Helpers.getTestDialect()
}
buster.spec.expose()
describe("[" + Helpers.getTestDialectTeaser() + "] DAO", function() {
describe('validations', function() {
before(function(done) {
Helpers.initTests({
dialect: dialect,
onComplete: function(sequelize) {
this.sequelize = sequelize
done()
}.bind(this)
})
}) //- before
var checks = {
is: {
spec: { args: ["[a-z]",'i'] },
fail: "0",
pass: "a"
},
not: {
spec: { args: ["[a-z]",'i'] },
fail: "a",
pass: "0"
},
isEmail : {
fail: "a",
pass: "abc@abc.com"
}
, isUrl : {
fail: "abc",
pass: "http://abc.com"
}
, isIP : {
fail: "abc",
pass: "129.89.23.1"
}
, isAlpha : {
fail: "012",
pass: "abc"
}
, isAlphanumeric : {
fail: "_abc019",
pass: "abc019"
}
, isNumeric : {
fail: "abc",
pass: "019"
}
, isInt : {
fail: "9.2",
pass: "-9"
}
, isLowercase : {
fail: "AB",
pass: "ab"
}
, isUppercase : {
fail: "ab",
pass: "AB"
}
, isDecimal : {
fail: "a",
pass: "0.2"
}
, isFloat : {
fail: "a",
pass: "9.2"
}
, notNull : {
fail: null,
pass: 0
}
, isNull : {
fail: 0,
pass: null
}
, notEmpty : {
fail: " ",
pass: "a"
}
, equals : {
spec : { args : "bla bla bla" },
fail: "bla",
pass: "bla bla bla"
}
, contains : {
spec : { args : "bla" },
fail: "la",
pass: "0bla23"
}
, notContains : {
spec : { args : "bla" },
fail: "0bla23",
pass: "la"
}
, regex : {
spec : { args: ["[a-z]",'i'] },
fail: "0",
pass: "a"
}
, notRegex : {
spec: { args: ["[a-z]",'i'] },
fail: "a",
pass: "0"
}
, len : {
spec: { args: [2,4] },
fail: ["1", "12345"],
pass: ["12", "123", "1234"],
raw: true
}
, len: {
spec: [2,4],
fail: ["1", "12345"],
pass: ["12", "123", "1234"],
raw: true
}
, isUUID : {
spec: { args: 4 },
fail: "f47ac10b-58cc-3372-a567-0e02b2c3d479",
pass: "f47ac10b-58cc-4372-a567-0e02b2c3d479"
}
, isDate : {
fail: "not a date",
pass: "2011-02-04"
}
, isAfter : {
spec: { args: "2011-11-05" },
fail: "2011-11-04",
pass: "2011-11-05"
}
, isBefore : {
spec: { args: "2011-11-05" },
fail: "2011-11-06",
pass: "2011-11-05"
}
, isIn : {
spec: { args: "abcdefghijk" },
fail: "ghik",
pass: "ghij"
}
, notIn : {
spec: { args: "abcdefghijk" },
fail: "ghij",
pass: "ghik"
}
, max : {
spec: { args: 23 },
fail: "24",
pass: "23"
}
, max : {
spec: 23,
fail: "24",
pass: "23"
}
, min : {
spec: { args: 23 },
fail: "22",
pass: "23"
}
, min : {
spec: 23,
fail: "22",
pass: "23"
}
, isArray : {
fail: 22,
pass: [22]
}
, isCreditCard : {
fail: "401288888888188f",
pass: "4012888888881881"
}
}
for (var validator in checks) {
if (checks.hasOwnProperty(validator)) {
var validatorDetails = checks[validator]
if (!validatorDetails.hasOwnProperty("raw")) {
validatorDetails.fail = [ validatorDetails.fail ]
validatorDetails.pass = [ validatorDetails.pass ]
}
//////////////////////////
// test the error cases //
//////////////////////////
for (var i = 0; i < validatorDetails.fail.length; i++) {
var failingValue = validatorDetails.fail[i]
it('correctly specifies an instance as invalid using a value of "' + failingValue + '" for the validation "' + validator + '"', function() {
var validations = {}
, message = validator + "(" + failingValue + ")"
if (validatorDetails.hasOwnProperty('spec')) {
validations[validator] = validatorDetails.spec
} else {
validations[validator] = {}
}
validations[validator].msg = message
var UserFail = this.sequelize.define('User' + Math.random(), {
name: {
type: Sequelize.STRING,
validate: validations
}
})
var failingUser = UserFail.build({ name : failingValue })
, errors = failingUser.validate()
expect(errors).not.toBeNull()
expect(errors).toEqual({ name : [message] })
})
}
////////////////////////////
// test the success cases //
////////////////////////////
for (var j = 0; j < validatorDetails.pass.length; j++) {
var succeedingValue = validatorDetails.pass[j]
it('correctly specifies an instance as valid using a value of "' + succeedingValue + '" for the validation "' + validator + '"', function() {
var validations = {}
if (validatorDetails.hasOwnProperty('spec')) {
validations[validator] = validatorDetails.spec
} else {
validations[validator] = {}
}
validations[validator].msg = validator + "(" + succeedingValue + ")"
var UserSuccess = this.sequelize.define('User' + Math.random(), {
name: {
type: Sequelize.STRING,
validate: validations
}
})
var successfulUser = UserSuccess.build({ name: succeedingValue })
expect(successfulUser.validate()).toBeNull()
})
}
}
}
it('correctly validates using custom validation methods', function() {
var User = this.sequelize.define('User' + Math.random(), {
name: {
type: Sequelize.STRING,
validate: {
customFn: function(val) {
if (val !== "2") {
throw new Error("name should equal '2'")
}
}
}
}
})
var failingUser = User.build({ name : "3" })
, errors = failingUser.validate()
expect(errors).not.toBeNull(null)
expect(errors).toEqual({ name: ["name should equal '2'"] })
var successfulUser = User.build({ name : "2" })
expect(successfulUser.validate()).toBeNull()
})
})
})
...@@ -2,13 +2,14 @@ if(typeof require === 'function') { ...@@ -2,13 +2,14 @@ if(typeof require === 'function') {
const buster = require("buster") const buster = require("buster")
, QueryChainer = require("../lib/query-chainer") , QueryChainer = require("../lib/query-chainer")
, CustomEventEmitter = require("../lib/emitters/custom-event-emitter") , CustomEventEmitter = require("../lib/emitters/custom-event-emitter")
, Helpers = require('./buster-helpers')
, dialect = Helpers.getTestDialect()
} }
buster.spec.expose() buster.spec.expose()
buster.testRunner.timeout = 1000 buster.testRunner.timeout = 1000
describe("[" + Helpers.getTestDialectTeaser() + "] QueryChainer", function() {
describe('QueryChainer', function() {
before(function() { before(function() {
this.queryChainer = new QueryChainer() this.queryChainer = new QueryChainer()
}) })
......
if(typeof require === 'function') { if(typeof require === 'function') {
const buster = require("buster") const buster = require("buster")
, Helpers = require('./buster-helpers') , Helpers = require('./buster-helpers')
, dialect = Helpers.getTestDialect()
} }
buster.spec.expose() buster.spec.expose()
describe('Sequelize', function() { describe("[" + Helpers.getTestDialectTeaser() + "] Sequelize", function() {
before(function(done) { before(function(done) {
var self = this
Helpers.initTests({ Helpers.initTests({
beforeComplete: function(sequelize) { self.sequelize = sequelize }, beforeComplete: function(sequelize) { this.sequelize = sequelize }.bind(this),
onComplete: done 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() { describe('query', function() {
it("returns the expected results as json", function() { before(function(done) {
expect(1).toEqual(1) this.User = this.sequelize.define('User', {
username: Helpers.Sequelize.STRING
})
this.insertQuery = "INSERT INTO " + this.User.tableName + " (username, createdAt, 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 }).success(function(result) {
expect(result).toBeNull()
done()
})
.error(function(err) {
console.log(err)
expect(err).not.toBeDefined()
done()
})
})
it('executes a query if only the sql is passed', function(done) {
this.sequelize.query(this.insertQuery).success(function(result) {
expect(result).not.toBeDefined()
done()
})
.error(function(err) {
console.log(err)
expect(err).not.toBeDefined()
done()
})
})
it('executes select queries correctly', function(done) {
this.sequelize.query(this.insertQuery).success(function() {
this.sequelize
.query("select * from " + this.User.tableName)
.success(function(users) {
expect(users.map(function(u){ return u.username })).toEqual(['john'])
done()
})
.error(function(err) {
console.log(err)
expect(err).not.toBeDefined()
done()
})
}.bind(this))
})
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))
})
it('uses the passed DAOFactory', function(done) {
this.sequelize.query(this.insertQuery).success(function() {
this.sequelize.query("SELECT * FROM " + this.User.tableName + ";", this.User).success(function(users) {
expect(users[0].__factory).toEqual(this.User)
done()
}.bind(this))
}.bind(this))
}) })
}) })
}) })
if(typeof require === 'function') { if(typeof require === 'function') {
const buster = require("buster") const buster = require("buster")
, Helpers = require('../buster-helpers') , Helpers = require('../buster-helpers')
, dialect = Helpers.getTestDialect()
} }
buster.spec.expose() buster.spec.expose()
describe('DAO@sqlite', function() { if (dialect === 'sqlite') {
describe('[SQLITE] DAO', function() {
before(function(done) { before(function(done) {
var self = this var self = this
...@@ -18,7 +20,7 @@ describe('DAO@sqlite', function() { ...@@ -18,7 +20,7 @@ describe('DAO@sqlite', function() {
username: DataTypes.STRING username: DataTypes.STRING
}) })
}, },
onComplete: function(sequelize) { onComplete: function() {
self.User.sync({ force: true }).success(done) self.User.sync({ force: true }).success(done)
} }
}) })
...@@ -47,4 +49,5 @@ describe('DAO@sqlite', function() { ...@@ -47,4 +49,5 @@ describe('DAO@sqlite', function() {
}) })
}) })
}) })
}) })
}
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!