├── test ├── tmp │ └── .gitkeep ├── integration │ ├── tmp │ │ └── .gitkeep │ ├── dialects │ │ ├── sqlite │ │ │ └── test.sqlite │ │ ├── mssql │ │ │ ├── query-queue.test.js │ │ │ └── connection-manager.test.js │ │ └── postgres │ │ │ ├── connection-manager.test.js │ │ │ ├── error.test.js │ │ │ └── hstore.test.js │ ├── assets │ │ ├── es6project.js │ │ └── project.js │ ├── support.js │ ├── vectors.test.js │ ├── pool.test.js │ ├── replication.test.js │ ├── schema.test.js │ ├── model │ │ ├── scope.test.js │ │ ├── findAll │ │ │ └── group.test.js │ │ ├── optimistic_locking.test.js │ │ ├── update.test.js │ │ ├── attributes.test.js │ │ ├── paranoid.test.js │ │ └── scope │ │ │ └── findAndCount.test.js │ ├── hooks │ │ ├── count.test.js │ │ ├── destroy.test.js │ │ ├── upsert.test.js │ │ └── restore.test.js │ ├── sequelize │ │ └── log.test.js │ ├── trigger.test.js │ ├── timezone.test.js │ └── include │ │ └── paranoid.test.js ├── unit │ ├── support.js │ ├── promise.test.js │ ├── sql │ │ ├── create-schema.test.js │ │ ├── remove-column.test.js │ │ ├── add-column.test.js │ │ ├── update.js │ │ ├── offset-limit.test.js │ │ ├── change-column.test.js │ │ └── create-table.test.js │ ├── errors.test.js │ ├── model │ │ ├── overwriting-builtins.test.js │ │ ├── removeAttribute.test.js │ │ ├── helpers.test.js │ │ ├── upsert.test.js │ │ ├── bulkcreate.test.js │ │ ├── destroy.test.js │ │ ├── define.test.js │ │ ├── find-or-create.test.js │ │ ├── update.test.js │ │ ├── schema.test.js │ │ ├── count.test.js │ │ ├── findone.test.js │ │ ├── indexes.test.js │ │ └── find-create-find.test.js │ ├── associations │ │ ├── association.test.js │ │ ├── belongs-to.test.js │ │ ├── has-one.test.js │ │ └── dont-modify-options.test.js │ ├── instance │ │ ├── get.test.js │ │ ├── previous.test.js │ │ ├── destroy.test.js │ │ ├── decrement.test.js │ │ ├── increment.test.js │ │ ├── reload.test.js │ │ ├── restore.test.js │ │ ├── save.test.js │ │ ├── set.test.js │ │ └── build.test.js │ ├── dialects │ │ └── mssql │ │ │ ├── connection-manager.js │ │ │ └── resource-lock.test.js │ ├── transaction.test.js │ └── connection-manager.test.js └── config │ ├── options.js │ ├── .docker.env │ └── config.js ├── docs ├── changelog.md ├── requirements.txt ├── favicon.ico ├── images │ ├── logo.png │ ├── filsh.png │ ├── clevertech.png │ ├── logo-small.png │ ├── metamarkets.png │ └── shutterstock.png ├── imprint.md ├── docs │ ├── schema.md │ └── legacy.md ├── index.md ├── css │ └── custom.css └── api │ ├── associations │ ├── belongs-to.md │ └── has-one.md │ └── deferrable.md ├── codecov.yml ├── index.js ├── .doclets.yml ├── lib ├── promise.js ├── model │ └── attribute.js ├── dialects │ ├── postgres │ │ ├── hstore.js │ │ ├── index.js │ │ └── range.js │ ├── parserStore.js │ ├── mssql │ │ ├── resource-lock.js │ │ ├── index.js │ │ └── query-interface.js │ ├── mysql │ │ ├── query-interface.js │ │ └── index.js │ ├── sqlite │ │ └── index.js │ └── abstract │ │ └── index.js ├── associations │ ├── index.js │ ├── base.js │ └── helpers.js ├── utils │ ├── inherits.js │ ├── logger.js │ ├── parameter-validator.js │ └── validator-extras.js ├── query-types.js ├── model-manager.js ├── sql-string.js └── deferrable.js ├── .mention-bot ├── sscce_template.js ├── .npmignore ├── .gitignore ├── .editorconfig ├── docker-compose.yml ├── CONTACT.md ├── .jshintrc ├── LICENSE ├── .github ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE.md ├── appveyor.yml ├── .travis.yml ├── .eslintrc.json ├── appveyor-setup.ps1 ├── CONTRIBUTING.DOCS.md ├── mkdocs.yml └── README.md /test/tmp/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/changelog.md: -------------------------------------------------------------------------------- 1 | ../changelog.md -------------------------------------------------------------------------------- /test/integration/tmp/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/integration/dialects/sqlite/test.sqlite: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | git+https://github.com/dart-lang/py-gfm.git 2 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: 2 | layout: "header, changes" 3 | behavior: default 4 | 5 | -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sequelize/master/docs/favicon.ico -------------------------------------------------------------------------------- /test/unit/support.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('../support'); 4 | -------------------------------------------------------------------------------- /docs/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sequelize/master/docs/images/logo.png -------------------------------------------------------------------------------- /docs/images/filsh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sequelize/master/docs/images/filsh.png -------------------------------------------------------------------------------- /docs/images/clevertech.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sequelize/master/docs/images/clevertech.png -------------------------------------------------------------------------------- /docs/images/logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sequelize/master/docs/images/logo-small.png -------------------------------------------------------------------------------- /docs/images/metamarkets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sequelize/master/docs/images/metamarkets.png -------------------------------------------------------------------------------- /docs/images/shutterstock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sequelize/master/docs/images/shutterstock.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * The entry point. 5 | * 6 | * @module Sequelize 7 | */ 8 | module.exports = require('./lib/sequelize'); 9 | -------------------------------------------------------------------------------- /.doclets.yml: -------------------------------------------------------------------------------- 1 | dir: lib 2 | packageJson: package.json 3 | articles: 4 | - Getting started: docs/articles/getting-started.md 5 | branches: 6 | - master 7 | - doclets 8 | 9 | -------------------------------------------------------------------------------- /lib/promise.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Promise = require('bluebird').getNewLibraryCopy(); 4 | 5 | module.exports = Promise; 6 | module.exports.Promise = Promise; 7 | module.exports.default = Promise; 8 | -------------------------------------------------------------------------------- /test/config/options.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | 5 | module.exports = { 6 | configFile: path.resolve('config', 'database.json'), 7 | migrationsPath: path.resolve('db', 'migrate') 8 | }; 9 | -------------------------------------------------------------------------------- /test/integration/assets/es6project.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | exports.default = function(sequelize, DataTypes) { 3 | return sequelize.define('Project' + parseInt(Math.random() * 9999999999999999), { 4 | name: DataTypes.STRING 5 | }); 6 | }; 7 | -------------------------------------------------------------------------------- /test/integration/assets/project.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(sequelize, DataTypes) { 4 | return sequelize.define('Project' + parseInt(Math.random() * 9999999999999999), { 5 | name: DataTypes.STRING 6 | }); 7 | }; 8 | -------------------------------------------------------------------------------- /test/config/.docker.env: -------------------------------------------------------------------------------- 1 | # Special ports needed for docker to prevent port conflicts 2 | SEQ_MYSQL_PORT=8999 3 | SEQ_MYSQL_USER=sequelize_test 4 | SEQ_MYSQL_PW=sequelize_test 5 | SEQ_PG_PORT=8998 6 | SEQ_PG_USER=sequelize_test 7 | SEQ_PG_PW=sequelize_test 8 | -------------------------------------------------------------------------------- /.mention-bot: -------------------------------------------------------------------------------- 1 | { 2 | "findPotentialReviewers": true, 3 | "fileBlacklist": ["*.md", "*.json"], 4 | "userBlacklist": ["sdepold"], 5 | "userBlacklistForPR": ["greenkeeperio-bot"], 6 | "actions": ["opened"], 7 | "delayed": true, 8 | "delayedUntil": "3d" 9 | } 10 | -------------------------------------------------------------------------------- /sscce_template.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | * Copy this file to ./sscce.js 5 | * Add code from issue 6 | * npm run sscce-{dialect} 7 | */ 8 | 9 | const Sequelize = require('./index'); 10 | const sequelize = require('./test/support').createSequelizeInstance(); 11 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | docs 2 | examples 3 | test 4 | README.md 5 | .watchr.js 6 | changelog.md 7 | Makefile 8 | coverage* 9 | .github 10 | 11 | appveyor-setup.ps1 12 | appveyor.yml 13 | codecov.yml 14 | docker-compose.yml 15 | mkdocs.yml 16 | .dockerignore 17 | .editorconfig 18 | .travis.yml 19 | 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | test*.js 2 | *.swp 3 | .idea 4 | .DS_STORE 5 | node_modules 6 | npm-debug.log 7 | *~ 8 | test/binary/tmp/* 9 | test/tmp/* 10 | test/dialects/sqlite/test.sqlite 11 | test/sqlite/test.sqlite 12 | coverage-* 13 | site 14 | docs/api/tmp.md 15 | ssce.js 16 | coverage 17 | .vscode/ 18 | *.sublime* -------------------------------------------------------------------------------- /lib/model/attribute.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class Attribute { 4 | constructor(options) { 5 | if (options.type === undefined) options = {type: options}; 6 | this.type = options.type; 7 | } 8 | } 9 | 10 | module.exports = Attribute; 11 | module.exports.Attribute = Attribute; 12 | module.exports.default = Attribute; 13 | -------------------------------------------------------------------------------- /lib/dialects/postgres/hstore.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const hstore = require('pg-hstore')({sanitize : true}); 4 | 5 | function stringify(data) { 6 | if (data === null) return null; 7 | return hstore.stringify(data); 8 | } 9 | exports.stringify = stringify; 10 | 11 | function parse(value) { 12 | if (value === null) return null; 13 | return hstore.parse(value); 14 | } 15 | exports.parse = parse; 16 | -------------------------------------------------------------------------------- /lib/associations/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Association = require('./base'); 4 | Association.BelongsTo = require('./belongs-to'); 5 | Association.HasOne = require('./has-one'); 6 | Association.HasMany = require('./has-many'); 7 | Association.BelongsToMany = require('./belongs-to-many'); 8 | 9 | module.exports = Association; 10 | module.exports.default = Association; 11 | module.exports.Association = Association; 12 | -------------------------------------------------------------------------------- /lib/utils/inherits.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const util = require('util'); 4 | const _ = require('lodash'); 5 | 6 | /** 7 | * like util.inherits, but also copies over static properties 8 | * @private 9 | */ 10 | function inherits(constructor, superConstructor) { 11 | util.inherits(constructor, superConstructor); // Instance (prototype) methods 12 | _.extend(constructor, superConstructor); // Static methods 13 | } 14 | 15 | module.exports = inherits; 16 | module.exports.inherits = inherits; 17 | module.exports.default = inherits; 18 | -------------------------------------------------------------------------------- /lib/dialects/parserStore.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stores = new Map(); 4 | 5 | module.exports = dialect => { 6 | 7 | if (!stores.has(dialect)) { 8 | stores.set(dialect, new Map()); 9 | } 10 | 11 | return { 12 | clear() { 13 | stores.get(dialect).clear(); 14 | }, 15 | refresh(dataType) { 16 | for (const type of dataType.types[dialect]) { 17 | stores.get(dialect).set(type, dataType.parse); 18 | } 19 | }, 20 | get(type) { 21 | return stores.get(dialect).get(type); 22 | } 23 | }; 24 | }; 25 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | # Change these settings to your own preference 10 | indent_style = space 11 | indent_size = 2 12 | 13 | # We recommend you to keep these unchanged 14 | end_of_line = lf 15 | charset = utf-8 16 | trim_trailing_whitespace = true 17 | insert_final_newline = true 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | indent_size = 4 22 | 23 | [Makefile] 24 | indent_style = tabs 25 | -------------------------------------------------------------------------------- /docs/imprint.md: -------------------------------------------------------------------------------- 1 | # Imprint - Boring legal stuff for the rest of us. 2 | As there are people who are suing for fun and glory, you can find the respective information about the author of the page right here. Have fun reading ... 3 | 4 | ## AUTHOR(S) 5 | 6 | ``` 7 | Main author: 8 | 9 | Sascha Depold 10 | Uhlandstr. 122 11 | 10717 Berlin 12 | sascha [at] depold [dot] com 13 | [plus] 49 152 [slash] 03878582 14 | ``` 15 | 16 | ## INHALTLICHE VERANTWORTUNG 17 | 18 | ``` 19 | Ich übernehme keine Haftung für ausgehende Links. 20 | Daher musst du dich bei Problemen an deren Betreiber wenden! 21 | ``` 22 | -------------------------------------------------------------------------------- /test/unit/promise.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | var chai = require('chai') 5 | , expect = chai.expect 6 | , Support = require(__dirname + '/support') 7 | , Sequelize = Support.Sequelize 8 | , Promise = Sequelize.Promise 9 | , Bluebird = require('bluebird'); 10 | 11 | describe('Promise', function() { 12 | it('should be an independent copy of bluebird library', function() { 13 | expect(Promise.prototype.then).to.be.a('function'); 14 | expect(Promise).to.not.equal(Bluebird); 15 | expect(Promise.prototype).to.not.equal(Bluebird.prototype); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /lib/dialects/mssql/resource-lock.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Promise = require('../../promise'); 4 | 5 | function ResourceLock(resource) { 6 | this.resource = resource; 7 | this.previous = Promise.resolve(resource); 8 | } 9 | 10 | ResourceLock.prototype.unwrap = function() { 11 | return this.resource; 12 | }; 13 | 14 | ResourceLock.prototype.lock = function() { 15 | var lock = this.previous; 16 | var resolve; 17 | 18 | this.previous = new Promise(function(r) { 19 | resolve = r; 20 | }); 21 | 22 | return lock.disposer(resolve); 23 | }; 24 | 25 | module.exports = ResourceLock; 26 | -------------------------------------------------------------------------------- /test/unit/sql/create-schema.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Support = require(__dirname + '/../support') 4 | , expectsql = Support.expectsql 5 | , current = Support.sequelize 6 | , sql = current.dialect.QueryGenerator; 7 | 8 | describe(Support.getTestDialectTeaser('SQL'), function() { 9 | if (current.dialect.name === 'postgres') { 10 | describe('dropSchema', function () { 11 | test('IF EXISTS', function () { 12 | expectsql(sql.dropSchema('foo'), { 13 | postgres: 'DROP SCHEMA IF EXISTS foo CASCADE;' 14 | }); 15 | }); 16 | }); 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /docs/docs/schema.md: -------------------------------------------------------------------------------- 1 | ## Syncing 2 | 3 | `sequelize.sync()` will, based on your model definitions, create any missing tables. 4 | If `force: true` it will first drop tables before recreating them. 5 | 6 | ## Migrations / Manual schema changes 7 | 8 | Sequelize has a [sister library](https://github.com/sequelize/umzug) for handling execution and logging of migration tasks. 9 | Sequelize provides a list of ways to programmatically create or change a table schema. 10 | 11 | ### createTable 12 | 13 | ### addColumn 14 | 15 | ### changeColumn 16 | 17 | ### removeColumn 18 | 19 | ### addIndex 20 | 21 | ### removeIndex 22 | 23 | ### addConstraint 24 | 25 | ### removeConstraint 26 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | # PostgreSQL 5 | postgres-95: 6 | image: camptocamp/postgis:9.5 7 | environment: 8 | POSTGRES_USER: sequelize_test 9 | POSTGRES_PASSWORD: sequelize_test 10 | POSTGRES_DB: sequelize_test 11 | ports: 12 | - "127.0.0.1:8998:5432" 13 | container_name: postgres-95 14 | 15 | # MySQL 16 | mysql-57: 17 | image: mysql:5.7 18 | environment: 19 | MYSQL_ROOT_PASSWORD: lollerskates 20 | MYSQL_DATABASE: sequelize_test 21 | MYSQL_USER: sequelize_test 22 | MYSQL_PASSWORD: sequelize_test 23 | ports: 24 | - "127.0.0.1:8999:3306" 25 | container_name: mysql-57 26 | -------------------------------------------------------------------------------- /CONTACT.md: -------------------------------------------------------------------------------- 1 | ## Contact Us 2 | 3 | In some cases you might want to reach the maintainers privately. These can be reporting a security vulnerability or any other specific cases. 4 | You can use the information below to contact maintainers directly. We will try to get back to you as soon as possible. 5 | 6 | ### Via Email 7 | 8 | - **Mick Hansen** mick.kasper.hansen@gmail.com 9 | - **Jan Aagaard Meier** janzeh@gmail.com 10 | - **Sushant Dhiman** sushantdhiman@outlook.com 11 | 12 | ### Via Slack 13 | 14 | Maintainer's usernames for [Sequelize Slack Channel](https://sequelize.slack.com) 15 | 16 | - **Mick Hansen** @mhansen 17 | - **Jan Aagaard Meier** @janmeier 18 | - **Sushant Dhiman** @sushantdhiman 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /test/unit/errors.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* jshint expr: true */ 3 | const errors = require('../../lib/errors'); 4 | const expect = require('chai').expect; 5 | 6 | describe('errors', () => { 7 | it('should maintain stack trace', () => { 8 | function throwError() { 9 | throw new errors.ValidationError('this is a message'); 10 | } 11 | let err; 12 | try { 13 | throwError(); 14 | } catch (error) { 15 | err = error; 16 | } 17 | expect(err).to.exist; 18 | const stackParts = err.stack.split('\n'); 19 | expect(stackParts[0]).to.equal('SequelizeValidationError: this is a message'); 20 | expect(stackParts[1]).to.match(/^ at throwError \(.*errors.test.js:\d+:\d+\)$/); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/unit/model/overwriting-builtins.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | /* jshint -W110 */ 5 | var chai = require('chai') 6 | , expect = chai.expect 7 | , Support = require(__dirname + '/../../support') 8 | , DataTypes = require(__dirname + '/../../../lib/data-types'); 9 | 10 | describe(Support.getTestDialectTeaser('Model'), function() { 11 | 12 | describe('not breaking built-ins', function() { 13 | it('it should not break instance.set by defining a model set attribute', function() { 14 | var User = this.sequelize.define('OverWrittenKeys', { 15 | set:DataTypes.STRING 16 | }); 17 | 18 | var user = User.build({set: 'A'}); 19 | expect(user.get('set')).to.equal('A'); 20 | user.set('set', 'B'); 21 | expect(user.get('set')).to.equal('B'); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /lib/query-types.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @namespace QueryTypes 5 | * @memberof Sequelize 6 | * 7 | * 8 | * @property SELECT 9 | * @property INSERT 10 | * @property UPDATE 11 | * @property BULKUPDATE 12 | * @property BULKDELETE 13 | * @property DELETE 14 | * @property UPSERT 15 | * @property VERSION 16 | * @property SHOWTABLES 17 | * @property SHOWINDEXES 18 | * @property DESCRIBE 19 | * @property RAW 20 | * @property FOREIGNKEYS 21 | */ 22 | module.exports = { 23 | SELECT: 'SELECT', 24 | INSERT: 'INSERT', 25 | UPDATE: 'UPDATE', 26 | BULKUPDATE: 'BULKUPDATE', 27 | BULKDELETE: 'BULKDELETE', 28 | DELETE: 'DELETE', 29 | UPSERT: 'UPSERT', 30 | VERSION: 'VERSION', 31 | SHOWTABLES: 'SHOWTABLES', 32 | SHOWINDEXES: 'SHOWINDEXES', 33 | DESCRIBE: 'DESCRIBE', 34 | RAW: 'RAW', 35 | FOREIGNKEYS: 'FOREIGNKEYS' 36 | }; 37 | -------------------------------------------------------------------------------- /test/integration/support.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Support = require('../support') 4 | , dialect = Support.getTestDialect(); 5 | 6 | before(function() { 7 | if (dialect !== 'postgres' && dialect !== 'postgres-native') { 8 | return; 9 | } 10 | return Support.sequelize.Promise.all([ 11 | Support.sequelize.query('CREATE EXTENSION IF NOT EXISTS hstore', {raw: true}), 12 | Support.sequelize.query('CREATE EXTENSION IF NOT EXISTS btree_gist', {raw: true}) 13 | ]); 14 | }); 15 | 16 | beforeEach(function() { 17 | this.sequelize.test.trackRunningQueries(); 18 | return Support.clearDatabase(this.sequelize); 19 | }); 20 | 21 | afterEach(function() { 22 | try { 23 | this.sequelize.test.verifyNoRunningQueries(); 24 | } catch (err) { 25 | err.message += ' in '+this.currentTest.fullTitle(); 26 | throw err; 27 | } 28 | }); 29 | 30 | module.exports = Support; 31 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 |
2 | Sequelize | The Node.js ORM 3 | Sequelize 4 |
5 | 6 | Sequelize is a promise-based ORM for Node.js. It supports the dialects PostgreSQL, MySQL, SQLite and MSSQL and features solid transaction support, relations, read replication and 7 | more. 8 | 9 | [Installation](docs/getting-started/) 10 | 11 | ## Example usage 12 | 13 | ```js 14 | var Sequelize = require('sequelize'); 15 | var sequelize = new Sequelize('database', 'username', 'password'); 16 | 17 | var User = sequelize.define('user', { 18 | username: Sequelize.STRING, 19 | birthday: Sequelize.DATE 20 | }); 21 | 22 | sequelize.sync().then(function() { 23 | return User.create({ 24 | username: 'janedoe', 25 | birthday: new Date(1980, 6, 20) 26 | }); 27 | }).then(function(jane) { 28 | console.log(jane.get({ 29 | plain: true 30 | })); 31 | }); 32 | ``` 33 | -------------------------------------------------------------------------------- /test/unit/model/removeAttribute.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | var chai = require('chai') 5 | , expect = chai.expect 6 | , Support = require(__dirname + '/../support') 7 | , current = Support.sequelize 8 | , _ = require('lodash') 9 | , DataTypes = require(__dirname + '/../../../lib/data-types'); 10 | 11 | describe(Support.getTestDialectTeaser('Model'), function() { 12 | describe('removeAttribute', function () { 13 | it('should support removing the primary key', function () { 14 | var Model = current.define('m', { 15 | name: DataTypes.STRING 16 | }); 17 | 18 | expect(Model.primaryKeyAttribute).not.to.be.undefined; 19 | expect(_.size(Model.primaryKeys)).to.equal(1); 20 | 21 | Model.removeAttribute('id'); 22 | 23 | expect(Model.primaryKeyAttribute).to.be.undefined; 24 | expect(_.size(Model.primaryKeys)).to.equal(0); 25 | }); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /test/unit/associations/association.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | const chai = require('chai'); 5 | const expect = chai.expect; 6 | const Support = require(__dirname + '/../support'); 7 | const current = Support.sequelize; 8 | const AssociationError = require(__dirname + '/../../../lib/errors').AssociationError; 9 | 10 | describe(Support.getTestDialectTeaser('belongsTo'), function() { 11 | it('should throw an AssociationError when two associations have the same alias', function() { 12 | const User = current.define('User'); 13 | const Task = current.define('Task'); 14 | 15 | User.belongsTo(Task, { as: 'task' }); 16 | const errorFunction = User.belongsTo.bind(User, Task, { as: 'task' }); 17 | const errorMessage = 'You have used the alias task in two separate associations. Aliased associations must have unique aliases.'; 18 | expect(errorFunction).to.throw(AssociationError, errorMessage); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/unit/model/helpers.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | const chai = require('chai'); 5 | const expect = chai.expect; 6 | const Support = require(__dirname + '/../support'); 7 | const current = Support.sequelize; 8 | 9 | describe(Support.getTestDialectTeaser('Model'), function() { 10 | describe('hasAlias', function () { 11 | beforeEach(function() { 12 | this.User = current.define('user'); 13 | this.Task = current.define('task'); 14 | }); 15 | 16 | it('returns true if a model has an association with the specified alias', function() { 17 | this.Task.belongsTo(this.User, { as: 'owner'}); 18 | expect(this.Task.hasAlias('owner')).to.equal(true); 19 | }); 20 | 21 | it('returns false if a model does not have an association with the specified alias', function() { 22 | this.Task.belongsTo(this.User, { as: 'owner'}); 23 | expect(this.Task.hasAlias('notOwner')).to.equal(false); 24 | }); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /test/unit/sql/remove-column.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Support = require(__dirname + '/../support') 4 | , expectsql = Support.expectsql 5 | , current = Support.sequelize 6 | , sql = current.dialect.QueryGenerator; 7 | 8 | // Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation 9 | 10 | if (current.dialect.name !== 'sqlite') { 11 | suite(Support.getTestDialectTeaser('SQL'), function() { 12 | suite('removeColumn', function () { 13 | test('schema', function () { 14 | expectsql(sql.removeColumnQuery({ 15 | schema: 'archive', 16 | tableName: 'user' 17 | }, 'email'), { 18 | mssql: 'ALTER TABLE [archive].[user] DROP COLUMN [email];', 19 | mysql: 'ALTER TABLE `archive.user` DROP `email`;', 20 | postgres: 'ALTER TABLE "archive"."user" DROP COLUMN "email";', 21 | }); 22 | }); 23 | }); 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise":false, 3 | "boss":true, 4 | "curly":false, 5 | "eqeqeq":true, 6 | "freeze":true, 7 | "immed":true, // deprecated 8 | "indent":2, // deprecated 9 | "latedef":"true", 10 | "newcap":true, // deprecated 11 | "noarg":true, 12 | "node":true, 13 | "strict":true, 14 | "undef":true, 15 | "esnext":false, 16 | "unused": "vars", 17 | "nonbsp": true, 18 | "maxdepth": 8, 19 | "esversion": 6, 20 | "quotmark": true, // deprecated 21 | "-W041": false, 22 | 23 | /* relaxing options */ 24 | "laxbreak":true, 25 | "laxcomma":true, 26 | 27 | /* questionable */ 28 | "loopfunc":true, 29 | 30 | "esversion": 6, 31 | "globals": { 32 | "Promise": true 33 | }, 34 | 35 | "predef": [ 36 | "alert", 37 | "describe", 38 | "it", 39 | "before", 40 | "beforeEach", 41 | "after", 42 | "afterEach", 43 | "suite", 44 | "setup", 45 | "teardown", 46 | "suiteSetup", 47 | "suiteTeardown", 48 | "test" 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /test/unit/instance/get.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | var chai = require('chai') 5 | , sinon = require('sinon') 6 | , expect = chai.expect 7 | , Support = require(__dirname + '/../support') 8 | , DataTypes = require(__dirname + '/../../../lib/data-types') 9 | , current = Support.sequelize; 10 | 11 | describe(Support.getTestDialectTeaser('Instance'), function() { 12 | describe('get', function () { 13 | beforeEach(function () { 14 | this.getSpy = sinon.spy(); 15 | this.User = current.define('User', { 16 | name: { 17 | type: DataTypes.STRING, 18 | get: this.getSpy 19 | } 20 | }); 21 | }); 22 | 23 | it('invokes getter if raw: false', function () { 24 | this.User.build().get('name'); 25 | 26 | expect(this.getSpy).to.have.been.called; 27 | }); 28 | 29 | it('does not invoke getter if raw: true', function () { 30 | expect(this.getSpy, { raw: true }).not.to.have.been.called; 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /test/integration/vectors.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | /* jshint -W110 */ 5 | var chai = require('chai') 6 | , expect = chai.expect 7 | , Sequelize = require('../../index') 8 | , Support = require(__dirname + '/support'); 9 | 10 | chai.should(); 11 | 12 | describe(Support.getTestDialectTeaser('Vectors'), function() { 13 | it('should not allow insert backslash', function () { 14 | var Student = this.sequelize.define('student', { 15 | name: Sequelize.STRING 16 | }, { 17 | tableName: 'student' 18 | }); 19 | 20 | return Student.sync({force: true}).then(function () { 21 | return Student.create({ 22 | name: 'Robert\\\'); DROP TABLE "students"; --' 23 | }).then(function(result) { 24 | expect(result.get('name')).to.equal('Robert\\\'); DROP TABLE "students"; --'); 25 | return Student.findAll(); 26 | }).then(function(result) { 27 | expect(result[0].name).to.equal('Robert\\\'); DROP TABLE "students"; --'); 28 | }); 29 | }); 30 | }); 31 | }); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2016 Sequelize contributors 2 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 3 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 4 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 5 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | _Thanks for wanting to fix something on Sequelize - we already love you long time! Please delete this text and fill in the template below. If unsure about something, just do as best as you're able._ 2 | 3 | _If your PR only contains changes to documentation, you may skip the template below._ 4 | 5 | ### Pull Request check-list 6 | 7 | _Please make sure to review and check all of these items:_ 8 | 9 | - [ ] Does `npm run test` or `npm run test-DIALECT` pass with this change (including linting)? 10 | - [ ] Does your issue contain a link to existing issue (Closes #[issue]) or a description of the issue you are solving? 11 | - [ ] Have you added new tests to prevent regressions? 12 | - [ ] Is a documentation update included (if this change modifies existing APIs, or introduces new ones)? 13 | - [ ] Have you added an entry under `Future` in the changelog? 14 | 15 | _NOTE: these things are not required to open a PR and can be done afterwards / while the PR is open._ 16 | 17 | ### Description of change 18 | 19 | _Please provide a description of the change here._ 20 | -------------------------------------------------------------------------------- /test/unit/associations/belongs-to.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | var chai = require('chai') 5 | , expect = chai.expect 6 | , Support = require(__dirname + '/../support') 7 | , current = Support.sequelize; 8 | 9 | describe(Support.getTestDialectTeaser('belongsTo'), function() { 10 | it('should not override custom methods with association mixin', function(){ 11 | const methods = { 12 | getTask : 'get', 13 | setTask: 'set', 14 | createTask: 'create' 15 | }; 16 | const User = current.define('User'); 17 | const Task = current.define('Task'); 18 | 19 | current.Utils._.each(methods, (alias, method) => { 20 | User.prototype[method] = function () { 21 | const realMethod = this.constructor.associations.task[alias]; 22 | expect(realMethod).to.be.a('function'); 23 | return realMethod; 24 | }; 25 | }); 26 | 27 | User.belongsTo(Task, { as: 'task' }); 28 | 29 | const user = User.build(); 30 | 31 | current.Utils._.each(methods, (alias, method) => { 32 | expect(user[method]()).to.be.a('function'); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /lib/utils/logger.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Sequelize module for debug and deprecation messages. 5 | * It require a `context` for which messages will be printed. 6 | * 7 | * @module logging 8 | * @private 9 | */ 10 | 11 | /* jshint -W030 */ 12 | const depd = require('depd') 13 | , debug = require('debug') 14 | , _ = require('lodash'); 15 | 16 | class Logger { 17 | constructor(config) { 18 | 19 | this.config = _.extend({ 20 | context: 'sequelize', 21 | debug: true 22 | }, config || {}); 23 | 24 | this.depd = depd(this.config.context); 25 | this.debug = debug(this.config.context); 26 | } 27 | 28 | deprecate(message) { 29 | this.depd(message); 30 | } 31 | 32 | debug(message) { 33 | this.config.debug && this.debug(message); 34 | } 35 | 36 | warn(message) { 37 | console.warn(`(${this.config.context}) Warning: ${message}`); 38 | } 39 | 40 | debugContext(childContext) { 41 | if (!childContext) { 42 | throw new Error('No context supplied to debug'); 43 | } 44 | return debug([this.config.context, childContext].join(':')); 45 | } 46 | } 47 | 48 | module.exports = Logger; 49 | -------------------------------------------------------------------------------- /test/integration/pool.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | /* jshint -W110 */ 5 | const chai = require('chai'); 6 | const expect = chai.expect; 7 | const Support = require(__dirname + '/support'); 8 | const dialect = Support.getTestDialect(); 9 | const sinon = require('sinon'); 10 | const Sequelize = Support.Sequelize; 11 | 12 | describe(Support.getTestDialectTeaser('Pooling'), function() { 13 | if (dialect === 'sqlite') return; 14 | 15 | beforeEach(() => { 16 | this.sinon = sinon.sandbox.create(); 17 | }); 18 | 19 | afterEach(() => { 20 | this.sinon.restore(); 21 | }); 22 | 23 | it('should reject when unable to acquire connection in given time', () => { 24 | this.testInstance = new Sequelize('localhost', 'ffd', 'dfdf', { 25 | dialect, 26 | databaseVersion: '1.2.3', 27 | pool: { 28 | acquire: 1000 //milliseconds 29 | } 30 | }); 31 | 32 | this.sinon.stub(this.testInstance.connectionManager, '_connect', () => new Sequelize.Promise(() => {})); 33 | 34 | return expect(this.testInstance.authenticate()) 35 | .to.eventually.be.rejectedWith('TimeoutError: ResourceRequest timed out'); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/integration/dialects/mssql/query-queue.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var chai = require('chai') 4 | , expect = chai.expect 5 | , Promise = require('../../../../lib/promise') 6 | , DataTypes = require('../../../../lib/data-types') 7 | , Support = require('../../support') 8 | , dialect = Support.getTestDialect(); 9 | 10 | if (dialect.match(/^mssql/)) { 11 | describe('[MSSQL Specific] Query Queue', function () { 12 | beforeEach(function () { 13 | var User = this.User = this.sequelize.define('User', { 14 | username: DataTypes.STRING 15 | }); 16 | 17 | return this.sequelize.sync({ force: true }).then(function () { 18 | return User.create({ username: 'John'}); 19 | }); 20 | }); 21 | 22 | it('should queue concurrent requests to a connection', function() { 23 | var User = this.User; 24 | 25 | return expect(this.sequelize.transaction(function (t) { 26 | return Promise.all([ 27 | User.findOne({ 28 | transaction: t 29 | }), 30 | User.findOne({ 31 | transaction: t 32 | }) 33 | ]); 34 | })).not.to.be.rejected; 35 | }); 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '{build}' 2 | 3 | platform: 4 | - x64 5 | 6 | services: 7 | - mssql2016 8 | 9 | shallow_clone: true 10 | 11 | cache: 12 | - node_modules 13 | 14 | environment: 15 | matrix: 16 | - {NODE_VERSION: 4, DIALECT: mssql} 17 | - {NODE_VERSION: 6, DIALECT: mssql, COVERAGE: true} 18 | 19 | install: 20 | - ps: Install-Product node $env:NODE_VERSION x64 21 | - ps: | 22 | $pkg = ConvertFrom-Json (Get-Content -Raw package.json) 23 | $pkg.devDependencies.PSObject.Properties.Remove('sqlite3') 24 | $pkg.devDependencies.PSObject.Properties.Remove('pg-native') 25 | ConvertTo-Json $pkg | Out-File package.json -Encoding UTF8 26 | - npm install 27 | 28 | build: off 29 | 30 | before_test: 31 | - ps: . .\appveyor-setup.ps1 32 | 33 | test_script: 34 | - 'IF "%COVERAGE%" == "true" (npm run cover) ELSE (npm test)' 35 | 36 | after_test: 37 | - ps: | 38 | $env:PATH = 'C:\Program Files\Git\usr\bin;' + $env:PATH 39 | if (Test-Path env:\COVERAGE) { 40 | Invoke-WebRequest -Uri 'https://codecov.io/bash' -OutFile codecov.sh 41 | bash codecov.sh -f "coverage\lcov.info" 42 | } 43 | 44 | branches: 45 | only: 46 | - master 47 | - v3 48 | -------------------------------------------------------------------------------- /test/unit/model/upsert.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | var chai = require('chai') 5 | , expect = chai.expect 6 | , Support = require(__dirname + '/../support') 7 | , current = Support.sequelize 8 | , sinon = require('sinon') 9 | , Promise = current.Promise 10 | , DataTypes = require('../../../lib/data-types'); 11 | 12 | describe(Support.getTestDialectTeaser('Model'), function() { 13 | 14 | if (current.dialect.supports.upserts) { 15 | describe('method upsert', function () { 16 | var User = current.define('User', { 17 | name: DataTypes.STRING, 18 | secretValue: { 19 | type: DataTypes.INTEGER, 20 | allowNull: false 21 | } 22 | }); 23 | 24 | before(function () { 25 | this.query = current.query; 26 | current.query = sinon.stub().returns(Promise.resolve()); 27 | }); 28 | 29 | after(function () { 30 | current.query = this.query; 31 | }); 32 | 33 | it('skip validations for missing fields', function() { 34 | return expect(User.upsert({ 35 | name: 'Grumpy Cat' 36 | })).not.to.be.rejectedWith(current.ValidationError); 37 | }); 38 | }); 39 | } 40 | }); 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | _Thanks for wanting to report an issue you've found in Sequelize. Please delete this text and fill in the template below. If unsure about something, just do as best as you're able._ 2 | 3 | _Note that it will be much easier for us to fix the issue if a test case that reproduces the problem is provided. Ideally this test case should not have any external dependencies. We understand that it is not always possible to reduce your code to a small test case, but we would appreciate to have as much data as possible. Thank you!_ 4 | 5 | _Github should only be used for feature requests and bugs - all questions belong on StackOverflow, Slack, or Google groups._ 6 | 7 | _This template is for submitting issues - if you want to submit a feature request you may skip this template_ 8 | 9 | 10 | ## What you are doing? 11 | _Post a minimal code sample that reproduces the issue, including models and associations_ 12 | 13 | ```js 14 | // code here 15 | ``` 16 | 17 | ## What do you expect to happen? 18 | _I wanted Foo!_ 19 | 20 | ## What is actually happening? 21 | _But the output was bar!_ 22 | 23 | _Output, either JSON or SQL_ 24 | 25 | 26 | __Dialect:__ mysql / postgres / sqlite / mssql / any 27 | __Database version:__ XXX 28 | __Sequelize version:__ XXX 29 | -------------------------------------------------------------------------------- /test/unit/instance/previous.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | const chai = require('chai'); 5 | const expect = chai.expect; 6 | const Support = require(__dirname + '/../support'); 7 | const DataTypes = require(__dirname + '/../../../lib/data-types'); 8 | const current = Support.sequelize; 9 | 10 | describe(Support.getTestDialectTeaser('Instance'), function () { 11 | describe('previous', function () { 12 | it('should return correct previous value', function () { 13 | const Model = current.define('Model', { 14 | text: DataTypes.STRING, 15 | textCustom: { 16 | type: DataTypes.STRING, 17 | set(val) { 18 | this.setDataValue('textCustom', val); 19 | }, 20 | get() { 21 | this.getDataValue('textCustom'); 22 | } 23 | } 24 | }); 25 | 26 | const instance = Model.build({ text: 'a', textCustom: 'abc' }); 27 | expect(instance.previous('text')).to.be.not.ok; 28 | expect(instance.previous('textCustom')).to.be.not.ok; 29 | 30 | instance.set('text', 'b'); 31 | instance.set('textCustom', 'def'); 32 | 33 | expect(instance.previous('text')).to.be.equal('a'); 34 | expect(instance.previous('textCustom')).to.be.equal('abc'); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /lib/associations/base.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const AssociationError = require('./../errors').AssociationError; 3 | 4 | class Association { 5 | constructor(source, target, options) { 6 | options = options || {}; 7 | this.source = source; 8 | this.target = target; 9 | this.options = options; 10 | this.scope = options.scope; 11 | this.isSelfAssociation = (this.source === this.target); 12 | this.as = options.as; 13 | 14 | if (source.hasAlias(options.as)) { 15 | throw new AssociationError(`You have used the alias ${options.as} in two separate associations. ` + 16 | 'Aliased associations must have unique aliases.' 17 | ); 18 | } 19 | } 20 | // Normalize input - may be array or single obj, instance or primary key - convert it to an array of built objects 21 | toInstanceArray(objs) { 22 | if (!Array.isArray(objs)) { 23 | objs = [objs]; 24 | } 25 | return objs.map(function(obj) { 26 | if (!(obj instanceof this.target)) { 27 | var tmpInstance = {}; 28 | tmpInstance[this.target.primaryKeyAttribute] = obj; 29 | return this.target.build(tmpInstance, { 30 | isNewRecord: false 31 | }); 32 | } 33 | return obj; 34 | }, this); 35 | } 36 | inspect() { 37 | return this.as; 38 | } 39 | } 40 | 41 | module.exports = Association; -------------------------------------------------------------------------------- /test/unit/instance/destroy.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var chai = require('chai') 4 | , expect = chai.expect 5 | , Support = require(__dirname + '/../support') 6 | , current = Support.sequelize 7 | , Sequelize = Support.Sequelize 8 | , sinon = require('sinon'); 9 | 10 | describe(Support.getTestDialectTeaser('Instance'), function() { 11 | describe('destroy', function () { 12 | describe('options tests', function() { 13 | var stub 14 | , Model = current.define('User', { 15 | id: { 16 | type: Sequelize.BIGINT, 17 | primaryKey: true, 18 | autoIncrement: true, 19 | } 20 | }) 21 | , instance; 22 | 23 | before(function() { 24 | stub = sinon.stub(current, 'query').returns( 25 | Sequelize.Promise.resolve({ 26 | _previousDataValues: {}, 27 | dataValues: {id: 1} 28 | }) 29 | ); 30 | }); 31 | 32 | after(function() { 33 | stub.restore(); 34 | }); 35 | 36 | it('should allow destroies even if options are not given', function () { 37 | instance = Model.build({id: 1}, {isNewRecord: false}); 38 | expect(function () { 39 | instance.destroy(); 40 | }).to.not.throw(); 41 | }); 42 | }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /test/integration/replication.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | /* jshint -W110 */ 5 | const chai = require('chai'); 6 | const expect = chai.expect; 7 | const Support = require(__dirname + '/support'); 8 | const DataTypes = require(__dirname + '/../../lib/data-types'); 9 | const dialect = Support.getTestDialect(); 10 | 11 | describe(Support.getTestDialectTeaser('Replication'), function() { 12 | if (dialect === 'sqlite') return; 13 | 14 | beforeEach(() => { 15 | this.sequelize = Support.getSequelizeInstance(null, null, null, { 16 | replication: { 17 | write: Support.getConnectionOptions(), 18 | read: [Support.getConnectionOptions()] 19 | } 20 | }); 21 | 22 | expect(this.sequelize.connectionManager.pool.write).to.be.ok; 23 | expect(this.sequelize.connectionManager.pool.read).to.be.ok; 24 | 25 | this.User = this.sequelize.define('User', { 26 | firstName: { 27 | type: DataTypes.STRING, 28 | field: 'first_name' 29 | } 30 | }); 31 | 32 | return this.User.sync({force: true}); 33 | }); 34 | 35 | it('should be able to make a write', () => { 36 | return this.User.create({ 37 | firstName: Math.random().toString() 38 | }); 39 | }); 40 | 41 | it('should be able to make a read', () => { 42 | return this.User.findAll(); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /test/unit/dialects/mssql/connection-manager.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var chai = require('chai') 4 | , expect = chai.expect 5 | , Sequelize = require(__dirname + '/../../../../index'); 6 | 7 | 8 | var tedious = require('tedious') 9 | , sinon = require('sinon') 10 | , connectionStub = sinon.stub(tedious, 'Connection'); 11 | 12 | connectionStub.returns({on: function () {}}); 13 | 14 | describe('[MSSQL] Connection Manager', function () { 15 | var instance 16 | , config; 17 | beforeEach(function () { 18 | config = { 19 | dialect: 'mssql', 20 | database: 'none', 21 | username: 'none', 22 | password: 'none', 23 | host: 'localhost', 24 | port: 2433, 25 | pool: {}, 26 | dialectOptions: { 27 | domain: 'TEST.COM' 28 | } 29 | }; 30 | instance = new Sequelize(config.database 31 | , config.username 32 | , config.password 33 | , config); 34 | }); 35 | 36 | it('connectionManager._connect() Does not delete `domain` from config.dialectOptions', 37 | function () { 38 | expect(config.dialectOptions.domain).to.equal('TEST.COM'); 39 | instance.dialect.connectionManager._connect(config); 40 | expect(config.dialectOptions.domain).to.equal('TEST.COM'); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/unit/instance/decrement.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var chai = require('chai') 4 | , expect = chai.expect 5 | , Support = require(__dirname + '/../support') 6 | , current = Support.sequelize 7 | , Sequelize = Support.Sequelize 8 | , sinon = require('sinon'); 9 | 10 | describe(Support.getTestDialectTeaser('Instance'), function() { 11 | describe('decrement', function () { 12 | describe('options tests', function() { 13 | var stub 14 | , Model = current.define('User', { 15 | id: { 16 | type: Sequelize.BIGINT, 17 | primaryKey: true, 18 | autoIncrement: true, 19 | } 20 | }) 21 | , instance; 22 | 23 | before(function() { 24 | stub = sinon.stub(current, 'query').returns( 25 | Sequelize.Promise.resolve({ 26 | _previousDataValues: {id: 3}, 27 | dataValues: {id: 1} 28 | }) 29 | ); 30 | }); 31 | 32 | after(function() { 33 | stub.restore(); 34 | }); 35 | 36 | it('should allow decrements even if options are not given', function () { 37 | instance = Model.build({id: 3}, {isNewRecord: false}); 38 | expect(function () { 39 | instance.decrement(['id']); 40 | }).to.not.throw(); 41 | }); 42 | }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /test/unit/instance/increment.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var chai = require('chai') 4 | , expect = chai.expect 5 | , Support = require(__dirname + '/../support') 6 | , current = Support.sequelize 7 | , Sequelize = Support.Sequelize 8 | , sinon = require('sinon'); 9 | 10 | describe(Support.getTestDialectTeaser('Instance'), function() { 11 | describe('increment', function () { 12 | describe('options tests', function() { 13 | var stub 14 | , Model = current.define('User', { 15 | id: { 16 | type: Sequelize.BIGINT, 17 | primaryKey: true, 18 | autoIncrement: true, 19 | } 20 | }) 21 | , instance; 22 | 23 | before(function() { 24 | stub = sinon.stub(current, 'query').returns( 25 | Sequelize.Promise.resolve({ 26 | _previousDataValues: {id: 1}, 27 | dataValues: {id: 3} 28 | }) 29 | ); 30 | }); 31 | 32 | after(function() { 33 | stub.restore(); 34 | }); 35 | 36 | it('should allow increments even if options are not given', function () { 37 | instance = Model.build({id: 1}, {isNewRecord: false}); 38 | expect(function () { 39 | instance.increment(['id']); 40 | }).to.not.throw(); 41 | }); 42 | }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | 4 | language: node_js 5 | 6 | node_js: 7 | - "4" 8 | - "6" 9 | 10 | branches: 11 | only: 12 | - master 13 | - v3 14 | 15 | env: 16 | global: 17 | # mysql info 18 | - SEQ_MYSQL_DB: sequelize_test 19 | - SEQ_MYSQL_USER: sequelize_test 20 | - SEQ_MYSQL_PW: sequelize_test 21 | - SEQ_MYSQL_HOST: 127.0.0.1 22 | - SEQ_MYSQL_PORT: 8999 23 | # postgres info 24 | - SEQ_PG_DB: sequelize_test 25 | - SEQ_PG_USER: sequelize_test 26 | - SEQ_PG_PW: sequelize_test 27 | - SEQ_PG_HOST: 127.0.0.1 28 | - SEQ_PG_PORT: 8998 29 | 30 | matrix: 31 | - DIALECT=sqlite COVERAGE=true 32 | - MYSQL_VER=mysql-57 DIALECT=mysql COVERAGE=true 33 | - POSTGRES_VER=postgres-95 DIALECT=postgres COVERAGE=true 34 | - POSTGRES_VER=postgres-95 DIALECT=postgres-native COVERAGE=true 35 | 36 | before_script: 37 | - "if [ $POSTGRES_VER ] || [ $MYSQL_VER ]; then docker-compose up -d ${POSTGRES_VER} ${MYSQL_VER}; fi" 38 | - "if [ $MYSQL_VER ]; then docker run --link ${MYSQL_VER}:db -e CHECK_PORT=3306 -e CHECK_HOST=db --net sequelize_default giorgos/takis; fi" 39 | - "if [ $POSTGRES_VER ]; then docker run --link ${POSTGRES_VER}:db -e CHECK_PORT=5432 -e CHECK_HOST=db --net sequelize_default giorgos/takis; fi" 40 | 41 | script: 42 | - "if [ $COVERAGE ]; then npm run cover && bash <(curl -s https://codecov.io/bash) -f coverage/lcov.info; else npm run test; fi" 43 | -------------------------------------------------------------------------------- /test/unit/instance/reload.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var chai = require('chai') 4 | , expect = chai.expect 5 | , Support = require(__dirname + '/../support') 6 | , current = Support.sequelize 7 | , Sequelize = Support.Sequelize 8 | , sinon = require('sinon'); 9 | 10 | describe(Support.getTestDialectTeaser('Instance'), function() { 11 | describe('reload', function () { 12 | describe('options tests', function() { 13 | var stub 14 | , Model = current.define('User', { 15 | id: { 16 | type: Sequelize.BIGINT, 17 | primaryKey: true, 18 | autoIncrement: true, 19 | }, 20 | deletedAt: { 21 | type: Sequelize.DATE, 22 | } 23 | }, { 24 | paranoid: true 25 | }) 26 | , instance; 27 | 28 | before(function() { 29 | stub = sinon.stub(current, 'query').returns( 30 | Sequelize.Promise.resolve({ 31 | _previousDataValues: {id: 1}, 32 | dataValues: {id: 2} 33 | }) 34 | ); 35 | }); 36 | 37 | after(function() { 38 | stub.restore(); 39 | }); 40 | 41 | it('should allow reloads even if options are not given', function () { 42 | instance = Model.build({id: 1}, {isNewRecord: false}); 43 | expect(function () { 44 | instance.reload(); 45 | }).to.not.throw(); 46 | }); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/unit/instance/restore.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var chai = require('chai') 4 | , expect = chai.expect 5 | , Support = require(__dirname + '/../support') 6 | , current = Support.sequelize 7 | , Sequelize = Support.Sequelize 8 | , sinon = require('sinon'); 9 | 10 | describe(Support.getTestDialectTeaser('Instance'), function() { 11 | describe('restore', function () { 12 | describe('options tests', function() { 13 | var stub 14 | , Model = current.define('User', { 15 | id: { 16 | type: Sequelize.BIGINT, 17 | primaryKey: true, 18 | autoIncrement: true, 19 | }, 20 | deletedAt: { 21 | type: Sequelize.DATE, 22 | } 23 | }, { 24 | paranoid: true 25 | }) 26 | , instance; 27 | 28 | before(function() { 29 | stub = sinon.stub(current, 'query').returns( 30 | Sequelize.Promise.resolve([{ 31 | _previousDataValues: {id: 1}, 32 | dataValues: {id: 2} 33 | }, 1]) 34 | ); 35 | }); 36 | 37 | after(function() { 38 | stub.restore(); 39 | }); 40 | 41 | it('should allow restores even if options are not given', function () { 42 | instance = Model.build({id: 1}, {isNewRecord: false}); 43 | expect(function () { 44 | instance.restore(); 45 | }).to.not.throw(); 46 | }); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/integration/dialects/postgres/connection-manager.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | var chai = require('chai') 5 | , expect = chai.expect 6 | , Support = require(__dirname + '/../../support') 7 | , dialect = Support.getTestDialect() 8 | , DataTypes = require(__dirname + '/../../../../lib/data-types') 9 | , _ = require('lodash'); 10 | 11 | if (dialect.match(/^postgres/)) { 12 | describe('[POSTGRES] Sequelize', function() { 13 | function checkTimezoneParsing(baseOptions) { 14 | var options = _.extend({}, baseOptions, { timezone: 'Asia/Kolkata', timestamps: true }); 15 | var sequelize = Support.createSequelizeInstance(options); 16 | 17 | var tzTable = sequelize.define('tz_table', { foo: DataTypes.STRING }); 18 | return tzTable.sync({force: true}).then(function() { 19 | return tzTable.create({foo: 'test'}).then(function(row) { 20 | expect(row).to.be.not.null; 21 | }); 22 | }); 23 | } 24 | 25 | it('should correctly parse the moment based timezone', function() { 26 | return checkTimezoneParsing(this.sequelize.options); 27 | }); 28 | 29 | it('should correctly parse the moment based timezone while fetching hstore oids', function() { 30 | // reset oids so we need to refetch them 31 | DataTypes.HSTORE.types.postgres.oids = []; 32 | DataTypes.HSTORE.types.postgres.array_oids = []; 33 | return checkTimezoneParsing(this.sequelize.options); 34 | }); 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /test/unit/model/bulkcreate.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030, -W110 */ 4 | var chai = require('chai') 5 | , expect = chai.expect 6 | , sinon = require('sinon') 7 | , Support = require(__dirname + '/../support') 8 | , DataTypes = require('../../../lib/data-types') 9 | , current = Support.sequelize 10 | , Promise = current.Promise; 11 | 12 | describe(Support.getTestDialectTeaser('Model'), function() { 13 | describe('bulkCreate', function () { 14 | var Model = current.define('model', { 15 | accountId: { 16 | type: DataTypes.INTEGER(11).UNSIGNED, 17 | allowNull: false, 18 | field: 'account_id' 19 | } 20 | }, { timestamps: false }); 21 | 22 | before(function () { 23 | 24 | this.stub = sinon.stub(current.getQueryInterface(), 'bulkInsert', function () { 25 | return Promise.resolve([]); 26 | }); 27 | }); 28 | 29 | beforeEach(function () { 30 | this.stub.reset(); 31 | }); 32 | 33 | after(function () { 34 | this.stub.restore(); 35 | }); 36 | 37 | describe('validations', function () { 38 | it('should not fail for renamed fields', function () { 39 | return Model.bulkCreate([ 40 | { accountId: 42 } 41 | ], { validate: true }).bind(this).then(function () { 42 | expect(this.stub.getCall(0).args[1]).to.deep.equal([ 43 | { account_id: 42, id: null } 44 | ]); 45 | }); 46 | }); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/unit/model/destroy.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | var chai = require('chai') 5 | , expect = chai.expect 6 | , Support = require(__dirname + '/../support') 7 | , current = Support.sequelize 8 | , sinon = require('sinon') 9 | , Promise = current.Promise 10 | , DataTypes = require('../../../lib/data-types') 11 | , _ = require('lodash'); 12 | 13 | describe(Support.getTestDialectTeaser('Model'), function() { 14 | 15 | describe('method destroy', function () { 16 | var User = current.define('User', { 17 | name: DataTypes.STRING, 18 | secretValue: DataTypes.INTEGER 19 | }); 20 | 21 | before(function () { 22 | this.stubDelete = sinon.stub(current.getQueryInterface(), 'bulkDelete', function () { 23 | return Promise.resolve([]); 24 | }); 25 | }); 26 | 27 | beforeEach(function () { 28 | this.deloptions = {where: {secretValue: '1'}}; 29 | this.cloneOptions = _.clone(this.deloptions); 30 | this.stubDelete.reset(); 31 | }); 32 | 33 | afterEach(function () { 34 | delete this.deloptions; 35 | delete this.cloneOptions; 36 | }); 37 | 38 | after(function () { 39 | this.stubDelete.restore(); 40 | }); 41 | 42 | it('can detect complexe objects', function() { 43 | var Where = function () { this.secretValue = '1'; }; 44 | 45 | expect(function () { 46 | User.destroy({where: new Where()}); 47 | }).to.throw(); 48 | 49 | }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "off", 4 | "no-extra-parens": "warn", 5 | "valid-jsdoc": "off", 6 | "new-cap": ["warn", {"properties": false}], 7 | "comma-spacing": ["error", {"before": false, "after": true}], 8 | "no-extra-boolean-cast": "warn", 9 | "strict": ["warn", "global"], 10 | "no-var": "error", 11 | "prefer-const": "error", 12 | "semi": ["error", "always"], 13 | "space-before-function-paren": ["warn", "never"], 14 | "prefer-arrow-callback": "error", 15 | "comma-style": ["warn", "last"], 16 | "no-bitwise": "off", 17 | "no-cond-assign": ["error", "except-parens"], 18 | "curly": "off", 19 | "eqeqeq": "error", 20 | "no-extend-native": "error", 21 | "wrap-iife": ["error", "any"], 22 | "indent": ["error", 2, {"SwitchCase": 1}], 23 | "no-use-before-define": "off", 24 | "no-caller": "error", 25 | "no-undef": "error", 26 | "no-unused-vars": "error", 27 | "no-irregular-whitespace": "error", 28 | "max-depth": ["error", 8], 29 | "quotes": ["error", "single", {"avoidEscape": true}], 30 | "linebreak-style": "error", 31 | "no-loop-func": "warn", 32 | "object-shorthand": "error", 33 | "one-var-declaration-per-line": "warn", 34 | "comma-dangle": "warn", 35 | "no-shadow": "warn", 36 | "camelcase": "warn" 37 | }, 38 | "parserOptions": { 39 | "ecmaVersion": 6, 40 | "sourceType": "script" 41 | }, 42 | "env": { 43 | "node": true, 44 | "mocha": true, 45 | "es6": true 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/integration/schema.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | var chai = require('chai') 5 | , expect = chai.expect 6 | , Support = require(__dirname + '/support') 7 | , DataTypes = require(__dirname + '/../../lib/data-types'); 8 | 9 | describe(Support.getTestDialectTeaser('Schema'), function() { 10 | beforeEach(function() { 11 | return this.sequelize.createSchema('testschema'); 12 | }); 13 | 14 | afterEach(function() { 15 | return this.sequelize.dropSchema('testschema'); 16 | }); 17 | 18 | beforeEach(function() { 19 | this.User = this.sequelize.define('User', { 20 | aNumber: { type: DataTypes.INTEGER } 21 | }, { 22 | schema: 'testschema' 23 | }); 24 | 25 | return this.User.sync({ force: true }); 26 | }); 27 | 28 | it('supports increment', function() { 29 | return this.User.create({ aNumber: 1 }).then(function(user) { 30 | return user.increment('aNumber', { by: 3 }); 31 | }).then(function(result) { 32 | return result.reload(); 33 | }).then(function(user) { 34 | expect(user).to.be.ok; 35 | expect(user.aNumber).to.be.equal(4); 36 | }); 37 | }); 38 | 39 | it('supports decrement', function() { 40 | return this.User.create({ aNumber: 10 }).then(function(user) { 41 | return user.decrement('aNumber', { by: 3 }); 42 | }).then(function(result) { 43 | return result.reload(); 44 | }).then(function(user) { 45 | expect(user).to.be.ok; 46 | expect(user.aNumber).to.be.equal(7); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /docs/docs/legacy.md: -------------------------------------------------------------------------------- 1 | While out of the box Sequelize will seem a bit opinionated it's trivial to both legacy and forward proof your application by defining (otherwise generated) table and field names. 2 | 3 | ## Tables 4 | ```js 5 | sequelize.define('user', { 6 | 7 | }, { 8 | tableName: 'users' 9 | }); 10 | ``` 11 | 12 | ## Fields 13 | ```js 14 | sequelize.define('modelName', { 15 | userId: { 16 | type: Sequelize.INTEGER, 17 | field: 'user_id' 18 | } 19 | }); 20 | ``` 21 | 22 | ## Primary keys 23 | Sequelize will assume your table has a `id` primary key property by default. 24 | 25 | To define your own primary key: 26 | 27 | ```js 28 | sequelize.define('collection', { 29 | uid: { 30 | type: Sequelize.INTEGER, 31 | primaryKey: true, 32 | autoIncrement: true // Automatically gets converted to SERIAL for postgres 33 | } 34 | }); 35 | 36 | sequelize.define('collection', { 37 | uuid: { 38 | type: Sequelize.UUID, 39 | primaryKey: true 40 | } 41 | }); 42 | ``` 43 | 44 | And if your model has no primary key at all you can use `Model.removeAttribute('id');` 45 | 46 | ## Foreign keys 47 | ```js 48 | // 1:1 49 | Organization.belongsTo(User, {foreignKey: 'owner_id'}); 50 | User.hasOne(Organization, {foreignKey: 'owner_id'}); 51 | 52 | // 1:M 53 | Project.hasMany(Task, {foreignkey: 'tasks_pk'}); 54 | Task.belongsTo(Project, {foreignKey: 'tasks_pk'}); 55 | 56 | // N:M 57 | User.hasMany(Role, {through: 'user_has_roles', foreignKey: 'user_role_user_id'}); 58 | Role.hasMany(User, {through: 'user_has_roles', foreignKey: 'roles_identifier'}); 59 | ``` 60 | -------------------------------------------------------------------------------- /lib/dialects/mysql/query-interface.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | Returns an object that treats MySQL's inabilities to do certain queries. 5 | 6 | @class QueryInterface 7 | @static 8 | @private 9 | */ 10 | 11 | const _ = require('lodash'); 12 | 13 | /** 14 | A wrapper that fixes MySQL's inability to cleanly remove columns from existing tables if they have a foreign key constraint. 15 | 16 | @method removeColumn 17 | @for QueryInterface 18 | 19 | @param {String} tableName The name of the table. 20 | @param {String} columnName The name of the attribute that we want to remove. 21 | @param {Object} options 22 | @private 23 | */ 24 | function removeColumn(tableName, columnName, options) { 25 | options = options || {}; 26 | 27 | /* jshint validthis:true */ 28 | return this.sequelize.query( 29 | this.QueryGenerator.getForeignKeyQuery(tableName, columnName), 30 | _.assign({ raw: true }, options) 31 | ) 32 | .spread(results => { 33 | //Exclude primary key constraint 34 | if (!results.length || results[0].constraint_name === 'PRIMARY') { 35 | // No foreign key constraints found, so we can remove the column 36 | return; 37 | } 38 | return this.sequelize.query( 39 | this.QueryGenerator.dropForeignKeyQuery(tableName, results[0].constraint_name), 40 | _.assign({ raw: true }, options) 41 | ); 42 | }) 43 | .then(() => this.sequelize.query( 44 | this.QueryGenerator.removeColumnQuery(tableName, columnName), 45 | _.assign({ raw: true }, options) 46 | )); 47 | } 48 | exports.removeColumn = removeColumn; 49 | -------------------------------------------------------------------------------- /test/unit/associations/has-one.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | var chai = require('chai') 5 | , expect = chai.expect 6 | , Support = require(__dirname + '/../support') 7 | , DataTypes = require(__dirname + '/../../../lib/data-types') 8 | , current = Support.sequelize; 9 | 10 | describe(Support.getTestDialectTeaser('hasOne'), function() { 11 | it('properly use the `as` key to generate foreign key name', function(){ 12 | var User = current.define('User', { username: DataTypes.STRING }) 13 | , Task = current.define('Task', { title: DataTypes.STRING }); 14 | 15 | User.hasOne(Task); 16 | expect(Task.attributes.UserId).not.to.be.empty; 17 | 18 | User.hasOne(Task, {as : 'Shabda'}); 19 | expect(Task.attributes.ShabdaId).not.to.be.empty; 20 | }); 21 | 22 | it('should not override custom methods with association mixin', function(){ 23 | const methods = { 24 | getTask : 'get', 25 | setTask: 'set', 26 | createTask: 'create' 27 | }; 28 | const User = current.define('User'); 29 | const Task = current.define('Task'); 30 | 31 | current.Utils._.each(methods, (alias, method) => { 32 | User.prototype[method] = function () { 33 | const realMethod = this.constructor.associations.task[alias]; 34 | expect(realMethod).to.be.a('function'); 35 | return realMethod; 36 | }; 37 | }); 38 | 39 | User.hasOne(Task, { as: 'task' }); 40 | 41 | const user = User.build(); 42 | 43 | current.Utils._.each(methods, (alias, method) => { 44 | expect(user[method]()).to.be.a('function'); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /appveyor-setup.ps1: -------------------------------------------------------------------------------- 1 | 2 | Set-Service sqlbrowser -StartupType auto 3 | Start-Service sqlbrowser 4 | 5 | [reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | Out-Null 6 | [reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.SqlWmiManagement") | Out-Null 7 | 8 | $wmi = New-Object('Microsoft.SqlServer.Management.Smo.Wmi.ManagedComputer') 9 | $tcp = $wmi.GetSmoObject("ManagedComputer[@Name='${env:computername}']/ServerInstance[@Name='SQL2016']/ServerProtocol[@Name='Tcp']") 10 | $tcp.IsEnabled = $true 11 | $tcp.Alter() 12 | 13 | $wmi = New-Object('Microsoft.SqlServer.Management.Smo.Wmi.ManagedComputer') 14 | $ipall = $wmi.GetSmoObject("ManagedComputer[@Name='${env:computername}']/ServerInstance[@Name='SQL2016']/ServerProtocol[@Name='Tcp']/IPAddress[@Name='IPAll']") 15 | $port = $ipall.IPAddressProperties.Item("TcpDynamicPorts").Value 16 | 17 | $config = @{ 18 | instanceName = "SQL2016" 19 | host = "localhost" 20 | username = "sa" 21 | password = "Password12!" 22 | port = $port 23 | database = "sequelize_test" 24 | dialectOptions = @{ 25 | requestTimeout = 25000 26 | cryptoCredentialsDetails = @{ 27 | ciphers = "RC4-MD5" 28 | } 29 | } 30 | pool = @{ 31 | max = 5 32 | idle = 3000 33 | } 34 | } 35 | 36 | $json = $config | ConvertTo-Json -Depth 3 37 | 38 | # Create sequelize_test database 39 | sqlcmd -S "(local)" -U "sa" -P "Password12!" -d "master" -Q "CREATE DATABASE [sequelize_test]; ALTER DATABASE [sequelize_test] SET READ_COMMITTED_SNAPSHOT ON;" 40 | 41 | # cannot use Out-File because it outputs a BOM 42 | [IO.File]::WriteAllLines((Join-Path $pwd "test\config\mssql.json"), $json) 43 | -------------------------------------------------------------------------------- /test/unit/instance/save.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var chai = require('chai') 4 | , expect = chai.expect 5 | , Support = require(__dirname + '/../support') 6 | , current = Support.sequelize 7 | , Sequelize = Support.Sequelize 8 | , sinon = require('sinon'); 9 | 10 | describe(Support.getTestDialectTeaser('Instance'), function() { 11 | describe('save', function () { 12 | it('should disallow saves if no primary key values is present', function () { 13 | var Model = current.define('User', { 14 | 15 | }) 16 | , instance; 17 | 18 | instance = Model.build({}, {isNewRecord: false}); 19 | 20 | expect(function () { 21 | instance.save(); 22 | }).to.throw(); 23 | }); 24 | 25 | describe('options tests', function() { 26 | var stub 27 | , Model = current.define('User', { 28 | id: { 29 | type: Sequelize.BIGINT, 30 | primaryKey: true, 31 | autoIncrement: true, 32 | } 33 | }) 34 | , instance; 35 | 36 | before(function() { 37 | stub = sinon.stub(current, 'query').returns( 38 | Sequelize.Promise.resolve([{ 39 | _previousDataValues: {}, 40 | dataValues: {id: 1} 41 | }, 1]) 42 | ); 43 | }); 44 | 45 | after(function() { 46 | stub.restore(); 47 | }); 48 | 49 | it('should allow saves even if options are not given', function () { 50 | instance = Model.build({}); 51 | expect(function () { 52 | instance.save(); 53 | }).to.not.throw(); 54 | }); 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/unit/transaction.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | const chai = require('chai'); 5 | const expect = chai.expect; 6 | const sinon = require('sinon'); 7 | const Support = require(__dirname + '/support'); 8 | const Sequelize = Support.Sequelize; 9 | const dialect = Support.getTestDialect(); 10 | const current = Support.sequelize; 11 | 12 | describe('Transaction', function() { 13 | before(() => { 14 | this.stub = sinon.stub(current, 'query').returns(Sequelize.Promise.resolve({})); 15 | 16 | this.stubConnection = sinon.stub(current.connectionManager, 'getConnection') 17 | .returns(Sequelize.Promise.resolve({ 18 | uuid: 'ssfdjd-434fd-43dfg23-2d', 19 | close(){} 20 | })); 21 | 22 | this.stubRelease = sinon.stub(current.connectionManager, 'releaseConnection') 23 | .returns(Sequelize.Promise.resolve()); 24 | }); 25 | 26 | beforeEach(() => { 27 | this.stub.reset(); 28 | this.stubConnection.reset(); 29 | this.stubRelease.reset(); 30 | }); 31 | 32 | after(() => { 33 | this.stub.restore(); 34 | this.stubConnection.restore(); 35 | }); 36 | 37 | it('should run auto commit query only when needed', () => { 38 | const expectations = { 39 | all: [ 40 | 'START TRANSACTION;' 41 | ], 42 | sqlite: [ 43 | 'BEGIN DEFERRED TRANSACTION;' 44 | ], 45 | mssql: [ 46 | 'BEGIN TRANSACTION;' 47 | ] 48 | }; 49 | return current.transaction(() => { 50 | expect(this.stub.args.map(arg => arg[0])).to.deep.equal(expectations[dialect] || expectations.all); 51 | return Sequelize.Promise.resolve(); 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /CONTRIBUTING.DOCS.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | The sequelize documentation is written in a combination of markdown (articles and example based documentation) and [JSDoc](http://usejsdoc.org) (API reference generated from source code comments). 4 | 5 | All documentation is located in the `docs` folder. 6 | 7 | The documentation is rendered using [mkdocs](http://mkdocs.org) and hosted at [Read the docs](http://sequelize.readthedocs.org). Mkdocs generates static HTML from markdown files. The files in `articles` and `docs` should be edited directly, and the files in `api` are generated from source code comments (more on that later). 8 | 9 | All pages in the documentation are defined in the `pages` section of `mkdocs.yml`. Each page is given as a separate line: 10 | ```yml 11 | - ['index.md', 'Home', 'Welcome'] 12 | ``` 13 | 14 | The first array element is the path of the markdown file, relative to the `docs` dir. The second element is the section the page should be placed in, and the third is the name of the page. 15 | 16 | To view the docs locally use `mkdocs serve`. This will start a local server at port 8000. The documentation is automatically regenerated when you edit an `.md` file. However, you'll have to restart the server if you add new pages in the configuration file. 17 | 18 | ## Articles and example based docs 19 | Write markdown, and have fun :) 20 | 21 | ## API docs 22 | The API documentation is generated from source code comments by a custom script, which outputs markdown into the `docs/api` folder. To regenerate the documentation, run: 23 | ```bash 24 | $ npm run docs 25 | ``` 26 | By default all generation will be regenerated, but you can run the generation for a single file by specifying `--file`. 27 | -------------------------------------------------------------------------------- /docs/css/custom.css: -------------------------------------------------------------------------------- 1 | @import url(http://fonts.googleapis.com/css?family=Titillium+Web); 2 | 3 | table { 4 | width:100%; 5 | } 6 | 7 | th:nth-child(1), 8 | td:nth-child(1) { 9 | width: 35%; 10 | word-break: break-all; 11 | } 12 | 13 | td:nth-child(2), 14 | td:nth-child(2) { 15 | width: 20%; 16 | word-break: break-word; 17 | } 18 | 19 | td, 20 | th { 21 | padding: 6px 13px; 22 | border: 1px solid #ddd; 23 | } 24 | tr:nth-child(2n) { 25 | background-color: #f8f8f8; 26 | } 27 | 28 | #teaser-home { 29 | height: 250px; 30 | position: relative; 31 | margin-bottom: 24px; 32 | } 33 | 34 | #teaser-home img { 35 | position: absolute; 36 | top: 0; 37 | } 38 | 39 | #teaser-home span { 40 | color: #2F406A; 41 | position: absolute; 42 | top: 0; 43 | left: 250px; 44 | font-size: 100px; 45 | display: inline-block; 46 | height: 250px; 47 | line-height: 250px; 48 | font-family: 'Titillium Web', sans-serif; 49 | } 50 | 51 | a.toctree-l3 { 52 | margin-left: 0.8em; 53 | } 54 | 55 | p > code { 56 | padding: 0; 57 | padding-top: 0.2em; 58 | padding-bottom: 0.2em; 59 | margin: 0; 60 | font-size: 85%; 61 | background-color: rgba(0,0,0,0.04); 62 | border-radius: 3px; 63 | font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; 64 | } 65 | 66 | p > code:before, p > code:after { 67 | letter-spacing: -0.2em; 68 | content: "\00a0"; 69 | } 70 | 71 | @media screen and (max-width: 768px) { 72 | #teaser-home { 73 | height: auto; 74 | text-align: center; 75 | } 76 | 77 | #teaser-home img { 78 | position: relative; 79 | width: 100%; 80 | max-width: 250px; 81 | content:url(/images/logo.png); 82 | } 83 | 84 | #teaser-home span { 85 | display: none; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /lib/dialects/mysql/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('lodash'); 4 | const AbstractDialect = require('../abstract'); 5 | const ConnectionManager = require('./connection-manager'); 6 | const Query = require('./query'); 7 | const QueryGenerator = require('./query-generator'); 8 | const DataTypes = require('../../data-types').mysql; 9 | 10 | class MysqlDialect extends AbstractDialect { 11 | constructor(sequelize) { 12 | super(); 13 | this.sequelize = sequelize; 14 | this.connectionManager = new ConnectionManager(this, sequelize); 15 | this.QueryGenerator = _.extend({}, QueryGenerator, { 16 | options: sequelize.options, 17 | _dialect: this, 18 | sequelize 19 | }); 20 | } 21 | } 22 | 23 | MysqlDialect.prototype.supports = _.merge(_.cloneDeep(AbstractDialect.prototype.supports), { 24 | 'VALUES ()': true, 25 | 'LIMIT ON UPDATE': true, 26 | 'IGNORE': ' IGNORE', 27 | lock: true, 28 | forShare: 'LOCK IN SHARE MODE', 29 | index: { 30 | collate: false, 31 | length: true, 32 | parser: true, 33 | type: true, 34 | using: 1 35 | }, 36 | ignoreDuplicates: ' IGNORE', 37 | updateOnDuplicate: true, 38 | indexViaAlter: true, 39 | NUMERIC: true, 40 | GEOMETRY: true 41 | }); 42 | 43 | ConnectionManager.prototype.defaultVersion = '5.6.0'; 44 | MysqlDialect.prototype.Query = Query; 45 | MysqlDialect.prototype.QueryGenerator = QueryGenerator; 46 | MysqlDialect.prototype.DataTypes = DataTypes; 47 | MysqlDialect.prototype.name = 'mysql'; 48 | MysqlDialect.prototype.TICK_CHAR = '`'; 49 | MysqlDialect.prototype.TICK_CHAR_LEFT = MysqlDialect.prototype.TICK_CHAR; 50 | MysqlDialect.prototype.TICK_CHAR_RIGHT = MysqlDialect.prototype.TICK_CHAR; 51 | 52 | module.exports = MysqlDialect; 53 | -------------------------------------------------------------------------------- /lib/dialects/sqlite/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('lodash'); 4 | const AbstractDialect = require('../abstract'); 5 | const ConnectionManager = require('./connection-manager'); 6 | const Query = require('./query'); 7 | const QueryGenerator = require('./query-generator'); 8 | const DataTypes = require('../../data-types').sqlite; 9 | 10 | class SqliteDialect extends AbstractDialect { 11 | constructor(sequelize) { 12 | super(); 13 | this.sequelize = sequelize; 14 | this.connectionManager = new ConnectionManager(this, sequelize); 15 | this.QueryGenerator = _.extend({}, QueryGenerator, { 16 | options: sequelize.options, 17 | _dialect: this, 18 | sequelize 19 | }); 20 | } 21 | } 22 | 23 | SqliteDialect.prototype.supports = _.merge(_.cloneDeep(AbstractDialect.prototype.supports), { 24 | 'DEFAULT': false, 25 | 'DEFAULT VALUES': true, 26 | 'UNION ALL': false, 27 | 'IGNORE': ' OR IGNORE', 28 | index: { 29 | using: false 30 | }, 31 | transactionOptions: { 32 | type: true, 33 | autocommit: false 34 | }, 35 | joinTableDependent: false, 36 | groupedLimit: false, 37 | ignoreDuplicates: ' OR IGNORE', 38 | JSON: true 39 | }); 40 | 41 | ConnectionManager.prototype.defaultVersion = '3.8.0'; 42 | SqliteDialect.prototype.Query = Query; 43 | SqliteDialect.prototype.DataTypes = DataTypes; 44 | SqliteDialect.prototype.name = 'sqlite'; 45 | SqliteDialect.prototype.TICK_CHAR = '`'; 46 | SqliteDialect.prototype.TICK_CHAR_LEFT = SqliteDialect.prototype.TICK_CHAR; 47 | SqliteDialect.prototype.TICK_CHAR_RIGHT = SqliteDialect.prototype.TICK_CHAR; 48 | 49 | module.exports = SqliteDialect; 50 | module.exports.SqliteDialect = SqliteDialect; 51 | module.exports.default = SqliteDialect; 52 | -------------------------------------------------------------------------------- /test/unit/sql/add-column.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030, -W110 */ 4 | var Support = require(__dirname + '/../support') 5 | , DataTypes = require('../../../lib/data-types') 6 | , expectsql = Support.expectsql 7 | , current = Support.sequelize 8 | , sql = current.dialect.QueryGenerator; 9 | 10 | 11 | if (current.dialect.name === 'mysql') { 12 | describe(Support.getTestDialectTeaser('SQL'), function() { 13 | describe('addColumn', function () { 14 | 15 | var Model = current.define('users', { 16 | id: { 17 | type: DataTypes.INTEGER, 18 | primaryKey: true, 19 | autoIncrement: true 20 | } 21 | }, { timestamps: false }); 22 | 23 | it('properly generate alter queries', function(){ 24 | return expectsql(sql.addColumnQuery(Model.getTableName(), 'level_id', current.normalizeAttribute({ 25 | type: DataTypes.FLOAT, 26 | allowNull: false, 27 | })), { 28 | mysql: 'ALTER TABLE `users` ADD `level_id` FLOAT NOT NULL;', 29 | }); 30 | }); 31 | 32 | it('properly generate alter queries for foreign keys', function(){ 33 | return expectsql(sql.addColumnQuery(Model.getTableName(), 'level_id', current.normalizeAttribute({ 34 | type: DataTypes.INTEGER, 35 | references: { 36 | model: 'level', 37 | key: 'id' 38 | }, 39 | onUpdate: 'cascade', 40 | onDelete: 'cascade' 41 | })), { 42 | mysql: 'ALTER TABLE `users` ADD `level_id` INTEGER, ADD CONSTRAINT `users_level_id_foreign_idx` FOREIGN KEY (`level_id`) REFERENCES `level` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;', 43 | }); 44 | }); 45 | 46 | }); 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /test/unit/model/define.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030, -W110 */ 4 | var chai = require('chai') 5 | , expect = chai.expect 6 | , Support = require(__dirname + '/../support') 7 | , DataTypes = require('../../../lib/data-types') 8 | , current = Support.sequelize; 9 | 10 | describe(Support.getTestDialectTeaser('Model'), function() { 11 | describe('define', function () { 12 | it('should allow custom timestamps with underscored: true', function () { 13 | var Model; 14 | 15 | Model = current.define('User', {}, { 16 | createdAt : 'createdAt', 17 | updatedAt : 'updatedAt', 18 | timestamps : true, 19 | underscored : true 20 | }); 21 | 22 | expect(Model.rawAttributes.createdAt).to.be.defined; 23 | expect(Model.rawAttributes.updatedAt).to.be.defined; 24 | 25 | expect(Model._timestampAttributes.createdAt).to.equal('createdAt'); 26 | expect(Model._timestampAttributes.updatedAt).to.equal('updatedAt'); 27 | 28 | expect(Model.rawAttributes.created_at).not.to.be.defined; 29 | expect(Model.rawAttributes.updated_at).not.to.be.defined; 30 | }); 31 | 32 | it('should throw when id is added but not marked as PK', function () { 33 | expect(function () { 34 | current.define('foo', { 35 | id: DataTypes.INTEGER 36 | }); 37 | }).to.throw("A column called 'id' was added to the attributes of 'foos' but not marked with 'primaryKey: true'"); 38 | 39 | expect(function () { 40 | current.define('bar', { 41 | id: { 42 | type: DataTypes.INTEGER 43 | } 44 | }); 45 | }).to.throw("A column called 'id' was added to the attributes of 'bars' but not marked with 'primaryKey: true'"); 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Sequelize | The Node.js ORM for PostgreSQL, MySQL, SQLite and MSSQL 2 | site_description: Sequelize is an ORM for Node.js. It supports the dialects PostgreSQL, MySQL, SQLite and MSSQL. 3 | repo_url: https://github.com/sequelize/sequelize 4 | site_favicon: favicon.ico 5 | site_url: http://docs.sequelizejs.com 6 | theme: readthedocs 7 | extra_css: 8 | - css/custom.css 9 | extra_javascript: 10 | - //cdnjs.cloudflare.com/ajax/libs/highlight.js/8.4/highlight.min.js 11 | pages: 12 | - Home: 'index.md' 13 | - Documentation: 14 | - 'Getting Started': 'docs/getting-started.md' 15 | - 'Working with table schemas': 'docs/schema.md' 16 | - Models: 17 | - 'Definition': 'docs/models-definition.md' 18 | - 'Usage': 'docs/models-usage.md' 19 | - 'Querying': 'docs/querying.md' 20 | - 'Scopes': 'docs/scopes.md' 21 | - 'Instances': 'docs/instances.md' 22 | - 'Relations / Associations': 'docs/associations.md' 23 | - 'Hooks': 'docs/hooks.md' 24 | - 'Transactions': 'docs/transactions.md' 25 | - 'Working with legacy tables': 'docs/legacy.md' 26 | - 'Raw queries': 'docs/raw-queries.md' 27 | - 'Migrations': 'docs/migrations.md' 28 | - API: 29 | - 'Sequelize': 'api/sequelize.md' 30 | - 'Model': 'api/model.md' 31 | - 'Instance': 'api/instance.md' 32 | - Associations: 33 | - Overview: 'api/associations/index.md' 34 | - 'BelongsTo (1:1)': 'api/associations/belongs-to.md' 35 | - 'HasOne (1:1)': 'api/associations/has-one.md' 36 | - 'HasMany (1:m)': 'api/associations/has-many.md' 37 | - 'BelongsToMany (n:m)': 'api/associations/belongs-to-many.md' 38 | - 'Hooks': 'api/hooks.md' 39 | - 'Transaction': 'api/transaction.md' 40 | - 'Datatypes': 'api/datatypes.md' 41 | - 'Deferrable': 'api/deferrable.md' 42 | - 'Errors': 'api/errors.md' 43 | - Misc: 44 | - 'Changelog': 'changelog.md' 45 | - 'Imprint': 'imprint.md' 46 | -------------------------------------------------------------------------------- /lib/dialects/abstract/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class AbstractDialect {} 4 | 5 | AbstractDialect.prototype.supports = { 6 | 'DEFAULT': true, 7 | 'DEFAULT VALUES': false, 8 | 'VALUES ()': false, 9 | 'LIMIT ON UPDATE': false, 10 | 'ON DUPLICATE KEY': true, 11 | 'ORDER NULLS': false, 12 | 'UNION': true, 13 | 'UNION ALL': true, 14 | /* What is the dialect's keyword for INSERT IGNORE */ 15 | 'IGNORE': '', 16 | 17 | /* does the dialect support returning values for inserted/updated fields */ 18 | returnValues: false, 19 | 20 | /* features specific to autoIncrement values */ 21 | autoIncrement: { 22 | /* does the dialect require modification of insert queries when inserting auto increment fields */ 23 | identityInsert: false, 24 | 25 | /* does the dialect support inserting default/null values for autoincrement fields */ 26 | defaultValue: true, 27 | 28 | /* does the dialect support updating autoincrement fields */ 29 | update: true 30 | }, 31 | /* Do we need to say DEFAULT for bulk insert */ 32 | bulkDefault: false, 33 | /* The dialect's words for INSERT IGNORE */ 34 | ignoreDuplicates: '', 35 | /* Does the dialect support ON DUPLICATE KEY UPDATE */ 36 | updateOnDuplicate: false, 37 | schemas: false, 38 | transactions: true, 39 | transactionOptions: { 40 | type: false 41 | }, 42 | migrations: true, 43 | upserts: true, 44 | constraints: { 45 | restrict: true 46 | }, 47 | index: { 48 | collate: true, 49 | length: false, 50 | parser: false, 51 | concurrently: false, 52 | type: false, 53 | using: true 54 | }, 55 | joinTableDependent: true, 56 | groupedLimit: true, 57 | indexViaAlter: false, 58 | JSON: false, 59 | deferrableConstraints: false 60 | }; 61 | 62 | module.exports = AbstractDialect; 63 | module.exports.AbstractDialect = AbstractDialect; 64 | module.exports.default = AbstractDialect; 65 | -------------------------------------------------------------------------------- /lib/dialects/mssql/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('lodash'); 4 | const AbstractDialect = require('../abstract'); 5 | const ConnectionManager = require('./connection-manager'); 6 | const Query = require('./query'); 7 | const QueryGenerator = require('./query-generator'); 8 | const DataTypes = require('../../data-types').mssql; 9 | 10 | class MssqlDialect extends AbstractDialect { 11 | constructor(sequelize) { 12 | super(); 13 | this.sequelize = sequelize; 14 | this.connectionManager = new ConnectionManager(this, sequelize); 15 | this.QueryGenerator = _.extend({}, QueryGenerator, { 16 | options: sequelize.options, 17 | _dialect: this, 18 | sequelize 19 | }); 20 | } 21 | } 22 | 23 | MssqlDialect.prototype.supports = _.merge(_.cloneDeep(AbstractDialect.prototype.supports), { 24 | 'DEFAULT': true, 25 | 'DEFAULT VALUES': true, 26 | 'LIMIT ON UPDATE': true, 27 | 'ORDER NULLS': false, 28 | lock: false, 29 | transactions: true, 30 | migrations: false, 31 | upserts: true, 32 | returnValues: { 33 | output: true 34 | }, 35 | schemas: true, 36 | autoIncrement: { 37 | identityInsert: true, 38 | defaultValue: false, 39 | update: false 40 | }, 41 | constraints: { 42 | restrict: false 43 | }, 44 | index: { 45 | collate: false, 46 | length: false, 47 | parser: false, 48 | type: true, 49 | using: false, 50 | where: true 51 | }, 52 | NUMERIC: true, 53 | tmpTableTrigger: true 54 | }); 55 | 56 | ConnectionManager.prototype.defaultVersion = '12.0.2000'; // SQL Server 2014 Express 57 | MssqlDialect.prototype.Query = Query; 58 | MssqlDialect.prototype.name = 'mssql'; 59 | MssqlDialect.prototype.TICK_CHAR = '"'; 60 | MssqlDialect.prototype.TICK_CHAR_LEFT = '['; 61 | MssqlDialect.prototype.TICK_CHAR_RIGHT = ']'; 62 | MssqlDialect.prototype.DataTypes = DataTypes; 63 | 64 | module.exports = MssqlDialect; 65 | -------------------------------------------------------------------------------- /lib/utils/parameter-validator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('lodash'); 4 | const util = require('util'); 5 | const Utils = require('../utils'); 6 | 7 | function validateDeprecation(value, expectation, options) { 8 | if (!options.deprecated) { 9 | return; 10 | } 11 | 12 | const valid = value instanceof options.deprecated || Object.prototype.toString.call(value) === Object.prototype.toString.call(options.deprecated.call()); 13 | 14 | if (valid) { 15 | const message = `${util.inspect(value)} should not be of type "${options.deprecated.name}"`; 16 | Utils.deprecate(options.deprecationWarning || message); 17 | } 18 | 19 | return valid; 20 | } 21 | 22 | function validate(value, expectation) { 23 | // the second part of this check is a workaround to deal with an issue that occurs in node-webkit when 24 | // using object literals. https://github.com/sequelize/sequelize/issues/2685 25 | if (value instanceof expectation || Object.prototype.toString.call(value) === Object.prototype.toString.call(expectation.call())) { 26 | return true; 27 | } 28 | 29 | throw new Error(`The parameter (value: ${value}) is no ${expectation.name}`); 30 | } 31 | 32 | function check(value, expectation, options) { 33 | options = _.extend({ 34 | deprecated: false, 35 | index: null, 36 | method: null, 37 | optional: false 38 | }, options || {}); 39 | 40 | if (!value && options.optional) { 41 | return true; 42 | } 43 | 44 | if (value === undefined) { 45 | throw new Error('No value has been passed.'); 46 | } 47 | 48 | if (expectation === undefined) { 49 | throw new Error('No expectation has been passed.'); 50 | } 51 | 52 | return false 53 | || validateDeprecation(value, expectation, options) 54 | || validate(value, expectation, options); 55 | } 56 | 57 | module.exports = check; 58 | module.exports.check = check; 59 | module.exports.default = check; 60 | -------------------------------------------------------------------------------- /test/unit/model/find-or-create.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | var chai = require('chai') 5 | , expect = chai.expect 6 | , Support = require(__dirname + '/../support') 7 | , current = Support.sequelize 8 | , cls = require('continuation-local-storage') 9 | , sinon = require('sinon') 10 | , stub = sinon.stub 11 | , Promise = require('bluebird'); 12 | 13 | describe(Support.getTestDialectTeaser('Model'), function() { 14 | 15 | describe('method findOrCreate', function () { 16 | 17 | before(function () { 18 | current.constructor.useCLS(cls.createNamespace('sequelize')); 19 | }); 20 | 21 | after(function () { 22 | delete current.constructor._cls; 23 | }); 24 | 25 | beforeEach(function () { 26 | this.User = current.define('User', {}, { 27 | name: 'John' 28 | }); 29 | 30 | this.transactionStub = stub(this.User.sequelize, 'transaction'); 31 | this.transactionStub.returns(new Promise(function () {})); 32 | 33 | this.clsStub = stub(current.constructor._cls, 'get'); 34 | this.clsStub.returns({ id: 123 }); 35 | }); 36 | 37 | afterEach(function () { 38 | this.transactionStub.restore(); 39 | this.clsStub.restore(); 40 | }); 41 | 42 | it('should use transaction from cls if available', function () { 43 | 44 | var options = { 45 | where : { 46 | name : 'John' 47 | } 48 | }; 49 | 50 | this.User.findOrCreate(options); 51 | 52 | expect(this.clsStub.calledOnce).to.equal(true, 'expected to ask for transaction'); 53 | }); 54 | 55 | it('should not use transaction from cls if provided as argument', function () { 56 | 57 | var options = { 58 | where : { 59 | name : 'John' 60 | }, 61 | transaction : { id : 123 } 62 | }; 63 | 64 | this.User.findOrCreate(options); 65 | 66 | expect(this.clsStub.called).to.equal(false); 67 | }); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /test/unit/dialects/mssql/resource-lock.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var ResourceLock = require('../../../../lib/dialects/mssql/resource-lock') 4 | , Promise = require('../../../../lib/promise') 5 | , assert = require('assert'); 6 | 7 | describe('[MSSQL Specific] ResourceLock', function () { 8 | it('should process requests serially', function () { 9 | var expected = {}; 10 | var lock = new ResourceLock(expected); 11 | var last = 0; 12 | 13 | function validateResource(actual) { 14 | assert.equal(actual, expected); 15 | } 16 | 17 | return Promise.all([ 18 | Promise.using(lock.lock(), function (resource) { 19 | validateResource(resource); 20 | assert.equal(last, 0); 21 | last = 1; 22 | 23 | return Promise.delay(15); 24 | }), 25 | Promise.using(lock.lock(), function (resource) { 26 | validateResource(resource); 27 | assert.equal(last, 1); 28 | last = 2; 29 | }), 30 | Promise.using(lock.lock(), function (resource) { 31 | validateResource(resource); 32 | assert.equal(last, 2); 33 | last = 3; 34 | 35 | return Promise.delay(5); 36 | }) 37 | ]); 38 | }); 39 | 40 | it('should still return resource after failure', function () { 41 | var expected = {}; 42 | var lock = new ResourceLock(expected); 43 | 44 | function validateResource(actual) { 45 | assert.equal(actual, expected); 46 | } 47 | 48 | return Promise.all([ 49 | Promise.using(lock.lock(), function (resource) { 50 | validateResource(resource); 51 | 52 | throw new Error('unexpected error'); 53 | }).catch(function () {}), 54 | Promise.using(lock.lock(), validateResource) 55 | ]); 56 | }); 57 | 58 | it('should be able to.lock resource without waiting on lock', function () { 59 | var expected = {}; 60 | var lock = new ResourceLock(expected); 61 | 62 | assert.equal(lock.unwrap(), expected); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /test/integration/model/scope.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | /* jshint -W110 */ 5 | var chai = require('chai') 6 | , Sequelize = require('../../../index') 7 | , expect = chai.expect 8 | , Support = require(__dirname + '/../support'); 9 | 10 | describe(Support.getTestDialectTeaser('Model'), function() { 11 | describe('scope', function () { 12 | beforeEach(function () { 13 | this.ScopeMe = this.sequelize.define('ScopeMe', { 14 | username: Sequelize.STRING, 15 | email: Sequelize.STRING, 16 | access_level: Sequelize.INTEGER, 17 | other_value: Sequelize.INTEGER 18 | }, { 19 | scopes: { 20 | lowAccess: { 21 | attributes: ['other_value', 'access_level'], 22 | where: { 23 | access_level: { 24 | lte: 5 25 | } 26 | } 27 | }, 28 | withName: { 29 | attributes: ['username'] 30 | } 31 | } 32 | }); 33 | 34 | return this.sequelize.sync({force: true}).then(function() { 35 | var records = [ 36 | {username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7}, 37 | {username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11}, 38 | {username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10}, 39 | {username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7} 40 | ]; 41 | return this.ScopeMe.bulkCreate(records); 42 | }.bind(this)); 43 | }); 44 | 45 | it('should be able to merge attributes as array', function () { 46 | return this.ScopeMe.scope('lowAccess','withName').findOne() 47 | .then(function(record){ 48 | expect(record.other_value).to.exist; 49 | expect(record.username).to.exist; 50 | expect(record.access_level).to.exist; 51 | }); 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /test/integration/hooks/count.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | var chai = require('chai') 5 | , expect = chai.expect 6 | , Support = require(__dirname + '/../support') 7 | , DataTypes = require(__dirname + '/../../../lib/data-types'); 8 | 9 | describe(Support.getTestDialectTeaser('Hooks'), function() { 10 | beforeEach(function() { 11 | this.User = this.sequelize.define('User', { 12 | username: { 13 | type: DataTypes.STRING, 14 | allowNull: false 15 | }, 16 | mood: { 17 | type: DataTypes.ENUM, 18 | values: ['happy', 'sad', 'neutral'] 19 | } 20 | }); 21 | return this.sequelize.sync({ force: true }); 22 | }); 23 | 24 | describe('#count', function() { 25 | beforeEach(function() { 26 | return this.User.bulkCreate([ 27 | {username: 'adam', mood: 'happy'}, 28 | {username: 'joe', mood: 'sad'}, 29 | {username: 'joe', mood: 'happy'} 30 | ]); 31 | }); 32 | 33 | describe('on success', function() { 34 | it('hook runs', function() { 35 | var beforeHook = false; 36 | 37 | this.User.beforeCount(function() { 38 | beforeHook = true; 39 | }); 40 | 41 | return this.User.count().then(function(count) { 42 | expect(count).to.equal(3); 43 | expect(beforeHook).to.be.true; 44 | }); 45 | }); 46 | 47 | it('beforeCount hook can change options', function() { 48 | this.User.beforeCount(function(options) { 49 | options.where.username = 'adam'; 50 | }); 51 | 52 | return expect(this.User.count({where: {username: 'joe'}})).to.eventually.equal(1); 53 | }); 54 | }); 55 | 56 | describe('on error', function() { 57 | it('in beforeCount hook returns error', function() { 58 | this.User.beforeCount(function() { 59 | throw new Error('Oops!'); 60 | }); 61 | 62 | return expect(this.User.count({where: {username: 'adam'}})).to.be.rejectedWith('Oops!'); 63 | }); 64 | }); 65 | }); 66 | 67 | }); 68 | -------------------------------------------------------------------------------- /lib/dialects/postgres/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('lodash'); 4 | const AbstractDialect = require('../abstract'); 5 | const ConnectionManager = require('./connection-manager'); 6 | const Query = require('./query'); 7 | const QueryGenerator = require('./query-generator'); 8 | const DataTypes = require('../../data-types').postgres; 9 | 10 | class PostgresDialect extends AbstractDialect { 11 | constructor(sequelize) { 12 | super(); 13 | this.sequelize = sequelize; 14 | this.connectionManager = new ConnectionManager(this, sequelize); 15 | this.QueryGenerator = _.extend({}, QueryGenerator, { 16 | options: sequelize.options, 17 | _dialect: this, 18 | sequelize 19 | }); 20 | } 21 | } 22 | 23 | PostgresDialect.prototype.supports = _.merge(_.cloneDeep(AbstractDialect.prototype.supports), { 24 | 'DEFAULT VALUES': true, 25 | 'EXCEPTION': true, 26 | 'ON DUPLICATE KEY': false, 27 | 'ORDER NULLS': true, 28 | returnValues: { 29 | returning: true 30 | }, 31 | bulkDefault: true, 32 | schemas: true, 33 | lock: true, 34 | lockOf: true, 35 | lockKey: true, 36 | lockOuterJoinFailure: true, 37 | forShare: 'FOR SHARE', 38 | index: { 39 | concurrently: true, 40 | using: 2, 41 | where: true 42 | }, 43 | NUMERIC: true, 44 | ARRAY: true, 45 | RANGE: true, 46 | GEOMETRY: true, 47 | GEOGRAPHY: true, 48 | JSON: true, 49 | JSONB: true, 50 | HSTORE: true, 51 | deferrableConstraints: true, 52 | searchPath : true 53 | }); 54 | 55 | ConnectionManager.prototype.defaultVersion = '9.4.0'; 56 | PostgresDialect.prototype.Query = Query; 57 | PostgresDialect.prototype.DataTypes = DataTypes; 58 | PostgresDialect.prototype.name = 'postgres'; 59 | PostgresDialect.prototype.TICK_CHAR = '"'; 60 | PostgresDialect.prototype.TICK_CHAR_LEFT = PostgresDialect.prototype.TICK_CHAR; 61 | PostgresDialect.prototype.TICK_CHAR_RIGHT = PostgresDialect.prototype.TICK_CHAR; 62 | 63 | module.exports = PostgresDialect; 64 | module.exports.default = PostgresDialect; 65 | module.exports.PostgresDialect = PostgresDialect; 66 | -------------------------------------------------------------------------------- /test/integration/model/findAll/group.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | var chai = require('chai') 5 | , expect = chai.expect 6 | , Support = require(__dirname + '/../../support') 7 | , Sequelize = Support.Sequelize 8 | , DataTypes = require(__dirname + '/../../../../lib/data-types') 9 | , current = Support.sequelize; 10 | 11 | describe(Support.getTestDialectTeaser('Model'), function() { 12 | describe('findAll', function () { 13 | describe('group', function () { 14 | it('should correctly group with attributes, #3009', function() { 15 | 16 | var Post = current.define('Post', { 17 | id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true }, 18 | name: { type: DataTypes.STRING, allowNull: false } 19 | }); 20 | 21 | var Comment = current.define('Comment', { 22 | id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true }, 23 | text: { type: DataTypes.STRING, allowNull: false } 24 | }); 25 | 26 | Post.hasMany(Comment); 27 | 28 | return current.sync({ force: true }).then(function() { 29 | // Create an enviroment 30 | return Post.bulkCreate([ 31 | { name: 'post-1' }, 32 | { name: 'post-2' } 33 | ]); 34 | }).then(function(u) { 35 | return Comment.bulkCreate([ 36 | { text: 'Market', PostId: 1}, 37 | { text: 'Text', PostId: 2}, 38 | { text: 'Abc', PostId: 2}, 39 | { text: 'Semaphor', PostId: 1}, 40 | { text: 'Text', PostId: 1}, 41 | ]); 42 | }).then(function(p) { 43 | return Post.findAll({ 44 | attributes: [ [ Sequelize.fn('COUNT', Sequelize.col('Comments.id')), 'comment_count' ] ], 45 | include: [ 46 | { model: Comment, attributes: [] } 47 | ], 48 | group: [ 'Post.id' ] 49 | }); 50 | }).then(function(posts) { 51 | expect(parseInt(posts[0].get('comment_count'))).to.be.equal(3); 52 | expect(parseInt(posts[1].get('comment_count'))).to.be.equal(2); 53 | }); 54 | }); 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/unit/model/update.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | var chai = require('chai') 5 | , expect = chai.expect 6 | , Support = require(__dirname + '/../support') 7 | , current = Support.sequelize 8 | , sinon = require('sinon') 9 | , Promise = current.Promise 10 | , DataTypes = require('../../../lib/data-types') 11 | , _ = require('lodash'); 12 | 13 | describe(Support.getTestDialectTeaser('Model'), function() { 14 | 15 | describe('method update', function () { 16 | var User = current.define('User', { 17 | name: DataTypes.STRING, 18 | secretValue: DataTypes.INTEGER 19 | }); 20 | 21 | before(function () { 22 | this.stubUpdate = sinon.stub(current.getQueryInterface(), 'bulkUpdate', function () { 23 | return Promise.resolve([]); 24 | }); 25 | }); 26 | 27 | beforeEach(function () { 28 | this.updates = { name: 'Batman', secretValue: '7' }; 29 | this.cloneUpdates = _.clone(this.updates); 30 | this.stubUpdate.reset(); 31 | }); 32 | 33 | afterEach(function () { 34 | delete this.updates; 35 | delete this.cloneUpdates; 36 | }); 37 | 38 | after(function () { 39 | this.stubUpdate.restore(); 40 | }); 41 | 42 | describe('properly clones input values', function () { 43 | it('with default options', function() { 44 | var self = this; 45 | return User.update(self.updates, {where: {secretValue: '1'}}).bind(this).then(function(e) { 46 | expect(self.updates).to.be.deep.eql(self.cloneUpdates); 47 | }); 48 | }); 49 | 50 | it('when using fields option', function() { 51 | var self = this; 52 | return User.update(self.updates, {where: {secretValue: '1'}, fields: ['name']}).bind(this).then(function() { 53 | expect(self.updates).to.be.deep.eql(self.cloneUpdates); 54 | }); 55 | }); 56 | }); 57 | 58 | it('can detect complexe objects', function() { 59 | var self = this; 60 | var Where = function () { this.secretValue = '1'; }; 61 | 62 | expect(function () { 63 | User.update(self.updates, {where:new Where()}); 64 | }).to.throw(); 65 | 66 | }); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /test/unit/associations/dont-modify-options.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | var chai = require('chai') 5 | , expect = chai.expect 6 | , Support = require(__dirname + '/../support') 7 | , DataTypes = require(__dirname + '/../../../lib/data-types') 8 | , Sequelize = require('../../../index'); 9 | 10 | describe(Support.getTestDialectTeaser('associations'), function() { 11 | describe('Test options.foreignKey', function() { 12 | beforeEach(function() { 13 | 14 | this.A = this.sequelize.define('A', { 15 | id: { 16 | type: DataTypes.CHAR(20), 17 | primaryKey: true 18 | } 19 | }); 20 | this.B = this.sequelize.define('B', { 21 | id: { 22 | type: Sequelize.CHAR(20), 23 | primaryKey: true 24 | } 25 | }); 26 | this.C = this.sequelize.define('C', {}); 27 | }); 28 | 29 | it('should not be overwritten for belongsTo', function(){ 30 | var reqValidForeignKey = { foreignKey: { allowNull: false }}; 31 | this.A.belongsTo(this.B, reqValidForeignKey); 32 | this.A.belongsTo(this.C, reqValidForeignKey); 33 | expect(this.A.attributes.CId.type).to.deep.equal(this.C.attributes.id.type); 34 | }); 35 | it('should not be overwritten for belongsToMany', function(){ 36 | var reqValidForeignKey = { foreignKey: { allowNull: false }, through: 'ABBridge'}; 37 | this.B.belongsToMany(this.A, reqValidForeignKey); 38 | this.A.belongsTo(this.C, reqValidForeignKey); 39 | expect(this.A.attributes.CId.type).to.deep.equal(this.C.attributes.id.type); 40 | }); 41 | it('should not be overwritten for hasOne', function(){ 42 | var reqValidForeignKey = { foreignKey: { allowNull: false }}; 43 | this.B.hasOne(this.A, reqValidForeignKey); 44 | this.A.belongsTo(this.C, reqValidForeignKey); 45 | expect(this.A.attributes.CId.type).to.deep.equal(this.C.attributes.id.type); 46 | }); 47 | it('should not be overwritten for hasMany', function(){ 48 | var reqValidForeignKey = { foreignKey: { allowNull: false }}; 49 | this.B.hasMany(this.A, reqValidForeignKey); 50 | this.A.belongsTo(this.C, reqValidForeignKey); 51 | expect(this.A.attributes.CId.type).to.deep.equal(this.C.attributes.id.type); 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /test/unit/sql/update.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W110 */ 4 | var Support = require(__dirname + '/../support') 5 | , DataTypes = require(__dirname + '/../../../lib/data-types') 6 | , expectsql = Support.expectsql 7 | , current = Support.sequelize 8 | , sql = current.dialect.QueryGenerator; 9 | 10 | // Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation 11 | 12 | describe(Support.getTestDialectTeaser('SQL'), function() { 13 | describe('update', function () { 14 | it('with temp table for trigger', function () { 15 | var User = Support.sequelize.define('user', { 16 | username: { 17 | type: DataTypes.STRING, 18 | field:'user_name' 19 | } 20 | },{ 21 | timestamps:false, 22 | hasTrigger:true 23 | }); 24 | 25 | var options = { 26 | returning : true, 27 | hasTrigger : true 28 | }; 29 | expectsql(sql.updateQuery(User.tableName,{user_name: 'triggertest'},{id:2},options,User.rawAttributes), 30 | { 31 | mssql: 'declare @tmp table ([id] INTEGER,[user_name] NVARCHAR(255));UPDATE [users] SET [user_name]=N\'triggertest\' OUTPUT INSERTED.[id],INSERTED.[user_name] into @tmp WHERE [id] = 2;select * from @tmp', 32 | postgres:'UPDATE "users" SET "user_name"=\'triggertest\' WHERE "id" = 2 RETURNING *', 33 | default: "UPDATE `users` SET `user_name`=\'triggertest\' WHERE `id` = 2" 34 | }); 35 | }); 36 | 37 | 38 | it('Works with limit', function () { 39 | const User = Support.sequelize.define('User', { 40 | username: { 41 | type: DataTypes.STRING 42 | }, 43 | userId: { 44 | type: DataTypes.INTEGER 45 | } 46 | }, { 47 | timestamps: false 48 | }); 49 | 50 | expectsql(sql.updateQuery(User.tableName, { username: 'new.username' }, { username: 'username' }, { limit: 1 }), { 51 | mssql: "UPDATE TOP(1) [Users] SET [username]=N'new.username' OUTPUT INSERTED.* WHERE [username] = N'username'", 52 | mysql: "UPDATE `Users` SET `username`='new.username' WHERE `username` = 'username' LIMIT 1", 53 | default: "UPDATE [Users] SET [username]='new.username' WHERE [username] = 'username'" 54 | }); 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /lib/dialects/postgres/range.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('lodash'); 4 | 5 | function stringifyRangeBound(bound) { 6 | if (bound === null) { 7 | return '' ; 8 | } else if (bound === Infinity || bound === -Infinity) { 9 | return bound.toString().toLowerCase(); 10 | } else { 11 | return JSON.stringify(bound); 12 | } 13 | } 14 | 15 | function parseRangeBound(bound, parseType) { 16 | if (!bound) { 17 | return null; 18 | } else if (bound === 'infinity') { 19 | return Infinity; 20 | } else if (bound === '-infinity') { 21 | return -Infinity; 22 | } else { 23 | return parseType(bound); 24 | } 25 | } 26 | 27 | function stringify(data) { 28 | if (data === null) return null; 29 | 30 | if (!_.isArray(data)) throw new Error('range must be an array'); 31 | if (!data.length) return 'empty'; 32 | if (data.length !== 2) throw new Error('range array length must be 0 (empty) or 2 (lower and upper bounds)'); 33 | 34 | if (data.hasOwnProperty('inclusive')) { 35 | if (data.inclusive === false) data.inclusive = [false, false]; 36 | else if (!data.inclusive) data.inclusive = [true, false]; 37 | else if (data.inclusive === true) data.inclusive = [true, true]; 38 | } else { 39 | data.inclusive = [true, false]; 40 | } 41 | 42 | _.each(data, (value, index) => { 43 | if (_.isObject(value)) { 44 | if (value.hasOwnProperty('inclusive')) data.inclusive[index] = !!value.inclusive; 45 | if (value.hasOwnProperty('value')) data[index] = value.value; 46 | } 47 | }); 48 | 49 | const lowerBound = stringifyRangeBound(data[0]); 50 | const upperBound = stringifyRangeBound(data[1]); 51 | 52 | return (data.inclusive[0] ? '[' : '(') + lowerBound + ',' + upperBound + (data.inclusive[1] ? ']' : ')'); 53 | } 54 | exports.stringify = stringify; 55 | 56 | function parse(value, parser) { 57 | if (value === null) return null; 58 | if (value === 'empty') { 59 | const empty = []; 60 | empty.inclusive = []; 61 | return empty; 62 | } 63 | 64 | let result = value 65 | .substring(1, value.length - 1) 66 | .split(',', 2); 67 | 68 | if (result.length !== 2) return value; 69 | 70 | result = result.map(value => parseRangeBound(value, parser)); 71 | 72 | result.inclusive = [(value[0] === '['), (value[value.length - 1] === ']')]; 73 | 74 | return result; 75 | } 76 | exports.parse = parse; 77 | -------------------------------------------------------------------------------- /test/integration/dialects/postgres/error.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var chai = require('chai') 4 | , expect = chai.expect 5 | , DataTypes = require(__dirname + '/../../../../lib/data-types') 6 | , Support = require(__dirname + '/../../support') 7 | , Sequelize = Support.Sequelize 8 | , dialect = Support.getTestDialect() 9 | , _ = require('lodash'); 10 | 11 | if (dialect.match(/^postgres/)) { 12 | var constraintName = 'overlap_period'; 13 | beforeEach(function () { 14 | var self = this; 15 | this.Booking = self.sequelize.define('Booking', { 16 | roomNo: DataTypes.INTEGER, 17 | period: DataTypes.RANGE(DataTypes.DATE) 18 | }); 19 | return self.Booking 20 | .sync({ force: true }) 21 | .then(function () { 22 | return self.sequelize.query('ALTER TABLE "' + self.Booking.tableName + '" ADD CONSTRAINT ' + constraintName + 23 | ' EXCLUDE USING gist ("roomNo" WITH =, period WITH &&)'); 24 | }); 25 | }); 26 | 27 | describe('[POSTGRES Specific] ExclusionConstraintError', function () { 28 | 29 | it('should contain error specific properties', function () { 30 | var errDetails = { 31 | message: 'Exclusion constraint error', 32 | constraint: 'constraint_name', 33 | fields: { 'field1': 1, 'field2': [123, 321] }, 34 | table: 'table_name', 35 | parent: new Error('Test error') 36 | }; 37 | var err = new Sequelize.ExclusionConstraintError(errDetails); 38 | 39 | _.each(errDetails, function (value, key) { 40 | expect(value).to.be.deep.equal(err[key]); 41 | }); 42 | }); 43 | 44 | it('should throw ExclusionConstraintError when "period" value overlaps existing', function () { 45 | var Booking = this.Booking; 46 | 47 | return Booking 48 | .create({ 49 | roomNo: 1, 50 | guestName: 'Incognito Visitor', 51 | period: [new Date(2015, 0, 1), new Date(2015, 0, 3)] 52 | }) 53 | .then(function () { 54 | return expect(Booking 55 | .create({ 56 | roomNo: 1, 57 | guestName: 'Frequent Visitor', 58 | period: [new Date(2015, 0, 2), new Date(2015, 0, 5)] 59 | })).to.eventually.be.rejectedWith(Sequelize.ExclusionConstraintError); 60 | }); 61 | }); 62 | 63 | }); 64 | } 65 | -------------------------------------------------------------------------------- /test/unit/model/schema.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | var chai = require('chai') 5 | , expect = chai.expect 6 | , Support = require(__dirname + '/../support') 7 | , current = Support.sequelize; 8 | 9 | describe(Support.getTestDialectTeaser('Model') + 'Schemas', function() { 10 | if (current.dialect.supports.schemas) { 11 | var Project = current.define('project'), 12 | Company = current.define('company', {}, { 13 | schema: 'default', 14 | schemaDelimiter: '&' 15 | }); 16 | 17 | describe('schema', function() { 18 | it('should work with no default schema', function() { 19 | expect(Project._schema).to.be.null; 20 | }); 21 | 22 | it('should apply default schema from define', function() { 23 | expect(Company._schema).to.equal('default'); 24 | }); 25 | 26 | it('should be able to override the default schema', function() { 27 | expect(Company.schema('newSchema')._schema).to.equal('newSchema'); 28 | }); 29 | 30 | it('should be able nullify schema', function() { 31 | expect(Company.schema(null)._schema).to.be.null; 32 | }); 33 | 34 | it('should support multiple, coexistent schema models', function() { 35 | var schema1 = Company.schema('schema1') 36 | , schema2 = Company.schema('schema1'); 37 | 38 | expect(schema1._schema).to.equal('schema1'); 39 | expect(schema2._schema).to.equal('schema1'); 40 | }); 41 | }); 42 | 43 | describe('schema delimiter', function() { 44 | it('should work with no default schema delimiter', function() { 45 | expect(Project._schemaDelimiter).to.equal(''); 46 | }); 47 | 48 | it('should apply default schema delimiter from define', function() { 49 | expect(Company._schemaDelimiter).to.equal('&'); 50 | }); 51 | 52 | it('should be able to override the default schema delimiter', function() { 53 | expect(Company.schema(Company._schema,'^')._schemaDelimiter).to.equal('^'); 54 | }); 55 | 56 | it('should support multiple, coexistent schema delimiter models', function() { 57 | var schema1 = Company.schema(Company._schema,'$') 58 | , schema2 = Company.schema(Company._schema,'#'); 59 | 60 | expect(schema1._schemaDelimiter).to.equal('$'); 61 | expect(schema2._schemaDelimiter).to.equal('#'); 62 | }); 63 | }); 64 | } 65 | }); 66 | -------------------------------------------------------------------------------- /test/integration/model/optimistic_locking.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Support = require(__dirname + '/../support'); 4 | const DataTypes = require(__dirname + '/../../../lib/data-types'); 5 | const chai = require('chai'); 6 | const expect = chai.expect; 7 | 8 | describe(Support.getTestDialectTeaser('Model'), function() { 9 | describe('optimistic locking', function () { 10 | var Account; 11 | beforeEach(function() { 12 | Account = this.sequelize.define('Account', { 13 | number: { 14 | type: DataTypes.INTEGER, 15 | } 16 | }, { 17 | version: true 18 | }); 19 | return Account.sync({force: true}); 20 | }); 21 | 22 | it('should increment the version on save', function() { 23 | return Account.create({number: 1}).then(account => { 24 | account.number += 1; 25 | expect(account.version).to.eq(0); 26 | return account.save(); 27 | }).then(account => { 28 | expect(account.version).to.eq(1); 29 | }); 30 | }); 31 | 32 | it('should increment the version on update', function() { 33 | return Account.create({number: 1}).then(account => { 34 | expect(account.version).to.eq(0); 35 | return account.update({ number: 2 }); 36 | }).then(account => { 37 | expect(account.version).to.eq(1); 38 | account.number += 1; 39 | return account.save(); 40 | }).then(account => { 41 | expect(account.number).to.eq(3); 42 | expect(account.version).to.eq(2); 43 | }); 44 | }); 45 | 46 | it('prevents stale instances from being saved', function() { 47 | return expect(Account.create({number: 1}).then(accountA => { 48 | return Account.findById(accountA.id).then(accountB => { 49 | accountA.number += 1; 50 | return accountA.save().then(function() { return accountB; }); 51 | }); 52 | }).then(accountB => { 53 | accountB.number += 1; 54 | return accountB.save(); 55 | })).to.eventually.be.rejectedWith(Support.Sequelize.OptimisticLockError); 56 | }); 57 | 58 | it('increment() also increments the version', function() { 59 | return Account.create({number: 1}).then(account => { 60 | expect(account.version).to.eq(0); 61 | return account.increment('number', { by: 1} ); 62 | }).then(account => { 63 | return account.reload(); 64 | }).then(account => { 65 | expect(account.version).to.eq(1); 66 | }); 67 | }); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /test/unit/model/count.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | var chai = require('chai') 5 | , expect = chai.expect 6 | , Support = require(__dirname + '/../support') 7 | , current = Support.sequelize 8 | , sinon = require('sinon') 9 | , DataTypes = require(__dirname + '/../../../lib/data-types') 10 | , Promise = require('bluebird'); 11 | 12 | describe(Support.getTestDialectTeaser('Model'), () => { 13 | describe('method count', () => { 14 | before(() => { 15 | this.oldFindAll = current.Model.findAll; 16 | this.oldAggregate = current.Model.aggregate; 17 | 18 | current.Model.findAll = sinon.stub().returns(Promise.resolve()); 19 | 20 | this.User = current.define('User', { 21 | username: DataTypes.STRING, 22 | age: DataTypes.INTEGER 23 | }); 24 | this.Project = current.define('Project', { 25 | name: DataTypes.STRING 26 | }); 27 | 28 | this.User.hasMany(this.Project); 29 | this.Project.belongsTo(this.User); 30 | }); 31 | 32 | after(() => { 33 | current.Model.findAll = this.oldFindAll; 34 | current.Model.aggregate = this.oldAggregate; 35 | }); 36 | 37 | beforeEach(() => { 38 | this.stub = current.Model.aggregate = sinon.stub().returns(Promise.resolve()); 39 | }); 40 | 41 | describe('should pass the same options to model.aggregate as findAndCount', () => { 42 | it('with includes', () => { 43 | const queryObject = { 44 | include: [this.Project] 45 | }; 46 | return this.User.count(queryObject) 47 | .then(() => this.User.findAndCount(queryObject)) 48 | .then(() => { 49 | const count = this.stub.getCall(0).args; 50 | const findAndCount = this.stub.getCall(1).args; 51 | expect(count).to.eql(findAndCount); 52 | }); 53 | }); 54 | 55 | it('attributes should be stripped in case of findAndCount', () => { 56 | const queryObject = { 57 | attributes: ['username'] 58 | }; 59 | return this.User.count(queryObject) 60 | .then(() => this.User.findAndCount(queryObject)) 61 | .then(() => { 62 | const count = this.stub.getCall(0).args; 63 | const findAndCount = this.stub.getCall(1).args; 64 | expect(count).not.to.eql(findAndCount); 65 | count[2].attributes = undefined; 66 | expect(count).to.eql(findAndCount); 67 | }); 68 | }); 69 | }); 70 | 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /test/unit/sql/offset-limit.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W110 */ 4 | var Support = require(__dirname + '/../support') 5 | , util = require('util') 6 | , expectsql = Support.expectsql 7 | , current = Support.sequelize 8 | , sql = current.dialect.QueryGenerator; 9 | 10 | // Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation 11 | 12 | suite(Support.getTestDialectTeaser('SQL'), function() { 13 | suite('offset/limit', function () { 14 | var testsql = function (options, expectation) { 15 | var model = options.model; 16 | 17 | test(util.inspect(options, {depth: 2}), function () { 18 | return expectsql( 19 | sql.addLimitAndOffset( 20 | options, 21 | model 22 | ), 23 | expectation 24 | ); 25 | }); 26 | }; 27 | 28 | testsql({ 29 | limit: 10,//when no order by present, one is automagically prepended, test it's existence 30 | model:{primaryKeyField:'id', name:'tableRef'} 31 | }, { 32 | default: ' LIMIT 10', 33 | mssql: ' ORDER BY [tableRef].[id] OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY' 34 | }); 35 | 36 | testsql({ 37 | limit: 10, 38 | order: [ 39 | ['email', 'DESC'] // for MSSQL 40 | ] 41 | }, { 42 | default: ' LIMIT 10', 43 | mssql: ' OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY' 44 | }); 45 | 46 | testsql({ 47 | limit: 10, 48 | offset: 20, 49 | order: [ 50 | ['email', 'DESC'] // for MSSQL 51 | ] 52 | }, { 53 | default: ' LIMIT 20, 10', 54 | postgres: ' LIMIT 10 OFFSET 20', 55 | mssql: ' OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY' 56 | }); 57 | 58 | testsql({ 59 | limit: "';DELETE FROM user", 60 | order: [ 61 | ['email', 'DESC'] // for MSSQL 62 | ] 63 | }, { 64 | default: " LIMIT ''';DELETE FROM user'", 65 | mysql: " LIMIT '\\';DELETE FROM user'", 66 | mssql: " OFFSET 0 ROWS FETCH NEXT N''';DELETE FROM user' ROWS ONLY" 67 | }); 68 | 69 | testsql({ 70 | limit: 10, 71 | offset: "';DELETE FROM user", 72 | order: [ 73 | ['email', 'DESC'] // for MSSQL 74 | ] 75 | }, { 76 | sqlite: " LIMIT ''';DELETE FROM user', 10", 77 | postgres: " LIMIT 10 OFFSET ''';DELETE FROM user'", 78 | mysql: " LIMIT '\\';DELETE FROM user', 10", 79 | mssql: " OFFSET N''';DELETE FROM user' ROWS FETCH NEXT 10 ROWS ONLY" 80 | }); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /test/unit/instance/set.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | const chai = require('chai') 5 | , expect = chai.expect 6 | , Support = require(__dirname + '/../support') 7 | , DataTypes = require(__dirname + '/../../../lib/data-types') 8 | , current = Support.sequelize; 9 | 10 | describe(Support.getTestDialectTeaser('Instance'), function() { 11 | describe('set', function () { 12 | it('sets nested keys in JSON objects', function () { 13 | const User = current.define('User', { 14 | meta: DataTypes.JSONB 15 | }); 16 | const user = User.build({ 17 | meta: { 18 | location: 'Stockhollm' 19 | } 20 | }, { 21 | isNewRecord: false, 22 | raw: true 23 | }); 24 | 25 | const meta = user.get('meta'); 26 | 27 | user.set('meta.location', 'Copenhagen'); 28 | expect(user.dataValues['meta.location']).not.to.be.ok; 29 | expect(user.get('meta').location).to.equal('Copenhagen'); 30 | expect(user.get('meta') === meta).to.equal(true); 31 | expect(user.get('meta') === meta).to.equal(true); 32 | }); 33 | 34 | it('doesnt mutate the JSONB defaultValue', function() { 35 | const User = current.define('User', { 36 | meta: { 37 | type: DataTypes.JSONB, 38 | allowNull: false, 39 | defaultValue: {} 40 | } 41 | }); 42 | const user1 = User.build({}); 43 | user1.set('meta.location', 'Stockhollm'); 44 | const user2 = User.build({}); 45 | expect(user2.get('meta')).to.deep.equal({}); 46 | }); 47 | 48 | it('sets the date "1970-01-01" to previously null field', function() { 49 | const User = current.define('User', { 50 | date: { 51 | type: DataTypes.DATE, 52 | allowNull: true 53 | } 54 | }); 55 | const user1 = User.build({ 56 | date: null 57 | }); 58 | user1.set('date', '1970-01-01'); 59 | expect(user1.get('date')).to.be.ok; 60 | expect(user1.get('date').getTime()).to.equal(new Date('1970-01-01').getTime()); 61 | }); 62 | 63 | it('overwrites non-date originalValue with date', function() { 64 | const User = current.define('User', { 65 | date: DataTypes.DATE 66 | }); 67 | const user = User.build({ 68 | date: ' ' 69 | }, { 70 | isNewRecord: false, 71 | raw: true 72 | }); 73 | 74 | user.set('date', new Date()); 75 | expect(user.get('date')).to.be.an.instanceof(Date); 76 | expect(user.get('date')).not.to.be.NaN; 77 | }); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /test/unit/model/findone.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | var chai = require('chai') 5 | , expect = chai.expect 6 | , Support = require(__dirname + '/../support') 7 | , current = Support.sequelize 8 | , sinon = require('sinon') 9 | , DataTypes = require(__dirname + '/../../../lib/data-types') 10 | , Promise = require('bluebird'); 11 | 12 | describe(Support.getTestDialectTeaser('Model'), function() { 13 | describe('method findOne', function () { 14 | before(function () { 15 | this.oldFindAll = current.Model.findAll; 16 | }); 17 | after(function () { 18 | current.Model.findAll = this.oldFindAll; 19 | }); 20 | 21 | beforeEach(function () { 22 | this.stub = current.Model.findAll = sinon.stub().returns(Promise.resolve()); 23 | }); 24 | 25 | describe('should not add limit when querying on a primary key', function () { 26 | it('with id primary key', function () { 27 | var Model = current.define('model'); 28 | 29 | return Model.findOne({ where: { id: 42 }}).bind(this).then(function () { 30 | expect(this.stub.getCall(0).args[0]).to.be.an('object').not.to.have.property('limit'); 31 | }); 32 | }); 33 | 34 | it('with custom primary key', function () { 35 | var Model = current.define('model', { 36 | uid: { 37 | type: DataTypes.INTEGER, 38 | primaryKey: true, 39 | autoIncrement: true 40 | } 41 | }); 42 | 43 | return Model.findOne({ where: { uid: 42 }}).bind(this).then(function () { 44 | expect(this.stub.getCall(0).args[0]).to.be.an('object').not.to.have.property('limit'); 45 | }); 46 | }); 47 | 48 | it('with blob primary key', function () { 49 | var Model = current.define('model', { 50 | id: { 51 | type: DataTypes.BLOB, 52 | primaryKey: true, 53 | autoIncrement: true 54 | } 55 | }); 56 | 57 | return Model.findOne({ where: { id: new Buffer('foo') }}).bind(this).then(function () { 58 | expect(this.stub.getCall(0).args[0]).to.be.an('object').not.to.have.property('limit'); 59 | }); 60 | }); 61 | }); 62 | 63 | it('should add limit when using { $ gt on the primary key', function () { 64 | var Model = current.define('model'); 65 | 66 | return Model.findOne({ where: { id: { $gt: 42 }}}).bind(this).then(function () { 67 | expect(this.stub.getCall(0).args[0]).to.be.an('object').to.have.property('limit'); 68 | }); 69 | }); 70 | 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /test/unit/model/indexes.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | var chai = require('chai') 5 | , expect = chai.expect 6 | , Support = require(__dirname + '/../support') 7 | , current = Support.sequelize 8 | , DataTypes = require(__dirname + '/../../../lib/data-types'); 9 | 10 | describe(Support.getTestDialectTeaser('Model'), function() { 11 | describe('indexes', function () { 12 | it('should automatically set a gin index for JSONB indexes', function () { 13 | var Model = current.define('event', { 14 | eventData: { 15 | type: DataTypes.JSONB, 16 | index: true, 17 | field: 'data' 18 | } 19 | }); 20 | 21 | expect(Model.rawAttributes.eventData.index).not.to.equal(true); 22 | expect(Model.options.indexes.length).to.equal(1); 23 | expect(Model.options.indexes[0].fields).to.eql(['data']); 24 | expect(Model.options.indexes[0].using).to.equal('gin'); 25 | }); 26 | 27 | it('should set the unique property when type is unique', function () { 28 | var Model = current.define('m', {}, { 29 | indexes: [ 30 | { 31 | type: 'unique' 32 | }, 33 | { 34 | type: 'UNIQUE' 35 | } 36 | ] 37 | }); 38 | 39 | expect(Model.options.indexes[0].unique).to.eql(true); 40 | expect(Model.options.indexes[1].unique).to.eql(true); 41 | }); 42 | 43 | it('should set rawAttributes when indexes are defined via options', function() { 44 | const User = current.define('User', { 45 | username: DataTypes.STRING 46 | }, { 47 | indexes: [{ 48 | unique: true, 49 | fields: ['username'] 50 | }] 51 | }); 52 | 53 | expect(User.rawAttributes.username).to.have.property('unique'); 54 | expect(User.rawAttributes.username.unique).to.be.true; 55 | }); 56 | 57 | it('should set rawAttributes when composite unique indexes are defined via options', function() { 58 | const User = current.define('User', { 59 | name: DataTypes.STRING, 60 | address: DataTypes.STRING 61 | }, { 62 | indexes: [{ 63 | unique: 'users_name_address', 64 | fields: ['name', 'address'] 65 | }] 66 | }); 67 | 68 | expect(User.rawAttributes.name).to.have.property('unique'); 69 | expect(User.rawAttributes.name.unique).to.be.equal('users_name_address'); 70 | 71 | expect(User.rawAttributes.address).to.have.property('unique'); 72 | expect(User.rawAttributes.address.unique).to.be.equal('users_name_address'); 73 | }); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /test/unit/model/find-create-find.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | var chai = require('chai') 5 | , expect = chai.expect 6 | , Support = require(__dirname + '/../support') 7 | , UniqueConstraintError = require(__dirname + '/../../../lib/errors').UniqueConstraintError 8 | , current = Support.sequelize 9 | , sinon = require('sinon') 10 | , Promise = require('bluebird'); 11 | 12 | describe(Support.getTestDialectTeaser('Model'), function() { 13 | describe('findCreateFind', function () { 14 | var Model = current.define('Model', {}); 15 | 16 | beforeEach(function () { 17 | this.sinon = sinon.sandbox.create(); 18 | }); 19 | 20 | afterEach(function () { 21 | this.sinon.restore(); 22 | }); 23 | 24 | it('should return the result of the first find call if not empty', function () { 25 | var result = {} 26 | , where = {prop: Math.random().toString()} 27 | , findSpy = this.sinon.stub(Model, 'findOne').returns(Promise.resolve(result)); 28 | 29 | return expect(Model.findCreateFind({ 30 | where: where 31 | })).to.eventually.eql([result, false]).then(function () { 32 | expect(findSpy).to.have.been.calledOnce; 33 | expect(findSpy.getCall(0).args[0].where).to.equal(where); 34 | }); 35 | }); 36 | 37 | it('should create if first find call is empty', function () { 38 | var result = {} 39 | , where = {prop: Math.random().toString()} 40 | , createSpy = this.sinon.stub(Model, 'create').returns(Promise.resolve(result)); 41 | 42 | this.sinon.stub(Model, 'findOne').returns(Promise.resolve(null)); 43 | 44 | return expect(Model.findCreateFind({ 45 | where: where 46 | })).to.eventually.eql([result, true]).then(function () { 47 | expect(createSpy).to.have.been.calledWith(where); 48 | }); 49 | }); 50 | 51 | it('should do a second find if create failed do to unique constraint', function () { 52 | var result = {} 53 | , where = {prop: Math.random().toString()} 54 | , findSpy = this.sinon.stub(Model, 'findOne'); 55 | 56 | this.sinon.stub(Model, 'create').returns(Promise.reject(new UniqueConstraintError())); 57 | 58 | findSpy.onFirstCall().returns(Promise.resolve(null)); 59 | findSpy.onSecondCall().returns(Promise.resolve(result)); 60 | 61 | return expect(Model.findCreateFind({ 62 | where: where 63 | })).to.eventually.eql([result, false]).then(function () { 64 | expect(findSpy).to.have.been.calledTwice; 65 | expect(findSpy.getCall(1).args[0].where).to.equal(where); 66 | }); 67 | }); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /lib/model-manager.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Toposort = require('toposort-class'); 4 | const _ = require('lodash'); 5 | 6 | class ModelManager { 7 | constructor(sequelize) { 8 | this.models = []; 9 | this.sequelize = sequelize; 10 | } 11 | 12 | addModel(model) { 13 | this.models.push(model); 14 | this.sequelize.models[model.name] = model; 15 | 16 | return model; 17 | } 18 | 19 | removeModel(modelToRemove) { 20 | this.models = this.models.filter(model => model.name !== modelToRemove.name); 21 | 22 | delete this.sequelize.models[modelToRemove.name]; 23 | } 24 | 25 | getModel(against, options) { 26 | options = _.defaults(options || {}, { 27 | attribute: 'name' 28 | }); 29 | 30 | const model = this.models.filter(model => model[options.attribute] === against); 31 | 32 | return !!model ? model[0] : null; 33 | } 34 | 35 | get all() { 36 | return this.models; 37 | } 38 | 39 | /** 40 | * Iterate over Models in an order suitable for e.g. creating tables. Will 41 | * take foreign key constraints into account so that dependencies are visited 42 | * before dependents. 43 | * @private 44 | */ 45 | forEachModel(iterator, options) { 46 | const models = {}; 47 | const sorter = new Toposort(); 48 | let sorted; 49 | let dep; 50 | 51 | options = _.defaults(options || {}, { 52 | reverse: true 53 | }); 54 | 55 | for (const model of this.models) { 56 | let deps = []; 57 | let tableName = model.getTableName(); 58 | 59 | if (_.isObject(tableName)) { 60 | tableName = tableName.schema + '.' + tableName.tableName; 61 | } 62 | 63 | models[tableName] = model; 64 | 65 | for (const attrName in model.rawAttributes) { 66 | if (model.rawAttributes.hasOwnProperty(attrName)) { 67 | const attribute = model.rawAttributes[attrName]; 68 | 69 | if (attribute.references) { 70 | dep = attribute.references.model; 71 | 72 | if (_.isObject(dep)) { 73 | dep = dep.schema + '.' + dep.tableName; 74 | } 75 | 76 | deps.push(dep); 77 | } 78 | } 79 | } 80 | 81 | deps = deps.filter(dep => tableName !== dep); 82 | 83 | sorter.add(tableName, deps); 84 | } 85 | 86 | sorted = sorter.sort(); 87 | if (options.reverse) { 88 | sorted = sorted.reverse(); 89 | } 90 | for (const name of sorted) { 91 | iterator(models[name], name); 92 | } 93 | } 94 | } 95 | 96 | module.exports = ModelManager; 97 | module.exports.ModelManager = ModelManager; 98 | module.exports.default = ModelManager; 99 | -------------------------------------------------------------------------------- /test/integration/sequelize/log.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | var chai = require('chai') 5 | , sinon = require('sinon') 6 | , expect = chai.expect 7 | , Support = require(__dirname + '/../support') 8 | , dialect = Support.getTestDialect(); 9 | 10 | describe(Support.getTestDialectTeaser('Sequelize'), function() { 11 | describe('log', function() { 12 | beforeEach(function() { 13 | this.spy = sinon.spy(console, 'log'); 14 | }); 15 | 16 | afterEach(function() { 17 | console.log.restore(); 18 | }); 19 | 20 | describe('with disabled logging', function() { 21 | beforeEach(function() { 22 | this.sequelize = new Support.Sequelize('db', 'user', 'pw', { dialect: dialect, logging: false }); 23 | }); 24 | 25 | it('does not call the log method of the logger', function() { 26 | this.sequelize.log(); 27 | expect(this.spy.calledOnce).to.be.false; 28 | }); 29 | }); 30 | 31 | describe('with default logging options', function() { 32 | beforeEach(function() { 33 | this.sequelize = new Support.Sequelize('db', 'user', 'pw', { dialect: dialect }); 34 | }); 35 | 36 | describe('called with no arguments', function() { 37 | it('calls the log method', function() { 38 | this.sequelize.log(); 39 | expect(this.spy.calledOnce).to.be.true; 40 | }); 41 | 42 | it('logs an empty string as info event', function() { 43 | this.sequelize.log(''); 44 | expect(this.spy.calledOnce).to.be.true; 45 | }); 46 | }); 47 | 48 | describe('called with one argument', function() { 49 | it('logs the passed string as info event', function() { 50 | this.sequelize.log('my message'); 51 | expect(this.spy.withArgs('my message').calledOnce).to.be.true; 52 | }); 53 | }); 54 | 55 | describe('called with more than two arguments', function() { 56 | it('passes the arguments to the logger', function() { 57 | this.sequelize.log('error', 'my message', 1, { a: 1 }); 58 | expect(this.spy.withArgs('error', 'my message', 1, { a: 1 }).calledOnce).to.be.true; 59 | }); 60 | }); 61 | }); 62 | 63 | describe('with a custom function for logging', function() { 64 | beforeEach(function() { 65 | this.spy = sinon.spy(); 66 | this.sequelize = new Support.Sequelize('db', 'user', 'pw', { dialect: dialect, logging: this.spy }); 67 | }); 68 | 69 | it('calls the custom logger method', function() { 70 | this.sequelize.log('om nom'); 71 | expect(this.spy.calledOnce).to.be.true; 72 | }); 73 | }); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /docs/api/associations/belongs-to.md: -------------------------------------------------------------------------------- 1 | 2 | # Mixin BelongsTo 3 | [View code](https://github.com/sequelize/sequelize/blob/3e5b8772ef75169685fc96024366bca9958fee63/lib/associations/belongs-to.js#L17) 4 | 5 | One-to-one association 6 | 7 | In the API reference below, replace `Assocation` with the actual name of your association, e.g. for `User.belongsTo(Project)` the getter will be `user.getProject()`. 8 | 9 | *** 10 | 11 | 12 | ## `getAssociation([options])` -> `Promise.` 13 | [View code](https://github.com/sequelize/sequelize/blob/3e5b8772ef75169685fc96024366bca9958fee63/lib/associations/belongs-to.js#L83) 14 | 15 | Get the associated instance. 16 | 17 | **Params:** 18 | 19 | | Name | Type | Description | 20 | | ---- | ---- | ----------- | 21 | | [options] | Object | | 22 | | [options.scope] | String | Boolean | Apply a scope on the related model, or remove its default scope by passing false. | 23 | | [options.schema] | String | Apply a schema on the related model | 24 | 25 | 26 | *** 27 | 28 | 29 | ## `setAssociation([newAssociation], [options])` -> `Promise` 30 | [View code](https://github.com/sequelize/sequelize/blob/3e5b8772ef75169685fc96024366bca9958fee63/lib/associations/belongs-to.js#L93) 31 | 32 | Set the associated model. 33 | 34 | **Params:** 35 | 36 | | Name | Type | Description | 37 | | ---- | ---- | ----------- | 38 | | [newAssociation] | Instance | String | Number | An instance or the primary key of an instance to associate with this. Pass `null` or `undefined` to remove the association. | 39 | | [options] | Object | Options passed to `this.save` | 40 | | [options.save=true] | Boolean | Skip saving this after setting the foreign key if false. | 41 | 42 | 43 | *** 44 | 45 | 46 | ## `createAssociation([values], [options])` -> `Promise` 47 | [View code](https://github.com/sequelize/sequelize/blob/3e5b8772ef75169685fc96024366bca9958fee63/lib/associations/belongs-to.js#L102) 48 | 49 | Create a new instance of the associated model and associate it with this. 50 | 51 | **Params:** 52 | 53 | | Name | Type | Description | 54 | | ---- | ---- | ----------- | 55 | | [values] | Object | | 56 | | [options] | Object | Options passed to `target.create` and setAssociation. | 57 | 58 | 59 | *** 60 | 61 | _This document is automatically generated based on source code comments. Please do not edit it directly, as your changes will be ignored. Please write on IRC, open an issue or a create a pull request if you feel something can be improved. For help on how to write source code documentation see [JSDoc](http://usejsdoc.org) and [dox](https://github.com/tj/dox)_ -------------------------------------------------------------------------------- /docs/api/associations/has-one.md: -------------------------------------------------------------------------------- 1 | 2 | # Mixin HasOne 3 | [View code](https://github.com/sequelize/sequelize/blob/3e5b8772ef75169685fc96024366bca9958fee63/lib/associations/has-one.js#L17) 4 | 5 | One-to-one association 6 | 7 | In the API reference below, replace `Association` with the actual name of your association, e.g. for `User.hasOne(Project)` the getter will be `user.getProject()`. 8 | This is almost the same as `belongsTo` with one exception. The foreign key will be defined on the target model. 9 | 10 | *** 11 | 12 | 13 | ## `getAssociation([options])` -> `Promise.` 14 | [View code](https://github.com/sequelize/sequelize/blob/3e5b8772ef75169685fc96024366bca9958fee63/lib/associations/has-one.js#L78) 15 | 16 | Get the associated instance. 17 | 18 | **Params:** 19 | 20 | | Name | Type | Description | 21 | | ---- | ---- | ----------- | 22 | | [options] | Object | | 23 | | [options.scope] | String | Boolean | Apply a scope on the related model, or remove its default scope by passing false | 24 | | [options.schema] | String | Apply a schema on the related model | 25 | 26 | 27 | *** 28 | 29 | 30 | ## `setAssociation([newAssociation], [options])` -> `Promise` 31 | [View code](https://github.com/sequelize/sequelize/blob/3e5b8772ef75169685fc96024366bca9958fee63/lib/associations/has-one.js#L87) 32 | 33 | Set the associated model. 34 | 35 | **Params:** 36 | 37 | | Name | Type | Description | 38 | | ---- | ---- | ----------- | 39 | | [newAssociation] | Instance | String | Number | An instance or the primary key of an instance to associate with this. Pass `null` or `undefined` to remove the association. | 40 | | [options] | Object | Options passed to getAssociation and `target.save` | 41 | 42 | 43 | *** 44 | 45 | 46 | ## `createAssociation([values], [options])` -> `Promise` 47 | [View code](https://github.com/sequelize/sequelize/blob/3e5b8772ef75169685fc96024366bca9958fee63/lib/associations/has-one.js#L96) 48 | 49 | Create a new instance of the associated model and associate it with this. 50 | 51 | **Params:** 52 | 53 | | Name | Type | Description | 54 | | ---- | ---- | ----------- | 55 | | [values] | Object | | 56 | | [options] | Object | Options passed to `target.create` and setAssociation. | 57 | 58 | 59 | *** 60 | 61 | _This document is automatically generated based on source code comments. Please do not edit it directly, as your changes will be ignored. Please write on IRC, open an issue or a create a pull request if you feel something can be improved. For help on how to write source code documentation see [JSDoc](http://usejsdoc.org) and [dox](https://github.com/tj/dox)_ -------------------------------------------------------------------------------- /test/unit/connection-manager.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | var chai = require('chai') 5 | , sinon = require('sinon') 6 | , expect = chai.expect 7 | , Support = require(__dirname + '/support') 8 | , Sequelize = require(__dirname + '/../../index') 9 | , ConnectionManager = require(__dirname + '/../../lib/dialects/abstract/connection-manager') 10 | , Promise = Sequelize.Promise; 11 | 12 | describe('connection manager', function () { 13 | describe('_connect', function () { 14 | beforeEach(function () { 15 | this.sinon = sinon.sandbox.create(); 16 | this.connection = {}; 17 | 18 | this.dialect = { 19 | connectionManager: { 20 | connect: this.sinon.stub().returns(Promise.resolve(this.connection)) 21 | } 22 | }; 23 | 24 | this.sequelize = Support.createSequelizeInstance(); 25 | }); 26 | 27 | afterEach(function () { 28 | this.sinon.restore(); 29 | }); 30 | 31 | it('should resolve connection on dialect connection manager', function () { 32 | var connection = {}; 33 | this.dialect.connectionManager.connect.returns(Promise.resolve(connection)); 34 | 35 | var connectionManager = new ConnectionManager(this.dialect, this.sequelize); 36 | 37 | var config = {}; 38 | 39 | return expect(connectionManager._connect(config)).to.eventually.equal(connection).then(function () { 40 | expect(this.dialect.connectionManager.connect).to.have.been.calledWith(config); 41 | }.bind(this)); 42 | }); 43 | 44 | it('should let beforeConnect hook modify config', function () { 45 | var username = Math.random().toString() 46 | , password = Math.random().toString(); 47 | 48 | this.sequelize.beforeConnect(function (config) { 49 | config.username = username; 50 | config.password = password; 51 | return config; 52 | }); 53 | 54 | var connectionManager = new ConnectionManager(this.dialect, this.sequelize); 55 | 56 | return connectionManager._connect({}).then(function () { 57 | expect(this.dialect.connectionManager.connect).to.have.been.calledWith({ 58 | username: username, 59 | password: password 60 | }); 61 | }.bind(this)); 62 | }); 63 | 64 | it('should call afterConnect', function() { 65 | const spy = sinon.spy(); 66 | this.sequelize.afterConnect(spy); 67 | 68 | var connectionManager = new ConnectionManager(this.dialect, this.sequelize); 69 | 70 | return connectionManager._connect({}).then(() => { 71 | expect(spy.callCount).to.equal(1); 72 | expect(spy.firstCall.args[0]).to.equal(this.connection); 73 | expect(spy.firstCall.args[1]).to.eql({}); 74 | }); 75 | }); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /lib/dialects/mssql/query-interface.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | Returns an object that treats MSSQL's inabilities to do certain queries. 5 | 6 | @class QueryInterface 7 | @static 8 | @private 9 | */ 10 | 11 | /** 12 | A wrapper that fixes MSSQL's inability to cleanly remove columns from existing tables if they have a default constraint. 13 | 14 | @method removeColumn 15 | @for QueryInterface 16 | 17 | @param {String} tableName The name of the table. 18 | @param {String} attributeName The name of the attribute that we want to remove. 19 | @param {Object} options 20 | @param {Boolean|Function} [options.logging] A function that logs the sql queries, or false for explicitly not logging these queries 21 | @private 22 | */ 23 | const removeColumn = function(tableName, attributeName, options) { 24 | options = Object.assign({ raw: true }, options || {}); 25 | 26 | const findConstraintSql = this.QueryGenerator.getDefaultConstraintQuery(tableName, attributeName); 27 | return this.sequelize.query(findConstraintSql, options) 28 | .spread(results => { 29 | if (!results.length) { 30 | // No default constraint found -- we can cleanly remove the column 31 | return; 32 | } 33 | const dropConstraintSql = this.QueryGenerator.dropConstraintQuery(tableName, results[0].name); 34 | return this.sequelize.query(dropConstraintSql, options); 35 | }) 36 | .then(() => { 37 | const findForeignKeySql = this.QueryGenerator.getForeignKeyQuery(tableName, attributeName); 38 | return this.sequelize.query(findForeignKeySql, options); 39 | }) 40 | .spread(results => { 41 | if (!results.length) { 42 | // No foreign key constraints found, so we can remove the column 43 | return; 44 | } 45 | const dropForeignKeySql = this.QueryGenerator.dropForeignKeyQuery(tableName, results[0].constraint_name); 46 | return this.sequelize.query(dropForeignKeySql, options); 47 | }) 48 | .then(() => { 49 | //Check if the current column is a primaryKey 50 | const primaryKeyConstraintSql = this.QueryGenerator.getPrimaryKeyConstraintQuery(tableName, attributeName); 51 | return this.sequelize.query(primaryKeyConstraintSql, options); 52 | }) 53 | .spread(result => { 54 | if (!result.length) { 55 | return; 56 | } 57 | const dropConstraintSql = this.QueryGenerator.dropConstraintQuery(tableName, result[0].constraintName); 58 | return this.sequelize.query(dropConstraintSql, options); 59 | }) 60 | .then(() => { 61 | const removeSql = this.QueryGenerator.removeColumnQuery(tableName, attributeName); 62 | return this.sequelize.query(removeSql, options); 63 | }); 64 | }; 65 | 66 | module.exports = { 67 | removeColumn 68 | }; 69 | -------------------------------------------------------------------------------- /lib/utils/validator-extras.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('lodash'); 4 | const validator = _.cloneDeep(require('validator')); 5 | 6 | const extensions = { 7 | extend(name, fn) { 8 | this[name] = fn; 9 | 10 | return this; 11 | }, 12 | notEmpty(str) { 13 | return !str.match(/^[\s\t\r\n]*$/); 14 | }, 15 | len(str, min, max) { 16 | return this.isLength(str, min, max); 17 | }, 18 | isUrl(str) { 19 | return this.isURL(str); 20 | }, 21 | isIPv6(str) { 22 | return this.isIP(str, 6); 23 | }, 24 | isIPv4(str) { 25 | return this.isIP(str, 4); 26 | }, 27 | notIn(str, values) { 28 | return !this.isIn(str, values); 29 | }, 30 | regex(str, pattern, modifiers) { 31 | str += ''; 32 | if (Object.prototype.toString.call(pattern).slice(8, -1) !== 'RegExp') { 33 | pattern = new RegExp(pattern, modifiers); 34 | } 35 | return str.match(pattern); 36 | }, 37 | notRegex(str, pattern, modifiers) { 38 | return !this.regex(str, pattern, modifiers); 39 | }, 40 | isDecimal(str) { 41 | return str !== '' && !!str.match(/^(?:-?(?:[0-9]+))?(?:\.[0-9]*)?(?:[eE][\+\-]?(?:[0-9]+))?$/); 42 | }, 43 | min(str, val) { 44 | const number = parseFloat(str); 45 | return isNaN(number) || number >= val; 46 | }, 47 | max(str, val) { 48 | const number = parseFloat(str); 49 | return isNaN(number) || number <= val; 50 | }, 51 | not(str, pattern, modifiers) { 52 | return this.notRegex(str, pattern, modifiers); 53 | }, 54 | contains(str, elem) { 55 | return str.indexOf(elem) >= 0 && !!elem; 56 | }, 57 | notContains(str, elem) { 58 | return !this.contains(str, elem); 59 | }, 60 | is(str, pattern, modifiers) { 61 | return this.regex(str, pattern, modifiers); 62 | } 63 | }; 64 | exports.extensions = extensions; 65 | 66 | function extendModelValidations(modelInstance) { 67 | const extensions = { 68 | isImmutable(str, param, field) { 69 | return (modelInstance.isNewRecord || modelInstance.dataValues[field] === modelInstance._previousDataValues[field]); 70 | } 71 | }; 72 | 73 | _.forEach(extensions, (extend, key) => { 74 | validator[key] = extend; 75 | }); 76 | } 77 | exports.extendModelValidations = extendModelValidations; 78 | 79 | // Deprecate this. 80 | validator.notNull = function() { 81 | throw new Error('Warning "notNull" validation has been deprecated in favor of Schema based "allowNull"'); 82 | }; 83 | 84 | // https://github.com/chriso/validator.js/blob/6.2.0/validator.js 85 | _.forEach(extensions, (extend, key) => { 86 | validator[key] = extend; 87 | }); 88 | 89 | // map isNull to isEmpty 90 | // https://github.com/chriso/validator.js/commit/e33d38a26ee2f9666b319adb67c7fc0d3dea7125 91 | validator.isNull = validator.isEmpty; 92 | 93 | exports.validator = validator; 94 | -------------------------------------------------------------------------------- /lib/associations/helpers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Utils = require('./../utils'); 4 | 5 | function checkNamingCollision(association) { 6 | if (association.source.rawAttributes.hasOwnProperty(association.as)) { 7 | throw new Error( 8 | 'Naming collision between attribute \'' + association.as + 9 | '\' and association \'' + association.as + '\' on model ' + association.source.name + 10 | '. To remedy this, change either foreignKey or as in your association definition' 11 | ); 12 | } 13 | } 14 | exports.checkNamingCollision = checkNamingCollision; 15 | 16 | function addForeignKeyConstraints(newAttribute, source, target, options, key) { 17 | // FK constraints are opt-in: users must either set `foreignKeyConstraints` 18 | // on the association, or request an `onDelete` or `onUpdate` behaviour 19 | 20 | if (options.foreignKeyConstraint || options.onDelete || options.onUpdate) { 21 | 22 | // Find primary keys: composite keys not supported with this approach 23 | const primaryKeys = Utils._.chain(source.rawAttributes).keys() 24 | .filter(key => source.rawAttributes[key].primaryKey) 25 | .map(key => source.rawAttributes[key].field || key).value(); 26 | 27 | if (primaryKeys.length === 1) { 28 | if (!!source._schema) { 29 | newAttribute.references = { 30 | model: source.sequelize.queryInterface.QueryGenerator.addSchema({ 31 | tableName: source.tableName, 32 | _schema: source._schema, 33 | _schemaDelimiter: source._schemaDelimiter 34 | }) 35 | }; 36 | } else { 37 | newAttribute.references = { model: source.tableName }; 38 | } 39 | 40 | newAttribute.references.key = key || primaryKeys[0]; 41 | newAttribute.onDelete = options.onDelete; 42 | newAttribute.onUpdate = options.onUpdate; 43 | } 44 | } 45 | } 46 | exports.addForeignKeyConstraints = addForeignKeyConstraints; 47 | 48 | /** 49 | * Mixin (inject) association methods to model prototype 50 | * 51 | * @param {Object} Association instance 52 | * @param {Object} Model prototype 53 | * @param {Array} Method names to inject 54 | * @param {Object} Mapping between model and association method names 55 | */ 56 | function mixinMethods(association, obj, methods, aliases) { 57 | aliases = aliases || {}; 58 | 59 | for (const method of methods) { 60 | // don't override custom methods 61 | if (!obj[association.accessors[method]]) { 62 | const realMethod = aliases[method] || method; 63 | 64 | obj[association.accessors[method]] = function() { 65 | const instance = this; 66 | const args = [instance].concat(Array.from(arguments)); 67 | 68 | return association[realMethod].apply(association, args); 69 | }; 70 | } 71 | } 72 | } 73 | exports.mixinMethods = mixinMethods; 74 | -------------------------------------------------------------------------------- /test/integration/model/update.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | const Support = require(__dirname + '/../support'); 5 | const DataTypes = require(__dirname + '/../../../lib/data-types'); 6 | const chai = require('chai'); 7 | const expect = chai.expect; 8 | const current = Support.sequelize; 9 | const _ = require('lodash'); 10 | 11 | describe(Support.getTestDialectTeaser('Model'), function () { 12 | describe('update', function () { 13 | beforeEach(function() { 14 | this.Account = this.sequelize.define('Account', { 15 | ownerId: { 16 | type: DataTypes.INTEGER, 17 | allowNull: false, 18 | field: 'owner_id' 19 | }, 20 | name: { 21 | type: DataTypes.STRING 22 | } 23 | }); 24 | return this.Account.sync({force: true}); 25 | }); 26 | 27 | it('should only update the passed fields', function () { 28 | return this.Account 29 | .create({ ownerId: 2 }) 30 | .then(account => this.Account.update({ 31 | name: Math.random().toString() 32 | }, { 33 | where: { 34 | id: account.get('id') 35 | } 36 | })); 37 | }); 38 | 39 | 40 | if (_.get(current.dialect.supports, 'returnValues.returning')) { 41 | it('should return the updated record', function () { 42 | return this.Account.create({ ownerId: 2 }).then(account => { 43 | return this.Account.update({ name: 'FooBar' }, { 44 | where: { 45 | id: account.get('id') 46 | }, 47 | returning: true 48 | }).spread((count, accounts) => { 49 | const firstAcc = accounts[0]; 50 | expect(firstAcc.ownerId).to.be.equal(2); 51 | expect(firstAcc.name).to.be.equal('FooBar'); 52 | }); 53 | }); 54 | }); 55 | } 56 | 57 | if (current.dialect.supports['LIMIT ON UPDATE']) { 58 | it('should only update one row', function () { 59 | return this.Account.create({ 60 | ownerId: 2, 61 | name: 'Account Name 1' 62 | }) 63 | .then(() => { 64 | return this.Account.create({ 65 | ownerId: 2, 66 | name: 'Account Name 2' 67 | }); 68 | }) 69 | .then(() => { 70 | return this.Account.create({ 71 | ownerId: 2, 72 | name: 'Account Name 3' 73 | }); 74 | }) 75 | .then(() => { 76 | const options = { 77 | where: { 78 | ownerId: 2 79 | }, 80 | limit: 1 81 | }; 82 | return this.Account.update({ name: 'New Name' }, options); 83 | }) 84 | .then(account => { 85 | expect(account[0]).to.equal(1); 86 | }); 87 | }); 88 | } 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /test/integration/hooks/destroy.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | var chai = require('chai') 5 | , expect = chai.expect 6 | , Support = require(__dirname + '/../support') 7 | , DataTypes = require(__dirname + '/../../../lib/data-types') 8 | , sinon = require('sinon'); 9 | 10 | describe(Support.getTestDialectTeaser('Hooks'), function() { 11 | beforeEach(function() { 12 | this.User = this.sequelize.define('User', { 13 | username: { 14 | type: DataTypes.STRING, 15 | allowNull: false 16 | }, 17 | mood: { 18 | type: DataTypes.ENUM, 19 | values: ['happy', 'sad', 'neutral'] 20 | } 21 | }); 22 | return this.sequelize.sync({ force: true }); 23 | }); 24 | 25 | describe('#destroy', function() { 26 | describe('on success', function() { 27 | it('should run hooks', function() { 28 | var beforeHook = sinon.spy() 29 | , afterHook = sinon.spy(); 30 | 31 | this.User.beforeDestroy(beforeHook); 32 | this.User.afterDestroy(afterHook); 33 | 34 | return this.User.create({username: 'Toni', mood: 'happy'}).then(function(user) { 35 | return user.destroy().then(function() { 36 | expect(beforeHook).to.have.been.calledOnce; 37 | expect(afterHook).to.have.been.calledOnce; 38 | }); 39 | }); 40 | }); 41 | }); 42 | 43 | describe('on error', function() { 44 | it('should return an error from before', function() { 45 | var beforeHook = sinon.spy() 46 | , afterHook = sinon.spy(); 47 | 48 | this.User.beforeDestroy(function(user, options) { 49 | beforeHook(); 50 | throw new Error('Whoops!'); 51 | }); 52 | this.User.afterDestroy(afterHook); 53 | 54 | return this.User.create({username: 'Toni', mood: 'happy'}).then(function(user) { 55 | return expect(user.destroy()).to.be.rejected.then(function() { 56 | expect(beforeHook).to.have.been.calledOnce; 57 | expect(afterHook).not.to.have.been.called; 58 | }); 59 | }); 60 | }); 61 | 62 | it('should return an error from after', function() { 63 | var beforeHook = sinon.spy() 64 | , afterHook = sinon.spy(); 65 | 66 | this.User.beforeDestroy(beforeHook); 67 | this.User.afterDestroy(function(user, options) { 68 | afterHook(); 69 | throw new Error('Whoops!'); 70 | }); 71 | 72 | return this.User.create({username: 'Toni', mood: 'happy'}).then(function(user) { 73 | return expect(user.destroy()).to.be.rejected.then(function() { 74 | expect(beforeHook).to.have.been.calledOnce; 75 | expect(afterHook).to.have.been.calledOnce; 76 | }); 77 | }); 78 | }); 79 | }); 80 | }); 81 | 82 | }); 83 | -------------------------------------------------------------------------------- /test/unit/sql/change-column.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030, -W110 */ 4 | var Support = require(__dirname + '/../support') 5 | , DataTypes = require('../../../lib/data-types') 6 | , expectsql = Support.expectsql 7 | , sinon = require('sinon') 8 | , current = Support.sequelize 9 | , Promise = current.Promise; 10 | 11 | 12 | if (current.dialect.name !== 'sqlite') { 13 | describe(Support.getTestDialectTeaser('SQL'), function() { 14 | describe('changeColumn', function () { 15 | 16 | var Model = current.define('users', { 17 | id: { 18 | type: DataTypes.INTEGER, 19 | primaryKey: true, 20 | autoIncrement: true 21 | }, 22 | level_id: { 23 | type: DataTypes.INTEGER 24 | } 25 | }, { timestamps: false }); 26 | 27 | before(function () { 28 | 29 | this.stub = sinon.stub(current, 'query', function (sql) { 30 | return Promise.resolve(sql); 31 | }); 32 | }); 33 | 34 | beforeEach(function () { 35 | this.stub.reset(); 36 | }); 37 | 38 | after(function () { 39 | this.stub.restore(); 40 | }); 41 | 42 | it('properly generate alter queries', function(){ 43 | return current.getQueryInterface().changeColumn(Model.getTableName(), 'level_id', { 44 | type: DataTypes.FLOAT, 45 | allowNull: false, 46 | }).then(function(sql){ 47 | expectsql(sql, { 48 | mssql: 'ALTER TABLE [users] ALTER COLUMN [level_id] FLOAT NOT NULL;', 49 | mysql: 'ALTER TABLE `users` CHANGE `level_id` `level_id` FLOAT NOT NULL;', 50 | postgres: 'ALTER TABLE "users" ALTER COLUMN "level_id" SET NOT NULL;ALTER TABLE "users" ALTER COLUMN "level_id" DROP DEFAULT;ALTER TABLE "users" ALTER COLUMN "level_id" TYPE FLOAT;', 51 | }); 52 | }); 53 | }); 54 | 55 | it('properly generate alter queries for foreign keys', function(){ 56 | return current.getQueryInterface().changeColumn(Model.getTableName(), 'level_id', { 57 | type: DataTypes.INTEGER, 58 | references: { 59 | model: 'level', 60 | key: 'id' 61 | }, 62 | onUpdate: 'cascade', 63 | onDelete: 'cascade' 64 | }).then(function(sql){ 65 | expectsql(sql, { 66 | mssql: 'ALTER TABLE [users] ADD CONSTRAINT [level_id_foreign_idx] FOREIGN KEY ([level_id]) REFERENCES [level] ([id]) ON DELETE CASCADE;', 67 | mysql: 'ALTER TABLE `users` ADD CONSTRAINT `users_level_id_foreign_idx` FOREIGN KEY (`level_id`) REFERENCES `level` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;', 68 | postgres: 'ALTER TABLE "users" ADD CONSTRAINT "level_id_foreign_idx" FOREIGN KEY ("level_id") REFERENCES "level" ("id") ON DELETE CASCADE ON UPDATE CASCADE;', 69 | }); 70 | }); 71 | }); 72 | 73 | }); 74 | }); 75 | } 76 | -------------------------------------------------------------------------------- /test/config/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | let mssqlConfig; 5 | try { 6 | mssqlConfig = JSON.parse(fs.readFileSync(__dirname + '/mssql.json', 'utf8')); 7 | } catch (e) { 8 | // ignore 9 | } 10 | 11 | module.exports = { 12 | username: process.env.SEQ_USER || 'root', 13 | password: process.env.SEQ_PW || null, 14 | database: process.env.SEQ_DB || 'sequelize_test', 15 | host: process.env.SEQ_HOST || '127.0.0.1', 16 | pool: { 17 | max: process.env.SEQ_POOL_MAX || 5, 18 | idle: process.env.SEQ_POOL_IDLE || 30000 19 | }, 20 | 21 | rand: function() { 22 | return parseInt(Math.random() * 999, 10); 23 | }, 24 | 25 | mssql: mssqlConfig || { 26 | database: process.env.SEQ_MSSQL_DB || process.env.SEQ_DB || 'sequelize_test', 27 | username: process.env.SEQ_MSSQL_USER || process.env.SEQ_USER || 'sequelize', 28 | password: process.env.SEQ_MSSQL_PW || process.env.SEQ_PW || 'nEGkLma26gXVHFUAHJxcmsrK', 29 | host: process.env.SEQ_MSSQL_HOST || process.env.SEQ_HOST || 'mssql.sequelizejs.com', 30 | port: process.env.SEQ_MSSQL_PORT || process.env.SEQ_PORT || 1433, 31 | dialectOptions: { 32 | // big insert queries need a while 33 | requestTimeout: 60000 34 | }, 35 | pool: { 36 | max: process.env.SEQ_MSSQL_POOL_MAX || process.env.SEQ_POOL_MAX || 5, 37 | idle: process.env.SEQ_MSSQL_POOL_IDLE || process.env.SEQ_POOL_IDLE || 3000 38 | } 39 | }, 40 | 41 | //make idle time small so that tests exit promptly 42 | mysql: { 43 | database: process.env.SEQ_MYSQL_DB || process.env.SEQ_DB || 'sequelize_test', 44 | username: process.env.SEQ_MYSQL_USER || process.env.SEQ_USER || 'root', 45 | password: process.env.SEQ_MYSQL_PW || process.env.SEQ_PW || null, 46 | host: process.env.MYSQL_PORT_3306_TCP_ADDR || process.env.SEQ_MYSQL_HOST || process.env.SEQ_HOST || '127.0.0.1', 47 | port: process.env.MYSQL_PORT_3306_TCP_PORT || process.env.SEQ_MYSQL_PORT || process.env.SEQ_PORT || 3306, 48 | pool: { 49 | max: process.env.SEQ_MYSQL_POOL_MAX || process.env.SEQ_POOL_MAX || 5, 50 | idle: process.env.SEQ_MYSQL_POOL_IDLE || process.env.SEQ_POOL_IDLE || 3000 51 | } 52 | }, 53 | 54 | sqlite: { 55 | }, 56 | 57 | postgres: { 58 | database: process.env.SEQ_PG_DB || process.env.SEQ_DB || 'sequelize_test', 59 | username: process.env.SEQ_PG_USER || process.env.SEQ_USER || 'postgres', 60 | password: process.env.SEQ_PG_PW || process.env.SEQ_PW || 'postgres', 61 | host: process.env.POSTGRES_PORT_5432_TCP_ADDR || process.env.SEQ_PG_HOST || process.env.SEQ_HOST || '127.0.0.1', 62 | port: process.env.POSTGRES_PORT_5432_TCP_PORT || process.env.SEQ_PG_PORT || process.env.SEQ_PORT || 5432, 63 | pool: { 64 | max: process.env.SEQ_PG_POOL_MAX || process.env.SEQ_POOL_MAX || 5, 65 | idle: process.env.SEQ_PG_POOL_IDLE || process.env.SEQ_POOL_IDLE || 3000 66 | } 67 | } 68 | }; 69 | -------------------------------------------------------------------------------- /test/integration/model/attributes.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var chai = require('chai') 4 | , Sequelize = require('../../../index') 5 | , Promise = Sequelize.Promise 6 | , expect = chai.expect 7 | , Support = require(__dirname + '/../support'); 8 | 9 | describe(Support.getTestDialectTeaser('Model'), function() { 10 | describe('attributes', function() { 11 | describe('set', function() { 12 | it('should only be called once when used on a join model called with an association getter', function() { 13 | var self = this; 14 | self.callCount = 0; 15 | 16 | this.Student = this.sequelize.define('student', { 17 | no: {type: Sequelize.INTEGER, primaryKey: true}, 18 | name: Sequelize.STRING 19 | }, { 20 | tableName: 'student', 21 | timestamps: false 22 | }); 23 | 24 | this.Course = this.sequelize.define('course', { 25 | no: {type: Sequelize.INTEGER, primaryKey: true}, 26 | name: Sequelize.STRING 27 | },{ 28 | tableName: 'course', 29 | timestamps: false 30 | }); 31 | 32 | this.Score = this.sequelize.define('score', { 33 | score: Sequelize.INTEGER, 34 | test_value: { 35 | type: Sequelize.INTEGER, 36 | set: function(v) { 37 | self.callCount++; 38 | this.setDataValue('test_value', v + 1); 39 | } 40 | } 41 | }, { 42 | tableName: 'score', 43 | timestamps: false 44 | }); 45 | 46 | this.Student.belongsToMany(this.Course, {through: this.Score, foreignKey: 'StudentId'}); 47 | this.Course.belongsToMany(this.Student, {through: this.Score, foreignKey: 'CourseId'}); 48 | 49 | return this.sequelize.sync({force: true}).then(function() { 50 | return Promise.join( 51 | self.Student.create({no: 1, name: 'ryan'}), 52 | self.Course.create({no: 100, name: 'history'}) 53 | ).spread(function(student, course) { 54 | return student.addCourse(course, { through: {score: 98, test_value: 1000}}); 55 | }).then(function() { 56 | expect(self.callCount).to.equal(1); 57 | return self.Score.find({ where: { StudentId: 1, CourseId: 100 } }).then(function(score) { 58 | expect(score.test_value).to.equal(1001); 59 | }); 60 | }) 61 | .then(function() { 62 | return Promise.join( 63 | self.Student.build({no: 1}).getCourses({where: {no: 100}}), 64 | self.Score.find({ where: { StudentId: 1, CourseId: 100 } }) 65 | ); 66 | }) 67 | .spread(function(courses, score) { 68 | expect(score.test_value).to.equal(1001); 69 | expect(courses[0].score.toJSON().test_value).to.equal(1001); 70 | expect(self.callCount).to.equal(1); 71 | }); 72 | }); 73 | }); 74 | }); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sequelize 2 | 3 | [![Build Status](https://travis-ci.org/sequelize/sequelize.svg?branch=master)](https://travis-ci.org/sequelize/sequelize) 4 | [![Windows Build status](https://ci.appveyor.com/api/projects/status/9l1ypgwsp5ij46m3/branch/master?svg=true)](https://ci.appveyor.com/project/sushantdhiman/sequelize/branch/master) 5 | [![codecov](https://codecov.io/gh/sequelize/sequelize/branch/master/graph/badge.svg)](https://codecov.io/gh/sequelize/sequelize) 6 | [![Bountysource](https://www.bountysource.com/badge/team?team_id=955&style=bounties_received)](https://www.bountysource.com/teams/sequelize/issues?utm_source=Sequelize&utm_medium=shield&utm_campaign=bounties_received) 7 | [![Slack Status](http://sequelize-slack.herokuapp.com/badge.svg)](http://sequelize-slack.herokuapp.com/) 8 | [![npm](https://img.shields.io/npm/dm/sequelize.svg?maxAge=2592000)](https://www.npmjs.com/package/sequelize) 9 | [![License](https://img.shields.io/npm/l/sequelize.svg?maxAge=2592000?style=plastic)](https://github.com/sequelize/sequelize/blob/master/LICENSE) 10 | 11 | Sequelize is a promise-based Node.js ORM for Postgres, MySQL, SQLite and Microsoft SQL Server. It features solid transaction support, relations, read replication and more. 12 | 13 | [Stable (v3) documentation](http://docs.sequelizejs.com/en/v3/) 14 | 15 | [Master / unstable (v4-pre) documentation](http://docs.sequelizejs.com/en/latest/) 16 | 17 | ## Installation 18 | 19 | `npm install sequelize` 20 | 21 | From 3.0.0 and up Sequelize will follow [SEMVER](http://semver.org). 3.0.0 contains important security fixes so we highly recommend that users upgrade. 22 | 23 | Starting from 4.0.0 Sequelize will only support Node v4 and above to use ES6 features. 24 | 25 | ## Features 26 | 27 | - Schema definition 28 | - Schema synchronization/dropping 29 | - 1:1, 1:M & N:M Associations 30 | - Through models 31 | - Promises 32 | - Hooks/callbacks/lifecycle events 33 | - Prefetching/association including 34 | - Transactions 35 | - Migrations 36 | - CLI ([sequelize-cli](https://github.com/sequelize/cli)) 37 | 38 | ## Responsible disclosure 39 | If you have any security issue to report, contact project maintainers privately. You can find contact information [here](https://github.com/sequelize/sequelize/blob/master/CONTACT.md) 40 | 41 | ## Resources 42 | - [Changelog](https://github.com/sequelize/sequelize/blob/master/changelog.md) 43 | - [Getting Started](http://docs.sequelizejs.com/en/latest/docs/getting-started/) 44 | - [Express Example](https://github.com/sequelize/express-example) 45 | - [Documentation](http://docs.sequelizejs.com/en/latest/) 46 | - [Collaboration and pull requests](https://github.com/sequelize/sequelize/blob/master/CONTRIBUTING.md) 47 | - [Roadmap](https://github.com/sequelize/sequelize/issues/2869) 48 | - [Twitter](https://twitter.com/SequelizeJS): @SequelizeJS 49 | - [Slack](https://sequelize.slack.com) 50 | - [IRC](http://webchat.freenode.net?channels=sequelizejs): sequelizejs on Freenode 51 | - [Google Groups](https://groups.google.com/forum/#!forum/sequelize) 52 | - [Add-ons & Plugins](https://github.com/sequelize/sequelize/wiki/Add-ons-&-Plugins) 53 | -------------------------------------------------------------------------------- /test/unit/sql/create-table.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W110 */ 4 | var Support = require(__dirname + '/../support') 5 | , DataTypes = require(__dirname + '/../../../lib/data-types') 6 | , expectsql = Support.expectsql 7 | , current = Support.sequelize 8 | , sql = current.dialect.QueryGenerator 9 | , _ = require('lodash'); 10 | 11 | 12 | describe(Support.getTestDialectTeaser('SQL'), function() { 13 | describe('createTable', function () { 14 | var FooUser = current.define('user', { 15 | mood: DataTypes.ENUM('happy', 'sad') 16 | },{ 17 | schema: 'foo', 18 | timestamps: false 19 | }); 20 | describe('with enums', function () { 21 | it('references enum in the right schema #3171', function () { 22 | expectsql(sql.createTableQuery(FooUser.getTableName(), sql.attributesToSQL(FooUser.rawAttributes), { }), { 23 | sqlite: 'CREATE TABLE IF NOT EXISTS `foo.users` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `mood` TEXT);', 24 | postgres: 'CREATE TABLE IF NOT EXISTS "foo"."users" ("id" SERIAL , "mood" "foo"."enum_users_mood", PRIMARY KEY ("id"));', 25 | mysql: "CREATE TABLE IF NOT EXISTS `foo.users` (`id` INTEGER NOT NULL auto_increment , `mood` ENUM('happy', 'sad'), PRIMARY KEY (`id`)) ENGINE=InnoDB;", 26 | mssql: "IF OBJECT_ID('[foo].[users]', 'U') IS NULL CREATE TABLE [foo].[users] ([id] INTEGER NOT NULL IDENTITY(1,1) , [mood] VARCHAR(255) CHECK ([mood] IN(N'happy', N'sad')), PRIMARY KEY ([id]));" 27 | }); 28 | }); 29 | }); 30 | if (current.dialect.name === 'postgres') { 31 | describe('IF NOT EXISTS version check', function() { 32 | var modifiedSQL = _.clone(sql); 33 | var createTableQueryModified = sql.createTableQuery.bind(modifiedSQL); 34 | it('it will not have IF NOT EXISTS for version 9.0 or below', function () { 35 | modifiedSQL.sequelize.options.databaseVersion = '9.0.0'; 36 | expectsql(createTableQueryModified(FooUser.getTableName(), sql.attributesToSQL(FooUser.rawAttributes), { }), { 37 | postgres: 'CREATE TABLE "foo"."users" ("id" SERIAL , "mood" "foo"."enum_users_mood", PRIMARY KEY ("id"));' 38 | }); 39 | }); 40 | it('it will have IF NOT EXISTS for version 9.1 or above', function () { 41 | modifiedSQL.sequelize.options.databaseVersion = '9.1.0'; 42 | expectsql(createTableQueryModified(FooUser.getTableName(), sql.attributesToSQL(FooUser.rawAttributes), { }), { 43 | postgres: 'CREATE TABLE IF NOT EXISTS "foo"."users" ("id" SERIAL , "mood" "foo"."enum_users_mood", PRIMARY KEY ("id"));' 44 | }); 45 | }); 46 | it('it will have IF NOT EXISTS for default version', function () { 47 | modifiedSQL.sequelize.options.databaseVersion = 0; 48 | expectsql(createTableQueryModified(FooUser.getTableName(), sql.attributesToSQL(FooUser.rawAttributes), { }), { 49 | postgres: 'CREATE TABLE IF NOT EXISTS "foo"."users" ("id" SERIAL , "mood" "foo"."enum_users_mood", PRIMARY KEY ("id"));' 50 | }); 51 | }); 52 | }); 53 | } 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /test/integration/dialects/mssql/connection-manager.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | const chai = require('chai'); 5 | const expect = chai.expect; 6 | const Support = require('../../support'); 7 | const dialect = Support.getTestDialect(); 8 | 9 | if (dialect.match(/^mssql/)) { 10 | describe('[MSSQL Specific] Query Queue', function () { 11 | it('should work with handleDisconnects', () => { 12 | const sequelize = Support.createSequelizeInstance({ pool: { min: 1, max: 1, idle: 5000 } }); 13 | const cm = sequelize.connectionManager; 14 | let conn; 15 | 16 | return sequelize 17 | .sync() 18 | .then(() => cm.getConnection()) 19 | .then(connection => { 20 | // Save current connection 21 | conn = connection; 22 | 23 | // simulate a unexpected end 24 | // connection removed from pool by MSSQL Conn Manager 25 | conn.unwrap().emit('error', {code: 'ECONNRESET'}); 26 | }) 27 | .then(() => cm.getConnection()) 28 | .then(connection => { 29 | expect(conn).to.not.be.equal(connection); 30 | expect(cm.validate(conn)).to.not.be.ok; 31 | 32 | return cm.releaseConnection(connection); 33 | }); 34 | }); 35 | 36 | it('should handle double disconnect', () => { 37 | const sequelize = Support.createSequelizeInstance({ pool: { min: 1, max: 1, idle: 5000 } }); 38 | const cm = sequelize.connectionManager; 39 | let count = 0; 40 | let conn = null; 41 | 42 | return sequelize 43 | .sync() 44 | .then(() => cm.getConnection()) 45 | .then(connection => { 46 | conn = connection; 47 | const unwrapConn = conn.unwrap(); 48 | unwrapConn.on('end', () => { 49 | count++; 50 | }); 51 | 52 | return cm.disconnect(conn); 53 | }) 54 | .then(() => cm.disconnect(conn)) 55 | .then(() => { 56 | expect(count).to.be.eql(1); 57 | }); 58 | }); 59 | 60 | describe('Errors', () => { 61 | it('ECONNREFUSED', () => { 62 | const sequelize = Support.createSequelizeInstance({ port: 34237 }); 63 | return expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(sequelize.ConnectionRefusedError); 64 | }); 65 | 66 | it('ENOTFOUND', () => { 67 | const sequelize = Support.createSequelizeInstance({ host: 'http://wowow.example.com' }); 68 | return expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(sequelize.HostNotFoundError); 69 | }); 70 | 71 | it('EHOSTUNREACH', () => { 72 | const sequelize = Support.createSequelizeInstance({ host: '255.255.255.255' }); 73 | return expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(sequelize.HostNotReachableError); 74 | }); 75 | 76 | it('ER_ACCESS_DENIED_ERROR | ELOGIN', () => { 77 | const sequelize = new Support.Sequelize('localhost', 'was', 'ddsd', Support.sequelize.options); 78 | return expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(sequelize.AccessDeniedError); 79 | }); 80 | }); 81 | }); 82 | } 83 | -------------------------------------------------------------------------------- /test/integration/trigger.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var chai = require('chai') 4 | , Sequelize = require('../../index') 5 | , expect = chai.expect 6 | , Support = require(__dirname + '/../support') 7 | , current = Support.sequelize; 8 | 9 | if (current.dialect.supports.tmpTableTrigger) { 10 | describe(Support.getTestDialectTeaser('Model'), function() { 11 | describe('trigger', function() { 12 | var User; 13 | var triggerQuery = 'create trigger User_ChangeTracking on [users] for insert,update, delete \n' + 14 | 'as\n' + 15 | 'SET NOCOUNT ON\n' + 16 | 'if exists(select 1 from inserted)\n' + 17 | 'begin\n' + 18 | 'select * from inserted\n' + 19 | 'end\n' + 20 | 'if exists(select 1 from deleted)\n' + 21 | 'begin\n' + 22 | 'select * from deleted\n' + 23 | 'end\n'; 24 | 25 | beforeEach(function () { 26 | User = this.sequelize.define('user', { 27 | username: { 28 | type: Sequelize.STRING, 29 | field:'user_name' 30 | } 31 | },{ 32 | hasTrigger:true 33 | }); 34 | 35 | return User.sync({force: true}).bind(this).then(function () { 36 | return this.sequelize.query(triggerQuery,{type:this.sequelize.QueryTypes.RAW}); 37 | }); 38 | }); 39 | 40 | it('should return output rows after insert', function() { 41 | return User.create({ 42 | username: 'triggertest' 43 | }).then(function () { 44 | return expect(User.find({username: 'triggertest'})).to.eventually.have.property('username').which.equals('triggertest'); 45 | }); 46 | }); 47 | 48 | it('should return output rows after instance update', function() { 49 | return User.create({ 50 | username: 'triggertest' 51 | }).then(function(user){ 52 | user.username = 'usernamechanged'; 53 | return user.save(); 54 | }) 55 | .then(function (user) { 56 | return expect(User.find({username: 'usernamechanged'})).to.eventually.have.property('username').which.equals('usernamechanged'); 57 | }); 58 | }); 59 | 60 | it('should return output rows after Model update', function() { 61 | return User.create({ 62 | username: 'triggertest' 63 | }).then(function(user){ 64 | return User.update({ 65 | username: 'usernamechanged' 66 | }, { 67 | where: { 68 | id: user.get('id') 69 | } 70 | }); 71 | }) 72 | .then(function (user) { 73 | return expect(User.find({username: 'usernamechanged'})).to.eventually.have.property('username').which.equals('usernamechanged'); 74 | }); 75 | }); 76 | 77 | it('should successfully delete with a trigger on the table', function() { 78 | return User.create({ 79 | username: 'triggertest' 80 | }).then(function(user){ 81 | return user.destroy(); 82 | }).then(function (user) { 83 | return expect(User.find({username: 'triggertest'})).to.eventually.be.null; 84 | }); 85 | }); 86 | }); 87 | }); 88 | } 89 | -------------------------------------------------------------------------------- /test/integration/timezone.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var chai = require('chai') 4 | , expect = chai.expect 5 | , Support = require(__dirname + '/support') 6 | , dialect = Support.getTestDialect() 7 | , Sequelize = require(__dirname + '/../../index') 8 | , Promise = Sequelize.Promise; 9 | 10 | if (dialect !== 'sqlite') { 11 | // Sqlite does not support setting timezone 12 | 13 | describe(Support.getTestDialectTeaser('Timezone'), function() { 14 | beforeEach(function() { 15 | this.sequelizeWithTimezone = Support.createSequelizeInstance({ 16 | timezone: '+07:00' 17 | }); 18 | this.sequelizeWithNamedTimezone = Support.createSequelizeInstance({ 19 | timezone: 'America/New_York' 20 | }); 21 | }); 22 | 23 | it('returns the same value for current timestamp', function() { 24 | var now = 'now()' 25 | , startQueryTime = Date.now(); 26 | 27 | if (dialect === 'mssql') { 28 | now = 'GETDATE()'; 29 | } 30 | 31 | var query = 'SELECT ' + now + ' as now'; 32 | return Promise.all([ 33 | this.sequelize.query(query, { type: this.sequelize.QueryTypes.SELECT }), 34 | this.sequelizeWithTimezone.query(query, { type: this.sequelize.QueryTypes.SELECT }) 35 | ]).spread(function(now1, now2) { 36 | var elapsedQueryTime = (Date.now() - startQueryTime) + 1001; 37 | expect(now1[0].now.getTime()).to.be.closeTo(now2[0].now.getTime(), elapsedQueryTime); 38 | }); 39 | }); 40 | 41 | if (dialect === 'mysql') { 42 | it('handles existing timestamps', function() { 43 | var NormalUser = this.sequelize.define('user', {}) 44 | , TimezonedUser = this.sequelizeWithTimezone.define('user', {}); 45 | 46 | return this.sequelize.sync({ force: true }).bind(this).then(function() { 47 | return NormalUser.create({}); 48 | }).then(function(normalUser) { 49 | this.normalUser = normalUser; 50 | return TimezonedUser.findById(normalUser.id); 51 | }).then(function(timezonedUser) { 52 | // Expect 7 hours difference, in milliseconds. 53 | // This difference is expected since two instances, configured for each their timezone is trying to read the same timestamp 54 | // this test does not apply to PG, since it stores the timezone along with the timestamp. 55 | expect(this.normalUser.createdAt.getTime() - timezonedUser.createdAt.getTime()).to.be.closeTo(60 * 60 * 7 * 1000, 1000); 56 | }); 57 | }); 58 | 59 | it('handles named timezones', function() { 60 | var NormalUser = this.sequelize.define('user', {}) 61 | , TimezonedUser = this.sequelizeWithNamedTimezone.define('user', {}); 62 | 63 | return this.sequelize.sync({ force: true }).bind(this).then(function() { 64 | return TimezonedUser.create({}); 65 | }).then(function(timezonedUser) { 66 | return Promise.all([ 67 | NormalUser.findById(timezonedUser.id), 68 | TimezonedUser.findById(timezonedUser.id) 69 | ]); 70 | }).spread(function(normalUser, timezonedUser) { 71 | // Expect 5 hours difference, in milliseconds, +/- 1 hour for DST 72 | expect(normalUser.createdAt.getTime() - timezonedUser.createdAt.getTime()).to.be.closeTo(60 * 60 * 4 * 1000 * -1, 60 * 60 * 1000); 73 | }); 74 | }); 75 | } 76 | }); 77 | } 78 | -------------------------------------------------------------------------------- /test/integration/hooks/upsert.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | var chai = require('chai') 5 | , expect = chai.expect 6 | , Support = require(__dirname + '/../support') 7 | , DataTypes = require(__dirname + '/../../../lib/data-types') 8 | , sinon = require('sinon'); 9 | 10 | if (Support.sequelize.dialect.supports.upserts) { 11 | describe(Support.getTestDialectTeaser('Hooks'), function() { 12 | beforeEach(function() { 13 | this.User = this.sequelize.define('User', { 14 | username: { 15 | type: DataTypes.STRING, 16 | allowNull: false, 17 | unique: true //Either Primary Key/Unique Keys should be passed to upsert 18 | }, 19 | mood: { 20 | type: DataTypes.ENUM, 21 | values: ['happy', 'sad', 'neutral'] 22 | } 23 | }); 24 | return this.sequelize.sync({ force: true }); 25 | }); 26 | 27 | describe('#upsert', function() { 28 | describe('on success', function() { 29 | it('should run hooks', function() { 30 | var beforeHook = sinon.spy() 31 | , afterHook = sinon.spy(); 32 | 33 | this.User.beforeUpsert(beforeHook); 34 | this.User.afterUpsert(afterHook); 35 | 36 | return this.User.upsert({username: 'Toni', mood: 'happy'}).then(function() { 37 | expect(beforeHook).to.have.been.calledOnce; 38 | expect(afterHook).to.have.been.calledOnce; 39 | }); 40 | }); 41 | }); 42 | 43 | describe('on error', function() { 44 | it('should return an error from before', function() { 45 | var beforeHook = sinon.spy() 46 | , afterHook = sinon.spy(); 47 | 48 | this.User.beforeUpsert(function(values, options) { 49 | beforeHook(); 50 | throw new Error('Whoops!'); 51 | }); 52 | this.User.afterUpsert(afterHook); 53 | 54 | return expect(this.User.upsert({username: 'Toni', mood: 'happy'})).to.be.rejected.then(function(err) { 55 | expect(beforeHook).to.have.been.calledOnce; 56 | expect(afterHook).not.to.have.been.called; 57 | }); 58 | }); 59 | 60 | it('should return an error from after', function() { 61 | var beforeHook = sinon.spy() 62 | , afterHook = sinon.spy(); 63 | 64 | 65 | this.User.beforeUpsert(beforeHook); 66 | this.User.afterUpsert(function(user, options) { 67 | afterHook(); 68 | throw new Error('Whoops!'); 69 | }); 70 | 71 | return expect(this.User.upsert({username: 'Toni', mood: 'happy'})).to.be.rejected.then(function(err) { 72 | expect(beforeHook).to.have.been.calledOnce; 73 | expect(afterHook).to.have.been.calledOnce; 74 | }); 75 | }); 76 | }); 77 | 78 | describe('preserves changes to values', function() { 79 | it('beforeUpsert', function(){ 80 | var hookCalled = 0; 81 | var valuesOriginal = { mood: 'sad', username: 'leafninja' }; 82 | 83 | this.User.beforeUpsert(function(values, options) { 84 | values.mood = 'happy'; 85 | hookCalled++; 86 | }); 87 | 88 | return this.User.upsert(valuesOriginal).then(function() { 89 | expect(valuesOriginal.mood).to.equal('happy'); 90 | expect(hookCalled).to.equal(1); 91 | }); 92 | }); 93 | }); 94 | }); 95 | }); 96 | } 97 | -------------------------------------------------------------------------------- /lib/sql-string.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W110 */ 4 | const dataTypes = require('./data-types'); 5 | const _ = require('lodash'); 6 | 7 | function escape(val, timeZone, dialect, format) { 8 | let prependN = false; 9 | if (val === undefined || val === null) { 10 | return 'NULL'; 11 | } 12 | switch (typeof val) { 13 | case 'boolean': 14 | // SQLite doesn't have true/false support. MySQL aliases true/false to 1/0 15 | // for us. Postgres actually has a boolean type with true/false literals, 16 | // but sequelize doesn't use it yet. 17 | if (dialect === 'sqlite' || dialect === 'mssql') { 18 | return +!!val; 19 | } 20 | return '' + !!val; 21 | case 'number': 22 | return val + ''; 23 | case 'string': 24 | // In mssql, prepend N to all quoted vals which are originally a string (for 25 | // unicode compatibility) 26 | prependN = dialect === 'mssql'; 27 | break; 28 | } 29 | 30 | if (val instanceof Date) { 31 | val = dataTypes[dialect].DATE.prototype.stringify(val, { timezone: timeZone }); 32 | } 33 | 34 | if (Buffer.isBuffer(val)) { 35 | if (dataTypes[dialect].BLOB) { 36 | return dataTypes[dialect].BLOB.prototype.stringify(val); 37 | } 38 | 39 | return dataTypes.BLOB.prototype.stringify(val); 40 | } 41 | 42 | if (Array.isArray(val)) { 43 | const partialEscape = _.partial(escape, _, timeZone, dialect, format); 44 | if (dialect === 'postgres' && !format) { 45 | return dataTypes.ARRAY.prototype.stringify(val, {escape}); 46 | } 47 | return val.map(partialEscape); 48 | } 49 | 50 | if (!val.replace) { 51 | throw new Error('Invalid value ' + val); 52 | } 53 | 54 | if (dialect === 'postgres' || dialect === 'sqlite' || dialect === 'mssql') { 55 | // http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS 56 | // http://stackoverflow.com/q/603572/130598 57 | val = val.replace(/'/g, "''"); 58 | } else { 59 | val = val.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, s => { 60 | switch (s) { 61 | case '\0': return '\\0'; 62 | case '\n': return '\\n'; 63 | case '\r': return '\\r'; 64 | case '\b': return '\\b'; 65 | case '\t': return '\\t'; 66 | case '\x1a': return '\\Z'; 67 | default: return '\\' + s; 68 | } 69 | }); 70 | } 71 | return (prependN ? "N'" : "'") + val + "'"; 72 | } 73 | exports.escape = escape; 74 | 75 | function format(sql, values, timeZone, dialect) { 76 | values = [].concat(values); 77 | 78 | if (typeof sql !== 'string') { 79 | throw new Error('Invalid SQL string provided: ' + sql); 80 | } 81 | return sql.replace(/\?/g, match => { 82 | if (!values.length) { 83 | return match; 84 | } 85 | 86 | return escape(values.shift(), timeZone, dialect, true); 87 | }); 88 | } 89 | exports.format = format; 90 | 91 | function formatNamedParameters(sql, values, timeZone, dialect) { 92 | return sql.replace(/\:+(?!\d)(\w+)/g, (value, key) => { 93 | if ('postgres' === dialect && '::' === value.slice(0, 2)) { 94 | return value; 95 | } 96 | 97 | if (values[key] !== undefined) { 98 | return escape(values[key], timeZone, dialect, true); 99 | } else { 100 | throw new Error('Named parameter "' + value + '" has no value in the given object.'); 101 | } 102 | }); 103 | } 104 | exports.formatNamedParameters = formatNamedParameters; 105 | -------------------------------------------------------------------------------- /test/integration/hooks/restore.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | var chai = require('chai') 5 | , expect = chai.expect 6 | , Support = require(__dirname + '/../support') 7 | , DataTypes = require(__dirname + '/../../../lib/data-types') 8 | , sinon = require('sinon'); 9 | 10 | describe(Support.getTestDialectTeaser('Hooks'), function() { 11 | beforeEach(function() { 12 | this.User = this.sequelize.define('User', { 13 | username: { 14 | type: DataTypes.STRING, 15 | allowNull: false 16 | }, 17 | mood: { 18 | type: DataTypes.ENUM, 19 | values: ['happy', 'sad', 'neutral'] 20 | } 21 | }); 22 | 23 | this.ParanoidUser = this.sequelize.define('ParanoidUser', { 24 | username: DataTypes.STRING, 25 | mood: { 26 | type: DataTypes.ENUM, 27 | values: ['happy', 'sad', 'neutral'] 28 | } 29 | }, { 30 | paranoid: true 31 | }); 32 | 33 | return this.sequelize.sync({ force: true }); 34 | }); 35 | 36 | describe('#restore', function() { 37 | describe('on success', function() { 38 | it('should run hooks', function() { 39 | var beforeHook = sinon.spy() 40 | , afterHook = sinon.spy(); 41 | 42 | this.ParanoidUser.beforeRestore(beforeHook); 43 | this.ParanoidUser.afterRestore(afterHook); 44 | 45 | return this.ParanoidUser.create({username: 'Toni', mood: 'happy'}).then(function(user) { 46 | return user.destroy().then(function() { 47 | return user.restore().then(function(user) { 48 | expect(beforeHook).to.have.been.calledOnce; 49 | expect(afterHook).to.have.been.calledOnce; 50 | }); 51 | }); 52 | }); 53 | }); 54 | }); 55 | 56 | describe('on error', function() { 57 | it('should return an error from before', function() { 58 | var beforeHook = sinon.spy() 59 | , afterHook = sinon.spy(); 60 | 61 | this.ParanoidUser.beforeRestore(function(user, options) { 62 | beforeHook(); 63 | throw new Error('Whoops!'); 64 | }); 65 | this.ParanoidUser.afterRestore(afterHook); 66 | 67 | return this.ParanoidUser.create({username: 'Toni', mood: 'happy'}).then(function(user) { 68 | return user.destroy().then(function() { 69 | return expect(user.restore()).to.be.rejected.then(function() { 70 | expect(beforeHook).to.have.been.calledOnce; 71 | expect(afterHook).not.to.have.been.called; 72 | }); 73 | }); 74 | }); 75 | }); 76 | 77 | it('should return an error from after', function() { 78 | var beforeHook = sinon.spy() 79 | , afterHook = sinon.spy(); 80 | 81 | this.ParanoidUser.beforeRestore(beforeHook); 82 | this.ParanoidUser.afterRestore(function(user, options) { 83 | afterHook(); 84 | throw new Error('Whoops!'); 85 | }); 86 | 87 | return this.ParanoidUser.create({username: 'Toni', mood: 'happy'}).then(function(user) { 88 | return user.destroy().then(function() { 89 | return expect(user.restore()).to.be.rejected.then(function() { 90 | expect(beforeHook).to.have.been.calledOnce; 91 | expect(afterHook).to.have.been.calledOnce; 92 | }); 93 | }); 94 | }); 95 | }); 96 | }); 97 | }); 98 | 99 | }); 100 | -------------------------------------------------------------------------------- /test/integration/model/paranoid.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | var Support = require(__dirname + '/../support'); 5 | var DataTypes = require(__dirname + '/../../../lib/data-types'); 6 | var chai = require('chai'); 7 | var expect = chai.expect; 8 | var sinon = require('sinon'); 9 | var Support = require(__dirname + '/../support'); 10 | 11 | describe(Support.getTestDialectTeaser('Model'), function () { 12 | describe('paranoid', function () { 13 | before(function () { 14 | this.clock = sinon.useFakeTimers(); 15 | }); 16 | 17 | after(function () { 18 | this.clock.restore(); 19 | }); 20 | 21 | it('should be able to soft delete with timestamps', function () { 22 | const Account = this.sequelize.define('Account', { 23 | ownerId: { 24 | type: DataTypes.INTEGER, 25 | allowNull: false, 26 | field: 'owner_id' 27 | }, 28 | name: { 29 | type: DataTypes.STRING 30 | } 31 | }, { 32 | paranoid: true, 33 | timestamps: true 34 | }); 35 | 36 | return Account.sync({force: true}) 37 | .then(() => Account.create({ ownerId: 12 })) 38 | .then(() => Account.count()) 39 | .then((count) => { 40 | expect(count).to.be.equal(1); 41 | return Account.destroy({ where: { ownerId: 12 }}) 42 | .then((result) => { 43 | expect(result).to.be.equal(1); 44 | }); 45 | }) 46 | .then(() => Account.count()) 47 | .then((count) => { 48 | expect(count).to.be.equal(0); 49 | return Account.count({ paranoid: false }); 50 | }) 51 | .then((count) => { 52 | expect(count).to.be.equal(1); 53 | return Account.restore({ where: { ownerId: 12 }}); 54 | }) 55 | .then(() => Account.count()) 56 | .then((count) => { 57 | expect(count).to.be.equal(1); 58 | }); 59 | }); 60 | 61 | it('should be able to soft delete without timestamps', function () { 62 | const Account = this.sequelize.define('Account', { 63 | ownerId: { 64 | type: DataTypes.INTEGER, 65 | allowNull: false, 66 | field: 'owner_id' 67 | }, 68 | name: { 69 | type: DataTypes.STRING 70 | }, 71 | deletedAt: { 72 | type: DataTypes.DATE, 73 | allowNull: true, 74 | field: 'deleted_at' 75 | } 76 | }, { 77 | paranoid: true, 78 | timestamps: true, 79 | deletedAt: 'deletedAt', 80 | createdAt: false, 81 | updatedAt: false 82 | }); 83 | 84 | return Account.sync({force: true}) 85 | .then(() => Account.create({ ownerId: 12 })) 86 | .then(() => Account.count()) 87 | .then((count) => { 88 | expect(count).to.be.equal(1); 89 | return Account.destroy({ where: { ownerId: 12 }}); 90 | }) 91 | .then(() => Account.count()) 92 | .then((count) => { 93 | expect(count).to.be.equal(0); 94 | return Account.count({ paranoid: false }); 95 | }) 96 | .then((count) => { 97 | expect(count).to.be.equal(1); 98 | return Account.restore({ where: { ownerId: 12 }}); 99 | }) 100 | .then(() => Account.count()) 101 | .then((count) => { 102 | expect(count).to.be.equal(1); 103 | }); 104 | }); 105 | }); 106 | }); 107 | -------------------------------------------------------------------------------- /test/unit/instance/build.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | var chai = require('chai') 5 | , expect = chai.expect 6 | , Support = require(__dirname + '/../support') 7 | , DataTypes = require(__dirname + '/../../../lib/data-types') 8 | , current = Support.sequelize; 9 | 10 | describe(Support.getTestDialectTeaser('Instance'), function() { 11 | describe('build', function () { 12 | it('should populate NOW default values', function () { 13 | var Model = current.define('Model', { 14 | created_time: { 15 | type: DataTypes.DATE, 16 | allowNull: true, 17 | defaultValue: DataTypes.NOW 18 | }, 19 | updated_time: { 20 | type: DataTypes.DATE, 21 | allowNull: true, 22 | defaultValue: DataTypes.NOW 23 | }, 24 | ip: { 25 | type: DataTypes.STRING, 26 | validate: { 27 | isIP: true 28 | } 29 | }, 30 | ip2: { 31 | type: DataTypes.STRING, 32 | validate: { 33 | isIP: { 34 | msg: 'test' 35 | } 36 | } 37 | } 38 | }, { 39 | timestamp: false 40 | }) 41 | , instance; 42 | 43 | instance = Model.build({ip: '127.0.0.1', ip2: '0.0.0.0'}); 44 | 45 | expect(instance.get('created_time')).to.be.ok; 46 | expect(instance.get('created_time')).to.be.an.instanceof(Date); 47 | 48 | expect(instance.get('updated_time')).to.be.ok; 49 | expect(instance.get('updated_time')).to.be.an.instanceof(Date); 50 | 51 | return instance.validate(); 52 | }); 53 | 54 | it('should populate explicitly undefined UUID primary keys', function () { 55 | var Model = current.define('Model', { 56 | id: { 57 | type: DataTypes.UUID, 58 | primaryKey: true, 59 | allowNull: false, 60 | defaultValue: DataTypes.UUIDV4 61 | } 62 | }) 63 | , instance; 64 | 65 | instance = Model.build({ 66 | id: undefined 67 | }); 68 | 69 | expect(instance.get('id')).not.to.be.undefined; 70 | expect(instance.get('id')).to.be.ok; 71 | }); 72 | 73 | it('should populate undefined columns with default value', function () { 74 | var Model = current.define('Model', { 75 | number1: { 76 | type: DataTypes.INTEGER, 77 | defaultValue: 1 78 | }, 79 | number2: { 80 | type: DataTypes.INTEGER, 81 | defaultValue: 2 82 | } 83 | }) 84 | , instance; 85 | 86 | instance = Model.build({ 87 | number1: undefined 88 | }); 89 | 90 | expect(instance.get('number1')).not.to.be.undefined; 91 | expect(instance.get('number1')).to.equal(1); 92 | expect(instance.get('number2')).not.to.be.undefined; 93 | expect(instance.get('number2')).to.equal(2); 94 | }); 95 | 96 | it('should clone the default values', function () { 97 | var Model = current.define('Model', { 98 | data: { 99 | type: DataTypes.JSONB, 100 | defaultValue: { foo: 'bar' } 101 | } 102 | }) 103 | , instance; 104 | 105 | instance = Model.build(); 106 | instance.data.foo = 'biz'; 107 | 108 | expect(instance.get('data')).to.eql({ foo: 'biz' }); 109 | expect(Model.build().get('data')).to.eql({ foo: 'bar' }); 110 | }); 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /test/integration/dialects/postgres/hstore.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W110 */ 4 | var chai = require('chai') 5 | , expect = chai.expect 6 | , Support = require(__dirname + '/../../support') 7 | , dialect = Support.getTestDialect() 8 | , hstore = require('../../../../lib/dialects/postgres/hstore'); 9 | 10 | if (dialect.match(/^postgres/)) { 11 | describe('[POSTGRES Specific] hstore', function() { 12 | describe('stringify', function() { 13 | it('should handle empty objects correctly', function() { 14 | expect(hstore.stringify({ })).to.equal(''); 15 | }); 16 | 17 | it('should handle null values correctly', function() { 18 | expect(hstore.stringify({ null: null })).to.equal('"null"=>NULL'); 19 | }); 20 | 21 | it('should handle null values correctly', function() { 22 | expect(hstore.stringify({ foo: null })).to.equal('"foo"=>NULL'); 23 | }); 24 | 25 | it('should handle empty string correctly', function() { 26 | expect(hstore.stringify({foo: ''})).to.equal('"foo"=>\"\"'); 27 | }); 28 | 29 | it('should handle a string with backslashes correctly', function() { 30 | expect(hstore.stringify({foo: '\\'})).to.equal('"foo"=>"\\\\"'); 31 | }); 32 | 33 | it('should handle a string with double quotes correctly', function() { 34 | expect(hstore.stringify({foo: '""a"'})).to.equal('"foo"=>"\\"\\"a\\""'); 35 | }); 36 | 37 | it('should handle a string with single quotes correctly', function() { 38 | expect(hstore.stringify({foo: "''a'"})).to.equal('"foo"=>"\'\'\'\'a\'\'"'); 39 | }); 40 | 41 | it('should handle simple objects correctly', function() { 42 | expect(hstore.stringify({ test: 'value' })).to.equal('"test"=>"value"'); 43 | }); 44 | 45 | }); 46 | 47 | describe('parse', function() { 48 | it('should handle a null object correctly', function() { 49 | expect(hstore.parse(null)).to.deep.equal(null); 50 | }); 51 | 52 | it('should handle empty string correctly', function() { 53 | expect(hstore.parse('"foo"=>\"\"')).to.deep.equal({foo: ''}); 54 | }); 55 | 56 | it('should handle a string with double quotes correctly', function() { 57 | expect(hstore.parse('"foo"=>"\\\"\\\"a\\\""')).to.deep.equal({foo: '\"\"a\"'}); 58 | }); 59 | 60 | it('should handle a string with single quotes correctly', function() { 61 | expect(hstore.parse('"foo"=>"\'\'\'\'a\'\'"')).to.deep.equal({foo: "''a'"}); 62 | }); 63 | 64 | it('should handle a string with backslashes correctly', function() { 65 | expect(hstore.parse('"foo"=>"\\\\"')).to.deep.equal({foo: '\\'}); 66 | }); 67 | 68 | it('should handle empty objects correctly', function() { 69 | expect(hstore.parse('')).to.deep.equal({ }); 70 | }); 71 | 72 | it('should handle simple objects correctly', function() { 73 | expect(hstore.parse('"test"=>"value"')).to.deep.equal({ test: 'value' }); 74 | }); 75 | 76 | }); 77 | describe('stringify and parse', function() { 78 | it('should stringify then parse back the same structure', function() { 79 | var testObj = {foo: 'bar', count: '1', emptyString: '', quotyString: '""', extraQuotyString: '"""a"""""', backslashes: '\\f023', moreBackslashes: '\\f\\0\\2\\1', backslashesAndQuotes: '\\"\\"uhoh"\\"', nully: null}; 80 | expect(hstore.parse(hstore.stringify(testObj))).to.deep.equal(testObj); 81 | expect(hstore.parse(hstore.stringify(hstore.parse(hstore.stringify(testObj))))).to.deep.equal(testObj); 82 | }); 83 | }); 84 | }); 85 | } 86 | -------------------------------------------------------------------------------- /docs/api/deferrable.md: -------------------------------------------------------------------------------- 1 | 2 | ## `Deferrable()` -> `object` 3 | [View code](https://github.com/sequelize/sequelize/blob/3e5b8772ef75169685fc96024366bca9958fee63/lib/deferrable.js#L39) 4 | 5 | A collection of properties related to deferrable constraints. It can be used to 6 | make foreign key constraints deferrable and to set the constraints within a 7 | transaction. This is only supported in PostgreSQL. 8 | 9 | The foreign keys can be configured like this. It will create a foreign key 10 | that will check the constraints immediately when the data was inserted. 11 | 12 | ```js 13 | sequelize.define('Model', { 14 | foreign_id: { 15 | type: Sequelize.INTEGER, 16 | references: { 17 | model: OtherModel, 18 | key: 'id', 19 | deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE 20 | } 21 | } 22 | }); 23 | ``` 24 | 25 | The constraints can be configured in a transaction like this. It will 26 | trigger a query once the transaction has been started and set the constraints 27 | to be checked at the very end of the transaction. 28 | 29 | ```js 30 | sequelize.transaction({ 31 | deferrable: Sequelize.Deferrable.SET_DEFERRED 32 | }); 33 | ``` 34 | 35 | *** 36 | 37 | 38 | ## `INITIALLY_DEFERRED()` 39 | [View code](https://github.com/sequelize/sequelize/blob/3e5b8772ef75169685fc96024366bca9958fee63/lib/deferrable.js#L59) 40 | 41 | A property that will defer constraints checks to the end of transactions. 42 | 43 | *** 44 | 45 | 46 | ## `INITIALLY_IMMEDIATE()` 47 | [View code](https://github.com/sequelize/sequelize/blob/3e5b8772ef75169685fc96024366bca9958fee63/lib/deferrable.js#L76) 48 | 49 | A property that will trigger the constraint checks immediately 50 | 51 | *** 52 | 53 | 54 | ## `NOT()` 55 | [View code](https://github.com/sequelize/sequelize/blob/3e5b8772ef75169685fc96024366bca9958fee63/lib/deferrable.js#L95) 56 | 57 | A property that will set the constraints to not deferred. This is 58 | the default in PostgreSQL and it make it impossible to dynamically 59 | defer the constraints within a transaction. 60 | 61 | *** 62 | 63 | 64 | ## `SET_DEFERRED(constraints)` 65 | [View code](https://github.com/sequelize/sequelize/blob/3e5b8772ef75169685fc96024366bca9958fee63/lib/deferrable.js#L114) 66 | 67 | A property that will trigger an additional query at the beginning of a 68 | transaction which sets the constraints to deferred. 69 | 70 | **Params:** 71 | 72 | | Name | Type | Description | 73 | | ---- | ---- | ----------- | 74 | | constraints | Array | An array of constraint names. Will defer all constraints by default. | 75 | 76 | 77 | *** 78 | 79 | 80 | ## `SET_IMMEDIATE(constraints)` 81 | [View code](https://github.com/sequelize/sequelize/blob/3e5b8772ef75169685fc96024366bca9958fee63/lib/deferrable.js#L135) 82 | 83 | A property that will trigger an additional query at the beginning of a 84 | transaction which sets the constraints to immediately. 85 | 86 | **Params:** 87 | 88 | | Name | Type | Description | 89 | | ---- | ---- | ----------- | 90 | | constraints | Array | An array of constraint names. Will defer all constraints by default. | 91 | 92 | 93 | *** 94 | 95 | _This document is automatically generated based on source code comments. Please do not edit it directly, as your changes will be ignored. Please write on IRC, open an issue or a create a pull request if you feel something can be improved. For help on how to write source code documentation see [JSDoc](http://usejsdoc.org) and [dox](https://github.com/tj/dox)_ -------------------------------------------------------------------------------- /test/integration/include/paranoid.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var chai = require('chai') 4 | , expect = chai.expect 5 | , sinon = require('sinon') 6 | , Support = require(__dirname + '/../support') 7 | , DataTypes = require(__dirname + '/../../../lib/data-types'); 8 | 9 | describe(Support.getTestDialectTeaser('Paranoid'), function() { 10 | 11 | beforeEach(function( ) { 12 | var S = this.sequelize, 13 | DT = DataTypes, 14 | 15 | A = this.A = S.define('A', { name: DT.STRING }, { paranoid: true }), 16 | B = this.B = S.define('B', { name: DT.STRING }, { paranoid: true }), 17 | C = this.C = S.define('C', { name: DT.STRING }, { paranoid: true }), 18 | D = this.D = S.define('D', { name: DT.STRING }, { paranoid: true }); 19 | 20 | A.belongsTo(B); 21 | A.belongsToMany(D, {through: 'a_d'}); 22 | A.hasMany(C); 23 | 24 | B.hasMany(A); 25 | B.hasMany(C); 26 | 27 | C.belongsTo(A); 28 | C.belongsTo(B); 29 | 30 | D.belongsToMany(A, {through: 'a_d'}); 31 | 32 | return S.sync({ force: true }); 33 | }); 34 | 35 | before(function () { 36 | this.clock = sinon.useFakeTimers(); 37 | }); 38 | 39 | after(function () { 40 | this.clock.restore(); 41 | }); 42 | 43 | it('paranoid with timestamps: false should be ignored / not crash', function() { 44 | var S = this.sequelize 45 | , Test = S.define('Test', { 46 | name: DataTypes.STRING 47 | },{ 48 | timestamps: false, 49 | paranoid: true 50 | }); 51 | 52 | return S.sync({ force: true }).then(function() { 53 | return Test.findById(1); 54 | }); 55 | }); 56 | 57 | it('test if non required is marked as false', function( ) { 58 | var A = this.A, 59 | B = this.B, 60 | options = { 61 | include: [ 62 | { 63 | model: B, 64 | required: false 65 | } 66 | ] 67 | }; 68 | 69 | return A.find(options).then(function() { 70 | expect(options.include[0].required).to.be.equal(false); 71 | }); 72 | }); 73 | 74 | it('test if required is marked as true', function( ) { 75 | var A = this.A, 76 | B = this.B, 77 | options = { 78 | include: [ 79 | { 80 | model: B, 81 | required: true 82 | } 83 | ] 84 | }; 85 | 86 | return A.find(options).then(function() { 87 | expect(options.include[0].required).to.be.equal(true); 88 | }); 89 | }); 90 | 91 | it('should not load paranoid, destroyed instances, with a non-paranoid parent', function () { 92 | var X = this.sequelize.define('x', { 93 | name: DataTypes.STRING 94 | }, { 95 | paranoid: false 96 | }); 97 | 98 | var Y = this.sequelize.define('y', { 99 | name: DataTypes.STRING 100 | }, { 101 | timestamps: true, 102 | paranoid: true 103 | }); 104 | 105 | X.hasMany(Y); 106 | 107 | return this.sequelize.sync({ force: true}).bind(this).then(function () { 108 | return this.sequelize.Promise.all([ 109 | X.create(), 110 | Y.create() 111 | ]); 112 | }).spread(function (x, y) { 113 | this.x = x; 114 | this.y = y; 115 | 116 | return x.addY(y); 117 | }).then(function () { 118 | return this.y.destroy(); 119 | }).then(function () { 120 | //prevent CURRENT_TIMESTAMP to be same 121 | this.clock.tick(1000); 122 | 123 | return X.findAll({ 124 | include: [Y] 125 | }).get(0); 126 | }).then(function (x) { 127 | expect(x.ys).to.have.length(0); 128 | }); 129 | }); 130 | }); 131 | -------------------------------------------------------------------------------- /test/integration/model/scope/findAndCount.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint -W030 */ 4 | /* jshint -W110 */ 5 | var chai = require('chai') 6 | , Sequelize = require('../../../../index') 7 | , expect = chai.expect 8 | , Support = require(__dirname + '/../../support'); 9 | 10 | describe(Support.getTestDialectTeaser('Model'), function() { 11 | describe('scope', function () { 12 | 13 | describe('findAndCount', function () { 14 | 15 | beforeEach(function () { 16 | this.ScopeMe = this.sequelize.define('ScopeMe', { 17 | username: Sequelize.STRING, 18 | email: Sequelize.STRING, 19 | access_level: Sequelize.INTEGER, 20 | other_value: Sequelize.INTEGER 21 | }, { 22 | defaultScope: { 23 | where: { 24 | access_level: { 25 | gte: 5 26 | } 27 | }, 28 | attributes: ['username', 'email', 'access_level'] 29 | }, 30 | scopes: { 31 | lowAccess: { 32 | where: { 33 | access_level: { 34 | lte: 5 35 | } 36 | } 37 | }, 38 | withOrder: { 39 | order: ['username'] 40 | } 41 | } 42 | }); 43 | 44 | return this.sequelize.sync({force: true}).then(function() { 45 | var records = [ 46 | {username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7}, 47 | {username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11}, 48 | {username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10}, 49 | {username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7} 50 | ]; 51 | return this.ScopeMe.bulkCreate(records); 52 | }.bind(this)); 53 | }); 54 | 55 | it('should apply defaultScope', function () { 56 | return this.ScopeMe.findAndCount().then(function(result) { 57 | expect(result.count).to.equal(2); 58 | expect(result.rows.length).to.equal(2); 59 | }); 60 | }); 61 | 62 | it('should be able to override default scope', function () { 63 | return this.ScopeMe.findAndCount({ where: { access_level: { gt: 5 }}}) 64 | .then(function(result) { 65 | expect(result.count).to.equal(1); 66 | expect(result.rows.length).to.equal(1); 67 | }); 68 | }); 69 | 70 | it('should be able to unscope', function () { 71 | return this.ScopeMe.unscoped().findAndCount({ limit: 1 }) 72 | .then(function(result) { 73 | expect(result.count).to.equal(4); 74 | expect(result.rows.length).to.equal(1); 75 | }); 76 | }); 77 | 78 | it('should be able to apply other scopes', function () { 79 | return this.ScopeMe.scope('lowAccess').findAndCount() 80 | .then(function(result) { 81 | expect(result.count).to.equal(3); 82 | }); 83 | }); 84 | 85 | it('should be able to merge scopes with where', function () { 86 | return this.ScopeMe.scope('lowAccess') 87 | .findAndCount({ where: { username: 'dan'}}).then(function(result) { 88 | expect(result.count).to.equal(1); 89 | }); 90 | }); 91 | 92 | it('should ignore the order option if it is found within the scope', function () { 93 | return this.ScopeMe.scope('withOrder').findAndCount() 94 | .then(function(result) { 95 | expect(result.count).to.equal(4); 96 | }); 97 | }); 98 | }); 99 | }); 100 | }); 101 | -------------------------------------------------------------------------------- /lib/deferrable.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | 5 | 6 | /** 7 | * A collection of properties related to deferrable constraints. It can be used to 8 | * make foreign key constraints deferrable and to set the constraints within a 9 | * transaction. This is only supported in PostgreSQL. 10 | * 11 | * The foreign keys can be configured like this. It will create a foreign key 12 | * that will check the constraints immediately when the data was inserted. 13 | * 14 | * ```js 15 | * sequelize.define('Model', { 16 | * foreign_id: { 17 | * type: Sequelize.INTEGER, 18 | * references: { 19 | * model: OtherModel, 20 | * key: 'id', 21 | * deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE 22 | * } 23 | * } 24 | * }); 25 | * ``` 26 | * 27 | * The constraints can be configured in a transaction like this. It will 28 | * trigger a query once the transaction has been started and set the constraints 29 | * to be checked at the very end of the transaction. 30 | * 31 | * ```js 32 | * sequelize.transaction({ 33 | * deferrable: Sequelize.Deferrable.SET_DEFERRED 34 | * }); 35 | * ``` 36 | * 37 | * @namespace Deferrable 38 | * @memberof Sequelize 39 | * 40 | * @property INITIALLY_DEFERRED Defer constraints checks to the end of transactions. 41 | * @property INITIALLY_IMMEDIATE Trigger the constraint checks immediately 42 | * @property NOT Set the constraints to not deferred. This is the default in PostgreSQL and it make it impossible to dynamically defer the constraints within a transaction. 43 | * @property SET_DEFERRED 44 | * @property SET_IMMEDIATE 45 | */ 46 | var Deferrable = module.exports = { 47 | INITIALLY_DEFERRED, 48 | INITIALLY_IMMEDIATE, 49 | NOT, 50 | SET_DEFERRED, 51 | SET_IMMEDIATE 52 | }; 53 | 54 | function ABSTRACT() {} 55 | 56 | ABSTRACT.prototype.toString = function() { 57 | return this.toSql.apply(this, arguments); 58 | }; 59 | 60 | function INITIALLY_DEFERRED() { 61 | if (!(this instanceof INITIALLY_DEFERRED)) { 62 | return new INITIALLY_DEFERRED(); 63 | } 64 | } 65 | util.inherits(INITIALLY_DEFERRED, ABSTRACT); 66 | 67 | INITIALLY_DEFERRED.prototype.toSql = function() { 68 | return 'DEFERRABLE INITIALLY DEFERRED'; 69 | }; 70 | 71 | function INITIALLY_IMMEDIATE() { 72 | if (!(this instanceof INITIALLY_IMMEDIATE)) { 73 | return new INITIALLY_IMMEDIATE(); 74 | } 75 | } 76 | util.inherits(INITIALLY_IMMEDIATE, ABSTRACT); 77 | 78 | INITIALLY_IMMEDIATE.prototype.toSql = function() { 79 | return 'DEFERRABLE INITIALLY IMMEDIATE'; 80 | }; 81 | 82 | function NOT() { 83 | if (!(this instanceof NOT)) { 84 | return new NOT(); 85 | } 86 | } 87 | util.inherits(NOT, ABSTRACT); 88 | 89 | NOT.prototype.toSql = function() { 90 | return 'NOT DEFERRABLE'; 91 | }; 92 | 93 | function SET_DEFERRED(constraints) { 94 | if (!(this instanceof SET_DEFERRED)) { 95 | return new SET_DEFERRED(constraints); 96 | } 97 | 98 | this.constraints = constraints; 99 | } 100 | util.inherits(SET_DEFERRED, ABSTRACT); 101 | 102 | SET_DEFERRED.prototype.toSql = function(queryGenerator) { 103 | return queryGenerator.setDeferredQuery(this.constraints); 104 | }; 105 | 106 | function SET_IMMEDIATE(constraints) { 107 | if (!(this instanceof SET_IMMEDIATE)) { 108 | return new SET_IMMEDIATE(constraints); 109 | } 110 | 111 | this.constraints = constraints; 112 | } 113 | util.inherits(SET_IMMEDIATE, ABSTRACT); 114 | 115 | SET_IMMEDIATE.prototype.toSql = function(queryGenerator) { 116 | return queryGenerator.setImmediateQuery(this.constraints); 117 | }; 118 | 119 | Object.keys(Deferrable).forEach(function(key) { 120 | var DeferrableType = Deferrable[key]; 121 | 122 | DeferrableType.toString = function() { 123 | var instance = new DeferrableType(); 124 | return instance.toString.apply(instance, arguments); 125 | }; 126 | }); 127 | --------------------------------------------------------------------------------