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

Commit 8404668d by Jan Aagaard Meier

The mother of all merges

2 parents 655a603d 2203db2f
Showing with 1962 additions and 1594 deletions
......@@ -4,4 +4,6 @@ test*.js
.DS_STORE
node_modules
npm-debug.log
*~
\ No newline at end of file
*~
test/binary/tmp/*
test/tmp/*
{
"globals": {
"jasmine": false,
"spyOn": false,
"it": false,
"console": false,
......@@ -18,5 +17,6 @@
"unused": true,
"asi": true,
"evil": false,
"laxcomma": true
"laxcomma": true,
"es5": true
}
\ No newline at end of file
......@@ -3,12 +3,15 @@ before_script:
- "psql -c 'create database sequelize_test;' -U postgres"
script:
- "npm run test-buster-travis"
- "npm run test-jasmine"
- "make test"
notifications:
email:
- sascha@depold.com
hipchat:
- 40e8850aaba9854ac4c9963bd33f8b@253477
irc:
- "chat.freenode.net#sequelizejs"
env:
- DB=mysql DIALECT=mysql
......@@ -20,4 +23,10 @@ env:
language: 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.
<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>
MySQL, PostgresSQL, and SQLite Object Relational Mapper (ORM) for [node](http://nodejs.org).
## 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 ###
- 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
- Asynchronous library
- Associations
- 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).
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
- [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)
## 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
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
### 1.7.0
- ~~Check if lodash is a proper alternative to current underscore usage.~~
- Transactions
- Associations of not yet saved objects: [#864](https://github.com/sequelize/sequelize/issues/864)
- Support for update of tables without primary key
- 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
- Eager loading of nested associations [#388](https://github.com/sdepold/sequelize/issues/388#issuecomment-12019099)
- Model#delete
- Eager loading of nested associations [#388](https://github.com/sequelize/sequelize/issues/388)
- ~~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
- 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
### 1.7.x
......@@ -70,8 +81,12 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl
### 2.0.0
- ~~save datetimes in UTC~~
- encapsulate attributes if a dao inside the attributes property + add getters and setters
- encapsulate attributes if a dao inside the attributes property
- ~~add getters and setters for dao~~ Implemented in [#538](https://github.com/sequelize/sequelize/pull/538), thanks to iamjochem
- add proper error message everywhere
- refactor validate() output data structure, separating field-specific errors
from general model validator errors (i.e.
`{fields: {field1: ['field1error1']}, model: ['modelError1']}` or similar)
## Collaboration 2.0 ##
......@@ -126,32 +141,27 @@ $ npm install
### 4. Run the tests ###
Right now, the test base is split into the `spec` folder (which contains the
lovely [BusterJS](http://busterjs.org) tests) and the `spec-jasmine` folder
(which contains the ugly and awkward node-jasmine based tests). A main goal
is to get rid of the jasmine tests!
Right now, the test base is split into the `test` folder (which contains the
lovely [Mocha](http://visionmedia.github.io/mocha/) tests).
As you might haven't installed all of the supported SQL dialects, here is how
to run the test suites for your development environment:
```console
$ # run all tests at once:
$ npm test
$ # run only the jasmine tests (for all dialects):
$ npm run test-jasmine
$ # run all of the buster specs (for all dialects):
$ npm run test-buster
$ make all
$ # run the buster specs for mysql:
$ npm run test-buster-mysql
$ make mysql
$ # run the buster specs for sqlite:
$ npm run test-buster-sqlite
$ make sqlite
$ # 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 ###
......@@ -220,6 +230,17 @@ for (var key in obj) {
```js
{
"globals": {
"spyOn": false,
"it": false,
"console": false,
"describe": false,
"expect": false,
"beforeEach": false,
"waits": false,
"waitsFor": false,
"runs": false
},
"camelcase": true,
"curly": true,
"forin": true,
......@@ -227,13 +248,7 @@ for (var key in obj) {
"unused": true,
"asi": true,
"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
const path = require("path")
, fs = require("fs")
, program = require("commander")
, Sequelize = require(__dirname + '/../index')
, moment = require("moment")
, _ = Sequelize.Utils._
var configPath = process.cwd() + '/config'
, environment = process.env.NODE_ENV || 'development'
, migrationsPath = process.cwd() + '/migrations'
, packageJsonPath = __dirname + '/../package.json'
, packageJson = JSON.parse(fs.readFileSync(packageJsonPath).toString())
, configFile = configPath + '/config.json'
, configPathExists = fs.existsSync(configPath)
, configFileExists = fs.existsSync(configFile)
var writeConfig = function(config) {
!configPathExists && fs.mkdirSync(configPath)
config = JSON.stringify(config)
config = config.replace('{', '{\n ')
config = config.replace(/,/g, ",\n ")
config = config.replace('}', "\n}")
fs.writeFileSync(configFile, config)
var path = require("path")
, fs = require("fs")
, program = require("commander")
, Sequelize = require(__dirname + '/../index')
, moment = require("moment")
, _ = Sequelize.Utils._
var configuration = {
configFile: process.cwd() + '/config/config.json',
environment: process.env.NODE_ENV || 'development',
version: require(__dirname + '/../package.json').version,
migrationsPath: process.cwd() + '/migrations'
}
var configFileExists = function() {
return fs.existsSync(configuration.configFile)
}
var relativeConfigFile = function() {
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({
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'
}
}, undefined, 2) + "\n"
fs.writeFileSync(configuration.configFile, config)
}
var createMigrationsFolder = function(force) {
if(force) {
console.log('Deleting the migrations folder.')
console.log('Deleting the migrations folder. (--force)')
try {
fs.readdirSync(migrationsPath).forEach(function(filename) {
fs.unlinkSync(migrationsPath + '/' + filename)
fs.readdirSync(configuration.migrationsPath).forEach(function(filename) {
fs.unlinkSync(configuration.migrationsPath + '/' + filename)
})
} catch(e) {}
try {
fs.rmdirSync(migrationsPath)
fs.rmdirSync(configuration.migrationsPath)
console.log('Successfully deleted the migrations folder.')
} catch(e) {}
}
console.log('Creating migrations folder.')
try {
fs.mkdirSync(migrationsPath)
console.log('Successfully create migrations folder.')
fs.mkdirSync(configuration.migrationsPath)
console.log('Successfully created migrations folder at "' + configuration.migrationsPath + '".')
} catch(e) {
console.log('Migrations folder already exist.')
}
}
var readConfig = function() {
var config
try {
config = fs.readFileSync(configFile)
config = require(configuration.configFile);
} catch(e) {
throw new Error('Error reading "config/config.json".')
throw new Error('Error reading "' + relativeConfigFile() + '".')
}
try {
config = JSON.parse(config)
} catch (e) {
throw new Error('Error parsing "config/config.json" as JSON.')
if(typeof config != 'object') {
throw new Error('Config must be an object: ' + relativeConfigFile());
}
if (config[environment]) {
config = config[environment]
console.log('Loaded configuration file "' + relativeConfigFile() + '".')
if (config[configuration.environment]) {
console.log('Using environment "' + configuration.environment + '".')
config = config[configuration.environment]
}
return config
}
program
.version(packageJson.version)
.version(configuration.version)
.option('-i, --init', 'Initializes the project.')
.option('-e, --env <environment>', 'Specify the environment.')
.option('-m, --migrate', 'Run pending migrations.')
.option('-u, --undo', 'Undo the last migration.')
.option('-f, --force', 'Forces the action to be done.')
.option('-c, --create-migration [migration-name]', 'Creates a new migration.')
.option('--config <config_file>', 'Specifies alternate config file.')
.parse(process.argv)
if(typeof program.config === 'string') {
configuration.configFile = program.config
}
if(typeof program.env === 'string') {
environment = program.env
configuration.environment = program.env
}
console.log("Using environment '" + environment + "'.")
if(program.migrate) {
if(configFileExists) {
if (program.migrate) {
if (configFileExists()) {
var config
, options = {}
......@@ -101,57 +129,58 @@ if(program.migrate) {
if(['database', 'username', 'password'].indexOf(key) == -1) {
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 })
var sequelize = new Sequelize(config.database, config.username, config.password, options)
, migratorOptions = { path: migrationsPath }
, migratorOptions = { path: configuration.migrationsPath }
, migrator = sequelize.getMigrator(migratorOptions)
if(program.undo) {
if (program.undo) {
sequelize.migrator.findOrCreateSequelizeMetaDAO().success(function(Meta) {
Meta.find({ order: 'id DESC' }).success(function(meta) {
if(meta) {
if (meta) {
migrator = sequelize.getMigrator(_.extend(migratorOptions, meta), true)
}
migrator.migrate({ method: 'down' })
migrator.migrate({ method: 'down' }).success(function() {
process.exit(0)
})
})
})
} else {
sequelize.migrate()
sequelize.migrate().success(function() {
process.exit(0)
})
}
} 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)
}
} else if(program.init) {
if(!configFileExists || !!program.force) {
writeConfig({
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'
}
})
if(!configFileExists() || !!program.force) {
writeDefaultConfig()
console.log('Created "config/config.json"')
console.log('Created "' + relativeConfigFile() + '"')
} 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)
}
......@@ -175,9 +204,11 @@ if(program.migrate) {
" 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 {
console.log('Try "sequelize --help" for usage information.')
console.log('No action specified. Try "sequelize --help" for usage information.')
}
......@@ -5,11 +5,11 @@
First of all, Person is getting associated via many-to-many with other Person objects (e.g. Person.hasMany('brothers')).
Afterwards a Person becomes associated with a 'father' and a mother using a one-to-one association created by hasOneAndBelongsTo.
The last association has the type many-to-one and is defined by the function hasManyAndBelongsTo.
The rest of the example is about setting and getting the associated data.
The rest of the example is about setting and getting the associated data.
*/
var Sequelize = require(__dirname + "/../../index")
, config = require(__dirname + "/../../test/config")
, config = require(__dirname + "/../../spec/config/config")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false})
, Person = sequelize.define('Person', { name: Sequelize.STRING })
, Pet = sequelize.define('Pet', { name: Sequelize.STRING })
......@@ -36,13 +36,13 @@ sequelize.sync({force:true}).on('success', function() {
.add(brother.save())
.add(sister.save())
.add(pet.save())
chainer.run().on('success', function() {
person.setMother(mother).on('success', function() { person.getMother().on('success', function(mom) {
console.log('my mom: ', mom.name)
console.log('my mom: ', mom.name)
})})
person.setFather(father).on('success', function() { person.getFather().on('success', function(dad) {
console.log('my dad: ', dad.name)
console.log('my dad: ', dad.name)
})})
person.setBrothers([brother]).on('success', function() { person.getBrothers().on('success', function(brothers) {
console.log("my brothers: " + brothers.map(function(b) { return b.name }))
......
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})
var Person = sequelize.define('Person', { name: Sequelize.STRING })
......@@ -8,12 +8,12 @@ var Person = sequelize.define('Person', { name: Sequelize.STRING })
sequelize.sync({force: true}).on('success', function() {
var count = 10,
queries = []
for(var i = 0; i < count; i++)
chainer.add(Person.create({name: 'someone' + (i % 3)}))
console.log("Begin to save " + count + " items!")
chainer.run().on('success', function() {
console.log("finished")
Person.count().on('success', function(count) {
......
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})
var Person = sequelize.define('Person',
var Person = sequelize.define('Person',
{ name: Sequelize.STRING,
age : Sequelize.INTEGER
......@@ -12,12 +12,12 @@ var Person = sequelize.define('Person',
sequelize.sync({force: true}).on('success', function() {
var count = 10,
queries = []
for(var i = 0; i < count; i++)
chainer.add(Person.create({name: 'someone' + (i % 3), age : i+5}))
console.log("Begin to save " + count + " items!")
chainer.run().on('success', function() {
console.log("finished")
Person.max('age').on('success', function(max) {
......
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})
, QueryChainer = Sequelize.Utils.QueryChainer
, sys = require("sys")
......@@ -10,16 +10,16 @@ Person.sync({force: true}).on('success', function() {
var start = Date.now()
, count = 10000
, done = 0
var createPerson = function() {
Person.create({name: 'someone'}).on('success', function() {
if(++done == count) {
var duration = (Date.now() - start)
console.log("\nFinished creation of " + count + " people. Took: " + duration + "ms (avg: " + (duration/count) + "ms)")
start = Date.now()
console.log("Will now read them from the database:")
Person.findAll().on('success', function(people) {
console.log("Reading " + people.length + " items took: " + (Date.now() - start) + "ms")
})
......@@ -35,7 +35,7 @@ Person.sync({force: true}).on('success', function() {
for(var i = 0; i < count; i++) {
createPerson()
}
}).on('failure', function(err) {
console.log(err)
})
\ No newline at end of file
/*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")
, sequelize = new Sequelize('sequelize_test', 'root', null, {logging: false})
, Image = sequelize.define('Image', { data: Sequelize.TEXT })
/*
Title: Default values
Image.sync({force: true}).on('success', function() {
console.log("reading image")
var image = fs.readFileSync(__dirname + '/source.png').toString("base64")
console.log("done\n")
console.log("creating database entry")
Image.create({data: image}).on('success', function(img) {
console.log("done\n")
console.log("writing file")
fs.writeFileSync(__dirname + '/target.png', img.data, "base64")
console.log("done\n")
console.log("you might open the file ./target.png")
This example demonstrates the use of default values for defined model fields. Instead of just specifying the datatype,
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 Sequelize = require(__dirname + "/../../index")
, config = require(__dirname + "/../../spec/config/config")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false})
var User = sequelize.define('User', {
name: { type: Sequelize.STRING, allowNull: false},
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)
user.updateAttributes({ isAdmin: true }).on('success', function(user) {
console.log("user.isAdmin was overwritten to true: " + user.isAdmin)
})
})
}).on('failure', function(err) {
console.log(err)
})
\ No newline at end of file
/*
Title: Defining class and instance methods
This example shows the usage of the classMethods and instanceMethods option for Models.
*/
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})
// model definition
// model definition
var Task = sequelize.define("Task", {
name: Sequelize.STRING,
deadline: Sequelize.DATE,
......@@ -51,7 +51,7 @@ Task.sync({force: true}).on('success', function() {
console.log("should be false: " + task1.passedDeadline())
console.log("should be true: " + task2.passedDeadline())
console.log("should be 10: " + task1.importance)
Task.setImportance(30, function() {
Task.findAll().on('success', function(tasks) {
tasks.forEach(function(task) {
......
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, {
// use other database server or port
host: 'my.srv.tld',
port: 12345,
// disable logging
logging: false
})
......
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})
, Project = sequelize.import(__dirname + "/Project")
, Task = sequelize.import(__dirname + "/Task")
Project.hasMany(Task)
Task.belongsTo(Project)
sequelize.sync({force: true}).on('success', function() {
Project
.create({ name: 'Sequelize', description: 'A nice MySQL ORM for NodeJS' })
......
......@@ -11,9 +11,11 @@ module.exports = (function() {
this.isSelfAssociation = (this.source.tableName == this.target.tableName)
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
? Utils.combineTableNames(this.target.tableName, this.options.as || this.target.tableName)
: this.options.as || this.target.tableName
......@@ -22,28 +24,37 @@ module.exports = (function() {
// the id is in the source table
BelongsTo.prototype.injectAttributes = function() {
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)
newAttributes[this.identifier] = { type: DataTypes.INTEGER }
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: this.options.keyType || keyType }
Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.target, this.source, this.options)
Utils._.defaults(this.source.rawAttributes, newAttributes)
// 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
}
BelongsTo.prototype.injectGetter = function(obj) {
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) {
var id = this[self.identifier]
, where = {}
where[primaryKey] = id
if (!Utils._.isUndefined(params)) {
if (!Utils._.isUndefined(params.attributes)) {
params = Utils._.extend({where: {id:id}}, params)
if (!Utils._.isUndefined(params.where)) {
params.where = Utils._.extend(where, params.where)
} else {
params.where = where
}
} else {
params = id
......@@ -57,13 +68,16 @@ module.exports = (function() {
BelongsTo.prototype.injectSetter = function(obj) {
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) {
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.
return this.save([ self.identifier ])
return this.save([ self.identifier ], {allowNull: [self.identifier]})
}
return this
......
......@@ -10,23 +10,36 @@ module.exports = (function() {
var self = this, _options = options
var customEventEmitter = new Utils.CustomEventEmitter(function() {
var where = {}, options = _options || {};
var where = {}, options = _options || {}
//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)
, 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) {
Utils._.each(options.where, function(value, index) {
delete options.where[index];
options.where[self.__factory.target.tableName+"."+index] = value;
});
where[self.__factory.connectorDAO.tableName+"."+foreignKey] = {join: self.__factory.target.tableName+"."+foreignPrimary}
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 {
options.where = where;
}
......@@ -42,35 +55,55 @@ module.exports = (function() {
HasManyDoubleLinked.prototype.injectSetter = function(emitterProxy, oldAssociations, newAssociations) {
var self = this
, chainer = new Utils.QueryChainer()
, association = self.__factory.target.associations[self.__factory.associationAccessor]
, foreignIdentifier = association.isSelfAssociation ? association.foreignIdentifier : association.identifier
, 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)
})
})
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]
, foreignIdentifier = association.isSelfAssociation ? association.foreignIdentifier : association.identifier
, unassociatedObjects = newAssociations.filter(function (obj) {
return !Utils._.find(oldAssociations, function (old) {
return obj.id === old.id
})
})
unassociatedObjects.forEach(function(unassociatedObject) {
var attributes = {}
attributes[self.__factory.identifier] = self.instance.id
attributes[foreignIdentifier] = unassociatedObject.id
chainer.add(self.__factory.connectorDAO.create(attributes))
})
chainer
.run()
.success(function() { emitterProxy.emit('success', newAssociations) })
.error(function(err) { emitterProxy.emit('error', err) })
.on('sql', function(sql) { emitterProxy.emit('sql', sql) })
var unassociatedObjects = newAssociations.filter(function (obj) {
// Return only those associations that are new
return !Utils._.find(oldAssociations, function (old) {
return ((targetKeys.length === 1) ? obj[targetKeys[0]] === old[targetKeys[0]] : obj.id === old.id)
})
})
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 = {}
attributes[self.__factory.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id)
attributes[foreignIdentifier] = ((targetKeys.length === 1) ? unassociatedObject[targetKeys[0]] : unassociatedObject.id)
return attributes
})
chainer.add(self.__factory.connectorDAO.bulkCreate(bulk))
}
chainer
.run()
.success(function() { emitterProxy.emit('success', newAssociations) })
.error(function(err) { emitterProxy.emit('error', err) })
.on('sql', function(sql) { emitterProxy.emit('sql', sql) })
}
HasManyDoubleLinked.prototype.injectAdder = function(emitterProxy, newAssociation) {
......@@ -78,8 +111,11 @@ module.exports = (function() {
, association = this.__factory.target.associations[this.__factory.associationAccessor]
, foreignIdentifier = association.isSelfAssociation ? association.foreignIdentifier : association.identifier;
attributes[this.__factory.identifier] = this.instance.id
attributes[foreignIdentifier] = newAssociation.id
var sourceKeys = Object.keys(this.__factory.source.primaryKeys);
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)
.success(function() { emitterProxy.emit('success', newAssociation) })
......@@ -87,57 +123,5 @@ module.exports = (function() {
.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
})()
......@@ -7,49 +7,94 @@ module.exports = (function() {
}
HasManySingleLinked.prototype.injectGetter = function(options) {
var where = {}, options = options || {}
var self = this
, where = {}
options = options || {}
where[this.__factory.identifier] = this.instance.id
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]
options.where = options.where ? Utils._.extend(options.where, where) : where
return this.__factory.target.findAll(options)
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
}
return this.__factory.target.all(options)
}
HasManySingleLinked.prototype.injectSetter = function(emitter, oldAssociations, newAssociations) {
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()
, obsoleteAssociations = oldAssociations.filter(function (old) {
, obsoleteAssociations = oldAssociations.filter(function (old) {
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 obj.id === old.id
})
return obj[associationKey] === old[associationKey]
})
})
, update
if (obsoleteAssociations.length > 0) {
// clear the old associations
var obsoleteIds = obsoleteAssociations.map(function(associatedObject) {
associatedObject[self.__factory.identifier] = (newAssociations.length < 1 ? null : self.instance.id)
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 = {}
// clear the old associations
obsoleteAssociations.forEach(function(associatedObject) {
associatedObject[self.__factory.identifier] = null
chainer.add(associatedObject.save())
})
updateWhere[primaryKey] = obsoleteIds
chainer.add(this.__factory.target.update(update, updateWhere, {allowNull: [self.__factory.identifier]}))
}
// set the new associations
unassociatedObjects.forEach(function(associatedObject) {
associatedObject[self.__factory.identifier] = self.instance.id
chainer.add(associatedObject.save())
})
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
var unassociatedIds = unassociatedObjects.map(function(associatedObject) {
associatedObject[self.__factory.identifier] = self.instance[pkey] || self.instance.id
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
.run()
.success(function() { emitter.emit('success', newAssociations) })
.error(function(err) { emitter.emit('error', err) })
.on('sql', function(sql) { emitter.emit('sql', sql) })
}
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()
.success(function() { emitterProxy.emit('success', newAssociation) })
......
......@@ -18,16 +18,19 @@ module.exports = (function() {
this.source.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 = {
get: Utils._.camelize('get_' + as),
set: Utils._.camelize('set_' + as),
add: Utils._.camelize(Utils.singularize('add_' + as)),
remove: Utils._.camelize(Utils.singularize('remove_' + as)),
hasSingle: Utils._.camelize(Utils.singularize('has_' + as)),
add: Utils._.camelize(Utils.singularize('add_' + as, this.target.options.language)),
remove: Utils._.camelize(Utils.singularize('remove_' + as, this.target.options.language)),
hasSingle: Utils._.camelize(Utils.singularize('has_' + as, this.target.options.language)),
hasAll: Utils._.camelize('has_' + as)
}
}
......@@ -36,7 +39,7 @@ module.exports = (function() {
// or in an extra table which connects two tables
HasMany.prototype.injectAttributes = function() {
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?
// or is the association on the model itself?
......@@ -46,13 +49,19 @@ module.exports = (function() {
this.foreignIdentifier = Utils._.underscoredIf((this.options.as || this.target.tableName) + 'Id', this.options.underscored)
} else {
this.foreignIdentifier = this.target.associations[this.associationAccessor].identifier
this.target.associations[this.associationAccessor].foreignIdentifier = this.identifier
delete this.source.rawAttributes[this.foreignIdentifier]
delete this.target.associations[this.associationAccessor].source.rawAttributes[this.identifier]
}
// define a new model, which connects the models
var combinedTableAttributes = {}
combinedTableAttributes[this.identifier] = {type:DataTypes.INTEGER, primaryKey: true}
combinedTableAttributes[this.foreignIdentifier] = {type:DataTypes.INTEGER, primaryKey: true}
var sourceKeys = Object.keys(this.source.primaryKeys);
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)
......@@ -65,7 +74,7 @@ module.exports = (function() {
}
} else {
var newAttributes = {}
newAttributes[this.identifier] = { type: DataTypes.INTEGER }
newAttributes[this.identifier] = { type: this.options.keyType || DataTypes.INTEGER }
Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.source, this.target, this.options)
Utils._.defaults(this.target.rawAttributes, newAttributes)
}
......@@ -86,7 +95,7 @@ module.exports = (function() {
}
obj[this.accessors.hasAll] = function(objects) {
var instance = this;
var instance = this;
var customEventEmitter = new Utils.CustomEventEmitter(function() {
instance[self.accessors.get]()
.error(function(err){ customEventEmitter.emit('error', err)})
......@@ -106,7 +115,7 @@ module.exports = (function() {
}
obj[this.accessors.hasSingle] = function(o) {
var instance = this;
var instance = this;
var customEventEmitter = new Utils.CustomEventEmitter(function() {
instance[self.accessors.get]()
.error(function(err){ customEventEmitter.emit('error', err)})
......@@ -153,8 +162,13 @@ module.exports = (function() {
obj[this.accessors.add] = function(newAssociatedObject) {
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) {
instance[self.accessors.get]({ where: { id: newAssociatedObject.id }})
instance[self.accessors.get]({ where: where })
.error(function(err){ emitter.emit('error', err)})
.success(function(currentAssociatedObjects) {
if (currentAssociatedObjects.length === 0) {
......@@ -172,15 +186,46 @@ module.exports = (function() {
var customEventEmitter = new Utils.CustomEventEmitter(function() {
instance[self.accessors.get]().success(function(currentAssociatedObjects) {
var newAssociations = []
, oldAssociations = []
currentAssociatedObjects.forEach(function(association) {
if (!Utils._.isEqual(oldAssociatedObject.identifiers, association.identifiers))
newAssociations.push(association)
if (!Utils._.isEqual(oldAssociatedObject.identifiers, association.identifiers)) {
newAssociations[newAssociations.length] = association
} else {
oldAssociations[oldAssociations.length] = association
}
})
instance[self.accessors.set](newAssociations)
.success(function() { customEventEmitter.emit('success', null) })
.error(function(err) { customEventEmitter.emit('error', err) })
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)
.success(function() { customEventEmitter.emit('success', null) })
.error(function(err) { customEventEmitter.emit('error', err) })
}
if (oldAssociations.length > 0) {
next(null, tick)
} else {
run()
}
})
})
return customEventEmitter.run()
......
......@@ -11,25 +11,29 @@ module.exports = (function() {
this.isSelfAssociation = (this.source.tableName == this.target.tableName)
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
? Utils.combineTableNames(this.target.tableName, this.options.as || this.target.tableName)
: this.options.as || this.target.tableName
this.options.useHooks = options.useHooks
this.accessors = {
get: Utils._.camelize('get_' + (this.options.as || Utils.singularize(this.target.tableName))),
set: Utils._.camelize('set_' + (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, this.target.options.language)))
}
}
// the id is in the target table
HasOne.prototype.injectAttributes = function() {
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)
newAttributes[this.identifier] = { type: DataTypes.INTEGER }
this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.source.tableName, this.source.options.language) + "Id", this.options.underscored)
newAttributes[this.identifier] = { type: this.options.keyType || keyType }
Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.source, this.target, this.options)
Utils._.defaults(this.target.rawAttributes, newAttributes)
......@@ -43,8 +47,10 @@ module.exports = (function() {
var self = this
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 = {}
, id = this[primaryKey] || this.id
where[self.identifier] = id
......@@ -56,6 +62,12 @@ module.exports = (function() {
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)
}
......@@ -63,28 +75,39 @@ module.exports = (function() {
}
HasOne.prototype.injectSetter = function(obj) {
var self = this
, options = self.options || {}
var self = this
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) {
instance[self.accessors.get]().success(function(oldObj) {
if (oldObj) {
oldObj[self.identifier] = null
oldObj.save()
}
if (associatedObject) {
associatedObject[self.identifier] = instance.id
associatedObject
.save()
.success(function() { emitter.emit('success', associatedObject) })
.error(function(err) { emitter.emit('error', err) })
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 {
emitter.emit('success', null)
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)
}
}
})
}).run()
}
......
......@@ -7,6 +7,11 @@ var Utils = require("./../utils")
var Mixin = module.exports = function(){}
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
var association = new HasOne(this, associatedDAO, Utils._.extend((options||{}), this.options))
this.associations[association.associationAccessor] = association.injectAttributes()
......@@ -18,8 +23,13 @@ Mixin.hasOne = 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
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()
association.injectGetter(this.DAO.prototype)
......@@ -29,6 +39,11 @@ Mixin.belongsTo = 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
var association = new HasMany(this, associatedDAO, Utils._.extend((options||{}), this.options))
this.associations[association.associationAccessor] = association.injectAttributes()
......
var Toposort = require('toposort-class')
var Toposort = require('toposort-class')
, DaoFactory = require('./dao-factory')
module.exports = (function() {
var DAOFactoryManager = function(sequelize) {
......@@ -39,16 +40,17 @@ module.exports = (function() {
* before dependents.
*/
DAOFactoryManager.prototype.forEachDAO = function(iterator) {
var daos = {}
var daos = {}
, sorter = new Toposort()
this.daos.forEach(function(dao) {
daos[dao.tableName] = dao
var deps = []
for(var attrName in dao.rawAttributes) {
if(dao.rawAttributes.hasOwnProperty(attrName)) {
if(dao.rawAttributes[attrName].references) {
daos[dao.tableName] = dao
for (var attrName in dao.rawAttributes) {
if (dao.rawAttributes.hasOwnProperty(attrName)) {
if (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) {
if (this instanceof STRING) {
this._binary = !!binary;
this._binary = !!binary
if (typeof length === 'number') {
this._length = length;
this._length = length
} else {
this._length = 255;
this._length = 255
}
} else {
return new STRING(length, binary);
return new STRING(length, binary)
}
};
}
STRING.prototype = {
get BINARY() {
this._binary = true;
return this;
this._binary = true
return this
},
get type() {
return this.toString();
return this.toString()
},
toString: function() {
return 'VARCHAR(' + this._length + ')' + ((this._binary) ? ' BINARY' : '');
return 'VARCHAR(' + this._length + ')' + ((this._binary) ? ' BINARY' : '')
}
};
}
Object.defineProperty(STRING, 'BINARY', {
get: function() {
return new STRING(undefined, true);
return new STRING(undefined, true)
}
});
})
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() {
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() {
return FLOAT.prototype.construct.apply(this, [FLOAT].concat(Array.prototype.slice.apply(arguments)));
};
FLOAT._type = FLOAT;
FLOAT._typeName = 'FLOAT';
INTEGER._type = INTEGER;
INTEGER._typeName = 'INTEGER';
BIGINT._type = BIGINT;
BIGINT._typeName = 'BIGINT';
STRING._type = STRING;
STRING._typeName = 'VARCHAR';
STRING.toString = INTEGER.toString = FLOAT.toString = BIGINT.toString = function() {
return new this._type().toString();
};
return FLOAT.prototype.construct.apply(this, [FLOAT].concat(Array.prototype.slice.apply(arguments)))
}
var BLOB = function() {
return BLOB.prototype.construct.apply(this, [BLOB].concat(Array.prototype.slice.apply(arguments)))
}
FLOAT._type = FLOAT
FLOAT._typeName = 'FLOAT'
INTEGER._type = INTEGER
INTEGER._typeName = 'INTEGER'
BIGINT._type = BIGINT
BIGINT._typeName = 'BIGINT'
STRING._type = STRING
STRING._typeName = 'VARCHAR'
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 = {
construct: function(RealType, length, decimals, unsigned, zerofill) {
if (this instanceof RealType) {
this._typeName = RealType._typeName;
this._unsigned = !!unsigned;
this._zerofill = !!zerofill;
this._typeName = RealType._typeName
this._unsigned = !!unsigned
this._zerofill = !!zerofill
if (typeof length === 'number') {
this._length = length;
this._length = length
}
if (typeof decimals === 'number') {
this._decimals = decimals;
this._decimals = decimals
}
} else {
return new RealType(length, decimals, unsigned, zerofill);
return new RealType(length, decimals, unsigned, zerofill)
}
},
get type() {
return this.toString();
return this.toString()
},
get UNSIGNED() {
this._unsigned = true;
return this;
this._unsigned = true
return this
},
get ZEROFILL() {
this._zerofill = true;
return this;
this._zerofill = true
return this
},
toString: function() {
var result = this._typeName;
var result = this._typeName
if (this._length) {
result += '(' + this._length;
result += '(' + this._length
if (typeof this._decimals === 'number') {
result += ',' + this._decimals;
result += ',' + this._decimals
}
result += ')';
result += ')'
}
if (this._unsigned) {
result += ' UNSIGNED';
result += ' UNSIGNED'
}
if (this._zerofill) {
result += ' ZEROFILL';
result += ' ZEROFILL'
}
return result;
return result
}
};
}
var unsignedDesc = {
get: function() {
return new this._type(undefined, undefined, true);
return new this._type(undefined, undefined, true)
}
};
}
var zerofillDesc = {
get: function() {
return new this._type(undefined, undefined, undefined, true);
return new this._type(undefined, undefined, undefined, true)
}
};
}
var typeDesc = {
get: function() {
return new this._type().toString();
return new this._type().toString()
}
};
}
Object.defineProperty(STRING, 'type', typeDesc);
Object.defineProperty(INTEGER, 'type', typeDesc);
Object.defineProperty(BIGINT, 'type', typeDesc);
Object.defineProperty(FLOAT, 'type', typeDesc);
Object.defineProperty(STRING, 'type', typeDesc)
Object.defineProperty(INTEGER, 'type', typeDesc)
Object.defineProperty(BIGINT, 'type', typeDesc)
Object.defineProperty(FLOAT, 'type', typeDesc)
Object.defineProperty(BLOB, 'type', typeDesc)
Object.defineProperty(INTEGER, 'UNSIGNED', unsignedDesc);
Object.defineProperty(BIGINT, 'UNSIGNED', unsignedDesc);
Object.defineProperty(FLOAT, 'UNSIGNED', unsignedDesc);
Object.defineProperty(INTEGER, 'UNSIGNED', unsignedDesc)
Object.defineProperty(BIGINT, 'UNSIGNED', unsignedDesc)
Object.defineProperty(FLOAT, 'UNSIGNED', unsignedDesc)
Object.defineProperty(INTEGER, 'ZEROFILL', zerofillDesc);
Object.defineProperty(BIGINT, 'ZEROFILL', zerofillDesc);
Object.defineProperty(FLOAT, 'ZEROFILL', zerofillDesc);
Object.defineProperty(INTEGER, 'ZEROFILL', zerofillDesc)
Object.defineProperty(BIGINT, 'ZEROFILL', zerofillDesc)
Object.defineProperty(FLOAT, 'ZEROFILL', zerofillDesc)
module.exports = {
STRING: STRING,
......@@ -147,6 +187,7 @@ module.exports = {
BOOLEAN: 'TINYINT(1)',
FLOAT: FLOAT,
NOW: 'NOW',
BLOB: BLOB,
get ENUM() {
var result = function() {
......@@ -183,7 +224,7 @@ module.exports = {
}
result.type = 'HSTORE'
result.toString = result.valueOf = function() { return 'TEXT' }
result.toString = result.valueOf = function() { return 'HSTORE' }
return result
}
......
var Utils = require("../../utils")
, SqlString = require("../../sql-string")
module.exports = (function() {
var QueryGenerator = {
addSchema: function(opts) {
......@@ -150,6 +153,11 @@ module.exports = (function() {
If you use a string, you have to escape it on your own.
Options:
- 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) {
throwMethodUndefined('deleteQuery')
......@@ -264,6 +272,102 @@ module.exports = (function() {
*/
disableForeignKeyConstraintsQuery: function() {
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() {
result = transformRowsWithEagerLoadingIntoDaos.call(this, results)
} else {
result = results.map(function(result) {
return this.callee.build(result, { isNewRecord: false })
return this.callee.build(result, { isNewRecord: false, isDirty: false })
}.bind(this))
}
......@@ -287,7 +287,7 @@ module.exports = (function() {
var transformRowWithEagerLoadingIntoDao = function(result, dao) {
// 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
for (var tableName in result) {
......@@ -302,11 +302,12 @@ module.exports = (function() {
var buildAssociatedDaoInstances = function(tableName, associationData, dao) {
var associatedDaoFactory = this.sequelize.daoFactoryManager.getDAO(tableName, { attribute: 'tableName' })
, association = null
, self = this
if (!!associatedDaoFactory) {
association = this.callee.getAssociation(associatedDaoFactory)
} 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) {
association = this.callee.getAssociation(associatedDaoFactory)
......@@ -322,11 +323,11 @@ module.exports = (function() {
accessor = accessor.slice(0,1).toLowerCase() + accessor.slice(1)
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)
if (['BelongsTo', 'HasOne'].indexOf(association.associationType) > -1) {
accessor = Utils.singularize(accessor)
accessor = Utils.singularize(accessor, self.sequelize.language)
dao[accessor] = isEmpty ? null : daoInstance
} else {
dao[accessor] = dao[accessor] || []
......
......@@ -4,14 +4,22 @@ var mysql
, Utils = require("../../utils")
, 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() {
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.client = null
this.config = config || {}
this.config.port = this.config.port || 3306
this.disconnectTimeoutId = null
this.queue = []
this.activeQueue = []
......@@ -19,7 +27,9 @@ module.exports = (function() {
this.poolCfg = Utils._.defaults(this.config.pool, {
maxConnections: 10,
minConnections: 0,
maxIdleTime: 1000
maxIdleTime: 1000,
handleDisconnects: false,
validate: validateConnection
});
this.pendingQueries = 0;
this.useReplicaton = !!config.replication;
......@@ -72,7 +82,9 @@ module.exports = (function() {
read: Pooling.Pool({
name: 'sequelize-read',
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++];
connect.call(self, function (err, connection) {
......@@ -83,6 +95,7 @@ module.exports = (function() {
destroy: function(client) {
disconnect.call(self, client)
},
validate: self.poolCfg.validate,
max: self.poolCfg.maxConnections,
min: self.poolCfg.minConnections,
idleTimeoutMillis: self.poolCfg.maxIdleTime
......@@ -98,6 +111,7 @@ module.exports = (function() {
destroy: function(client) {
disconnect.call(self, client)
},
validate: self.poolCfg.validate,
max: self.poolCfg.maxConnections,
min: self.poolCfg.minConnections,
idleTimeoutMillis: self.poolCfg.maxIdleTime
......@@ -115,6 +129,7 @@ module.exports = (function() {
},
max: self.poolCfg.maxConnections,
min: self.poolCfg.minConnections,
validate: self.poolCfg.validate,
idleTimeoutMillis: self.poolCfg.maxIdleTime
})
}
......@@ -155,11 +170,12 @@ module.exports = (function() {
query.done(function() {
self.pendingQueries--;
if (self.pool) self.pool.release(query.client);
else {
if (self.pool) {
self.pool.release(query.client);
} else {
if (self.pendingQueries === 0) {
setTimeout(function() {
self.pendingQueries === 0 && self.disconnect.call(self);
self.pendingQueries === 0 && self.disconnect.call(self)
}, 100);
}
}
......@@ -167,16 +183,16 @@ module.exports = (function() {
if (!this.pool) {
query.run(sql);
}
else {
} else {
this.pool.acquire(function(err, client) {
if (err) return query.emit('error', err);
if (err) {
return query.emit('error', err)
}
query.client = client;
query.run(sql);
query.client = client
query.run(sql)
return;
}, undefined, options.type);
}, undefined, options.type)
}
return query;
......@@ -196,8 +212,10 @@ module.exports = (function() {
};
ConnectorManager.prototype.disconnect = function() {
if (this.client) disconnect.call(this, this.client);
return;
if (this.client) {
disconnect.call(this, this.client)
}
return
};
......@@ -236,21 +254,63 @@ module.exports = (function() {
var connect = function(done, config) {
config = config || this.config
var connection = mysql.createConnection({
var emitter = new (require('events').EventEmitter)()
var connectionConfig = {
host: config.host,
port: config.port,
user: config.username,
password: config.password,
database: config.database,
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'");
// client.setMaxListeners(self.maxConcurrentQueries)
this.isConnecting = false
if (config.pool.handleDisconnects) {
handleDisconnect(this.pool, 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) {
options = options || {}
if (this.activeQueue.length < this.maxConcurrentQueries) {
......@@ -295,8 +355,6 @@ module.exports = (function() {
}
var afterQuery = function(queueItem) {
var self = this
dequeue.call(this, queueItem)
transferQueuedItems.call(this, this.maxConcurrentQueries - this.activeQueue.length)
disconnectIfNoConnections.call(this)
......@@ -336,4 +394,4 @@ module.exports = (function() {
}
return ConnectorManager
})()
\ No newline at end of file
})()
......@@ -3,59 +3,80 @@ var Query = require("./query")
module.exports = (function() {
var ConnectorManager = function(sequelize, config) {
var pgModule = config.dialectModulePath || 'pg'
this.sequelize = sequelize
this.client = null
this.config = config || {}
this.pooling = (!!this.config.poolCfg && (this.config.poolCfg.maxConnections > 0))
this.pg = this.config.native ? require('pg').native : require('pg')
this.client = null
this.config = config || {}
this.config.port = this.config.port || 5432
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
if (this.pooling) {
this.pg.defaults.poolSize = this.config.poolCfg.maxConnections
this.pg.defaults.poolIdleTimeout = this.config.poolCfg.maxIdleTime
this.pg.defaults.poolSize = this.config.pool.maxConnections || 10
this.pg.defaults.poolIdleTimeout = this.config.pool.maxIdleTime || 30000
this.pg.defaults.reapIntervalMillis = this.config.pool.reapInterval || 1000
}
this.disconnectTimeoutId = null
this.pendingQueries = 0
this.maxConcurrentQueries = (this.config.maxConcurrentQueries || 50)
process.on('exit', function() {
this.disconnect()
}.bind(this))
}
Utils._.extend(ConnectorManager.prototype, require("../connector-manager").prototype)
var isConnecting = false
var isConnected = false
ConnectorManager.prototype.query = function(sql, callee, options) {
ConnectorManager.prototype.endQuery = function() {
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 query.run(sql)
.success(function() { self.endQuery.call(self) })
.error(function() { self.endQuery.call(self) })
}
ConnectorManager.prototype.endQuery = function() {
var self = this
self.pendingQueries -= 1
if (self.pendingQueries == 0) {
if (!self.pooling && self.pendingQueries === 0) {
setTimeout(function() {
self.pendingQueries == 0 && self.disconnect.call(self)
self.pendingQueries === 0 && self.disconnect.call(self)
}, 100)
}
}
ConnectorManager.prototype.connect = function() {
ConnectorManager.prototype.query = function(sql, callee, options) {
var self = this
self.pendingQueries++
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)
.complete(function(err) { done && done(err) })
.success(function(results) { self.endQuery.call(self) })
.error(function(err) { self.endQuery.call(self) })
.proxy(emitter)
})
}).run()
}
ConnectorManager.prototype.connect = function(callback) {
var self = this
var emitter = new (require('events').EventEmitter)()
// in case database is slow to connect, prevent orphaning the client
if (this.isConnecting) {
return
// TODO: We really need some sort of queue/flush/drain mechanism
if (this.isConnecting && !this.pooling && this.client === null) {
emitter.emit('success', null)
return emitter
}
this.isConnecting = true
......@@ -63,19 +84,39 @@ module.exports = (function() {
var uri = this.sequelize.getQueryInterface().QueryGenerator.databaseConnectionUri(this.config)
var connectCallback = function(err, client) {
var connectCallback = function(err, client, done) {
self.isConnecting = false
if (!!err) {
emitter.emit('error', 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)
break
}
}
} else if (client) {
client.query("SET TIME ZONE 'UTC'")
.on('end', function() {
self.isConnected = true
this.client = client
});
client.query("SET TIME ZONE 'UTC'").on('end', function() {
self.isConnected = true
self.client = client
emitter.emit('success', done)
})
} else {
this.client = null
done && done()
self.client = null
emitter.emit('success')
}
}
......@@ -83,21 +124,32 @@ module.exports = (function() {
// acquire client from pool
this.pg.connect(uri, connectCallback)
} else {
//create one-off client
this.client = new this.pg.Client(uri)
this.client.connect(connectCallback)
if (!!this.client) {
connectCallback(null, this.client)
} else {
//create one-off client
this.client = new this.pg.Client(uri)
this.client.connect(connectCallback)
}
}
return emitter
}
ConnectorManager.prototype.disconnect = function() {
var self = this
if (this.client) this.client.end()
this.client = null
if (this.poolIdentifier) {
this.poolIdentifier.destroyAllNow()
}
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.isConnected = false
}
return ConnectorManager
})()
})()
\ No newline at end of file
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")
, AbstractQuery = require('../abstract/query')
, DataTypes = require('../../data-types')
, hstore = require('./hstore')
module.exports = (function() {
var Query = function(client, sequelize, callee, options) {
......@@ -20,32 +21,33 @@ module.exports = (function() {
Query.prototype.run = function(sql) {
this.sql = sql
var self = this
, receivedError = false
, query = this.client.query(sql)
, rows = []
if (this.options.logging !== false) {
this.options.logging('Executing: ' + this.sql)
}
var receivedError = false
, query = this.client.query(sql)
, rows = []
query.on('row', function(row) {
rows.push(row)
})
query.on('error', function(err) {
receivedError = true
this.emit('error', err, this.callee)
}.bind(this))
self.emit('error', err, self.callee)
})
query.on('end', function() {
this.emit('sql', this.sql)
self.emit('sql', self.sql)
if (receivedError) {
return
}
onSuccess.call(this, rows)
}.bind(this))
onSuccess.call(self, rows, sql)
})
return this
}
......@@ -54,10 +56,11 @@ module.exports = (function() {
return 'id'
}
var onSuccess = function(rows) {
var onSuccess = function(rows, sql) {
var results = []
, isTableNameQuery = (this.sql.indexOf('SELECT table_name FROM information_schema.tables') === 0)
, isRelNameQuery = (this.sql.indexOf('SELECT relname FROM pg_class WHERE oid IN') === 0)
, self = this
, 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 (isRelNameQuery) {
......@@ -68,20 +71,21 @@ module.exports = (function() {
}
})
} else {
results = rows.map(function(row) { return Utils._.values(row) })
results = rows.map(function(row) { return Utils._.values(row) })
}
return this.emit('success', results)
}
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 = {}
rows.forEach(function(_result) {
result[_result.Field] = {
type: _result.Type.toUpperCase(),
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') {
......@@ -96,13 +100,30 @@ module.exports = (function() {
result[_result.Field].defaultValue = result[_result.Field].defaultValue.replace(/'/g, "")
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)
} 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))
}
} else if (this.send('isShowOrDescribeQuery')) {
......@@ -113,7 +134,7 @@ module.exports = (function() {
if (rows[0].hasOwnProperty(key)) {
var record = rows[0][key]
if (!!this.callee.daoFactory && !!this.callee.daoFactory.rawAttributes && !!this.callee.daoFactory.rawAttributes[key] && !!this.callee.daoFactory.rawAttributes[key].type && !!this.callee.daoFactory.rawAttributes[key].type.type && this.callee.daoFactory.rawAttributes[key].type.type === DataTypes.HSTORE.type) {
record = this.callee.daoFactory.daoFactoryManager.sequelize.queryInterface.QueryGenerator.toHstore(record)
record = hstore.parse(record)
}
this.callee[key] = record
}
......@@ -127,7 +148,7 @@ module.exports = (function() {
if (rows[0].hasOwnProperty(key)) {
var record = rows[0][key]
if (!!this.callee.daoFactory && !!this.callee.daoFactory.rawAttributes && !!this.callee.daoFactory.rawAttributes[key] && !!this.callee.daoFactory.rawAttributes[key].type && !!this.callee.daoFactory.rawAttributes[key].type.type && this.callee.daoFactory.rawAttributes[key].type.type === DataTypes.HSTORE.type) {
record = this.callee.daoFactory.daoFactoryManager.sequelize.queryInterface.QueryGenerator.toHstore(record)
record = hstore.parse(record)
}
this.callee[key] = record
}
......@@ -141,4 +162,4 @@ module.exports = (function() {
}
return Query
})()
})()
\ No newline at end of file
var Utils = require("../../utils")
, sqlite3 = require('sqlite3').verbose()
var sqlite3
, Utils = require("../../utils")
, Query = require("./query")
module.exports = (function() {
var ConnectorManager = function(sequelize) {
var ConnectorManager = function(sequelize, config) {
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
// explicitly disallowed. It's still opt-in per relation
db.run('PRAGMA FOREIGN_KEYS=ON')
}
})
}
Utils._.extend(ConnectorManager.prototype, require("../connector-manager").prototype)
ConnectorManager.prototype.query = function(sql, callee, options) {
if (!this.database) {
this.connect()
}
return new Query(this.database, this.sequelize, callee, options).run(sql)
}
......
......@@ -29,7 +29,7 @@ module.exports = (function() {
this.options.logging('Executing: ' + this.sql)
}
var columnTypes = {};
var columnTypes = {}
this.database.serialize(function() {
var executeSql = function() {
self.database[getDatabaseMethod.call(self)](self.sql, function(err, results) {
......@@ -48,16 +48,16 @@ module.exports = (function() {
self.database.all("PRAGMA table_info(" + tableName + ")", function(err, results) {
if (!err) {
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 {
executeSql();
executeSql()
}
} else {
executeSql();
executeSql()
}
})
......@@ -85,15 +85,18 @@ module.exports = (function() {
if (this.sql.indexOf('sqlite_master') !== -1) {
result = results.map(function(resultSet) { return resultSet.name })
} else if (this.send('isSelectQuery')) {
// we need to convert the timestamps into actual date objects
if(!this.options.raw) {
results = results.map(function(result) {
for (var name in result) {
if (result.hasOwnProperty(name) && (metaData.columnTypes[name] === 'DATETIME')) {
var val = result[name];
if(val !== null) {
result[name] = new Date(val+'Z'); // Z means UTC
if (result.hasOwnProperty(name) && metaData.columnTypes[name]) {
if (metaData.columnTypes[name] === 'DATETIME') {
// we need to convert the timestamps into actual date objects
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")
, EventEmitter = require("events").EventEmitter
, Promise = require("promise")
, 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() {
var CustomEventEmitter = function(fct) {
this.fct = fct
this.fct = bindToProcess(fct);
}
util.inherits(CustomEventEmitter, EventEmitter)
CustomEventEmitter.prototype.run = function() {
process.nextTick(function() {
tick(function() {
if (this.fct) {
this.fct.call(this, this)
}
}.bind(this))
return this
}
CustomEventEmitter.prototype.success =
CustomEventEmitter.prototype.ok =
function(fct) {
this.on('success', fct)
this.on('success', bindToProcess(fct))
return this
}
......@@ -29,18 +41,25 @@ module.exports = (function() {
CustomEventEmitter.prototype.fail =
CustomEventEmitter.prototype.error =
function(fct) {
this.on('error', fct)
this.on('error', bindToProcess(fct))
return this;
}
CustomEventEmitter.prototype.done =
CustomEventEmitter.prototype.complete =
function(fct) {
fct = bindToProcess(fct);
this.on('error', function(err) { fct(err, null) })
.on('success', function(result) { fct(null, result) })
return this
}
CustomEventEmitter.prototype.sql =
function(fct) {
this.on('sql', bindToProcess(fct))
return this;
}
CustomEventEmitter.prototype.proxy = function(emitter) {
proxyEventKeys.forEach(function (eventKey) {
this.on(eventKey, function (result) {
......@@ -49,6 +68,18 @@ module.exports = (function() {
}.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;
})()
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")
, path = require("path")
, Utils = require("./utils")
, DataTypes = require("./data-types")
, QueryInterface = require("./query-interface")
module.exports = (function() {
var Migration = function(migrator, path) {
var Migration = function(migrator, p) {
this.migrator = migrator
this.path = path
this.filename = Utils._.last(this.path.split('/'))
this.path = path.normalize(p)
this.filename = Utils._.last(this.path.split(path.sep))
var parsed = Migration.parseFilename(this.filename)
......
const fs = require("fs")
, moment = require("moment")
var Utils = require("./utils")
, Migration = require("./migration")
, DataTypes = require("./data-types")
var Utils = require(__dirname + "/utils")
, Migration = require(__dirname + "/migration")
, DataTypes = require(__dirname + "/data-types")
module.exports = (function() {
var Migrator = function(sequelize, options) {
......@@ -45,7 +45,7 @@ module.exports = (function() {
if (err) {
emitter.emit('error', err)
} else {
var chainer = new Utils.QueryChainer
var chainer = new Utils.QueryChainer()
, from = migrations[0]
if (options.method === 'down') {
......@@ -58,8 +58,10 @@ module.exports = (function() {
} else {
self.options.logging("Running migrations...")
}
migrations.forEach(function(migration) {
var migrationTime
chainer.add(migration, 'execute', [options], {
before: function(migration) {
if (self.options.logging !== false) {
......@@ -67,6 +69,7 @@ module.exports = (function() {
}
migrationTime = process.hrtime()
},
after: function(migration) {
migrationTime = process.hrtime(migrationTime)
migrationTime = Math.round( (migrationTime[0] * 1000) + (migrationTime[1] / 1000000));
......@@ -75,6 +78,7 @@ module.exports = (function() {
self.options.logging('Completed in ' + migrationTime + 'ms')
}
},
success: function(migration, callback) {
if (options.method === 'down') {
deleteUndoneMigration.call(self, from, migration, callback)
......@@ -177,17 +181,77 @@ module.exports = (function() {
}).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
var getLastMigrationFromDatabase = function() {
var self = this
return new Utils.CustomEventEmitter(function(emitter) {
self.findOrCreateSequelizeMetaDAO().success(function(SequelizeMeta) {
SequelizeMeta.find({ order: 'id DESC' }).success(function(meta) {
emitter.emit('success', meta ? meta : null)
}).error(function(err) { emitter.emit('error', err) })
}).error(function(err) { emitter.emit(err) })
self
.findOrCreateSequelizeMetaDAO()
.success(function(SequelizeMeta) {
SequelizeMeta
.find({ order: 'id DESC' })
.success(function(meta) {
emitter.emit('success', meta ? meta : null)
})
.error(function(err) {
emitter.emit('error', err)
})
})
.error(function(err) {
emitter.emit('error', err)
})
}).run()
}
......@@ -195,7 +259,8 @@ module.exports = (function() {
var self = this
return new Utils.CustomEventEmitter(function(emitter) {
getLastMigrationFromDatabase.call(self)
getLastMigrationFromDatabase
.call(self)
.success(function(meta) {
emitter.emit('success', meta ? meta.to : null)
})
......
var Utils = require("./utils")
var Utils = require(__dirname + "/utils")
module.exports = (function() {
var QueryChainer = function(emitters) {
......@@ -125,15 +125,15 @@ module.exports = (function() {
this.finished = true
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) {
this.finished = (this.finishedEmits == this.serials.length)
this.finished = (this.finishedEmits === this.serials.length)
}
if (this.finished && this.wasRunning) {
var status = (this.fails.length == 0 ? 'success' : 'error')
, result = (this.fails.length == 0 ? this[resultsName] : this.fails)
var status = (this.fails.length === 0 ? 'success' : 'error')
, result = (this.fails.length === 0 ? this[resultsName] : this.fails)
this.eventEmitter.emit.apply(this.eventEmitter, [status, result].concat(result))
}
......
var url = require("url")
, Path = require("path")
, Utils = require("./utils")
, DAOFactory = require("./dao-factory")
, DataTypes = require('./data-types')
......@@ -14,6 +15,7 @@ module.exports = (function() {
@param {String} [password=null] The password which is used to authenticate against the database.
@param {Object} [options={}] An object with options.
@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 {Integer} [options.port=3306] The port of the relational database.
@param {String} [options.protocol='tcp'] The protocol of the relational database.
......@@ -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.replication=false] I have absolutely no idea.
@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
// without password and options
......@@ -47,7 +50,8 @@ module.exports = (function() {
var urlParts
options = options || {}
if (arguments.length === 1) {
if (arguments.length === 1 || (arguments.length === 2 && typeof username === 'object')) {
options = username || {}
urlParts = url.parse(arguments[0])
database = urlParts.path.replace(/^\//, '')
dialect = urlParts.protocol
......@@ -66,8 +70,8 @@ module.exports = (function() {
this.options = Utils._.extend({
dialect: 'mysql',
dialectModulePath: null,
host: 'localhost',
port: 3306,
protocol: 'tcp',
define: {},
query: {},
......@@ -78,7 +82,9 @@ module.exports = (function() {
native: false,
replication: false,
ssl: undefined,
pool: {}
pool: {},
quoteIdentifiers: true,
language: 'en'
}, options || {})
if (this.options.logging === true) {
......@@ -98,10 +104,16 @@ module.exports = (function() {
native : this.options.native,
ssl : this.options.ssl,
replication: this.options.replication,
maxConcurrentQueries: this.options.maxConcurrentQueries
dialectModulePath: this.options.dialectModulePath,
maxConcurrentQueries: this.options.maxConcurrentQueries,
dialectOptions: this.options.dialectOptions,
}
var ConnectorManager = require("./dialects/" + this.options.dialect + "/connector-manager")
try {
var ConnectorManager = require("./dialects/" + this.options.dialect + "/connector-manager")
} catch(err) {
throw new Error("The dialect " + this.options.dialect + " is not supported.")
}
this.daoFactoryManager = new DAOFactoryManager(this)
this.connectorManager = new ConnectorManager(this, this.config)
......@@ -151,17 +163,8 @@ module.exports = (function() {
Sequelize.prototype.define = function(daoName, attributes, options) {
options = options || {}
var globalOptions = this.options
// If you don't specify a valid data type lets help you debug it
Utils._.each(attributes, function(dataType, name){
if (Utils.isHash(dataType)) {
dataType = dataType.type
}
if (dataType === undefined) {
throw new Error('Unrecognized data type for field '+ name)
}
})
var self = this
, globalOptions = this.options
if (globalOptions.define) {
options = Utils._.extend({}, globalOptions.define, options)
......@@ -172,26 +175,87 @@ module.exports = (function() {
}
})
}
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(this.isDefined(daoName)) {
this.daoFactoryManager.removeDAO(this.daoFactoryManager.getDAO(daoName))
this.daoFactoryManager.removeDAO(this.daoFactoryManager.getDAO(daoName))
}
var factory = new DAOFactory(daoName, attributes, options)
this.daoFactoryManager.addDAO(factory.init(this.daoFactoryManager))
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) {
var daos = this.daoFactoryManager.daos
return (daos.filter(function(dao) { return dao.name === daoName }).length !== 0)
}
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]) {
var defineCall = require(path)
var defineCall = (arguments.length > 1 ? arguments[1] : require(path))
this.importCache[path] = defineCall(this, DataTypes)
}
......@@ -199,12 +263,17 @@ module.exports = (function() {
}
Sequelize.prototype.migrate = function(options) {
this.getMigrator().migrate(options)
return this.getMigrator().migrate(options)
}
Sequelize.prototype.query = function(sql, callee, options, replacements) {
if (arguments.length === 4) {
sql = Utils.format([sql].concat(replacements), this.options.dialect)
if (Array.isArray(replacements)) {
sql = Utils.format([sql].concat(replacements), this.options.dialect)
}
else {
sql = Utils.formatNamedParameters(sql, replacements, this.options.dialect)
}
} else if (arguments.length === 3) {
options = options
} else if (arguments.length === 2) {
......@@ -288,5 +357,13 @@ module.exports = (function() {
}).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
})()
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) {
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) {
return 'NULL';
}
switch (typeof val) {
case 'boolean': return (val) ? 'true' : 'false';
case 'number': return val+'';
case 'boolean':
// SQLite doesn't have true/false support. MySQL aliases true/false to 1/0
// for us. Postgres actually has a boolean type with true/false literals,
// but sequelize doesn't use it yet.
return dialect === 'sqlite' ? +!!val : ('' + !!val);
case 'number':
return val+'';
}
if (val instanceof Date) {
val = SqlString.dateToString(val, timeZone || "Z");
val = SqlString.dateToString(val, timeZone || "Z", dialect)
}
if (Buffer.isBuffer(val)) {
return SqlString.bufferToString(val);
return SqlString.bufferToString(val, dialect)
}
if (Array.isArray(val)) {
return SqlString.arrayToList(val, timeZone);
if (Array.isArray(val) || isArrayBufferView(val)) {
return SqlString.arrayToList(val, timeZone, dialect, field)
}
if (typeof val === 'object') {
if (stringifyObjects) {
val = val.toString();
val = val.toString()
} 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
val = val.replace(/'/g, "''");
// http://stackoverflow.com/q/603572/130598
val = val.replace(/'/g, "''")
} else {
val = val.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) {
switch(s) {
......@@ -51,91 +89,134 @@ SqlString.escape = function(val, stringifyObjects, timeZone, dialect) {
case "\x1a": return "\\Z";
default: return "\\"+s;
}
});
})
}
return "'"+val+"'";
};
return "'"+val+"'"
}
SqlString.arrayToList = function(array, timeZone) {
return array.map(function(v) {
if (Array.isArray(v)) return '(' + SqlString.arrayToList(v) + ')';
return SqlString.escape(v, true, timeZone);
}).join(', ');
};
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) {
if (Array.isArray(v)) {
return '(' + SqlString.arrayToList(v, timeZone, dialect) + ')'
}
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) {
values = [].concat(values);
return sql.replace(/\?/g, function(match) {
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) {
var dt = new Date(date);
// TODO: Ideally all dialects would work a bit more like this
if (dialect === "postgres") {
return moment(dt).zone('+00:00').format("YYYY-MM-DD HH:mm:ss.SSS Z")
}
if (timeZone != 'local') {
var tz = convertTimezone(timeZone);
if (timeZone !== 'local') {
var tz = convertTimezone(timeZone)
dt.setTime(dt.getTime() + (dt.getTimezoneOffset() * 60000));
dt.setTime(dt.getTime() + (dt.getTimezoneOffset() * 60000))
if (tz !== false) {
dt.setTime(dt.getTime() + (tz * 60000));
dt.setTime(dt.getTime() + (tz * 60000))
}
}
var year = dt.getFullYear();
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;
};
return moment(dt).format("YYYY-MM-DD HH:mm:ss")
}
SqlString.bufferToString = function(buffer) {
var hex = '';
SqlString.bufferToString = function(buffer, dialect) {
var hex = ''
try {
hex = buffer.toString('hex');
hex = buffer.toString('hex')
} catch (err) {
// node v0.4.x does not support hex / throws unknown encoding error
for (var i = 0; i < buffer.length; i++) {
var byte = buffer[i];
hex += zeroPad(byte.toString(16));
var byte = buffer[i]
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) {
var values = [];
var values = []
for (var key in object) {
var value = object[key];
var value = object[key]
if(typeof value === 'function') {
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) {
return (number < 10) ? '0' + number : number;
return (number < 10) ? '0' + number : number
}
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) {
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",
"description": "Multi dialect ORM for Node.JS",
"version": "1.7.0-alpha1",
"version": "1.7.0-beta.0",
"author": "Sascha Depold <sascha@depold.com>",
"contributors": [
{
......@@ -17,8 +17,15 @@
"email": "sky@skytrife.com"
},
{
"name": "Jan Aagaard Meier (Innofluence)",
"email": "jam@innofluence.com"
"name": "Jan Aagaard Meier",
"email": [
"janzeh@gmail.com",
"jmei@itu.dk"
]
},
{
"name": "Daniel Durante",
"email": "me@danieldurante.com"
}
],
"repository": {
......@@ -29,25 +36,29 @@
"url": "https://github.com/sequelize/sequelize/issues"
},
"dependencies": {
"lodash": "~1.2.1",
"lodash": "~2.1.0",
"underscore.string": "~2.3.0",
"lingo": "~0.0.5",
"validator": "1.1.1",
"moment": "~1.7.0",
"commander": "~0.6.0",
"generic-pool": "1.0.12",
"dottie": "0.0.6-1",
"toposort-class": "0.1.4"
"validator": "~1.5.0",
"moment": "~2.2.1",
"commander": "~2.0.0",
"dottie": "0.0.8-0",
"toposort-class": "~0.2.0",
"generic-pool": "2.0.4",
"promise": "~3.2.0",
"sql": "~0.26.0"
},
"devDependencies": {
"jasmine-node": "1.5.0",
"sqlite3": "~2.1.5",
"mysql": "~2.0.0-alpha7",
"pg": "~0.10.2",
"mariasql": "~0.1.18",
"buster": "~0.6.0",
"watchr": "~2.2.0",
"yuidocjs": "~0.3.36"
"sqlite3": "~2.1.12",
"mysql": "~2.0.0-alpha8",
"pg": "~2.6.0",
"watchr": "~2.4.3",
"yuidocjs": "~0.3.36",
"chai": "~1.8.0",
"mocha": "~1.13.0",
"chai-datetime": "~1.1.1",
"sinon": "~1.7.3",
"mariasql": "~0.1.19"
},
"keywords": [
"mysql",
......@@ -57,22 +68,14 @@
],
"main": "index",
"scripts": {
"test": "npm run test-jasmine && npm run test-buster",
"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",
"test": "make all",
"docs": "node_modules/.bin/yuidoc . -o docs"
},
"bin": {
"sequelize": "bin/sequelize"
},
"engines": {
"node": ">=0.4.6"
"node": ">=0.6.21"
},
"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()
})
})
})
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!