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

Commit 70f3cded by Jochem Maas

Merge remote-tracking branch 'upstream/master' into feature/findAndCountSugar

2 parents 3ef54244 eaefe44c
Showing with 2491 additions and 225 deletions
...@@ -4,4 +4,4 @@ test*.js ...@@ -4,4 +4,4 @@ test*.js
.DS_STORE .DS_STORE
node_modules node_modules
npm-debug.log npm-debug.log
*~ *~
\ No newline at end of file
{
"globals": {
"jasmine": false,
"spyOn": false,
"it": false,
"console": false,
"describe": false,
"expect": false,
"beforeEach": false,
"waits": false,
"waitsFor": false,
"runs": false
},
"camelcase": true,
"curly": true,
"forin": true,
"indent": 2,
"unused": true,
"asi": true,
"evil": false,
"laxcomma": true
}
\ No newline at end of file
...@@ -19,5 +19,4 @@ env: ...@@ -19,5 +19,4 @@ env:
language: node_js language: node_js
node_js: node_js:
- 0.8 - 0.8
\ No newline at end of file
...@@ -2,6 +2,9 @@ ...@@ -2,6 +2,9 @@
The Sequelize library provides easy access to MySQL, SQLite or PostgreSQL databases by mapping database entries to objects and vice versa. To put it in a nutshell... it's an ORM (Object-Relational-Mapper). The library is written entirely in JavaScript and can be used in the Node.JS environment. The Sequelize library provides easy access to MySQL, SQLite or PostgreSQL databases by mapping database entries to objects and vice versa. To put it in a nutshell... it's an ORM (Object-Relational-Mapper). The library is written entirely in JavaScript and can be used in the Node.JS environment.
<a href="http://flattr.com/thing/1259407/Sequelize" target="_blank">
<img src="http://api.flattr.com/button/flattr-badge-large.png" alt="Flattr this" title="Flattr this" border="0" /></a>
## Important Notes ## ## Important Notes ##
### 1.6.0 ### ### 1.6.0 ###
...@@ -27,7 +30,7 @@ The Sequelize library provides easy access to MySQL, SQLite or PostgreSQL databa ...@@ -27,7 +30,7 @@ The Sequelize library provides easy access to MySQL, SQLite or PostgreSQL databa
- Associations - Associations
- Importing definitions from single files - Importing definitions from single files
## Documentation, Examples and Updates ## ## Documentation and Updates ##
You can find the documentation and announcements of updates on the [project's website](http://www.sequelizejs.com). You can find the documentation and announcements of updates on the [project's website](http://www.sequelizejs.com).
If you want to know about latest development and releases, follow me on [Twitter](http://twitter.com/sdepold). If you want to know about latest development and releases, follow me on [Twitter](http://twitter.com/sdepold).
...@@ -35,29 +38,31 @@ Also make sure to take a look at the examples in the repository. The website wil ...@@ -35,29 +38,31 @@ Also make sure to take a look at the examples in the repository. The website wil
- [Documentation](http://www.sequelizejs.com) - [Documentation](http://www.sequelizejs.com)
- [Twitter](http://twitter.com/sdepold) - [Twitter](http://twitter.com/sdepold)
- [IRC](irc://irc.freenode.net/sequelizejs) - [IRC](http://webchat.freenode.net?channels=sequelizejs)
- [Google Groups](https://groups.google.com/forum/#!forum/sequelize) - [Google Groups](https://groups.google.com/forum/#!forum/sequelize)
- [XING](https://www.xing.com/net/priec1b5cx/sequelize) (pretty much inactive, but you might want to name it on your profile) - [XING](https://www.xing.com/net/priec1b5cx/sequelize) (pretty much inactive, but you might want to name it on your profile)
## Running Examples
Instructions for running samples are located in the [example directory](https://github.com/sequelize/sequelize/tree/master/examples). Try these samples in a live sandbox environment:
<a href="https://runnable.com/sequelize" target="_blank"><img src="https://runnable.com/external/styles/assets/runnablebtn.png"></a>
## Roadmap ## Roadmap
A very basic roadmap. Chances aren't too bad, that not mentioned things are implemented as well. Don't panic :) A very basic roadmap. Chances aren't too bad, that not mentioned things are implemented as well. Don't panic :)
### 1.6.0 (ToDo)
- ~~Fix last issues with eager loading of associated data~~
- ~~Find out why Person.belongsTo(House) would add person_id to house. It should add house_id to person~~
### 1.7.0 ### 1.7.0
- Check if lodash is a proper alternative to current underscore usage. - ~~Check if lodash is a proper alternative to current underscore usage.~~
- Transactions - Transactions
- Support for update of tables without primary key - Support for update of tables without primary key
- MariaDB support - MariaDB support
- Support for update and delete calls for whole tables without previous loading of instances - ~~Support for update and delete calls for whole tables without previous loading of instances~~ Implemented in [#569](https://github.com/sequelize/sequelize/pull/569) thanks to @optiltude
- Eager loading of nested associations [#388](https://github.com/sdepold/sequelize/issues/388#issuecomment-12019099) - Eager loading of nested associations [#388](https://github.com/sdepold/sequelize/issues/388#issuecomment-12019099)
- Model#delete - Model#delete
- Validate a model before it gets saved. (Move validation of enum attribute value to validate method) - ~~Validate a model before it gets saved.~~ Implemented in [#601](https://github.com/sequelize/sequelize/pull/601), thanks to @durango
- BLOB [#99](https://github.com/sdepold/sequelize/issues/99) - Move validation of enum attribute value to validate method
- Support for foreign keys - BLOB [#99](https://github.com/sequelize/sequelize/issues/99)
- ~~Support for foreign keys~~ Implemented in [#595](https://github.com/sequelize/sequelize/pull/595), thanks to @optilude
### 1.7.x ### 1.7.x
- Complete support for non-id primary keys - Complete support for non-id primary keys
...@@ -70,7 +75,13 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl ...@@ -70,7 +75,13 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl
### 2.0.0 ### 2.0.0
- ~~save datetimes in UTC~~ - ~~save datetimes in UTC~~
- encapsulate attributes if a dao inside the attributes property + add getters and setters - encapsulate attributes if a dao inside the attributes property
- ~~add getters and setters for dao~~ Implemented in [#538](https://github.com/sequelize/sequelize/pull/538), thanks to iamjochem
- add proper error message everywhere
- refactor validate() output data structure, separating field-specific errors
from general model validator errors (i.e.
`{fields: {field1: ['field1error1']}, model: ['modelError1']}` or similar)
## Collaboration 2.0 ## ## Collaboration 2.0 ##
...@@ -234,4 +245,4 @@ for (var key in obj) { ...@@ -234,4 +245,4 @@ for (var key in obj) {
The automated tests we talk about just so much are running on The automated tests we talk about just so much are running on
[Travis public CI](http://travis-ci.org), here is its status: [Travis public CI](http://travis-ci.org), here is its status:
[![Build Status](https://secure.travis-ci.org/sdepold/sequelize.png)](http://travis-ci.org/sdepold/sequelize) [![Build Status](https://secure.travis-ci.org/sequelize/sequelize.png)](http://travis-ci.org/sequelize/sequelize)
...@@ -8,6 +8,7 @@ const path = require("path") ...@@ -8,6 +8,7 @@ const path = require("path")
, _ = Sequelize.Utils._ , _ = Sequelize.Utils._
var configPath = process.cwd() + '/config' var configPath = process.cwd() + '/config'
, environment = process.env.NODE_ENV || 'development'
, migrationsPath = process.cwd() + '/migrations' , migrationsPath = process.cwd() + '/migrations'
, packageJsonPath = __dirname + '/../package.json' , packageJsonPath = __dirname + '/../package.json'
, packageJson = JSON.parse(fs.readFileSync(packageJsonPath).toString()) , packageJson = JSON.parse(fs.readFileSync(packageJsonPath).toString())
...@@ -50,34 +51,52 @@ var createMigrationsFolder = function(force) { ...@@ -50,34 +51,52 @@ var createMigrationsFolder = function(force) {
} }
var readConfig = function() { var readConfig = function() {
var config
try { try {
var config = JSON.parse(fs.readFileSync(configFile)) config = fs.readFileSync(configFile)
, env = process.env.NODE_ENV || 'development' } catch(e) {
throw new Error('Error reading "config/config.json".')
}
if (config[env]) { try {
config = config[env] config = JSON.parse(config)
} } catch (e) {
throw new Error('Error parsing "config/config.json" as JSON.')
}
return config if (config[environment]) {
} catch(e) { config = config[environment]
throw new Error('The config.json is not available or contains invalid JSON.')
} }
return config
} }
program program
.version(packageJson.version) .version(packageJson.version)
.option('-i, --init', 'Initializes the project. Creates a config/config.json') .option('-i, --init', 'Initializes the project.')
.option('-m, --migrate', 'Runs undone migrations') .option('-e, --env <environment>', 'Specify the environment.')
.option('-m, --migrate', 'Run pending migrations.')
.option('-u, --undo', 'Undo the last migration.') .option('-u, --undo', 'Undo the last migration.')
.option('-f, --force', 'Forces the action to be done.') .option('-f, --force', 'Forces the action to be done.')
.option('-c, --create-migration [migration-name]', 'Create a new migration skeleton file.') .option('-c, --create-migration [migration-name]', 'Creates a new migration.')
.parse(process.argv) .parse(process.argv)
if(typeof program.env === 'string') {
environment = program.env
}
console.log("Using environment '" + environment + "'.")
if(program.migrate) { if(program.migrate) {
if(configFileExists) { if(configFileExists) {
var config = readConfig() var config
, options = {} , options = {}
try {
config = readConfig()
} catch(e) {
console.log(e.message)
process.exit(1)
}
_.each(config, function(value, key) { _.each(config, function(value, key) {
if(['database', 'username', 'password'].indexOf(key) == -1) { if(['database', 'username', 'password'].indexOf(key) == -1) {
options[key] = value options[key] = value
...@@ -104,7 +123,8 @@ if(program.migrate) { ...@@ -104,7 +123,8 @@ if(program.migrate) {
sequelize.migrate() sequelize.migrate()
} }
} else { } else {
throw new Error('Please add a configuration file under config/config.json. You might run "sequelize --init".') console.log('Cannot find "config/config.json". Have you run "sequelize --init"?')
process.exit(1)
} }
} else if(program.init) { } else if(program.init) {
if(!configFileExists || !!program.force) { if(!configFileExists || !!program.force) {
...@@ -129,9 +149,10 @@ if(program.migrate) { ...@@ -129,9 +149,10 @@ if(program.migrate) {
} }
}) })
console.log('Successfully created config.json') console.log('Created "config/config.json"')
} else { } else {
console.log('A config.json already exists. Run "sequelize --init --force" to overwrite it.') console.log('"config/config.json" already exists. Run "sequelize --init --force" to overwrite.')
process.exit(1)
} }
createMigrationsFolder(program.force) createMigrationsFolder(program.force)
...@@ -140,21 +161,23 @@ if(program.migrate) { ...@@ -140,21 +161,23 @@ if(program.migrate) {
var migrationName = [ var migrationName = [
moment().format('YYYYMMDDHHmmss'), moment().format('YYYYMMDDHHmmss'),
(typeof program.createMigration == 'string') ? program.createMigration : 'unnamed-migration' (typeof program.createMigration === 'string') ? program.createMigration : 'unnamed-migration'
].join('-') + '.js' ].join('-') + '.js'
var migrationContent = [ var migrationContent = [
"module.exports = {", "module.exports = {",
" up: function(migration, DataTypes) {", " up: function(migration, DataTypes, done) {",
" // add altering commands here", " // add altering commands here, calling 'done' when finished",
" done()",
" },", " },",
" down: function(migration) {", " down: function(migration, DataTypes, done) {",
" // add reverting commands here", " // add reverting commands here, calling 'done' when finished",
" done()",
" }", " }",
"}" "}"
].join('\n') ].join('\n')
fs.writeFileSync(migrationsPath + '/' + migrationName, migrationContent) fs.writeFileSync(migrationsPath + '/' + migrationName, migrationContent)
} else { } else {
console.log('Please define any params!') console.log('Try "sequelize --help" for usage information.')
} }
# v1.7.0 #
- [DEPENDENCIES] Upgraded validator for IPv6 support. [#603](https://github.com/sequelize/sequelize/pull/603). thanks to durango
- [DEPENDENCIES] replaced underscore by lodash. [#954](https://github.com/sequelize/sequelize/pull/594). thanks to durango
- [BUG] Fix string escape with postgresql on raw SQL queries. [#586](https://github.com/sequelize/sequelize/pull/586). thanks to zanamixx
- [BUG] "order by" is now after "group by". [#585](https://github.com/sequelize/sequelize/pull/585). thanks to mekanics
- [BUG] Added decimal support for min/max. [#583](https://github.com/sequelize/sequelize/pull/583). thanks to durango
- [BUG] Null dates don't break SQLite anymore. [#572](https://github.com/sequelize/sequelize/pull/572). thanks to mweibel
- [BUG] Correctly handle booleans in MySQL. [#608](https://github.com/sequelize/sequelize/pull/608). Thanks to terraflubb
- [BUG] Fixed empty where conditions in MySQL. [#619](https://github.com/sequelize/sequelize/pull/619). Thanks to terraflubb
- [BUG] Allow overriding of default columns. [#635](https://github.com/sequelize/sequelize/pull/635). Thanks to sevastos
- [BUG] Fix where params for belongsTo [#658](https://github.com/sequelize/sequelize/pull/658). Thanks to mweibel
- [BUG] Default ports are now declared in the connector manager, which means the default port for PG correctly becomes 5432. [#633](https://github.com/sequelize/sequelize/issues/633)
- [FEATURE] Validate a model before it gets saved. [#601](https://github.com/sequelize/sequelize/pull/601). thanks to durango
- [FEATURE] Schematics. [#564](https://github.com/sequelize/sequelize/pull/564). thanks to durango
- [FEATURE] Foreign key constraints. [#595](https://github.com/sequelize/sequelize/pull/595). thanks to optilude
- [FEATURE] Support for bulk insert (`<DAOFactory>.bulkCreate()`, update (`<DAOFactory>.update()`) and delete (`<DAOFactory>.destroy()`) [#569](https://github.com/sequelize/sequelize/pull/569). thanks to optilude
- [FEATURE] Add an extra `queryOptions` parameter to `DAOFactory.find` and `DAOFactory.findAll`. This allows a user to specify `{ raw: true }`, meaning that the raw result should be returned, instead of built DAOs. Usefull for queries returning large datasets, see [#611](https://github.com/sequelize/sequelize/pull/611) janmeier
- [FEATURE] Added convenient data types. [#616](https://github.com/sequelize/sequelize/pull/616). Thanks to Costent
- [FEATURE] Binary is more verbose now. [#612](https://github.com/sequelize/sequelize/pull/612). Thanks to terraflubb
- [FEATURE] Promises/A support. [#626](https://github.com/sequelize/sequelize/pull/626). Thanks to kevinbeaty
- [FEATURE] Added Getters/Setters method for DAO. [#538](https://github.com/sequelize/sequelize/pull/538). Thanks to iamjochem
- [FEATURE] Added model wide validations. [#640](https://github.com/sequelize/sequelize/pull/640). Thanks to tremby
- [FEATURE] `findOrCreate` now returns an additional flag (`created`), that is true if a model was created, and false if it was found [#648](https://github.com/sequelize/sequelize/pull/648). janmeier
- [FEATURE] Field and table comments for MySQL and PG. [#523](https://github.com/sequelize/sequelize/pull/523). MySQL by iamjochen. PG by janmeier
- [FEATURE] BigInts can now be used for autoincrement/serial columns. [#673](https://github.com/sequelize/sequelize/pull/673). thanks to sevastos
# v1.6.0 # # v1.6.0 #
- [DEPENDENCIES] upgrade mysql to alpha7. You *MUST* use this version or newer for DATETIMEs to work - [DEPENDENCIES] upgrade mysql to alpha7. You *MUST* use this version or newer for DATETIMEs to work
- [DEPENDENCIES] upgraded most dependencies. most important: mysql was upgraded to 2.0.0-alpha-3 - [DEPENDENCIES] upgraded most dependencies. most important: mysql was upgraded to 2.0.0-alpha-3
......
...@@ -5,11 +5,11 @@ ...@@ -5,11 +5,11 @@
First of all, Person is getting associated via many-to-many with other Person objects (e.g. Person.hasMany('brothers')). First of all, Person is getting associated via many-to-many with other Person objects (e.g. Person.hasMany('brothers')).
Afterwards a Person becomes associated with a 'father' and a mother using a one-to-one association created by hasOneAndBelongsTo. Afterwards a Person becomes associated with a 'father' and a mother using a one-to-one association created by hasOneAndBelongsTo.
The last association has the type many-to-one and is defined by the function hasManyAndBelongsTo. The last association has the type many-to-one and is defined by the function hasManyAndBelongsTo.
The rest of the example is about setting and getting the associated data. The rest of the example is about setting and getting the associated data.
*/ */
var Sequelize = require(__dirname + "/../../index") var Sequelize = require(__dirname + "/../../index")
, config = require(__dirname + "/../../test/config") , config = require(__dirname + "/../../spec/config/config")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false}) , sequelize = new Sequelize(config.database, config.username, config.password, {logging: false})
, Person = sequelize.define('Person', { name: Sequelize.STRING }) , Person = sequelize.define('Person', { name: Sequelize.STRING })
, Pet = sequelize.define('Pet', { name: Sequelize.STRING }) , Pet = sequelize.define('Pet', { name: Sequelize.STRING })
...@@ -36,13 +36,13 @@ sequelize.sync({force:true}).on('success', function() { ...@@ -36,13 +36,13 @@ sequelize.sync({force:true}).on('success', function() {
.add(brother.save()) .add(brother.save())
.add(sister.save()) .add(sister.save())
.add(pet.save()) .add(pet.save())
chainer.run().on('success', function() { chainer.run().on('success', function() {
person.setMother(mother).on('success', function() { person.getMother().on('success', function(mom) { person.setMother(mother).on('success', function() { person.getMother().on('success', function(mom) {
console.log('my mom: ', mom.name) console.log('my mom: ', mom.name)
})}) })})
person.setFather(father).on('success', function() { person.getFather().on('success', function(dad) { person.setFather(father).on('success', function() { person.getFather().on('success', function(dad) {
console.log('my dad: ', dad.name) console.log('my dad: ', dad.name)
})}) })})
person.setBrothers([brother]).on('success', function() { person.getBrothers().on('success', function(brothers) { person.setBrothers([brother]).on('success', function() { person.getBrothers().on('success', function(brothers) {
console.log("my brothers: " + brothers.map(function(b) { return b.name })) console.log("my brothers: " + brothers.map(function(b) { return b.name }))
......
var Sequelize = require(__dirname + "/../../index") var Sequelize = require(__dirname + "/../../index")
, config = require(__dirname + "/../../test/config") , config = require(__dirname + "/../../spec/config/config")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false}) , sequelize = new Sequelize(config.database, config.username, config.password, {logging: false})
var Person = sequelize.define('Person', { name: Sequelize.STRING }) var Person = sequelize.define('Person', { name: Sequelize.STRING })
...@@ -8,12 +8,12 @@ var Person = sequelize.define('Person', { name: Sequelize.STRING }) ...@@ -8,12 +8,12 @@ var Person = sequelize.define('Person', { name: Sequelize.STRING })
sequelize.sync({force: true}).on('success', function() { sequelize.sync({force: true}).on('success', function() {
var count = 10, var count = 10,
queries = [] queries = []
for(var i = 0; i < count; i++) for(var i = 0; i < count; i++)
chainer.add(Person.create({name: 'someone' + (i % 3)})) chainer.add(Person.create({name: 'someone' + (i % 3)}))
console.log("Begin to save " + count + " items!") console.log("Begin to save " + count + " items!")
chainer.run().on('success', function() { chainer.run().on('success', function() {
console.log("finished") console.log("finished")
Person.count().on('success', function(count) { Person.count().on('success', function(count) {
......
var Sequelize = require(__dirname + "/../../index") var Sequelize = require(__dirname + "/../../index")
, config = require(__dirname + "/../../test/config") , config = require(__dirname + "/../../spec/config/config")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false}) , sequelize = new Sequelize(config.database, config.username, config.password, {logging: false})
var Person = sequelize.define('Person', var Person = sequelize.define('Person',
{ name: Sequelize.STRING, { name: Sequelize.STRING,
age : Sequelize.INTEGER age : Sequelize.INTEGER
...@@ -12,12 +12,12 @@ var Person = sequelize.define('Person', ...@@ -12,12 +12,12 @@ var Person = sequelize.define('Person',
sequelize.sync({force: true}).on('success', function() { sequelize.sync({force: true}).on('success', function() {
var count = 10, var count = 10,
queries = [] queries = []
for(var i = 0; i < count; i++) for(var i = 0; i < count; i++)
chainer.add(Person.create({name: 'someone' + (i % 3), age : i+5})) chainer.add(Person.create({name: 'someone' + (i % 3), age : i+5}))
console.log("Begin to save " + count + " items!") console.log("Begin to save " + count + " items!")
chainer.run().on('success', function() { chainer.run().on('success', function() {
console.log("finished") console.log("finished")
Person.max('age').on('success', function(max) { Person.max('age').on('success', function(max) {
......
var Sequelize = require(__dirname + "/../../index") var Sequelize = require(__dirname + "/../../index")
, config = require("../../test/config") , config = require(__dirname + "/../../spec/config/config")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false, host: config.host}) , sequelize = new Sequelize(config.database, config.username, config.password, {logging: false, host: config.host})
, QueryChainer = Sequelize.Utils.QueryChainer , QueryChainer = Sequelize.Utils.QueryChainer
, sys = require("sys") , sys = require("sys")
...@@ -10,16 +10,16 @@ Person.sync({force: true}).on('success', function() { ...@@ -10,16 +10,16 @@ Person.sync({force: true}).on('success', function() {
var start = Date.now() var start = Date.now()
, count = 10000 , count = 10000
, done = 0 , done = 0
var createPerson = function() { var createPerson = function() {
Person.create({name: 'someone'}).on('success', function() { Person.create({name: 'someone'}).on('success', function() {
if(++done == count) { if(++done == count) {
var duration = (Date.now() - start) var duration = (Date.now() - start)
console.log("\nFinished creation of " + count + " people. Took: " + duration + "ms (avg: " + (duration/count) + "ms)") console.log("\nFinished creation of " + count + " people. Took: " + duration + "ms (avg: " + (duration/count) + "ms)")
start = Date.now() start = Date.now()
console.log("Will now read them from the database:") console.log("Will now read them from the database:")
Person.findAll().on('success', function(people) { Person.findAll().on('success', function(people) {
console.log("Reading " + people.length + " items took: " + (Date.now() - start) + "ms") console.log("Reading " + people.length + " items took: " + (Date.now() - start) + "ms")
}) })
...@@ -35,7 +35,7 @@ Person.sync({force: true}).on('success', function() { ...@@ -35,7 +35,7 @@ Person.sync({force: true}).on('success', function() {
for(var i = 0; i < count; i++) { for(var i = 0; i < count; i++) {
createPerson() createPerson()
} }
}).on('failure', function(err) { }).on('failure', function(err) {
console.log(err) console.log(err)
}) })
\ No newline at end of file
var fs = require("fs") /*
, Sequelize = require("sequelize") Title: Default values
, sequelize = new Sequelize('sequelize_test', 'root', null, {logging: false})
, Image = sequelize.define('Image', { data: Sequelize.TEXT })
Image.sync({force: true}).on('success', function() { This example demonstrates the use of default values for defined model fields. Instead of just specifying the datatype,
console.log("reading image") you have to pass a hash with a type and a default. You also might want to specify either an attribute can be null or not!
var image = fs.readFileSync(__dirname + '/source.png').toString("base64") */
console.log("done\n")
var Sequelize = require(__dirname + "/../../index")
console.log("creating database entry") , config = require(__dirname + "/../../spec/config/config")
Image.create({data: image}).on('success', function(img) { , sequelize = new Sequelize(config.database, config.username, config.password, {logging: false})
console.log("done\n")
var User = sequelize.define('User', {
console.log("writing file") name: { type: Sequelize.STRING, allowNull: false},
fs.writeFileSync(__dirname + '/target.png', img.data, "base64") isAdmin: { type: Sequelize.BOOLEAN, allowNull: false, defaultValue: false }
console.log("done\n") })
, user = User.build({ name: 'Someone' })
console.log("you might open the file ./target.png")
sequelize.sync({force: true}).on('success', function() {
user.save().on('success', function(user) {
console.log("user.isAdmin should be the default value (false): ", user.isAdmin)
user.updateAttributes({ isAdmin: true }).on('success', function(user) {
console.log("user.isAdmin was overwritten to true: " + user.isAdmin)
})
}) })
}).on('failure', function(err) {
console.log(err)
}) })
\ No newline at end of file
/* /*
Title: Defining class and instance methods Title: Defining class and instance methods
This example shows the usage of the classMethods and instanceMethods option for Models. This example shows the usage of the classMethods and instanceMethods option for Models.
*/ */
var Sequelize = require(__dirname + "/../../index") var Sequelize = require(__dirname + "/../../index")
, config = require(__dirname + "/../../test/config") , config = require(__dirname + "/../../spec/config/config")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false}) , sequelize = new Sequelize(config.database, config.username, config.password, {logging: false})
// model definition // model definition
var Task = sequelize.define("Task", { var Task = sequelize.define("Task", {
name: Sequelize.STRING, name: Sequelize.STRING,
deadline: Sequelize.DATE, deadline: Sequelize.DATE,
...@@ -51,7 +51,7 @@ Task.sync({force: true}).on('success', function() { ...@@ -51,7 +51,7 @@ Task.sync({force: true}).on('success', function() {
console.log("should be false: " + task1.passedDeadline()) console.log("should be false: " + task1.passedDeadline())
console.log("should be true: " + task2.passedDeadline()) console.log("should be true: " + task2.passedDeadline())
console.log("should be 10: " + task1.importance) console.log("should be 10: " + task1.importance)
Task.setImportance(30, function() { Task.setImportance(30, function() {
Task.findAll().on('success', function(tasks) { Task.findAll().on('success', function(tasks) {
tasks.forEach(function(task) { tasks.forEach(function(task) {
......
var Sequelize = require(__dirname + "/../../index") var Sequelize = require(__dirname + "/../../index")
, config = require(__dirname + "/../../test/config") , config = require(__dirname + "/../../spec/config/config")
, sequelize = new Sequelize(config.database, config.username, config.password, { , sequelize = new Sequelize(config.database, config.username, config.password, {
// use other database server or port // use other database server or port
host: 'my.srv.tld', host: 'my.srv.tld',
port: 12345, port: 12345,
// disable logging // disable logging
logging: false logging: false
}) })
......
var Sequelize = require(__dirname + "/../../index") var Sequelize = require(__dirname + "/../../index")
, config = require(__dirname + "/../../test/config") , config = require(__dirname + "/../../spec/config/config")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false}) , sequelize = new Sequelize(config.database, config.username, config.password, {logging: false})
, Project = sequelize.import(__dirname + "/Project") , Project = sequelize.import(__dirname + "/Project")
, Task = sequelize.import(__dirname + "/Task") , Task = sequelize.import(__dirname + "/Task")
Project.hasMany(Task) Project.hasMany(Task)
Task.belongsTo(Project) Task.belongsTo(Project)
sequelize.sync({force: true}).on('success', function() { sequelize.sync({force: true}).on('success', function() {
Project Project
.create({ name: 'Sequelize', description: 'A nice MySQL ORM for NodeJS' }) .create({ name: 'Sequelize', description: 'A nice MySQL ORM for NodeJS' })
......
var Utils = require("./../utils") var Utils = require("./../utils")
, DataTypes = require('./../data-types') , DataTypes = require('./../data-types')
, Helpers = require('./helpers')
module.exports = (function() { module.exports = (function() {
var BelongsTo = function(srcDAO, targetDAO, options) { var BelongsTo = function(srcDAO, targetDAO, options) {
...@@ -23,7 +24,8 @@ module.exports = (function() { ...@@ -23,7 +24,8 @@ module.exports = (function() {
var newAttributes = {} var newAttributes = {}
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: this.options.keyType || DataTypes.INTEGER }
Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.target, this.source, this.options)
Utils._.defaults(this.source.rawAttributes, newAttributes) Utils._.defaults(this.source.rawAttributes, newAttributes)
// Sync attributes to DAO proto each time a new assoc is added // Sync attributes to DAO proto each time a new assoc is added
...@@ -40,8 +42,10 @@ module.exports = (function() { ...@@ -40,8 +42,10 @@ module.exports = (function() {
var id = this[self.identifier] var id = this[self.identifier]
if (!Utils._.isUndefined(params)) { if (!Utils._.isUndefined(params)) {
if (!Utils._.isUndefined(params.attributes)) { if (!Utils._.isUndefined(params.where)) {
params = Utils._.extend({where: {id:id}}, params) params.where = Utils._.extend({id:id}, params.where)
} else {
params.where = {id: id}
} }
} else { } else {
params = id params = id
......
var Utils = require("./../utils") var Utils = require("./../utils")
, DataTypes = require('./../data-types') , DataTypes = require('./../data-types')
, Helpers = require('./helpers')
var HasManySingleLinked = require("./has-many-single-linked") var HasManySingleLinked = require("./has-many-single-linked")
, HasManyMultiLinked = require("./has-many-double-linked") , HasManyMultiLinked = require("./has-many-double-linked")
...@@ -50,8 +51,9 @@ module.exports = (function() { ...@@ -50,8 +51,9 @@ module.exports = (function() {
// define a new model, which connects the models // define a new model, which connects the models
var combinedTableAttributes = {} var combinedTableAttributes = {}
combinedTableAttributes[this.identifier] = {type:DataTypes.INTEGER, primaryKey: true} var keyType = this.options.keyType || DataTypes.INTEGER
combinedTableAttributes[this.foreignIdentifier] = {type:DataTypes.INTEGER, primaryKey: true} combinedTableAttributes[this.identifier] = {type: keyType, primaryKey: true}
combinedTableAttributes[this.foreignIdentifier] = {type: keyType, 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)
...@@ -64,7 +66,8 @@ module.exports = (function() { ...@@ -64,7 +66,8 @@ module.exports = (function() {
} }
} else { } else {
var newAttributes = {} var newAttributes = {}
newAttributes[this.identifier] = { type: DataTypes.INTEGER } newAttributes[this.identifier] = { type: this.options.keyType || DataTypes.INTEGER }
Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.source, this.target, this.options)
Utils._.defaults(this.target.rawAttributes, newAttributes) Utils._.defaults(this.target.rawAttributes, newAttributes)
} }
......
var Utils = require("./../utils") var Utils = require("./../utils")
, DataTypes = require('./../data-types') , DataTypes = require('./../data-types')
, Helpers = require("./helpers")
module.exports = (function() { module.exports = (function() {
var HasOne = function(srcDAO, targetDAO, options) { var HasOne = function(srcDAO, targetDAO, options) {
...@@ -28,7 +29,8 @@ module.exports = (function() { ...@@ -28,7 +29,8 @@ module.exports = (function() {
var newAttributes = {} var newAttributes = {}
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: this.options.keyType || DataTypes.INTEGER }
Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.source, this.target, this.options)
Utils._.defaults(this.target.rawAttributes, newAttributes) Utils._.defaults(this.target.rawAttributes, newAttributes)
// Sync attributes to DAO proto each time a new assoc is added // Sync attributes to DAO proto each time a new assoc is added
......
var Utils = require("./../utils")
module.exports = {
addForeignKeyConstraints: function(newAttribute, source, target, options) {
// FK constraints are opt-in: users must either rset `foreignKeyConstraints`
// on the association, or request an `onDelete` or `onUpdate` behaviour
if(options.foreignKeyConstraint || options.onDelete || options.onUpdate) {
// Find primary keys: composite keys not supported with this approach
var primaryKeys = Utils._.filter(Utils._.keys(source.rawAttributes), function(key) {
return source.rawAttributes[key].primaryKey
})
if(primaryKeys.length == 1) {
newAttribute.references = source.tableName,
newAttribute.referencesKey = primaryKeys[0]
newAttribute.onDelete = options.onDelete,
newAttribute.onUpdate = options.onUpdate
}
}
}
}
var Toposort = require('toposort-class')
module.exports = (function() { module.exports = (function() {
var DAOFactoryManager = function(sequelize) { var DAOFactoryManager = function(sequelize) {
this.daos = [] this.daos = []
...@@ -31,5 +33,34 @@ module.exports = (function() { ...@@ -31,5 +33,34 @@ module.exports = (function() {
return this.daos return this.daos
}) })
/**
* Iterate over DAOs in an order suitable for e.g. creating tables. Will
* take foreign key constraints into account so that dependencies are visited
* before dependents.
*/
DAOFactoryManager.prototype.forEachDAO = function(iterator) {
var daos = {}
, sorter = new Toposort()
this.daos.forEach(function(dao) {
daos[dao.tableName] = dao
var deps = []
for(var attrName in dao.rawAttributes) {
if(dao.rawAttributes.hasOwnProperty(attrName)) {
if(dao.rawAttributes[attrName].references) {
deps.push(dao.rawAttributes[attrName].references)
}
}
}
sorter.add(dao.tableName, deps)
})
sorter.sort().reverse().forEach(function(name) {
iterator(daos[name])
})
}
return DAOFactoryManager return DAOFactoryManager
})() })()
var Validator = require("validator")
, Utils = require("./utils")
var DaoValidator = module.exports = function(model) {
this.model = model
}
DaoValidator.prototype.validate = function() {
var errors = {}
errors = Utils._.extend(errors, validateAttributes.call(this))
errors = Utils._.extend(errors, validateModel.call(this))
return errors
}
// private
var validateModel = function() {
var errors = {}
// for each model validator for this DAO
Utils._.each(this.model.__options.validate, function(validator, validatorType) {
try {
validator.apply(this.model)
} catch (err) {
errors[validatorType] = [err.message] // TODO: data structure needs to change for 2.0
}
}.bind(this))
return errors
}
var validateAttributes = function() {
var errors = {}
// for each field and value
Utils._.each(this.model.values, function(value, field) {
var rawAttribute = this.model.rawAttributes[field]
, hasAllowedNull = ((rawAttribute.allowNull === true) && ((value === null) || (value === undefined)))
if (this.model.validators.hasOwnProperty(field) && !hasAllowedNull) {
errors = Utils._.merge(errors, validateAttribute.call(this, value, field))
}
}.bind(this)) // for each field
return errors
}
var validateAttribute = function(value, field) {
var errors = {}
// for each validator
Utils._.each(this.model.validators[field], function(details, validatorType) {
var validator = prepareValidationOfAttribute.call(this, value, details, validatorType)
try {
validator.fn.apply(null, validator.args)
} catch (err) {
var msg = err.message
// if we didn't provide a custom error message then augment the default one returned by the validator
if (!validator.msg && !validator.isCustom) {
msg += ": " + field
}
// each field can have multiple validation errors stored against it
errors[field] = errors[field] || []
errors[field].push(msg)
}
}.bind(this)) // for each validator for this field
return errors
}
var prepareValidationOfAttribute = function(value, details, validatorType) {
var isCustomValidator = false // if true then it's a custom validation method
, validatorFunction = null // the validation function to call
, validatorArgs = [] // extra arguments to pass to validation function
, errorMessage = "" // the error message to return if validation fails
if (typeof details === 'function') {
// it is a custom validator function?
isCustomValidator = true
validatorFunction = Utils._.bind(details, this.model, value)
} else {
// it is a validator module function?
// extract extra arguments for the validator
validatorArgs = details.hasOwnProperty("args") ? details.args : details
if (!Array.isArray(validatorArgs)) {
validatorArgs = [validatorArgs]
}
// extract the error msg
errorMessage = details.hasOwnProperty("msg") ? details.msg : false
// check method exists
var validator = Validator.check(value, errorMessage)
// check if Validator knows that kind of validation test
if (!Utils._.isFunction(validator[validatorType])) {
throw new Error("Invalid validator function: " + validatorType)
}
// bind to validator obj
validatorFunction = Utils._.bind(validator[validatorType], validator)
}
return {
fn: validatorFunction,
msg: errorMessage,
args: validatorArgs,
isCustom: isCustomValidator
}
}
var STRING = function(length, binary) {
if (this instanceof STRING) {
this._binary = !!binary;
if (typeof length === 'number') {
this._length = length;
} else {
this._length = 255;
}
} else {
return new STRING(length, binary);
}
};
STRING.prototype = {
get BINARY() {
this._binary = true;
return this;
},
get type() {
return this.toString();
},
toString: function() {
return 'VARCHAR(' + this._length + ')' + ((this._binary) ? ' BINARY' : '');
}
};
Object.defineProperty(STRING, 'BINARY', {
get: function() {
return new STRING(undefined, true);
}
});
var INTEGER = function() {
return INTEGER.prototype.construct.apply(this, [INTEGER].concat(Array.prototype.slice.apply(arguments)));
};
var BIGINT = function() {
return BIGINT.prototype.construct.apply(this, [BIGINT].concat(Array.prototype.slice.apply(arguments)));
};
var FLOAT = function() {
return FLOAT.prototype.construct.apply(this, [FLOAT].concat(Array.prototype.slice.apply(arguments)));
};
FLOAT._type = FLOAT;
FLOAT._typeName = 'FLOAT';
INTEGER._type = INTEGER;
INTEGER._typeName = 'INTEGER';
BIGINT._type = BIGINT;
BIGINT._typeName = 'BIGINT';
STRING._type = STRING;
STRING._typeName = 'VARCHAR';
STRING.toString = INTEGER.toString = FLOAT.toString = BIGINT.toString = function() {
return new this._type().toString();
};
FLOAT.prototype = BIGINT.prototype = INTEGER.prototype = {
construct: function(RealType, length, decimals, unsigned, zerofill) {
if (this instanceof RealType) {
this._typeName = RealType._typeName;
this._unsigned = !!unsigned;
this._zerofill = !!zerofill;
if (typeof length === 'number') {
this._length = length;
}
if (typeof decimals === 'number') {
this._decimals = decimals;
}
} else {
return new RealType(length, decimals, unsigned, zerofill);
}
},
get type() {
return this.toString();
},
get UNSIGNED() {
this._unsigned = true;
return this;
},
get ZEROFILL() {
this._zerofill = true;
return this;
},
toString: function() {
var result = this._typeName;
if (this._length) {
result += '(' + this._length;
if (typeof this._decimals === 'number') {
result += ',' + this._decimals;
}
result += ')';
}
if (this._unsigned) {
result += ' UNSIGNED';
}
if (this._zerofill) {
result += ' ZEROFILL';
}
return result;
}
};
var unsignedDesc = {
get: function() {
return new this._type(undefined, undefined, true);
}
};
var zerofillDesc = {
get: function() {
return new this._type(undefined, undefined, undefined, true);
}
};
var typeDesc = {
get: function() {
return new this._type().toString();
}
};
Object.defineProperty(STRING, 'type', typeDesc);
Object.defineProperty(INTEGER, 'type', typeDesc);
Object.defineProperty(BIGINT, 'type', typeDesc);
Object.defineProperty(FLOAT, 'type', typeDesc);
Object.defineProperty(INTEGER, 'UNSIGNED', unsignedDesc);
Object.defineProperty(BIGINT, 'UNSIGNED', unsignedDesc);
Object.defineProperty(FLOAT, 'UNSIGNED', unsignedDesc);
Object.defineProperty(INTEGER, 'ZEROFILL', zerofillDesc);
Object.defineProperty(BIGINT, 'ZEROFILL', zerofillDesc);
Object.defineProperty(FLOAT, 'ZEROFILL', zerofillDesc);
module.exports = { module.exports = {
STRING: 'VARCHAR(255)', STRING: STRING,
TEXT: 'TEXT', TEXT: 'TEXT',
INTEGER: 'INTEGER', INTEGER: INTEGER,
BIGINT: 'BIGINT', BIGINT: BIGINT,
DATE: 'DATETIME', DATE: 'DATETIME',
BOOLEAN: 'TINYINT(1)', BOOLEAN: 'TINYINT(1)',
FLOAT: 'FLOAT', FLOAT: FLOAT,
NOW: 'NOW', NOW: 'NOW',
get ENUM() { get ENUM() {
...@@ -32,5 +172,19 @@ module.exports = { ...@@ -32,5 +172,19 @@ module.exports = {
return result return result
}, },
ARRAY: function(type) { return type + '[]' }
ARRAY: function(type) { return type + '[]' },
get HSTORE() {
var result = function() {
return {
type: 'HSTORE'
}
}
result.type = 'HSTORE'
result.toString = result.valueOf = function() { return 'TEXT' }
return result
}
} }
...@@ -12,6 +12,7 @@ module.exports = (function() { ...@@ -12,6 +12,7 @@ module.exports = (function() {
this.sequelize = sequelize this.sequelize = sequelize
this.client = null this.client = null
this.config = config || {} this.config = config || {}
this.config.port = this.config.port || 3306
this.disconnectTimeoutId = null this.disconnectTimeoutId = null
this.queue = [] this.queue = []
this.activeQueue = [] this.activeQueue = []
...@@ -19,7 +20,9 @@ module.exports = (function() { ...@@ -19,7 +20,9 @@ module.exports = (function() {
this.poolCfg = Utils._.defaults(this.config.pool, { this.poolCfg = Utils._.defaults(this.config.pool, {
maxConnections: 10, maxConnections: 10,
minConnections: 0, minConnections: 0,
maxIdleTime: 1000 maxIdleTime: 1000,
handleDisconnects: false,
validate: validateConnection
}); });
this.pendingQueries = 0; this.pendingQueries = 0;
this.useReplicaton = !!config.replication; this.useReplicaton = !!config.replication;
...@@ -83,6 +86,7 @@ module.exports = (function() { ...@@ -83,6 +86,7 @@ module.exports = (function() {
destroy: function(client) { destroy: function(client) {
disconnect.call(self, client) disconnect.call(self, client)
}, },
validate: self.poolCfg.validate,
max: self.poolCfg.maxConnections, max: self.poolCfg.maxConnections,
min: self.poolCfg.minConnections, min: self.poolCfg.minConnections,
idleTimeoutMillis: self.poolCfg.maxIdleTime idleTimeoutMillis: self.poolCfg.maxIdleTime
...@@ -98,6 +102,7 @@ module.exports = (function() { ...@@ -98,6 +102,7 @@ module.exports = (function() {
destroy: function(client) { destroy: function(client) {
disconnect.call(self, client) disconnect.call(self, client)
}, },
validate: self.poolCfg.validate,
max: self.poolCfg.maxConnections, max: self.poolCfg.maxConnections,
min: self.poolCfg.minConnections, min: self.poolCfg.minConnections,
idleTimeoutMillis: self.poolCfg.maxIdleTime idleTimeoutMillis: self.poolCfg.maxIdleTime
...@@ -115,6 +120,7 @@ module.exports = (function() { ...@@ -115,6 +120,7 @@ module.exports = (function() {
}, },
max: self.poolCfg.maxConnections, max: self.poolCfg.maxConnections,
min: self.poolCfg.minConnections, min: self.poolCfg.minConnections,
validate: self.poolCfg.validate,
idleTimeoutMillis: self.poolCfg.maxIdleTime idleTimeoutMillis: self.poolCfg.maxIdleTime
}) })
} }
...@@ -247,10 +253,25 @@ module.exports = (function() { ...@@ -247,10 +253,25 @@ module.exports = (function() {
connection.query("SET time_zone = '+0:00'"); connection.query("SET time_zone = '+0:00'");
// client.setMaxListeners(self.maxConcurrentQueries) // client.setMaxListeners(self.maxConcurrentQueries)
this.isConnecting = false this.isConnecting = false
if (config.pool.handleDisconnects) {
handleDisconnect(this.pool, connection)
}
done(null, connection) done(null, connection)
} }
var handleDisconnect = function(pool, client) {
client.on('error', function(err) {
if (err.code !== 'PROTOCOL_CONNECTION_LOST') {
throw err
}
pool.destroy(client)
})
}
var validateConnection = function(client) {
return client && client.state != 'disconnected'
}
var enqueue = function(queueItem, options) { var enqueue = function(queueItem, options) {
options = options || {} options = options || {}
if (this.activeQueue.length < this.maxConcurrentQueries) { if (this.activeQueue.length < this.maxConcurrentQueries) {
...@@ -336,4 +357,4 @@ module.exports = (function() { ...@@ -336,4 +357,4 @@ module.exports = (function() {
} }
return ConnectorManager return ConnectorManager
})() })()
\ No newline at end of file
...@@ -4,10 +4,11 @@ var Query = require("./query") ...@@ -4,10 +4,11 @@ var Query = require("./query")
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 && (this.config.poolCfg.maxConnections > 0)) this.config.port = this.config.port || 5432
this.pg = this.config.native ? require('pg').native : require('pg') 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) {
......
var Utils = require("../../utils") var Utils = require("../../utils")
, AbstractQuery = require('../abstract/query') , AbstractQuery = require('../abstract/query')
, DataTypes = require('../../data-types')
module.exports = (function() { module.exports = (function() {
var Query = function(client, sequelize, callee, options) { var Query = function(client, sequelize, callee, options) {
...@@ -55,6 +56,7 @@ module.exports = (function() { ...@@ -55,6 +56,7 @@ module.exports = (function() {
var onSuccess = function(rows) { var onSuccess = function(rows) {
var results = [] var results = []
, self = this
, isTableNameQuery = (this.sql.indexOf('SELECT table_name FROM information_schema.tables') === 0) , 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) , isRelNameQuery = (this.sql.indexOf('SELECT relname FROM pg_class WHERE oid IN') === 0)
...@@ -73,14 +75,15 @@ module.exports = (function() { ...@@ -73,14 +75,15 @@ module.exports = (function() {
} }
if (this.send('isSelectQuery')) { if (this.send('isSelectQuery')) {
if (this.sql.toLowerCase().indexOf('select column_name') === 0) { if (this.sql.toLowerCase().indexOf('select c.column_name') === 0) {
var result = {} var result = {}
rows.forEach(function(_result) { rows.forEach(function(_result) {
result[_result.Field] = { result[_result.Field] = {
type: _result.Type.toUpperCase(), type: _result.Type.toUpperCase(),
allowNull: (_result.Null === 'YES'), allowNull: (_result.Null === 'YES'),
defaultValue: _result.Default defaultValue: _result.Default,
special: (!!_result.special ? self.sequelize.queryInterface.QueryGenerator.fromArray(_result.special) : [])
} }
if (result[_result.Field].type === 'BOOLEAN') { if (result[_result.Field].type === 'BOOLEAN') {
...@@ -95,29 +98,58 @@ module.exports = (function() { ...@@ -95,29 +98,58 @@ module.exports = (function() {
result[_result.Field].defaultValue = result[_result.Field].defaultValue.replace(/'/g, "") result[_result.Field].defaultValue = result[_result.Field].defaultValue.replace(/'/g, "")
if (result[_result.Field].defaultValue.indexOf('::') > -1) { if (result[_result.Field].defaultValue.indexOf('::') > -1) {
result[_result.Field].defaultValue = result[_result.Field].defaultValue.split('::')[0] var split = result[_result.Field].defaultValue.split('::');
if (split[1].toLowerCase() !== "regclass)") {
result[_result.Field].defaultValue = split[0]
}
} }
} }
}) })
this.emit('success', result) this.emit('success', result)
} else { } else {
// Postgres will treat tables as case-insensitive, so fix the case
// of the returned values to match attributes
if(this.sequelize.options.quoteIdentifiers == false) {
var attrsMap = Utils._.reduce(this.callee.attributes, function(m, v, k) { m[k.toLowerCase()] = k; return m}, {})
rows.forEach(function(row) {
Utils._.keys(row).forEach(function(key) {
var targetAttr = attrsMap[key]
if(targetAttr != key) {
row[targetAttr] = row[key]
delete row[key]
}
})
})
}
this.emit('success', this.send('handleSelectQuery', rows)) this.emit('success', this.send('handleSelectQuery', rows))
} }
} else if (this.send('isShowOrDescribeQuery')) { } else if (this.send('isShowOrDescribeQuery')) {
this.emit('success', results) this.emit('success', results)
} else if (this.send('isInsertQuery')) { } else if (this.send('isInsertQuery')) {
for (var key in rows[0]) { if(this.callee !== null) { // may happen for bulk inserts
if (rows[0].hasOwnProperty(key)) { for (var key in rows[0]) {
this.callee[key] = rows[0][key] if (rows[0].hasOwnProperty(key)) {
var record = rows[0][key]
if (!!this.callee.daoFactory && !!this.callee.daoFactory.rawAttributes && !!this.callee.daoFactory.rawAttributes[key] && !!this.callee.daoFactory.rawAttributes[key].type && !!this.callee.daoFactory.rawAttributes[key].type.type && this.callee.daoFactory.rawAttributes[key].type.type === DataTypes.HSTORE.type) {
record = this.callee.daoFactory.daoFactoryManager.sequelize.queryInterface.QueryGenerator.toHstore(record)
}
this.callee[key] = record
}
} }
} }
this.emit('success', this.callee) this.emit('success', this.callee)
} else if (this.send('isUpdateQuery')) { } else if (this.send('isUpdateQuery')) {
for (var key in rows[0]) { if(this.callee !== null) { // may happen for bulk updates
if (rows[0].hasOwnProperty(key)) { for (var key in rows[0]) {
this.callee[key] = rows[0][key] if (rows[0].hasOwnProperty(key)) {
var record = rows[0][key]
if (!!this.callee.daoFactory && !!this.callee.daoFactory.rawAttributes && !!this.callee.daoFactory.rawAttributes[key] && !!this.callee.daoFactory.rawAttributes[key].type && !!this.callee.daoFactory.rawAttributes[key].type.type && this.callee.daoFactory.rawAttributes[key].type.type === DataTypes.HSTORE.type) {
record = this.callee.daoFactory.daoFactoryManager.sequelize.queryInterface.QueryGenerator.toHstore(record)
}
this.callee[key] = record
}
} }
} }
......
module.exports = (function() { module.exports = (function() {
var QueryGenerator = { var QueryGenerator = {
addSchema: function(opts) {
throwMethodUndefined('addSchema')
},
/* /*
Returns a query for creating a table. Returns a query for creating a table.
Parameters: Parameters:
...@@ -115,6 +119,14 @@ module.exports = (function() { ...@@ -115,6 +119,14 @@ module.exports = (function() {
}, },
/* /*
Returns an insert into command for multiple values.
Parameters: table name + list of hashes of attribute-value-pairs.
*/
bulkInsertQuery: function(tableName, attrValueHashes) {
throwMethodUndefined('bulkInsertQuery')
},
/*
Returns an update query. Returns an update query.
Parameters: Parameters:
- tableName -> Name of the table - tableName -> Name of the table
...@@ -138,12 +150,30 @@ module.exports = (function() { ...@@ -138,12 +150,30 @@ module.exports = (function() {
If you use a string, you have to escape it on your own. If you use a string, you have to escape it on your own.
Options: Options:
- limit -> Maximaum count of lines to delete - limit -> Maximaum count of lines to delete
- truncate -> boolean - whether to use an 'optimized' mechanism (i.e. TRUNCATE) if available,
note that this should not be the default behaviour because TRUNCATE does not
always play nicely (e.g. InnoDB tables with FK constraints)
(@see http://dev.mysql.com/doc/refman/5.6/en/truncate-table.html).
Note that truncate must ignore limit and where
*/ */
deleteQuery: function(tableName, where, options) { deleteQuery: function(tableName, where, options) {
throwMethodUndefined('deleteQuery') throwMethodUndefined('deleteQuery')
}, },
/* /*
Returns a bulk deletion query.
Parameters:
- tableName -> Name of the table
- where -> A hash with conditions (e.g. {name: 'foo'})
OR an ID as integer
OR a string with conditions (e.g. 'name="foo"').
If you use a string, you have to escape it on your own.
*/
bulkDeleteQuery: function(tableName, where, options) {
throwMethodUndefined('bulkDeleteQuery')
},
/*
Returns an update query. Returns an update query.
Parameters: Parameters:
- tableName -> Name of the table - tableName -> Name of the table
...@@ -225,7 +255,43 @@ module.exports = (function() { ...@@ -225,7 +255,43 @@ module.exports = (function() {
*/ */
findAutoIncrementField: function(factory) { findAutoIncrementField: function(factory) {
throwMethodUndefined('findAutoIncrementField') throwMethodUndefined('findAutoIncrementField')
},
/*
Globally enable foreign key constraints
*/
enableForeignKeyConstraintsQuery: function() {
throwMethodUndefined('enableForeignKeyConstraintsQuery')
},
/*
Globally disable foreign key constraints
*/
disableForeignKeyConstraintsQuery: function() {
throwMethodUndefined('disableForeignKeyConstraintsQuery')
},
/*
Escape an identifier (e.g. a table or attribute name)
*/
quoteIdentifier: function(identifier, force) {
throwMethodUndefined('quoteIdentifier')
},
/*
Split an identifier into .-separated tokens and quote each part
*/
quoteIdentifiers: function(identifiers, force) {
throwMethodUndefined('quoteIdentifiers')
},
/*
Escape a value (e.g. a string, number or date)
*/
escape: function(value) {
throwMethodUndefined('quoteIdentifier')
} }
} }
var throwMethodUndefined = function(methodName) { var throwMethodUndefined = function(methodName) {
......
...@@ -5,7 +5,13 @@ var Utils = require("../../utils") ...@@ -5,7 +5,13 @@ var Utils = require("../../utils")
module.exports = (function() { module.exports = (function() {
var ConnectorManager = function(sequelize) { var ConnectorManager = function(sequelize) {
this.sequelize = sequelize this.sequelize = sequelize
this.database = new sqlite3.Database(sequelize.options.storage || ':memory:') this.database = db = new sqlite3.Database(sequelize.options.storage || ':memory:', function(err) {
if(!err && sequelize.options.foreignKeys !== false) {
// Make it possible to define and use foreign key constraints unless
// explicitly disallowed. It's still opt-in per relation
db.run('PRAGMA FOREIGN_KEYS=ON')
}
})
} }
Utils._.extend(ConnectorManager.prototype, require("../connector-manager").prototype) Utils._.extend(ConnectorManager.prototype, require("../connector-manager").prototype)
......
...@@ -91,7 +91,10 @@ module.exports = (function() { ...@@ -91,7 +91,10 @@ module.exports = (function() {
results = results.map(function(result) { results = results.map(function(result) {
for (var name in result) { for (var name in result) {
if (result.hasOwnProperty(name) && (metaData.columnTypes[name] === 'DATETIME')) { if (result.hasOwnProperty(name) && (metaData.columnTypes[name] === 'DATETIME')) {
result[name] = new Date(result[name]+'Z'); // Z means UTC var val = result[name];
if(val !== null) {
result[name] = new Date(val+'Z'); // Z means UTC
}
} }
} }
return result return result
......
var util = require("util") var util = require("util")
, EventEmitter = require("events").EventEmitter , EventEmitter = require("events").EventEmitter
, Promise = require("promise")
, proxyEventKeys = ['success', 'error', 'sql']
var bindToProcess = function(fct) {
if (fct) {
if(process.domain) {
return process.domain.bind(fct);
}
}
return fct;
};
module.exports = (function() { module.exports = (function() {
var CustomEventEmitter = function(fct) { var CustomEventEmitter = function(fct) {
this.fct = fct this.fct = bindToProcess(fct);
} }
util.inherits(CustomEventEmitter, EventEmitter) util.inherits(CustomEventEmitter, EventEmitter)
...@@ -13,14 +26,14 @@ module.exports = (function() { ...@@ -13,14 +26,14 @@ module.exports = (function() {
this.fct.call(this, this) this.fct.call(this, this)
} }
}.bind(this)) }.bind(this))
return this return this
} }
CustomEventEmitter.prototype.success = CustomEventEmitter.prototype.success =
CustomEventEmitter.prototype.ok = CustomEventEmitter.prototype.ok =
function(fct) { function(fct) {
this.on('success', fct) this.on('success', bindToProcess(fct))
return this return this
} }
...@@ -28,18 +41,39 @@ module.exports = (function() { ...@@ -28,18 +41,39 @@ module.exports = (function() {
CustomEventEmitter.prototype.fail = CustomEventEmitter.prototype.fail =
CustomEventEmitter.prototype.error = CustomEventEmitter.prototype.error =
function(fct) { function(fct) {
this.on('error', fct) this.on('error', bindToProcess(fct))
return this; return this;
} }
CustomEventEmitter.prototype.done = CustomEventEmitter.prototype.done =
CustomEventEmitter.prototype.complete = CustomEventEmitter.prototype.complete =
function(fct) { function(fct) {
fct = bindToProcess(fct);
this.on('error', function(err) { fct(err, null) }) this.on('error', function(err) { fct(err, null) })
.on('success', function(result) { fct(null, result) }) .on('success', function(result) { fct(null, result) })
return this return this
} }
CustomEventEmitter.prototype.proxy = function(emitter) {
proxyEventKeys.forEach(function (eventKey) {
this.on(eventKey, function (result) {
emitter.emit(eventKey, result)
})
}.bind(this))
}
CustomEventEmitter.prototype.then =
function (onFulfilled, onRejected) {
var self = this
onFulfilled = bindToProcess(onFulfilled)
onRejected = bindToProcess(onRejected)
return new Promise(function (resolve, reject) {
self.on('error', reject)
.on('success', resolve);
}).then(onFulfilled, onRejected)
}
return CustomEventEmitter; return CustomEventEmitter;
})() })()
...@@ -12,7 +12,8 @@ module.exports = (function() { ...@@ -12,7 +12,8 @@ module.exports = (function() {
path: __dirname + '/../migrations', path: __dirname + '/../migrations',
from: null, from: null,
to: null, to: null,
logging: console.log logging: console.log,
filesFilter: /\.js$/
}, options || {}) }, options || {})
if (this.options.logging === true) { if (this.options.logging === true) {
...@@ -52,16 +53,26 @@ module.exports = (function() { ...@@ -52,16 +53,26 @@ module.exports = (function() {
migrations.reverse() migrations.reverse()
} }
if (migrations.length === 0) {
self.options.logging("There are no pending migrations.")
} else {
self.options.logging("Running migrations...")
}
migrations.forEach(function(migration) { migrations.forEach(function(migration) {
var migrationTime
chainer.add(migration, 'execute', [options], { chainer.add(migration, 'execute', [options], {
before: function(migration) { before: function(migration) {
if (self.options.logging !== false) { if (self.options.logging !== false) {
self.options.logging('Executing migration: ' + migration.filename) self.options.logging(migration.filename)
} }
migrationTime = process.hrtime()
}, },
after: function(migration) { after: function(migration) {
migrationTime = process.hrtime(migrationTime)
migrationTime = Math.round( (migrationTime[0] * 1000) + (migrationTime[1] / 1000000));
if (self.options.logging !== false) { if (self.options.logging !== false) {
self.options.logging('Executed migration: ' + migration.filename) self.options.logging('Completed in ' + migrationTime + 'ms')
} }
}, },
success: function(migration, callback) { success: function(migration, callback) {
...@@ -96,7 +107,7 @@ module.exports = (function() { ...@@ -96,7 +107,7 @@ module.exports = (function() {
} }
var migrationFiles = fs.readdirSync(this.options.path).filter(function(file) { var migrationFiles = fs.readdirSync(this.options.path).filter(function(file) {
return /\.js$/.test(file) return self.options.filesFilter.test(file)
}) })
var migrations = migrationFiles.map(function(file) { var migrations = migrationFiles.map(function(file) {
......
...@@ -10,6 +10,58 @@ module.exports = (function() { ...@@ -10,6 +10,58 @@ module.exports = (function() {
} }
Utils.addEventEmitter(QueryInterface) Utils.addEventEmitter(QueryInterface)
QueryInterface.prototype.createSchema = function(schema) {
var sql = this.QueryGenerator.createSchema(schema)
return queryAndEmit.call(this, sql, 'createSchema')
}
QueryInterface.prototype.dropSchema = function(schema) {
var sql = this.QueryGenerator.dropSchema(schema)
return queryAndEmit.call(this, sql, 'dropSchema')
}
QueryInterface.prototype.dropAllSchemas = function() {
var self = this
return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer()
self.showAllSchemas().success(function(schemaNames) {
schemaNames.forEach(function(schemaName) {
chainer.add(self.dropSchema(schemaName))
})
chainer
.run()
.success(function() {
self.emit('dropAllSchemas', null)
emitter.emit('success', null)
})
.error(function(err) {
self.emit('dropAllSchemas', err)
emitter.emit('error', err)
})
}).error(function(err) {
self.emit('dropAllSchemas', err)
emitter.emit('error', err)
})
}).run()
}
QueryInterface.prototype.showAllSchemas = function() {
var self = this
return new Utils.CustomEventEmitter(function(emitter) {
var showSchemasSql = self.QueryGenerator.showSchemasQuery()
self.sequelize.query(showSchemasSql, null, { raw: true }).success(function(schemaNames) {
self.emit('showAllSchemas', null)
emitter.emit('success', Utils._.flatten(Utils._.map(schemaNames, function(value){ return value.schema_name })))
}).error(function(err) {
self.emit('showAllSchemas', err)
emitter.emit('error', err)
})
}).run()
}
QueryInterface.prototype.createTable = function(tableName, attributes, options) { QueryInterface.prototype.createTable = function(tableName, attributes, options) {
var attributeHashes = {} var attributeHashes = {}
...@@ -27,8 +79,8 @@ module.exports = (function() { ...@@ -27,8 +79,8 @@ module.exports = (function() {
return queryAndEmit.call(this, sql, 'createTable') return queryAndEmit.call(this, sql, 'createTable')
} }
QueryInterface.prototype.dropTable = function(tableName) { QueryInterface.prototype.dropTable = function(tableName, options) {
var sql = this.QueryGenerator.dropTableQuery(tableName) var sql = this.QueryGenerator.dropTableQuery(tableName, options)
return queryAndEmit.call(this, sql, 'dropTable') return queryAndEmit.call(this, sql, 'dropTable')
} }
...@@ -39,11 +91,17 @@ module.exports = (function() { ...@@ -39,11 +91,17 @@ module.exports = (function() {
var chainer = new Utils.QueryChainer() var chainer = new Utils.QueryChainer()
self.showAllTables().success(function(tableNames) { self.showAllTables().success(function(tableNames) {
chainer.add(self, 'disableForeignKeyConstraints', [])
tableNames.forEach(function(tableName) { tableNames.forEach(function(tableName) {
chainer.add(self.dropTable(tableName)) chainer.add(self, 'dropTable', [tableName, {cascade: true}])
}) })
chainer.add(self, 'enableForeignKeyConstraints', [])
chainer chainer
.run() .runSerially()
.success(function() { .success(function() {
self.emit('dropAllTables', null) self.emit('dropAllTables', null)
emitter.emit('success', null) emitter.emit('success', null)
...@@ -202,16 +260,31 @@ module.exports = (function() { ...@@ -202,16 +260,31 @@ module.exports = (function() {
}) })
} }
QueryInterface.prototype.bulkInsert = function(tableName, records) {
var sql = this.QueryGenerator.bulkInsertQuery(tableName, records)
return queryAndEmit.call(this, sql, 'bulkInsert')
}
QueryInterface.prototype.update = function(dao, tableName, values, identifier) { QueryInterface.prototype.update = function(dao, tableName, values, identifier) {
var sql = this.QueryGenerator.updateQuery(tableName, values, identifier) var sql = this.QueryGenerator.updateQuery(tableName, values, identifier)
return queryAndEmit.call(this, [sql, dao], 'update') return queryAndEmit.call(this, [sql, dao], 'update')
} }
QueryInterface.prototype.bulkUpdate = function(tableName, values, identifier) {
var sql = this.QueryGenerator.updateQuery(tableName, values, identifier)
return queryAndEmit.call(this, sql, 'bulkUpdate')
}
QueryInterface.prototype.delete = function(dao, tableName, identifier) { QueryInterface.prototype.delete = function(dao, tableName, identifier) {
var sql = this.QueryGenerator.deleteQuery(tableName, identifier) var sql = this.QueryGenerator.deleteQuery(tableName, identifier)
return queryAndEmit.call(this, [sql, dao], 'delete') return queryAndEmit.call(this, [sql, dao], 'delete')
} }
QueryInterface.prototype.bulkDelete = function(tableName, identifier, options) {
var sql = this.QueryGenerator.deleteQuery(tableName, identifier, Utils._.defaults(options || {}, {limit: null}))
return queryAndEmit.call(this, sql, 'bulkDelete')
}
QueryInterface.prototype.select = function(factory, tableName, options, queryOptions) { QueryInterface.prototype.select = function(factory, tableName, options, queryOptions) {
options = options || {} options = options || {}
...@@ -244,6 +317,10 @@ module.exports = (function() { ...@@ -244,6 +317,10 @@ module.exports = (function() {
result = parseInt(result) result = parseInt(result)
} }
if (options && options.parseFloat) {
result = parseFloat(result)
}
self.emit('rawSelect', null) self.emit('rawSelect', null)
emitter.emit('success', result) emitter.emit('success', result)
}) })
...@@ -257,6 +334,57 @@ module.exports = (function() { ...@@ -257,6 +334,57 @@ module.exports = (function() {
}).run() }).run()
} }
QueryInterface.prototype.enableForeignKeyConstraints = function() {
var sql = this.QueryGenerator.enableForeignKeyConstraintsQuery()
if(sql) {
return queryAndEmit.call(this, sql, 'enableForeignKeyConstraints')
} else {
return new Utils.CustomEventEmitter(function(emitter) {
this.emit('enableForeignKeyConstraints', null)
emitter.emit('success')
}).run()
}
}
QueryInterface.prototype.disableForeignKeyConstraints = function() {
var sql = this.QueryGenerator.disableForeignKeyConstraintsQuery()
if(sql){
return queryAndEmit.call(this, sql, 'disableForeignKeyConstraints')
} else {
return new Utils.CustomEventEmitter(function(emitter) {
this.emit('disableForeignKeyConstraints', null)
emitter.emit('success')
}).run()
}
}
// Helper methods useful for querying
/**
* Escape an identifier (e.g. a table or attribute name). If force is true,
* the identifier will be quoted even if the `quoteIdentifiers` option is
* false.
*/
QueryInterface.prototype.quoteIdentifier = function(identifier, force) {
return this.QueryGenerator.quoteIdentifier(identifier, force)
}
/**
* Split an identifier into .-separated tokens and quote each part.
* If force is true, the identifier will be quoted even if the
* `quoteIdentifiers` option is false.
*/
QueryInterface.prototype.quoteIdentifiers = function(identifiers, force) {
return this.QueryGenerator.quoteIdentifiers(identifiers, force)
}
/**
* Escape a value (e.g. a string, number or date)
*/
QueryInterface.prototype.escape = function(value) {
return this.QueryGenerator.escape(value)
}
// private // private
var queryAndEmit = function(sqlOrQueryParams, methodName, options, emitter) { var queryAndEmit = function(sqlOrQueryParams, methodName, options, emitter) {
......
var Utils = require("./utils") var url = require("url")
, Utils = require("./utils")
, DAOFactory = require("./dao-factory") , DAOFactory = require("./dao-factory")
, DataTypes = require('./data-types') , DataTypes = require('./data-types')
, DAOFactoryManager = require("./dao-factory-manager") , DAOFactoryManager = require("./dao-factory-manager")
...@@ -25,6 +26,7 @@ module.exports = (function() { ...@@ -25,6 +26,7 @@ module.exports = (function() {
@param {Boolean} [options.native=false] A flag that defines if native library shall be used or not. @param {Boolean} [options.native=false] A flag that defines if native library shall be used or not.
@param {Boolean} [options.replication=false] I have absolutely no idea. @param {Boolean} [options.replication=false] I have absolutely no idea.
@param {Object} [options.pool={}] Something. @param {Object} [options.pool={}] Something.
@param {Boolean} [options.quoteIdentifiers=true] Set to `false` to make table names and attributes case-insensitive on Postgres and skip double quoting of them.
@example @example
// without password and options // without password and options
...@@ -43,10 +45,30 @@ module.exports = (function() { ...@@ -43,10 +45,30 @@ module.exports = (function() {
@constructor @constructor
*/ */
var Sequelize = function(database, username, password, options) { var Sequelize = function(database, username, password, options) {
var urlParts
options = options || {}
if (arguments.length === 1 || (arguments.length === 2 && typeof username === 'object')) {
options = username || {}
urlParts = url.parse(arguments[0])
database = urlParts.path.replace(/^\//, '')
dialect = urlParts.protocol
options.dialect = urlParts.protocol.replace(/:$/, '')
options.host = urlParts.hostname
if (urlParts.port) {
options.port = urlParts.port
}
if (urlParts.auth) {
username = urlParts.auth.split(':')[0]
password = urlParts.auth.split(':')[1]
}
}
this.options = Utils._.extend({ this.options = Utils._.extend({
dialect: 'mysql', dialect: 'mysql',
host: 'localhost', host: 'localhost',
port: 3306,
protocol: 'tcp', protocol: 'tcp',
define: {}, define: {},
query: {}, query: {},
...@@ -56,7 +78,8 @@ module.exports = (function() { ...@@ -56,7 +78,8 @@ module.exports = (function() {
queue: true, queue: true,
native: false, native: false,
replication: false, replication: false,
pool: {} pool: {},
quoteIdentifiers: true
}, options || {}) }, options || {})
if (this.options.logging === true) { if (this.options.logging === true) {
...@@ -130,6 +153,16 @@ module.exports = (function() { ...@@ -130,6 +153,16 @@ module.exports = (function() {
options = options || {} options = options || {}
var globalOptions = this.options var globalOptions = this.options
// If you don't specify a valid data type lets help you debug it
Utils._.each(attributes, function(dataType, name){
if (Utils.isHash(dataType)) {
dataType = dataType.type
}
if (dataType === undefined) {
throw new Error('Unrecognized data type for field '+ name)
}
})
if (globalOptions.define) { if (globalOptions.define) {
options = Utils._.extend({}, globalOptions.define, options) options = Utils._.extend({}, globalOptions.define, options)
Utils._(['classMethods', 'instanceMethods']).each(function(key) { Utils._(['classMethods', 'instanceMethods']).each(function(key) {
...@@ -171,7 +204,7 @@ module.exports = (function() { ...@@ -171,7 +204,7 @@ module.exports = (function() {
Sequelize.prototype.query = function(sql, callee, options, replacements) { Sequelize.prototype.query = function(sql, callee, options, replacements) {
if (arguments.length === 4) { if (arguments.length === 4) {
sql = Utils.format([sql].concat(replacements)) sql = Utils.format([sql].concat(replacements), this.options.dialect)
} else if (arguments.length === 3) { } else if (arguments.length === 3) {
options = options options = options
} else if (arguments.length === 2) { } else if (arguments.length === 2) {
...@@ -189,6 +222,38 @@ module.exports = (function() { ...@@ -189,6 +222,38 @@ module.exports = (function() {
return this.connectorManager.query(sql, callee, options) return this.connectorManager.query(sql, callee, options)
} }
Sequelize.prototype.createSchema = function(schema) {
var chainer = new Utils.QueryChainer()
chainer.add(this.getQueryInterface().createSchema(schema))
return chainer.run()
}
Sequelize.prototype.showAllSchemas = function() {
var chainer = new Utils.QueryChainer()
chainer.add(this.getQueryInterface().showAllSchemas())
return chainer.run()
}
Sequelize.prototype.dropSchema = function(schema) {
var chainer = new Utils.QueryChainer()
chainer.add(this.getQueryInterface().dropSchema(schema))
return chainer.run()
}
Sequelize.prototype.dropAllSchemas = function() {
var self = this
var chainer = new Utils.QueryChainer()
chainer.add(self.getQueryInterface().dropAllSchemas())
return chainer.run()
}
Sequelize.prototype.sync = function(options) { Sequelize.prototype.sync = function(options) {
options = options || {} options = options || {}
...@@ -198,11 +263,14 @@ module.exports = (function() { ...@@ -198,11 +263,14 @@ module.exports = (function() {
var chainer = new Utils.QueryChainer() var chainer = new Utils.QueryChainer()
this.daoFactoryManager.daos.forEach(function(dao) { // Topologically sort by foreign key constraints to give us an appropriate
chainer.add(dao.sync(options)) // creation order
this.daoFactoryManager.forEachDAO(function(dao) {
chainer.add(dao, 'sync', [options])
}) })
return chainer.run() return chainer.runSerially()
} }
Sequelize.prototype.drop = function() { Sequelize.prototype.drop = function() {
......
...@@ -7,7 +7,7 @@ SqlString.escapeId = function (val, forbidQualified) { ...@@ -7,7 +7,7 @@ SqlString.escapeId = function (val, forbidQualified) {
return '`' + val.replace(/`/g, '``').replace(/\./g, '`.`') + '`'; return '`' + val.replace(/`/g, '``').replace(/\./g, '`.`') + '`';
}; };
SqlString.escape = function(val, stringifyObjects, timeZone) { SqlString.escape = function(val, stringifyObjects, timeZone, dialect) {
if (val === undefined || val === null) { if (val === undefined || val === null) {
return 'NULL'; return 'NULL';
} }
...@@ -37,17 +37,22 @@ SqlString.escape = function(val, stringifyObjects, timeZone) { ...@@ -37,17 +37,22 @@ SqlString.escape = function(val, stringifyObjects, timeZone) {
} }
} }
val = val.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) { if (dialect == "postgres") {
switch(s) { // http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS
case "\0": return "\\0"; val = val.replace(/'/g, "''");
case "\n": return "\\n"; } else {
case "\r": return "\\r"; val = val.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) {
case "\b": return "\\b"; switch(s) {
case "\t": return "\\t"; case "\0": return "\\0";
case "\x1a": return "\\Z"; case "\n": return "\\n";
default: return "\\"+s; case "\r": return "\\r";
} case "\b": return "\\b";
}); case "\t": return "\\t";
case "\x1a": return "\\Z";
default: return "\\"+s;
}
});
}
return "'"+val+"'"; return "'"+val+"'";
}; };
...@@ -58,7 +63,7 @@ SqlString.arrayToList = function(array, timeZone) { ...@@ -58,7 +63,7 @@ SqlString.arrayToList = function(array, timeZone) {
}).join(', '); }).join(', ');
}; };
SqlString.format = function(sql, values, timeZone) { SqlString.format = function(sql, values, timeZone, dialect) {
values = [].concat(values); values = [].concat(values);
return sql.replace(/\?/g, function(match) { return sql.replace(/\?/g, function(match) {
...@@ -66,7 +71,7 @@ SqlString.format = function(sql, values, timeZone) { ...@@ -66,7 +71,7 @@ SqlString.format = function(sql, values, timeZone) {
return match; return match;
} }
return SqlString.escape(values.shift(), false, timeZone); return SqlString.escape(values.shift(), false, timeZone, dialect);
}); });
}; };
......
...@@ -4,7 +4,7 @@ var util = require("util") ...@@ -4,7 +4,7 @@ var util = require("util")
var Utils = module.exports = { var Utils = module.exports = {
_: (function() { _: (function() {
var _ = require("underscore") var _ = require("lodash")
, _s = require('underscore.string') , _s = require('underscore.string')
_.mixin(_s.exports()) _.mixin(_s.exports())
...@@ -35,20 +35,9 @@ var Utils = module.exports = { ...@@ -35,20 +35,9 @@ var Utils = module.exports = {
addEventEmitter: function(_class) { addEventEmitter: function(_class) {
util.inherits(_class, require('events').EventEmitter) util.inherits(_class, require('events').EventEmitter)
}, },
TICK_CHAR: '`', format: function(arr, dialect) {
addTicks: function(s, tickChar) { var timeZone = null;
tickChar = tickChar || Utils.TICK_CHAR return SqlString.format(arr.shift(), arr, timeZone, dialect)
return tickChar + Utils.removeTicks(s, tickChar) + tickChar
},
removeTicks: function(s, tickChar) {
tickChar = tickChar || Utils.TICK_CHAR
return s.replace(new RegExp(tickChar, 'g'), "")
},
escape: function(s) {
return SqlString.escape(s, true, "local").replace(/\\"/g, '"')
},
format: function(arr) {
return SqlString.format(arr.shift(), arr)
}, },
isHash: function(obj) { isHash: function(obj) {
return Utils._.isObject(obj) && !Array.isArray(obj); return Utils._.isObject(obj) && !Array.isArray(obj);
...@@ -179,9 +168,25 @@ var Utils = module.exports = { ...@@ -179,9 +168,25 @@ var Utils = module.exports = {
var now = new Date() var now = new Date()
now.setMilliseconds(0) now.setMilliseconds(0)
return now return now
},
// Note: Use the `quoteIdentifier()` and `escape()` methods on the
// `QueryInterface` instead for more portable code.
TICK_CHAR: '`',
addTicks: function(s, tickChar) {
tickChar = tickChar || Utils.TICK_CHAR
return tickChar + Utils.removeTicks(s, tickChar) + tickChar
},
removeTicks: function(s, tickChar) {
tickChar = tickChar || Utils.TICK_CHAR
return s.replace(new RegExp(tickChar, 'g'), "")
},
escape: function(s) {
return SqlString.escape(s, true, "local").replace(/\\"/g, '"')
} }
} }
Utils.CustomEventEmitter = require("./emitters/custom-event-emitter") Utils.CustomEventEmitter = require("./emitters/custom-event-emitter")
Utils.QueryChainer = require("./query-chainer") Utils.QueryChainer = require("./query-chainer")
Utils.Lingo = require("lingo") Utils.Lingo = require("lingo")
\ No newline at end of file
{ {
"name": "sequelize", "name": "sequelize",
"description": "Multi dialect ORM for Node.JS", "description": "Multi dialect ORM for Node.JS",
"version": "1.6.0", "version": "1.7.0-alpha2",
"author": "Sascha Depold <sascha@depold.com>", "author": "Sascha Depold <sascha@depold.com>",
"contributors": [ "contributors": [
{ {
...@@ -23,27 +23,29 @@ ...@@ -23,27 +23,29 @@
], ],
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/sdepold/sequelize.git" "url": "https://github.com/sequelize/sequelize.git"
}, },
"bugs": { "bugs": {
"url": "https://github.com/sdepold/sequelize/issues" "url": "https://github.com/sequelize/sequelize/issues"
}, },
"dependencies": { "dependencies": {
"underscore": "~1.4.0", "lodash": "~1.2.1",
"underscore.string": "~2.3.0", "underscore.string": "~2.3.0",
"lingo": "~0.0.5", "lingo": "~0.0.5",
"validator": "0.4.x", "validator": "1.1.1",
"moment": "~1.7.0", "moment": "~1.7.0",
"commander": "~0.6.0", "commander": "~0.6.0",
"generic-pool": "1.0.9", "dottie": "0.0.6-1",
"dottie": "0.0.6-1" "toposort-class": "0.1.4",
"generic-pool": "2.0.3",
"promise": "~3.0.0"
}, },
"devDependencies": { "devDependencies": {
"jasmine-node": "1.0.17", "jasmine-node": "1.5.0",
"sqlite3": "~2.1.5", "sqlite3": "~2.1.5",
"mysql": "~2.0.0-alpha7", "mysql": "~2.0.0-alpha7",
"pg": "~0.10.2", "pg": "~0.10.2",
"buster": "~0.6.0", "buster": "~0.6.3",
"watchr": "~2.2.0", "watchr": "~2.2.0",
"yuidocjs": "~0.3.36" "yuidocjs": "~0.3.36"
}, },
...@@ -56,13 +58,13 @@ ...@@ -56,13 +58,13 @@
"main": "index", "main": "index",
"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": "jasmine-node spec-jasmine/",
"test-buster": "npm run test-buster-mysql && npm run test-buster-postgres && npm run test-buster-postgres-native && npm run test-buster-sqlite", "test-buster": "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-travis": "buster-test",
"test-buster-mysql": "DIALECT=mysql ./node_modules/.bin/buster-test", "test-buster-mysql": "DIALECT=mysql buster-test",
"test-buster-postgres": "DIALECT=postgres ./node_modules/.bin/buster-test", "test-buster-postgres": "DIALECT=postgres buster-test",
"test-buster-postgres-native": "DIALECT=postgres-native ./node_modules/.bin/buster-test", "test-buster-postgres-native": "DIALECT=postgres-native buster-test",
"test-buster-sqlite": "DIALECT=sqlite ./node_modules/.bin/buster-test", "test-buster-sqlite": "DIALECT=sqlite buster-test",
"docs": "node_modules/.bin/yuidoc . -o docs" "docs": "node_modules/.bin/yuidoc . -o docs"
}, },
"bin": { "bin": {
......
var config = require("../config/config") var config = require("../config/config")
, Sequelize = require("../../index") , Sequelize = require("../../index")
, sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { logging: false }) , sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { pool: config.mysql.pool, logging: false, host: config.mysql.host, port: config.mysql.port })
, Helpers = new (require("../config/helpers"))(sequelize) , Helpers = new (require("../config/helpers"))(sequelize)
describe('BelongsTo', function() { describe('BelongsTo', function() {
...@@ -8,7 +8,10 @@ describe('BelongsTo', function() { ...@@ -8,7 +8,10 @@ describe('BelongsTo', function() {
, Task = null , Task = null
var setup = function() { var setup = function() {
User = sequelize.define('User', { username: Sequelize.STRING }) User = sequelize.define('User', { username: Sequelize.STRING, enabled: {
type: Sequelize.BOOLEAN,
defaultValue: true
}})
Task = sequelize.define('Task', { title: Sequelize.STRING }) Task = sequelize.define('Task', { title: Sequelize.STRING })
} }
...@@ -88,6 +91,29 @@ describe('BelongsTo', function() { ...@@ -88,6 +91,29 @@ describe('BelongsTo', function() {
}) })
}) })
it('extends the id where param with the supplied where params', function() {
Task.belongsTo(User, {as: 'User'})
Helpers.async(function(done) {
User.sync({force: true}).success(function() {
Task.sync({force: true}).success(done)
})
})
Helpers.async(function(done) {
User.create({username: 'asd', enabled: false}).success(function(u) {
Task.create({title: 'a task'}).success(function(t) {
t.setUser(u).success(function() {
t.getUser({where: {enabled: true}}).success(function(user) {
expect(user).toEqual(null)
done()
})
})
})
})
})
})
it("handles self associations", function() { it("handles self associations", function() {
Helpers.async(function(done) { Helpers.async(function(done) {
var Person = sequelize.define('Person', { name: Sequelize.STRING }) var Person = sequelize.define('Person', { name: Sequelize.STRING })
......
var config = require("../config/config") var config = require("../config/config")
, Sequelize = require("../../index") , Sequelize = require("../../index")
, sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { logging: false }) , sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { pool: config.mysql.pool, logging: false, host: config.mysql.host, port: config.mysql.port })
, Helpers = new (require("../config/helpers"))(sequelize) , Helpers = new (require("../config/helpers"))(sequelize)
describe('HasMany', function() { describe('HasMany', function() {
...@@ -10,7 +10,7 @@ describe('HasMany', function() { ...@@ -10,7 +10,7 @@ describe('HasMany', function() {
, Helpers = null , Helpers = null
var setup = function() { var setup = function() {
sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { logging: false }) sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { pool: config.mysql.pool, logging: false, host: config.mysql.host, port: config.mysql.port })
Helpers = new (require("../config/helpers"))(sequelize) Helpers = new (require("../config/helpers"))(sequelize)
Helpers.dropAllTables() Helpers.dropAllTables()
......
var config = require("../config/config") var config = require("../config/config")
, Sequelize = require("../../index") , Sequelize = require("../../index")
, sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { logging: false }) , sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { pool: config.mysql.pool, logging: false, host: config.mysql.host, port: config.mysql.port })
, Helpers = new (require("../config/helpers"))(sequelize) , Helpers = new (require("../config/helpers"))(sequelize)
describe('HasOne', function() { describe('HasOne', function() {
......
...@@ -90,25 +90,6 @@ describe('DAOFactory', function() { ...@@ -90,25 +90,6 @@ describe('DAOFactory', function() {
}) })
}) })
it('marks the database entry as deleted if dao is paranoid', function() {
Helpers.async(function(done) {
User = sequelize.define('User', {
name: Sequelize.STRING, bio: Sequelize.TEXT
}, { paranoid:true })
User.sync({ force: true }).success(done)
})
Helpers.async(function(done) {
User.create({ name: 'asd', bio: 'asd' }).success(function(u) {
expect(u.deletedAt).toBeNull()
u.destroy().success(function(u) {
expect(u.deletedAt).toBeTruthy()
done()
})
})
})
})
it('allows sql logging of update statements', function() { it('allows sql logging of update statements', function() {
Helpers.async(function(done) { Helpers.async(function(done) {
User = sequelize.define('User', { User = sequelize.define('User', {
......
...@@ -13,7 +13,8 @@ describe('DAO', function() { ...@@ -13,7 +13,8 @@ describe('DAO', function() {
{ {
logging: false, logging: false,
dialect: dialect, dialect: dialect,
port: config[dialect].port port: config[dialect].port,
host: config[dialect].host
} }
) )
, Helpers = new (require("./config/helpers"))(sequelize) , Helpers = new (require("./config/helpers"))(sequelize)
......
var config = require("../config/config") var config = require("../config/config")
, Sequelize = require("../../index") , Sequelize = require("../../index")
, sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { pool: config.mysql.pool, logging: false }) , sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { pool: config.mysql.pool, logging: false, host: config.mysql.host, port: config.mysql.port })
, Helpers = new (require("../config/helpers"))(sequelize) , Helpers = new (require("../config/helpers"))(sequelize)
describe('HasMany', function() { describe('HasMany', function() {
......
var config = require("../config/config") var config = require("../config/config")
, Sequelize = require("../../index") , Sequelize = require("../../index")
, sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { pool: config.mysql.pool, logging: false }) , sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { pool: config.mysql.pool, logging: false, host: config.mysql.host, port: config.mysql.port })
, Helpers = new (require("../config/helpers"))(sequelize) , Helpers = new (require("../config/helpers"))(sequelize)
describe('Associations', function() { describe('Associations', function() {
......
var config = require("../config/config") var config = require("../config/config")
, Sequelize = require("../../index") , Sequelize = require("../../index")
, sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { pool: config.mysql.pool, logging: false }) , sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { pool: config.mysql.pool, logging: false, host: config.mysql.host, port: config.mysql.port })
, Helpers = new (require("../config/helpers"))(sequelize) , Helpers = new (require("../config/helpers"))(sequelize)
describe('ConnectorManager', function() { describe('ConnectorManager', function() {
......
var config = require("../config/config") var config = require("../config/config")
, Sequelize = require("../../index") , Sequelize = require("../../index")
, sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { pool: config.mysql.pool, logging: false }) , sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { pool: config.mysql.pool, logging: false, host: config.mysql.host, port: config.mysql.port })
, Helpers = new (require("../config/helpers"))(sequelize) , Helpers = new (require("../config/helpers"))(sequelize)
describe('DAOFactory', function() { describe('DAOFactory', function() {
...@@ -31,6 +31,13 @@ describe('DAOFactory', function() { ...@@ -31,6 +31,13 @@ describe('DAOFactory', function() {
expect(User.attributes).toEqual({username:"VARCHAR(255) NOT NULL",id:"INTEGER NOT NULL auto_increment PRIMARY KEY"}) expect(User.attributes).toEqual({username:"VARCHAR(255) NOT NULL",id:"INTEGER NOT NULL auto_increment PRIMARY KEY"})
}) })
it("handles extended attributes (comment)", function() {
var User = sequelize.define('User' + config.rand(), {
username: {type: Sequelize.STRING, comment: 'This be\'s a comment'}
}, { timestamps: false })
expect(User.attributes).toEqual({username:"VARCHAR(255) COMMENT 'This be\\'s a comment'",id:"INTEGER NOT NULL auto_increment PRIMARY KEY"})
})
it("handles extended attributes (primaryKey)", function() { it("handles extended attributes (primaryKey)", function() {
var User = sequelize.define('User' + config.rand(), { var User = sequelize.define('User' + config.rand(), {
username: {type: Sequelize.STRING, primaryKey: true} username: {type: Sequelize.STRING, primaryKey: true}
......
...@@ -8,7 +8,17 @@ describe('Sequelize', function() { ...@@ -8,7 +8,17 @@ describe('Sequelize', function() {
var setup = function(options) { var setup = function(options) {
options = options || {logging: false} options = options || {}
if (!options.hasOwnProperty('pool'))
options.pool = config.mysql.pool
if (!options.hasOwnProperty('logging'))
options.logging = false
if (!options.hasOwnProperty('host'))
options.host = config.mysql.host
if (!options.hasOwnProperty('port'))
options.port = config.mysql.port
sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, options) sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, options)
Helpers = new (require("./config/helpers"))(sequelize) Helpers = new (require("./config/helpers"))(sequelize)
......
...@@ -9,6 +9,81 @@ describe('QueryGenerator', function() { ...@@ -9,6 +9,81 @@ describe('QueryGenerator', function() {
afterEach(function() { Helpers.drop() }) afterEach(function() { Helpers.drop() })
var suites = { var suites = {
attributesToSQL: [
{
arguments: [{id: 'INTEGER'}],
expectation: {id: 'INTEGER'}
},
{
arguments: [{id: 'INTEGER', foo: 'VARCHAR(255)'}],
expectation: {id: 'INTEGER', foo: 'VARCHAR(255)'}
},
{
arguments: [{id: {type: 'INTEGER'}}],
expectation: {id: 'INTEGER'}
},
{
arguments: [{id: {type: 'INTEGER', allowNull: false}}],
expectation: {id: 'INTEGER NOT NULL'}
},
{
arguments: [{id: {type: 'INTEGER', allowNull: true}}],
expectation: {id: 'INTEGER'}
},
{
arguments: [{id: {type: 'INTEGER', primaryKey: true, autoIncrement: true}}],
expectation: {id: 'INTEGER PRIMARY KEY AUTOINCREMENT'}
},
{
arguments: [{id: {type: 'INTEGER', defaultValue: 0}}],
expectation: {id: 'INTEGER DEFAULT 0'}
},
{
arguments: [{id: {type: 'INTEGER', unique: true}}],
expectation: {id: 'INTEGER UNIQUE'}
},
{
arguments: [{id: {type: 'INTEGER', references: 'Bar'}}],
expectation: {id: 'INTEGER REFERENCES `Bar` (`id`)'}
},
{
arguments: [{id: {type: 'INTEGER', references: 'Bar', referencesKey: 'pk'}}],
expectation: {id: 'INTEGER REFERENCES `Bar` (`pk`)'}
},
{
arguments: [{id: {type: 'INTEGER', references: 'Bar', onDelete: 'CASCADE'}}],
expectation: {id: 'INTEGER REFERENCES `Bar` (`id`) ON DELETE CASCADE'}
},
{
arguments: [{id: {type: 'INTEGER', references: 'Bar', onUpdate: 'RESTRICT'}}],
expectation: {id: 'INTEGER REFERENCES `Bar` (`id`) ON UPDATE RESTRICT'}
},
{
arguments: [{id: {type: 'INTEGER', allowNull: false, defaultValue: 1, references: 'Bar', onDelete: 'CASCADE', onUpdate: 'RESTRICT'}}],
expectation: {id: 'INTEGER NOT NULL DEFAULT 1 REFERENCES `Bar` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT'}
},
],
createTableQuery: [
{
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255));"
},
{
arguments: ['myTable', {title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)'}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` ENUM(\"A\", \"B\", \"C\"), `name` VARCHAR(255));"
},
{
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)', id: 'INTEGER PRIMARY KEY'}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255), `id` INTEGER PRIMARY KEY);"
},
{
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)', otherId: 'INTEGER REFERENCES `otherTable` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION'}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255), `otherId` INTEGER REFERENCES `otherTable` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION);"
}
],
insertQuery: [ insertQuery: [
{ {
arguments: ['myTable', { name: 'foo' }], arguments: ['myTable', { name: 'foo' }],
...@@ -46,6 +121,43 @@ describe('QueryGenerator', function() { ...@@ -46,6 +121,43 @@ describe('QueryGenerator', function() {
} }
], ],
bulkInsertQuery: [
{
arguments: ['myTable', [{name: 'foo'}, {name: 'bar'}]],
expectation: "INSERT INTO `myTable` (`name`) VALUES ('foo'),('bar');"
}, {
arguments: ['myTable', [{name: "'bar'"}, {name: 'foo'}]],
expectation: "INSERT INTO `myTable` (`name`) VALUES ('''bar'''),('foo');"
}, {
arguments: ['myTable', [{name: "bar", value: null}, {name: 'foo', value: 1}]],
expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('bar',NULL),('foo',1);"
}, {
arguments: ['myTable', [{name: "bar", value: undefined}, {name: 'bar', value: 2}]],
expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('bar',NULL),('bar',2);"
}, {
arguments: ['myTable', [{name: "foo", value: true}, {name: 'bar', value: false}]],
expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('foo',1),('bar',0);"
}, {
arguments: ['myTable', [{name: "foo", value: false}, {name: 'bar', value: false}]],
expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('foo',0),('bar',0);"
}, {
arguments: ['myTable', [{name: 'foo', foo: 1, nullValue: null}, {name: 'bar', foo: 2, nullValue: null}]],
expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);"
}, {
arguments: ['myTable', [{name: 'foo', foo: 1, nullValue: null}, {name: 'bar', foo: 2, nullValue: null}]],
expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);",
context: {options: {omitNull: false}}
}, {
arguments: ['myTable', [{name: 'foo', foo: 1, nullValue: null}, {name: 'bar', foo: 2, nullValue: null}]],
expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);",
context: {options: {omitNull: true}} // Note: We don't honour this because it makes little sense when some rows may have nulls and others not
}, {
arguments: ['myTable', [{name: 'foo', foo: 1, nullValue: null}, {name: 'bar', foo: 2, nullValue: null}]],
expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);",
context: {options: {omitNull: true}} // Note: As above
}
],
updateQuery: [ updateQuery: [
{ {
arguments: ['myTable', { name: 'foo' }, { id: 2 }], arguments: ['myTable', { name: 'foo' }, { id: 2 }],
...@@ -77,6 +189,28 @@ describe('QueryGenerator', function() { ...@@ -77,6 +189,28 @@ describe('QueryGenerator', function() {
expectation: "UPDATE `myTable` SET `bar`=2 WHERE `name`='foo'", expectation: "UPDATE `myTable` SET `bar`=2 WHERE `name`='foo'",
context: {options: {omitNull: true}} context: {options: {omitNull: true}}
} }
],
deleteQuery: [
{
arguments: ['myTable', {name: 'foo'}],
expectation: "DELETE FROM `myTable` WHERE `name`='foo'"
}, {
arguments: ['myTable', 1],
expectation: "DELETE FROM `myTable` WHERE `id`=1"
}, {
arguments: ['myTable', 1, {truncate: true}],
expectation: "DELETE FROM `myTable` WHERE `id`=1"
}, {
arguments: ['myTable', 1, {limit: 10}],
expectation: "DELETE FROM `myTable` WHERE `id`=1"
}, {
arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {limit: 10}],
expectation: "DELETE FROM `myTable` WHERE `name`='foo'';DROP TABLE myTable;'"
}, {
arguments: ['myTable', {name: 'foo'}, {limit: null}],
expectation: "DELETE FROM `myTable` WHERE `name`='foo'"
}
] ]
}; };
...@@ -87,8 +221,8 @@ describe('QueryGenerator', function() { ...@@ -87,8 +221,8 @@ describe('QueryGenerator', function() {
it(title, function() { it(title, function() {
// Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly // Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly
var context = test.context || {options: {}}; var context = test.context || {options: {}};
QueryGenerator.options = context.options
var conditions = QueryGenerator[suiteTitle].apply(context, test.arguments) var conditions = QueryGenerator[suiteTitle].apply(QueryGenerator, test.arguments)
expect(conditions).toEqual(test.expectation) expect(conditions).toEqual(test.expectation)
}) })
}) })
......
...@@ -47,4 +47,161 @@ describe(Helpers.getTestDialectTeaser("BelongsTo"), function() { ...@@ -47,4 +47,161 @@ describe(Helpers.getTestDialectTeaser("BelongsTo"), function() {
}) })
}) })
}) })
describe("Foreign key constraints", function() {
it("are not enabled by default", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
Task.belongsTo(User)
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
task.setUser(user).success(function() {
user.destroy().success(function() {
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(1)
done()
})
})
})
})
})
})
})
it("can cascade deletes", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
Task.belongsTo(User, {onDelete: 'cascade'})
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
task.setUser(user).success(function() {
user.destroy().success(function() {
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(0)
done()
})
})
})
})
})
})
})
it("can restrict deletes", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
Task.belongsTo(User, {onDelete: 'restrict'})
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
task.setUser(user).success(function() {
user.destroy().error(function() {
// Should fail due to FK restriction
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(1)
done()
})
})
})
})
})
})
})
it("can cascade updates", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
Task.belongsTo(User, {onUpdate: 'cascade'})
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
task.setUser(user).success(function() {
// Changing the id of a DAO requires a little dance since
// the `UPDATE` query generated by `save()` uses `id` in the
// `WHERE` clause
var tableName = user.QueryInterface.QueryGenerator.addSchema(user.__factory)
user.QueryInterface.update(user, tableName, {id: 999}, user.id)
.success(function() {
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(1)
expect(tasks[0].UserId).toEqual(999)
done()
})
})
})
})
})
})
})
it("can restrict updates", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
Task.belongsTo(User, {onUpdate: 'restrict'})
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
task.setUser(user).success(function() {
// Changing the id of a DAO requires a little dance since
// the `UPDATE` query generated by `save()` uses `id` in the
// `WHERE` clause
var tableName = user.QueryInterface.QueryGenerator.addSchema(user.__factory)
user.QueryInterface.update(user, tableName, {id: 999}, user.id)
.error(function() {
// Should fail due to FK restriction
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(1)
done()
})
})
})
})
})
})
})
})
describe("Association options", function() {
it('can specify data type for autogenerated relational keys', function(done) {
var User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING })
, dataTypes = [Sequelize.INTEGER, Sequelize.BIGINT, Sequelize.STRING]
, self = this
dataTypes.forEach(function(dataType) {
var tableName = 'TaskXYZ_' + dataType.toString()
, Task = self.sequelize.define(tableName, { title: Sequelize.STRING })
Task.belongsTo(User, { foreignKey: 'userId', keyType: dataType })
self.sequelize.sync({ force: true }).success(function() {
expect(Task.rawAttributes.userId.type.toString())
.toEqual(dataType.toString())
dataTypes.splice(dataTypes.indexOf(dataType), 1)
if (!dataTypes.length) {
done()
}
})
})
})
})
}) })
...@@ -9,6 +9,7 @@ buster.spec.expose() ...@@ -9,6 +9,7 @@ buster.spec.expose()
buster.testRunner.timeout = 500 buster.testRunner.timeout = 500
describe(Helpers.getTestDialectTeaser("HasMany"), function() { describe(Helpers.getTestDialectTeaser("HasMany"), function() {
before(function(done) { before(function(done) {
var self = this var self = this
...@@ -288,7 +289,7 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() { ...@@ -288,7 +289,7 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() {
var add = this.spy() var add = this.spy()
this.stub(Sequelize.Utils, 'QueryChainer').returns({ add: add, run: function(){} }) this.stub(Sequelize.Utils, 'QueryChainer').returns({ add: add, runSerially: function(){} })
this.sequelize.sync({ force: true }) this.sequelize.sync({ force: true })
expect(add).toHaveBeenCalledThrice() expect(add).toHaveBeenCalledThrice()
...@@ -323,4 +324,161 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() { ...@@ -323,4 +324,161 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() {
}) })
}) })
}) })
describe("Foreign key constraints", function() {
it("are not enabled by default", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
User.hasMany(Task)
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
user.setTasks([task]).success(function() {
user.destroy().success(function() {
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(1)
done()
})
})
})
})
})
})
})
it("can cascade deletes", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
User.hasMany(Task, {onDelete: 'cascade'})
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
user.setTasks([task]).success(function() {
user.destroy().success(function() {
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(0)
done()
})
})
})
})
})
})
})
it("can restrict deletes", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
User.hasMany(Task, {onDelete: 'restrict'})
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
user.setTasks([task]).success(function() {
user.destroy().error(function() {
// Should fail due to FK restriction
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(1)
done()
})
})
})
})
})
})
})
it("can cascade updates", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
User.hasMany(Task, {onUpdate: 'cascade'})
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
user.setTasks([task]).success(function() {
// Changing the id of a DAO requires a little dance since
// the `UPDATE` query generated by `save()` uses `id` in the
// `WHERE` clause
var tableName = user.QueryInterface.QueryGenerator.addSchema(user.__factory)
user.QueryInterface.update(user, tableName, {id: 999}, user.id)
.success(function() {
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(1)
expect(tasks[0].UserId).toEqual(999)
done()
})
})
})
})
})
})
})
it("can restrict updates", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
User.hasMany(Task, {onUpdate: 'restrict'})
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
user.setTasks([task]).success(function() {
// Changing the id of a DAO requires a little dance since
// the `UPDATE` query generated by `save()` uses `id` in the
// `WHERE` clause
var tableName = user.QueryInterface.QueryGenerator.addSchema(user.__factory)
user.QueryInterface.update(user, tableName, {id: 999}, user.id)
.error(function() {
// Should fail due to FK restriction
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(1)
done()
})
})
})
})
})
})
})
})
describe("Association options", function() {
it('can specify data type for autogenerated relational keys', function(done) {
var User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING })
, dataTypes = [Sequelize.INTEGER, Sequelize.BIGINT, Sequelize.STRING]
, self = this
dataTypes.forEach(function(dataType) {
var tableName = 'TaskXYZ_' + dataType.toString()
, Task = self.sequelize.define(tableName, { title: Sequelize.STRING })
User.hasMany(Task, { foreignKey: 'userId', keyType: dataType })
self.sequelize.sync({ force: true }).success(function() {
expect(Task.rawAttributes.userId.type.toString())
.toEqual(dataType.toString())
dataTypes.splice(dataTypes.indexOf(dataType), 1)
if (!dataTypes.length) {
done()
}
})
})
})
})
}) })
...@@ -47,4 +47,161 @@ describe(Helpers.getTestDialectTeaser("HasOne"), function() { ...@@ -47,4 +47,161 @@ describe(Helpers.getTestDialectTeaser("HasOne"), function() {
}) })
}) })
}) })
describe("Foreign key constraints", function() {
it("are not enabled by default", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
User.hasOne(Task)
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
user.setTask(task).success(function() {
user.destroy().success(function() {
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(1)
done()
})
})
})
})
})
})
})
it("can cascade deletes", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
User.hasOne(Task, {onDelete: 'cascade'})
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
user.setTask(task).success(function() {
user.destroy().success(function() {
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(0)
done()
})
})
})
})
})
})
})
it("can restrict deletes", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
User.hasOne(Task, {onDelete: 'restrict'})
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
user.setTask(task).success(function() {
user.destroy().error(function() {
// Should fail due to FK restriction
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(1)
done()
})
})
})
})
})
})
})
it("can cascade updates", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
User.hasOne(Task, {onUpdate: 'cascade'})
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
user.setTask(task).success(function() {
// Changing the id of a DAO requires a little dance since
// the `UPDATE` query generated by `save()` uses `id` in the
// `WHERE` clause
var tableName = user.QueryInterface.QueryGenerator.addSchema(user.__factory)
user.QueryInterface.update(user, tableName, {id: 999}, user.id)
.success(function() {
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(1)
expect(tasks[0].UserId).toEqual(999)
done()
})
})
})
})
})
})
})
it("can restrict updates", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
User.hasOne(Task, {onUpdate: 'restrict'})
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
user.setTask(task).success(function() {
// Changing the id of a DAO requires a little dance since
// the `UPDATE` query generated by `save()` uses `id` in the
// `WHERE` clause
var tableName = user.QueryInterface.QueryGenerator.addSchema(user.__factory)
user.QueryInterface.update(user, tableName, {id: 999}, user.id)
.error(function() {
// Should fail due to FK restriction
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(1)
done()
})
})
})
})
})
})
})
})
describe("Association options", function() {
it('can specify data type for autogenerated relational keys', function(done) {
var User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING })
, dataTypes = [Sequelize.INTEGER, Sequelize.BIGINT, Sequelize.STRING]
, self = this
dataTypes.forEach(function(dataType) {
var tableName = 'TaskXYZ_' + dataType.toString()
, Task = self.sequelize.define(tableName, { title: Sequelize.STRING })
User.hasOne(Task, { foreignKey: 'userId', keyType: dataType })
self.sequelize.sync({ force: true }).success(function() {
expect(Task.rawAttributes.userId.type.toString())
.toEqual(dataType.toString())
dataTypes.splice(dataTypes.indexOf(dataType), 1)
if (!dataTypes.length) {
done()
}
})
})
})
})
}) })
if(typeof require === 'function') {
const buster = require("buster")
, CustomEventEmitter = require("../lib/emitters/custom-event-emitter")
, Helpers = require('./buster-helpers')
, dialect = Helpers.getTestDialect()
}
buster.spec.expose()
buster.testRunner.timeout = 1000
var Sequelize = require(__dirname + '/../index')
describe(Helpers.getTestDialectTeaser("Configuration"), function() {
describe('Instantiation with a URL string', function() {
it('should accept username, password, host, port, and database', function() {
var sequelize = new Sequelize('mysql://user:pass@example.com:9821/dbname')
var config = sequelize.config
var options = sequelize.options
expect(options.dialect).toEqual('mysql')
expect(config.database).toEqual('dbname')
expect(config.host).toEqual('example.com')
expect(config.username).toEqual('user')
expect(config.password).toEqual('pass')
expect(config.port).toEqual(9821)
})
it('should work with no authentication options', function() {
var sequelize = new Sequelize('mysql://example.com:9821/dbname')
var config = sequelize.config
expect(config.username).toEqual(undefined)
expect(config.password).toEqual(null)
})
it('should use the default port when no other is specified', function() {
var sequelize = new Sequelize('mysql://example.com/dbname')
var config = sequelize.config
// The default port should be set
expect(config.port).toEqual(3306)
})
})
describe('Intantiation with arguments', function() {
it('should accept two parameters (database, username)', function() {
var sequelize = new Sequelize('dbname', 'root')
var config = sequelize.config
expect(config.database).toEqual('dbname')
expect(config.username).toEqual('root')
})
it('should accept three parameters (database, username, password)', function() {
var sequelize = new Sequelize('dbname', 'root', 'pass')
var config = sequelize.config
expect(config.database).toEqual('dbname')
expect(config.username).toEqual('root')
expect(config.password).toEqual('pass')
})
it('should accept four parameters (database, username, password, options)', function() {
var sequelize = new Sequelize('dbname', 'root', 'pass', { port: 999 })
var config = sequelize.config
expect(config.database).toEqual('dbname')
expect(config.username).toEqual('root')
expect(config.password).toEqual('pass')
expect(config.port).toEqual(999)
})
})
})
...@@ -2,7 +2,7 @@ if (typeof require === 'function') { ...@@ -2,7 +2,7 @@ if (typeof require === 'function') {
const buster = require("buster") const buster = require("buster")
, Helpers = require('./buster-helpers') , Helpers = require('./buster-helpers')
, dialect = Helpers.getTestDialect() , dialect = Helpers.getTestDialect()
, _ = require('underscore') , _ = require('lodash')
} }
buster.spec.expose() buster.spec.expose()
...@@ -19,7 +19,23 @@ describe(Helpers.getTestDialectTeaser("DAO"), function() { ...@@ -19,7 +19,23 @@ describe(Helpers.getTestDialectTeaser("DAO"), function() {
username: { type: DataTypes.STRING }, username: { type: DataTypes.STRING },
touchedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, touchedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },
aNumber: { type: DataTypes.INTEGER }, aNumber: { type: DataTypes.INTEGER },
bNumber: { type: DataTypes.INTEGER } bNumber: { type: DataTypes.INTEGER },
validateTest: {
type: DataTypes.INTEGER,
allowNull: true,
validate: {isInt: true}
},
validateCustom: {
type: DataTypes.STRING,
allowNull: true,
validate: {len: {msg: 'Length failed.', args: [1,20]}}
},
dateAllowNullTrue: {
type: DataTypes.DATE,
allowNull: true
}
}) })
self.HistoryLog = sequelize.define('HistoryLog', { self.HistoryLog = sequelize.define('HistoryLog', {
...@@ -27,10 +43,20 @@ describe(Helpers.getTestDialectTeaser("DAO"), function() { ...@@ -27,10 +43,20 @@ describe(Helpers.getTestDialectTeaser("DAO"), function() {
aNumber: { type: DataTypes.INTEGER }, aNumber: { type: DataTypes.INTEGER },
aRandomId: { type: DataTypes.INTEGER } aRandomId: { type: DataTypes.INTEGER }
}) })
self.ParanoidUser = sequelize.define('ParanoidUser', {
username: { type: DataTypes.STRING }
}, {
paranoid: true
})
self.ParanoidUser.hasOne( self.ParanoidUser )
}, },
onComplete: function() { onComplete: function() {
self.User.sync({ force: true }).success(function(){ self.User.sync({ force: true }).success(function(){
self.HistoryLog.sync({ force: true }).success(done) self.HistoryLog.sync({ force: true }).success(function(){
self.ParanoidUser.sync({force: true }).success(done)
})
}) })
} }
}) })
...@@ -320,6 +346,29 @@ describe(Helpers.getTestDialectTeaser("DAO"), function() { ...@@ -320,6 +346,29 @@ describe(Helpers.getTestDialectTeaser("DAO"), function() {
expect(+user.touchedAt).toBe(5000) expect(+user.touchedAt).toBe(5000)
}) })
}) })
describe('allowNull date', function() {
it('should be just "null" and not Date with Invalid Date', function(done) {
var self = this;
this.User.build({ username: 'a user'}).save().success(function() {
self.User.find({where: {username: 'a user'}}).success(function(user) {
expect(user.dateAllowNullTrue).toBe(null)
done()
})
})
})
it('should be the same valid date when saving the date', function(done) {
var self = this;
var date = new Date();
this.User.build({ username: 'a user', dateAllowNullTrue: date}).save().success(function() {
self.User.find({where: {username: 'a user'}}).success(function(user) {
expect(user.dateAllowNullTrue.toString()).toEqual(date.toString())
done()
})
})
})
})
}) })
describe('complete', function() { describe('complete', function() {
...@@ -341,6 +390,44 @@ describe(Helpers.getTestDialectTeaser("DAO"), function() { ...@@ -341,6 +390,44 @@ describe(Helpers.getTestDialectTeaser("DAO"), function() {
}) })
describe('save', function() { describe('save', function() {
it('should fail a validation upon creating', function(done){
this.User.create({aNumber: 0, validateTest: 'hello'}).error(function(err){
expect(err).toBeDefined()
expect(err).toBeObject()
expect(err.validateTest).toBeArray()
expect(err.validateTest[0]).toBeDefined()
expect(err.validateTest[0].indexOf('Invalid integer')).toBeGreaterThan(-1);
done();
});
})
it('should fail a validation upon building', function(done){
this.User.build({aNumber: 0, validateCustom: 'aaaaaaaaaaaaaaaaaaaaaaaaaa'}).save()
.error(function(err){
expect(err).toBeDefined()
expect(err).toBeObject()
expect(err.validateCustom).toBeDefined()
expect(err.validateCustom).toBeArray()
expect(err.validateCustom[0]).toBeDefined()
expect(err.validateCustom[0]).toEqual('Length failed.')
done()
})
})
it('should fail a validation when updating', function(done){
this.User.create({aNumber: 0}).success(function(user){
user.updateAttributes({validateTest: 'hello'}).error(function(err){
expect(err).toBeDefined()
expect(err).toBeObject()
expect(err.validateTest).toBeDefined()
expect(err.validateTest).toBeArray()
expect(err.validateTest[0]).toBeDefined()
expect(err.validateTest[0].indexOf('Invalid integer:')).toBeGreaterThan(-1)
done()
})
})
})
it('takes zero into account', function(done) { it('takes zero into account', function(done) {
this.User.build({ aNumber: 0 }).save([ 'aNumber' ]).success(function(user) { this.User.build({ aNumber: 0 }).save([ 'aNumber' ]).success(function(user) {
expect(user.aNumber).toEqual(0) expect(user.aNumber).toEqual(0)
...@@ -467,6 +554,51 @@ describe(Helpers.getTestDialectTeaser("DAO"), function() { ...@@ -467,6 +554,51 @@ describe(Helpers.getTestDialectTeaser("DAO"), function() {
}.bind(this)) }.bind(this))
}) })
it("creates the deletedAt property, when defining paranoid as true", function(done) {
this.ParanoidUser.create({ username: 'fnord' }).success(function() {
this.ParanoidUser.findAll().success(function(users) {
expect(users[0].deletedAt).toBeDefined()
expect(users[0].deletedAt).toBe(null)
done()
}.bind(this))
}.bind(this))
})
it("sets deletedAt property to a specific date when deleting an instance", function(done) {
this.ParanoidUser.create({ username: 'fnord' }).success(function() {
this.ParanoidUser.findAll().success(function(users) {
users[0].destroy().success(function(user) {
expect(user.deletedAt.getMonth).toBeDefined()
done()
}.bind(this))
}.bind(this))
}.bind(this))
})
it("keeps the deletedAt-attribute with value null, when running updateAttributes", function(done) {
this.ParanoidUser.create({ username: 'fnord' }).success(function() {
this.ParanoidUser.findAll().success(function(users) {
users[0].updateAttributes({username: 'newFnord'}).success(function(user) {
expect(user.deletedAt).toBe(null)
done()
}.bind(this))
}.bind(this))
}.bind(this))
})
it("keeps the deletedAt-attribute with value null, when updating associations", function(done) {
this.ParanoidUser.create({ username: 'fnord' }).success(function() {
this.ParanoidUser.findAll().success(function(users) {
this.ParanoidUser.create({ username: 'linkedFnord' }).success(function( linkedUser ) {
users[0].setParanoidUser( linkedUser ).success(function(user) {
expect(user.deletedAt).toBe(null)
done()
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
})
it("can reuse query option objects", function(done) { it("can reuse query option objects", function(done) {
this.User.create({ username: 'fnord' }).success(function() { this.User.create({ username: 'fnord' }).success(function() {
var query = { where: { username: 'fnord' }} var query = { where: { username: 'fnord' }}
...@@ -512,4 +644,44 @@ describe(Helpers.getTestDialectTeaser("DAO"), function() { ...@@ -512,4 +644,44 @@ describe(Helpers.getTestDialectTeaser("DAO"), function() {
}.bind(this)) }.bind(this))
}) })
}) })
describe('updateAttributes', function() {
it('stores and restores null values', function(done) {
var Download = this.sequelize.define('download', {
startedAt: Helpers.Sequelize.DATE,
canceledAt: Helpers.Sequelize.DATE,
finishedAt: Helpers.Sequelize.DATE
})
Download.sync({ force: true }).success(function() {
Download.create({
startedAt: new Date()
}).success(function(download) {
expect(download.startedAt instanceof Date).toBeTrue()
expect(download.canceledAt).toBeFalsy()
expect(download.finishedAt).toBeFalsy()
download.updateAttributes({
canceledAt: new Date()
}).success(function(download) {
expect(download.startedAt instanceof Date).toBeTrue()
expect(download.canceledAt instanceof Date).toBeTrue()
expect(download.finishedAt).toBeFalsy()
Download.all({
where: (dialect === 'postgres' ? '"finishedAt" IS NULL' : "`finishedAt` IS NULL")
}).success(function(downloads) {
downloads.forEach(function(download) {
expect(download.startedAt instanceof Date).toBeTrue()
expect(download.canceledAt instanceof Date).toBeTrue()
expect(download.finishedAt).toBeFalsy()
})
done()
})
})
})
})
})
})
}) })
...@@ -7,7 +7,7 @@ if(typeof require === 'function') { ...@@ -7,7 +7,7 @@ if(typeof require === 'function') {
buster.spec.expose() buster.spec.expose()
describe(Helpers.getTestDialectTeaser("DAO"), function() { describe(Helpers.getTestDialectTeaser("DaoValidator"), function() {
describe('validations', function() { describe('validations', function() {
before(function(done) { before(function(done) {
Helpers.initTests({ Helpers.initTests({
...@@ -42,6 +42,10 @@ describe(Helpers.getTestDialectTeaser("DAO"), function() { ...@@ -42,6 +42,10 @@ describe(Helpers.getTestDialectTeaser("DAO"), function() {
fail: "abc", fail: "abc",
pass: "129.89.23.1" pass: "129.89.23.1"
} }
, isIPv6 : {
fail: '1111:2222:3333::5555:',
pass: 'fe80:0000:0000:0000:0204:61ff:fe9d:f156'
}
, isAlpha : { , isAlpha : {
fail: "012", fail: "012",
pass: "abc" pass: "abc"
...@@ -278,5 +282,59 @@ describe(Helpers.getTestDialectTeaser("DAO"), function() { ...@@ -278,5 +282,59 @@ describe(Helpers.getTestDialectTeaser("DAO"), function() {
var successfulUser = User.build({ name : "2" }) var successfulUser = User.build({ name : "2" })
expect(successfulUser.validate()).toBeNull() expect(successfulUser.validate()).toBeNull()
}) })
it('skips other validations if allowNull is true and the value is null', function() {
var User = this.sequelize.define('User' + Math.random(), {
age: {
type: Sequelize.INTEGER,
allowNull: true,
validate: {
min: { args: 0, msg: 'must be positive' }
}
}
})
var failingUser = User.build({ age: -1 })
, errors = failingUser.validate()
expect(errors).not.toBeNull(null)
expect(errors).toEqual({ age: ['must be positive'] })
var successfulUser1 = User.build({ age: null })
expect(successfulUser1.validate()).toBeNull()
var successfulUser2 = User.build({ age: 1 })
expect(successfulUser2.validate()).toBeNull()
})
it('validates a model with custom model-wide validation methods', function() {
var Foo = this.sequelize.define('Foo' + Math.random(), {
field1: {
type: Sequelize.INTEGER,
allowNull: true
},
field2: {
type: Sequelize.INTEGER,
allowNull: true
}
}, {
validate: {
xnor: function() {
if ((this.field1 === null) === (this.field2 === null)) {
throw new Error('xnor failed');
}
}
}
})
var failingFoo = Foo.build({ field1: null, field2: null })
, errors = failingFoo.validate()
expect(errors).not.toBeNull()
expect(errors).toEqual({ 'xnor': ['xnor failed'] })
var successfulFoo = Foo.build({ field1: 33, field2: null })
expect(successfulFoo.validate()).toBeNull()
})
}) })
}) })
...@@ -7,7 +7,7 @@ if(typeof require === 'function') { ...@@ -7,7 +7,7 @@ if(typeof require === 'function') {
buster.spec.expose() buster.spec.expose()
describe(Helpers.getTestDialectTeaser('Data types'), function() { describe(Helpers.getTestDialectTeaser('DataTypes'), function() {
it('should return DECIMAL for the default decimal type', function() { it('should return DECIMAL for the default decimal type', function() {
expect(Sequelize.DECIMAL).toEqual('DECIMAL'); expect(Sequelize.DECIMAL).toEqual('DECIMAL');
}); });
...@@ -15,4 +15,52 @@ describe(Helpers.getTestDialectTeaser('Data types'), function() { ...@@ -15,4 +15,52 @@ describe(Helpers.getTestDialectTeaser('Data types'), function() {
it('should return DECIMAL(10,2) for the default decimal type with arguments', function() { it('should return DECIMAL(10,2) for the default decimal type with arguments', function() {
expect(Sequelize.DECIMAL(10, 2)).toEqual('DECIMAL(10,2)'); expect(Sequelize.DECIMAL(10, 2)).toEqual('DECIMAL(10,2)');
}); });
});
var tests = [
[Sequelize.STRING, 'STRING', 'VARCHAR(255)'],
[Sequelize.STRING(1234), 'STRING(1234)', 'VARCHAR(1234)'],
[Sequelize.STRING(1234).BINARY, 'STRING(1234).BINARY', 'VARCHAR(1234) BINARY'],
[Sequelize.STRING.BINARY, 'STRING.BINARY', 'VARCHAR(255) BINARY'],
[Sequelize.TEXT, 'TEXT', 'TEXT'],
[Sequelize.DATE, 'DATE', 'DATETIME'],
[Sequelize.NOW, 'NOW', 'NOW'],
[Sequelize.BOOLEAN, 'BOOLEAN', 'TINYINT(1)'],
[Sequelize.INTEGER, 'INTEGER', 'INTEGER'],
[Sequelize.INTEGER.UNSIGNED, 'INTEGER.UNSIGNED', 'INTEGER UNSIGNED'],
[Sequelize.INTEGER(11), 'INTEGER(11)','INTEGER(11)'],
[Sequelize.INTEGER(11).UNSIGNED, 'INTEGER(11).UNSIGNED', 'INTEGER(11) UNSIGNED'],
[Sequelize.INTEGER(11).UNSIGNED.ZEROFILL,'INTEGER(11).UNSIGNED.ZEROFILL','INTEGER(11) UNSIGNED ZEROFILL'],
[Sequelize.INTEGER(11).ZEROFILL,'INTEGER(11).ZEROFILL', 'INTEGER(11) ZEROFILL'],
[Sequelize.INTEGER(11).ZEROFILL.UNSIGNED,'INTEGER(11).ZEROFILL.UNSIGNED', 'INTEGER(11) UNSIGNED ZEROFILL'],
[Sequelize.BIGINT, 'BIGINT', 'BIGINT'],
[Sequelize.BIGINT.UNSIGNED, 'BIGINT.UNSIGNED', 'BIGINT UNSIGNED'],
[Sequelize.BIGINT(11), 'BIGINT(11)','BIGINT(11)'],
[Sequelize.BIGINT(11).UNSIGNED, 'BIGINT(11).UNSIGNED', 'BIGINT(11) UNSIGNED'],
[Sequelize.BIGINT(11).UNSIGNED.ZEROFILL, 'BIGINT(11).UNSIGNED.ZEROFILL','BIGINT(11) UNSIGNED ZEROFILL'],
[Sequelize.BIGINT(11).ZEROFILL, 'BIGINT(11).ZEROFILL', 'BIGINT(11) ZEROFILL'],
[Sequelize.BIGINT(11).ZEROFILL.UNSIGNED, 'BIGINT(11).ZEROFILL.UNSIGNED', 'BIGINT(11) UNSIGNED ZEROFILL'],
[Sequelize.FLOAT, 'FLOAT', 'FLOAT'],
[Sequelize.FLOAT.UNSIGNED, 'FLOAT.UNSIGNED', 'FLOAT UNSIGNED'],
[Sequelize.FLOAT(11), 'FLOAT(11)','FLOAT(11)'],
[Sequelize.FLOAT(11).UNSIGNED, 'FLOAT(11).UNSIGNED', 'FLOAT(11) UNSIGNED'],
[Sequelize.FLOAT(11).UNSIGNED.ZEROFILL,'FLOAT(11).UNSIGNED.ZEROFILL','FLOAT(11) UNSIGNED ZEROFILL'],
[Sequelize.FLOAT(11).ZEROFILL,'FLOAT(11).ZEROFILL', 'FLOAT(11) ZEROFILL'],
[Sequelize.FLOAT(11).ZEROFILL.UNSIGNED,'FLOAT(11).ZEROFILL.UNSIGNED', 'FLOAT(11) UNSIGNED ZEROFILL'],
[Sequelize.FLOAT(11, 12), 'FLOAT(11,12)','FLOAT(11,12)'],
[Sequelize.FLOAT(11, 12).UNSIGNED, 'FLOAT(11,12).UNSIGNED', 'FLOAT(11,12) UNSIGNED'],
[Sequelize.FLOAT(11, 12).UNSIGNED.ZEROFILL,'FLOAT(11,12).UNSIGNED.ZEROFILL','FLOAT(11,12) UNSIGNED ZEROFILL'],
[Sequelize.FLOAT(11, 12).ZEROFILL,'FLOAT(11,12).ZEROFILL', 'FLOAT(11,12) ZEROFILL'],
[Sequelize.FLOAT(11, 12).ZEROFILL.UNSIGNED,'FLOAT(11,12).ZEROFILL.UNSIGNED', 'FLOAT(11,12) UNSIGNED ZEROFILL']
]
tests.forEach(function(test) {
it('transforms "' + test[1] + '" to "' + test[2] + '"', function() {
expect(test[0]).toEqual(test[2])
})
})
})
if(typeof require === 'function') {
const buster = require("buster")
, CustomEventEmitter = require("../../lib/emitters/custom-event-emitter")
, Helpers = require('../buster-helpers')
, dialect = Helpers.getTestDialect()
}
buster.spec.expose()
buster.testRunner.timeout = 1000
describe(Helpers.getTestDialectTeaser("CustomEventEmitter"), function() {
describe("proxy", function () {
/* Tests could _probably_ be run synchronously, but for future proofing we're basing it on the events */
it("should correctly work with success listeners", function (done) {
var emitter = new CustomEventEmitter()
, proxy = new CustomEventEmitter()
, success = this.spy()
emitter.success(success)
proxy.success(function () {
process.nextTick(function () {
expect(success.called).toEqual(true)
done();
})
})
proxy.proxy(emitter)
proxy.emit('success')
})
it("should correctly work with error listeners", function (done) {
var emitter = new CustomEventEmitter()
, proxy = new CustomEventEmitter()
, error = this.spy()
emitter.error(error)
proxy.error(function () {
process.nextTick(function () {
expect(error.called).toEqual(true)
done();
})
})
proxy.proxy(emitter)
proxy.emit('error')
})
it("should correctly work with complete/done listeners", function (done) {
var emitter = new CustomEventEmitter()
, proxy = new CustomEventEmitter()
, complete = this.spy()
emitter.complete(complete)
proxy.complete(function () {
process.nextTick(function () {
expect(complete.called).toEqual(true)
done();
})
})
proxy.proxy(emitter)
proxy.emit('success')
})
});
});
\ No newline at end of file
...@@ -14,8 +14,8 @@ describe(Helpers.getTestDialectTeaser("Migrator"), function() { ...@@ -14,8 +14,8 @@ describe(Helpers.getTestDialectTeaser("Migrator"), function() {
before(function(done) { before(function(done) {
this.init = function(options, callback) { this.init = function(options, callback) {
options = Helpers.Sequelize.Utils._.extend({ options = Helpers.Sequelize.Utils._.extend({
path: __dirname + '/assets/migrations', path: __dirname + '/assets/migrations',
logging: false logging: function(){}
}, options || {}) }, options || {})
var migrator = new Migrator(this.sequelize, options) var migrator = new Migrator(this.sequelize, options)
......
...@@ -18,7 +18,8 @@ if (dialect.match(/^postgres/)) { ...@@ -18,7 +18,8 @@ if (dialect.match(/^postgres/)) {
self.User = sequelize.define('User', { self.User = sequelize.define('User', {
username: DataTypes.STRING, username: DataTypes.STRING,
email: {type: DataTypes.ARRAY(DataTypes.TEXT)} email: {type: DataTypes.ARRAY(DataTypes.TEXT)},
document: {type: DataTypes.HSTORE, defaultValue: 'default=>value'}
}) })
}, },
onComplete: function() { onComplete: function() {
...@@ -34,13 +35,90 @@ if (dialect.match(/^postgres/)) { ...@@ -34,13 +35,90 @@ if (dialect.match(/^postgres/)) {
this.User this.User
.create({ username: 'user', email: ['foo@bar.com', 'bar@baz.com'] }) .create({ username: 'user', email: ['foo@bar.com', 'bar@baz.com'] })
.success(function(oldUser) { .success(function(oldUser) {
expect(oldUser.email).toEqual(['foo@bar.com', 'bar@baz.com']); expect(oldUser.email).toEqual(['foo@bar.com', 'bar@baz.com'])
done(); done()
}) })
.error(function(err) { .error(function(err) {
console.log(err) console.log(err)
}) })
}) })
it("should handle hstore correctly", function(done) {
var self = this
this.User
.create({ username: 'user', email: ['foo@bar.com'], document: {hello: 'world'}})
.success(function(newUser) {
expect(newUser.document).toEqual({hello: 'world'})
// Check to see if updating an hstore field works
newUser.updateAttributes({document: {should: 'update', to: 'this', first: 'place'}}).success(function(oldUser){
// Postgres always returns keys in alphabetical order (ascending)
expect(oldUser.document).toEqual({first: 'place', should: 'update', to: 'this'})
// Check to see if the default value for an hstore field works
self.User.create({ username: 'user2', email: ['bar@baz.com']}).success(function(defaultUser){
expect(defaultUser.document).toEqual({default: 'value'})
done()
})
})
})
.error(console.log)
})
})
})
describe('[POSTGRES] Unquoted identifiers', function() {
before(function(done) {
var self = this
Helpers.initTests({
dialect: dialect,
beforeComplete: function(sequelize, DataTypes) {
self.sequelize = sequelize
self.sequelize.options.quoteIdentifiers = false
self.User = sequelize.define('User', {
username: DataTypes.STRING,
fullName: DataTypes.STRING // Note mixed case
})
},
onComplete: function() {
// We can create a table with non-quoted identifiers
self.User.sync({ force: true }).success(done)
}
})
})
it("can insert and select", function(done) {
var self = this
self.User
.create({ username: 'user', fullName: "John Smith" })
.success(function(user) {
// We can insert into a table with non-quoted identifiers
expect(user.id).toBeDefined()
expect(user.id).not.toBeNull()
expect(user.username).toEqual('user')
expect(user.fullName).toEqual('John Smith')
// We can query by non-quoted identifiers
self.User.find({
where: {fullName: "John Smith"}
})
.success(function(user2) {
// We can map values back to non-quoted identifiers
expect(user2.id).toEqual(user.id)
expect(user2.username).toEqual('user')
expect(user2.fullName).toEqual('John Smith')
done();
})
.error(function(err) {
console.log(err)
})
})
.error(function(err) {
console.log(err)
})
}) })
}) })
} }
if (typeof require === 'function') {
const buster = require("buster")
, Helpers = require('./buster-helpers')
, dialect = Helpers.getTestDialect()
, _ = require('lodash')
}
buster.spec.expose()
describe(Helpers.getTestDialectTeaser("Promise"), function () {
before(function (done) {
var self = this
Helpers.initTests({
dialect: dialect,
beforeComplete: function (sequelize, DataTypes) {
self.sequelize = sequelize
self.User = sequelize.define('User', {
username: { type: DataTypes.STRING },
touchedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },
aNumber: { type: DataTypes.INTEGER },
bNumber: { type: DataTypes.INTEGER },
validateTest: {
type: DataTypes.INTEGER,
allowNull: true,
validate: {isInt: true}
},
validateCustom: {
type: DataTypes.STRING,
allowNull: true,
validate: {len: {msg: 'Length failed.', args: [1, 20]}}
},
dateAllowNullTrue: {
type: DataTypes.DATE,
allowNull: true
}
})
self.HistoryLog = sequelize.define('HistoryLog', {
someText: { type: DataTypes.STRING },
aNumber: { type: DataTypes.INTEGER },
aRandomId: { type: DataTypes.INTEGER }
})
self.ParanoidUser = sequelize.define('ParanoidUser', {
username: { type: DataTypes.STRING }
}, {
paranoid: true
})
self.ParanoidUser.hasOne(self.ParanoidUser)
},
onComplete: function () {
self.User.sync({ force: true }).then(function () {
return self.HistoryLog.sync({ force: true })
}).then(function () {
return self.ParanoidUser.sync({force: true })
})
.then(function () {done()}, done)
}
})
})
describe('increment', function () {
before(function (done) {
this.User.create({ id: 1, aNumber: 0, bNumber: 0 }).done(done)
});
it('with array', function (done) {
var self = this;
// Select something
this.User.find(1).then(function (user1) {
return user1.increment(['aNumber'], 2)
}).then(function (user2) {
return self.User.find(1)
}).then(function (user3) {
expect(user3.aNumber).toBe(2);
done();
}, done);
});
it('should still work right with other concurrent updates', function (done) {
var self = this;
// Select something
this.User.find(1).then(function (user1) {
// Select the user again (simulating a concurrent query)
return self.User.find(1).then(function (user2) {
return user2.updateAttributes({
aNumber: user2.aNumber + 1
}).then(function (user3) {
return user1.increment(['aNumber'], 2)
}).then(function (user4) {
return self.User.find(1)
}).then(function (user5) {
expect(user5.aNumber).toBe(3);
done();
}, done)
});
});
});
it('with key value pair', function (done) {
var self = this;
// Select something
this.User.find(1).then(function (user1) {
return user1.increment({ 'aNumber': 1, 'bNumber': 2})
}).then(function () {
return self.User.find(1)
}).then(function (user3) {
expect(user3.aNumber).toBe(1);
expect(user3.bNumber).toBe(2);
done();
}, done);
});
});
describe('decrement', function () {
before(function (done) {
this.User.create({ id: 1, aNumber: 0, bNumber: 0 }).done(done)
});
it('with array', function (done) {
var self = this;
// Select something
this.User.find(1).then(function (user1) {
return user1.decrement(['aNumber'], 2)
}).then(function () {
return self.User.find(1);
}).then(function (user3) {
expect(user3.aNumber).toBe(-2);
done();
}, done);
});
it('with single field', function (done) {
var self = this;
// Select something
this.User.find(1).then(function (user1) {
return user1.decrement(['aNumber'], 2)
}).then(function () {
return self.User.find(1);
}).then(function (user3) {
expect(user3.aNumber).toBe(-2);
done();
}, done);
});
it('should still work right with other concurrent decrements', function (done) {
var self = this;
// Select something
this.User.find(1).then(function (user1) {
var _done = _.after(3, function () {
self.User.find(1).then(function (user2) {
expect(user2.aNumber).toEqual(-6);
done();
})
});
user1.decrement(['aNumber'], 2).done(_done);
user1.decrement(['aNumber'], 2).done(_done);
user1.decrement(['aNumber'], 2).done(_done);
});
});
});
describe('reload', function () {
it("should return a reference to the same DAO instead of creating a new one", function (done) {
this.User.create({ username: 'John Doe' }).then(function (originalUser) {
return originalUser.updateAttributes({ username: 'Doe John' }).then(function () {
return originalUser.reload()
}).then(function (updatedUser) {
expect(originalUser === updatedUser).toBeTrue()
done();
}, done)
})
})
it("should update the values on all references to the DAO", function (done) {
var self = this
this.User.create({ username: 'John Doe' }).then(function (originalUser) {
return self.User.find(originalUser.id).then(function (updater) {
return updater.updateAttributes({ username: 'Doe John' })
}).then(function () {
// We used a different reference when calling updateAttributes, so originalUser is now out of sync
expect(originalUser.username).toEqual('John Doe')
return originalUser.reload()
}).then(function (updatedUser) {
expect(originalUser.username).toEqual('Doe John')
expect(updatedUser.username).toEqual('Doe John')
done();
}, done)
})
})
it("should update the associations as well", function (done) {
var Book = this.sequelize.define('Book', { title: Helpers.Sequelize.STRING })
, Page = this.sequelize.define('Page', { content: Helpers.Sequelize.TEXT })
Book.hasMany(Page)
Page.belongsTo(Book)
this.sequelize.sync({ force: true }).then(function () {
return Book.create({ title: 'A very old book' })
}).then(function (book) {
return Page.create({ content: 'om nom nom' }).then(function (page) {
return book.setPages([ page ]).then(function () {
return Book.find({
where: (dialect === 'postgres' ? '"Books"."id"=' : '`Books`.`id`=') + book.id,
include: [Page]
}).then(function (leBook) {
return page.updateAttributes({ content: 'something totally different' }).then(function (page) {
expect(leBook.pages[0].content).toEqual('om nom nom')
expect(page.content).toEqual('something totally different')
return leBook.reload().then(function (leBook) {
expect(leBook.pages[0].content).toEqual('something totally different')
expect(page.content).toEqual('something totally different')
done()
})
})
})
})
})
}, done)
})
});
describe('complete', function () {
it("gets triggered if an error occurs", function (done) {
this.User.find({ where: "asdasdasd" }).then(null, function (err) {
expect(err).toBeDefined()
expect(err.message).toBeDefined()
done()
})
})
it("gets triggered if everything was ok", function (done) {
this.User.count().then(function (result) {
expect(result).toBeDefined()
done()
})
})
})
describe('save', function () {
it('should fail a validation upon creating', function (done) {
this.User.create({aNumber: 0, validateTest: 'hello'}).then(null, function (err) {
expect(err).toBeDefined()
expect(err).toBeObject()
expect(err.validateTest).toBeArray()
expect(err.validateTest[0]).toBeDefined()
expect(err.validateTest[0].indexOf('Invalid integer')).toBeGreaterThan(-1);
done();
});
})
it('should fail a validation upon building', function (done) {
this.User.build({aNumber: 0, validateCustom: 'aaaaaaaaaaaaaaaaaaaaaaaaaa'}).save()
.then(null, function (err) {
expect(err).toBeDefined()
expect(err).toBeObject()
expect(err.validateCustom).toBeDefined()
expect(err.validateCustom).toBeArray()
expect(err.validateCustom[0]).toBeDefined()
expect(err.validateCustom[0]).toEqual('Length failed.')
done()
})
})
it('should fail a validation when updating', function (done) {
this.User.create({aNumber: 0}).then(function (user) {
return user.updateAttributes({validateTest: 'hello'})
}).then(null, function (err) {
expect(err).toBeDefined()
expect(err).toBeObject()
expect(err.validateTest).toBeDefined()
expect(err.validateTest).toBeArray()
expect(err.validateTest[0]).toBeDefined()
expect(err.validateTest[0].indexOf('Invalid integer:')).toBeGreaterThan(-1)
done()
})
})
})
})
...@@ -93,7 +93,8 @@ describe(Helpers.getTestDialectTeaser("QueryInterface"), function() { ...@@ -93,7 +93,8 @@ describe(Helpers.getTestDialectTeaser("QueryInterface"), function() {
before(function(done) { before(function(done) {
this.interface.createTable('User', { this.interface.createTable('User', {
username: Helpers.Sequelize.STRING, username: Helpers.Sequelize.STRING,
isAdmin: Helpers.Sequelize.BOOLEAN isAdmin: Helpers.Sequelize.BOOLEAN,
enumVals: Helpers.Sequelize.ENUM('hello', 'world')
}).success(done) }).success(done)
}) })
...@@ -103,6 +104,7 @@ describe(Helpers.getTestDialectTeaser("QueryInterface"), function() { ...@@ -103,6 +104,7 @@ describe(Helpers.getTestDialectTeaser("QueryInterface"), function() {
var username = metadata.username var username = metadata.username
var isAdmin = metadata.isAdmin var isAdmin = metadata.isAdmin
var enumVals = metadata.enumVals
expect(username.type).toEqual(dialect === 'postgres' ? 'CHARACTER VARYING' : 'VARCHAR(255)') expect(username.type).toEqual(dialect === 'postgres' ? 'CHARACTER VARYING' : 'VARCHAR(255)')
expect(username.allowNull).toBeTrue() expect(username.allowNull).toBeTrue()
...@@ -112,6 +114,11 @@ describe(Helpers.getTestDialectTeaser("QueryInterface"), function() { ...@@ -112,6 +114,11 @@ describe(Helpers.getTestDialectTeaser("QueryInterface"), function() {
expect(isAdmin.allowNull).toBeTrue() expect(isAdmin.allowNull).toBeTrue()
expect(isAdmin.defaultValue).toBeNull() expect(isAdmin.defaultValue).toBeNull()
if (dialect === 'postgres') {
expect(enumVals.special).toBeArray();
expect(enumVals.special.length).toEqual(2);
}
done() done()
}) })
}) })
......
...@@ -194,5 +194,29 @@ describe(Helpers.getTestDialectTeaser("Sequelize"), function() { ...@@ -194,5 +194,29 @@ describe(Helpers.getTestDialectTeaser("Sequelize"), function() {
}) })
}) })
}) })
describe('table', function() {
[
{ id: { type: Helpers.Sequelize.BIGINT } },
{ id: { type: Helpers.Sequelize.STRING, allowNull: true } },
{ id: { type: Helpers.Sequelize.BIGINT, allowNull: false, primaryKey: true, autoIncrement: true } }
].forEach(function(customAttributes) {
it('should be able to override options on the default attributes', function(done) {
var Picture = this.sequelize.define('picture', Helpers.Sequelize.Utils._.cloneDeep(customAttributes))
Picture.sync({ force: true }).success(function() {
Object.keys(customAttributes).forEach(function(attribute) {
Object.keys(customAttributes[attribute]).forEach(function(option) {
var optionValue = customAttributes[attribute][option];
expect(Picture.rawAttributes[attribute][option]).toBe(optionValue)
});
})
done()
})
})
})
})
}) })
}) })
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!