├── 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 |
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.