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

Commit 8404668d by Jan Aagaard Meier

The mother of all merges

2 parents 655a603d 2203db2f
Showing with 1871 additions and 1702 deletions
...@@ -5,3 +5,5 @@ test*.js ...@@ -5,3 +5,5 @@ test*.js
node_modules node_modules
npm-debug.log npm-debug.log
*~ *~
test/binary/tmp/*
test/tmp/*
{ {
"globals": { "globals": {
"jasmine": false,
"spyOn": false, "spyOn": false,
"it": false, "it": false,
"console": false, "console": false,
...@@ -18,5 +17,6 @@ ...@@ -18,5 +17,6 @@
"unused": true, "unused": true,
"asi": true, "asi": true,
"evil": false, "evil": false,
"laxcomma": true "laxcomma": true,
"es5": true
} }
\ No newline at end of file
...@@ -3,12 +3,15 @@ before_script: ...@@ -3,12 +3,15 @@ before_script:
- "psql -c 'create database sequelize_test;' -U postgres" - "psql -c 'create database sequelize_test;' -U postgres"
script: script:
- "npm run test-buster-travis" - "make test"
- "npm run test-jasmine"
notifications: notifications:
email: email:
- sascha@depold.com - sascha@depold.com
hipchat:
- 40e8850aaba9854ac4c9963bd33f8b@253477
irc:
- "chat.freenode.net#sequelizejs"
env: env:
- DB=mysql DIALECT=mysql - DB=mysql DIALECT=mysql
...@@ -20,4 +23,10 @@ env: ...@@ -20,4 +23,10 @@ env:
language: node_js language: node_js
node_js: node_js:
- 0.8 - "0.8"
- "0.10"
branches:
only:
- master
- milestones/2.0.0
REPORTER ?= dot
TESTS = $(shell find ./test/* -name "*.test.js")
DIALECT ?= mysql
# test commands
teaser:
@echo "" && \
node -pe "Array(20 + '$(DIALECT)'.length + 3).join('#')" && \
echo '# Running tests for $(DIALECT) #' && \
node -pe "Array(20 + '$(DIALECT)'.length + 3).join('#')" && \
echo ''
test:
@make teaser && \
./node_modules/mocha/bin/mocha \
--colors \
--reporter $(REPORTER) \
$(TESTS)
mariadb:
@DIALECT=mariadb make test
sqlite:
@DIALECT=sqlite make test
mysql:
@DIALECT=mysql make test
postgres:
@DIALECT=postgres make test
postgres-native:
@DIALECT=postgres-native make test
binary:
@./test/binary/sequelize.test.bats
# test aliases
pgsql: postgres
postgresn: postgres-native
# test all the dialects \o/
all: sqlite mysql postgres postgres-native mariadb
.PHONY: sqlite mysql postgres pgsql postgres-native postgresn all test
# Sequelize # # Sequelize [![Build Status](https://secure.travis-ci.org/sequelize/sequelize.png)](http://travis-ci.org/sequelize/sequelize) [![Dependency Status](https://david-dm.org/sequelize/sequelize.png)](https://david-dm.org/sequelize/sequelize) [![Dependency Status](https://david-dm.org/sequelize/sequelize.png)](https://david-dm.org/sequelize/sequelize) [![Flattr this](http://api.flattr.com/button/flattr-badge-large.png)](http://flattr.com/thing/1259407/Sequelize) #
The Sequelize library provides easy access to MySQL, SQLite or PostgreSQL databases by mapping database entries to objects and vice versa. To put it in a nutshell... it's an ORM (Object-Relational-Mapper). The library is written entirely in JavaScript and can be used in the Node.JS environment. MySQL, PostgresSQL, and SQLite Object Relational Mapper (ORM) for [node](http://nodejs.org).
<a href="http://flattr.com/thing/1259407/Sequelize" target="_blank">
<img src="http://api.flattr.com/button/flattr-badge-large.png" alt="Flattr this" title="Flattr this" border="0" /></a>
## Important Notes ## ## Important Notes ##
### 2.0.0 ###
There is a parallel "branch" of the project, released as `2.0.0-alphaX` in NPM. All those releases are based on the master
and will get all the changes of the master. However, `2.0.0` will contain backwards compatibility breaking changes. Check the
changelog of the branch: https://github.com/sequelize/sequelize/blob/milestones/2.0.0/changelog.md
### 1.6.0 ### ### 1.6.0 ###
- We changed the way timestamps are handled. From v1.6.0 on timestamps are stored and loaded as UTC. - We changed the way timestamps are handled. From v1.6.0 on timestamps are stored and loaded as UTC.
...@@ -29,8 +32,10 @@ The Sequelize library provides easy access to MySQL, SQLite or PostgreSQL databa ...@@ -29,8 +32,10 @@ The Sequelize library provides easy access to MySQL, SQLite or PostgreSQL databa
- Asynchronous library - Asynchronous library
- Associations - Associations
- Importing definitions from single files - Importing definitions from single files
- Promises
- Hooks/callbacks/lifecycle events
## 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).
...@@ -42,6 +47,11 @@ Also make sure to take a look at the examples in the repository. The website wil ...@@ -42,6 +47,11 @@ Also make sure to take a look at the examples in the repository. The website wil
- [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 :)
...@@ -49,14 +59,15 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl ...@@ -49,14 +59,15 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl
### 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
- Associations of not yet saved objects: [#864](https://github.com/sequelize/sequelize/issues/864)
- 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~~ Implemented in [#569](https://github.com/sequelize/sequelize/pull/569) thanks to @optiltude - ~~Support for update and delete calls for whole tables without previous loading of instances~~ Implemented in [#569](https://github.com/sequelize/sequelize/pull/569) thanks to @optiltude
- Eager loading of nested associations [#388](https://github.com/sdepold/sequelize/issues/388#issuecomment-12019099) - Eager loading of nested associations [#388](https://github.com/sequelize/sequelize/issues/388)
- Model#delete - ~~Model#delete~~ (renamed to [Model.destroy()](http://sequelizejs.com/documentation#instances-destroy))
- ~~Validate a model before it gets saved.~~ Implemented in [#601](https://github.com/sequelize/sequelize/pull/601), thanks to @durango - ~~Validate a model before it gets saved.~~ Implemented in [#601](https://github.com/sequelize/sequelize/pull/601), thanks to @durango
- Move validation of enum attribute value to validate method - Move validation of enum attribute value to validate method
- BLOB [#99](https://github.com/sequelize/sequelize/issues/99) - ~~BLOB~~ [#842](https://github.com/sequelize/sequelize/pull/842), thanks to @janmeier
- ~~Support for foreign keys~~ Implemented in [#595](https://github.com/sequelize/sequelize/pull/595), thanks to @optilude - ~~Support for foreign keys~~ Implemented in [#595](https://github.com/sequelize/sequelize/pull/595), thanks to @optilude
### 1.7.x ### 1.7.x
...@@ -70,8 +81,12 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl ...@@ -70,8 +81,12 @@ 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 - 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 ##
...@@ -126,32 +141,27 @@ $ npm install ...@@ -126,32 +141,27 @@ $ npm install
### 4. Run the tests ### ### 4. Run the tests ###
Right now, the test base is split into the `spec` folder (which contains the Right now, the test base is split into the `test` folder (which contains the
lovely [BusterJS](http://busterjs.org) tests) and the `spec-jasmine` folder lovely [Mocha](http://visionmedia.github.io/mocha/) tests).
(which contains the ugly and awkward node-jasmine based tests). A main goal
is to get rid of the jasmine tests!
As you might haven't installed all of the supported SQL dialects, here is how As you might haven't installed all of the supported SQL dialects, here is how
to run the test suites for your development environment: to run the test suites for your development environment:
```console ```console
$ # run all tests at once: $ # run all tests at once:
$ npm test $ make all
$ # run only the jasmine tests (for all dialects):
$ npm run test-jasmine
$ # run all of the buster specs (for all dialects):
$ npm run test-buster
$ # run the buster specs for mysql: $ # run the buster specs for mysql:
$ npm run test-buster-mysql $ make mysql
$ # run the buster specs for sqlite: $ # run the buster specs for sqlite:
$ npm run test-buster-sqlite $ make sqlite
$ # run the buster specs for postgresql: $ # run the buster specs for postgresql:
$ npm run test-buster-postgres $ make pgsql
$ # alternatively you can pass database credentials with $variables when testing
$ DIALECT=dialect SEQ_DB=database SEQ_USER=user SEQ_PW=password make test
``` ```
### 5. That's all ### ### 5. That's all ###
...@@ -220,6 +230,17 @@ for (var key in obj) { ...@@ -220,6 +230,17 @@ for (var key in obj) {
```js ```js
{ {
"globals": {
"spyOn": false,
"it": false,
"console": false,
"describe": false,
"expect": false,
"beforeEach": false,
"waits": false,
"waitsFor": false,
"runs": false
},
"camelcase": true, "camelcase": true,
"curly": true, "curly": true,
"forin": true, "forin": true,
...@@ -227,13 +248,7 @@ for (var key in obj) { ...@@ -227,13 +248,7 @@ for (var key in obj) {
"unused": true, "unused": true,
"asi": true, "asi": true,
"evil": false, "evil": false,
"laxcomma": true "laxcomma": true,
"es5": true
} }
``` ```
# Build status
The automated tests we talk about just so much are running on
[Travis public CI](http://travis-ci.org), here is its status:
[![Build Status](https://secure.travis-ci.org/sequelize/sequelize.png)](http://travis-ci.org/sequelize/sequelize)
#!/usr/bin/env node #!/usr/bin/env node
const path = require("path") var path = require("path")
, fs = require("fs") , fs = require("fs")
, program = require("commander") , program = require("commander")
, Sequelize = require(__dirname + '/../index') , Sequelize = require(__dirname + '/../index')
, moment = require("moment") , moment = require("moment")
, _ = Sequelize.Utils._ , _ = Sequelize.Utils._
var configPath = process.cwd() + '/config' var configuration = {
, environment = process.env.NODE_ENV || 'development' configFile: process.cwd() + '/config/config.json',
, migrationsPath = process.cwd() + '/migrations' environment: process.env.NODE_ENV || 'development',
, packageJsonPath = __dirname + '/../package.json' version: require(__dirname + '/../package.json').version,
, packageJson = JSON.parse(fs.readFileSync(packageJsonPath).toString()) migrationsPath: process.cwd() + '/migrations'
, configFile = configPath + '/config.json' }
, configPathExists = fs.existsSync(configPath)
, configFileExists = fs.existsSync(configFile) var configFileExists = function() {
return fs.existsSync(configuration.configFile)
}
var writeConfig = function(config) { var relativeConfigFile = function() {
!configPathExists && fs.mkdirSync(configPath) return path.relative(process.cwd(), configuration.configFile)
}
var writeDefaultConfig = function(config) {
var configPath = path.dirname(configuration.configFile)
if (!fs.existsSync(configPath)) {
fs.mkdirSync(configPath)
}
config = JSON.stringify(config) config = JSON.stringify({
config = config.replace('{', '{\n ') development: {
config = config.replace(/,/g, ",\n ") username: "root",
config = config.replace('}', "\n}") password: null,
database: 'database_development',
host: '127.0.0.1'
},
test: {
username: "root",
password: null,
database: 'database_test',
host: '127.0.0.1'
},
production: {
username: "root",
password: null,
database: 'database_production',
host: '127.0.0.1'
}
}, undefined, 2) + "\n"
fs.writeFileSync(configFile, config) fs.writeFileSync(configuration.configFile, config)
} }
var createMigrationsFolder = function(force) { var createMigrationsFolder = function(force) {
if(force) { if(force) {
console.log('Deleting the migrations folder.') console.log('Deleting the migrations folder. (--force)')
try { try {
fs.readdirSync(migrationsPath).forEach(function(filename) { fs.readdirSync(configuration.migrationsPath).forEach(function(filename) {
fs.unlinkSync(migrationsPath + '/' + filename) fs.unlinkSync(configuration.migrationsPath + '/' + filename)
}) })
} catch(e) {} } catch(e) {}
try { try {
fs.rmdirSync(migrationsPath) fs.rmdirSync(configuration.migrationsPath)
console.log('Successfully deleted the migrations folder.') console.log('Successfully deleted the migrations folder.')
} catch(e) {} } catch(e) {}
} }
console.log('Creating migrations folder.')
try { try {
fs.mkdirSync(migrationsPath) fs.mkdirSync(configuration.migrationsPath)
console.log('Successfully create migrations folder.') console.log('Successfully created migrations folder at "' + configuration.migrationsPath + '".')
} catch(e) { } catch(e) {
console.log('Migrations folder already exist.')
} }
} }
var readConfig = function() { var readConfig = function() {
var config var config
try { try {
config = fs.readFileSync(configFile) config = require(configuration.configFile);
} catch(e) { } catch(e) {
throw new Error('Error reading "config/config.json".') throw new Error('Error reading "' + relativeConfigFile() + '".')
} }
try { if(typeof config != 'object') {
config = JSON.parse(config) throw new Error('Config must be an object: ' + relativeConfigFile());
} catch (e) {
throw new Error('Error parsing "config/config.json" as JSON.')
} }
if (config[environment]) { console.log('Loaded configuration file "' + relativeConfigFile() + '".')
config = config[environment] if (config[configuration.environment]) {
console.log('Using environment "' + configuration.environment + '".')
config = config[configuration.environment]
} }
return config return config
} }
program program
.version(packageJson.version) .version(configuration.version)
.option('-i, --init', 'Initializes the project.') .option('-i, --init', 'Initializes the project.')
.option('-e, --env <environment>', 'Specify the environment.') .option('-e, --env <environment>', 'Specify the environment.')
.option('-m, --migrate', 'Run pending migrations.') .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]', 'Creates a new migration.') .option('-c, --create-migration [migration-name]', 'Creates a new migration.')
.option('--config <config_file>', 'Specifies alternate config file.')
.parse(process.argv) .parse(process.argv)
if(typeof program.config === 'string') {
configuration.configFile = program.config
}
if(typeof program.env === 'string') { if(typeof program.env === 'string') {
environment = program.env configuration.environment = program.env
} }
console.log("Using environment '" + environment + "'.")
if(program.migrate) { if (program.migrate) {
if(configFileExists) { if (configFileExists()) {
var config var config
, options = {} , options = {}
...@@ -101,57 +129,58 @@ if(program.migrate) { ...@@ -101,57 +129,58 @@ if(program.migrate) {
if(['database', 'username', 'password'].indexOf(key) == -1) { if(['database', 'username', 'password'].indexOf(key) == -1) {
options[key] = value options[key] = value
} }
if (key === "use_env_variable") {
if (process.env[value]) {
var db_info = process.env[value].match(
/([^:]+):\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/(.+)/);
config.database = db_info[6];
config.username = db_info[2];
config.password = db_info[3];
options = _.extend(options, {
host: db_info[4],
port: db_info[5],
dialect: db_info[1],
protocol: db_info[1]
});
}
}
}) })
options = _.extend(options, { logging: false }) options = _.extend(options, { logging: false })
var sequelize = new Sequelize(config.database, config.username, config.password, options) var sequelize = new Sequelize(config.database, config.username, config.password, options)
, migratorOptions = { path: migrationsPath } , migratorOptions = { path: configuration.migrationsPath }
, migrator = sequelize.getMigrator(migratorOptions) , migrator = sequelize.getMigrator(migratorOptions)
if(program.undo) { if (program.undo) {
sequelize.migrator.findOrCreateSequelizeMetaDAO().success(function(Meta) { sequelize.migrator.findOrCreateSequelizeMetaDAO().success(function(Meta) {
Meta.find({ order: 'id DESC' }).success(function(meta) { Meta.find({ order: 'id DESC' }).success(function(meta) {
if(meta) { if (meta) {
migrator = sequelize.getMigrator(_.extend(migratorOptions, meta), true) migrator = sequelize.getMigrator(_.extend(migratorOptions, meta), true)
} }
migrator.migrate({ method: 'down' }) migrator.migrate({ method: 'down' }).success(function() {
process.exit(0)
})
}) })
}) })
} else { } else {
sequelize.migrate() sequelize.migrate().success(function() {
process.exit(0)
})
} }
} else { } else {
console.log('Cannot find "config/config.json". Have you run "sequelize --init"?') console.log('Cannot find "' + relativeConfigFile() + '". Have you run "sequelize --init"?')
process.exit(1) process.exit(1)
} }
} else if(program.init) { } else if(program.init) {
if(!configFileExists || !!program.force) { if(!configFileExists() || !!program.force) {
writeConfig({ writeDefaultConfig()
development: {
username: "root",
password: null,
database: 'database_development',
host: '127.0.0.1'
},
test: {
username: "root",
password: null,
database: 'database_test',
host: '127.0.0.1'
},
production: {
username: "root",
password: null,
database: 'database_production',
host: '127.0.0.1'
}
})
console.log('Created "config/config.json"') console.log('Created "' + relativeConfigFile() + '"')
} else { } else {
console.log('"config/config.json" already exists. Run "sequelize --init --force" to overwrite.') console.log('The file "' + relativeConfigFile() + '" already exists. Run "sequelize --init --force" to overwrite it.')
process.exit(1) process.exit(1)
} }
...@@ -175,9 +204,11 @@ if(program.migrate) { ...@@ -175,9 +204,11 @@ if(program.migrate) {
" done()", " done()",
" }", " }",
"}" "}"
].join('\n') ].join('\n') + "\n"
fs.writeFileSync(migrationsPath + '/' + migrationName, migrationContent) fs.writeFileSync(configuration.migrationsPath + '/' + migrationName, migrationContent)
console.log('New migration "' + migrationName + '" was added to "' +
path.relative(process.cwd(), configuration.migrationsPath) + '/".')
} else { } else {
console.log('Try "sequelize --help" for usage information.') console.log('No action specified. Try "sequelize --help" for usage information.')
} }
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
*/ */
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 })
......
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 })
......
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',
......
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")
......
/*jslint node:true */
"use strict";
var Sequelize = require('sequelize');
// initialize database connection
var sequelize = new Sequelize('testsequelize', 'testsequelize', 'testsequelize', {
dialect: 'postgres',
port: 5432,
define: {
freezeTableName: true
}
});
// load models
var models = [
'Trainer',
'Series',
'Video'
];
models.forEach(function(model) {
module.exports[model] = sequelize.import(__dirname + '/' + model);
});
// describe relationships
(function(m) {
m.Series.hasOne(m.Video);
m.Trainer.hasMany(m.Series);
})(module.exports);
// export connection
module.exports.sequelize = sequelize;
\ No newline at end of file
/*jslint node:true */
"use strict";
module.exports = function(sequelize, DataTypes) {
return sequelize.define('Series', {
title: {
type: DataTypes.STRING
},
sub_title: {
type: DataTypes.STRING
},
description: {
type: DataTypes.TEXT
},
// Set FK relationship (hasMany) with `Trainer`
trainer_id: {
type: DataTypes.INTEGER,
references: "Trainer",
referencesKey: 'id'
}
}, {
// don't need timestamp attributes for this model
timestamps: false,
underscored: true
});
};
/*jslint node:true */
"use strict";
module.exports = function(sequelize, DataTypes) {
return sequelize.define('Trainer', {
first_name: {
type: DataTypes.STRING
},
last_name: {
type: DataTypes.STRING
}
}, {
// don't need timestamp attributes for this model
timestamps: false,
underscored: true
});
};
/*jslint node:true */
"use strict";
module.exports = function(sequelize, DataTypes) {
return sequelize.define('Video', {
title: {
type: DataTypes.STRING
},
sequence: {
type: DataTypes.INTEGER
},
description: {
type: DataTypes.TEXT
},
// set relationship (hasOne) with `Series`
series_id: {
type: DataTypes.INTEGER,
references: "Series",
referencesKey: 'id'
}
}, {
// don't need timestamp attributes for this model
timestamps: false,
underscored: true
});
};
Results after syncing the dabatase
----------------------------------
After syncing the database, you'll see in the console the following:
```
Executing: CREATE TABLE IF NOT EXISTS "Trainer" ("first_name" VARCHAR(255), "last_name" VARCHAR(255), "id" SERIAL , PRIMARY KEY ("id"));
Executing: CREATE TABLE IF NOT EXISTS "Series" ("title" VARCHAR(255), "sub_title" VARCHAR(255), "description" TEXT, "trainer_id" INTEGER REFERENCES "Trainer" ("id"), "id" SERIAL , PRIMARY KEY ("id"));
Executing: CREATE TABLE IF NOT EXISTS "Video" ("title" VARCHAR(255), "sequence" INTEGER, "description" TEXT, "series_id" INTEGER REFERENCES "Series" ("id"), "id" SERIAL , PRIMARY KEY ("id"));
```
Notice in the `Video` that `series_id` field has a referential integrity to `Series`:
```
"series_id" INTEGER REFERENCES "Series" ("id")
```
This is the output when describing the table's structure of the Postgres database:
**Trainer** table:
```
testsequelize=> \d+ "Trainer";
Table "public.Trainer"
Column | Type | Modifiers |
------------+------------------------+--------------------------------------------------------+
first_name | character varying(255) | |
last_name | character varying(255) | |
id | integer | not null default nextval('"Trainer_id_seq"'::regclass) |
Indexes:
"Trainer_pkey" PRIMARY KEY, btree (id)
Referenced by:
TABLE ""Series"" CONSTRAINT "Series_trainer_id_fkey" FOREIGN KEY (trainer_id) REFERENCES "Trainer"(id)
Has OIDs: no
```
**Series** table:
```
testsequelize=> \d+ "Series";
Table "public.Series"
Column | Type | Modifiers |
-----------------+------------------------+-------------------------------------------------------+
title | character varying(255) | |
sub_title | character varying(255) | |
description | text | |
trainer_id | integer | |
id | integer | not null default nextval('"Series_id_seq"'::regclass) |
Indexes:
"Series_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
"Series_trainer_id_fkey" FOREIGN KEY (trainer_id) REFERENCES "Trainer"(id)
Referenced by:
TABLE ""Video"" CONSTRAINT "Video_series_id_fkey" FOREIGN KEY (series_id) REFERENCES "Series"(id)
Has OIDs: no
```
**Video** table:
```
testsequelize=> \d+ "Video";
Table "public.Video"
Column | Type | Modifiers |
-------------+------------------------+------------------------------------------------------+
title | character varying(255) | |
sequence | integer | |
description | text | |
series_id | integer | |
id | integer | not null default nextval('"Video_id_seq"'::regclass) |
Indexes:
"Video_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
"Video_series_id_fkey" FOREIGN KEY (series_id) REFERENCES "Series"(id)
Has OIDs: no
```
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")
console.log("creating database entry") var Sequelize = require(__dirname + "/../../index")
Image.create({data: image}).on('success', function(img) { , config = require(__dirname + "/../../spec/config/config")
console.log("done\n") , sequelize = new Sequelize(config.database, config.username, config.password, {logging: false})
console.log("writing file") var User = sequelize.define('User', {
fs.writeFileSync(__dirname + '/target.png', img.data, "base64") name: { type: Sequelize.STRING, allowNull: false},
console.log("done\n") isAdmin: { type: Sequelize.BOOLEAN, allowNull: false, defaultValue: false }
})
, user = User.build({ name: 'Someone' })
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)
console.log("you might open the file ./target.png") 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
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
*/ */
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 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',
......
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")
......
...@@ -11,9 +11,11 @@ module.exports = (function() { ...@@ -11,9 +11,11 @@ module.exports = (function() {
this.isSelfAssociation = (this.source.tableName == this.target.tableName) this.isSelfAssociation = (this.source.tableName == this.target.tableName)
if (this.isSelfAssociation && !this.options.foreignKey && !!this.options.as) { if (this.isSelfAssociation && !this.options.foreignKey && !!this.options.as) {
this.options.foreignKey = Utils._.underscoredIf(Utils.singularize(this.options.as) + "Id", this.source.options.underscored) this.options.foreignKey = Utils._.underscoredIf(Utils.singularize(this.options.as, this.source.options.language) + "Id", this.source.options.underscored)
} }
this.options.useHooks = options.useHooks
this.associationAccessor = this.isSelfAssociation this.associationAccessor = this.isSelfAssociation
? Utils.combineTableNames(this.target.tableName, this.options.as || this.target.tableName) ? Utils.combineTableNames(this.target.tableName, this.options.as || this.target.tableName)
: this.options.as || this.target.tableName : this.options.as || this.target.tableName
...@@ -22,28 +24,37 @@ module.exports = (function() { ...@@ -22,28 +24,37 @@ module.exports = (function() {
// the id is in the source table // the id is in the source table
BelongsTo.prototype.injectAttributes = function() { BelongsTo.prototype.injectAttributes = function() {
var newAttributes = {} var newAttributes = {}
, targetKeys = Object.keys(this.target.primaryKeys)
, keyType = ((this.target.hasPrimaryKeys && targetKeys.length === 1) ? this.target.rawAttributes[targetKeys[0]].type : DataTypes.INTEGER)
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, this.target.options.language) + "Id", this.source.options.underscored)
newAttributes[this.identifier] = { type: DataTypes.INTEGER } newAttributes[this.identifier] = { type: this.options.keyType || keyType }
Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.target, this.source, this.options) 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
this.source.DAO.prototype.attributes = Object.keys(this.source.DAO.prototype.rawAttributes); this.source.DAO.prototype.attributes = Object.keys(this.source.DAO.prototype.rawAttributes)
return this return this
} }
BelongsTo.prototype.injectGetter = function(obj) { BelongsTo.prototype.injectGetter = function(obj) {
var self = this var self = this
, accessor = Utils._.camelize('get_' + (this.options.as || Utils.singularize(this.target.tableName))) , accessor = Utils._.camelize('get_' + (this.options.as || Utils.singularize(this.target.tableName, this.target.options.language)))
, primaryKeys = Object.keys(self.target.primaryKeys)
, primaryKey = primaryKeys.length === 1 ? primaryKeys[0] : 'id'
obj[accessor] = function(params) { obj[accessor] = function(params) {
var id = this[self.identifier] var id = this[self.identifier]
, where = {}
where[primaryKey] = id
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(where, params.where)
} else {
params.where = where
} }
} else { } else {
params = id params = id
...@@ -57,13 +68,16 @@ module.exports = (function() { ...@@ -57,13 +68,16 @@ module.exports = (function() {
BelongsTo.prototype.injectSetter = function(obj) { BelongsTo.prototype.injectSetter = function(obj) {
var self = this var self = this
, accessor = Utils._.camelize('set_' + (this.options.as || Utils.singularize(this.target.tableName))) , accessor = Utils._.camelize('set_' + (this.options.as || Utils.singularize(this.target.tableName, this.target.options.language)))
obj[accessor] = function(associatedObject) { obj[accessor] = function(associatedObject) {
this[self.identifier] = associatedObject ? associatedObject.id : null var primaryKeys = !!associatedObject && !!associatedObject.daoFactory ? Object.keys(associatedObject.daoFactory.primaryKeys) : []
, primaryKey = primaryKeys.length === 1 ? primaryKeys[0] : 'id'
this[self.identifier] = associatedObject ? associatedObject[primaryKey] : null
// passes the changed field to save, so only that field get updated. // passes the changed field to save, so only that field get updated.
return this.save([ self.identifier ]) return this.save([ self.identifier ], {allowNull: [self.identifier]})
} }
return this return this
......
...@@ -10,23 +10,36 @@ module.exports = (function() { ...@@ -10,23 +10,36 @@ module.exports = (function() {
var self = this, _options = options var self = this, _options = options
var customEventEmitter = new Utils.CustomEventEmitter(function() { var customEventEmitter = new Utils.CustomEventEmitter(function() {
var where = {}, options = _options || {}; var where = {}, options = _options || {}
//fully qualify //fully qualify
where[self.__factory.connectorDAO.tableName+"."+self.__factory.identifier] = self.instance.id var instancePrimaryKeys = Object.keys(self.instance.daoFactory.primaryKeys)
, instancePrimaryKey = instancePrimaryKeys.length > 0 ? instancePrimaryKeys[0] : 'id'
where[self.__factory.connectorDAO.tableName+"."+self.__factory.identifier] = self.instance[instancePrimaryKey]
var primaryKeys = Object.keys(self.__factory.connectorDAO.rawAttributes) var primaryKeys = Object.keys(self.__factory.connectorDAO.rawAttributes)
, foreignKey = primaryKeys.filter(function(pk) { return pk != self.__factory.identifier })[0] , foreignKey = primaryKeys.filter(function(pk) { return pk != self.__factory.identifier })[0]
, foreignPrimary = Object.keys(self.__factory.target.primaryKeys)
where[self.__factory.connectorDAO.tableName+"."+foreignKey] = {join: self.__factory.target.tableName+".id"} foreignPrimary = foreignPrimary.length === 1 ? foreignPrimary[0] : 'id'
if (options.where) { where[self.__factory.connectorDAO.tableName+"."+foreignKey] = {join: self.__factory.target.tableName+"."+foreignPrimary}
Utils._.each(options.where, function(value, index) {
delete options.where[index];
options.where[self.__factory.target.tableName+"."+index] = value;
});
Utils._.extend(options.where, where) if (options.where) {
if (Array.isArray(options.where)) {
smart = Utils.smartWhere([where, options.where], self.__factory.target.daoFactoryManager.sequelize.options.dialect)
smart = Utils.compileSmartWhere.call(self.__factory.target, smart, self.__factory.target.daoFactoryManager.sequelize.options.dialect)
if (smart.length > 0) {
options.where = smart
}
} else {
smart = Utils.smartWhere([where, options.where], self.__factory.target.daoFactoryManager.sequelize.options.dialect)
smart = Utils.compileSmartWhere.call(self.__factory.target, smart, self.__factory.target.daoFactoryManager.sequelize.options.dialect)
if (smart.length > 0) {
options.where = smart
}
}
} else { } else {
options.where = where; options.where = where;
} }
...@@ -42,35 +55,55 @@ module.exports = (function() { ...@@ -42,35 +55,55 @@ module.exports = (function() {
HasManyDoubleLinked.prototype.injectSetter = function(emitterProxy, oldAssociations, newAssociations) { HasManyDoubleLinked.prototype.injectSetter = function(emitterProxy, oldAssociations, newAssociations) {
var self = this var self = this
, chainer = new Utils.QueryChainer()
destroyObsoleteAssociations
.call(this, oldAssociations, newAssociations)
.on('sql', function(sql) { emitterProxy.emit('sql', sql) })
.error(function(err) { emitterProxy.emit('error', err) })
.success(function() {
var chainer = new Utils.QueryChainer
, association = self.__factory.target.associations[self.__factory.associationAccessor] , association = self.__factory.target.associations[self.__factory.associationAccessor]
, foreignIdentifier = association.isSelfAssociation ? association.foreignIdentifier : association.identifier , foreignIdentifier = association.isSelfAssociation ? association.foreignIdentifier : association.identifier
, unassociatedObjects = newAssociations.filter(function (obj) { , sourceKeys = Object.keys(self.__factory.source.primaryKeys)
, targetKeys = Object.keys(self.__factory.target.primaryKeys)
var obsoleteAssociations = oldAssociations.filter(function (old) {
// Return only those old associations that are not found in new
return !Utils._.find(newAssociations, function (obj) {
return ((targetKeys.length === 1) ? obj[targetKeys[0]] === old[targetKeys[0]] : obj.id === old.id)
})
})
var unassociatedObjects = newAssociations.filter(function (obj) {
// Return only those associations that are new
return !Utils._.find(oldAssociations, function (old) { return !Utils._.find(oldAssociations, function (old) {
return obj.id === old.id return ((targetKeys.length === 1) ? obj[targetKeys[0]] === old[targetKeys[0]] : obj.id === old.id)
}) })
}) })
unassociatedObjects.forEach(function(unassociatedObject) { if (obsoleteAssociations.length > 0) {
var foreignIds = obsoleteAssociations.map(function (associatedObject) {
return ((targetKeys.length === 1) ? associatedObject[targetKeys[0]] : associatedObject.id)
})
var where = {}
where[self.__factory.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id)
where[foreignIdentifier] = foreignIds
chainer.add(self.__factory.connectorDAO.destroy(where))
}
if (unassociatedObjects.length > 0) {
var bulk = unassociatedObjects.map(function(unassociatedObject) {
var attributes = {} var attributes = {}
attributes[self.__factory.identifier] = self.instance.id attributes[self.__factory.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id)
attributes[foreignIdentifier] = unassociatedObject.id attributes[foreignIdentifier] = ((targetKeys.length === 1) ? unassociatedObject[targetKeys[0]] : unassociatedObject.id)
chainer.add(self.__factory.connectorDAO.create(attributes)) return attributes
}) })
chainer.add(self.__factory.connectorDAO.bulkCreate(bulk))
}
chainer chainer
.run() .run()
.success(function() { emitterProxy.emit('success', newAssociations) }) .success(function() { emitterProxy.emit('success', newAssociations) })
.error(function(err) { emitterProxy.emit('error', err) }) .error(function(err) { emitterProxy.emit('error', err) })
.on('sql', function(sql) { emitterProxy.emit('sql', sql) }) .on('sql', function(sql) { emitterProxy.emit('sql', sql) })
})
} }
HasManyDoubleLinked.prototype.injectAdder = function(emitterProxy, newAssociation) { HasManyDoubleLinked.prototype.injectAdder = function(emitterProxy, newAssociation) {
...@@ -78,8 +111,11 @@ module.exports = (function() { ...@@ -78,8 +111,11 @@ module.exports = (function() {
, association = this.__factory.target.associations[this.__factory.associationAccessor] , association = this.__factory.target.associations[this.__factory.associationAccessor]
, foreignIdentifier = association.isSelfAssociation ? association.foreignIdentifier : association.identifier; , foreignIdentifier = association.isSelfAssociation ? association.foreignIdentifier : association.identifier;
attributes[this.__factory.identifier] = this.instance.id var sourceKeys = Object.keys(this.__factory.source.primaryKeys);
attributes[foreignIdentifier] = newAssociation.id var targetKeys = Object.keys(this.__factory.target.primaryKeys);
attributes[this.__factory.identifier] = ((sourceKeys.length === 1) ? this.instance[sourceKeys[0]] : this.instance.id)
attributes[foreignIdentifier] = ((targetKeys.length === 1) ? newAssociation[targetKeys[0]] : newAssociation.id)
this.__factory.connectorDAO.create(attributes) this.__factory.connectorDAO.create(attributes)
.success(function() { emitterProxy.emit('success', newAssociation) }) .success(function() { emitterProxy.emit('success', newAssociation) })
...@@ -87,57 +123,5 @@ module.exports = (function() { ...@@ -87,57 +123,5 @@ module.exports = (function() {
.on('sql', function(sql) { emitterProxy.emit('sql', sql) }) .on('sql', function(sql) { emitterProxy.emit('sql', sql) })
} }
// private
var destroyObsoleteAssociations = function(oldAssociations, newAssociations) {
var self = this
return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer()
var foreignIdentifier = self.__factory.target.associations[self.__factory.associationAccessor].identifier
var obsoleteAssociations = oldAssociations.filter(function (old) {
// Return only those old associations that are not found in new
return !Utils._.find(newAssociations, function (obj) {
return obj.id === old.id
})
})
if (obsoleteAssociations.length === 0) {
return emitter.emit('success', null)
}
obsoleteAssociations.forEach(function(associatedObject) {
var where = {}
, primaryKeys = Object.keys(self.__factory.connectorDAO.rawAttributes)
, foreignKey = primaryKeys.filter(function(pk) { return pk != self.__factory.identifier })[0]
, notFoundEmitters = []
where[self.__factory.identifier] = self.instance.id
where[foreignKey] = associatedObject.id
self.__factory.connectorDAO
.find({ where: where })
.success(function(connector) {
if (connector === null) {
notFoundEmitters.push(null)
} else {
chainer.add(connector.destroy())
}
if ((chainer.emitters.length + notFoundEmitters.length) === obsoleteAssociations.length) {
// found all obsolete connectors and will delete them now
chainer
.run()
.success(function() { emitter.emit('success', null) })
.error(function(err) { emitter.emit('error', err) })
.on('sql', function(sql) { emitter.emit('sql', sql) })
}
})
.error(function(err) { emitter.emit('error', err) })
.on('sql', function(sql) { emitter.emit('sql', sql) })
})
}).run()
}
return HasManyDoubleLinked return HasManyDoubleLinked
})() })()
...@@ -7,49 +7,94 @@ module.exports = (function() { ...@@ -7,49 +7,94 @@ module.exports = (function() {
} }
HasManySingleLinked.prototype.injectGetter = function(options) { HasManySingleLinked.prototype.injectGetter = function(options) {
var where = {}, options = options || {} var self = this
, where = {}
options = options || {}
var primaryKey = Object.keys(this.instance.rawAttributes).filter(function(k) { return self.instance.rawAttributes[k].primaryKey === true })
primaryKey = primaryKey.length === 1 ? primaryKey[0] : 'id'
where[this.__factory.identifier] = this.instance[primaryKey]
where[this.__factory.identifier] = this.instance.id if (options.where) {
smart = Utils.smartWhere([where, options.where], this.__factory.target.daoFactoryManager.sequelize.options.dialect)
smart = Utils.compileSmartWhere.call(this.__factory.target, smart, this.__factory.target.daoFactoryManager.sequelize.options.dialect)
if (smart.length > 0) {
options.where = smart
}
} else {
options.where = where
}
options.where = options.where ? Utils._.extend(options.where, where) : where return this.__factory.target.all(options)
return this.__factory.target.findAll(options)
} }
HasManySingleLinked.prototype.injectSetter = function(emitter, oldAssociations, newAssociations) { HasManySingleLinked.prototype.injectSetter = function(emitter, oldAssociations, newAssociations) {
var self = this var self = this
, options = this.__factory.options , associationKeys = Object.keys((oldAssociations[0] || newAssociations[0] || {daoFactory: {primaryKeys: {}}}).daoFactory.primaryKeys || {})
, associationKey = associationKeys.length === 1 ? associationKeys[0] : 'id'
, chainer = new Utils.QueryChainer() , chainer = new Utils.QueryChainer()
, obsoleteAssociations = oldAssociations.filter(function (old) { , obsoleteAssociations = oldAssociations.filter(function (old) {
return !Utils._.find(newAssociations, function (obj) { return !Utils._.find(newAssociations, function (obj) {
return obj.id === old.id return obj[associationKey] === old[associationKey]
}) })
}) })
, unassociatedObjects = newAssociations.filter(function (obj) { , unassociatedObjects = newAssociations.filter(function (obj) {
return !Utils._.find(oldAssociations, function (old) { return !Utils._.find(oldAssociations, function (old) {
return obj.id === old.id return obj[associationKey] === old[associationKey]
}) })
}) })
, update
if (obsoleteAssociations.length > 0) {
// clear the old associations // clear the old associations
obsoleteAssociations.forEach(function(associatedObject) { var obsoleteIds = obsoleteAssociations.map(function(associatedObject) {
associatedObject[self.__factory.identifier] = null associatedObject[self.__factory.identifier] = (newAssociations.length < 1 ? null : self.instance.id)
chainer.add(associatedObject.save()) return associatedObject[associationKey]
}) })
update = {}
update[self.__factory.identifier] = null
var primaryKeys = Object.keys(this.__factory.target.primaryKeys)
, primaryKey = primaryKeys.length === 1 ? primaryKeys[0] : 'id'
, updateWhere = {}
updateWhere[primaryKey] = obsoleteIds
chainer.add(this.__factory.target.update(update, updateWhere, {allowNull: [self.__factory.identifier]}))
}
if (unassociatedObjects.length > 0) {
// For the self.instance
var pkeys = Object.keys(self.instance.daoFactory.primaryKeys)
, pkey = pkeys.length === 1 ? pkeys[0] : 'id'
// For chainer
, primaryKeys = Object.keys(this.__factory.target.primaryKeys)
, primaryKey = primaryKeys.length === 1 ? primaryKeys[0] : 'id'
, updateWhere = {}
// set the new associations // set the new associations
unassociatedObjects.forEach(function(associatedObject) { var unassociatedIds = unassociatedObjects.map(function(associatedObject) {
associatedObject[self.__factory.identifier] = self.instance.id associatedObject[self.__factory.identifier] = self.instance[pkey] || self.instance.id
chainer.add(associatedObject.save()) return associatedObject[associationKey]
}) })
update = {}
update[self.__factory.identifier] = (newAssociations.length < 1 ? null : self.instance[pkey] || self.instance.id)
updateWhere[primaryKey] = unassociatedIds
chainer.add(this.__factory.target.update(update, updateWhere, {allowNull: [self.__factory.identifier]}))
}
chainer chainer
.run() .run()
.success(function() { emitter.emit('success', newAssociations) }) .success(function() { emitter.emit('success', newAssociations) })
.error(function(err) { emitter.emit('error', err) }) .error(function(err) { emitter.emit('error', err) })
.on('sql', function(sql) { emitter.emit('sql', sql) })
} }
HasManySingleLinked.prototype.injectAdder = function(emitterProxy, newAssociation) { HasManySingleLinked.prototype.injectAdder = function(emitterProxy, newAssociation) {
newAssociation[this.__factory.identifier] = this.instance.id var primaryKeys = Object.keys(this.instance.daoFactory.primaryKeys)
, primaryKey = primaryKeys.length === 1 ? primaryKeys[0] : 'id'
newAssociation[this.__factory.identifier] = this.instance[primaryKey]
newAssociation.save() newAssociation.save()
.success(function() { emitterProxy.emit('success', newAssociation) }) .success(function() { emitterProxy.emit('success', newAssociation) })
......
...@@ -18,16 +18,19 @@ module.exports = (function() { ...@@ -18,16 +18,19 @@ module.exports = (function() {
this.source.tableName, this.source.tableName,
this.isSelfAssociation ? (this.options.as || this.target.tableName) : this.target.tableName this.isSelfAssociation ? (this.options.as || this.target.tableName) : this.target.tableName
) )
this.associationAccessor = this.combinedName = (this.options.joinTableName || combinedTableName) this.options.tableName = this.combinedName = (this.options.joinTableName || combinedTableName)
this.associationAccessor = this.options.as || this.combinedName
var as = (this.options.as || Utils.pluralize(this.target.tableName)) this.options.useHooks = options.useHooks
var as = (this.options.as || Utils.pluralize(this.target.tableName, this.target.options.language))
this.accessors = { this.accessors = {
get: Utils._.camelize('get_' + as), get: Utils._.camelize('get_' + as),
set: Utils._.camelize('set_' + as), set: Utils._.camelize('set_' + as),
add: Utils._.camelize(Utils.singularize('add_' + as)), add: Utils._.camelize(Utils.singularize('add_' + as, this.target.options.language)),
remove: Utils._.camelize(Utils.singularize('remove_' + as)), remove: Utils._.camelize(Utils.singularize('remove_' + as, this.target.options.language)),
hasSingle: Utils._.camelize(Utils.singularize('has_' + as)), hasSingle: Utils._.camelize(Utils.singularize('has_' + as, this.target.options.language)),
hasAll: Utils._.camelize('has_' + as) hasAll: Utils._.camelize('has_' + as)
} }
} }
...@@ -36,7 +39,7 @@ module.exports = (function() { ...@@ -36,7 +39,7 @@ module.exports = (function() {
// or in an extra table which connects two tables // or in an extra table which connects two tables
HasMany.prototype.injectAttributes = function() { HasMany.prototype.injectAttributes = function() {
var multiAssociation = this.target.associations.hasOwnProperty(this.associationAccessor) var multiAssociation = this.target.associations.hasOwnProperty(this.associationAccessor)
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, this.source.options.language) + "Id", this.options.underscored)
// is there already a single sided association between the source and the target? // is there already a single sided association between the source and the target?
// or is the association on the model itself? // or is the association on the model itself?
...@@ -46,13 +49,19 @@ module.exports = (function() { ...@@ -46,13 +49,19 @@ module.exports = (function() {
this.foreignIdentifier = Utils._.underscoredIf((this.options.as || this.target.tableName) + 'Id', this.options.underscored) this.foreignIdentifier = Utils._.underscoredIf((this.options.as || this.target.tableName) + 'Id', this.options.underscored)
} else { } else {
this.foreignIdentifier = this.target.associations[this.associationAccessor].identifier this.foreignIdentifier = this.target.associations[this.associationAccessor].identifier
this.target.associations[this.associationAccessor].foreignIdentifier = this.identifier
delete this.source.rawAttributes[this.foreignIdentifier] delete this.source.rawAttributes[this.foreignIdentifier]
delete this.target.associations[this.associationAccessor].source.rawAttributes[this.identifier]
} }
// 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 sourceKeys = Object.keys(this.source.primaryKeys);
combinedTableAttributes[this.foreignIdentifier] = {type:DataTypes.INTEGER, primaryKey: true} var sourceKeyType = ((!this.source.hasPrimaryKeys || sourceKeys.length !== 1) ? DataTypes.INTEGER : this.source.rawAttributes[sourceKeys[0]].type)
var targetKeys = Object.keys(this.target.primaryKeys);
var targetKeyType = ((!this.target.hasPrimaryKeys || targetKeys.length !== 1) ? DataTypes.INTEGER : this.target.rawAttributes[targetKeys[0]].type)
combinedTableAttributes[this.identifier] = {type: sourceKeyType, primaryKey: true}
combinedTableAttributes[this.foreignIdentifier] = {type: targetKeyType, 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)
...@@ -65,7 +74,7 @@ module.exports = (function() { ...@@ -65,7 +74,7 @@ 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) Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.source, this.target, this.options)
Utils._.defaults(this.target.rawAttributes, newAttributes) Utils._.defaults(this.target.rawAttributes, newAttributes)
} }
...@@ -153,8 +162,13 @@ module.exports = (function() { ...@@ -153,8 +162,13 @@ module.exports = (function() {
obj[this.accessors.add] = function(newAssociatedObject) { obj[this.accessors.add] = function(newAssociatedObject) {
var instance = this var instance = this
, primaryKeys = Object.keys(newAssociatedObject.daoFactory.primaryKeys || {})
, primaryKey = primaryKeys.length === 1 ? primaryKeys[0] : 'id'
, where = {}
where[newAssociatedObject.daoFactory.tableName+'.'+primaryKey] = newAssociatedObject[primaryKey]
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
instance[self.accessors.get]({ where: { id: newAssociatedObject.id }}) instance[self.accessors.get]({ where: where })
.error(function(err){ emitter.emit('error', err)}) .error(function(err){ emitter.emit('error', err)})
.success(function(currentAssociatedObjects) { .success(function(currentAssociatedObjects) {
if (currentAssociatedObjects.length === 0) { if (currentAssociatedObjects.length === 0) {
...@@ -172,15 +186,46 @@ module.exports = (function() { ...@@ -172,15 +186,46 @@ module.exports = (function() {
var customEventEmitter = new Utils.CustomEventEmitter(function() { var customEventEmitter = new Utils.CustomEventEmitter(function() {
instance[self.accessors.get]().success(function(currentAssociatedObjects) { instance[self.accessors.get]().success(function(currentAssociatedObjects) {
var newAssociations = [] var newAssociations = []
, oldAssociations = []
currentAssociatedObjects.forEach(function(association) { currentAssociatedObjects.forEach(function(association) {
if (!Utils._.isEqual(oldAssociatedObject.identifiers, association.identifiers)) if (!Utils._.isEqual(oldAssociatedObject.identifiers, association.identifiers)) {
newAssociations.push(association) newAssociations[newAssociations.length] = association
} else {
oldAssociations[oldAssociations.length] = association
}
})
var tick = 0
var next = function(err, i) {
if (!!err || i >= oldAssociations.length) {
return run(err)
}
oldAssociations[i].destroy().error(function(err) {
next(err)
}) })
.success(function() {
tick++
next(null, tick)
})
}
var run = function(err) {
if (!!err) {
return customEventEmitter.emit('error', err)
}
instance[self.accessors.set](newAssociations) instance[self.accessors.set](newAssociations)
.success(function() { customEventEmitter.emit('success', null) }) .success(function() { customEventEmitter.emit('success', null) })
.error(function(err) { customEventEmitter.emit('error', err) }) .error(function(err) { customEventEmitter.emit('error', err) })
}
if (oldAssociations.length > 0) {
next(null, tick)
} else {
run()
}
}) })
}) })
return customEventEmitter.run() return customEventEmitter.run()
......
...@@ -11,25 +11,29 @@ module.exports = (function() { ...@@ -11,25 +11,29 @@ module.exports = (function() {
this.isSelfAssociation = (this.source.tableName == this.target.tableName) this.isSelfAssociation = (this.source.tableName == this.target.tableName)
if (this.isSelfAssociation && !this.options.foreignKey && !!this.options.as) { if (this.isSelfAssociation && !this.options.foreignKey && !!this.options.as) {
this.options.foreignKey = Utils._.underscoredIf(Utils.singularize(this.options.as) + "Id", this.options.underscored) this.options.foreignKey = Utils._.underscoredIf(Utils.singularize(this.options.as, this.target.options.language) + "Id", this.options.underscored)
} }
this.associationAccessor = this.isSelfAssociation this.associationAccessor = this.isSelfAssociation
? Utils.combineTableNames(this.target.tableName, this.options.as || this.target.tableName) ? Utils.combineTableNames(this.target.tableName, this.options.as || this.target.tableName)
: this.options.as || this.target.tableName : this.options.as || this.target.tableName
this.options.useHooks = options.useHooks
this.accessors = { this.accessors = {
get: Utils._.camelize('get_' + (this.options.as || Utils.singularize(this.target.tableName))), get: Utils._.camelize('get_' + (this.options.as || Utils.singularize(this.target.tableName, this.target.options.language))),
set: Utils._.camelize('set_' + (this.options.as || Utils.singularize(this.target.tableName))) set: Utils._.camelize('set_' + (this.options.as || Utils.singularize(this.target.tableName, this.target.options.language)))
} }
} }
// the id is in the target table // the id is in the target table
HasOne.prototype.injectAttributes = function() { HasOne.prototype.injectAttributes = function() {
var newAttributes = {} var newAttributes = {}
, sourceKeys = Object.keys(this.source.primaryKeys)
, keyType = ((this.source.hasPrimaryKeys && sourceKeys.length === 1) ? this.source.rawAttributes[sourceKeys[0]].type : DataTypes.INTEGER)
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, this.source.options.language) + "Id", this.options.underscored)
newAttributes[this.identifier] = { type: DataTypes.INTEGER } newAttributes[this.identifier] = { type: this.options.keyType || keyType }
Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.source, this.target, this.options) Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.source, this.target, this.options)
Utils._.defaults(this.target.rawAttributes, newAttributes) Utils._.defaults(this.target.rawAttributes, newAttributes)
...@@ -43,8 +47,10 @@ module.exports = (function() { ...@@ -43,8 +47,10 @@ module.exports = (function() {
var self = this var self = this
obj[this.accessors.get] = function(params) { obj[this.accessors.get] = function(params) {
var id = this.id var primaryKeys = Object.keys(this.daoFactory.primaryKeys)
, primaryKey = primaryKeys.length === 1 ? primaryKeys[0] : 'id'
, where = {} , where = {}
, id = this[primaryKey] || this.id
where[self.identifier] = id where[self.identifier] = id
...@@ -56,6 +62,12 @@ module.exports = (function() { ...@@ -56,6 +62,12 @@ module.exports = (function() {
params = {where: where} params = {where: where}
} }
smart = Utils.smartWhere(params.where || [], self.target.daoFactoryManager.sequelize.options.dialect)
smart = Utils.compileSmartWhere.call(self.target, smart, self.target.daoFactoryManager.sequelize.options.dialect)
if (smart.length > 0) {
params.where = smart
}
return self.target.find(params) return self.target.find(params)
} }
...@@ -64,19 +76,30 @@ module.exports = (function() { ...@@ -64,19 +76,30 @@ module.exports = (function() {
HasOne.prototype.injectSetter = function(obj) { HasOne.prototype.injectSetter = function(obj) {
var self = this var self = this
, options = self.options || {}
obj[this.accessors.set] = function(associatedObject) { obj[this.accessors.set] = function(associatedObject) {
var instance = this; var instance = this
, instanceKeys = Object.keys(instance.daoFactory.primaryKeys)
, instanceKey = instanceKeys.length === 1 ? instanceKeys[0] : 'id'
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
instance[self.accessors.get]().success(function(oldObj) { instance[self.accessors.get]().success(function(oldObj) {
if (oldObj) { if (oldObj) {
oldObj[self.identifier] = null oldObj[self.identifier] = null
oldObj.save() oldObj.save([self.identifier], {allowNull: [self.identifier]}).success(function() {
if (associatedObject) {
associatedObject[self.identifier] = instance[instanceKey]
associatedObject
.save()
.success(function() { emitter.emit('success', associatedObject) })
.error(function(err) { emitter.emit('error', err) })
} else {
emitter.emit('success', null)
} }
})
} else {
if (associatedObject) { if (associatedObject) {
associatedObject[self.identifier] = instance.id associatedObject[self.identifier] = instance[instanceKey]
associatedObject associatedObject
.save() .save()
.success(function() { emitter.emit('success', associatedObject) }) .success(function() { emitter.emit('success', associatedObject) })
...@@ -84,7 +107,7 @@ module.exports = (function() { ...@@ -84,7 +107,7 @@ module.exports = (function() {
} else { } else {
emitter.emit('success', null) emitter.emit('success', null)
} }
}
}) })
}).run() }).run()
} }
......
...@@ -7,6 +7,11 @@ var Utils = require("./../utils") ...@@ -7,6 +7,11 @@ var Utils = require("./../utils")
var Mixin = module.exports = function(){} var Mixin = module.exports = function(){}
Mixin.hasOne = function(associatedDAO, options) { Mixin.hasOne = function(associatedDAO, options) {
// Since this is a mixin, we'll need a unique variable name for hooks (since DAOFactory will override our hooks option)
options = options || {}
options.hooks = options.hooks === undefined ? false : Boolean(options.hooks)
options.useHooks = options.hooks
// the id is in the foreign table // the id is in the foreign table
var association = new HasOne(this, associatedDAO, Utils._.extend((options||{}), this.options)) var association = new HasOne(this, associatedDAO, Utils._.extend((options||{}), this.options))
this.associations[association.associationAccessor] = association.injectAttributes() this.associations[association.associationAccessor] = association.injectAttributes()
...@@ -18,8 +23,13 @@ Mixin.hasOne = function(associatedDAO, options) { ...@@ -18,8 +23,13 @@ Mixin.hasOne = function(associatedDAO, options) {
} }
Mixin.belongsTo = function(associatedDAO, options) { Mixin.belongsTo = function(associatedDAO, options) {
// Since this is a mixin, we'll need a unique variable name for hooks (since DAOFactory will override our hooks option)
options = options || {}
options.hooks = options.hooks === undefined ? false : Boolean(options.hooks)
options.useHooks = options.hooks
// the id is in this table // the id is in this table
var association = new BelongsTo(this, associatedDAO, Utils._.extend((options || {}), this.options)) var association = new BelongsTo(this, associatedDAO, Utils._.extend(options, this.options))
this.associations[association.associationAccessor] = association.injectAttributes() this.associations[association.associationAccessor] = association.injectAttributes()
association.injectGetter(this.DAO.prototype) association.injectGetter(this.DAO.prototype)
...@@ -29,6 +39,11 @@ Mixin.belongsTo = function(associatedDAO, options) { ...@@ -29,6 +39,11 @@ Mixin.belongsTo = function(associatedDAO, options) {
} }
Mixin.hasMany = function(associatedDAO, options) { Mixin.hasMany = function(associatedDAO, options) {
// Since this is a mixin, we'll need a unique variable name for hooks (since DAOFactory will override our hooks option)
options = options || {}
options.hooks = options.hooks === undefined ? false : Boolean(options.hooks)
options.useHooks = options.hooks
// the id is in the foreign table or in a connecting table // the id is in the foreign table or in a connecting table
var association = new HasMany(this, associatedDAO, Utils._.extend((options||{}), this.options)) var association = new HasMany(this, associatedDAO, Utils._.extend((options||{}), this.options))
this.associations[association.associationAccessor] = association.injectAttributes() this.associations[association.associationAccessor] = association.injectAttributes()
......
var Toposort = require('toposort-class') var Toposort = require('toposort-class')
, DaoFactory = require('./dao-factory')
module.exports = (function() { module.exports = (function() {
var DAOFactoryManager = function(sequelize) { var DAOFactoryManager = function(sequelize) {
...@@ -43,12 +44,13 @@ module.exports = (function() { ...@@ -43,12 +44,13 @@ module.exports = (function() {
, sorter = new Toposort() , sorter = new Toposort()
this.daos.forEach(function(dao) { this.daos.forEach(function(dao) {
daos[dao.tableName] = dao
var deps = [] var deps = []
for(var attrName in dao.rawAttributes) { daos[dao.tableName] = dao
if(dao.rawAttributes.hasOwnProperty(attrName)) {
if(dao.rawAttributes[attrName].references) { for (var attrName in dao.rawAttributes) {
if (dao.rawAttributes.hasOwnProperty(attrName)) {
if (dao.rawAttributes[attrName].references) {
deps.push(dao.rawAttributes[attrName].references) deps.push(dao.rawAttributes[attrName].references)
} }
} }
......
var Validator = require("validator")
, Utils = require("./utils")
var DaoValidator = module.exports = function(model, options) {
options = options || {}
options.skip = options.skip || []
this.model = model
this.options = options
}
DaoValidator.prototype.validate = function() {
var errors = {}
errors = Utils._.extend(errors, validateAttributes.call(this))
errors = Utils._.extend(errors, validateModel.call(this))
return errors
}
DaoValidator.prototype.hookValidate = function() {
var self = this
, errors = {}
return new Utils.CustomEventEmitter(function(emitter) {
self.model.daoFactory.runHooks('beforeValidate', self.model.dataValues, function(err, newValues) {
if (!!err) {
return emitter.emit('error', err)
}
self.model.dataValues = newValues || self.model.dataValues
errors = Utils._.extend(errors, validateAttributes.call(self))
errors = Utils._.extend(errors, validateModel.call(self))
if (Object.keys(errors).length > 0) {
return emitter.emit('error', errors)
}
self.model.daoFactory.runHooks('afterValidate', self.model.dataValues, function(err, newValues) {
if (!!err) {
return emitter.emit('error', err)
}
self.model.dataValues = newValues || self.model.dataValues
emitter.emit('success', self.model)
})
})
}).run()
}
// private
var validateModel = function() {
var self = this
, errors = {}
// for each model validator for this DAO
Utils._.each(this.model.__options.validate, function(validator, validatorType) {
try {
validator.apply(self.model)
} catch (err) {
errors[validatorType] = [err.message] // TODO: data structure needs to change for 2.0
}
})
return errors
}
var validateAttributes = function() {
var self = this
, errors = {}
// for each field and value
Utils._.each(this.model.dataValues, function(value, field) {
var rawAttribute = self.model.rawAttributes[field]
, hasAllowedNull = ((rawAttribute === undefined || rawAttribute.allowNull === true) && ((value === null) || (value === undefined)))
, isSkipped = self.options.skip.length > 0 && self.options.skip.indexOf(field) === -1
if (self.model.validators.hasOwnProperty(field) && !hasAllowedNull && !isSkipped) {
errors = Utils._.merge(errors, validateAttribute.call(self, value, field))
}
})
return errors
}
var validateAttribute = function(value, field) {
var self = this
, errors = {}
// for each validator
Utils._.each(this.model.validators[field], function(details, validatorType) {
var validator = prepareValidationOfAttribute.call(self, 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)
}
})
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 : undefined
// 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) { var STRING = function(length, binary) {
if (this instanceof STRING) { if (this instanceof STRING) {
this._binary = !!binary; this._binary = !!binary
if (typeof length === 'number') { if (typeof length === 'number') {
this._length = length; this._length = length
} else { } else {
this._length = 255; this._length = 255
} }
} else { } else {
return new STRING(length, binary); return new STRING(length, binary)
} }
}; }
STRING.prototype = { STRING.prototype = {
get BINARY() { get BINARY() {
this._binary = true; this._binary = true
return this; return this
}, },
get type() { get type() {
return this.toString(); return this.toString()
}, },
toString: function() { toString: function() {
return 'VARCHAR(' + this._length + ')' + ((this._binary) ? ' BINARY' : ''); return 'VARCHAR(' + this._length + ')' + ((this._binary) ? ' BINARY' : '')
} }
}; }
Object.defineProperty(STRING, 'BINARY', { Object.defineProperty(STRING, 'BINARY', {
get: function() { get: function() {
return new STRING(undefined, true); return new STRING(undefined, true)
} }
}); })
var INTEGER = function() { var INTEGER = function() {
return INTEGER.prototype.construct.apply(this, [INTEGER].concat(Array.prototype.slice.apply(arguments))); return INTEGER.prototype.construct.apply(this, [INTEGER].concat(Array.prototype.slice.apply(arguments)))
}; }
var BIGINT = function() { var BIGINT = function() {
return BIGINT.prototype.construct.apply(this, [BIGINT].concat(Array.prototype.slice.apply(arguments))); return BIGINT.prototype.construct.apply(this, [BIGINT].concat(Array.prototype.slice.apply(arguments)))
}; }
var FLOAT = function() { var FLOAT = function() {
return FLOAT.prototype.construct.apply(this, [FLOAT].concat(Array.prototype.slice.apply(arguments))); return FLOAT.prototype.construct.apply(this, [FLOAT].concat(Array.prototype.slice.apply(arguments)))
}; }
FLOAT._type = FLOAT; var BLOB = function() {
FLOAT._typeName = 'FLOAT'; return BLOB.prototype.construct.apply(this, [BLOB].concat(Array.prototype.slice.apply(arguments)))
INTEGER._type = INTEGER; }
INTEGER._typeName = 'INTEGER';
BIGINT._type = BIGINT; FLOAT._type = FLOAT
BIGINT._typeName = 'BIGINT'; FLOAT._typeName = 'FLOAT'
STRING._type = STRING; INTEGER._type = INTEGER
STRING._typeName = 'VARCHAR'; INTEGER._typeName = 'INTEGER'
BIGINT._type = BIGINT
STRING.toString = INTEGER.toString = FLOAT.toString = BIGINT.toString = function() { BIGINT._typeName = 'BIGINT'
return new this._type().toString(); STRING._type = STRING
}; STRING._typeName = 'VARCHAR'
BLOB._type = BLOB
BLOB._typeName = 'BLOB'
BLOB.toString = STRING.toString = INTEGER.toString = FLOAT.toString = BIGINT.toString = function() {
return new this._type().toString()
}
BLOB.prototype = {
construct: function(RealType, length) {
if (this instanceof RealType) {
this._typeName = RealType._typeName
if (typeof length === 'string') {
this._length = length
} else {
this._length = ''
}
} else {
return new RealType(length)
}
},
get type() {
return this.toString()
},
toString: function() {
switch (this._length.toLowerCase()) {
case 'tiny':
return 'TINYBLOB'
case 'medium':
return 'MEDIUMBLOB'
case 'long':
return 'LONGBLOB'
default:
return this._typeName
}
}
}
FLOAT.prototype = BIGINT.prototype = INTEGER.prototype = { FLOAT.prototype = BIGINT.prototype = INTEGER.prototype = {
construct: function(RealType, length, decimals, unsigned, zerofill) { construct: function(RealType, length, decimals, unsigned, zerofill) {
if (this instanceof RealType) { if (this instanceof RealType) {
this._typeName = RealType._typeName; this._typeName = RealType._typeName
this._unsigned = !!unsigned; this._unsigned = !!unsigned
this._zerofill = !!zerofill; this._zerofill = !!zerofill
if (typeof length === 'number') { if (typeof length === 'number') {
this._length = length; this._length = length
} }
if (typeof decimals === 'number') { if (typeof decimals === 'number') {
this._decimals = decimals; this._decimals = decimals
} }
} else { } else {
return new RealType(length, decimals, unsigned, zerofill); return new RealType(length, decimals, unsigned, zerofill)
} }
}, },
get type() { get type() {
return this.toString(); return this.toString()
}, },
get UNSIGNED() { get UNSIGNED() {
this._unsigned = true; this._unsigned = true
return this; return this
}, },
get ZEROFILL() { get ZEROFILL() {
this._zerofill = true; this._zerofill = true
return this; return this
}, },
toString: function() { toString: function() {
var result = this._typeName; var result = this._typeName
if (this._length) { if (this._length) {
result += '(' + this._length; result += '(' + this._length
if (typeof this._decimals === 'number') { if (typeof this._decimals === 'number') {
result += ',' + this._decimals; result += ',' + this._decimals
} }
result += ')'; result += ')'
} }
if (this._unsigned) { if (this._unsigned) {
result += ' UNSIGNED'; result += ' UNSIGNED'
} }
if (this._zerofill) { if (this._zerofill) {
result += ' ZEROFILL'; result += ' ZEROFILL'
} }
return result; return result
} }
}; }
var unsignedDesc = { var unsignedDesc = {
get: function() { get: function() {
return new this._type(undefined, undefined, true); return new this._type(undefined, undefined, true)
} }
}; }
var zerofillDesc = { var zerofillDesc = {
get: function() { get: function() {
return new this._type(undefined, undefined, undefined, true); return new this._type(undefined, undefined, undefined, true)
} }
}; }
var typeDesc = { var typeDesc = {
get: function() { get: function() {
return new this._type().toString(); return new this._type().toString()
} }
}; }
Object.defineProperty(STRING, 'type', typeDesc); Object.defineProperty(STRING, 'type', typeDesc)
Object.defineProperty(INTEGER, 'type', typeDesc); Object.defineProperty(INTEGER, 'type', typeDesc)
Object.defineProperty(BIGINT, 'type', typeDesc); Object.defineProperty(BIGINT, 'type', typeDesc)
Object.defineProperty(FLOAT, 'type', typeDesc); Object.defineProperty(FLOAT, 'type', typeDesc)
Object.defineProperty(BLOB, 'type', typeDesc)
Object.defineProperty(INTEGER, 'UNSIGNED', unsignedDesc); Object.defineProperty(INTEGER, 'UNSIGNED', unsignedDesc)
Object.defineProperty(BIGINT, 'UNSIGNED', unsignedDesc); Object.defineProperty(BIGINT, 'UNSIGNED', unsignedDesc)
Object.defineProperty(FLOAT, 'UNSIGNED', unsignedDesc); Object.defineProperty(FLOAT, 'UNSIGNED', unsignedDesc)
Object.defineProperty(INTEGER, 'ZEROFILL', zerofillDesc); Object.defineProperty(INTEGER, 'ZEROFILL', zerofillDesc)
Object.defineProperty(BIGINT, 'ZEROFILL', zerofillDesc); Object.defineProperty(BIGINT, 'ZEROFILL', zerofillDesc)
Object.defineProperty(FLOAT, 'ZEROFILL', zerofillDesc); Object.defineProperty(FLOAT, 'ZEROFILL', zerofillDesc)
module.exports = { module.exports = {
STRING: STRING, STRING: STRING,
...@@ -147,6 +187,7 @@ module.exports = { ...@@ -147,6 +187,7 @@ module.exports = {
BOOLEAN: 'TINYINT(1)', BOOLEAN: 'TINYINT(1)',
FLOAT: FLOAT, FLOAT: FLOAT,
NOW: 'NOW', NOW: 'NOW',
BLOB: BLOB,
get ENUM() { get ENUM() {
var result = function() { var result = function() {
...@@ -183,7 +224,7 @@ module.exports = { ...@@ -183,7 +224,7 @@ module.exports = {
} }
result.type = 'HSTORE' result.type = 'HSTORE'
result.toString = result.valueOf = function() { return 'TEXT' } result.toString = result.valueOf = function() { return 'HSTORE' }
return result return result
} }
......
var Utils = require("../../utils")
, SqlString = require("../../sql-string")
module.exports = (function() { module.exports = (function() {
var QueryGenerator = { var QueryGenerator = {
addSchema: function(opts) { addSchema: function(opts) {
...@@ -150,6 +153,11 @@ module.exports = (function() { ...@@ -150,6 +153,11 @@ 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')
...@@ -264,6 +272,102 @@ module.exports = (function() { ...@@ -264,6 +272,102 @@ module.exports = (function() {
*/ */
disableForeignKeyConstraintsQuery: function() { disableForeignKeyConstraintsQuery: function() {
throwMethodUndefined('disableForeignKeyConstraintsQuery') throwMethodUndefined('disableForeignKeyConstraintsQuery')
},
/*
Quote an object based on its type. This is a more general version of quoteIdentifiers
Strings: should proxy to quoteIdentifiers
Arrays: First argument should be qouted, second argument should be append without quoting
Objects:
* If raw is set, that value should be returned verbatim, without quoting
* If fn is set, the string should start with the value of fn, starting paren, followed by
the values of cols (which is assumed to be an array), quoted and joined with ', ',
unless they are themselves objects
* If direction is set, should be prepended
Currently this function is only used for ordering / grouping columns, but it could
potentially also be used for other places where we want to be able to call SQL functions (e.g. as default values)
*/
quote: function(obj, force) {
if (Utils._.isString(obj)) {
return this.quoteIdentifiers(obj, force)
} else if (Array.isArray(obj)) {
return this.quote(obj[0], force) + ' ' + obj[1]
} else if (obj instanceof Utils.fn || obj instanceof Utils.col) {
return obj.toString(this)
} else if (Utils._.isObject(obj) && 'raw' in obj) {
return obj.raw
} else {
throw new Error('Unknown structure passed to order / group: ' + JSON.stringify(obj))
}
},
/*
Create a trigger
*/
createTrigger: function(tableName, triggerName, timingType, fireOnArray, functionName, functionParams,
optionsArray) {
throwMethodUndefined('createTrigger')
},
/*
Drop a trigger
*/
dropTrigger: function(tableName, triggerName) {
throwMethodUndefined('dropTrigger')
},
/*
Rename a trigger
*/
renameTrigger: function(tableName, oldTriggerName, newTriggerName) {
throwMethodUndefined('renameTrigger')
},
/*
Create a function
*/
createFunction: function(functionName, params, returnType, language, body, options) {
throwMethodUndefined('createFunction')
},
/*
Drop a function
*/
dropFunction: function(functionName, params) {
throwMethodUndefined('dropFunction')
},
/*
Rename a function
*/
renameFunction: function(oldFunctionName, params, newFunctionName) {
throwMethodUndefined('renameFunction')
},
/*
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, field) {
if (value instanceof Utils.fn || value instanceof Utils.col) {
return value.toString(this)
} else {
return SqlString.escape(value, false, null, this.dialect, field)
}
} }
} }
......
...@@ -262,7 +262,7 @@ module.exports = (function() { ...@@ -262,7 +262,7 @@ module.exports = (function() {
result = transformRowsWithEagerLoadingIntoDaos.call(this, results) result = transformRowsWithEagerLoadingIntoDaos.call(this, results)
} else { } else {
result = results.map(function(result) { result = results.map(function(result) {
return this.callee.build(result, { isNewRecord: false }) return this.callee.build(result, { isNewRecord: false, isDirty: false })
}.bind(this)) }.bind(this))
} }
...@@ -287,7 +287,7 @@ module.exports = (function() { ...@@ -287,7 +287,7 @@ module.exports = (function() {
var transformRowWithEagerLoadingIntoDao = function(result, dao) { var transformRowWithEagerLoadingIntoDao = function(result, dao) {
// let's build the actual dao instance first... // let's build the actual dao instance first...
dao = dao || this.callee.build(result[this.callee.tableName], { isNewRecord: false }) dao = dao || this.callee.build(result[this.callee.tableName], { isNewRecord: false, isDirty: false })
// ... and afterwards the prefetched associations // ... and afterwards the prefetched associations
for (var tableName in result) { for (var tableName in result) {
...@@ -302,11 +302,12 @@ module.exports = (function() { ...@@ -302,11 +302,12 @@ module.exports = (function() {
var buildAssociatedDaoInstances = function(tableName, associationData, dao) { var buildAssociatedDaoInstances = function(tableName, associationData, dao) {
var associatedDaoFactory = this.sequelize.daoFactoryManager.getDAO(tableName, { attribute: 'tableName' }) var associatedDaoFactory = this.sequelize.daoFactoryManager.getDAO(tableName, { attribute: 'tableName' })
, association = null , association = null
, self = this
if (!!associatedDaoFactory) { if (!!associatedDaoFactory) {
association = this.callee.getAssociation(associatedDaoFactory) association = this.callee.getAssociation(associatedDaoFactory)
} else { } else {
associatedDaoFactory = this.sequelize.daoFactoryManager.getDAO(Utils.pluralize(tableName), { attribute: 'tableName' }) associatedDaoFactory = this.sequelize.daoFactoryManager.getDAO(Utils.pluralize(tableName, this.sequelize.language), { attribute: 'tableName' })
if (!!associatedDaoFactory) { if (!!associatedDaoFactory) {
association = this.callee.getAssociation(associatedDaoFactory) association = this.callee.getAssociation(associatedDaoFactory)
...@@ -322,11 +323,11 @@ module.exports = (function() { ...@@ -322,11 +323,11 @@ module.exports = (function() {
accessor = accessor.slice(0,1).toLowerCase() + accessor.slice(1) accessor = accessor.slice(0,1).toLowerCase() + accessor.slice(1)
associationData.forEach(function(data) { associationData.forEach(function(data) {
var daoInstance = associatedDaoFactory.build(data, { isNewRecord: false }) var daoInstance = associatedDaoFactory.build(data, { isNewRecord: false, isDirty: false })
, isEmpty = !Utils.firstValueOfHash(daoInstance.identifiers) , isEmpty = !Utils.firstValueOfHash(daoInstance.identifiers)
if (['BelongsTo', 'HasOne'].indexOf(association.associationType) > -1) { if (['BelongsTo', 'HasOne'].indexOf(association.associationType) > -1) {
accessor = Utils.singularize(accessor) accessor = Utils.singularize(accessor, self.sequelize.language)
dao[accessor] = isEmpty ? null : daoInstance dao[accessor] = isEmpty ? null : daoInstance
} else { } else {
dao[accessor] = dao[accessor] || [] dao[accessor] = dao[accessor] || []
......
...@@ -4,14 +4,22 @@ var mysql ...@@ -4,14 +4,22 @@ var mysql
, Utils = require("../../utils") , Utils = require("../../utils")
, without = function(arr, elem) { return arr.filter(function(e) { return e != elem }) } , without = function(arr, elem) { return arr.filter(function(e) { return e != elem }) }
try { mysql = require("mysql") } catch (err) {
console.log("You need to install mysql package manually"); }
module.exports = (function() { module.exports = (function() {
var ConnectorManager = function(sequelize, config) { var ConnectorManager = function(sequelize, config) {
try {
if (config.dialectModulePath) {
mysql = require(config.dialectModulePath)
} else {
mysql = require('mysql')
}
} catch (err) {
console.log('You need to install mysql package manually')
}
this.sequelize = sequelize this.sequelize = sequelize
this.client = null this.client = null
this.config = config || {} this.config = config || {}
this.config.port = this.config.port || 3306
this.disconnectTimeoutId = null this.disconnectTimeoutId = null
this.queue = [] this.queue = []
this.activeQueue = [] this.activeQueue = []
...@@ -19,7 +27,9 @@ module.exports = (function() { ...@@ -19,7 +27,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;
...@@ -72,7 +82,9 @@ module.exports = (function() { ...@@ -72,7 +82,9 @@ module.exports = (function() {
read: Pooling.Pool({ read: Pooling.Pool({
name: 'sequelize-read', name: 'sequelize-read',
create: function (done) { create: function (done) {
if (reads >= self.config.replication.read.length) reads = 0; if (reads >= self.config.replication.read.length) {
reads = 0
}
var config = self.config.replication.read[reads++]; var config = self.config.replication.read[reads++];
connect.call(self, function (err, connection) { connect.call(self, function (err, connection) {
...@@ -83,6 +95,7 @@ module.exports = (function() { ...@@ -83,6 +95,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 +111,7 @@ module.exports = (function() { ...@@ -98,6 +111,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 +129,7 @@ module.exports = (function() { ...@@ -115,6 +129,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
}) })
} }
...@@ -155,11 +170,12 @@ module.exports = (function() { ...@@ -155,11 +170,12 @@ module.exports = (function() {
query.done(function() { query.done(function() {
self.pendingQueries--; self.pendingQueries--;
if (self.pool) self.pool.release(query.client); if (self.pool) {
else { self.pool.release(query.client);
} else {
if (self.pendingQueries === 0) { if (self.pendingQueries === 0) {
setTimeout(function() { setTimeout(function() {
self.pendingQueries === 0 && self.disconnect.call(self); self.pendingQueries === 0 && self.disconnect.call(self)
}, 100); }, 100);
} }
} }
...@@ -167,16 +183,16 @@ module.exports = (function() { ...@@ -167,16 +183,16 @@ module.exports = (function() {
if (!this.pool) { if (!this.pool) {
query.run(sql); query.run(sql);
} } else {
else {
this.pool.acquire(function(err, client) { this.pool.acquire(function(err, client) {
if (err) return query.emit('error', err); if (err) {
return query.emit('error', err)
}
query.client = client; query.client = client
query.run(sql); query.run(sql)
return; return;
}, undefined, options.type); }, undefined, options.type)
} }
return query; return query;
...@@ -196,8 +212,10 @@ module.exports = (function() { ...@@ -196,8 +212,10 @@ module.exports = (function() {
}; };
ConnectorManager.prototype.disconnect = function() { ConnectorManager.prototype.disconnect = function() {
if (this.client) disconnect.call(this, this.client); if (this.client) {
return; disconnect.call(this, this.client)
}
return
}; };
...@@ -236,21 +254,63 @@ module.exports = (function() { ...@@ -236,21 +254,63 @@ module.exports = (function() {
var connect = function(done, config) { var connect = function(done, config) {
config = config || this.config config = config || this.config
var connection = mysql.createConnection({
var emitter = new (require('events').EventEmitter)()
var connectionConfig = {
host: config.host, host: config.host,
port: config.port, port: config.port,
user: config.username, user: config.username,
password: config.password, password: config.password,
database: config.database, database: config.database,
timezone: 'Z' timezone: 'Z'
};
if (config.dialectOptions) {
Object.keys(config.dialectOptions).forEach(function (key) {
connectionConfig[key] = config.dialectOptions[key];
});
}
var connection = mysql.createConnection(connectionConfig);
connection.connect(function(err) {
if (err) {
switch(err.code) {
case 'ECONNREFUSED':
case 'ER_ACCESS_DENIED_ERROR':
emitter.emit('error', 'Failed to authenticate for MySQL. Please double check your settings.')
break
case 'ENOTFOUND':
case 'EHOSTUNREACH':
case 'EINVAL':
emitter.emit('error', 'Failed to find MySQL server. Please double check your settings.')
break
}
}
}) })
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) {
...@@ -295,8 +355,6 @@ module.exports = (function() { ...@@ -295,8 +355,6 @@ module.exports = (function() {
} }
var afterQuery = function(queueItem) { var afterQuery = function(queueItem) {
var self = this
dequeue.call(this, queueItem) dequeue.call(this, queueItem)
transferQueuedItems.call(this, this.maxConcurrentQueries - this.activeQueue.length) transferQueuedItems.call(this, this.maxConcurrentQueries - this.activeQueue.length)
disconnectIfNoConnections.call(this) disconnectIfNoConnections.call(this)
......
...@@ -3,59 +3,80 @@ var Query = require("./query") ...@@ -3,59 +3,80 @@ var Query = require("./query")
module.exports = (function() { module.exports = (function() {
var ConnectorManager = function(sequelize, config) { var ConnectorManager = function(sequelize, config) {
var pgModule = config.dialectModulePath || 'pg'
this.sequelize = sequelize this.sequelize = sequelize
this.client = null this.client = null
this.config = config || {} this.config = config || {}
this.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.pool && (this.config.pool.maxConnections > 0))
this.pg = this.config.native ? require(pgModule).native : require(pgModule)
this.poolIdentifier = null
// Better support for BigInts
// https://github.com/brianc/node-postgres/issues/166#issuecomment-9514935
this.pg.types.setTypeParser(20, String);
// set pooling parameters if specified // set pooling parameters if specified
if (this.pooling) { if (this.pooling) {
this.pg.defaults.poolSize = this.config.poolCfg.maxConnections this.pg.defaults.poolSize = this.config.pool.maxConnections || 10
this.pg.defaults.poolIdleTimeout = this.config.poolCfg.maxIdleTime this.pg.defaults.poolIdleTimeout = this.config.pool.maxIdleTime || 30000
this.pg.defaults.reapIntervalMillis = this.config.pool.reapInterval || 1000
} }
this.disconnectTimeoutId = null this.disconnectTimeoutId = null
this.pendingQueries = 0 this.pendingQueries = 0
this.maxConcurrentQueries = (this.config.maxConcurrentQueries || 50) this.maxConcurrentQueries = (this.config.maxConcurrentQueries || 50)
process.on('exit', function() {
this.disconnect()
}.bind(this))
} }
Utils._.extend(ConnectorManager.prototype, require("../connector-manager").prototype) Utils._.extend(ConnectorManager.prototype, require("../connector-manager").prototype)
var isConnecting = false ConnectorManager.prototype.endQuery = function() {
var isConnected = false var self = this
self.pendingQueries--
if (!self.pooling && self.pendingQueries === 0) {
setTimeout(function() {
self.pendingQueries === 0 && self.disconnect.call(self)
}, 100)
}
}
ConnectorManager.prototype.query = function(sql, callee, options) { ConnectorManager.prototype.query = function(sql, callee, options) {
var self = this var self = this
if (this.client == null) {
this.connect()
}
var query = new Query(this.client, this.sequelize, callee, options || {}) self.pendingQueries++
self.pendingQueries += 1 return new Utils.CustomEventEmitter(function(emitter) {
self.connect()
.on('error', function(err) {
emitter.emit('error', err)
})
.on('success', function(done) {
var query = new Query(self.client, self.sequelize, callee, options || {})
return query.run(sql) return query.run(sql)
.success(function() { self.endQuery.call(self) }) .complete(function(err) { done && done(err) })
.error(function() { self.endQuery.call(self) }) .success(function(results) { self.endQuery.call(self) })
} .error(function(err) { self.endQuery.call(self) })
.proxy(emitter)
ConnectorManager.prototype.endQuery = function() { })
var self = this }).run()
self.pendingQueries -= 1
if (self.pendingQueries == 0) {
setTimeout(function() {
self.pendingQueries == 0 && self.disconnect.call(self)
}, 100)
}
} }
ConnectorManager.prototype.connect = function() { ConnectorManager.prototype.connect = function(callback) {
var self = this var self = this
var emitter = new (require('events').EventEmitter)() var emitter = new (require('events').EventEmitter)()
// in case database is slow to connect, prevent orphaning the client // in case database is slow to connect, prevent orphaning the client
if (this.isConnecting) { // TODO: We really need some sort of queue/flush/drain mechanism
return if (this.isConnecting && !this.pooling && this.client === null) {
emitter.emit('success', null)
return emitter
} }
this.isConnecting = true this.isConnecting = true
...@@ -63,19 +84,39 @@ module.exports = (function() { ...@@ -63,19 +84,39 @@ module.exports = (function() {
var uri = this.sequelize.getQueryInterface().QueryGenerator.databaseConnectionUri(this.config) var uri = this.sequelize.getQueryInterface().QueryGenerator.databaseConnectionUri(this.config)
var connectCallback = function(err, client) { var connectCallback = function(err, client, done) {
self.isConnecting = false self.isConnecting = false
if (!!err) { if (!!err) {
// release the pool immediately, very important.
done && done(err)
self.client = null
if (err.code) {
switch(err.code) {
case 'ECONNREFUSED':
emitter.emit('error', new Error("Failed to authenticate for PostgresSQL. Please double check your settings."))
break
case 'ENOTFOUND':
case 'EHOSTUNREACH':
case 'EINVAL':
emitter.emit('error', new Error("Failed to find PostgresSQL server. Please double check your settings."))
break
default:
emitter.emit('error', err) emitter.emit('error', err)
break
}
}
} else if (client) { } else if (client) {
client.query("SET TIME ZONE 'UTC'") client.query("SET TIME ZONE 'UTC'").on('end', function() {
.on('end', function() {
self.isConnected = true self.isConnected = true
this.client = client self.client = client
}); emitter.emit('success', done)
})
} else { } else {
this.client = null done && done()
self.client = null
emitter.emit('success')
} }
} }
...@@ -83,18 +124,29 @@ module.exports = (function() { ...@@ -83,18 +124,29 @@ module.exports = (function() {
// acquire client from pool // acquire client from pool
this.pg.connect(uri, connectCallback) this.pg.connect(uri, connectCallback)
} else { } else {
if (!!this.client) {
connectCallback(null, this.client)
} else {
//create one-off client //create one-off client
this.client = new this.pg.Client(uri) this.client = new this.pg.Client(uri)
this.client.connect(connectCallback) this.client.connect(connectCallback)
} }
}
return emitter return emitter
} }
ConnectorManager.prototype.disconnect = function() { ConnectorManager.prototype.disconnect = function() {
var self = this if (this.poolIdentifier) {
if (this.client) this.client.end() this.poolIdentifier.destroyAllNow()
this.client = null }
if (this.client) {
// Closes a client correctly even if we have backed up queries
// https://github.com/brianc/node-postgres/pull/346
this.client.on('drain', this.client.end.bind(this.client))
}
this.isConnecting = false this.isConnecting = false
this.isConnected = false this.isConnected = false
} }
......
module.exports = {
stringifyPart: function(part) {
switch(typeof part) {
case 'boolean':
case 'number':
return String(part)
case 'string':
return '"' + part.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"'
case 'undefined':
return 'NULL'
default:
if (part === null)
return 'NULL'
else
return '"' + JSON.stringify(part).replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"'
}
},
stringify: function(data) {
var self = this
return Object.keys(data).map(function(key) {
return self.stringifyPart(key) + '=>' + self.stringifyPart(data[key])
}).join(',')
},
parsePart: function(part) {
part = part.replace(/\\\\/g, '\\').replace(/\\"/g, '"')
switch(part[0]) {
case '{':
case '[':
return JSON.parse(part)
default:
return part
}
},
parse: function(string) {
var self = this
const rx = /\"((?:\\\"|[^"])*)\"\s*\=\>\s*((?:true|false|NULL|\d+|\d+\.\d+|\"((?:\\\"|[^"])*)\"))/g
var object = { }
string.replace(rx, function(match, key, value, innerValue) {
switch(value) {
case 'true':
object[self.parsePart(key)] = true
break
case 'false':
object[self.parsePart(key)] = false
break
case 'NULL':
object[self.parsePart(key)] = null
break
default:
object[self.parsePart(key)] = self.parsePart(innerValue || value)
break
}
})
return object;
}
}
var Utils = require("../../utils") var Utils = require("../../utils")
, AbstractQuery = require('../abstract/query') , AbstractQuery = require('../abstract/query')
, DataTypes = require('../../data-types') , DataTypes = require('../../data-types')
, hstore = require('./hstore')
module.exports = (function() { module.exports = (function() {
var Query = function(client, sequelize, callee, options) { var Query = function(client, sequelize, callee, options) {
...@@ -20,32 +21,33 @@ module.exports = (function() { ...@@ -20,32 +21,33 @@ module.exports = (function() {
Query.prototype.run = function(sql) { Query.prototype.run = function(sql) {
this.sql = sql this.sql = sql
var self = this
, receivedError = false
, query = this.client.query(sql)
, rows = []
if (this.options.logging !== false) { if (this.options.logging !== false) {
this.options.logging('Executing: ' + this.sql) this.options.logging('Executing: ' + this.sql)
} }
var receivedError = false
, query = this.client.query(sql)
, rows = []
query.on('row', function(row) { query.on('row', function(row) {
rows.push(row) rows.push(row)
}) })
query.on('error', function(err) { query.on('error', function(err) {
receivedError = true receivedError = true
this.emit('error', err, this.callee) self.emit('error', err, self.callee)
}.bind(this)) })
query.on('end', function() { query.on('end', function() {
this.emit('sql', this.sql) self.emit('sql', self.sql)
if (receivedError) { if (receivedError) {
return return
} }
onSuccess.call(this, rows) onSuccess.call(self, rows, sql)
}.bind(this)) })
return this return this
} }
...@@ -54,10 +56,11 @@ module.exports = (function() { ...@@ -54,10 +56,11 @@ module.exports = (function() {
return 'id' return 'id'
} }
var onSuccess = function(rows) { var onSuccess = function(rows, sql) {
var results = [] var results = []
, isTableNameQuery = (this.sql.indexOf('SELECT table_name FROM information_schema.tables') === 0) , self = this
, isRelNameQuery = (this.sql.indexOf('SELECT relname FROM pg_class WHERE oid IN') === 0) , isTableNameQuery = (sql.indexOf('SELECT table_name FROM information_schema.tables') === 0)
, isRelNameQuery = (sql.indexOf('SELECT relname FROM pg_class WHERE oid IN') === 0)
if (isTableNameQuery || isRelNameQuery) { if (isTableNameQuery || isRelNameQuery) {
if (isRelNameQuery) { if (isRelNameQuery) {
...@@ -74,14 +77,15 @@ module.exports = (function() { ...@@ -74,14 +77,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') {
...@@ -96,13 +100,30 @@ module.exports = (function() { ...@@ -96,13 +100,30 @@ 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.options.raw === false && this.sequelize.options.quoteIdentifiers === false) {
var attrsMap = Utils._.reduce(this.callee.attributes, function(m, v, k) { m[k.toLowerCase()] = k; return m}, {})
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')) {
...@@ -113,7 +134,7 @@ module.exports = (function() { ...@@ -113,7 +134,7 @@ module.exports = (function() {
if (rows[0].hasOwnProperty(key)) { if (rows[0].hasOwnProperty(key)) {
var record = rows[0][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) { 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) record = hstore.parse(record)
} }
this.callee[key] = record this.callee[key] = record
} }
...@@ -127,7 +148,7 @@ module.exports = (function() { ...@@ -127,7 +148,7 @@ module.exports = (function() {
if (rows[0].hasOwnProperty(key)) { if (rows[0].hasOwnProperty(key)) {
var record = rows[0][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) { 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) record = hstore.parse(record)
} }
this.callee[key] = record this.callee[key] = record
} }
......
var Utils = require("../../utils") var sqlite3
, sqlite3 = require('sqlite3').verbose() , Utils = require("../../utils")
, Query = require("./query") , Query = require("./query")
module.exports = (function() { module.exports = (function() {
var ConnectorManager = function(sequelize) { var ConnectorManager = function(sequelize, config) {
this.sequelize = sequelize this.sequelize = sequelize
this.database = db = new sqlite3.Database(sequelize.options.storage || ':memory:', function(err) {
if(!err && sequelize.options.foreignKeys !== false) { if (config.dialectModulePath) {
sqlite3 = require(config.dialectModulePath).verbose()
} else {
sqlite3 = require('sqlite3').verbose()
}
}
Utils._.extend(ConnectorManager.prototype, require("../connector-manager").prototype)
ConnectorManager.prototype.connect = function() {
var emitter = new (require('events').EventEmitter)()
, self = this
, db
this.database = db = new sqlite3.Database(self.sequelize.options.storage || ':memory:', function(err) {
if (err) {
if (err.code === "SQLITE_CANTOPEN") {
emitter.emit('error', 'Failed to find SQL server. Please double check your settings.')
}
}
if(!err && self.sequelize.options.foreignKeys !== false) {
// Make it possible to define and use foreign key constraints unless // Make it possible to define and use foreign key constraints unless
// explicitly disallowed. It's still opt-in per relation // explicitly disallowed. It's still opt-in per relation
db.run('PRAGMA FOREIGN_KEYS=ON') db.run('PRAGMA FOREIGN_KEYS=ON')
} }
}) })
} }
Utils._.extend(ConnectorManager.prototype, require("../connector-manager").prototype)
ConnectorManager.prototype.query = function(sql, callee, options) { ConnectorManager.prototype.query = function(sql, callee, options) {
if (!this.database) {
this.connect()
}
return new Query(this.database, this.sequelize, callee, options).run(sql) return new Query(this.database, this.sequelize, callee, options).run(sql)
} }
......
...@@ -29,7 +29,7 @@ module.exports = (function() { ...@@ -29,7 +29,7 @@ module.exports = (function() {
this.options.logging('Executing: ' + this.sql) this.options.logging('Executing: ' + this.sql)
} }
var columnTypes = {}; var columnTypes = {}
this.database.serialize(function() { this.database.serialize(function() {
var executeSql = function() { var executeSql = function() {
self.database[getDatabaseMethod.call(self)](self.sql, function(err, results) { self.database[getDatabaseMethod.call(self)](self.sql, function(err, results) {
...@@ -48,16 +48,16 @@ module.exports = (function() { ...@@ -48,16 +48,16 @@ module.exports = (function() {
self.database.all("PRAGMA table_info(" + tableName + ")", function(err, results) { self.database.all("PRAGMA table_info(" + tableName + ")", function(err, results) {
if (!err) { if (!err) {
for (var i=0, l=results.length; i<l; i++) { for (var i=0, l=results.length; i<l; i++) {
columnTypes[results[i].name] = results[i].type; columnTypes[results[i].name] = results[i].type
} }
} }
executeSql(); executeSql()
}); });
} else { } else {
executeSql(); executeSql()
} }
} else { } else {
executeSql(); executeSql()
} }
}) })
...@@ -85,15 +85,18 @@ module.exports = (function() { ...@@ -85,15 +85,18 @@ module.exports = (function() {
if (this.sql.indexOf('sqlite_master') !== -1) { if (this.sql.indexOf('sqlite_master') !== -1) {
result = results.map(function(resultSet) { return resultSet.name }) result = results.map(function(resultSet) { return resultSet.name })
} else if (this.send('isSelectQuery')) { } else if (this.send('isSelectQuery')) {
// we need to convert the timestamps into actual date objects
if(!this.options.raw) { if(!this.options.raw) {
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]) {
var val = result[name]; if (metaData.columnTypes[name] === 'DATETIME') {
if(val !== null) { // we need to convert the timestamps into actual date objects
result[name] = new Date(val+'Z'); // Z means UTC var val = result[name]
if (val !== null) {
result[name] = new Date(val+'Z') // Z means UTC
}
} else if (metaData.columnTypes[name].lastIndexOf('BLOB') !== -1) {
result[name] = new Buffer(result[name])
} }
} }
} }
......
var util = require("util") var util = require("util")
, EventEmitter = require("events").EventEmitter , EventEmitter = require("events").EventEmitter
, Promise = require("promise")
, proxyEventKeys = ['success', 'error', 'sql'] , proxyEventKeys = ['success', 'error', 'sql']
, tick = (typeof setImmediate !== "undefined" ? setImmediate : process.nextTick)
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)
CustomEventEmitter.prototype.run = function() { CustomEventEmitter.prototype.run = function() {
process.nextTick(function() { tick(function() {
if (this.fct) { if (this.fct) {
this.fct.call(this, this) this.fct.call(this, this)
} }
...@@ -21,7 +33,7 @@ module.exports = (function() { ...@@ -21,7 +33,7 @@ module.exports = (function() {
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
} }
...@@ -29,18 +41,25 @@ module.exports = (function() { ...@@ -29,18 +41,25 @@ 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.sql =
function(fct) {
this.on('sql', bindToProcess(fct))
return this;
}
CustomEventEmitter.prototype.proxy = function(emitter) { CustomEventEmitter.prototype.proxy = function(emitter) {
proxyEventKeys.forEach(function (eventKey) { proxyEventKeys.forEach(function (eventKey) {
this.on(eventKey, function (result) { this.on(eventKey, function (result) {
...@@ -49,6 +68,18 @@ module.exports = (function() { ...@@ -49,6 +68,18 @@ module.exports = (function() {
}.bind(this)) }.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;
})() })()
var Hooks = module.exports = function(){}
Hooks.runHooks = function() {
var self = this
, tick = 0
, hooks = arguments[0]
, args = Array.prototype.slice.call(arguments, 1, arguments.length-1)
, fn = arguments[arguments.length-1]
if (typeof hooks === "string") {
hooks = this.options.hooks[hooks] || []
}
if (!Array.isArray(hooks)) {
hooks = hooks === undefined ? [] : [hooks]
}
if (hooks.length < 1) {
return fn.apply(this, [null].concat(args))
}
var run = function(hook) {
if (!hook) {
return fn.apply(this, [null].concat(args))
}
if (typeof hook === "object") {
hook = hook.fn
}
hook.apply(self, args.concat(function() {
tick++
if (!!arguments[0]) {
return fn(arguments[0])
}
// daoValues = newValues
return run(hooks[tick])
}))
}
run(hooks[tick])
}
Hooks.hook = function(hookType, name, fn) {
// For aliases, we may want to incorporate some sort of way to mitigate this
if (hookType === "beforeDelete") {
hookType = 'beforeDestroy'
}
else if (hookType === "afterDelete") {
hookType = 'afterDestroy'
}
Hooks.addHook.call(this, hookType, name, fn)
}
Hooks.addHook = function(hookType, name, fn) {
if (typeof name === "function") {
fn = name
name = null
}
var method = function() {
fn.apply(this, Array.prototype.slice.call(arguments, 0, arguments.length-1).concat(arguments[arguments.length-1]))
}
// Just in case if we override the default DAOFactory.options
this.options.hooks[hookType] = this.options.hooks[hookType] || []
this.options.hooks[hookType][this.options.hooks[hookType].length] = !!name ? {name: name, fn: method} : method
}
Hooks.beforeValidate = function(name, fn) {
Hooks.addHook.call(this, 'beforeValidate', name, fn)
}
Hooks.afterValidate = function(name, fn) {
Hooks.addHook.call(this, 'afterValidate', name, fn)
}
Hooks.beforeCreate = function(name, fn) {
Hooks.addHook.call(this, 'beforeCreate', name, fn)
}
Hooks.afterCreate = function(name, fn) {
Hooks.addHook.call(this, 'afterCreate', name, fn)
}
Hooks.beforeDestroy = function(name, fn) {
Hooks.addHook.call(this, 'beforeDestroy', name, fn)
}
Hooks.afterDestroy = function(name, fn) {
Hooks.addHook.call(this, 'afterDestroy', name, fn)
}
Hooks.beforeDelete = function(name, fn) {
Hooks.addHook.call(this, 'beforeDestroy', name, fn)
}
Hooks.afterDelete = function(name, fn) {
Hooks.addHook.call(this, 'afterDestroy', name, fn)
}
Hooks.beforeUpdate = function(name, fn) {
Hooks.addHook.call(this, 'beforeUpdate', name, fn)
}
Hooks.afterUpdate = function(name, fn) {
Hooks.addHook.call(this, 'afterUpdate', name, fn)
}
Hooks.beforeBulkCreate = function(name, fn) {
Hooks.addHook.call(this, 'beforeBulkCreate', name, fn)
}
Hooks.afterBulkCreate = function(name, fn) {
Hooks.addHook.call(this, 'afterBulkCreate', name, fn)
}
Hooks.beforeBulkDestroy = function(name, fn) {
Hooks.addHook.call(this, 'beforeBulkDestroy', name, fn)
}
Hooks.afterBulkDestroy = function(name, fn) {
Hooks.addHook.call(this, 'afterBulkDestroy', name, fn)
}
Hooks.beforeBulkUpdate = function(name, fn) {
Hooks.addHook.call(this, 'beforeBulkUpdate', name, fn)
}
Hooks.afterBulkUpdate = function(name, fn) {
Hooks.addHook.call(this, 'afterBulkUpdate', name, fn)
}
var moment = require("moment") var moment = require("moment")
, path = require("path")
, Utils = require("./utils") , Utils = require("./utils")
, DataTypes = require("./data-types") , DataTypes = require("./data-types")
, QueryInterface = require("./query-interface") , QueryInterface = require("./query-interface")
module.exports = (function() { module.exports = (function() {
var Migration = function(migrator, path) { var Migration = function(migrator, p) {
this.migrator = migrator this.migrator = migrator
this.path = path this.path = path.normalize(p)
this.filename = Utils._.last(this.path.split('/')) this.filename = Utils._.last(this.path.split(path.sep))
var parsed = Migration.parseFilename(this.filename) var parsed = Migration.parseFilename(this.filename)
......
const fs = require("fs") const fs = require("fs")
, moment = require("moment") , moment = require("moment")
var Utils = require("./utils") var Utils = require(__dirname + "/utils")
, Migration = require("./migration") , Migration = require(__dirname + "/migration")
, DataTypes = require("./data-types") , DataTypes = require(__dirname + "/data-types")
module.exports = (function() { module.exports = (function() {
var Migrator = function(sequelize, options) { var Migrator = function(sequelize, options) {
...@@ -45,7 +45,7 @@ module.exports = (function() { ...@@ -45,7 +45,7 @@ module.exports = (function() {
if (err) { if (err) {
emitter.emit('error', err) emitter.emit('error', err)
} else { } else {
var chainer = new Utils.QueryChainer var chainer = new Utils.QueryChainer()
, from = migrations[0] , from = migrations[0]
if (options.method === 'down') { if (options.method === 'down') {
...@@ -58,8 +58,10 @@ module.exports = (function() { ...@@ -58,8 +58,10 @@ module.exports = (function() {
} else { } else {
self.options.logging("Running migrations...") self.options.logging("Running migrations...")
} }
migrations.forEach(function(migration) { migrations.forEach(function(migration) {
var migrationTime 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) {
...@@ -67,6 +69,7 @@ module.exports = (function() { ...@@ -67,6 +69,7 @@ module.exports = (function() {
} }
migrationTime = process.hrtime() migrationTime = process.hrtime()
}, },
after: function(migration) { after: function(migration) {
migrationTime = process.hrtime(migrationTime) migrationTime = process.hrtime(migrationTime)
migrationTime = Math.round( (migrationTime[0] * 1000) + (migrationTime[1] / 1000000)); migrationTime = Math.round( (migrationTime[0] * 1000) + (migrationTime[1] / 1000000));
...@@ -75,6 +78,7 @@ module.exports = (function() { ...@@ -75,6 +78,7 @@ module.exports = (function() {
self.options.logging('Completed in ' + migrationTime + 'ms') self.options.logging('Completed in ' + migrationTime + 'ms')
} }
}, },
success: function(migration, callback) { success: function(migration, callback) {
if (options.method === 'down') { if (options.method === 'down') {
deleteUndoneMigration.call(self, from, migration, callback) deleteUndoneMigration.call(self, from, migration, callback)
...@@ -177,17 +181,77 @@ module.exports = (function() { ...@@ -177,17 +181,77 @@ module.exports = (function() {
}).run() }).run()
} }
/**
* Explicitly executes one or multiple migrations.
*
* @param filename {String|Array} Absolute filename(s) of the migrations script
* @param options {Object} Can contain three functions, before, after and success, which are executed before
* or after each migration respectively, with one parameter, the migration.
*/
Migrator.prototype.exec = function(filename, options) {
var self = this;
return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer()
var addMigration = function(filename) {
self.options.logging('Adding migration script at ' + filename)
var migration = new Migration(self, filename)
chainer.add(migration, 'execute', [{ method: 'up' }], {
before: function(migration) {
if (options && Utils._.isFunction(options.before)) {
options.before.call(self, migration);
}
},
after: function(migration) {
if (options && Utils._.isFunction(options.after)) {
options.after.call(self, migration);
}
},
success: function(migration, callback) {
if (options && Utils._.isFunction(options.success)) {
options.success.call(self, migration);
}
callback();
}
})
}
if (Utils._.isArray(filename)) {
Utils._.each(filename, function(f) {
addMigration(f);
})
} else {
addMigration(filename);
}
chainer
.runSerially({ skipOnError: true })
.success(function() { emitter.emit('success', null) })
.error(function(err) { emitter.emit('error', err) })
}).run()
}
// private // private
var getLastMigrationFromDatabase = function() { var getLastMigrationFromDatabase = function() {
var self = this var self = this
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
self.findOrCreateSequelizeMetaDAO().success(function(SequelizeMeta) { self
SequelizeMeta.find({ order: 'id DESC' }).success(function(meta) { .findOrCreateSequelizeMetaDAO()
.success(function(SequelizeMeta) {
SequelizeMeta
.find({ order: 'id DESC' })
.success(function(meta) {
emitter.emit('success', meta ? meta : null) emitter.emit('success', meta ? meta : null)
}).error(function(err) { emitter.emit('error', err) }) })
}).error(function(err) { emitter.emit(err) }) .error(function(err) {
emitter.emit('error', err)
})
})
.error(function(err) {
emitter.emit('error', err)
})
}).run() }).run()
} }
...@@ -195,7 +259,8 @@ module.exports = (function() { ...@@ -195,7 +259,8 @@ module.exports = (function() {
var self = this var self = this
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
getLastMigrationFromDatabase.call(self) getLastMigrationFromDatabase
.call(self)
.success(function(meta) { .success(function(meta) {
emitter.emit('success', meta ? meta.to : null) emitter.emit('success', meta ? meta.to : null)
}) })
......
var Utils = require("./utils") var Utils = require(__dirname + "/utils")
module.exports = (function() { module.exports = (function() {
var QueryChainer = function(emitters) { var QueryChainer = function(emitters) {
...@@ -125,15 +125,15 @@ module.exports = (function() { ...@@ -125,15 +125,15 @@ module.exports = (function() {
this.finished = true this.finished = true
if (this.emitters.length > 0) { if (this.emitters.length > 0) {
this.finished = (this.finishedEmits == this.emitters.length) this.finished = (this.finishedEmits === this.emitters.length)
} }
else if (this.serials.length > 0) { else if (this.serials.length > 0) {
this.finished = (this.finishedEmits == this.serials.length) this.finished = (this.finishedEmits === this.serials.length)
} }
if (this.finished && this.wasRunning) { if (this.finished && this.wasRunning) {
var status = (this.fails.length == 0 ? 'success' : 'error') var status = (this.fails.length === 0 ? 'success' : 'error')
, result = (this.fails.length == 0 ? this[resultsName] : this.fails) , result = (this.fails.length === 0 ? this[resultsName] : this.fails)
this.eventEmitter.emit.apply(this.eventEmitter, [status, result].concat(result)) this.eventEmitter.emit.apply(this.eventEmitter, [status, result].concat(result))
} }
......
var url = require("url") var url = require("url")
, Path = require("path")
, Utils = require("./utils") , Utils = require("./utils")
, DAOFactory = require("./dao-factory") , DAOFactory = require("./dao-factory")
, DataTypes = require('./data-types') , DataTypes = require('./data-types')
...@@ -14,6 +15,7 @@ module.exports = (function() { ...@@ -14,6 +15,7 @@ module.exports = (function() {
@param {String} [password=null] The password which is used to authenticate against the database. @param {String} [password=null] The password which is used to authenticate against the database.
@param {Object} [options={}] An object with options. @param {Object} [options={}] An object with options.
@param {String} [options.dialect='mysql'] The dialect of the relational database. @param {String} [options.dialect='mysql'] The dialect of the relational database.
@param {String} [options.dialectModulePath=null] If specified, load the dialect library from this path.
@param {String} [options.host='localhost'] The host of the relational database. @param {String} [options.host='localhost'] The host of the relational database.
@param {Integer} [options.port=3306] The port of the relational database. @param {Integer} [options.port=3306] The port of the relational database.
@param {String} [options.protocol='tcp'] The protocol of the relational database. @param {String} [options.protocol='tcp'] The protocol of the relational database.
...@@ -26,6 +28,7 @@ module.exports = (function() { ...@@ -26,6 +28,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
...@@ -47,7 +50,8 @@ module.exports = (function() { ...@@ -47,7 +50,8 @@ module.exports = (function() {
var urlParts var urlParts
options = options || {} options = options || {}
if (arguments.length === 1) { if (arguments.length === 1 || (arguments.length === 2 && typeof username === 'object')) {
options = username || {}
urlParts = url.parse(arguments[0]) urlParts = url.parse(arguments[0])
database = urlParts.path.replace(/^\//, '') database = urlParts.path.replace(/^\//, '')
dialect = urlParts.protocol dialect = urlParts.protocol
...@@ -66,8 +70,8 @@ module.exports = (function() { ...@@ -66,8 +70,8 @@ module.exports = (function() {
this.options = Utils._.extend({ this.options = Utils._.extend({
dialect: 'mysql', dialect: 'mysql',
dialectModulePath: null,
host: 'localhost', host: 'localhost',
port: 3306,
protocol: 'tcp', protocol: 'tcp',
define: {}, define: {},
query: {}, query: {},
...@@ -78,7 +82,9 @@ module.exports = (function() { ...@@ -78,7 +82,9 @@ module.exports = (function() {
native: false, native: false,
replication: false, replication: false,
ssl: undefined, ssl: undefined,
pool: {} pool: {},
quoteIdentifiers: true,
language: 'en'
}, options || {}) }, options || {})
if (this.options.logging === true) { if (this.options.logging === true) {
...@@ -98,10 +104,16 @@ module.exports = (function() { ...@@ -98,10 +104,16 @@ module.exports = (function() {
native : this.options.native, native : this.options.native,
ssl : this.options.ssl, ssl : this.options.ssl,
replication: this.options.replication, replication: this.options.replication,
maxConcurrentQueries: this.options.maxConcurrentQueries dialectModulePath: this.options.dialectModulePath,
maxConcurrentQueries: this.options.maxConcurrentQueries,
dialectOptions: this.options.dialectOptions,
} }
try {
var ConnectorManager = require("./dialects/" + this.options.dialect + "/connector-manager") var ConnectorManager = require("./dialects/" + this.options.dialect + "/connector-manager")
} catch(err) {
throw new Error("The dialect " + this.options.dialect + " is not supported.")
}
this.daoFactoryManager = new DAOFactoryManager(this) this.daoFactoryManager = new DAOFactoryManager(this)
this.connectorManager = new ConnectorManager(this, this.config) this.connectorManager = new ConnectorManager(this, this.config)
...@@ -151,17 +163,8 @@ module.exports = (function() { ...@@ -151,17 +163,8 @@ module.exports = (function() {
Sequelize.prototype.define = function(daoName, attributes, options) { Sequelize.prototype.define = function(daoName, attributes, options) {
options = options || {} options = options || {}
var globalOptions = this.options var self = this
, 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)
...@@ -172,7 +175,43 @@ module.exports = (function() { ...@@ -172,7 +175,43 @@ module.exports = (function() {
} }
}) })
} }
options.omitNull = globalOptions.omitNull options.omitNull = globalOptions.omitNull
options.language = globalOptions.language
// 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 (dataType.toString() === "ENUM") {
attributes[name].validate = attributes[name].validate || {
_checkEnum: function(value) {
var hasValue = value !== undefined
, isMySQL = self.options.dialect === "mysql"
, ciCollation = !!options.collate && options.collate.match(/_ci$/i) !== null
, valueOutOfScope
if (isMySQL && ciCollation && hasValue) {
var scopeIndex = (attributes[name].values || []).map(function(d) { return d.toLowerCase() }).indexOf(value.toLowerCase())
valueOutOfScope = scopeIndex === -1
} else {
valueOutOfScope = ((attributes[name].values || []).indexOf(value) === -1)
}
if (hasValue && valueOutOfScope && !(attributes[name].allowNull === true && values[attrName] === null)) {
throw new Error('Value "' + value + '" for ENUM ' + name + ' is out of allowed scope. Allowed values: ' + attributes[name].values.join(', '))
}
}
}
}
})
// if you call "define" multiple times for the same daoName, do not clutter the factory // if you call "define" multiple times for the same daoName, do not clutter the factory
if(this.isDefined(daoName)) { if(this.isDefined(daoName)) {
...@@ -181,17 +220,42 @@ module.exports = (function() { ...@@ -181,17 +220,42 @@ module.exports = (function() {
var factory = new DAOFactory(daoName, attributes, options) var factory = new DAOFactory(daoName, attributes, options)
this.daoFactoryManager.addDAO(factory.init(this.daoFactoryManager)) this.daoFactoryManager.addDAO(factory.init(this.daoFactoryManager))
return factory return factory
} }
/**
Fetch a DAO factory
@param {String} daoName The name of a model defined with Sequelize.define
@returns {DAOFactory} The DAOFactory for daoName
*/
Sequelize.prototype.model = function(daoName) {
if(!this.isDefined(daoName)) {
throw new Error(daoName + ' has not been defined')
}
return this.daoFactoryManager.getDAO(daoName)
}
Sequelize.prototype.isDefined = function(daoName) { Sequelize.prototype.isDefined = function(daoName) {
var daos = this.daoFactoryManager.daos var daos = this.daoFactoryManager.daos
return (daos.filter(function(dao) { return dao.name === daoName }).length !== 0) return (daos.filter(function(dao) { return dao.name === daoName }).length !== 0)
} }
Sequelize.prototype.import = function(path) { Sequelize.prototype.import = function(path) {
// is it a relative path?
if (url.parse(path).pathname.indexOf('/') !== 0) {
// make path relative to the caller
var callerFilename = Utils.stack()[1].getFileName()
, callerMatch = callerFilename.match(/(.+\/).+?$/)
, callerPath = callerMatch[1]
path = Path.resolve(callerPath, path)
}
if (!this.importCache[path]) { if (!this.importCache[path]) {
var defineCall = require(path) var defineCall = (arguments.length > 1 ? arguments[1] : require(path))
this.importCache[path] = defineCall(this, DataTypes) this.importCache[path] = defineCall(this, DataTypes)
} }
...@@ -199,12 +263,17 @@ module.exports = (function() { ...@@ -199,12 +263,17 @@ module.exports = (function() {
} }
Sequelize.prototype.migrate = function(options) { Sequelize.prototype.migrate = function(options) {
this.getMigrator().migrate(options) return this.getMigrator().migrate(options)
} }
Sequelize.prototype.query = function(sql, callee, options, replacements) { Sequelize.prototype.query = function(sql, callee, options, replacements) {
if (arguments.length === 4) { if (arguments.length === 4) {
if (Array.isArray(replacements)) {
sql = Utils.format([sql].concat(replacements), this.options.dialect) sql = Utils.format([sql].concat(replacements), this.options.dialect)
}
else {
sql = Utils.formatNamedParameters(sql, 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) {
...@@ -288,5 +357,13 @@ module.exports = (function() { ...@@ -288,5 +357,13 @@ module.exports = (function() {
}).run() }).run()
} }
Sequelize.prototype.fn = function (fn) {
return new Utils.fn(fn, Array.prototype.slice.call(arguments, 1))
}
Sequelize.prototype.col = function (col) {
return new Utils.col(col)
}
return Sequelize return Sequelize
})() })()
var SqlString = exports; var moment = require("moment")
, isArrayBufferView
, SqlString = exports;
if (typeof ArrayBufferView === 'function') {
isArrayBufferView = function(object) { return object && (object instanceof ArrayBufferView) }
} else {
var arrayBufferViews = [
Int8Array, Uint8Array, Int16Array, Uint16Array,
Int32Array, Uint32Array, Float32Array, Float64Array
]
isArrayBufferView = function(object) {
for (var i=0; i<8; i++) {
if (object instanceof arrayBufferViews[i]) {
return true
}
}
return false
};
}
SqlString.escapeId = function (val, forbidQualified) { SqlString.escapeId = function (val, forbidQualified) {
if (forbidQualified) { if (forbidQualified) {
return '`' + val.replace(/`/g, '``') + '`'; return '`' + val.replace(/`/g, '``') + '`'
}
return '`' + val.replace(/`/g, '``').replace(/\./g, '`.`') + '`'
}
SqlString.escape = function(val, stringifyObjects, timeZone, dialect, field) {
if (arguments.length === 1 && typeof arguments[0] === "object") {
val = val.val || val.value || null
stringifyObjects = val.stringifyObjects || val.objects || undefined
timeZone = val.timeZone || val.zone || null
dialect = val.dialect || null
field = val.field || null
}
else if (arguments.length < 3 && typeof arguments[1] === "object") {
timeZone = stringifyObjects.timeZone || stringifyObjects.zone || null
dialect = stringifyObjects.dialect || null
field = stringifyObjects.field || null
} }
return '`' + val.replace(/`/g, '``').replace(/\./g, '`.`') + '`';
};
SqlString.escape = function(val, stringifyObjects, timeZone, dialect) {
if (val === undefined || val === null) { if (val === undefined || val === null) {
return 'NULL'; return 'NULL';
} }
switch (typeof val) { switch (typeof val) {
case 'boolean': return (val) ? 'true' : 'false'; case 'boolean':
case 'number': return val+''; // SQLite doesn't have true/false support. MySQL aliases true/false to 1/0
// for us. Postgres actually has a boolean type with true/false literals,
// but sequelize doesn't use it yet.
return dialect === 'sqlite' ? +!!val : ('' + !!val);
case 'number':
return val+'';
} }
if (val instanceof Date) { if (val instanceof Date) {
val = SqlString.dateToString(val, timeZone || "Z"); val = SqlString.dateToString(val, timeZone || "Z", dialect)
} }
if (Buffer.isBuffer(val)) { if (Buffer.isBuffer(val)) {
return SqlString.bufferToString(val); return SqlString.bufferToString(val, dialect)
} }
if (Array.isArray(val) || isArrayBufferView(val)) {
if (Array.isArray(val)) { return SqlString.arrayToList(val, timeZone, dialect, field)
return SqlString.arrayToList(val, timeZone);
} }
if (typeof val === 'object') { if (typeof val === 'object') {
if (stringifyObjects) { if (stringifyObjects) {
val = val.toString(); val = val.toString()
} else { } else {
return SqlString.objectToValues(val, timeZone); return SqlString.objectToValues(val, timeZone)
} }
} }
if (dialect == "postgres") { if (dialect === 'postgres' || dialect === 'sqlite') {
// http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS // http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS
val = val.replace(/'/g, "''"); // http://stackoverflow.com/q/603572/130598
val = val.replace(/'/g, "''")
} else { } else {
val = val.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) { val = val.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) {
switch(s) { switch(s) {
...@@ -51,91 +89,134 @@ SqlString.escape = function(val, stringifyObjects, timeZone, dialect) { ...@@ -51,91 +89,134 @@ SqlString.escape = function(val, stringifyObjects, timeZone, dialect) {
case "\x1a": return "\\Z"; case "\x1a": return "\\Z";
default: return "\\"+s; default: return "\\"+s;
} }
}); })
} }
return "'"+val+"'"; return "'"+val+"'"
}; }
SqlString.arrayToList = function(array, timeZone) { SqlString.arrayToList = function(array, timeZone, dialect, field) {
if (dialect === 'postgres') {
if (array.map) {
var valstr = array.map(function(v) {
return SqlString.escape(v, true, timeZone, dialect, field)
}).join(',')
} else {
var valstr = ""
for (var i = 0; i < array.length; i++) {
valstr += SqlString.escape(array[i], true, timeZone, dialect, field) + ','
}
valstr = valstr.slice(0,-1)
}
var ret = 'ARRAY[' + valstr + ']'
if (!!field && !!field.type) {
ret += '::' + field.type.replace(/\(\d+\)/g, '')
}
return ret
} else {
if (array.map) {
return array.map(function(v) { return array.map(function(v) {
if (Array.isArray(v)) return '(' + SqlString.arrayToList(v) + ')'; if (Array.isArray(v)) {
return SqlString.escape(v, true, timeZone); return '(' + SqlString.arrayToList(v, timeZone, dialect) + ')'
}).join(', '); }
}; return SqlString.escape(v, true, timeZone, dialect)
}).join(', ')
} else {
var valstr = ""
for (var i = 0; i < array.length; i++) {
valstr += SqlString.escape(array[i], true, timeZone, dialect) + ', '
}
return valstr.slice(0, -2)
}
}
}
SqlString.format = function(sql, values, timeZone, dialect) { 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) {
if (!values.length) { if (!values.length) {
return match; return match
}
return SqlString.escape(values.shift(), false, timeZone, dialect)
})
}
SqlString.formatNamedParameters = function(sql, values, timeZone, dialect) {
return sql.replace(/\:(\w+)/g, function (value, key) {
if (values.hasOwnProperty(key)) {
return SqlString.escape(values[key], false, timeZone, dialect)
} else {
throw new Error('Named parameter "' + value + '" has no value in the given object.')
} }
})
}
return SqlString.escape(values.shift(), false, timeZone, dialect); SqlString.dateToString = function(date, timeZone, dialect) {
}); var dt = new Date(date)
};
SqlString.dateToString = function(date, timeZone) { // TODO: Ideally all dialects would work a bit more like this
var dt = new Date(date); if (dialect === "postgres") {
return moment(dt).zone('+00:00').format("YYYY-MM-DD HH:mm:ss.SSS Z")
}
if (timeZone != 'local') { if (timeZone !== 'local') {
var tz = convertTimezone(timeZone); var tz = convertTimezone(timeZone)
dt.setTime(dt.getTime() + (dt.getTimezoneOffset() * 60000)); dt.setTime(dt.getTime() + (dt.getTimezoneOffset() * 60000))
if (tz !== false) { if (tz !== false) {
dt.setTime(dt.getTime() + (tz * 60000)); dt.setTime(dt.getTime() + (tz * 60000))
} }
} }
var year = dt.getFullYear(); return moment(dt).format("YYYY-MM-DD HH:mm:ss")
var month = zeroPad(dt.getMonth() + 1); }
var day = zeroPad(dt.getDate());
var hour = zeroPad(dt.getHours());
var minute = zeroPad(dt.getMinutes());
var second = zeroPad(dt.getSeconds());
return year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second;
};
SqlString.bufferToString = function(buffer) { SqlString.bufferToString = function(buffer, dialect) {
var hex = ''; var hex = ''
try { try {
hex = buffer.toString('hex'); hex = buffer.toString('hex')
} catch (err) { } catch (err) {
// node v0.4.x does not support hex / throws unknown encoding error // node v0.4.x does not support hex / throws unknown encoding error
for (var i = 0; i < buffer.length; i++) { for (var i = 0; i < buffer.length; i++) {
var byte = buffer[i]; var byte = buffer[i]
hex += zeroPad(byte.toString(16)); hex += zeroPad(byte.toString(16))
} }
} }
return "X'" + hex+ "'"; if (dialect === 'postgres') {
}; // bytea hex format http://www.postgresql.org/docs/current/static/datatype-binary.html
return "E'\\\\x" + hex+ "'"
}
return "X'" + hex+ "'"
}
SqlString.objectToValues = function(object, timeZone) { SqlString.objectToValues = function(object, timeZone) {
var values = []; var values = []
for (var key in object) { for (var key in object) {
var value = object[key]; var value = object[key]
if(typeof value === 'function') { if(typeof value === 'function') {
continue; continue;
} }
values.push(this.escapeId(key) + ' = ' + SqlString.escape(value, true, timeZone)); values.push(this.escapeId(key) + ' = ' + SqlString.escape(value, true, timeZone))
} }
return values.join(', '); return values.join(', ')
}; }
function zeroPad(number) { function zeroPad(number) {
return (number < 10) ? '0' + number : number; return (number < 10) ? '0' + number : number
} }
function convertTimezone(tz) { function convertTimezone(tz) {
if (tz == "Z") return 0; if (tz == "Z") {
return 0
}
var m = tz.match(/([\+\-\s])(\d\d):?(\d\d)?/); var m = tz.match(/([\+\-\s])(\d\d):?(\d\d)?/)
if (m) { if (m) {
return (m[1] == '-' ? -1 : 1) * (parseInt(m[2], 10) + ((m[3] ? parseInt(m[3], 10) : 0) / 60)) * 60; return (m[1] == '-' ? -1 : 1) * (parseInt(m[2], 10) + ((m[3] ? parseInt(m[3], 10) : 0) / 60)) * 60
} }
return false; return false
} }
{ {
"name": "sequelize", "name": "sequelize",
"description": "Multi dialect ORM for Node.JS", "description": "Multi dialect ORM for Node.JS",
"version": "1.7.0-alpha1", "version": "1.7.0-beta.0",
"author": "Sascha Depold <sascha@depold.com>", "author": "Sascha Depold <sascha@depold.com>",
"contributors": [ "contributors": [
{ {
...@@ -17,8 +17,15 @@ ...@@ -17,8 +17,15 @@
"email": "sky@skytrife.com" "email": "sky@skytrife.com"
}, },
{ {
"name": "Jan Aagaard Meier (Innofluence)", "name": "Jan Aagaard Meier",
"email": "jam@innofluence.com" "email": [
"janzeh@gmail.com",
"jmei@itu.dk"
]
},
{
"name": "Daniel Durante",
"email": "me@danieldurante.com"
} }
], ],
"repository": { "repository": {
...@@ -29,25 +36,29 @@ ...@@ -29,25 +36,29 @@
"url": "https://github.com/sequelize/sequelize/issues" "url": "https://github.com/sequelize/sequelize/issues"
}, },
"dependencies": { "dependencies": {
"lodash": "~1.2.1", "lodash": "~2.1.0",
"underscore.string": "~2.3.0", "underscore.string": "~2.3.0",
"lingo": "~0.0.5", "lingo": "~0.0.5",
"validator": "1.1.1", "validator": "~1.5.0",
"moment": "~1.7.0", "moment": "~2.2.1",
"commander": "~0.6.0", "commander": "~2.0.0",
"generic-pool": "1.0.12", "dottie": "0.0.8-0",
"dottie": "0.0.6-1", "toposort-class": "~0.2.0",
"toposort-class": "0.1.4" "generic-pool": "2.0.4",
"promise": "~3.2.0",
"sql": "~0.26.0"
}, },
"devDependencies": { "devDependencies": {
"jasmine-node": "1.5.0", "sqlite3": "~2.1.12",
"sqlite3": "~2.1.5", "mysql": "~2.0.0-alpha8",
"mysql": "~2.0.0-alpha7", "pg": "~2.6.0",
"pg": "~0.10.2", "watchr": "~2.4.3",
"mariasql": "~0.1.18", "yuidocjs": "~0.3.36",
"buster": "~0.6.0", "chai": "~1.8.0",
"watchr": "~2.2.0", "mocha": "~1.13.0",
"yuidocjs": "~0.3.36" "chai-datetime": "~1.1.1",
"sinon": "~1.7.3",
"mariasql": "~0.1.19"
}, },
"keywords": [ "keywords": [
"mysql", "mysql",
...@@ -57,22 +68,14 @@ ...@@ -57,22 +68,14 @@
], ],
"main": "index", "main": "index",
"scripts": { "scripts": {
"test": "npm run test-jasmine && npm run test-buster", "test": "make all",
"test-jasmine": "jasmine-node spec-jasmine/",
"test-buster": "npm run test-buster-mysql && npm run test-buster-postgres && npm run test-buster-postgres-native && npm run test-buster-sqlite && npm run test-buster-mariadb",
"test-buster-travis": "buster-test",
"test-buster-mysql": "DIALECT=mysql buster-test",
"test-buster-postgres": "DIALECT=postgres buster-test",
"test-buster-postgres-native": "DIALECT=postgres-native buster-test",
"test-buster-sqlite": "DIALECT=sqlite buster-test",
"test-buster-mariadb": "DIALECT=mariadb buster-test",
"docs": "node_modules/.bin/yuidoc . -o docs" "docs": "node_modules/.bin/yuidoc . -o docs"
}, },
"bin": { "bin": {
"sequelize": "bin/sequelize" "sequelize": "bin/sequelize"
}, },
"engines": { "engines": {
"node": ">=0.4.6" "node": ">=0.6.21"
}, },
"license": "MIT" "license": "MIT"
} }
module.exports = {
up: function() {},
down: function() {}
}
module.exports = {
up: function(migration, DataTypes) {
migration.addColumn('User', 'signature', DataTypes.TEXT)
migration.addColumn('User', 'shopId', { type: DataTypes.INTEGER, allowNull: true })
migration.addColumn('User', 'isAdmin', { type: DataTypes.BOOLEAN, defaultValue: false, allowNull: false })
},
down: function(migration, DataTypes) {
migration.removeColumn('User', 'signature')
migration.removeColumn('User', 'shopId')
migration.removeColumn('User', 'isAdmin')
}
}
module.exports = {
up: function(migration, DataTypes) {
migration.removeColumn('User', 'shopId')
},
down: function(migration, DataTypes) {
migration.addColumn('User', 'shopId', { type: DataTypes.INTEGER, allowNull: true })
}
}
module.exports = {
up: function(migration, DataTypes) {
migration.changeColumn('User', 'signature', {
type: DataTypes.STRING,
allowNull: false,
defaultValue: 'Signature'
})
},
down: function(migration, DataTypes) {}
}
module.exports = {
up: function(migration, DataTypes) {
migration.renameColumn('User', 'signature', 'sig')
},
down: function(migration, DataTypes) {
migration.renameColumn('User', 'sig', 'signature')
}
}
var config = require("../config/config")
, Sequelize = require("../../index")
, sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { pool: config.mysql.pool, logging: false, host: config.mysql.host, port: config.mysql.port })
, Helpers = new (require("../config/helpers"))(sequelize)
describe('BelongsTo', function() {
var User = null
, Task = null
var setup = function() {
User = sequelize.define('User', { username: Sequelize.STRING })
Task = sequelize.define('Task', { title: Sequelize.STRING })
}
beforeEach(function() { Helpers.dropAllTables(); setup() })
afterEach(function() { Helpers.dropAllTables() })
it('adds the foreign key', function() {
Task.belongsTo(User)
expect(Task.attributes['UserId']).toEqual("INTEGER")
})
it("underscores the foreign key", function() {
Task = sequelize.define('Task', { title: Sequelize.STRING }, {underscored: true})
Task.belongsTo(User)
expect(Task.attributes['user_id']).toEqual("INTEGER")
})
it("uses the passed foreign key", function() {
Task.belongsTo(User, {foreignKey: 'person_id'})
expect(Task.attributes['person_id']).toEqual("INTEGER")
})
it("defines getters and setters", function() {
Task.belongsTo(User)
var task = Task.build({title: 'asd'})
expect(task.setUser).toBeDefined()
expect(task.getUser).toBeDefined()
})
it("aliases the getters and setters according to the passed 'as' option", function() {
Task.belongsTo(User, {as: 'Person'})
var task = Task.build({title: 'asd'})
expect(task.setPerson).toBeDefined()
expect(task.getPerson).toBeDefined()
})
it("aliases associations to the same table according to the passed 'as' option", function() {
Task.belongsTo(User, {as: 'Poster'})
Task.belongsTo(User, {as: 'Owner'})
var task = Task.build({title: 'asd'})
expect(task.getPoster).toBeDefined()
expect(task.setPoster).toBeDefined()
expect(task.getOwner).toBeDefined()
expect(task.setOwner).toBeDefined()
})
it("intializes the foreign key with null", function() {
Task.belongsTo(User)
var task = Task.build({title: 'asd'})
expect(task['UserId']).not.toBeDefined();
})
it("sets and gets the correct objects", function() {
Task.belongsTo(User, {as: 'User'})
Helpers.async(function(done) {
User.sync({force: true}).success(function() {
Task.sync({force: true}).success(done)
})
})
Helpers.async(function(done) {
User.create({username: 'asd'}).success(function(u) {
Task.create({title: 'a task'}).success(function(t) {
t.setUser(u).success(function() {
t.getUser().success(function(user) {
expect(user.username).toEqual('asd')
done()
})
})
})
})
})
})
it("handles self associations", function() {
Helpers.async(function(done) {
var Person = sequelize.define('Person', { name: Sequelize.STRING })
Person.belongsTo(Person, {as: 'Mother', foreignKey: 'MotherId'})
Person.belongsTo(Person, {as: 'Father', foreignKey: 'FatherId'})
Person.sync({force: true}).success(function() {
var p = Person.build()
expect(p.setFather).toBeDefined()
expect(p.setMother).toBeDefined()
done()
})
})
})
it("sets the foreign key in self associations", function() {
var Person = sequelize.define('Person', { name: Sequelize.STRING })
Person.belongsTo(Person, {as: 'Mother'})
expect(Person.associations.MotherPersons.options.foreignKey).toEqual('MotherId')
})
})
var config = require("../config/config")
, Sequelize = require("../../index")
, sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { pool: config.mysql.pool, logging: false, host: config.mysql.host, port: config.mysql.port })
, Helpers = new (require("../config/helpers"))(sequelize)
describe('HasOne', function() {
var User = null
, Task = null
var setup = function() {
User = sequelize.define('User', { username: Sequelize.STRING })
Task = sequelize.define('Task', { title: Sequelize.STRING })
}
beforeEach(function() { Helpers.dropAllTables(); setup() })
afterEach(function() { Helpers.dropAllTables() })
it("adds the foreign key", function() {
User.hasOne(Task)
expect(Task.attributes.UserId).toEqual("INTEGER")
})
it("adds an underscored foreign key", function() {
User = sequelize.define('User', { username: Sequelize.STRING }, {underscored: true})
Task = sequelize.define('Task', { title: Sequelize.STRING })
User.hasOne(Task)
expect(Task.attributes.user_id).toEqual("INTEGER")
})
it("uses the passed foreign key", function() {
User = sequelize.define('User', { username: Sequelize.STRING }, {underscored: true})
Task = sequelize.define('Task', { title: Sequelize.STRING })
User.hasOne(Task, {foreignKey: 'person_id'})
expect(Task.attributes.person_id).toEqual("INTEGER")
})
it("defines the getter and the setter", function() {
User.hasOne(Task)
var u = User.build({username: 'asd'})
expect(u.setTask).toBeDefined()
expect(u.getTask).toBeDefined()
})
it("defined the getter and the setter according to the passed 'as' option", function() {
User.hasOne(Task, {as: 'Work'})
var u = User.build({username: 'asd'})
expect(u.setWork).toBeDefined()
expect(u.getWork).toBeDefined()
})
it("aliases associations to the same table according to the passed 'as' option", function() {
User.hasOne(Task, {as: 'Work'});
User.hasOne(Task, {as: 'Play'});
var u = User.build({username: 'asd'})
expect(u.getWork).toBeDefined()
expect(u.setWork).toBeDefined()
expect(u.getPlay).toBeDefined()
expect(u.setPlay).toBeDefined()
})
it("gets and sets the correct objects", function() {
var user, task;
User.hasOne(Task, {as: 'Task'})
Helpers.async(function(done) {
User.sync({force: true}).success(function() {
Task.sync({force: true}).success(function() {
User.create({username: 'name'}).success(function(_user) {
Task.create({title: 'snafu'}).success(function(_task) {
user = _user
task = _task
done()
})
})
})
})
})
Helpers.async(function(done) {
user.setTask(task).on('success', function() {
user.getTask().on('success', function(task2) {
expect(task.title).toEqual(task2.title)
user.getTask({attributes: ['title']}).on('success', function(task2) {
expect(task2.selectedValues.title).toEqual('snafu')
expect(task2.selectedValues.id).toEqual(null)
done()
})
})
})
})
})
it("unsets unassociated objects", function() {
var user, task1, task2;
User.hasOne(Task, {as: 'Task'})
Helpers.async(function(done) {
User.sync({force: true}).success(function() {
Task.sync({force: true}).success(function() {
User.create({username: 'name'}).success(function(_user) {
Task.create({title: 'snafu'}).success(function(_task1) {
Task.create({title: 'another task'}).success(function(_task2) {
user = _user
task1 = _task1
task2 = _task2
done()
})
})
})
})
})
})
Helpers.async(function(done) {
user.setTask(task1).success(function() {
user.getTask().success(function(_task) {
expect(task1.title).toEqual(_task.title)
user.setTask(task2).success(function() {
user.getTask().success(function(_task2) {
expect(task2.title).toEqual(task2.title)
done()
})
})
})
})
})
})
it("sets self associations", function() {
Helpers.async(function(done) {
var Person = sequelize.define('Person', { name: Sequelize.STRING })
Person.hasOne(Person, {as: 'Mother', foreignKey: 'MotherId'})
Person.hasOne(Person, {as: 'Father', foreignKey: 'FatherId'})
Person.sync({force: true}).success(function() {
var p = Person.build()
expect(p.setFather).toBeDefined()
expect(p.setMother).toBeDefined()
done()
})
})
})
it("automatically sets the foreign key on self associations", function() {
var Person = sequelize.define('Person', { name: Sequelize.STRING })
Person.hasOne(Person, {as: 'Mother'})
expect(Person.associations.MotherPersons.options.foreignKey).toEqual('MotherId')
})
})
module.exports = {
rand: function() {
return parseInt(Math.random() * 999)
},
//make maxIdleTime small so that tests exit promptly
mysql: {
username: "root",
password: null,
database: 'sequelize_test',
host: '127.0.0.1',
port: 3306,
pool: { maxConnections: 5, maxIdleTime: 30}
},
mariadb: {
username: "root",
password: null,
database: 'sequelize_test',
host: '127.0.0.1',
port: 3306,
pool: { maxConnections: 5, maxIdleTime: 30}
},
sqlite: {
},
postgres: {
database: 'sequelize_test',
username: "postgres",
port: 5432,
pool: { maxConnections: 5, maxIdleTime: 30}
}
}
var Factories = module.exports = function(helpers) {
this.helpers = helpers
this.sequelize = this.helpers.sequelize
}
Factories.prototype.DAO = function(daoName, options, callback, count) {
count = count || 1
var self = this
, daos = []
this.helpers.async(function(done) {
var DAO = self.sequelize.daoFactoryManager.getDAO(daoName)
var create = function(cb) {
DAO.create(options).on('success', function(dao) {
daos.push(dao)
cb && cb()
}).on('error', function(err) {
console.log(err)
done()
})
}
var cb = function() {
if(--count) {
create(cb)
} else {
done()
callback && callback(daos)
}
}
create(cb)
})
}
Factories.prototype.User = function(options, callback, count) {
this.DAO('User', options, callback, count)
}
Sequelize = require("../../index")
var Helpers = module.exports = function(sequelize) {
this.sequelize = sequelize
this.Factories = new (require("./factories"))(this)
}
Helpers.prototype.sync = function() {
var self = this
this.async(function(done) {
self.sequelize
.sync({force: true})
.success(done)
.failure(function(err) { console.log(err) })
})
}
Helpers.prototype.drop = function() {
var self = this
this.async(function(done) {
self.sequelize
.drop()
.on('success', done)
.on('error', function(err) { console.log(err) })
})
}
Helpers.prototype.dropAllTables = function() {
var self = this
this.async(function(done) {
self.sequelize
.getQueryInterface()
.dropAllTables()
.success(done)
.error(function(err) { console.log(err) })
})
}
Helpers.prototype.async = function(fct) {
var done = false
runs(function() {
fct(function() { return done = true })
})
waitsFor(function(){ return done })
}
var config = require("../config/config")
, Sequelize = require("../../index")
, sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { pool: config.mysql.pool, logging: false, host: config.mysql.host, port: config.mysql.port })
, Helpers = new (require("../config/helpers"))(sequelize)
describe('HasMany', function() {
beforeEach(function() { Helpers.sync() })
afterEach(function() { Helpers.drop() })
//prevent periods from occurring in the table name since they are used to delimit (table.column)
var User = sequelize.define('User' + Math.ceil(Math.random()*10000000), { name: Sequelize.STRING })
, Task = sequelize.define('Task' + Math.ceil(Math.random()*10000000), { name: Sequelize.STRING })
, users = null
, tasks = null
User.hasMany(Task, {as:'Tasks'})
Task.hasMany(User, {as:'Users'})
beforeEach(function() {
Helpers.async(function(_done) {
Helpers.Factories.DAO(User.name, {name: 'User' + Math.random()}, function(_users) {
users = _users; _done()
}, 5)
})
Helpers.async(function(_done) {
Helpers.Factories.DAO(Task.name, {name: 'Task' + Math.random()}, function(_tasks) {
tasks = _tasks; _done()
}, 2)
})
})
describe('addDAO / getDAO', function() {
var user = null
, task = null
beforeEach(function() {
Helpers.async(function(done) {
User.all().on('success', function(_users) {
Task.all().on('success', function(_tasks) {
user = _users[0]
task = _tasks[0]
done()
})
})
})
})
it('should correctly add an association to the dao', function() {
Helpers.async(function(done) {
user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(0)
user.addTask(task).on('success', function() {
user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(1)
done()
})
})
})
})
})
})
describe('removeDAO', function() {
var user = null
, tasks = null
beforeEach(function() {
Helpers.async(function(done) {
User.all().on('success', function(users) {
Task.all().on('success', function(_tasks) {
user = users[0]
tasks = _tasks
done()
})
})
})
})
it("should correctly remove associated objects", function() {
Helpers.async(function(done) {
user.getTasks().on('success', function(__tasks) {
expect(__tasks.length).toEqual(0)
user.setTasks(tasks).on('success', function() {
user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(tasks.length)
user.removeTask(tasks[0]).on('success', function() {
user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(tasks.length - 1)
done()
})
})
})
})
})
})
})
})
})
var config = require("../config/config")
, Sequelize = require("../../index")
, sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { pool: config.mysql.pool, logging: false, host: config.mysql.host, port: config.mysql.port })
, Helpers = new (require("../config/helpers"))(sequelize)
describe('Associations', function() {
beforeEach(function() { Helpers.sync() })
afterEach(function() { Helpers.drop() })
/////////// many-to-many with same prefix ////////////
describe('many-to-many', function() {
describe('where tables have the same prefix', function() {
var Table2 = sequelize.define('wp_table2', {foo: Sequelize.STRING})
, Table1 = sequelize.define('wp_table1', {foo: Sequelize.STRING})
Table1.hasMany(Table2)
Table2.hasMany(Table1)
it("should create a table wp_table1wp_table2s", function() {
Helpers.async(function(done) {
expect(sequelize.daoFactoryManager.getDAO('wp_table1swp_table2s')).toBeDefined()
done()
})
})
})
describe('when join table name is specified', function() {
var Table2 = sequelize.define('ms_table1', {foo: Sequelize.STRING})
, Table1 = sequelize.define('ms_table2', {foo: Sequelize.STRING})
Table1.hasMany(Table2, {joinTableName: 'table1_to_table2'})
Table2.hasMany(Table1, {joinTableName: 'table1_to_table2'})
it("should not use a combined name", function() {
expect(sequelize.daoFactoryManager.getDAO('ms_table1sms_table2s')).toBeUndefined()
})
it("should use the specified name", function() {
expect(sequelize.daoFactoryManager.getDAO('table1_to_table2')).toBeDefined()
})
})
})
})
var config = require("../config/config")
, Sequelize = require("../../index")
, sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { pool: config.mysql.pool, logging: false, host: config.mysql.host, port: config.mysql.port })
, Helpers = new (require("../config/helpers"))(sequelize)
describe('ConnectorManager', function() {
beforeEach(function() {
Helpers.dropAllTables()
})
afterEach(function() {
Helpers.dropAllTables()
})
it('works correctly after being idle', function() {
var User = sequelize.define('User', { username: Sequelize.STRING })
Helpers.async(function(done) {
User.sync({force: true}).on('success', function() {
User.create({username: 'user1'}).on('success', function() {
User.count().on('success', function(count) {
expect(count).toEqual(1)
done()
})
})
})
})
Helpers.async(function(done) {
setTimeout(function() {
User.count().on('success', function(count) {
expect(count).toEqual(1)
done()
})
}, 1000)
})
})
})
var config = require("../config/config")
, Sequelize = require("../../index")
, sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { pool: config.mysql.pool, logging: false, host: config.mysql.host, port: config.mysql.port })
, Helpers = new (require("../config/helpers"))(sequelize)
describe('DAOFactory', function() {
beforeEach(function() { Helpers.sync() })
afterEach(function() { Helpers.drop() })
var User = sequelize.define('User', { age: Sequelize.INTEGER, name: Sequelize.STRING, bio: Sequelize.TEXT })
describe('constructor', function() {
it("handles extended attributes (unique)", function() {
var User = sequelize.define('User' + config.rand(), {
username: { type: Sequelize.STRING, unique: true }
}, { timestamps: false })
expect(User.attributes).toEqual({username:"VARCHAR(255) UNIQUE",id:"INTEGER NOT NULL auto_increment PRIMARY KEY"})
})
it("handles extended attributes (default)", function() {
var User = sequelize.define('User' + config.rand(), {
username: {type: Sequelize.STRING, defaultValue: 'foo'}
}, { timestamps: false })
expect(User.attributes).toEqual({username:"VARCHAR(255) DEFAULT 'foo'",id:"INTEGER NOT NULL auto_increment PRIMARY KEY"})
})
it("handles extended attributes (null)", function() {
var User = sequelize.define('User' + config.rand(), {
username: {type: Sequelize.STRING, allowNull: false}
}, { timestamps: false })
expect(User.attributes).toEqual({username:"VARCHAR(255) NOT NULL",id:"INTEGER NOT NULL auto_increment PRIMARY KEY"})
})
it("handles extended attributes (primaryKey)", function() {
var User = sequelize.define('User' + config.rand(), {
username: {type: Sequelize.STRING, primaryKey: true}
}, { timestamps: false })
expect(User.attributes).toEqual({username:"VARCHAR(255) PRIMARY KEY"})
})
it("adds timestamps", function() {
var User1 = sequelize.define('User' + config.rand(), {})
var User2 = sequelize.define('User' + config.rand(), {}, { timestamps: true })
expect(User1.attributes).toEqual({id:"INTEGER NOT NULL auto_increment PRIMARY KEY", updatedAt:"DATETIME NOT NULL", createdAt:"DATETIME NOT NULL"})
expect(User2.attributes).toEqual({id:"INTEGER NOT NULL auto_increment PRIMARY KEY", updatedAt:"DATETIME NOT NULL", createdAt:"DATETIME NOT NULL"})
})
it("adds deletedAt if paranoid", function() {
var User = sequelize.define('User' + config.rand(), {}, { paranoid: true })
expect(User.attributes).toEqual({id:"INTEGER NOT NULL auto_increment PRIMARY KEY", deletedAt:"DATETIME", updatedAt:"DATETIME NOT NULL", createdAt:"DATETIME NOT NULL"})
})
it("underscores timestamps if underscored", function() {
var User = sequelize.define('User' + config.rand(), {}, { paranoid: true, underscored: true })
expect(User.attributes).toEqual({id:"INTEGER NOT NULL auto_increment PRIMARY KEY", deleted_at:"DATETIME", updated_at:"DATETIME NOT NULL", created_at:"DATETIME NOT NULL"})
})
})
describe('primaryKeys', function() {
it("determines the correct primaryKeys", function() {
var User = sequelize.define('User' + config.rand(), {
foo: {type: Sequelize.STRING, primaryKey: true},
bar: Sequelize.STRING
})
expect(User.primaryKeys).toEqual({"foo":"VARCHAR(255) PRIMARY KEY"})
})
})
})
var config = require("../config/config")
, Sequelize = require("../../index")
, sequelize = new Sequelize(config.postgres.database, config.postgres.username, config.postgres.password, {
logging: false,
port: config.postgres.port,
dialect: 'postgres'
})
, Helpers = new (require("../config/helpers"))(sequelize)
describe('HasMany', function() {
beforeEach(function() { Helpers.sync() })
afterEach(function() { Helpers.drop() })
//prevent periods from occurring in the table name since they are used to delimit (table.column)
var User = sequelize.define('User' + Math.ceil(Math.random()*10000000), { name: Sequelize.STRING })
, Task = sequelize.define('Task' + Math.ceil(Math.random()*10000000), { name: Sequelize.STRING })
, users = null
, tasks = null
User.hasMany(Task, {as:'Tasks'})
Task.hasMany(User, {as:'Users'})
beforeEach(function() {
Helpers.async(function(_done) {
Helpers.Factories.DAO(User.name, {name: 'User' + Math.random()}, function(_users) {
users = _users; _done()
}, 5)
})
Helpers.async(function(_done) {
Helpers.Factories.DAO(Task.name, {name: 'Task' + Math.random()}, function(_tasks) {
tasks = _tasks; _done()
}, 2)
})
})
describe('addDAO / getDAO', function() {
var user = null
, task = null
beforeEach(function() {
Helpers.async(function(done) {
User.all().on('success', function(_users) {
Task.all().on('success', function(_tasks) {
user = _users[0]
task = _tasks[0]
done()
})
})
})
})
it('should correctly add an association to the dao', function() {
Helpers.async(function(done) {
user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(0)
user.addTask(task).on('success', function() {
user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(1)
done()
})
})
})
})
})
})
describe('removeDAO', function() {
var user = null
, tasks = null
beforeEach(function() {
Helpers.async(function(done) {
User.all().on('success', function(users) {
Task.all().on('success', function(_tasks) {
user = users[0]
tasks = _tasks
done()
})
})
})
})
it("should correctly remove associated objects", function() {
Helpers.async(function(done) {
user.getTasks().on('success', function(__tasks) {
expect(__tasks.length).toEqual(0)
user.setTasks(tasks).on('success', function() {
user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(tasks.length)
user.removeTask(tasks[0]).on('success', function() {
user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(tasks.length - 1)
done()
})
})
})
})
})
})
})
})
})
var config = require("../config/config")
, Sequelize = require("../../index")
, sequelize = new Sequelize(config.postgres.database, config.postgres.username, config.postgres.password, {
logging: false,
port: config.postgres.port,
dialect: 'postgres'
})
, Helpers = new (require("../config/helpers"))(sequelize)
describe('Associations', function() {
beforeEach(function() { Helpers.sync() })
afterEach(function() { Helpers.drop() })
/////////// many-to-many with same prefix ////////////
describe('many-to-many', function() {
describe('where tables have the same prefix', function() {
var Table2 = sequelize.define('wp_table2', {foo: Sequelize.STRING})
, Table1 = sequelize.define('wp_table1', {foo: Sequelize.STRING})
Table1.hasMany(Table2)
Table2.hasMany(Table1)
it("should create a table wp_table1wp_table2s", function() {
Helpers.async(function(done) {
expect(sequelize.daoFactoryManager.getDAO('wp_table1swp_table2s')).toBeDefined()
done()
})
})
})
describe('when join table name is specified', function() {
var Table2 = sequelize.define('ms_table1', {foo: Sequelize.STRING})
, Table1 = sequelize.define('ms_table2', {foo: Sequelize.STRING})
Table1.hasMany(Table2, {joinTableName: 'table1_to_table2'})
Table2.hasMany(Table1, {joinTableName: 'table1_to_table2'})
it("should not use a combined name", function() {
expect(sequelize.daoFactoryManager.getDAO('ms_table1sms_table2s')).toBeUndefined()
})
it("should use the specified name", function() {
expect(sequelize.daoFactoryManager.getDAO('table1_to_table2')).toBeDefined()
})
})
})
})
var config = require("./config/config")
, Sequelize = require("../index")
, QueryInterface = require("../lib/query-interface")
describe('Sequelize', function() {
var sequelize = null
, Helpers = null
var setup = function(options) {
options = options || {}
if (!options.hasOwnProperty('pool'))
options.pool = config.mysql.pool
if (!options.hasOwnProperty('logging'))
options.logging = false
if (!options.hasOwnProperty('host'))
options.host = config.mysql.host
if (!options.hasOwnProperty('port'))
options.port = config.mysql.port
sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, options)
Helpers = new (require("./config/helpers"))(sequelize)
return options
}
beforeEach(function() { setup() })
afterEach(function() { sequelize = null })
describe('constructor', function() {
it('should pass the global options correctly', function() {
setup({ logging: false, define: { underscored:true } })
var DAO = sequelize.define('dao', {name: Sequelize.STRING})
expect(DAO.options.underscored).toBeTruthy()
})
it('should correctly set the host and the port', function() {
var options = setup({ host: '127.0.0.1', port: 1234 })
expect(sequelize.config.host).toEqual(options.host)
expect(sequelize.config.port).toEqual(options.port)
})
})
describe('define', function() {
it("adds a new dao to the dao manager", function() {
expect(sequelize.daoFactoryManager.all.length).toEqual(0)
sequelize.define('foo', { title: Sequelize.STRING })
expect(sequelize.daoFactoryManager.all.length).toEqual(1)
})
it("overwrites global options", function() {
setup({ define: { collate: 'utf8_general_ci' } })
var DAO = sequelize.define('foo', {bar: Sequelize.STRING}, {collate: 'utf8_bin'})
expect(DAO.options.collate).toEqual('utf8_bin')
})
it("inherits global collate option", function() {
setup({ define: { collate: 'utf8_general_ci' } })
var DAO = sequelize.define('foo', {bar: Sequelize.STRING})
expect(DAO.options.collate).toEqual('utf8_general_ci')
})
it("inherits global classMethods and instanceMethods", function() {
setup({
define: {
classMethods : { globalClassMethod : function() {} },
instanceMethods : { globalInstanceMethod : function() {} }
}
})
var DAO = sequelize.define('foo', {bar: Sequelize.STRING}, {
classMethods : { localClassMethod : function() {} }
})
expect(typeof DAO.options.classMethods.globalClassMethod).toEqual('function')
expect(typeof DAO.options.classMethods.localClassMethod).toEqual('function')
expect(typeof DAO.options.instanceMethods.globalInstanceMethod).toEqual('function')
})
it("uses the passed tableName", function(done) {
var Photo = sequelize.define('Foto', { name: Sequelize.STRING }, { tableName: 'photos' })
Photo.sync({ force: true }).success(function() {
sequelize.getQueryInterface().showAllTables().success(function(tableNames) {
expect(tableNames).toContain('photos')
done()
})
})
})
})
describe('sync', function() {
it("synchronizes all daos", function() {
var Project = sequelize.define('project' + config.rand(), { title: Sequelize.STRING })
var Task = sequelize.define('task' + config.rand(), { title: Sequelize.STRING })
Helpers.async(function(done) {
sequelize.sync().success(function() {
Project.create({title: 'bla'}).success(function() {
Task.create({title: 'bla'}).success(done)
})
})
})
})
})
describe('import', function() {
it("imports a dao definition from a file", function() {
var Project = sequelize.import(__dirname + "/assets/project")
expect(Project).toBeDefined()
})
})
})
var config = require("../config/config")
, Sequelize = require("../../index")
, dbFile = __dirname + '/test.sqlite'
, storages = [':memory:', dbFile]
describe('DAOFactory', function() {
storages.forEach(function(storage) {
describe('with storage "' + storage + '"', function() {
var User = null
, sequelize = null
, Helpers = null
beforeEach(function() {
sequelize = new Sequelize(config.database, config.username, config.password, {
logging: false,
dialect: 'sqlite',
storage: storage
})
Helpers = new (require("../config/helpers"))(sequelize)
User = sequelize.define('User', {
age: Sequelize.INTEGER,
name: Sequelize.STRING,
bio: Sequelize.TEXT
})
Helpers.sync()
})
afterEach(function() {
Helpers.dropAllTables()
if(storage == dbFile) {
Helpers.async(function(done) {
require("fs").unlink(__dirname + '/test.sqlite', done)
})
}
})
describe('create', function() {
it('creates a table entry', function() {
Helpers.async(function(done) {
User
.create({ age: 21, name: 'John Wayne', bio: 'noot noot' })
.success(done)
.error(function(err) { console.log(err) })
})
Helpers.async(function(done) {
User.all().success(function(users) {
var usernames = users.map(function(user) {
return user.name
})
expect(usernames).toEqual(['John Wayne'])
done()
}).error(function(err){ console.log(err) })
})
})
it('should allow the creation of an object with options as attribute', function() {
var Person = sequelize.define('Person', {
name: Sequelize.STRING,
options: Sequelize.TEXT
})
Helpers.async(function(done) {
Person.sync({force: true}).success(done)
})
Helpers.async(function(done) {
var options = JSON.stringify({ foo: 'bar', bar: 'foo' })
Helpers.Factories.DAO('Person', {
name: 'John Doe',
options: options
}, function(people) {
expect(people[0].options).toEqual(options)
done()
})
})
})
it('should allow the creation of an object with a boolean (true) as attribute', function() {
var Person = sequelize.define('Person', {
name: Sequelize.STRING,
has_swag: Sequelize.BOOLEAN
})
Helpers.async(function(done) {
Person.sync({force: true}).success(done)
})
Helpers.async(function(done) {
Helpers.Factories.DAO('Person', {
name: 'John Doe',
has_swag: true
}, function(people) {
expect(people[0].has_swag).toBeTruthy();
done()
})
})
})
it('should allow the creation of an object with a boolean (false) as attribute', function() {
var Person = sequelize.define('Person', {
name: Sequelize.STRING,
has_swag: Sequelize.BOOLEAN
})
Helpers.async(function(done) {
Person.sync({force: true}).success(done)
})
Helpers.async(function(done) {
Helpers.Factories.DAO('Person', {
name: 'John Doe',
has_swag: false
}, function(people) {
expect(people[0].has_swag).toBeFalsy();
done()
})
})
})
})
////////// find //////////////
describe('.find', function() {
beforeEach(function() {
Helpers.Factories.User({name: 'user', bio: 'foobar'}, null, 2)
})
it("finds normal lookups", function() {
Helpers.async(function(done) {
User.find({ where: { name:'user' } }).success(function(user) {
expect(user.name).toEqual('user')
done()
})
})
})
it("should make aliased attributes available", function() {
Helpers.async(function(done) {
User.find({ where: { name:'user' }, attributes: ['id', ['name', 'username']] }).success(function(user) {
expect(user.username).toEqual('user')
done()
})
})
})
})
////////// all //////////////
describe('.all', function() {
beforeEach(function() {
Helpers.Factories.User({name: 'user', bio: 'foobar'}, null, 2)
})
it("should return all users", function() {
Helpers.async(function(done) {
User.all().on('success', function(users) {
done()
expect(users.length).toEqual(2)
}).on('error', function(err) { console.log(err) })
})
})
})
////////// min //////////////
describe('.min', function() {
it("should return the min value", function() {
for(var i = 2; i < 5; i++) Helpers.Factories.User({ age: i })
Helpers.async(function(done) {
User.min('age').on('success', function(min) {
expect(min).toEqual(2); done()
})
})
})
})
////////// max //////////////
describe('.max', function() {
it("should return the max value", function() {
for(var i = 2; i <= 5; i++) Helpers.Factories.User({ age: i })
Helpers.async(function(done) {
User.max('age').on('success', function(min) {
expect(min).toEqual(5); done()
})
})
})
})
})
})
})
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!