├── .editorconfig ├── .esdoc.json ├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── stale.yml ├── .gitignore ├── .npmignore ├── .npmrc ├── .travis.yml ├── CONTACT.md ├── CONTRIBUTING.DOCS.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── appveyor-setup.ps1 ├── appveyor.yml ├── codecov.yml ├── docker-compose.yml ├── docs ├── ROUTER ├── associations.md ├── css │ ├── style.css │ └── theme.css ├── favicon.ico ├── getting-started.md ├── hooks.md ├── images │ ├── bitovi-logo.png │ ├── clevertech.png │ ├── connected-cars.png │ ├── filsh.png │ ├── logo-small.png │ ├── logo-snaplytics-green.png │ ├── logo.png │ ├── metamarkets.png │ ├── shutterstock.png │ ├── slack.svg │ └── walmart-labs-logo.png ├── imprint.md ├── index.md ├── instances.md ├── legacy.md ├── migrations.md ├── models-definition.md ├── models-usage.md ├── plugins │ └── esdoc-sequelize.js ├── querying.md ├── raw-queries.md ├── scopes.md ├── scripts │ └── script.js ├── transactions.md ├── upgrade-to-v4.md ├── usage.md └── whos-using.md ├── index.js ├── lib ├── associations │ ├── base.js │ ├── belongs-to-many.js │ ├── belongs-to.js │ ├── has-many.js │ ├── has-one.js │ ├── helpers.js │ ├── index.js │ └── mixin.js ├── data-types.js ├── deferrable.js ├── dialects │ ├── abstract │ │ ├── connection-manager.js │ │ ├── index.js │ │ ├── query-generator.js │ │ └── query.js │ ├── mssql │ │ ├── connection-manager.js │ │ ├── data-types.js │ │ ├── index.js │ │ ├── query-generator.js │ │ ├── query-interface.js │ │ ├── query.js │ │ └── resource-lock.js │ ├── mysql │ │ ├── connection-manager.js │ │ ├── data-types.js │ │ ├── index.js │ │ ├── query-generator.js │ │ ├── query-interface.js │ │ └── query.js │ ├── parserStore.js │ ├── postgres │ │ ├── connection-manager.js │ │ ├── data-types.js │ │ ├── hstore.js │ │ ├── index.js │ │ ├── query-generator.js │ │ ├── query.js │ │ └── range.js │ └── sqlite │ │ ├── connection-manager.js │ │ ├── data-types.js │ │ ├── index.js │ │ ├── query-generator.js │ │ ├── query-interface.js │ │ └── query.js ├── errors.js ├── errors │ └── index.js ├── hooks.js ├── instance-validator.js ├── model-manager.js ├── model.js ├── operators.js ├── promise.js ├── query-interface.js ├── query-types.js ├── sequelize.js ├── sql-string.js ├── transaction.js ├── utils.js └── utils │ ├── inherits.js │ ├── logger.js │ ├── parameter-validator.js │ └── validator-extras.js ├── package.json ├── sscce_template.js └── test ├── config ├── .docker.env ├── config.js └── options.js ├── integration ├── assets │ ├── es6project.js │ └── project.js ├── associations │ ├── alias.test.js │ ├── belongs-to-many.test.js │ ├── belongs-to.test.js │ ├── has-many.test.js │ ├── has-one.test.js │ ├── multiple-level-filters.test.js │ ├── scope.test.js │ └── self.test.js ├── cls.test.js ├── configuration.test.js ├── data-types.test.js ├── dialects │ ├── abstract │ │ └── connection-manager.test.js │ ├── mssql │ │ ├── connection-manager.test.js │ │ └── query-queue.test.js │ ├── mysql │ │ ├── associations.test.js │ │ ├── connector-manager.test.js │ │ ├── dao-factory.test.js │ │ └── errors.test.js │ ├── postgres │ │ ├── associations.test.js │ │ ├── connection-manager.test.js │ │ ├── dao.test.js │ │ ├── data-types.test.js │ │ ├── error.test.js │ │ ├── hstore.test.js │ │ ├── query-interface.test.js │ │ └── range.test.js │ └── sqlite │ │ ├── dao-factory.test.js │ │ ├── dao.test.js │ │ └── test.sqlite ├── error.test.js ├── hooks │ ├── associations.test.js │ ├── bulkOperation.test.js │ ├── count.test.js │ ├── create.test.js │ ├── destroy.test.js │ ├── find.test.js │ ├── hooks.test.js │ ├── restore.test.js │ ├── updateAttributes.test.js │ ├── upsert.test.js │ └── validate.test.js ├── include.test.js ├── include │ ├── find.test.js │ ├── findAll.test.js │ ├── findAndCountAll.test.js │ ├── paranoid.test.js │ ├── schema.test.js │ └── seperate.test.js ├── instance.test.js ├── instance.validations.test.js ├── instance │ ├── update.test.js │ └── values.test.js ├── json.test.js ├── model.test.js ├── model │ ├── attributes.test.js │ ├── attributes │ │ ├── field.test.js │ │ ├── operators.test.js │ │ └── types.test.js │ ├── count.test.js │ ├── create.test.js │ ├── create │ │ └── include.test.js │ ├── find.test.js │ ├── findAll.test.js │ ├── findAll │ │ ├── group.test.js │ │ ├── groupedLimit.test.js │ │ └── order.test.js │ ├── geography.test.js │ ├── geometry.test.js │ ├── json.test.js │ ├── optimistic_locking.test.js │ ├── paranoid.test.js │ ├── schema.test.js │ ├── scope.test.js │ ├── scope │ │ ├── aggregate.test.js │ │ ├── associations.test.js │ │ ├── count.test.js │ │ ├── destroy.test.js │ │ ├── find.test.js │ │ ├── findAndCount.test.js │ │ └── update.test.js │ ├── searchPath.test.js │ ├── sync.test.js │ ├── update.test.js │ └── upsert.test.js ├── pool.test.js ├── query-interface.test.js ├── replication.test.js ├── schema.test.js ├── sequelize.test.js ├── sequelize.transaction.test.js ├── sequelize │ ├── deferrable.test.js │ └── log.test.js ├── support.js ├── timezone.test.js ├── tmp │ └── .gitkeep ├── transaction.test.js ├── trigger.test.js ├── utils.test.js └── vectors.test.js ├── support.js ├── supportShim.js ├── tmp └── .gitkeep └── unit ├── associations ├── association.test.js ├── belongs-to-many.test.js ├── belongs-to.test.js ├── dont-modify-options.test.js ├── has-many.test.js └── has-one.test.js ├── configuration.test.js ├── connection-manager.test.js ├── dialects ├── abstract │ ├── query-generator.test.js │ └── query.test.js ├── mssql │ ├── connection-manager.js │ ├── query-generator.test.js │ ├── query.js │ └── resource-lock.test.js ├── mysql │ ├── errors.test.js │ └── query-generator.test.js ├── postgres │ └── query-generator.test.js └── sqlite │ └── query-generator.test.js ├── errors.test.js ├── hooks.test.js ├── increment.test.js ├── instance-validator.test.js ├── instance ├── build.test.js ├── changed.test.js ├── decrement.test.js ├── destroy.test.js ├── get.test.js ├── increment.test.js ├── is-soft-deleted.test.js ├── previous.test.js ├── reload.test.js ├── restore.test.js ├── save.test.js ├── set.test.js └── to-json.test.js ├── model ├── bulkcreate.test.js ├── count.test.js ├── define.test.js ├── destroy.test.js ├── find-create-find.test.js ├── find-or-create.test.js ├── findall.test.js ├── findone.test.js ├── helpers.test.js ├── include.test.js ├── indexes.test.js ├── overwriting-builtins.test.js ├── removeAttribute.test.js ├── schema.test.js ├── scope.test.js ├── update.test.js ├── upsert.test.js └── validation.test.js ├── promise.test.js ├── query.test.js ├── sql ├── add-column.test.js ├── add-constraint.test.js ├── change-column.test.js ├── create-schema.test.js ├── create-table.test.js ├── data-types.test.js ├── delete.test.js ├── enum.test.js ├── generateJoin.test.js ├── get-constraint-snippet.test.js ├── index.test.js ├── insert.test.js ├── json.test.js ├── offset-limit.test.js ├── order.test.js ├── remove-column.test.js ├── remove-constraint.test.js ├── select.test.js ├── show-constraints.test.js ├── update.test.js └── where.test.js ├── support.js ├── transaction.test.js └── utils.test.js /.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 | 22 | [Makefile] 23 | indent_style = tabs 24 | -------------------------------------------------------------------------------- /.esdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": " Sequelize | The node.js ORM for PostgreSQL, MySQL, SQLite and MSSQL", 3 | "source": "./lib", 4 | "destination": "./esdoc", 5 | "access": ["public"], 6 | "builtinExternal": false, 7 | "unexportIdentifier": true, 8 | "undocumentIdentifier": false, 9 | "includeSource": false, 10 | "coverage": false, 11 | "lint": false, 12 | "plugins": [ 13 | {"name": "./docs/plugins/esdoc-sequelize.js"} 14 | ], 15 | "styles": [ 16 | "./docs/css/style.css", 17 | "./docs/css/theme.css" 18 | ], 19 | "scripts": [ 20 | "./docs/scripts/script.js" 21 | ], 22 | "manual": { 23 | "globalIndex": true, 24 | "index": "./docs/index.md", 25 | "asset": "./docs/images", 26 | "badge": false, 27 | "installation": [ 28 | "./docs/getting-started.md", 29 | "./docs/usage.md" 30 | ], 31 | "tutorial": [ 32 | "./docs/models-definition.md", 33 | "./docs/models-usage.md", 34 | "./docs/querying.md", 35 | "./docs/instances.md", 36 | "./docs/associations.md", 37 | "./docs/transactions.md", 38 | "./docs/scopes.md", 39 | "./docs/hooks.md", 40 | "./docs/raw-queries.md", 41 | "./docs/migrations.md", 42 | "./docs/upgrade-to-v4.md" 43 | ], 44 | "advanced": [ 45 | "./docs/legacy.md" 46 | ], 47 | "faq": [ 48 | "./docs/whos-using.md", 49 | "./docs/imprint.md" 50 | ] 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "mocha/no-exclusive-tests": "error", 4 | "mocha/no-skipped-tests": "warn", 5 | 6 | "array-bracket-spacing": "error", 7 | "comma-spacing": "error", 8 | "key-spacing": "error", 9 | "keyword-spacing": "error", 10 | 11 | "no-console": "off", 12 | "no-extra-parens": "warn", 13 | "valid-jsdoc": "off", 14 | "new-cap": [ 15 | "warn", 16 | { 17 | "properties": false 18 | } 19 | ], 20 | "no-extra-boolean-cast": "warn", 21 | "strict": [ 22 | "warn", 23 | "global" 24 | ], 25 | "no-var": "error", 26 | "prefer-const": "error", 27 | "semi": [ 28 | "error", 29 | "always" 30 | ], 31 | "space-before-function-paren": [ 32 | "warn", 33 | "never" 34 | ], 35 | "space-before-blocks": "error", 36 | "prefer-arrow-callback": "error", 37 | "arrow-parens": [ 38 | "error", 39 | "as-needed" 40 | ], 41 | "comma-style": [ 42 | "warn", 43 | "last" 44 | ], 45 | "no-bitwise": "off", 46 | "no-cond-assign": [ 47 | "error", 48 | "except-parens" 49 | ], 50 | "curly": "off", 51 | "eqeqeq": "error", 52 | "no-extend-native": "error", 53 | "wrap-iife": [ 54 | "error", 55 | "any" 56 | ], 57 | "indent": [ 58 | "error", 59 | 2, 60 | { 61 | "SwitchCase": 1 62 | } 63 | ], 64 | "no-use-before-define": "off", 65 | "no-caller": "error", 66 | "no-undef": "error", 67 | "no-unused-vars": "error", 68 | "no-irregular-whitespace": "error", 69 | "max-depth": [ 70 | "error", 71 | 8 72 | ], 73 | "quotes": [ 74 | "error", 75 | "single", 76 | { 77 | "avoidEscape": true 78 | } 79 | ], 80 | "linebreak-style": "error", 81 | "no-loop-func": "warn", 82 | "object-shorthand": "error", 83 | "one-var-declaration-per-line": "warn", 84 | "comma-dangle": "warn", 85 | "no-shadow": "warn", 86 | "camelcase": "warn" 87 | }, 88 | "parserOptions": { 89 | "ecmaVersion": 6, 90 | "sourceType": "script" 91 | }, 92 | "plugins": [ 93 | "mocha" 94 | ], 95 | "env": { 96 | "node": true, 97 | "mocha": true, 98 | "es6": true 99 | } 100 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | ## What are you doing? 11 | 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 | __Dialect version:__ XXX 28 | __Database version:__ XXX 29 | __Sequelize version:__ XXX 30 | __Tested with latest release:__ No (If yes, specify that version) 31 | 32 | 33 | **Note :** _Your issue may be ignored OR closed by maintainers if it's not tested against latest version OR does not follow issue template._ 34 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | ### Pull Request check-list 10 | 11 | _Please make sure to review and check all of these items:_ 12 | 13 | - [ ] Does `npm run test` or `npm run test-DIALECT` pass with this change (including linting)? 14 | - [ ] Does the description below contain a link to an existing issue (Closes #[issue]) or a description of the issue you are solving? 15 | - [ ] Have you added new tests to prevent regressions? 16 | - [ ] Is a documentation update included (if this change modifies existing APIs, or introduces new ones)? 17 | - [ ] Did you follow the commit message conventions explained in [CONTRIBUTING.md](../CONTRIBUTING.md)? 18 | 19 | 20 | 21 | ### Description of change 22 | 23 | 24 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | 2 | # Configuration for probot-stale - https://github.com/probot/stale 3 | 4 | # Number of days of inactivity before an issue becomes stale 5 | daysUntilStale: 100 6 | 7 | # Number of days of inactivity before a stale issue is closed 8 | daysUntilClose: 7 9 | 10 | # Issues with these labels will never be considered stale 11 | exemptLabels: 12 | - pinned 13 | - feature 14 | - documentation 15 | - bug 16 | - discussion 17 | 18 | # Label to use when marking an issue as stale 19 | staleLabel: stale 20 | 21 | # Comment to post when marking an issue as stale. Set to `false` to disable 22 | markComment: > 23 | This issue has been automatically marked as stale because it has not had 24 | recent activity. It will be closed if no further activity occurs. 25 | If this is still an issue, just leave a comment 🙂 26 | 27 | # Comment to post when closing a stale issue. Set to `false` to disable 28 | closeComment: false 29 | -------------------------------------------------------------------------------- /.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 | sscce.js 17 | coverage 18 | .vscode/ 19 | *.sublime* 20 | esdoc 21 | package-lock.json 22 | yarn.lock 23 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | docs 2 | esdoc 3 | examples 4 | test 5 | README.md 6 | .watchr.js 7 | changelog.md 8 | Makefile 9 | coverage* 10 | .github 11 | 12 | appveyor-setup.ps1 13 | appveyor.yml 14 | codecov.yml 15 | docker-compose.yml 16 | mkdocs.yml 17 | .dockerignore 18 | .editorconfig 19 | .travis.yml 20 | package-lock.json 21 | 22 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | dist: trusty 3 | 4 | language: node_js 5 | 6 | branches: 7 | only: 8 | - master 9 | - v3 10 | - /^greenkeeper/.*$/ 11 | except: 12 | - /^v\d+\.\d+\.\d+$/ 13 | 14 | cache: 15 | directories: 16 | - $HOME/.npm 17 | 18 | env: 19 | global: 20 | # mysql info 21 | - SEQ_MYSQL_DB=sequelize_test 22 | - SEQ_MYSQL_USER=sequelize_test 23 | - SEQ_MYSQL_PW=sequelize_test 24 | - SEQ_MYSQL_HOST=127.0.0.1 25 | - SEQ_MYSQL_PORT=8999 26 | # postgres info 27 | - SEQ_PG_DB=sequelize_test 28 | - SEQ_PG_USER=sequelize_test 29 | - SEQ_PG_PW=sequelize_test 30 | - SEQ_PG_HOST=127.0.0.1 31 | - SEQ_PG_PORT=8998 32 | 33 | before_script: 34 | # mount ramdisk 35 | - "if [ $POSTGRES_VER ]; then sudo mkdir /mnt/sequelize-postgres-ramdisk; fi" 36 | - "if [ $POSTGRES_VER ]; then sudo mount -t ramfs tmpfs /mnt/sequelize-postgres-ramdisk; fi" 37 | - "if [ $MYSQL_VER ]; then sudo mkdir /mnt/sequelize-mysql-ramdisk; fi" 38 | - "if [ $MYSQL_VER ]; then sudo mount -t ramfs tmpfs /mnt/sequelize-mysql-ramdisk; fi" 39 | 40 | # setup docker 41 | - "if [ $POSTGRES_VER ] || [ $MYSQL_VER ]; then docker-compose up -d ${POSTGRES_VER} ${MYSQL_VER}; fi" 42 | - "if [ $MYSQL_VER ]; then docker run --link ${MYSQL_VER}:db -e CHECK_PORT=3306 -e CHECK_HOST=db --net sequelize_default giorgos/takis; fi" 43 | - "if [ $POSTGRES_VER ]; then docker run --link ${POSTGRES_VER}:db -e CHECK_PORT=5432 -e CHECK_HOST=db --net sequelize_default giorgos/takis; fi" 44 | 45 | script: 46 | - npm run lint 47 | - "if [ $COVERAGE ]; then npm run cover && bash <(curl -s https://codecov.io/bash) -f coverage/lcov.info; else npm run test; fi" 48 | 49 | jobs: 50 | include: 51 | - stage: test 52 | node_js: '4' 53 | env: DIALECT=sqlite COVERAGE=true 54 | - stage: test 55 | node_js: '4' 56 | sudo: required 57 | env: MYSQL_VER=mysql-57 DIALECT=mysql COVERAGE=true 58 | - stage: test 59 | node_js: '4' 60 | sudo: required 61 | env: POSTGRES_VER=postgres-95 DIALECT=postgres COVERAGE=true 62 | - stage: test 63 | node_js: '4' 64 | sudo: required 65 | env: POSTGRES_VER=postgres-95 DIALECT=postgres-native COVERAGE=true 66 | - stage: test 67 | node_js: '8' 68 | env: DIALECT=sqlite 69 | - stage: test 70 | node_js: '6' 71 | env: DIALECT=sqlite 72 | - stage: release 73 | node_js: '8' 74 | script: 75 | - npm run semantic-release 76 | before_deploy: 77 | - npm run docs 78 | deploy: 79 | provider: surge 80 | project: ./esdoc/ 81 | domain: docs.sequelizejs.com 82 | skip_cleanup: true 83 | 84 | stages: 85 | - test 86 | - name: release 87 | if: branch = master AND type = push AND fork = false 88 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 [esdoc](http://esdoc.org) and continously deployed to [Surge](http://surge.sh). esdoc generates static HTML from the code comments. 8 | 9 | All pages in the documentation are defined in the `manual` section of `.esdoc.json`. Each page is given as a separate line: 10 | 11 | To view the docs locally run `npm run docs` and open the generated HTML in your favorite browser. 12 | 13 | ## Articles and example based docs 14 | Write markdown, and have fun :) 15 | 16 | ## API docs 17 | Change the source-code, and rerun `npm run docs` to see your changes. 18 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:6 2 | 3 | RUN apt-get install libpq-dev 4 | 5 | WORKDIR /sequelize 6 | VOLUME /sequelize 7 | 8 | COPY . /sequelize 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2014-present Sequelize contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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, COVERAGE: true} 17 | 18 | install: 19 | - ps: Install-Product node $env:NODE_VERSION x64 20 | - ps: | 21 | $pkg = ConvertFrom-Json (Get-Content -Raw package.json) 22 | $pkg.devDependencies.PSObject.Properties.Remove('sqlite3') 23 | $pkg.devDependencies.PSObject.Properties.Remove('pg-native') 24 | ConvertTo-Json $pkg | Out-File package.json -Encoding UTF8 25 | - npm install 26 | 27 | build: off 28 | 29 | before_test: 30 | - ps: . .\appveyor-setup.ps1 31 | 32 | test_script: 33 | - 'IF "%COVERAGE%" == "true" (npm run cover) ELSE (npm test)' 34 | 35 | after_test: 36 | - ps: | 37 | $env:PATH = 'C:\Program Files\Git\usr\bin;' + $env:PATH 38 | if (Test-Path env:\COVERAGE) { 39 | Invoke-WebRequest -Uri 'https://codecov.io/bash' -OutFile codecov.sh 40 | bash codecov.sh -f "coverage\lcov.info" 41 | } 42 | 43 | branches: 44 | only: 45 | - master 46 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: 2 | layout: "header, changes" 3 | behavior: default 4 | 5 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | sequelize: 5 | build: . 6 | links: 7 | - mysql-57 8 | - postgres-95 9 | volumes: 10 | - .:/sequelize 11 | environment: 12 | SEQ_DB: sequelize_test 13 | SEQ_USER: sequelize_test 14 | SEQ_PW: sequelize_test 15 | 16 | # PostgreSQL 17 | postgres-95: 18 | image: sushantdhiman/postgres:9.5 19 | environment: 20 | POSTGRES_USER: sequelize_test 21 | POSTGRES_PASSWORD: sequelize_test 22 | POSTGRES_DB: sequelize_test 23 | volumes: 24 | - /mnt/sequelize-postgres-ramdisk:/var/lib/postgresql/data 25 | ports: 26 | - "8998:5432" 27 | container_name: postgres-95 28 | 29 | # MySQL 30 | mysql-57: 31 | image: mysql:5.7 32 | environment: 33 | MYSQL_ROOT_PASSWORD: lollerskates 34 | MYSQL_DATABASE: sequelize_test 35 | MYSQL_USER: sequelize_test 36 | MYSQL_PASSWORD: sequelize_test 37 | volumes: 38 | - /mnt/sequelize-mysql-ramdisk:/var/lib/mysql 39 | ports: 40 | - "8999:3306" 41 | container_name: mysql-57 42 | 43 | # MSSQL 44 | mssql: 45 | image: microsoft/mssql-server-linux:latest 46 | environment: 47 | ACCEPT_EULA: "Y" 48 | SA_PASSWORD: yourStrong(!)Password 49 | ports: 50 | - "8997:1433" 51 | container_name: mssql 52 | 53 | -------------------------------------------------------------------------------- /docs/ROUTER: -------------------------------------------------------------------------------- 1 | 301 /en/v3/ https://sequelize.readthedocs.io/en/v3/ 2 | 301 /en/v3/:foo https://sequelize.readthedocs.io/en/v3/:foo 3 | 301 /en/v3/:foo/:bar https://sequelize.readthedocs.io/en/v3/:foo/:bar 4 | 301 /en/v3/:foo/:bar/:baz https://sequelize.readthedocs.io/en/v3/:foo/:bar/:baz 5 | 301 /en/v3/:foo/:bar/:baz/:quz https://sequelize.readthedocs.io/en/v3/:foo/:bar/:baz/:quz 6 | 7 | 301 /en/latest / 8 | 301 /en/latest/ / 9 | 10 | 301 /en/latest/docs/getting-started/ /manual/installation/getting-started.html 11 | 301 /en/latest/docs/:section/ /manual/tutorial/:section.html 12 | 13 | 301 /en/latest/api/sequelize/ /class/lib/sequelize.js~Sequelize.html 14 | 301 /en/latest/api/model/ /class/lib/model.js~Model.html 15 | 301 /en/latest/api/instance/ /class/lib/model.js~Model.html 16 | 301 /en/latest/api/associations/ /class/lib/associations/base.js~Association.html 17 | 301 /en/latest/api/associations/belongs-to/ /class/lib/associations/belongs-to.js~BelongsTo.html 18 | 301 /en/latest/api/associations/belongs-to-many/ /class/lib/associations/belongs-to-.many.js~BelongsToMany.html 19 | 301 /en/latest/api/associations/has-one/ /class/lib/associations/has-one.js~HasOne.html 20 | 301 /en/latest/api/associations/has-many/ /class/lib/associations/has-many.js~HasMany.html 21 | 301 /en/latest/api/transaction/ /class/lib/transaction.js~Transaction.html 22 | 301 /en/latest/api/datatypes/ /variable/index.html#static-variable-DataTypes 23 | 301 /en/latest/api/deferrable/ /variable/index.html#static-variable-Deferrable 24 | 301 /en/latest/api/errors/ /class/lib/errors/index.js~BaseError.html 25 | -------------------------------------------------------------------------------- /docs/css/style.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Titillium+Web); 2 | 3 | .algolia-autocomplete .algolia-docsearch-suggestion--category-header, 4 | .algolia-autocomplete .algolia-docsearch-suggestion--title, 5 | .algolia-autocomplete .algolia-docsearch-suggestion--subcategory-column, 6 | #docs-search-input { 7 | font-size: 16px !important; 8 | line-height: normal; 9 | } 10 | 11 | .algolia-autocomplete .algolia-docsearch-suggestion--wrapper { 12 | padding: 0 !important; 13 | } 14 | 15 | .docs-search-container { 16 | float: right; 17 | margin-right: 30px; 18 | } 19 | 20 | #docs-search-input { 21 | border: none; 22 | background: transparent; 23 | padding: 10px; 24 | } 25 | 26 | #docs-search-input, .docs-search-container { 27 | height: 40px; 28 | width: 300px; 29 | } 30 | 31 | /* ESDoc search */ 32 | .search-box { 33 | display: none; 34 | } 35 | 36 | div.logo img { 37 | width: 200px; 38 | height: 200px; 39 | } 40 | 41 | div.sequelize { 42 | color: #399af3; 43 | font-size: 60px; 44 | font-family: 'Titillium Web', sans-serif; 45 | } 46 | 47 | .content { 48 | max-width: 1200px; 49 | } 50 | 51 | .navigation { 52 | overflow-x: hidden; 53 | } 54 | 55 | .manual-root .content img { 56 | box-shadow: none !important; 57 | max-width: 300px; 58 | } 59 | 60 | .manual-toc a { 61 | width: 240px; 62 | white-space: pre-wrap; 63 | } 64 | 65 | blockquote { 66 | background: #fafafa; 67 | border-left: 10px solid #ddd; 68 | margin: 1.5em 10px; 69 | padding: 0.5em 10px; 70 | } 71 | 72 | blockquote p { 73 | margin-bottom: 0; 74 | } 75 | 76 | h3#static-variable-DataTypes ~ div[data-ice="properties"] table.params tr td:nth-child(3) { 77 | display: none; 78 | } 79 | 80 | h3#static-variable-QueryTypes ~ div[data-ice="properties"] table.params tr td:nth-child(2), 81 | h3#static-variable-QueryTypes ~ div[data-ice="properties"] table.params tr td:nth-child(3), 82 | h3#static-variable-QueryTypes ~ div[data-ice="properties"] table.params tr td:nth-child(4) { 83 | display: none; 84 | } 85 | 86 | .manual-badge, 87 | .manual-cards { 88 | display: none !important; 89 | } 90 | 91 | a[href="source.html"], 92 | a[href^="file/lib/"] { 93 | display: none; 94 | } 95 | 96 | .manual-color:after { 97 | content: '' !important; 98 | } 99 | 100 | .slack-logo { 101 | height: 20px; 102 | position: relative; 103 | top: 3px; 104 | padding: 0 5px; 105 | } 106 | 107 | .header-logo { 108 | height: 25px; 109 | position: relative; 110 | top: 6px; 111 | } 112 | 113 | .center { 114 | text-align: center; 115 | } 116 | 117 | pre { 118 | white-space: pre-wrap; 119 | } 120 | -------------------------------------------------------------------------------- /docs/css/theme.css: -------------------------------------------------------------------------------- 1 | /* Side Nav */ 2 | .manual-color, .kind-class, .manual-color-faq { 3 | background-color: #6eb5f7; 4 | } 5 | 6 | .manual-color a, .kind-class, .manual-color-faq a { 7 | color: #ffffff !important; 8 | } 9 | 10 | .kind-variable, .manual-color-reference { 11 | background-color: #074278; 12 | } 13 | 14 | .kind-variable, .manual-color-reference a, .manual-color-faq a { 15 | color: #ffffff; 16 | } 17 | 18 | .manual-toc-root ul li { 19 | margin-top: 0.5em; 20 | margin-bottom: 0.5em; 21 | font-weight: normal !important; 22 | } 23 | 24 | .manual-toc .indent-h1 { 25 | margin: 0.5em 0 0 .5em; 26 | } 27 | 28 | /* Class Summay*/ 29 | .summary thead tr { 30 | background-color: #074278; 31 | } 32 | 33 | .summary thead span a { 34 | font-weight: 600; 35 | } 36 | 37 | /* Params Table */ 38 | table.params thead { 39 | background: none; 40 | color: inherit; 41 | font-weight: 600; 42 | } 43 | 44 | table.params ul { 45 | padding: 0; 46 | } 47 | 48 | table.params ul li { 49 | list-style: none; 50 | } 51 | 52 | table.params td { 53 | padding: 4px 8px; 54 | } 55 | 56 | table.params td:first-child { 57 | border-left: none; 58 | } 59 | 60 | table.params td:last-child { 61 | border-right: none; 62 | } 63 | 64 | table.params thead td { 65 | border-top: none; 66 | } 67 | 68 | table.params tbody tr:last-child td { 69 | border-bottom: none; 70 | } 71 | 72 | .informal { 73 | display: block; 74 | padding-left: 1.3em; 75 | border-left: 5px solid #dfdfdf; 76 | font-size: 1.1em; 77 | font-family: 'Kalam', cursive; 78 | margin: 22px 0; 79 | } 80 | -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fhrryDeveloper/sequelize/fea0ca730741ceb80a0a1a937432b5425b9760ed/docs/favicon.ico -------------------------------------------------------------------------------- /docs/images/bitovi-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fhrryDeveloper/sequelize/fea0ca730741ceb80a0a1a937432b5425b9760ed/docs/images/bitovi-logo.png -------------------------------------------------------------------------------- /docs/images/clevertech.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fhrryDeveloper/sequelize/fea0ca730741ceb80a0a1a937432b5425b9760ed/docs/images/clevertech.png -------------------------------------------------------------------------------- /docs/images/connected-cars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fhrryDeveloper/sequelize/fea0ca730741ceb80a0a1a937432b5425b9760ed/docs/images/connected-cars.png -------------------------------------------------------------------------------- /docs/images/filsh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fhrryDeveloper/sequelize/fea0ca730741ceb80a0a1a937432b5425b9760ed/docs/images/filsh.png -------------------------------------------------------------------------------- /docs/images/logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fhrryDeveloper/sequelize/fea0ca730741ceb80a0a1a937432b5425b9760ed/docs/images/logo-small.png -------------------------------------------------------------------------------- /docs/images/logo-snaplytics-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fhrryDeveloper/sequelize/fea0ca730741ceb80a0a1a937432b5425b9760ed/docs/images/logo-snaplytics-green.png -------------------------------------------------------------------------------- /docs/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fhrryDeveloper/sequelize/fea0ca730741ceb80a0a1a937432b5425b9760ed/docs/images/logo.png -------------------------------------------------------------------------------- /docs/images/metamarkets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fhrryDeveloper/sequelize/fea0ca730741ceb80a0a1a937432b5425b9760ed/docs/images/metamarkets.png -------------------------------------------------------------------------------- /docs/images/shutterstock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fhrryDeveloper/sequelize/fea0ca730741ceb80a0a1a937432b5425b9760ed/docs/images/shutterstock.png -------------------------------------------------------------------------------- /docs/images/slack.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/images/walmart-labs-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fhrryDeveloper/sequelize/fea0ca730741ceb80a0a1a937432b5425b9760ed/docs/images/walmart-labs-logo.png -------------------------------------------------------------------------------- /docs/imprint.md: -------------------------------------------------------------------------------- 1 | # Imprint 2 | 3 | - Boring legal stuff for the rest of us. 4 | 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 ... 5 | 6 | ## AUTHOR(S) 7 | 8 | ``` 9 | Main author: 10 | 11 | Sascha Depold 12 | Uhlandstr. 160 13 | 10719 Berlin 14 | sascha [at] depold [dot] com 15 | [plus] 49 152 [slash] 03878582 16 | ``` 17 | 18 | ## INHALTLICHE VERANTWORTUNG 19 | 20 | ``` 21 | Ich übernehme keine Haftung für ausgehende Links. 22 | Daher musst du dich bei Problemen an deren Betreiber wenden! 23 | ``` 24 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 |
2 | 5 |
Sequelize 6 |
7 | 8 | [![Travis build](https://img.shields.io/travis/sequelize/sequelize/master.svg?style=flat-square)](https://travis-ci.org/sequelize/sequelize) 9 | [![npm](https://img.shields.io/npm/dm/sequelize.svg?style=flat-square)](https://npmjs.org/package/sequelize) 10 | [![npm](https://img.shields.io/npm/v/sequelize.svg?style=flat-square)](https://github.com/sequelize/sequelize/releases) 11 | 12 | Sequelize is a promise-based ORM for Node.js v4 and up. It supports the dialects PostgreSQL, MySQL, SQLite and MSSQL and features solid transaction support, relations, read replication and more. 13 | 14 | ## Example usage 15 | 16 | ```js 17 | const Sequelize = require('sequelize'); 18 | const sequelize = new Sequelize('database', 'username', 'password', { 19 | host: 'localhost', 20 | dialect: 'mysql'|'sqlite'|'postgres'|'mssql', 21 | 22 | pool: { 23 | max: 5, 24 | min: 0, 25 | acquire: 30000, 26 | idle: 10000 27 | }, 28 | 29 | // SQLite only 30 | storage: 'path/to/database.sqlite', 31 | 32 | // http://docs.sequelizejs.com/manual/tutorial/querying.html#operators 33 | operatorsAliases: false 34 | }); 35 | 36 | const User = sequelize.define('user', { 37 | username: Sequelize.STRING, 38 | birthday: Sequelize.DATE 39 | }); 40 | 41 | sequelize.sync() 42 | .then(() => User.create({ 43 | username: 'janedoe', 44 | birthday: new Date(1980, 6, 20) 45 | })) 46 | .then(jane => { 47 | console.log(jane.toJSON()); 48 | }); 49 | ``` 50 | 51 | Please use [Getting Started](manual/installation/getting-started) to learn more. If you wish to learn about Sequelize API please use [API Reference](identifiers) 52 | -------------------------------------------------------------------------------- /docs/legacy.md: -------------------------------------------------------------------------------- 1 | # Working with legacy tables 2 | 3 | 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. 4 | 5 | ## Tables 6 | ```js 7 | sequelize.define('user', { 8 | 9 | }, { 10 | tableName: 'users' 11 | }); 12 | ``` 13 | 14 | ## Fields 15 | ```js 16 | sequelize.define('modelName', { 17 | userId: { 18 | type: Sequelize.INTEGER, 19 | field: 'user_id' 20 | } 21 | }); 22 | ``` 23 | 24 | ## Primary keys 25 | Sequelize will assume your table has a `id` primary key property by default. 26 | 27 | To define your own primary key: 28 | 29 | ```js 30 | sequelize.define('collection', { 31 | uid: { 32 | type: Sequelize.INTEGER, 33 | primaryKey: true, 34 | autoIncrement: true // Automatically gets converted to SERIAL for postgres 35 | } 36 | }); 37 | 38 | sequelize.define('collection', { 39 | uuid: { 40 | type: Sequelize.UUID, 41 | primaryKey: true 42 | } 43 | }); 44 | ``` 45 | 46 | And if your model has no primary key at all you can use `Model.removeAttribute('id');` 47 | 48 | ## Foreign keys 49 | ```js 50 | // 1:1 51 | Organization.belongsTo(User, {foreignKey: 'owner_id'}); 52 | User.hasOne(Organization, {foreignKey: 'owner_id'}); 53 | 54 | // 1:M 55 | Project.hasMany(Task, {foreignKey: 'tasks_pk'}); 56 | Task.belongsTo(Project, {foreignKey: 'tasks_pk'}); 57 | 58 | // N:M 59 | User.hasMany(Role, {through: 'user_has_roles', foreignKey: 'user_role_user_id'}); 60 | Role.hasMany(User, {through: 'user_has_roles', foreignKey: 'roles_identifier'}); 61 | ``` 62 | -------------------------------------------------------------------------------- /docs/plugins/esdoc-sequelize.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const cheerio = require('cheerio'); 4 | const esdocConfig = require('../../.esdoc.json'); 5 | 6 | exports.onHandleHTML = function(ev) { 7 | const $ = cheerio.load(ev.data.html); 8 | 9 | const $title = $('head title'); 10 | if ($title.text().indexOf(esdocConfig.title) === -1) { 11 | $title.text($title.text() + ' | ' + esdocConfig.title); 12 | } 13 | 14 | const $header = $('header'); 15 | $header.prepend(''); 16 | $('.repo-url-github').after('Join us on Slack'); 17 | 18 | // remove unnecessary scripts 19 | const scripts = ['script/search_index.js', 'script/search.js', 'script/inherited-summary.js', 'script/test-summary.js', 'script/inner-link.js']; 20 | for (const script of scripts) { 21 | $(`script[src="${script}"]`).remove(); 22 | } 23 | 24 | // Algolia search 25 | if (process.env.ALGOLIA_API_KEY) { 26 | $('head').append(''); 27 | $header.append('
'); 28 | $('body').append(` 29 | 30 | 39 | `); 40 | } else { 41 | console.log('Set ALGOLIA_API_KEY environment variable to enable Algolia search field'); 42 | } 43 | 44 | ev.data.html = $.html(); 45 | }; 46 | -------------------------------------------------------------------------------- /docs/scripts/script.js: -------------------------------------------------------------------------------- 1 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 2 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 3 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 4 | })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); 5 | 6 | ga('create', 'UA-96831523-1', 'auto'); 7 | ga('send', 'pageview'); 8 | -------------------------------------------------------------------------------- /docs/whos-using.md: -------------------------------------------------------------------------------- 1 | # Who's using sequelize? 2 | 3 | [![Walmart labs logo](asset/walmart-labs-logo.png)](http://www.walmartlabs.com/) 4 | 5 | > ... we are avid users of sequelize (and have been for the past 18 months) (Feb 2017) 6 | 7 |
8 | 9 | [![Snaplytics logo](asset/logo-snaplytics-green.png)](https://snaplytics.io) 10 | 11 | > We've been using sequelize since we started in the beginning of 2015. We use it for our graphql servers (in connection with [graphql-sequelize](http://github.com/mickhansen/graphql-sequelize)), and for all our background workers. 12 | 13 |
14 | 15 | [![Connected Cars logo](asset/connected-cars.png)](https://connectedcars.dk/) 16 | 17 |
18 | 19 | [![Bitovi Logo](asset/bitovi-logo.png)](https://bitovi.com) 20 | 21 | > We have used Sequelize in enterprise projects for some of our Fortune 100 and Fortune 500 clients. It is used in deployments that are depended on by hundreds of millions of devices every year. 22 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * The entry point. 5 | * 6 | * @module Sequelize 7 | */ 8 | module.exports = require('./lib/sequelize'); 9 | -------------------------------------------------------------------------------- /lib/associations/helpers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('lodash'); 4 | 5 | function checkNamingCollision(association) { 6 | if (association.source.rawAttributes.hasOwnProperty(association.as)) { 7 | throw new Error( 8 | 'Naming collision between attribute \'' + association.as + 9 | '\' and association \'' + association.as + '\' on model ' + association.source.name + 10 | '. To remedy this, change either foreignKey or as in your association definition' 11 | ); 12 | } 13 | } 14 | exports.checkNamingCollision = checkNamingCollision; 15 | 16 | function addForeignKeyConstraints(newAttribute, source, target, options, key) { 17 | // FK constraints are opt-in: users must either set `foreignKeyConstraints` 18 | // on the association, or request an `onDelete` or `onUpdate` behaviour 19 | 20 | if (options.foreignKeyConstraint || options.onDelete || options.onUpdate) { 21 | 22 | // Find primary keys: composite keys not supported with this approach 23 | const primaryKeys = _.chain(source.rawAttributes).keys() 24 | .filter(key => source.rawAttributes[key].primaryKey) 25 | .map(key => source.rawAttributes[key].field || key).value(); 26 | 27 | if (primaryKeys.length === 1) { 28 | if (source._schema) { 29 | newAttribute.references = { 30 | model: source.sequelize.getQueryInterface().QueryGenerator.addSchema({ 31 | tableName: source.tableName, 32 | _schema: source._schema, 33 | _schemaDelimiter: source._schemaDelimiter 34 | }) 35 | }; 36 | } else { 37 | newAttribute.references = { model: source.tableName }; 38 | } 39 | 40 | newAttribute.references.key = key || primaryKeys[0]; 41 | newAttribute.onDelete = options.onDelete; 42 | newAttribute.onUpdate = options.onUpdate; 43 | } 44 | } 45 | } 46 | exports.addForeignKeyConstraints = addForeignKeyConstraints; 47 | 48 | /** 49 | * Mixin (inject) association methods to model prototype 50 | * 51 | * @private 52 | * @param {Object} Association instance 53 | * @param {Object} Model prototype 54 | * @param {Array} Method names to inject 55 | * @param {Object} Mapping between model and association method names 56 | */ 57 | function mixinMethods(association, obj, methods, aliases) { 58 | aliases = aliases || {}; 59 | 60 | for (const method of methods) { 61 | // don't override custom methods 62 | if (!obj[association.accessors[method]]) { 63 | const realMethod = aliases[method] || method; 64 | 65 | obj[association.accessors[method]] = function() { 66 | const instance = this; 67 | const args = [instance].concat(Array.from(arguments)); 68 | 69 | return association[realMethod].apply(association, args); 70 | }; 71 | } 72 | } 73 | } 74 | exports.mixinMethods = mixinMethods; 75 | -------------------------------------------------------------------------------- /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/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 | addConstraint: true, 47 | dropConstraint: true, 48 | unique: true, 49 | default: false, 50 | check: true, 51 | foreignKey: true, 52 | primaryKey: true 53 | }, 54 | index: { 55 | collate: true, 56 | length: false, 57 | parser: false, 58 | concurrently: false, 59 | type: false, 60 | using: true 61 | }, 62 | joinTableDependent: true, 63 | groupedLimit: true, 64 | indexViaAlter: false, 65 | JSON: false, 66 | deferrableConstraints: false 67 | }; 68 | 69 | module.exports = AbstractDialect; 70 | module.exports.AbstractDialect = AbstractDialect; 71 | module.exports.default = AbstractDialect; 72 | -------------------------------------------------------------------------------- /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 | default: true 44 | }, 45 | index: { 46 | collate: false, 47 | length: false, 48 | parser: false, 49 | type: true, 50 | using: false, 51 | where: true 52 | }, 53 | NUMERIC: true, 54 | tmpTableTrigger: true 55 | }); 56 | 57 | ConnectionManager.prototype.defaultVersion = '12.0.2000'; // SQL Server 2014 Express 58 | MssqlDialect.prototype.Query = Query; 59 | MssqlDialect.prototype.name = 'mssql'; 60 | MssqlDialect.prototype.TICK_CHAR = '"'; 61 | MssqlDialect.prototype.TICK_CHAR_LEFT = '['; 62 | MssqlDialect.prototype.TICK_CHAR_RIGHT = ']'; 63 | MssqlDialect.prototype.DataTypes = DataTypes; 64 | 65 | module.exports = MssqlDialect; 66 | -------------------------------------------------------------------------------- /lib/dialects/mssql/query-interface.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | Returns an object that treats MSSQL's inabilities to do certain queries. 5 | 6 | @class QueryInterface 7 | @static 8 | @private 9 | */ 10 | 11 | /** 12 | A wrapper that fixes MSSQL's inability to cleanly remove columns from existing tables if they have a default constraint. 13 | 14 | @method removeColumn 15 | @for QueryInterface 16 | 17 | @param {String} tableName The name of the table. 18 | @param {String} attributeName The name of the attribute that we want to remove. 19 | @param {Object} options 20 | @param {Boolean|Function} [options.logging] A function that logs the sql queries, or false for explicitly not logging these queries 21 | @private 22 | */ 23 | const removeColumn = function(tableName, attributeName, options) { 24 | options = Object.assign({ raw: true }, options || {}); 25 | 26 | const findConstraintSql = this.QueryGenerator.getDefaultConstraintQuery(tableName, attributeName); 27 | return this.sequelize.query(findConstraintSql, options) 28 | .spread(results => { 29 | if (!results.length) { 30 | // No default constraint found -- we can cleanly remove the column 31 | return; 32 | } 33 | const dropConstraintSql = this.QueryGenerator.dropConstraintQuery(tableName, results[0].name); 34 | return this.sequelize.query(dropConstraintSql, options); 35 | }) 36 | .then(() => { 37 | const findForeignKeySql = this.QueryGenerator.getForeignKeyQuery(tableName, attributeName); 38 | return this.sequelize.query(findForeignKeySql, options); 39 | }) 40 | .spread(results => { 41 | if (!results.length) { 42 | // No foreign key constraints found, so we can remove the column 43 | return; 44 | } 45 | const dropForeignKeySql = this.QueryGenerator.dropForeignKeyQuery(tableName, results[0].constraint_name); 46 | return this.sequelize.query(dropForeignKeySql, options); 47 | }) 48 | .then(() => { 49 | //Check if the current column is a primaryKey 50 | const primaryKeyConstraintSql = this.QueryGenerator.getPrimaryKeyConstraintQuery(tableName, attributeName); 51 | return this.sequelize.query(primaryKeyConstraintSql, options); 52 | }) 53 | .spread(result => { 54 | if (!result.length) { 55 | return; 56 | } 57 | const dropConstraintSql = this.QueryGenerator.dropConstraintQuery(tableName, result[0].constraintName); 58 | return this.sequelize.query(dropConstraintSql, options); 59 | }) 60 | .then(() => { 61 | const removeSql = this.QueryGenerator.removeColumnQuery(tableName, attributeName); 62 | return this.sequelize.query(removeSql, options); 63 | }); 64 | }; 65 | 66 | module.exports = { 67 | removeColumn 68 | }; 69 | -------------------------------------------------------------------------------- /lib/dialects/mssql/resource-lock.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const 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 | const lock = this.previous; 16 | let resolve; 17 | 18 | this.previous = new Promise(r => { 19 | resolve = r; 20 | }); 21 | 22 | return lock.disposer(resolve); 23 | }; 24 | 25 | module.exports = ResourceLock; 26 | -------------------------------------------------------------------------------- /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 | constraints: { 37 | dropConstraint: false, 38 | check: false 39 | }, 40 | ignoreDuplicates: ' IGNORE', 41 | updateOnDuplicate: true, 42 | indexViaAlter: true, 43 | NUMERIC: true, 44 | GEOMETRY: true, 45 | JSON: true, 46 | REGEXP: true 47 | }); 48 | 49 | ConnectionManager.prototype.defaultVersion = '5.6.0'; 50 | MysqlDialect.prototype.Query = Query; 51 | MysqlDialect.prototype.QueryGenerator = QueryGenerator; 52 | MysqlDialect.prototype.DataTypes = DataTypes; 53 | MysqlDialect.prototype.name = 'mysql'; 54 | MysqlDialect.prototype.TICK_CHAR = '`'; 55 | MysqlDialect.prototype.TICK_CHAR_LEFT = MysqlDialect.prototype.TICK_CHAR; 56 | MysqlDialect.prototype.TICK_CHAR_RIGHT = MysqlDialect.prototype.TICK_CHAR; 57 | 58 | module.exports = MysqlDialect; 59 | -------------------------------------------------------------------------------- /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 | const UnknownConstraintError = require('../../errors').UnknownConstraintError; 13 | 14 | /** 15 | A wrapper that fixes MySQL's inability to cleanly remove columns from existing tables if they have a foreign key constraint. 16 | 17 | @method removeColumn 18 | @for QueryInterface 19 | 20 | @param {String} tableName The name of the table. 21 | @param {String} columnName The name of the attribute that we want to remove. 22 | @param {Object} options 23 | @private 24 | */ 25 | function removeColumn(tableName, columnName, options) { 26 | options = options || {}; 27 | 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 | 49 | 50 | function removeConstraint(tableName, constraintName, options) { 51 | const sql = this.QueryGenerator.showConstraintsQuery(tableName, constraintName); 52 | 53 | return this.sequelize.query(sql, Object.assign({}, options, { type: this.sequelize.QueryTypes.SHOWCONSTRAINTS })) 54 | .then(constraints => { 55 | const constraint = constraints[0]; 56 | let query; 57 | if (constraint && constraint.constraintType) { 58 | if (constraint.constraintType === 'FOREIGN KEY') { 59 | query = this.QueryGenerator.dropForeignKeyQuery(tableName, constraintName); 60 | } else { 61 | query = this.QueryGenerator.removeIndexQuery(constraint.tableName, constraint.constraintName); 62 | } 63 | } else { 64 | throw new UnknownConstraintError(`Constraint ${constraintName} on table ${tableName} does not exist`); 65 | } 66 | 67 | return this.sequelize.query(query, options); 68 | }); 69 | } 70 | 71 | exports.removeConstraint = removeConstraint; 72 | exports.removeColumn = removeColumn; 73 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | REGEXP: true, 48 | GEOGRAPHY: true, 49 | JSON: true, 50 | JSONB: true, 51 | HSTORE: true, 52 | deferrableConstraints: true, 53 | searchPath: true 54 | }); 55 | 56 | ConnectionManager.prototype.defaultVersion = '9.4.0'; 57 | PostgresDialect.prototype.Query = Query; 58 | PostgresDialect.prototype.DataTypes = DataTypes; 59 | PostgresDialect.prototype.name = 'postgres'; 60 | PostgresDialect.prototype.TICK_CHAR = '"'; 61 | PostgresDialect.prototype.TICK_CHAR_LEFT = PostgresDialect.prototype.TICK_CHAR; 62 | PostgresDialect.prototype.TICK_CHAR_RIGHT = PostgresDialect.prototype.TICK_CHAR; 63 | 64 | module.exports = PostgresDialect; 65 | module.exports.default = PostgresDialect; 66 | module.exports.PostgresDialect = PostgresDialect; 67 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | where: true 31 | }, 32 | transactionOptions: { 33 | type: true, 34 | autocommit: false 35 | }, 36 | constraints: { 37 | addConstraint: false, 38 | dropConstraint: false 39 | }, 40 | joinTableDependent: false, 41 | groupedLimit: false, 42 | ignoreDuplicates: ' OR IGNORE', 43 | JSON: true 44 | }); 45 | 46 | ConnectionManager.prototype.defaultVersion = '3.8.0'; 47 | SqliteDialect.prototype.Query = Query; 48 | SqliteDialect.prototype.DataTypes = DataTypes; 49 | SqliteDialect.prototype.name = 'sqlite'; 50 | SqliteDialect.prototype.TICK_CHAR = '`'; 51 | SqliteDialect.prototype.TICK_CHAR_LEFT = SqliteDialect.prototype.TICK_CHAR; 52 | SqliteDialect.prototype.TICK_CHAR_RIGHT = SqliteDialect.prototype.TICK_CHAR; 53 | 54 | module.exports = SqliteDialect; 55 | module.exports.SqliteDialect = SqliteDialect; 56 | module.exports.default = SqliteDialect; 57 | -------------------------------------------------------------------------------- /lib/errors.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('./errors/index.js'); 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/query-types.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * An enum of query types used by `sequelize.query` 5 | * 6 | * @see {@link Sequelize#query} 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 | * @property SHOWCONSTRAINTS 22 | */ 23 | const QueryTypes = module.exports = { // eslint-disable-line 24 | SELECT: 'SELECT', 25 | INSERT: 'INSERT', 26 | UPDATE: 'UPDATE', 27 | BULKUPDATE: 'BULKUPDATE', 28 | BULKDELETE: 'BULKDELETE', 29 | DELETE: 'DELETE', 30 | UPSERT: 'UPSERT', 31 | VERSION: 'VERSION', 32 | SHOWTABLES: 'SHOWTABLES', 33 | SHOWINDEXES: 'SHOWINDEXES', 34 | DESCRIBE: 'DESCRIBE', 35 | RAW: 'RAW', 36 | FOREIGNKEYS: 'FOREIGNKEYS', 37 | SHOWCONSTRAINTS: 'SHOWCONSTRAINTS' 38 | }; 39 | -------------------------------------------------------------------------------- /lib/sql-string.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const dataTypes = require('./data-types'); 4 | const util = require('util'); 5 | const _ = require('lodash'); 6 | 7 | function escape(val, timeZone, dialect, format) { 8 | let prependN = false; 9 | if (val === undefined || val === null) { 10 | return 'NULL'; 11 | } 12 | switch (typeof val) { 13 | case 'boolean': 14 | // SQLite doesn't have true/false support. MySQL aliases true/false to 1/0 15 | // for us. Postgres actually has a boolean type with true/false literals, 16 | // but sequelize doesn't use it yet. 17 | if (dialect === 'sqlite' || dialect === 'mssql') { 18 | return +!!val; 19 | } 20 | return '' + !!val; 21 | case 'number': 22 | return val + ''; 23 | case 'string': 24 | // In mssql, prepend N to all quoted vals which are originally a string (for 25 | // unicode compatibility) 26 | prependN = dialect === 'mssql'; 27 | break; 28 | } 29 | 30 | if (val instanceof Date) { 31 | val = dataTypes[dialect].DATE.prototype.stringify(val, { timezone: timeZone }); 32 | } 33 | 34 | if (Buffer.isBuffer(val)) { 35 | if (dataTypes[dialect].BLOB) { 36 | return dataTypes[dialect].BLOB.prototype.stringify(val); 37 | } 38 | 39 | return dataTypes.BLOB.prototype.stringify(val); 40 | } 41 | 42 | if (Array.isArray(val)) { 43 | const partialEscape = _.partial(escape, _, timeZone, dialect, format); 44 | if (dialect === 'postgres' && !format) { 45 | return dataTypes.ARRAY.prototype.stringify(val, {escape: partialEscape}); 46 | } 47 | return val.map(partialEscape); 48 | } 49 | 50 | if (!val.replace) { 51 | throw new Error('Invalid value ' + util.inspect(val)); 52 | } 53 | 54 | if (dialect === 'postgres' || dialect === 'sqlite' || dialect === 'mssql') { 55 | // http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS 56 | // http://stackoverflow.com/q/603572/130598 57 | val = val.replace(/'/g, "''"); 58 | } else { 59 | val = val.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, s => { 60 | switch (s) { 61 | case '\0': return '\\0'; 62 | case '\n': return '\\n'; 63 | case '\r': return '\\r'; 64 | case '\b': return '\\b'; 65 | case '\t': return '\\t'; 66 | case '\x1a': return '\\Z'; 67 | default: return '\\' + s; 68 | } 69 | }); 70 | } 71 | return (prependN ? "N'" : "'") + val + "'"; 72 | } 73 | exports.escape = escape; 74 | 75 | function format(sql, values, timeZone, dialect) { 76 | values = [].concat(values); 77 | 78 | if (typeof sql !== 'string') { 79 | throw new Error('Invalid SQL string provided: ' + sql); 80 | } 81 | return sql.replace(/\?/g, match => { 82 | if (!values.length) { 83 | return match; 84 | } 85 | 86 | return escape(values.shift(), timeZone, dialect, true); 87 | }); 88 | } 89 | exports.format = format; 90 | 91 | function formatNamedParameters(sql, values, timeZone, dialect) { 92 | return sql.replace(/\:+(?!\d)(\w+)/g, (value, key) => { 93 | if ('postgres' === dialect && '::' === value.slice(0, 2)) { 94 | return value; 95 | } 96 | 97 | if (values[key] !== undefined) { 98 | return escape(values[key], timeZone, dialect, true); 99 | } else { 100 | throw new Error('Named parameter "' + value + '" has no value in the given object.'); 101 | } 102 | }); 103 | } 104 | exports.formatNamedParameters = formatNamedParameters; 105 | -------------------------------------------------------------------------------- /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/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 | const depd = require('depd'), 12 | debug = require('debug'), 13 | _ = require('lodash'); 14 | 15 | class Logger { 16 | constructor(config) { 17 | 18 | this.config = _.extend({ 19 | context: 'sequelize', 20 | debug: true 21 | }, config || {}); 22 | 23 | this.depd = depd(this.config.context); 24 | this.debug = debug(this.config.context); 25 | } 26 | 27 | deprecate(message) { 28 | this.depd(message); 29 | } 30 | 31 | debug(message) { 32 | this.config.debug && this.debug(message); 33 | } 34 | 35 | warn(message) { 36 | console.warn(`(${this.config.context}) Warning: ${message}`); 37 | } 38 | 39 | debugContext(childContext) { 40 | if (!childContext) { 41 | throw new Error('No context supplied to debug'); 42 | } 43 | return debug([this.config.context, childContext].join(':')); 44 | } 45 | } 46 | 47 | module.exports = Logger; 48 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/utils/validator-extras.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('lodash'); 4 | const validator = _.cloneDeep(require('validator')); 5 | const moment = require('moment'); 6 | 7 | const extensions = { 8 | extend(name, fn) { 9 | this[name] = fn; 10 | 11 | return this; 12 | }, 13 | notEmpty(str) { 14 | return !str.match(/^[\s\t\r\n]*$/); 15 | }, 16 | len(str, min, max) { 17 | return this.isLength(str, min, max); 18 | }, 19 | isUrl(str) { 20 | return this.isURL(str); 21 | }, 22 | isIPv6(str) { 23 | return this.isIP(str, 6); 24 | }, 25 | isIPv4(str) { 26 | return this.isIP(str, 4); 27 | }, 28 | notIn(str, values) { 29 | return !this.isIn(str, values); 30 | }, 31 | regex(str, pattern, modifiers) { 32 | str += ''; 33 | if (Object.prototype.toString.call(pattern).slice(8, -1) !== 'RegExp') { 34 | pattern = new RegExp(pattern, modifiers); 35 | } 36 | return str.match(pattern); 37 | }, 38 | notRegex(str, pattern, modifiers) { 39 | return !this.regex(str, pattern, modifiers); 40 | }, 41 | isDecimal(str) { 42 | return str !== '' && !!str.match(/^(?:-?(?:[0-9]+))?(?:\.[0-9]*)?(?:[eE][\+\-]?(?:[0-9]+))?$/); 43 | }, 44 | min(str, val) { 45 | const number = parseFloat(str); 46 | return isNaN(number) || number >= val; 47 | }, 48 | max(str, val) { 49 | const number = parseFloat(str); 50 | return isNaN(number) || number <= val; 51 | }, 52 | not(str, pattern, modifiers) { 53 | return this.notRegex(str, pattern, modifiers); 54 | }, 55 | contains(str, elem) { 56 | return str.indexOf(elem) >= 0 && !!elem; 57 | }, 58 | notContains(str, elem) { 59 | return !this.contains(str, elem); 60 | }, 61 | is(str, pattern, modifiers) { 62 | return this.regex(str, pattern, modifiers); 63 | } 64 | }; 65 | exports.extensions = extensions; 66 | 67 | function extendModelValidations(modelInstance) { 68 | const extensions = { 69 | isImmutable(str, param, field) { 70 | return modelInstance.isNewRecord || modelInstance.dataValues[field] === modelInstance._previousDataValues[field]; 71 | } 72 | }; 73 | 74 | _.forEach(extensions, (extend, key) => { 75 | validator[key] = extend; 76 | }); 77 | } 78 | exports.extendModelValidations = extendModelValidations; 79 | 80 | // Deprecate this. 81 | validator.notNull = function() { 82 | throw new Error('Warning "notNull" validation has been deprecated in favor of Schema based "allowNull"'); 83 | }; 84 | 85 | // https://github.com/chriso/validator.js/blob/6.2.0/validator.js 86 | _.forEach(extensions, (extend, key) => { 87 | validator[key] = extend; 88 | }); 89 | 90 | // map isNull to isEmpty 91 | // https://github.com/chriso/validator.js/commit/e33d38a26ee2f9666b319adb67c7fc0d3dea7125 92 | validator.isNull = validator.isEmpty; 93 | 94 | // isDate removed in 7.0.0 95 | // https://github.com/chriso/validator.js/commit/095509fc707a4dc0e99f85131df1176ad6389fc9 96 | validator.isDate = function(dateString) { 97 | // avoid http://momentjs.com/guides/#/warnings/js-date/ 98 | // by doing a preliminary check on `dateString` 99 | const parsed = Date.parse(dateString); 100 | if (isNaN(parsed)) { 101 | // fail if we can't parse it 102 | return false; 103 | } else { 104 | // otherwise convert to ISO 8601 as moment prefers 105 | // http://momentjs.com/docs/#/parsing/string/ 106 | const date = new Date(parsed); 107 | return moment(date.toISOString()).isValid(); 108 | } 109 | }; 110 | 111 | exports.validator = validator; 112 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | SEQ_MSSQL_PORT=8997 9 | SEQ_MSSQL_DB=master 10 | SEQ_MSSQL_USER=sa 11 | SEQ_MSSQL_PW=yourStrong(!)Password 12 | -------------------------------------------------------------------------------- /test/config/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | let mssqlConfig; 5 | try { 6 | mssqlConfig = JSON.parse(fs.readFileSync(__dirname + '/mssql.json', 'utf8')); 7 | } catch (e) { 8 | // ignore 9 | } 10 | 11 | module.exports = { 12 | username: process.env.SEQ_USER || 'root', 13 | password: process.env.SEQ_PW || null, 14 | database: process.env.SEQ_DB || 'sequelize_test', 15 | host: process.env.SEQ_HOST || '127.0.0.1', 16 | pool: { 17 | max: process.env.SEQ_POOL_MAX || 5, 18 | idle: process.env.SEQ_POOL_IDLE || 30000 19 | }, 20 | 21 | rand() { 22 | return parseInt(Math.random() * 999, 10); 23 | }, 24 | 25 | mssql: mssqlConfig || { 26 | database: process.env.SEQ_MSSQL_DB || process.env.SEQ_DB || 'sequelize_test', 27 | username: process.env.SEQ_MSSQL_USER || process.env.SEQ_USER || 'sequelize', 28 | password: process.env.SEQ_MSSQL_PW || process.env.SEQ_PW || 'nEGkLma26gXVHFUAHJxcmsrK', 29 | host: process.env.SEQ_MSSQL_HOST || process.env.SEQ_HOST || '127.0.0.1', 30 | port: process.env.SEQ_MSSQL_PORT || process.env.SEQ_PORT || 1433, 31 | dialectOptions: { 32 | // big insert queries need a while 33 | requestTimeout: 60000 34 | }, 35 | pool: { 36 | max: process.env.SEQ_MSSQL_POOL_MAX || process.env.SEQ_POOL_MAX || 5, 37 | idle: process.env.SEQ_MSSQL_POOL_IDLE || process.env.SEQ_POOL_IDLE || 3000 38 | } 39 | }, 40 | 41 | //make idle time small so that tests exit promptly 42 | mysql: { 43 | database: process.env.SEQ_MYSQL_DB || process.env.SEQ_DB || 'sequelize_test', 44 | username: process.env.SEQ_MYSQL_USER || process.env.SEQ_USER || 'root', 45 | password: process.env.SEQ_MYSQL_PW || process.env.SEQ_PW || null, 46 | host: process.env.MYSQL_PORT_3306_TCP_ADDR || process.env.SEQ_MYSQL_HOST || process.env.SEQ_HOST || '127.0.0.1', 47 | port: process.env.MYSQL_PORT_3306_TCP_PORT || process.env.SEQ_MYSQL_PORT || process.env.SEQ_PORT || 3306, 48 | pool: { 49 | max: process.env.SEQ_MYSQL_POOL_MAX || process.env.SEQ_POOL_MAX || 5, 50 | idle: process.env.SEQ_MYSQL_POOL_IDLE || process.env.SEQ_POOL_IDLE || 3000 51 | } 52 | }, 53 | 54 | sqlite: { 55 | }, 56 | 57 | postgres: { 58 | database: process.env.SEQ_PG_DB || process.env.SEQ_DB || 'sequelize_test', 59 | username: process.env.SEQ_PG_USER || process.env.SEQ_USER || 'postgres', 60 | password: process.env.SEQ_PG_PW || process.env.SEQ_PW || 'postgres', 61 | host: process.env.POSTGRES_PORT_5432_TCP_ADDR || process.env.SEQ_PG_HOST || process.env.SEQ_HOST || '127.0.0.1', 62 | port: process.env.POSTGRES_PORT_5432_TCP_PORT || process.env.SEQ_PG_PORT || process.env.SEQ_PORT || 5432, 63 | pool: { 64 | max: process.env.SEQ_PG_POOL_MAX || process.env.SEQ_POOL_MAX || 5, 65 | idle: process.env.SEQ_PG_POOL_IDLE || process.env.SEQ_POOL_IDLE || 3000 66 | } 67 | } 68 | }; 69 | -------------------------------------------------------------------------------- /test/config/options.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const 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/integration/dialects/mssql/query-queue.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const 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', () => { 12 | beforeEach(function() { 13 | const User = this.User = this.sequelize.define('User', { 14 | username: DataTypes.STRING 15 | }); 16 | 17 | return this.sequelize.sync({ force: true }).then(() => { 18 | return User.create({ username: 'John'}); 19 | }); 20 | }); 21 | 22 | it('should queue concurrent requests to a connection', function() { 23 | const User = this.User; 24 | 25 | return expect(this.sequelize.transaction(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 | -------------------------------------------------------------------------------- /test/integration/dialects/mysql/errors.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'); 4 | const expect = chai.expect; 5 | const Support = require(__dirname + '/../../support'); 6 | const dialect = Support.getTestDialect(); 7 | const DataTypes = require(__dirname + '/../../../../lib/data-types'); 8 | 9 | if (dialect === 'mysql') { 10 | describe('[MYSQL Specific] Errors', () => { 11 | 12 | const validateError = (promise, errClass, errValues) => { 13 | const wanted = Object.assign({}, errValues); 14 | 15 | return expect(promise).to.have.been.rejectedWith(errClass).then(() => 16 | promise.catch(err => Object.keys(wanted).forEach(k => expect(err[k]).to.eql(wanted[k])))); 17 | }; 18 | 19 | describe('ForeignKeyConstraintError', () => { 20 | beforeEach(function() { 21 | this.Task = this.sequelize.define('task', { title: DataTypes.STRING }); 22 | this.User = this.sequelize.define('user', { username: DataTypes.STRING }); 23 | this.UserTasks = this.sequelize.define('tasksusers', { userId: DataTypes.INTEGER, taskId: DataTypes.INTEGER }); 24 | 25 | this.User.belongsToMany(this.Task, { onDelete: 'RESTRICT', through: 'tasksusers' }); 26 | this.Task.belongsToMany(this.User, { onDelete: 'RESTRICT', through: 'tasksusers' }); 27 | 28 | this.Task.belongsTo(this.User, { foreignKey: 'primaryUserId', as: 'primaryUsers' }); 29 | }); 30 | 31 | it('in context of DELETE restriction', function() { 32 | const self = this, 33 | ForeignKeyConstraintError = this.sequelize.ForeignKeyConstraintError; 34 | 35 | return this.sequelize.sync({ force: true }).bind({}).then(() => { 36 | return Promise.all([ 37 | self.User.create({ id: 67, username: 'foo' }), 38 | self.Task.create({ id: 52, title: 'task' }) 39 | ]); 40 | }).spread(function(user1, task1) { 41 | this.user1 = user1; 42 | this.task1 = task1; 43 | return user1.setTasks([task1]); 44 | }).then(function() { 45 | return Promise.all([ 46 | validateError(this.user1.destroy(), ForeignKeyConstraintError, { 47 | fields: ['userId'], 48 | table: 'users', 49 | value: undefined, 50 | index: 'tasksusers_ibfk_1', 51 | reltype: 'parent' 52 | }), 53 | validateError(this.task1.destroy(), ForeignKeyConstraintError, { 54 | fields: ['taskId'], 55 | table: 'tasks', 56 | value: undefined, 57 | index: 'tasksusers_ibfk_2', 58 | reltype: 'parent' 59 | }) 60 | ]); 61 | }); 62 | }); 63 | 64 | it('in context of missing relation', function() { 65 | const self = this, 66 | ForeignKeyConstraintError = this.sequelize.ForeignKeyConstraintError; 67 | 68 | return this.sequelize.sync({ force: true }).then(() => 69 | validateError(self.Task.create({ title: 'task', primaryUserId: 5 }), ForeignKeyConstraintError, { 70 | fields: ['primaryUserId'], 71 | table: 'users', 72 | value: 5, 73 | index: 'tasks_ibfk_1', 74 | reltype: 'child' 75 | })); 76 | }); 77 | 78 | }); 79 | }); 80 | } 81 | -------------------------------------------------------------------------------- /test/integration/dialects/postgres/connection-manager.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | expect = chai.expect, 5 | Support = require(__dirname + '/../../support'), 6 | dialect = Support.getTestDialect(), 7 | DataTypes = require(__dirname + '/../../../../lib/data-types'), 8 | _ = require('lodash'); 9 | 10 | if (dialect.match(/^postgres/)) { 11 | describe('[POSTGRES] Sequelize', () => { 12 | function checkTimezoneParsing(baseOptions) { 13 | const options = _.extend({}, baseOptions, { timezone: 'Asia/Kolkata', timestamps: true }); 14 | const sequelize = Support.createSequelizeInstance(options); 15 | 16 | const tzTable = sequelize.define('tz_table', { foo: DataTypes.STRING }); 17 | return tzTable.sync({force: true}).then(() => { 18 | return tzTable.create({foo: 'test'}).then(row => { 19 | expect(row).to.be.not.null; 20 | }); 21 | }); 22 | } 23 | 24 | it('should correctly parse the moment based timezone', function() { 25 | return checkTimezoneParsing(this.sequelize.options); 26 | }); 27 | 28 | it('should correctly parse the moment based timezone while fetching hstore oids', function() { 29 | // reset oids so we need to refetch them 30 | DataTypes.HSTORE.types.postgres.oids = []; 31 | DataTypes.HSTORE.types.postgres.array_oids = []; 32 | return checkTimezoneParsing(this.sequelize.options); 33 | }); 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /test/integration/dialects/postgres/error.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const 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 | const constraintName = 'overlap_period'; 13 | beforeEach(function() { 14 | const 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(() => { 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', () => { 28 | 29 | it('should contain error specific properties', () => { 30 | const 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 | const err = new Sequelize.ExclusionConstraintError(errDetails); 38 | 39 | _.each(errDetails, (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 | const 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(() => { 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/integration/dialects/postgres/hstore.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | expect = chai.expect, 5 | Support = require(__dirname + '/../../support'), 6 | dialect = Support.getTestDialect(), 7 | hstore = require('../../../../lib/dialects/postgres/hstore'); 8 | 9 | if (dialect.match(/^postgres/)) { 10 | describe('[POSTGRES Specific] hstore', () => { 11 | describe('stringify', () => { 12 | it('should handle empty objects correctly', () => { 13 | expect(hstore.stringify({ })).to.equal(''); 14 | }); 15 | 16 | it('should handle null values correctly', () => { 17 | expect(hstore.stringify({ null: null })).to.equal('"null"=>NULL'); 18 | }); 19 | 20 | it('should handle null values correctly', () => { 21 | expect(hstore.stringify({ foo: null })).to.equal('"foo"=>NULL'); 22 | }); 23 | 24 | it('should handle empty string correctly', () => { 25 | expect(hstore.stringify({foo: ''})).to.equal('"foo"=>\"\"'); 26 | }); 27 | 28 | it('should handle a string with backslashes correctly', () => { 29 | expect(hstore.stringify({foo: '\\'})).to.equal('"foo"=>"\\\\"'); 30 | }); 31 | 32 | it('should handle a string with double quotes correctly', () => { 33 | expect(hstore.stringify({foo: '""a"'})).to.equal('"foo"=>"\\"\\"a\\""'); 34 | }); 35 | 36 | it('should handle a string with single quotes correctly', () => { 37 | expect(hstore.stringify({foo: "''a'"})).to.equal('"foo"=>"\'\'\'\'a\'\'"'); 38 | }); 39 | 40 | it('should handle simple objects correctly', () => { 41 | expect(hstore.stringify({ test: 'value' })).to.equal('"test"=>"value"'); 42 | }); 43 | 44 | }); 45 | 46 | describe('parse', () => { 47 | it('should handle a null object correctly', () => { 48 | expect(hstore.parse(null)).to.deep.equal(null); 49 | }); 50 | 51 | it('should handle empty string correctly', () => { 52 | expect(hstore.parse('"foo"=>\"\"')).to.deep.equal({foo: ''}); 53 | }); 54 | 55 | it('should handle a string with double quotes correctly', () => { 56 | expect(hstore.parse('"foo"=>"\\\"\\\"a\\\""')).to.deep.equal({foo: '\"\"a\"'}); 57 | }); 58 | 59 | it('should handle a string with single quotes correctly', () => { 60 | expect(hstore.parse('"foo"=>"\'\'\'\'a\'\'"')).to.deep.equal({foo: "''a'"}); 61 | }); 62 | 63 | it('should handle a string with backslashes correctly', () => { 64 | expect(hstore.parse('"foo"=>"\\\\"')).to.deep.equal({foo: '\\'}); 65 | }); 66 | 67 | it('should handle empty objects correctly', () => { 68 | expect(hstore.parse('')).to.deep.equal({ }); 69 | }); 70 | 71 | it('should handle simple objects correctly', () => { 72 | expect(hstore.parse('"test"=>"value"')).to.deep.equal({ test: 'value' }); 73 | }); 74 | 75 | }); 76 | describe('stringify and parse', () => { 77 | it('should stringify then parse back the same structure', () => { 78 | const testObj = {foo: 'bar', count: '1', emptyString: '', quotyString: '""', extraQuotyString: '"""a"""""', backslashes: '\\f023', moreBackslashes: '\\f\\0\\2\\1', backslashesAndQuotes: '\\"\\"uhoh"\\"', nully: null}; 79 | expect(hstore.parse(hstore.stringify(testObj))).to.deep.equal(testObj); 80 | expect(hstore.parse(hstore.stringify(hstore.parse(hstore.stringify(testObj))))).to.deep.equal(testObj); 81 | }); 82 | }); 83 | }); 84 | } 85 | -------------------------------------------------------------------------------- /test/integration/dialects/sqlite/test.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fhrryDeveloper/sequelize/fea0ca730741ceb80a0a1a937432b5425b9760ed/test/integration/dialects/sqlite/test.sqlite -------------------------------------------------------------------------------- /test/integration/hooks/count.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | expect = chai.expect, 5 | Support = require(__dirname + '/../support'), 6 | DataTypes = require(__dirname + '/../../../lib/data-types'); 7 | 8 | describe(Support.getTestDialectTeaser('Hooks'), () => { 9 | beforeEach(function() { 10 | this.User = this.sequelize.define('User', { 11 | username: { 12 | type: DataTypes.STRING, 13 | allowNull: false 14 | }, 15 | mood: { 16 | type: DataTypes.ENUM, 17 | values: ['happy', 'sad', 'neutral'] 18 | } 19 | }); 20 | return this.sequelize.sync({ force: true }); 21 | }); 22 | 23 | describe('#count', () => { 24 | beforeEach(function() { 25 | return this.User.bulkCreate([ 26 | {username: 'adam', mood: 'happy'}, 27 | {username: 'joe', mood: 'sad'}, 28 | {username: 'joe', mood: 'happy'} 29 | ]); 30 | }); 31 | 32 | describe('on success', () => { 33 | it('hook runs', function() { 34 | let beforeHook = false; 35 | 36 | this.User.beforeCount(() => { 37 | beforeHook = true; 38 | }); 39 | 40 | return this.User.count().then(count => { 41 | expect(count).to.equal(3); 42 | expect(beforeHook).to.be.true; 43 | }); 44 | }); 45 | 46 | it('beforeCount hook can change options', function() { 47 | this.User.beforeCount(options => { 48 | options.where.username = 'adam'; 49 | }); 50 | 51 | return expect(this.User.count({where: {username: 'joe'}})).to.eventually.equal(1); 52 | }); 53 | }); 54 | 55 | describe('on error', () => { 56 | it('in beforeCount hook returns error', function() { 57 | this.User.beforeCount(() => { 58 | throw new Error('Oops!'); 59 | }); 60 | 61 | return expect(this.User.count({where: {username: 'adam'}})).to.be.rejectedWith('Oops!'); 62 | }); 63 | }); 64 | }); 65 | 66 | }); 67 | -------------------------------------------------------------------------------- /test/integration/hooks/destroy.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | expect = chai.expect, 5 | Support = require(__dirname + '/../support'), 6 | DataTypes = require(__dirname + '/../../../lib/data-types'), 7 | sinon = require('sinon'); 8 | 9 | describe(Support.getTestDialectTeaser('Hooks'), () => { 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('#destroy', () => { 25 | describe('on success', () => { 26 | it('should run hooks', function() { 27 | const beforeHook = sinon.spy(), 28 | afterHook = sinon.spy(); 29 | 30 | this.User.beforeDestroy(beforeHook); 31 | this.User.afterDestroy(afterHook); 32 | 33 | return this.User.create({username: 'Toni', mood: 'happy'}).then(user => { 34 | return user.destroy().then(() => { 35 | expect(beforeHook).to.have.been.calledOnce; 36 | expect(afterHook).to.have.been.calledOnce; 37 | }); 38 | }); 39 | }); 40 | }); 41 | 42 | describe('on error', () => { 43 | it('should return an error from before', function() { 44 | const beforeHook = sinon.spy(), 45 | afterHook = sinon.spy(); 46 | 47 | this.User.beforeDestroy(() => { 48 | beforeHook(); 49 | throw new Error('Whoops!'); 50 | }); 51 | this.User.afterDestroy(afterHook); 52 | 53 | return this.User.create({username: 'Toni', mood: 'happy'}).then(user => { 54 | return expect(user.destroy()).to.be.rejected.then(() => { 55 | expect(beforeHook).to.have.been.calledOnce; 56 | expect(afterHook).not.to.have.been.called; 57 | }); 58 | }); 59 | }); 60 | 61 | it('should return an error from after', function() { 62 | const beforeHook = sinon.spy(), 63 | afterHook = sinon.spy(); 64 | 65 | this.User.beforeDestroy(beforeHook); 66 | this.User.afterDestroy(() => { 67 | afterHook(); 68 | throw new Error('Whoops!'); 69 | }); 70 | 71 | return this.User.create({username: 'Toni', mood: 'happy'}).then(user => { 72 | return expect(user.destroy()).to.be.rejected.then(() => { 73 | expect(beforeHook).to.have.been.calledOnce; 74 | expect(afterHook).to.have.been.calledOnce; 75 | }); 76 | }); 77 | }); 78 | }); 79 | }); 80 | 81 | }); 82 | -------------------------------------------------------------------------------- /test/integration/hooks/restore.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | expect = chai.expect, 5 | Support = require(__dirname + '/../support'), 6 | DataTypes = require(__dirname + '/../../../lib/data-types'), 7 | sinon = require('sinon'); 8 | 9 | describe(Support.getTestDialectTeaser('Hooks'), () => { 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 | 22 | this.ParanoidUser = this.sequelize.define('ParanoidUser', { 23 | username: DataTypes.STRING, 24 | mood: { 25 | type: DataTypes.ENUM, 26 | values: ['happy', 'sad', 'neutral'] 27 | } 28 | }, { 29 | paranoid: true 30 | }); 31 | 32 | return this.sequelize.sync({ force: true }); 33 | }); 34 | 35 | describe('#restore', () => { 36 | describe('on success', () => { 37 | it('should run hooks', function() { 38 | const beforeHook = sinon.spy(), 39 | afterHook = sinon.spy(); 40 | 41 | this.ParanoidUser.beforeRestore(beforeHook); 42 | this.ParanoidUser.afterRestore(afterHook); 43 | 44 | return this.ParanoidUser.create({username: 'Toni', mood: 'happy'}).then(user => { 45 | return user.destroy().then(() => { 46 | return user.restore().then(() => { 47 | expect(beforeHook).to.have.been.calledOnce; 48 | expect(afterHook).to.have.been.calledOnce; 49 | }); 50 | }); 51 | }); 52 | }); 53 | }); 54 | 55 | describe('on error', () => { 56 | it('should return an error from before', function() { 57 | const beforeHook = sinon.spy(), 58 | afterHook = sinon.spy(); 59 | 60 | this.ParanoidUser.beforeRestore(() => { 61 | beforeHook(); 62 | throw new Error('Whoops!'); 63 | }); 64 | this.ParanoidUser.afterRestore(afterHook); 65 | 66 | return this.ParanoidUser.create({username: 'Toni', mood: 'happy'}).then(user => { 67 | return user.destroy().then(() => { 68 | return expect(user.restore()).to.be.rejected.then(() => { 69 | expect(beforeHook).to.have.been.calledOnce; 70 | expect(afterHook).not.to.have.been.called; 71 | }); 72 | }); 73 | }); 74 | }); 75 | 76 | it('should return an error from after', function() { 77 | const beforeHook = sinon.spy(), 78 | afterHook = sinon.spy(); 79 | 80 | this.ParanoidUser.beforeRestore(beforeHook); 81 | this.ParanoidUser.afterRestore(() => { 82 | afterHook(); 83 | throw new Error('Whoops!'); 84 | }); 85 | 86 | return this.ParanoidUser.create({username: 'Toni', mood: 'happy'}).then(user => { 87 | return user.destroy().then(() => { 88 | return expect(user.restore()).to.be.rejected.then(() => { 89 | expect(beforeHook).to.have.been.calledOnce; 90 | expect(afterHook).to.have.been.calledOnce; 91 | }); 92 | }); 93 | }); 94 | }); 95 | }); 96 | }); 97 | 98 | }); 99 | -------------------------------------------------------------------------------- /test/integration/hooks/upsert.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | expect = chai.expect, 5 | Support = require(__dirname + '/../support'), 6 | DataTypes = require(__dirname + '/../../../lib/data-types'), 7 | sinon = require('sinon'); 8 | 9 | if (Support.sequelize.dialect.supports.upserts) { 10 | describe(Support.getTestDialectTeaser('Hooks'), () => { 11 | beforeEach(function() { 12 | this.User = this.sequelize.define('User', { 13 | username: { 14 | type: DataTypes.STRING, 15 | allowNull: false, 16 | unique: true //Either Primary Key/Unique Keys should be passed to upsert 17 | }, 18 | mood: { 19 | type: DataTypes.ENUM, 20 | values: ['happy', 'sad', 'neutral'] 21 | } 22 | }); 23 | return this.sequelize.sync({ force: true }); 24 | }); 25 | 26 | describe('#upsert', () => { 27 | describe('on success', () => { 28 | it('should run hooks', function() { 29 | const beforeHook = sinon.spy(), 30 | afterHook = sinon.spy(); 31 | 32 | this.User.beforeUpsert(beforeHook); 33 | this.User.afterUpsert(afterHook); 34 | 35 | return this.User.upsert({username: 'Toni', mood: 'happy'}).then(() => { 36 | expect(beforeHook).to.have.been.calledOnce; 37 | expect(afterHook).to.have.been.calledOnce; 38 | }); 39 | }); 40 | }); 41 | 42 | describe('on error', () => { 43 | it('should return an error from before', function() { 44 | const beforeHook = sinon.spy(), 45 | afterHook = sinon.spy(); 46 | 47 | this.User.beforeUpsert(() => { 48 | beforeHook(); 49 | throw new Error('Whoops!'); 50 | }); 51 | this.User.afterUpsert(afterHook); 52 | 53 | return expect(this.User.upsert({username: 'Toni', mood: 'happy'})).to.be.rejected.then(() => { 54 | expect(beforeHook).to.have.been.calledOnce; 55 | expect(afterHook).not.to.have.been.called; 56 | }); 57 | }); 58 | 59 | it('should return an error from after', function() { 60 | const beforeHook = sinon.spy(), 61 | afterHook = sinon.spy(); 62 | 63 | this.User.beforeUpsert(beforeHook); 64 | this.User.afterUpsert(() => { 65 | afterHook(); 66 | throw new Error('Whoops!'); 67 | }); 68 | 69 | return expect(this.User.upsert({username: 'Toni', mood: 'happy'})).to.be.rejected.then(() => { 70 | expect(beforeHook).to.have.been.calledOnce; 71 | expect(afterHook).to.have.been.calledOnce; 72 | }); 73 | }); 74 | }); 75 | 76 | describe('preserves changes to values', () => { 77 | it('beforeUpsert', function() { 78 | let hookCalled = 0; 79 | const valuesOriginal = { mood: 'sad', username: 'leafninja' }; 80 | 81 | this.User.beforeUpsert(values => { 82 | values.mood = 'happy'; 83 | hookCalled++; 84 | }); 85 | 86 | return this.User.upsert(valuesOriginal).then(() => { 87 | expect(valuesOriginal.mood).to.equal('happy'); 88 | expect(hookCalled).to.equal(1); 89 | }); 90 | }); 91 | }); 92 | }); 93 | }); 94 | } 95 | -------------------------------------------------------------------------------- /test/integration/include/paranoid.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | expect = chai.expect, 5 | sinon = require('sinon'), 6 | Support = require(__dirname + '/../support'), 7 | DataTypes = require(__dirname + '/../../../lib/data-types'); 8 | 9 | describe(Support.getTestDialectTeaser('Paranoid'), () => { 10 | 11 | beforeEach(function( ) { 12 | const S = this.sequelize, 13 | DT = DataTypes, 14 | 15 | A = this.A = S.define('A', { name: DT.STRING }, { paranoid: true }), 16 | B = this.B = S.define('B', { name: DT.STRING }, { paranoid: true }), 17 | C = this.C = S.define('C', { name: DT.STRING }, { paranoid: true }), 18 | D = this.D = S.define('D', { name: DT.STRING }, { paranoid: true }); 19 | 20 | A.belongsTo(B); 21 | A.belongsToMany(D, {through: 'a_d'}); 22 | A.hasMany(C); 23 | 24 | B.hasMany(A); 25 | B.hasMany(C); 26 | 27 | C.belongsTo(A); 28 | C.belongsTo(B); 29 | 30 | D.belongsToMany(A, {through: 'a_d'}); 31 | 32 | return S.sync({ force: true }); 33 | }); 34 | 35 | before(function() { 36 | this.clock = sinon.useFakeTimers(); 37 | }); 38 | 39 | after(function() { 40 | this.clock.restore(); 41 | }); 42 | 43 | it('paranoid with timestamps: false should be ignored / not crash', function() { 44 | const S = this.sequelize, 45 | Test = S.define('Test', { 46 | name: DataTypes.STRING 47 | }, { 48 | timestamps: false, 49 | paranoid: true 50 | }); 51 | 52 | return S.sync({ force: true }).then(() => { 53 | return Test.findById(1); 54 | }); 55 | }); 56 | 57 | it('test if non required is marked as false', function( ) { 58 | const A = this.A, 59 | B = this.B, 60 | options = { 61 | include: [ 62 | { 63 | model: B, 64 | required: false 65 | } 66 | ] 67 | }; 68 | 69 | return A.find(options).then(() => { 70 | expect(options.include[0].required).to.be.equal(false); 71 | }); 72 | }); 73 | 74 | it('test if required is marked as true', function( ) { 75 | const A = this.A, 76 | B = this.B, 77 | options = { 78 | include: [ 79 | { 80 | model: B, 81 | required: true 82 | } 83 | ] 84 | }; 85 | 86 | return A.find(options).then(() => { 87 | expect(options.include[0].required).to.be.equal(true); 88 | }); 89 | }); 90 | 91 | it('should not load paranoid, destroyed instances, with a non-paranoid parent', function() { 92 | const X = this.sequelize.define('x', { 93 | name: DataTypes.STRING 94 | }, { 95 | paranoid: false 96 | }); 97 | 98 | const Y = this.sequelize.define('y', { 99 | name: DataTypes.STRING 100 | }, { 101 | timestamps: true, 102 | paranoid: true 103 | }); 104 | 105 | X.hasMany(Y); 106 | 107 | return this.sequelize.sync({ force: true}).bind(this).then(function() { 108 | return this.sequelize.Promise.all([ 109 | X.create(), 110 | Y.create() 111 | ]); 112 | }).spread(function(x, y) { 113 | this.x = x; 114 | this.y = y; 115 | 116 | return x.addY(y); 117 | }).then(function() { 118 | return this.y.destroy(); 119 | }).then(function() { 120 | //prevent CURRENT_TIMESTAMP to be same 121 | this.clock.tick(1000); 122 | 123 | return X.findAll({ 124 | include: [Y] 125 | }).get(0); 126 | }).then(x => { 127 | expect(x.ys).to.have.length(0); 128 | }); 129 | }); 130 | }); 131 | -------------------------------------------------------------------------------- /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'), () => { 9 | describe('optimistic locking', () => { 10 | let 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', () => { 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', () => { 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', () => { 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(() => { 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', () => { 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 | it('decrement() also increments the version', () => { 70 | return Account.create({number: 1}).then(account => { 71 | expect(account.version).to.eq(0); 72 | return account.decrement('number', { by: 1} ); 73 | }).then(account => { 74 | return account.reload(); 75 | }).then(account => { 76 | expect(account.version).to.eq(1); 77 | }); 78 | }); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /test/integration/model/scope.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | Sequelize = require('../../../index'), 5 | expect = chai.expect, 6 | Support = require(__dirname + '/../support'); 7 | 8 | describe(Support.getTestDialectTeaser('Model'), () => { 9 | describe('scope', () => { 10 | beforeEach(function() { 11 | this.ScopeMe = this.sequelize.define('ScopeMe', { 12 | username: Sequelize.STRING, 13 | email: Sequelize.STRING, 14 | access_level: Sequelize.INTEGER, 15 | other_value: Sequelize.INTEGER 16 | }, { 17 | scopes: { 18 | lowAccess: { 19 | attributes: ['other_value', 'access_level'], 20 | where: { 21 | access_level: { 22 | lte: 5 23 | } 24 | } 25 | }, 26 | withName: { 27 | attributes: ['username'] 28 | } 29 | } 30 | }); 31 | 32 | return this.sequelize.sync({force: true}).then(() => { 33 | const records = [ 34 | {username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7}, 35 | {username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11}, 36 | {username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10}, 37 | {username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7} 38 | ]; 39 | return this.ScopeMe.bulkCreate(records); 40 | }); 41 | }); 42 | 43 | it('should be able to merge attributes as array', function() { 44 | return this.ScopeMe.scope('lowAccess', 'withName').findOne() 45 | .then(record => { 46 | expect(record.other_value).to.exist; 47 | expect(record.username).to.exist; 48 | expect(record.access_level).to.exist; 49 | }); 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /test/integration/model/scope/findAndCount.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | Sequelize = require('../../../../index'), 5 | expect = chai.expect, 6 | Support = require(__dirname + '/../../support'); 7 | 8 | describe(Support.getTestDialectTeaser('Model'), () => { 9 | describe('scope', () => { 10 | 11 | describe('findAndCount', () => { 12 | 13 | beforeEach(function() { 14 | this.ScopeMe = this.sequelize.define('ScopeMe', { 15 | username: Sequelize.STRING, 16 | email: Sequelize.STRING, 17 | access_level: Sequelize.INTEGER, 18 | other_value: Sequelize.INTEGER 19 | }, { 20 | defaultScope: { 21 | where: { 22 | access_level: { 23 | gte: 5 24 | } 25 | }, 26 | attributes: ['username', 'email', 'access_level'] 27 | }, 28 | scopes: { 29 | lowAccess: { 30 | where: { 31 | access_level: { 32 | lte: 5 33 | } 34 | } 35 | }, 36 | withOrder: { 37 | order: ['username'] 38 | } 39 | } 40 | }); 41 | 42 | return this.sequelize.sync({force: true}).then(() => { 43 | const records = [ 44 | {username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7}, 45 | {username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11}, 46 | {username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10}, 47 | {username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7} 48 | ]; 49 | return this.ScopeMe.bulkCreate(records); 50 | }); 51 | }); 52 | 53 | it('should apply defaultScope', function() { 54 | return this.ScopeMe.findAndCount().then(result => { 55 | expect(result.count).to.equal(2); 56 | expect(result.rows.length).to.equal(2); 57 | }); 58 | }); 59 | 60 | it('should be able to override default scope', function() { 61 | return this.ScopeMe.findAndCount({ where: { access_level: { gt: 5 }}}) 62 | .then(result => { 63 | expect(result.count).to.equal(1); 64 | expect(result.rows.length).to.equal(1); 65 | }); 66 | }); 67 | 68 | it('should be able to unscope', function() { 69 | return this.ScopeMe.unscoped().findAndCount({ limit: 1 }) 70 | .then(result => { 71 | expect(result.count).to.equal(4); 72 | expect(result.rows.length).to.equal(1); 73 | }); 74 | }); 75 | 76 | it('should be able to apply other scopes', function() { 77 | return this.ScopeMe.scope('lowAccess').findAndCount() 78 | .then(result => { 79 | expect(result.count).to.equal(3); 80 | }); 81 | }); 82 | 83 | it('should be able to merge scopes with where', function() { 84 | return this.ScopeMe.scope('lowAccess') 85 | .findAndCount({ where: { username: 'dan'}}).then(result => { 86 | expect(result.count).to.equal(1); 87 | }); 88 | }); 89 | 90 | it('should ignore the order option if it is found within the scope', function() { 91 | return this.ScopeMe.scope('withOrder').findAndCount() 92 | .then(result => { 93 | expect(result.count).to.equal(4); 94 | }); 95 | }); 96 | }); 97 | }); 98 | }); 99 | -------------------------------------------------------------------------------- /test/integration/model/update.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 | const current = Support.sequelize; 8 | const _ = require('lodash'); 9 | 10 | describe(Support.getTestDialectTeaser('Model'), () => { 11 | describe('update', () => { 12 | beforeEach(function() { 13 | this.Account = this.sequelize.define('Account', { 14 | ownerId: { 15 | type: DataTypes.INTEGER, 16 | allowNull: false, 17 | field: 'owner_id' 18 | }, 19 | name: { 20 | type: DataTypes.STRING 21 | } 22 | }); 23 | return this.Account.sync({force: true}); 24 | }); 25 | 26 | it('should only update the passed fields', function() { 27 | return this.Account 28 | .create({ ownerId: 2 }) 29 | .then(account => this.Account.update({ 30 | name: Math.random().toString() 31 | }, { 32 | where: { 33 | id: account.get('id') 34 | } 35 | })); 36 | }); 37 | 38 | 39 | if (_.get(current.dialect.supports, 'returnValues.returning')) { 40 | it('should return the updated record', function() { 41 | return this.Account.create({ ownerId: 2 }).then(account => { 42 | return this.Account.update({ name: 'FooBar' }, { 43 | where: { 44 | id: account.get('id') 45 | }, 46 | returning: true 47 | }).spread((count, accounts) => { 48 | const firstAcc = accounts[0]; 49 | expect(firstAcc.ownerId).to.be.equal(2); 50 | expect(firstAcc.name).to.be.equal('FooBar'); 51 | }); 52 | }); 53 | }); 54 | } 55 | 56 | if (current.dialect.supports['LIMIT ON UPDATE']) { 57 | it('should only update one row', function() { 58 | return this.Account.create({ 59 | ownerId: 2, 60 | name: 'Account Name 1' 61 | }) 62 | .then(() => { 63 | return this.Account.create({ 64 | ownerId: 2, 65 | name: 'Account Name 2' 66 | }); 67 | }) 68 | .then(() => { 69 | return this.Account.create({ 70 | ownerId: 2, 71 | name: 'Account Name 3' 72 | }); 73 | }) 74 | .then(() => { 75 | const options = { 76 | where: { 77 | ownerId: 2 78 | }, 79 | limit: 1 80 | }; 81 | return this.Account.update({ name: 'New Name' }, options); 82 | }) 83 | .then(account => { 84 | expect(account[0]).to.equal(1); 85 | }); 86 | }); 87 | } 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /test/integration/pool.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'); 4 | const expect = chai.expect; 5 | const Support = require(__dirname + '/support'); 6 | const dialect = Support.getTestDialect(); 7 | const sinon = require('sinon'); 8 | const Sequelize = Support.Sequelize; 9 | 10 | describe(Support.getTestDialectTeaser('Pooling'), function() { 11 | if (dialect === 'sqlite') return; 12 | 13 | beforeEach(() => { 14 | this.sinon = sinon.sandbox.create(); 15 | }); 16 | 17 | afterEach(() => { 18 | this.sinon.restore(); 19 | }); 20 | 21 | it('should reject when unable to acquire connection in given time', () => { 22 | this.testInstance = new Sequelize('localhost', 'ffd', 'dfdf', { 23 | dialect, 24 | databaseVersion: '1.2.3', 25 | pool: { 26 | acquire: 1000 //milliseconds 27 | } 28 | }); 29 | 30 | this.sinon.stub(this.testInstance.connectionManager, '_connect') 31 | .returns(new Sequelize.Promise(() => {})); 32 | 33 | return expect(this.testInstance.authenticate()) 34 | .to.eventually.be.rejectedWith('ResourceRequest timed out'); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/integration/replication.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'); 4 | const expect = chai.expect; 5 | const Support = require(__dirname + '/support'); 6 | const DataTypes = require(__dirname + '/../../lib/data-types'); 7 | const dialect = Support.getTestDialect(); 8 | const sinon = require('sinon'); 9 | 10 | describe(Support.getTestDialectTeaser('Replication'), function() { 11 | if (dialect === 'sqlite') return; 12 | 13 | let sandbox; 14 | let readSpy, writeSpy; 15 | 16 | beforeEach(() => { 17 | sandbox = sinon.sandbox.create(); 18 | 19 | this.sequelize = Support.getSequelizeInstance(null, null, null, { 20 | replication: { 21 | write: Support.getConnectionOptions(), 22 | read: [Support.getConnectionOptions()] 23 | } 24 | }); 25 | 26 | expect(this.sequelize.connectionManager.pool.write).to.be.ok; 27 | expect(this.sequelize.connectionManager.pool.read).to.be.ok; 28 | 29 | this.User = this.sequelize.define('User', { 30 | firstName: { 31 | type: DataTypes.STRING, 32 | field: 'first_name' 33 | } 34 | }); 35 | 36 | return this.User.sync({force: true}) 37 | .then(() => { 38 | readSpy = sandbox.spy(this.sequelize.connectionManager.pool.read, 'acquire'); 39 | writeSpy = sandbox.spy(this.sequelize.connectionManager.pool.write, 'acquire'); 40 | }); 41 | }); 42 | 43 | afterEach(() => { 44 | sandbox.restore(); 45 | }); 46 | 47 | function expectReadCalls() { 48 | chai.expect(readSpy.callCount).least(1); 49 | chai.expect(writeSpy.notCalled).eql(true); 50 | } 51 | 52 | function expectWriteCalls() { 53 | chai.expect(writeSpy.callCount).least(1); 54 | chai.expect(readSpy.notCalled).eql(true); 55 | } 56 | 57 | it('should be able to make a write', () => { 58 | return this.User.create({ 59 | firstName: Math.random().toString() 60 | }).then(expectWriteCalls); 61 | }); 62 | 63 | it('should be able to make a read', () => { 64 | return this.User.findAll().then(expectReadCalls); 65 | }); 66 | 67 | it('should run read-only transactions on the replica', () => { 68 | return this.sequelize.transaction({readOnly: true}, transaction => { 69 | return this.User.findAll({transaction}); 70 | }).then(expectReadCalls); 71 | }); 72 | 73 | it('should run non-read-only transactions on the primary', () => { 74 | return this.sequelize.transaction(transaction => { 75 | return this.User.findAll({transaction}); 76 | }).then(expectWriteCalls); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /test/integration/schema.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | expect = chai.expect, 5 | Support = require(__dirname + '/support'), 6 | DataTypes = require(__dirname + '/../../lib/data-types'); 7 | 8 | describe(Support.getTestDialectTeaser('Schema'), () => { 9 | beforeEach(function() { 10 | return this.sequelize.createSchema('testschema'); 11 | }); 12 | 13 | afterEach(function() { 14 | return this.sequelize.dropSchema('testschema'); 15 | }); 16 | 17 | beforeEach(function() { 18 | this.User = this.sequelize.define('User', { 19 | aNumber: { type: DataTypes.INTEGER } 20 | }, { 21 | schema: 'testschema' 22 | }); 23 | 24 | return this.User.sync({ force: true }); 25 | }); 26 | 27 | it('supports increment', function() { 28 | return this.User.create({ aNumber: 1 }).then(user => { 29 | return user.increment('aNumber', { by: 3 }); 30 | }).then(result => { 31 | return result.reload(); 32 | }).then(user => { 33 | expect(user).to.be.ok; 34 | expect(user.aNumber).to.be.equal(4); 35 | }); 36 | }); 37 | 38 | it('supports decrement', function() { 39 | return this.User.create({ aNumber: 10 }).then(user => { 40 | return user.decrement('aNumber', { by: 3 }); 41 | }).then(result => { 42 | return result.reload(); 43 | }).then(user => { 44 | expect(user).to.be.ok; 45 | expect(user.aNumber).to.be.equal(7); 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test/integration/sequelize/log.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | sinon = require('sinon'), 5 | expect = chai.expect, 6 | Support = require(__dirname + '/../support'), 7 | dialect = Support.getTestDialect(); 8 | 9 | describe(Support.getTestDialectTeaser('Sequelize'), () => { 10 | describe('log', () => { 11 | beforeEach(function() { 12 | this.spy = sinon.spy(console, 'log'); 13 | }); 14 | 15 | afterEach(() => { 16 | console.log.restore(); 17 | }); 18 | 19 | describe('with disabled logging', () => { 20 | beforeEach(function() { 21 | this.sequelize = new Support.Sequelize('db', 'user', 'pw', { dialect, logging: false }); 22 | }); 23 | 24 | it('does not call the log method of the logger', function() { 25 | this.sequelize.log(); 26 | expect(this.spy.calledOnce).to.be.false; 27 | }); 28 | }); 29 | 30 | describe('with default logging options', () => { 31 | beforeEach(function() { 32 | this.sequelize = new Support.Sequelize('db', 'user', 'pw', { dialect }); 33 | }); 34 | 35 | describe('called with no arguments', () => { 36 | it('calls the log method', function() { 37 | this.sequelize.log(); 38 | expect(this.spy.calledOnce).to.be.true; 39 | }); 40 | 41 | it('logs an empty string as info event', function() { 42 | this.sequelize.log(''); 43 | expect(this.spy.calledOnce).to.be.true; 44 | }); 45 | }); 46 | 47 | describe('called with one argument', () => { 48 | it('logs the passed string as info event', function() { 49 | this.sequelize.log('my message'); 50 | expect(this.spy.withArgs('my message').calledOnce).to.be.true; 51 | }); 52 | }); 53 | 54 | describe('called with more than two arguments', () => { 55 | it('passes the arguments to the logger', function() { 56 | this.sequelize.log('error', 'my message', 1, { a: 1 }); 57 | expect(this.spy.withArgs('error', 'my message', 1, { a: 1 }).calledOnce).to.be.true; 58 | }); 59 | }); 60 | }); 61 | 62 | describe('with a custom function for logging', () => { 63 | beforeEach(function() { 64 | this.spy = sinon.spy(); 65 | this.sequelize = new Support.Sequelize('db', 'user', 'pw', { dialect, logging: this.spy }); 66 | }); 67 | 68 | it('calls the custom logger method', function() { 69 | this.sequelize.log('om nom'); 70 | expect(this.spy.calledOnce).to.be.true; 71 | }); 72 | 73 | it('calls the custom logger method with options', function() { 74 | const message = 'om nom'; 75 | const timeTaken = 5; 76 | const options = {correlationId: 'ABC001'}; 77 | this.sequelize.log(message, timeTaken, options); 78 | expect(this.spy.withArgs(message, timeTaken, options).calledOnce).to.be.true; 79 | }); 80 | 81 | }); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /test/integration/support.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Support = require('../support'); 4 | 5 | beforeEach(function() { 6 | this.sequelize.test.trackRunningQueries(); 7 | return Support.clearDatabase(this.sequelize); 8 | }); 9 | 10 | afterEach(function() { 11 | try { 12 | this.sequelize.test.verifyNoRunningQueries(); 13 | } catch (err) { 14 | err.message += ' in '+this.currentTest.fullTitle(); 15 | throw err; 16 | } 17 | }); 18 | 19 | module.exports = Support; 20 | -------------------------------------------------------------------------------- /test/integration/timezone.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | expect = chai.expect, 5 | Support = require(__dirname + '/support'), 6 | dialect = Support.getTestDialect(), 7 | Sequelize = require(__dirname + '/../../index'), 8 | Promise = Sequelize.Promise; 9 | 10 | if (dialect !== 'sqlite') { 11 | // Sqlite does not support setting timezone 12 | 13 | describe(Support.getTestDialectTeaser('Timezone'), () => { 14 | beforeEach(function() { 15 | this.sequelizeWithTimezone = Support.createSequelizeInstance({ 16 | timezone: '+07:00' 17 | }); 18 | this.sequelizeWithNamedTimezone = Support.createSequelizeInstance({ 19 | timezone: 'America/New_York' 20 | }); 21 | }); 22 | 23 | it('returns the same value for current timestamp', function() { 24 | let now = 'now()'; 25 | const startQueryTime = Date.now(); 26 | 27 | if (dialect === 'mssql') { 28 | now = 'GETDATE()'; 29 | } 30 | 31 | const query = 'SELECT ' + now + ' as now'; 32 | return Promise.all([ 33 | this.sequelize.query(query, { type: this.sequelize.QueryTypes.SELECT }), 34 | this.sequelizeWithTimezone.query(query, { type: this.sequelize.QueryTypes.SELECT }) 35 | ]).spread((now1, now2) => { 36 | const elapsedQueryTime = Date.now() - startQueryTime + 1001; 37 | expect(now1[0].now.getTime()).to.be.closeTo(now2[0].now.getTime(), elapsedQueryTime); 38 | }); 39 | }); 40 | 41 | if (dialect === 'mysql') { 42 | it('handles existing timestamps', function() { 43 | const NormalUser = this.sequelize.define('user', {}), 44 | TimezonedUser = this.sequelizeWithTimezone.define('user', {}); 45 | 46 | return this.sequelize.sync({ force: true }).bind(this).then(() => { 47 | return NormalUser.create({}); 48 | }).then(function(normalUser) { 49 | this.normalUser = normalUser; 50 | return TimezonedUser.findById(normalUser.id); 51 | }).then(function(timezonedUser) { 52 | // Expect 7 hours difference, in milliseconds. 53 | // This difference is expected since two instances, configured for each their timezone is trying to read the same timestamp 54 | // this test does not apply to PG, since it stores the timezone along with the timestamp. 55 | expect(this.normalUser.createdAt.getTime() - timezonedUser.createdAt.getTime()).to.be.closeTo(60 * 60 * 7 * 1000, 1000); 56 | }); 57 | }); 58 | 59 | it('handles named timezones', function() { 60 | const NormalUser = this.sequelize.define('user', {}), 61 | TimezonedUser = this.sequelizeWithNamedTimezone.define('user', {}); 62 | 63 | return this.sequelize.sync({ force: true }).bind(this).then(() => { 64 | return TimezonedUser.create({}); 65 | }).then(timezonedUser => { 66 | return Promise.all([ 67 | NormalUser.findById(timezonedUser.id), 68 | TimezonedUser.findById(timezonedUser.id) 69 | ]); 70 | }).spread((normalUser, timezonedUser) => { 71 | // Expect 5 hours difference, in milliseconds, +/- 1 hour for DST 72 | expect(normalUser.createdAt.getTime() - timezonedUser.createdAt.getTime()).to.be.closeTo(60 * 60 * 4 * 1000 * -1, 60 * 60 * 1000); 73 | }); 74 | }); 75 | } 76 | }); 77 | } 78 | -------------------------------------------------------------------------------- /test/integration/tmp/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fhrryDeveloper/sequelize/fea0ca730741ceb80a0a1a937432b5425b9760ed/test/integration/tmp/.gitkeep -------------------------------------------------------------------------------- /test/integration/trigger.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | Sequelize = require('../../index'), 5 | expect = chai.expect, 6 | Support = require(__dirname + '/../support'), 7 | current = Support.sequelize; 8 | 9 | if (current.dialect.supports.tmpTableTrigger) { 10 | describe(Support.getTestDialectTeaser('Model'), () => { 11 | describe('trigger', () => { 12 | let User; 13 | const triggerQuery = 'create trigger User_ChangeTracking on [users] for insert,update, delete \n' + 14 | 'as\n' + 15 | 'SET NOCOUNT ON\n' + 16 | 'if exists(select 1 from inserted)\n' + 17 | 'begin\n' + 18 | 'select * from inserted\n' + 19 | 'end\n' + 20 | 'if exists(select 1 from deleted)\n' + 21 | 'begin\n' + 22 | 'select * from deleted\n' + 23 | 'end\n'; 24 | 25 | beforeEach(function() { 26 | User = this.sequelize.define('user', { 27 | username: { 28 | type: Sequelize.STRING, 29 | field: 'user_name' 30 | } 31 | }, { 32 | hasTrigger: true 33 | }); 34 | 35 | return User.sync({force: true}).bind(this).then(function() { 36 | return this.sequelize.query(triggerQuery, {type: this.sequelize.QueryTypes.RAW}); 37 | }); 38 | }); 39 | 40 | it('should return output rows after insert', () => { 41 | return User.create({ 42 | username: 'triggertest' 43 | }).then(() => { 44 | return expect(User.find({username: 'triggertest'})).to.eventually.have.property('username').which.equals('triggertest'); 45 | }); 46 | }); 47 | 48 | it('should return output rows after instance update', () => { 49 | return User.create({ 50 | username: 'triggertest' 51 | }).then(user => { 52 | user.username = 'usernamechanged'; 53 | return user.save(); 54 | }) 55 | .then(() => { 56 | return expect(User.find({username: 'usernamechanged'})).to.eventually.have.property('username').which.equals('usernamechanged'); 57 | }); 58 | }); 59 | 60 | it('should return output rows after Model update', () => { 61 | return User.create({ 62 | username: 'triggertest' 63 | }).then(user => { 64 | return User.update({ 65 | username: 'usernamechanged' 66 | }, { 67 | where: { 68 | id: user.get('id') 69 | } 70 | }); 71 | }) 72 | .then(() => { 73 | return expect(User.find({username: 'usernamechanged'})).to.eventually.have.property('username').which.equals('usernamechanged'); 74 | }); 75 | }); 76 | 77 | it('should successfully delete with a trigger on the table', () => { 78 | return User.create({ 79 | username: 'triggertest' 80 | }).then(user => { 81 | return user.destroy(); 82 | }).then(() => { 83 | return expect(User.find({username: 'triggertest'})).to.eventually.be.null; 84 | }); 85 | }); 86 | }); 87 | }); 88 | } 89 | -------------------------------------------------------------------------------- /test/integration/vectors.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | expect = chai.expect, 5 | Sequelize = require('../../index'), 6 | Support = require(__dirname + '/support'); 7 | 8 | chai.should(); 9 | 10 | describe(Support.getTestDialectTeaser('Vectors'), () => { 11 | it('should not allow insert backslash', function() { 12 | const Student = this.sequelize.define('student', { 13 | name: Sequelize.STRING 14 | }, { 15 | tableName: 'student' 16 | }); 17 | 18 | return Student.sync({force: true}).then(() => { 19 | return Student.create({ 20 | name: 'Robert\\\'); DROP TABLE "students"; --' 21 | }).then(result => { 22 | expect(result.get('name')).to.equal('Robert\\\'); DROP TABLE "students"; --'); 23 | return Student.findAll(); 24 | }).then(result => { 25 | expect(result[0].name).to.equal('Robert\\\'); DROP TABLE "students"; --'); 26 | }); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test/tmp/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fhrryDeveloper/sequelize/fea0ca730741ceb80a0a1a937432b5425b9760ed/test/tmp/.gitkeep -------------------------------------------------------------------------------- /test/unit/associations/association.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'); 4 | const expect = chai.expect; 5 | const Support = require(__dirname + '/../support'); 6 | const current = Support.sequelize; 7 | const AssociationError = require(__dirname + '/../../../lib/errors').AssociationError; 8 | 9 | describe(Support.getTestDialectTeaser('belongsTo'), () => { 10 | it('should throw an AssociationError when two associations have the same alias', () => { 11 | const User = current.define('User'); 12 | const Task = current.define('Task'); 13 | 14 | User.belongsTo(Task, { as: 'task' }); 15 | const errorFunction = User.belongsTo.bind(User, Task, { as: 'task' }); 16 | const errorMessage = 'You have used the alias task in two separate associations. Aliased associations must have unique aliases.'; 17 | expect(errorFunction).to.throw(AssociationError, errorMessage); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/unit/associations/belongs-to.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | expect = chai.expect, 5 | _ = require('lodash'), 6 | Support = require(__dirname + '/../support'), 7 | current = Support.sequelize; 8 | 9 | describe(Support.getTestDialectTeaser('belongsTo'), () => { 10 | it('should not override custom methods with association mixin', () => { 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 | _.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 | _.each(methods, (alias, method) => { 32 | expect(user[method]()).to.be.a('function'); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/unit/associations/dont-modify-options.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | expect = chai.expect, 5 | Support = require(__dirname + '/../support'), 6 | DataTypes = require(__dirname + '/../../../lib/data-types'), 7 | Sequelize = require('../../../index'); 8 | 9 | describe(Support.getTestDialectTeaser('associations'), () => { 10 | describe('Test options.foreignKey', () => { 11 | beforeEach(function() { 12 | 13 | this.A = this.sequelize.define('A', { 14 | id: { 15 | type: DataTypes.CHAR(20), 16 | primaryKey: true 17 | } 18 | }); 19 | this.B = this.sequelize.define('B', { 20 | id: { 21 | type: Sequelize.CHAR(20), 22 | primaryKey: true 23 | } 24 | }); 25 | this.C = this.sequelize.define('C', {}); 26 | }); 27 | 28 | it('should not be overwritten for belongsTo', function() { 29 | const reqValidForeignKey = { foreignKey: { allowNull: false }}; 30 | this.A.belongsTo(this.B, reqValidForeignKey); 31 | this.A.belongsTo(this.C, reqValidForeignKey); 32 | expect(this.A.rawAttributes.CId.type).to.deep.equal(this.C.rawAttributes.id.type); 33 | }); 34 | it('should not be overwritten for belongsToMany', function() { 35 | const reqValidForeignKey = { foreignKey: { allowNull: false }, through: 'ABBridge'}; 36 | this.B.belongsToMany(this.A, reqValidForeignKey); 37 | this.A.belongsTo(this.C, reqValidForeignKey); 38 | expect(this.A.rawAttributes.CId.type).to.deep.equal(this.C.rawAttributes.id.type); 39 | }); 40 | it('should not be overwritten for hasOne', function() { 41 | const reqValidForeignKey = { foreignKey: { allowNull: false }}; 42 | this.B.hasOne(this.A, reqValidForeignKey); 43 | this.A.belongsTo(this.C, reqValidForeignKey); 44 | expect(this.A.rawAttributes.CId.type).to.deep.equal(this.C.rawAttributes.id.type); 45 | }); 46 | it('should not be overwritten for hasMany', function() { 47 | const reqValidForeignKey = { foreignKey: { allowNull: false }}; 48 | this.B.hasMany(this.A, reqValidForeignKey); 49 | this.A.belongsTo(this.C, reqValidForeignKey); 50 | expect(this.A.rawAttributes.CId.type).to.deep.equal(this.C.rawAttributes.id.type); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /test/unit/associations/has-one.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | expect = chai.expect, 5 | _ = require('lodash'), 6 | Support = require(__dirname + '/../support'), 7 | DataTypes = require(__dirname + '/../../../lib/data-types'), 8 | current = Support.sequelize; 9 | 10 | describe(Support.getTestDialectTeaser('hasOne'), () => { 11 | it('properly use the `as` key to generate foreign key name', () => { 12 | const User = current.define('User', { username: DataTypes.STRING }), 13 | Task = current.define('Task', { title: DataTypes.STRING }); 14 | 15 | User.hasOne(Task); 16 | expect(Task.rawAttributes.UserId).not.to.be.empty; 17 | 18 | User.hasOne(Task, {as: 'Shabda'}); 19 | expect(Task.rawAttributes.ShabdaId).not.to.be.empty; 20 | }); 21 | 22 | it('should not override custom methods with association mixin', () => { 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 | _.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 | _.each(methods, (alias, method) => { 44 | expect(user[method]()).to.be.a('function'); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test/unit/connection-manager.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | sinon = require('sinon'), 5 | expect = chai.expect, 6 | Support = require(__dirname + '/support'), 7 | Sequelize = require(__dirname + '/../../index'), 8 | ConnectionManager = require(__dirname + '/../../lib/dialects/abstract/connection-manager'), 9 | Promise = Sequelize.Promise; 10 | 11 | describe('connection manager', () => { 12 | describe('_connect', () => { 13 | beforeEach(function() { 14 | this.sinon = sinon.sandbox.create(); 15 | this.connection = {}; 16 | 17 | this.dialect = { 18 | connectionManager: { 19 | connect: this.sinon.stub().returns(Promise.resolve(this.connection)) 20 | } 21 | }; 22 | 23 | this.sequelize = Support.createSequelizeInstance(); 24 | }); 25 | 26 | afterEach(function() { 27 | this.sinon.restore(); 28 | }); 29 | 30 | it('should resolve connection on dialect connection manager', function() { 31 | const connection = {}; 32 | this.dialect.connectionManager.connect.returns(Promise.resolve(connection)); 33 | 34 | const connectionManager = new ConnectionManager(this.dialect, this.sequelize); 35 | 36 | const config = {}; 37 | 38 | return expect(connectionManager._connect(config)).to.eventually.equal(connection).then(() => { 39 | expect(this.dialect.connectionManager.connect).to.have.been.calledWith(config); 40 | }); 41 | }); 42 | 43 | it('should let beforeConnect hook modify config', function() { 44 | const username = Math.random().toString(), 45 | password = Math.random().toString(); 46 | 47 | this.sequelize.beforeConnect(config => { 48 | config.username = username; 49 | config.password = password; 50 | return config; 51 | }); 52 | 53 | const connectionManager = new ConnectionManager(this.dialect, this.sequelize); 54 | 55 | return connectionManager._connect({}).then(() => { 56 | expect(this.dialect.connectionManager.connect).to.have.been.calledWith({ 57 | username, 58 | password 59 | }); 60 | }); 61 | }); 62 | 63 | it('should call afterConnect', function() { 64 | const spy = sinon.spy(); 65 | this.sequelize.afterConnect(spy); 66 | 67 | const connectionManager = new ConnectionManager(this.dialect, this.sequelize); 68 | 69 | return connectionManager._connect({}).then(() => { 70 | expect(spy.callCount).to.equal(1); 71 | expect(spy.firstCall.args[0]).to.equal(this.connection); 72 | expect(spy.firstCall.args[1]).to.eql({}); 73 | }); 74 | }); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /test/unit/dialects/mssql/connection-manager.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | expect = chai.expect, 5 | Sequelize = require(__dirname + '/../../../../index'), 6 | tedious = require('tedious'), 7 | sinon = require('sinon'), 8 | connectionStub = sinon.stub(tedious, 'Connection'); 9 | 10 | connectionStub.returns({on() {}}); 11 | 12 | describe('[MSSQL] Connection Manager', () => { 13 | let instance, 14 | config; 15 | beforeEach(() => { 16 | config = { 17 | dialect: 'mssql', 18 | database: 'none', 19 | username: 'none', 20 | password: 'none', 21 | host: 'localhost', 22 | port: 2433, 23 | pool: {}, 24 | dialectOptions: { 25 | domain: 'TEST.COM' 26 | } 27 | }; 28 | instance = new Sequelize(config.database 29 | , config.username 30 | , config.password 31 | , config); 32 | }); 33 | 34 | it('connectionManager._connect() Does not delete `domain` from config.dialectOptions', 35 | () => { 36 | expect(config.dialectOptions.domain).to.equal('TEST.COM'); 37 | instance.dialect.connectionManager._connect(config); 38 | expect(config.dialectOptions.domain).to.equal('TEST.COM'); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /test/unit/dialects/mssql/query.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const Query = require(path.resolve('./lib/dialects/mssql/query.js')); 5 | const Support = require(path.resolve('./test/support')); 6 | const sequelize = Support.sequelize; 7 | const sinon = require('sinon'); 8 | const expect = require('chai').expect; 9 | const tedious = require('tedious'); 10 | const tediousIsolationLevel = tedious.ISOLATION_LEVEL; 11 | const connectionStub = { beginTransaction: () => {}, lib: tedious }; 12 | 13 | let sandbox, query; 14 | 15 | describe('[MSSQL]', () => { 16 | describe('beginTransaction', () => { 17 | beforeEach(() => { 18 | sandbox = sinon.sandbox.create(); 19 | const options = { 20 | transaction: { name: 'transactionName' }, 21 | isolationLevel: 'REPEATABLE_READ', 22 | logging: false 23 | }; 24 | sandbox.stub(connectionStub, 'beginTransaction').callsFake(cb => { 25 | cb(); 26 | }); 27 | query = new Query(connectionStub, sequelize, options); 28 | }); 29 | 30 | it('should call beginTransaction with correct arguments', () => { 31 | return query._run(connectionStub, 'BEGIN TRANSACTION') 32 | .then(() => { 33 | expect(connectionStub.beginTransaction.called).to.equal(true); 34 | expect(connectionStub.beginTransaction.args[0][1]).to.equal('transactionName'); 35 | expect(connectionStub.beginTransaction.args[0][2]).to.equal(tediousIsolationLevel.REPEATABLE_READ); 36 | }); 37 | }); 38 | 39 | afterEach(() => { 40 | sandbox.restore(); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/unit/dialects/mssql/resource-lock.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const ResourceLock = require('../../../../lib/dialects/mssql/resource-lock'), 4 | Promise = require('../../../../lib/promise'), 5 | assert = require('assert'); 6 | 7 | describe('[MSSQL Specific] ResourceLock', () => { 8 | it('should process requests serially', () => { 9 | const expected = {}; 10 | const lock = new ResourceLock(expected); 11 | let last = 0; 12 | 13 | function validateResource(actual) { 14 | assert.equal(actual, expected); 15 | } 16 | 17 | return Promise.all([ 18 | Promise.using(lock.lock(), resource => { 19 | validateResource(resource); 20 | assert.equal(last, 0); 21 | last = 1; 22 | 23 | return Promise.delay(15); 24 | }), 25 | Promise.using(lock.lock(), resource => { 26 | validateResource(resource); 27 | assert.equal(last, 1); 28 | last = 2; 29 | }), 30 | Promise.using(lock.lock(), 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', () => { 41 | const expected = {}; 42 | const 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(), resource => { 50 | validateResource(resource); 51 | 52 | throw new Error('unexpected error'); 53 | }).catch(() => {}), 54 | Promise.using(lock.lock(), validateResource) 55 | ]); 56 | }); 57 | 58 | it('should be able to.lock resource without waiting on lock', () => { 59 | const expected = {}; 60 | const lock = new ResourceLock(expected); 61 | 62 | assert.equal(lock.unwrap(), expected); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /test/unit/dialects/mysql/errors.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'); 4 | const expect = chai.expect; 5 | const Support = require(__dirname + '/../../support'); 6 | const dialect = Support.getTestDialect(); 7 | const queryProto = Support.sequelize.dialect.Query.prototype; 8 | 9 | if (dialect === 'mysql') { 10 | describe('[MYSQL Specific] ForeignKeyConstraintError - error message parsing', () => { 11 | it('FK Errors with ` quotation char are parsed correctly', () => { 12 | const fakeErr = new Error('Cannot delete or update a parent row: a foreign key constraint fails (`table`.`brothers`, CONSTRAINT `brothers_ibfk_1` FOREIGN KEY (`personId`) REFERENCES `people` (`id`) ON UPDATE CASCADE).'); 13 | 14 | fakeErr.code = 1451; 15 | 16 | const parsedErr = queryProto.formatError(fakeErr); 17 | 18 | expect(parsedErr).to.be.instanceOf(Support.sequelize.ForeignKeyConstraintError); 19 | expect(parsedErr.parent).to.equal(fakeErr); 20 | expect(parsedErr.reltype).to.equal('parent'); 21 | expect(parsedErr.table).to.equal('people'); 22 | expect(parsedErr.fields).to.be.an('array').to.deep.equal(['personId']); 23 | expect(parsedErr.value).to.be.undefined; 24 | expect(parsedErr.index).to.equal('brothers_ibfk_1'); 25 | }); 26 | 27 | it('FK Errors with " quotation char are parsed correctly', () => { 28 | const fakeErr = new Error('Cannot delete or update a parent row: a foreign key constraint fails ("table"."brothers", CONSTRAINT "brothers_ibfk_1" FOREIGN KEY ("personId") REFERENCES "people" ("id") ON UPDATE CASCADE).'); 29 | 30 | fakeErr.code = 1451; 31 | 32 | const parsedErr = queryProto.formatError(fakeErr); 33 | 34 | expect(parsedErr).to.be.instanceOf(Support.sequelize.ForeignKeyConstraintError); 35 | expect(parsedErr.parent).to.equal(fakeErr); 36 | expect(parsedErr.reltype).to.equal('parent'); 37 | expect(parsedErr.table).to.equal('people'); 38 | expect(parsedErr.fields).to.be.an('array').to.deep.equal(['personId']); 39 | expect(parsedErr.value).to.be.undefined; 40 | expect(parsedErr.index).to.equal('brothers_ibfk_1'); 41 | }); 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /test/unit/errors.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const errors = require('../../lib/errors'); 4 | const expect = require('chai').expect; 5 | 6 | describe('errors', () => { 7 | it('should maintain stack trace with message', () => { 8 | const errorsWithMessage = [ 9 | 'BaseError', 'ValidationError', 'UnknownConstraintError', 'InstanceError', 10 | 'EmptyResultError', 'EagerLoadingError', 'AssociationError', 'QueryError' 11 | ]; 12 | 13 | errorsWithMessage.forEach(errorName => { 14 | function throwError() { 15 | throw new errors[errorName]('this is a message'); 16 | } 17 | let err; 18 | try { 19 | throwError(); 20 | } catch (error) { 21 | err = error; 22 | } 23 | expect(err).to.exist; 24 | const stackParts = err.stack.split('\n'); 25 | const fullErrorName = 'Sequelize' + errorName; 26 | expect(stackParts[0]).to.equal(fullErrorName + ': this is a message'); 27 | expect(stackParts[1]).to.match(/^ at throwError \(.*errors.test.js:\d+:\d+\)$/); 28 | }); 29 | }); 30 | 31 | it('should maintain stack trace without message', () => { 32 | const errorsWithoutMessage = [ 33 | 'ConnectionError', 'ConnectionRefusedError', 'ConnectionTimedOutError', 34 | 'AccessDeniedError', 'HostNotFoundError', 'HostNotReachableError', 'InvalidConnectionError' 35 | ]; 36 | 37 | errorsWithoutMessage.forEach(errorName => { 38 | function throwError() { 39 | throw new errors[errorName](null); 40 | } 41 | let err; 42 | try { 43 | throwError(); 44 | } catch (error) { 45 | err = error; 46 | } 47 | expect(err).to.exist; 48 | const stackParts = err.stack.split('\n'); 49 | 50 | const fullErrorName = 'Sequelize' + errorName; 51 | expect(stackParts[0]).to.equal(fullErrorName); 52 | expect(stackParts[1]).to.match(/^ at throwError \(.*errors.test.js:\d+:\d+\)$/); 53 | }); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /test/unit/increment.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | expect = chai.expect, 5 | Support = require(__dirname + '/../support'), 6 | current = Support.sequelize, 7 | Sequelize = Support.Sequelize; 8 | 9 | describe(Support.getTestDialectTeaser('Model'), () => { 10 | describe('increment', () => { 11 | describe('options tests', () => { 12 | const Model = current.define('User', { 13 | id: { 14 | type: Sequelize.BIGINT, 15 | primaryKey: true, 16 | autoIncrement: true 17 | }, 18 | count: Sequelize.BIGINT 19 | }); 20 | 21 | it('should reject if options are missing', () => { 22 | return expect(() => Model.increment(['id', 'count'])) 23 | .to.throw('Missing where attribute in the options parameter'); 24 | }); 25 | 26 | it('should reject if options.where are missing', () => { 27 | return expect(() => Model.increment(['id', 'count'], { by: 10})) 28 | .to.throw('Missing where attribute in the options parameter'); 29 | }); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /test/unit/instance/build.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | expect = chai.expect, 5 | Support = require(__dirname + '/../support'), 6 | DataTypes = require(__dirname + '/../../../lib/data-types'), 7 | current = Support.sequelize; 8 | 9 | describe(Support.getTestDialectTeaser('Instance'), () => { 10 | describe('build', () => { 11 | it('should populate NOW default values', () => { 12 | const Model = current.define('Model', { 13 | created_time: { 14 | type: DataTypes.DATE, 15 | allowNull: true, 16 | defaultValue: DataTypes.NOW 17 | }, 18 | updated_time: { 19 | type: DataTypes.DATE, 20 | allowNull: true, 21 | defaultValue: DataTypes.NOW 22 | }, 23 | ip: { 24 | type: DataTypes.STRING, 25 | validate: { 26 | isIP: true 27 | } 28 | }, 29 | ip2: { 30 | type: DataTypes.STRING, 31 | validate: { 32 | isIP: { 33 | msg: 'test' 34 | } 35 | } 36 | } 37 | }, { 38 | timestamp: false 39 | }), 40 | instance = Model.build({ip: '127.0.0.1', ip2: '0.0.0.0'}); 41 | 42 | expect(instance.get('created_time')).to.be.ok; 43 | expect(instance.get('created_time')).to.be.an.instanceof(Date); 44 | 45 | expect(instance.get('updated_time')).to.be.ok; 46 | expect(instance.get('updated_time')).to.be.an.instanceof(Date); 47 | 48 | return instance.validate(); 49 | }); 50 | 51 | it('should populate explicitly undefined UUID primary keys', () => { 52 | const Model = current.define('Model', { 53 | id: { 54 | type: DataTypes.UUID, 55 | primaryKey: true, 56 | allowNull: false, 57 | defaultValue: DataTypes.UUIDV4 58 | } 59 | }), 60 | instance = Model.build({ 61 | id: undefined 62 | }); 63 | 64 | expect(instance.get('id')).not.to.be.undefined; 65 | expect(instance.get('id')).to.be.ok; 66 | }); 67 | 68 | it('should populate undefined columns with default value', () => { 69 | const Model = current.define('Model', { 70 | number1: { 71 | type: DataTypes.INTEGER, 72 | defaultValue: 1 73 | }, 74 | number2: { 75 | type: DataTypes.INTEGER, 76 | defaultValue: 2 77 | } 78 | }), 79 | instance = Model.build({ 80 | number1: undefined 81 | }); 82 | 83 | expect(instance.get('number1')).not.to.be.undefined; 84 | expect(instance.get('number1')).to.equal(1); 85 | expect(instance.get('number2')).not.to.be.undefined; 86 | expect(instance.get('number2')).to.equal(2); 87 | }); 88 | 89 | it('should clone the default values', () => { 90 | const Model = current.define('Model', { 91 | data: { 92 | type: DataTypes.JSONB, 93 | defaultValue: { foo: 'bar' } 94 | } 95 | }), 96 | instance = Model.build(); 97 | instance.data.foo = 'biz'; 98 | 99 | expect(instance.get('data')).to.eql({ foo: 'biz' }); 100 | expect(Model.build().get('data')).to.eql({ foo: 'bar' }); 101 | }); 102 | }); 103 | }); 104 | -------------------------------------------------------------------------------- /test/unit/instance/decrement.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const 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'), () => { 11 | describe('decrement', () => { 12 | describe('options tests', () => { 13 | let stub, instance; 14 | const Model = current.define('User', { 15 | id: { 16 | type: Sequelize.BIGINT, 17 | primaryKey: true, 18 | autoIncrement: true 19 | } 20 | }); 21 | 22 | before(() => { 23 | stub = sinon.stub(current, 'query').returns( 24 | Sequelize.Promise.resolve({ 25 | _previousDataValues: {id: 3}, 26 | dataValues: {id: 1} 27 | }) 28 | ); 29 | }); 30 | 31 | after(() => { 32 | stub.restore(); 33 | }); 34 | 35 | it('should allow decrements even if options are not given', () => { 36 | instance = Model.build({id: 3}, {isNewRecord: false}); 37 | expect(() => { 38 | instance.decrement(['id']); 39 | }).to.not.throw(); 40 | }); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/unit/instance/destroy.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const 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'), () => { 11 | describe('destroy', () => { 12 | describe('options tests', () => { 13 | let stub, instance; 14 | const Model = current.define('User', { 15 | id: { 16 | type: Sequelize.BIGINT, 17 | primaryKey: true, 18 | autoIncrement: true 19 | } 20 | }); 21 | 22 | before(() => { 23 | stub = sinon.stub(current, 'query').returns( 24 | Sequelize.Promise.resolve({ 25 | _previousDataValues: {}, 26 | dataValues: {id: 1} 27 | }) 28 | ); 29 | }); 30 | 31 | after(() => { 32 | stub.restore(); 33 | }); 34 | 35 | it('should allow destroies even if options are not given', () => { 36 | instance = Model.build({id: 1}, {isNewRecord: false}); 37 | expect(() => { 38 | instance.destroy(); 39 | }).to.not.throw(); 40 | }); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/unit/instance/get.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | sinon = require('sinon'), 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'), () => { 11 | describe('get', () => { 12 | beforeEach(function() { 13 | this.getSpy = sinon.spy(); 14 | this.User = current.define('User', { 15 | name: { 16 | type: DataTypes.STRING, 17 | get: this.getSpy 18 | } 19 | }); 20 | }); 21 | 22 | it('invokes getter if raw: false', function() { 23 | this.User.build().get('name'); 24 | 25 | expect(this.getSpy).to.have.been.called; 26 | }); 27 | 28 | it('does not invoke getter if raw: true', function() { 29 | expect(this.getSpy, { raw: true }).not.to.have.been.called; 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /test/unit/instance/increment.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const 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'), () => { 11 | describe('increment', () => { 12 | describe('options tests', () => { 13 | let stub, instance; 14 | const Model = current.define('User', { 15 | id: { 16 | type: Sequelize.BIGINT, 17 | primaryKey: true, 18 | autoIncrement: true 19 | } 20 | }); 21 | 22 | before(() => { 23 | stub = sinon.stub(current, 'query').returns( 24 | Sequelize.Promise.resolve({ 25 | _previousDataValues: {id: 1}, 26 | dataValues: {id: 3} 27 | }) 28 | ); 29 | }); 30 | 31 | after(() => { 32 | stub.restore(); 33 | }); 34 | 35 | it('should allow increments even if options are not given', () => { 36 | instance = Model.build({id: 1}, {isNewRecord: false}); 37 | expect(() => { 38 | instance.increment(['id']); 39 | }).to.not.throw(); 40 | }); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/unit/instance/is-soft-deleted.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | expect = chai.expect, 5 | Support = require(__dirname + '/../support'), 6 | current = Support.sequelize, 7 | DataTypes = require(__dirname + '/../../../lib/data-types'), 8 | Sequelize = Support.Sequelize, 9 | moment = require('moment'); 10 | 11 | describe(Support.getTestDialectTeaser('Instance'), () => { 12 | describe('isSoftDeleted', () => { 13 | beforeEach(function() { 14 | const User = current.define('User', { 15 | name: DataTypes.STRING, 16 | birthdate: DataTypes.DATE, 17 | meta: DataTypes.JSON, 18 | deletedAt: { 19 | type: Sequelize.DATE 20 | } 21 | }); 22 | 23 | const ParanoidUser = current.define('User', { 24 | name: DataTypes.STRING, 25 | birthdate: DataTypes.DATE, 26 | meta: DataTypes.JSON, 27 | deletedAt: { 28 | type: Sequelize.DATE 29 | } 30 | }, { 31 | paranoid: true 32 | }); 33 | 34 | this.paranoidUser = ParanoidUser.build({ 35 | name: 'a' 36 | }, { 37 | isNewRecord: false, 38 | raw: true 39 | }); 40 | 41 | this.user = User.build({ 42 | name: 'a' 43 | }, { 44 | isNewRecord: false, 45 | raw: true 46 | }); 47 | }); 48 | 49 | it('should not throw if paranoid is set to true', function() { 50 | expect(() => { 51 | this.paranoidUser.isSoftDeleted(); 52 | }).to.not.throw(); 53 | }); 54 | 55 | it('should throw if paranoid is set to false', function() { 56 | expect(() => { 57 | this.user.isSoftDeleted(); 58 | }).to.throw('Model is not paranoid'); 59 | }); 60 | 61 | it('should return false if the soft-delete property is the same as ' + 62 | 'the default value', function() { 63 | this.paranoidUser.setDataValue('deletedAt', null); 64 | expect(this.paranoidUser.isSoftDeleted()).to.be.false; 65 | }); 66 | 67 | it('should return false if the soft-delete property is set to a date in ' + 68 | 'the future', function() { 69 | this.paranoidUser.setDataValue('deletedAt', moment().add(5, 'days').format()); 70 | expect(this.paranoidUser.isSoftDeleted()).to.be.false; 71 | }); 72 | 73 | it('should return true if the soft-delete property is set to a date ' + 74 | 'before now', function() { 75 | this.paranoidUser.setDataValue('deletedAt', moment().subtract(5, 'days').format()); 76 | expect(this.paranoidUser.isSoftDeleted()).to.be.true; 77 | }); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /test/unit/instance/previous.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'); 4 | const expect = chai.expect; 5 | const Support = require(__dirname + '/../support'); 6 | const DataTypes = require(__dirname + '/../../../lib/data-types'); 7 | const current = Support.sequelize; 8 | 9 | describe(Support.getTestDialectTeaser('Instance'), () => { 10 | describe('previous', () => { 11 | it('should return correct previous value', () => { 12 | const Model = current.define('Model', { 13 | text: DataTypes.STRING, 14 | textCustom: { 15 | type: DataTypes.STRING, 16 | set(val) { 17 | this.setDataValue('textCustom', val); 18 | }, 19 | get() { 20 | this.getDataValue('textCustom'); 21 | } 22 | } 23 | }); 24 | 25 | const instance = Model.build({ text: 'a', textCustom: 'abc' }); 26 | expect(instance.previous('text')).to.be.not.ok; 27 | expect(instance.previous('textCustom')).to.be.not.ok; 28 | 29 | instance.set('text', 'b'); 30 | instance.set('textCustom', 'def'); 31 | 32 | expect(instance.previous('text')).to.be.equal('a'); 33 | expect(instance.previous('textCustom')).to.be.equal('abc'); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/unit/instance/reload.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const 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'), () => { 11 | describe('reload', () => { 12 | describe('options tests', () => { 13 | let stub, instance; 14 | const 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 | 27 | before(() => { 28 | stub = sinon.stub(current, 'query').returns( 29 | Sequelize.Promise.resolve({ 30 | _previousDataValues: {id: 1}, 31 | dataValues: {id: 2} 32 | }) 33 | ); 34 | }); 35 | 36 | after(() => { 37 | stub.restore(); 38 | }); 39 | 40 | it('should allow reloads even if options are not given', () => { 41 | instance = Model.build({id: 1}, {isNewRecord: false}); 42 | expect(() => { 43 | instance.reload(); 44 | }).to.not.throw(); 45 | }); 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test/unit/instance/restore.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const 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'), () => { 11 | describe('restore', () => { 12 | describe('options tests', () => { 13 | let stub, instance; 14 | const 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 | 27 | before(() => { 28 | stub = sinon.stub(current, 'query').returns( 29 | Sequelize.Promise.resolve([{ 30 | _previousDataValues: {id: 1}, 31 | dataValues: {id: 2} 32 | }, 1]) 33 | ); 34 | }); 35 | 36 | after(() => { 37 | stub.restore(); 38 | }); 39 | 40 | it('should allow restores even if options are not given', () => { 41 | instance = Model.build({id: 1}, {isNewRecord: false}); 42 | expect(() => { 43 | instance.restore(); 44 | }).to.not.throw(); 45 | }); 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test/unit/instance/save.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const 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'), () => { 11 | describe('save', () => { 12 | it('should disallow saves if no primary key values is present', () => { 13 | const Model = current.define('User', { 14 | 15 | }), 16 | instance = Model.build({}, {isNewRecord: false}); 17 | 18 | expect(() => { 19 | instance.save(); 20 | }).to.throw(); 21 | }); 22 | 23 | describe('options tests', () => { 24 | let stub, instance; 25 | const Model = current.define('User', { 26 | id: { 27 | type: Sequelize.BIGINT, 28 | primaryKey: true, 29 | autoIncrement: true 30 | } 31 | }); 32 | 33 | before(() => { 34 | stub = sinon.stub(current, 'query').returns( 35 | Sequelize.Promise.resolve([{ 36 | _previousDataValues: {}, 37 | dataValues: {id: 1} 38 | }, 1]) 39 | ); 40 | }); 41 | 42 | after(() => { 43 | stub.restore(); 44 | }); 45 | 46 | it('should allow saves even if options are not given', () => { 47 | instance = Model.build({}); 48 | expect(() => { 49 | instance.save(); 50 | }).to.not.throw(); 51 | }); 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /test/unit/instance/to-json.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | expect = chai.expect, 5 | Support = require(__dirname + '/../support'), 6 | DataTypes = require(__dirname + '/../../../lib/data-types'), 7 | current = Support.sequelize; 8 | 9 | describe(Support.getTestDialectTeaser('Instance'), () => { 10 | describe('toJSON', () => { 11 | it('returns copy of json', () => { 12 | const User = current.define('User', { 13 | name: DataTypes.STRING 14 | }); 15 | const user = User.build({ name: 'my-name' }); 16 | const json1 = user.toJSON(); 17 | expect(json1).to.have.property('name').and.be.equal('my-name'); 18 | 19 | // remove value from json and ensure it's not changed in the instance 20 | delete json1.name; 21 | 22 | const json2 = user.toJSON(); 23 | expect(json2).to.have.property('name').and.be.equal('my-name'); 24 | }); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /test/unit/model/bulkcreate.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | expect = chai.expect, 5 | sinon = require('sinon'), 6 | Support = require(__dirname + '/../support'), 7 | DataTypes = require('../../../lib/data-types'), 8 | current = Support.sequelize, 9 | Promise = current.Promise; 10 | 11 | describe(Support.getTestDialectTeaser('Model'), () => { 12 | describe('bulkCreate', () => { 13 | before(function() { 14 | this.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 | this.stub = sinon.stub(current.getQueryInterface(), 'bulkInsert').returns(Promise.resolve([])); 23 | }); 24 | 25 | afterEach(function() { 26 | this.stub.reset(); 27 | }); 28 | 29 | after(function() { 30 | this.stub.restore(); 31 | }); 32 | 33 | describe('validations', () => { 34 | it('should not fail for renamed fields', function() { 35 | return this.Model.bulkCreate([ 36 | { accountId: 42 } 37 | ], { validate: true }).then(() => { 38 | expect(this.stub.getCall(0).args[1]).to.deep.equal([ 39 | { account_id: 42, id: null } 40 | ]); 41 | }); 42 | }); 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /test/unit/model/count.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | expect = chai.expect, 5 | Support = require(__dirname + '/../support'), 6 | current = Support.sequelize, 7 | sinon = require('sinon'), 8 | DataTypes = require(__dirname + '/../../../lib/data-types'), 9 | Promise = require('bluebird'); 10 | 11 | describe(Support.getTestDialectTeaser('Model'), () => { 12 | describe('method count', () => { 13 | before(() => { 14 | this.oldFindAll = current.Model.findAll; 15 | this.oldAggregate = current.Model.aggregate; 16 | 17 | current.Model.findAll = sinon.stub().returns(Promise.resolve()); 18 | 19 | this.User = current.define('User', { 20 | username: DataTypes.STRING, 21 | age: DataTypes.INTEGER 22 | }); 23 | this.Project = current.define('Project', { 24 | name: DataTypes.STRING 25 | }); 26 | 27 | this.User.hasMany(this.Project); 28 | this.Project.belongsTo(this.User); 29 | }); 30 | 31 | after(() => { 32 | current.Model.findAll = this.oldFindAll; 33 | current.Model.aggregate = this.oldAggregate; 34 | }); 35 | 36 | beforeEach(() => { 37 | this.stub = current.Model.aggregate = sinon.stub().returns(Promise.resolve()); 38 | }); 39 | 40 | describe('should pass the same options to model.aggregate as findAndCount', () => { 41 | it('with includes', () => { 42 | const queryObject = { 43 | include: [this.Project] 44 | }; 45 | return this.User.count(queryObject) 46 | .then(() => this.User.findAndCount(queryObject)) 47 | .then(() => { 48 | const count = this.stub.getCall(0).args; 49 | const findAndCount = this.stub.getCall(1).args; 50 | expect(count).to.eql(findAndCount); 51 | }); 52 | }); 53 | 54 | it('attributes should be stripped in case of findAndCount', () => { 55 | const queryObject = { 56 | attributes: ['username'] 57 | }; 58 | return this.User.count(queryObject) 59 | .then(() => this.User.findAndCount(queryObject)) 60 | .then(() => { 61 | const count = this.stub.getCall(0).args; 62 | const findAndCount = this.stub.getCall(1).args; 63 | expect(count).not.to.eql(findAndCount); 64 | count[2].attributes = undefined; 65 | expect(count).to.eql(findAndCount); 66 | }); 67 | }); 68 | }); 69 | 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /test/unit/model/define.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | expect = chai.expect, 5 | Support = require(__dirname + '/../support'), 6 | DataTypes = require('../../../lib/data-types'), 7 | current = Support.sequelize; 8 | 9 | describe(Support.getTestDialectTeaser('Model'), () => { 10 | describe('define', () => { 11 | it('should allow custom timestamps with underscored: true', () => { 12 | const Model = current.define('User', {}, { 13 | createdAt: 'createdAt', 14 | updatedAt: 'updatedAt', 15 | timestamps: true, 16 | underscored: true 17 | }); 18 | 19 | expect(Model.rawAttributes).to.haveOwnProperty('createdAt'); 20 | expect(Model.rawAttributes).to.haveOwnProperty('updatedAt'); 21 | 22 | expect(Model._timestampAttributes.createdAt).to.equal('createdAt'); 23 | expect(Model._timestampAttributes.updatedAt).to.equal('updatedAt'); 24 | 25 | expect(Model.rawAttributes).not.to.have.property('created_at'); 26 | expect(Model.rawAttributes).not.to.have.property('updated_at'); 27 | }); 28 | 29 | it('should throw when id is added but not marked as PK', () => { 30 | expect(() => { 31 | current.define('foo', { 32 | id: DataTypes.INTEGER 33 | }); 34 | }).to.throw("A column called 'id' was added to the attributes of 'foos' but not marked with 'primaryKey: true'"); 35 | 36 | expect(() => { 37 | current.define('bar', { 38 | id: { 39 | type: DataTypes.INTEGER 40 | } 41 | }); 42 | }).to.throw("A column called 'id' was added to the attributes of 'bars' but not marked with 'primaryKey: true'"); 43 | }); 44 | it('should defend against null or undefined "unique" attributes', () => { 45 | expect(() => { 46 | current.define('baz', { 47 | foo: { 48 | type: DataTypes.STRING, 49 | unique: null 50 | }, 51 | bar: { 52 | type: DataTypes.STRING, 53 | unique: undefined 54 | }, 55 | bop: { 56 | type: DataTypes.DATE 57 | } 58 | }); 59 | }).not.to.throw(); 60 | }); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /test/unit/model/destroy.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | expect = chai.expect, 5 | Support = require(__dirname + '/../support'), 6 | current = Support.sequelize, 7 | sinon = require('sinon'), 8 | Promise = current.Promise, 9 | DataTypes = require('../../../lib/data-types'), 10 | _ = require('lodash'); 11 | 12 | describe(Support.getTestDialectTeaser('Model'), () => { 13 | 14 | describe('method destroy', () => { 15 | const User = current.define('User', { 16 | name: DataTypes.STRING, 17 | secretValue: DataTypes.INTEGER 18 | }); 19 | 20 | before(function() { 21 | this.stubDelete = sinon.stub(current.getQueryInterface(), 'bulkDelete').callsFake(() => { 22 | return Promise.resolve([]); 23 | }); 24 | }); 25 | 26 | beforeEach(function() { 27 | this.deloptions = {where: {secretValue: '1'}}; 28 | this.cloneOptions = _.clone(this.deloptions); 29 | this.stubDelete.reset(); 30 | }); 31 | 32 | afterEach(function() { 33 | delete this.deloptions; 34 | delete this.cloneOptions; 35 | }); 36 | 37 | after(function() { 38 | this.stubDelete.restore(); 39 | }); 40 | 41 | it('can detect complexe objects', () => { 42 | const Where = function() { this.secretValue = '1'; }; 43 | 44 | expect(() => { 45 | User.destroy({where: new Where()}); 46 | }).to.throw(); 47 | 48 | }); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /test/unit/model/find-create-find.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | expect = chai.expect, 5 | Support = require(__dirname + '/../support'), 6 | UniqueConstraintError = require(__dirname + '/../../../lib/errors').UniqueConstraintError, 7 | current = Support.sequelize, 8 | sinon = require('sinon'), 9 | Promise = require('bluebird'); 10 | 11 | describe(Support.getTestDialectTeaser('Model'), () => { 12 | describe('findCreateFind', () => { 13 | const Model = current.define('Model', {}); 14 | 15 | beforeEach(function() { 16 | this.sinon = sinon.sandbox.create(); 17 | }); 18 | 19 | afterEach(function() { 20 | this.sinon.restore(); 21 | }); 22 | 23 | it('should return the result of the first find call if not empty', function() { 24 | const result = {}, 25 | where = {prop: Math.random().toString()}, 26 | findSpy = this.sinon.stub(Model, 'findOne').returns(Promise.resolve(result)); 27 | 28 | return expect(Model.findCreateFind({ 29 | where 30 | })).to.eventually.eql([result, false]).then(() => { 31 | expect(findSpy).to.have.been.calledOnce; 32 | expect(findSpy.getCall(0).args[0].where).to.equal(where); 33 | }); 34 | }); 35 | 36 | it('should create if first find call is empty', function() { 37 | const result = {}, 38 | where = {prop: Math.random().toString()}, 39 | createSpy = this.sinon.stub(Model, 'create').returns(Promise.resolve(result)); 40 | 41 | this.sinon.stub(Model, 'findOne').returns(Promise.resolve(null)); 42 | 43 | return expect(Model.findCreateFind({ 44 | where 45 | })).to.eventually.eql([result, true]).then(() => { 46 | expect(createSpy).to.have.been.calledWith(where); 47 | }); 48 | }); 49 | 50 | it('should do a second find if create failed do to unique constraint', function() { 51 | const result = {}, 52 | where = {prop: Math.random().toString()}, 53 | findSpy = this.sinon.stub(Model, 'findOne'); 54 | 55 | this.sinon.stub(Model, 'create').callsFake(() => { 56 | return Promise.reject(new UniqueConstraintError()); 57 | }); 58 | 59 | findSpy.onFirstCall().returns(Promise.resolve(null)); 60 | findSpy.onSecondCall().returns(Promise.resolve(result)); 61 | 62 | return expect(Model.findCreateFind({ 63 | where 64 | })).to.eventually.eql([result, false]).then(() => { 65 | expect(findSpy).to.have.been.calledTwice; 66 | expect(findSpy.getCall(1).args[0].where).to.equal(where); 67 | }); 68 | }); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /test/unit/model/find-or-create.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | expect = chai.expect, 5 | Support = require(__dirname + '/../support'), 6 | current = Support.sequelize, 7 | cls = require('continuation-local-storage'), 8 | sinon = require('sinon'), 9 | stub = sinon.stub, 10 | Promise = require('bluebird'); 11 | 12 | describe(Support.getTestDialectTeaser('Model'), () => { 13 | 14 | describe('method findOrCreate', () => { 15 | 16 | before(() => { 17 | current.constructor.useCLS(cls.createNamespace('sequelize')); 18 | }); 19 | 20 | after(() => { 21 | delete current.constructor._cls; 22 | }); 23 | 24 | beforeEach(function() { 25 | this.User = current.define('User', {}, { 26 | name: 'John' 27 | }); 28 | 29 | this.transactionStub = stub(this.User.sequelize, 'transaction'); 30 | this.transactionStub.returns(new Promise(() => {})); 31 | 32 | this.clsStub = stub(current.constructor._cls, 'get'); 33 | this.clsStub.returns({ id: 123 }); 34 | }); 35 | 36 | afterEach(function() { 37 | this.transactionStub.restore(); 38 | this.clsStub.restore(); 39 | }); 40 | 41 | it('should use transaction from cls if available', function() { 42 | 43 | const options = { 44 | where: { 45 | name: 'John' 46 | } 47 | }; 48 | 49 | this.User.findOrCreate(options); 50 | 51 | expect(this.clsStub.calledOnce).to.equal(true, 'expected to ask for transaction'); 52 | }); 53 | 54 | it('should not use transaction from cls if provided as argument', function() { 55 | 56 | const options = { 57 | where: { 58 | name: 'John' 59 | }, 60 | transaction: { id: 123 } 61 | }; 62 | 63 | this.User.findOrCreate(options); 64 | 65 | expect(this.clsStub.called).to.equal(false); 66 | }); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /test/unit/model/findone.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | expect = chai.expect, 5 | Support = require(__dirname + '/../support'), 6 | current = Support.sequelize, 7 | sinon = require('sinon'), 8 | DataTypes = require(__dirname + '/../../../lib/data-types'), 9 | Promise = require('bluebird'); 10 | 11 | describe(Support.getTestDialectTeaser('Model'), () => { 12 | describe('method findOne', () => { 13 | before(function() { 14 | this.oldFindAll = current.Model.findAll; 15 | }); 16 | after(function() { 17 | current.Model.findAll = this.oldFindAll; 18 | }); 19 | 20 | beforeEach(function() { 21 | this.stub = current.Model.findAll = sinon.stub().returns(Promise.resolve()); 22 | }); 23 | 24 | describe('should not add limit when querying on a primary key', () => { 25 | it('with id primary key', function() { 26 | const Model = current.define('model'); 27 | 28 | return Model.findOne({ where: { id: 42 }}).bind(this).then(function() { 29 | expect(this.stub.getCall(0).args[0]).to.be.an('object').not.to.have.property('limit'); 30 | }); 31 | }); 32 | 33 | it('with custom primary key', function() { 34 | const Model = current.define('model', { 35 | uid: { 36 | type: DataTypes.INTEGER, 37 | primaryKey: true, 38 | autoIncrement: true 39 | } 40 | }); 41 | 42 | return Model.findOne({ where: { uid: 42 }}).bind(this).then(function() { 43 | expect(this.stub.getCall(0).args[0]).to.be.an('object').not.to.have.property('limit'); 44 | }); 45 | }); 46 | 47 | it('with blob primary key', function() { 48 | const Model = current.define('model', { 49 | id: { 50 | type: DataTypes.BLOB, 51 | primaryKey: true, 52 | autoIncrement: true 53 | } 54 | }); 55 | 56 | return Model.findOne({ where: { id: new Buffer('foo') }}).bind(this).then(function() { 57 | expect(this.stub.getCall(0).args[0]).to.be.an('object').not.to.have.property('limit'); 58 | }); 59 | }); 60 | }); 61 | 62 | it('should add limit when using { $ gt on the primary key', function() { 63 | const Model = current.define('model'); 64 | 65 | return Model.findOne({ where: { id: { $gt: 42 }}}).bind(this).then(function() { 66 | expect(this.stub.getCall(0).args[0]).to.be.an('object').to.have.property('limit'); 67 | }); 68 | }); 69 | 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /test/unit/model/helpers.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'); 4 | const expect = chai.expect; 5 | const Support = require(__dirname + '/../support'); 6 | const current = Support.sequelize; 7 | 8 | describe(Support.getTestDialectTeaser('Model'), () => { 9 | describe('hasAlias', () => { 10 | beforeEach(function() { 11 | this.User = current.define('user'); 12 | this.Task = current.define('task'); 13 | }); 14 | 15 | it('returns true if a model has an association with the specified alias', function() { 16 | this.Task.belongsTo(this.User, { as: 'owner'}); 17 | expect(this.Task.hasAlias('owner')).to.equal(true); 18 | }); 19 | 20 | it('returns false if a model does not have an association with the specified alias', function() { 21 | this.Task.belongsTo(this.User, { as: 'owner'}); 22 | expect(this.Task.hasAlias('notOwner')).to.equal(false); 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /test/unit/model/indexes.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | expect = chai.expect, 5 | Support = require(__dirname + '/../support'), 6 | current = Support.sequelize, 7 | DataTypes = require(__dirname + '/../../../lib/data-types'); 8 | 9 | describe(Support.getTestDialectTeaser('Model'), () => { 10 | describe('indexes', () => { 11 | it('should automatically set a gin index for JSONB indexes', () => { 12 | const Model = current.define('event', { 13 | eventData: { 14 | type: DataTypes.JSONB, 15 | index: true, 16 | field: 'data' 17 | } 18 | }); 19 | 20 | expect(Model.rawAttributes.eventData.index).not.to.equal(true); 21 | expect(Model.options.indexes.length).to.equal(1); 22 | expect(Model.options.indexes[0].fields).to.eql(['data']); 23 | expect(Model.options.indexes[0].using).to.equal('gin'); 24 | }); 25 | 26 | it('should set the unique property when type is unique', () => { 27 | const Model = current.define('m', {}, { 28 | indexes: [ 29 | { 30 | type: 'unique' 31 | }, 32 | { 33 | type: 'UNIQUE' 34 | } 35 | ] 36 | }); 37 | 38 | expect(Model.options.indexes[0].unique).to.eql(true); 39 | expect(Model.options.indexes[1].unique).to.eql(true); 40 | }); 41 | 42 | it('should not set rawAttributes when indexes are defined via options', () => { 43 | const User = current.define('User', { 44 | username: DataTypes.STRING 45 | }, { 46 | indexes: [{ 47 | unique: true, 48 | fields: ['username'] 49 | }] 50 | }); 51 | 52 | expect(User.rawAttributes.username.unique).to.be.undefined; 53 | }); 54 | 55 | it('should not set rawAttributes when composite unique indexes are defined via options', () => { 56 | const User = current.define('User', { 57 | name: DataTypes.STRING, 58 | address: DataTypes.STRING 59 | }, { 60 | indexes: [{ 61 | unique: 'users_name_address', 62 | fields: ['name', 'address'] 63 | }] 64 | }); 65 | 66 | expect(User.rawAttributes.name.unique).to.be.undefined; 67 | expect(User.rawAttributes.address.unique).to.be.undefined; 68 | }); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /test/unit/model/overwriting-builtins.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | expect = chai.expect, 5 | Support = require(__dirname + '/../../support'), 6 | DataTypes = require(__dirname + '/../../../lib/data-types'); 7 | 8 | describe(Support.getTestDialectTeaser('Model'), () => { 9 | 10 | describe('not breaking built-ins', () => { 11 | it('it should not break instance.set by defining a model set attribute', function() { 12 | const User = this.sequelize.define('OverWrittenKeys', { 13 | set: DataTypes.STRING 14 | }); 15 | 16 | const user = User.build({set: 'A'}); 17 | expect(user.get('set')).to.equal('A'); 18 | user.set('set', 'B'); 19 | expect(user.get('set')).to.equal('B'); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/unit/model/removeAttribute.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | expect = chai.expect, 5 | Support = require(__dirname + '/../support'), 6 | current = Support.sequelize, 7 | _ = require('lodash'), 8 | DataTypes = require(__dirname + '/../../../lib/data-types'); 9 | 10 | describe(Support.getTestDialectTeaser('Model'), () => { 11 | describe('removeAttribute', () => { 12 | it('should support removing the primary key', () => { 13 | const Model = current.define('m', { 14 | name: DataTypes.STRING 15 | }); 16 | 17 | expect(Model.primaryKeyAttribute).not.to.be.undefined; 18 | expect(_.size(Model.primaryKeys)).to.equal(1); 19 | 20 | Model.removeAttribute('id'); 21 | 22 | expect(Model.primaryKeyAttribute).to.be.undefined; 23 | expect(_.size(Model.primaryKeys)).to.equal(0); 24 | }); 25 | 26 | it('should not add undefined attribute after removing primary key', () => { 27 | const Model = current.define('m', { 28 | name: DataTypes.STRING 29 | }); 30 | 31 | Model.removeAttribute('id'); 32 | 33 | const instance = Model.build(); 34 | expect(instance.dataValues).not.to.include.keys('undefined'); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/unit/model/schema.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | expect = chai.expect, 5 | Support = require(__dirname + '/../support'), 6 | current = Support.sequelize; 7 | 8 | describe(Support.getTestDialectTeaser('Model') + 'Schemas', () => { 9 | if (current.dialect.supports.schemas) { 10 | const Project = current.define('project'), 11 | Company = current.define('company', {}, { 12 | schema: 'default', 13 | schemaDelimiter: '&' 14 | }); 15 | 16 | describe('schema', () => { 17 | it('should work with no default schema', () => { 18 | expect(Project._schema).to.be.null; 19 | }); 20 | 21 | it('should apply default schema from define', () => { 22 | expect(Company._schema).to.equal('default'); 23 | }); 24 | 25 | it('should be able to override the default schema', () => { 26 | expect(Company.schema('newSchema')._schema).to.equal('newSchema'); 27 | }); 28 | 29 | it('should be able nullify schema', () => { 30 | expect(Company.schema(null)._schema).to.be.null; 31 | }); 32 | 33 | it('should support multiple, coexistent schema models', () => { 34 | const schema1 = Company.schema('schema1'), 35 | schema2 = Company.schema('schema1'); 36 | 37 | expect(schema1._schema).to.equal('schema1'); 38 | expect(schema2._schema).to.equal('schema1'); 39 | }); 40 | }); 41 | 42 | describe('schema delimiter', () => { 43 | it('should work with no default schema delimiter', () => { 44 | expect(Project._schemaDelimiter).to.equal(''); 45 | }); 46 | 47 | it('should apply default schema delimiter from define', () => { 48 | expect(Company._schemaDelimiter).to.equal('&'); 49 | }); 50 | 51 | it('should be able to override the default schema delimiter', () => { 52 | expect(Company.schema(Company._schema, '^')._schemaDelimiter).to.equal('^'); 53 | }); 54 | 55 | it('should support multiple, coexistent schema delimiter models', () => { 56 | const schema1 = Company.schema(Company._schema, '$'), 57 | schema2 = Company.schema(Company._schema, '#'); 58 | 59 | expect(schema1._schemaDelimiter).to.equal('$'); 60 | expect(schema2._schemaDelimiter).to.equal('#'); 61 | }); 62 | }); 63 | } 64 | }); 65 | -------------------------------------------------------------------------------- /test/unit/model/update.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | expect = chai.expect, 5 | Support = require(__dirname + '/../support'), 6 | current = Support.sequelize, 7 | sinon = require('sinon'), 8 | Promise = current.Promise, 9 | DataTypes = require('../../../lib/data-types'), 10 | _ = require('lodash'); 11 | 12 | describe(Support.getTestDialectTeaser('Model'), () => { 13 | describe('method update', () => { 14 | before(function() { 15 | this.User = current.define('User', { 16 | name: DataTypes.STRING, 17 | secretValue: DataTypes.INTEGER 18 | }); 19 | }); 20 | 21 | beforeEach(function() { 22 | this.stubUpdate = sinon.stub(current.getQueryInterface(), 'bulkUpdate').returns(Promise.resolve([])); 23 | this.updates = { name: 'Batman', secretValue: '7' }; 24 | this.cloneUpdates = _.clone(this.updates); 25 | }); 26 | 27 | afterEach(function() { 28 | this.stubUpdate.restore(); 29 | }); 30 | 31 | afterEach(function() { 32 | delete this.updates; 33 | delete this.cloneUpdates; 34 | }); 35 | 36 | describe('properly clones input values', () => { 37 | it('with default options', function() { 38 | return this.User.update(this.updates, { where: { secretValue: '1' } }).then(() => { 39 | expect(this.updates).to.be.deep.eql(this.cloneUpdates); 40 | }); 41 | }); 42 | 43 | it('when using fields option', function() { 44 | return this.User.update(this.updates, { where: { secretValue: '1' }, fields: ['name'] }).then(() => { 45 | expect(this.updates).to.be.deep.eql(this.cloneUpdates); 46 | }); 47 | }); 48 | }); 49 | 50 | it('can detect complexe objects', function() { 51 | const Where = function() { this.secretValue = '1'; }; 52 | 53 | expect(() => { 54 | this.User.update(this.updates, { where: new Where() }); 55 | }).to.throw(); 56 | }); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /test/unit/model/upsert.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | expect = chai.expect, 5 | Support = require(__dirname + '/../support'), 6 | current = Support.sequelize, 7 | sinon = require('sinon'), 8 | Promise = current.Promise, 9 | DataTypes = require('../../../lib/data-types'); 10 | 11 | describe(Support.getTestDialectTeaser('Model'), () => { 12 | 13 | if (current.dialect.supports.upserts) { 14 | describe('method upsert', () => { 15 | const self = this; 16 | const User = current.define('User', { 17 | name: DataTypes.STRING, 18 | virtualValue: { 19 | type: DataTypes.VIRTUAL, 20 | set(val) { 21 | return this.value = val; 22 | }, 23 | get() { 24 | return this.value; 25 | } 26 | }, 27 | value: DataTypes.STRING, 28 | secretValue: { 29 | type: DataTypes.INTEGER, 30 | allowNull: false 31 | }, 32 | createdAt: { 33 | type: DataTypes.DATE, 34 | field: 'created_at' 35 | } 36 | }); 37 | 38 | const UserNoTime = current.define('UserNoTime', { 39 | name: DataTypes.STRING 40 | }, { 41 | timestamps: false 42 | }); 43 | 44 | before(function() { 45 | this.query = current.query; 46 | current.query = sinon.stub().returns(Promise.resolve()); 47 | 48 | self.stub = sinon.stub(current.getQueryInterface(), 'upsert').callsFake(() => { 49 | return User.build({}); 50 | }); 51 | }); 52 | 53 | beforeEach(() => { 54 | self.stub.reset(); 55 | }); 56 | 57 | after(function() { 58 | current.query = this.query; 59 | self.stub.restore(); 60 | }); 61 | 62 | 63 | it('skip validations for missing fields', () => { 64 | return expect(User.upsert({ 65 | name: 'Grumpy Cat' 66 | })).not.to.be.rejectedWith(current.ValidationError); 67 | }); 68 | 69 | it('creates new record with correct field names', () => { 70 | return User 71 | .upsert({ 72 | name: 'Young Cat', 73 | virtualValue: 999 74 | }) 75 | .then(() => { 76 | expect(Object.keys(self.stub.getCall(0).args[1])).to.deep.equal([ 77 | 'name', 'value', 'created_at', 'updatedAt' 78 | ]); 79 | }); 80 | }); 81 | 82 | it('creates new record with timestamps disabled', () => { 83 | return UserNoTime 84 | .upsert({ 85 | name: 'Young Cat' 86 | }) 87 | .then(() => { 88 | expect(Object.keys(self.stub.getCall(0).args[1])).to.deep.equal([ 89 | 'name' 90 | ]); 91 | }); 92 | }); 93 | 94 | it('updates all changed fields by default', () => { 95 | return User 96 | .upsert({ 97 | name: 'Old Cat', 98 | virtualValue: 111 99 | }) 100 | .then(() => { 101 | expect(Object.keys(self.stub.getCall(0).args[2])).to.deep.equal([ 102 | 'name', 'value', 'updatedAt' 103 | ]); 104 | }); 105 | }); 106 | }); 107 | } 108 | }); 109 | -------------------------------------------------------------------------------- /test/unit/promise.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | expect = chai.expect, 5 | Support = require(__dirname + '/support'), 6 | Sequelize = Support.Sequelize, 7 | Promise = Sequelize.Promise, 8 | Bluebird = require('bluebird'); 9 | 10 | describe('Promise', () => { 11 | it('should be an independent copy of bluebird library', () => { 12 | expect(Promise.prototype.then).to.be.a('function'); 13 | expect(Promise).to.not.equal(Bluebird); 14 | expect(Promise.prototype).to.not.equal(Bluebird.prototype); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/unit/query.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'), 4 | sinon = require('sinon'), 5 | expect = chai.expect, 6 | Support = require(__dirname + '/support'), 7 | Sequelize = Support.Sequelize, 8 | Promise = Sequelize.Promise, 9 | current = Support.sequelize; 10 | 11 | describe('sequelize.query', () => { 12 | it('connection should be released only once when retry fails', () => { 13 | const getConnectionStub = sinon.stub(current.connectionManager, 'getConnection').callsFake(() => { 14 | return Promise.resolve({}); 15 | }); 16 | const releaseConnectionStub = sinon.stub(current.connectionManager, 'releaseConnection').callsFake(() => { 17 | return Promise.resolve(); 18 | }); 19 | const queryStub = sinon.stub(current.dialect.Query.prototype, 'run').callsFake(() => { 20 | return Promise.reject(new Error('wrong sql')); 21 | }); 22 | return current.query('THIS IS A WRONG SQL', { 23 | retry: { 24 | max: 2, 25 | // retry for all errors 26 | match: null 27 | } 28 | }) 29 | .catch(() => {}) 30 | .finally(() => { 31 | expect(releaseConnectionStub).have.been.calledOnce; 32 | queryStub.restore(); 33 | getConnectionStub.restore(); 34 | releaseConnectionStub.restore(); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/unit/sql/add-column.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Support = require(__dirname + '/../support'), 4 | DataTypes = require('../../../lib/data-types'), 5 | expectsql = Support.expectsql, 6 | current = Support.sequelize, 7 | sql = current.dialect.QueryGenerator; 8 | 9 | 10 | if (current.dialect.name === 'mysql') { 11 | describe(Support.getTestDialectTeaser('SQL'), () => { 12 | describe('addColumn', () => { 13 | 14 | const Model = current.define('users', { 15 | id: { 16 | type: DataTypes.INTEGER, 17 | primaryKey: true, 18 | autoIncrement: true 19 | } 20 | }, { timestamps: false }); 21 | 22 | it('properly generate alter queries', () => { 23 | return expectsql(sql.addColumnQuery(Model.getTableName(), 'level_id', current.normalizeAttribute({ 24 | type: DataTypes.FLOAT, 25 | allowNull: false 26 | })), { 27 | mysql: 'ALTER TABLE `users` ADD `level_id` FLOAT NOT NULL;' 28 | }); 29 | }); 30 | 31 | it('properly generate alter queries for foreign keys', () => { 32 | return expectsql(sql.addColumnQuery(Model.getTableName(), 'level_id', current.normalizeAttribute({ 33 | type: DataTypes.INTEGER, 34 | references: { 35 | model: 'level', 36 | key: 'id' 37 | }, 38 | onUpdate: 'cascade', 39 | onDelete: 'cascade' 40 | })), { 41 | 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;' 42 | }); 43 | }); 44 | 45 | it('properly generate alter queries with FIRST', () => { 46 | return expectsql(sql.addColumnQuery(Model.getTableName(), 'test_added_col_first', current.normalizeAttribute({ 47 | type: DataTypes.STRING, 48 | first: true 49 | })), { 50 | mysql: 'ALTER TABLE `users` ADD `test_added_col_first` VARCHAR(255) FIRST;' 51 | }); 52 | }); 53 | }); 54 | }); 55 | } 56 | -------------------------------------------------------------------------------- /test/unit/sql/change-column.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Support = require(__dirname + '/../support'), 4 | DataTypes = require('../../../lib/data-types'), 5 | expectsql = Support.expectsql, 6 | sinon = require('sinon'), 7 | current = Support.sequelize, 8 | Promise = current.Promise; 9 | 10 | 11 | if (current.dialect.name !== 'sqlite') { 12 | describe(Support.getTestDialectTeaser('SQL'), () => { 13 | describe('changeColumn', () => { 14 | 15 | const Model = current.define('users', { 16 | id: { 17 | type: DataTypes.INTEGER, 18 | primaryKey: true, 19 | autoIncrement: true 20 | }, 21 | level_id: { 22 | type: DataTypes.INTEGER 23 | } 24 | }, { timestamps: false }); 25 | 26 | before(function() { 27 | 28 | this.stub = sinon.stub(current, 'query').callsFake(sql => { 29 | return Promise.resolve(sql); 30 | }); 31 | }); 32 | 33 | beforeEach(function() { 34 | this.stub.resetHistory(); 35 | }); 36 | 37 | after(function() { 38 | this.stub.restore(); 39 | }); 40 | 41 | it('properly generate alter queries', () => { 42 | return current.getQueryInterface().changeColumn(Model.getTableName(), 'level_id', { 43 | type: DataTypes.FLOAT, 44 | allowNull: false 45 | }).then(sql => { 46 | expectsql(sql, { 47 | mssql: 'ALTER TABLE [users] ALTER COLUMN [level_id] FLOAT NOT NULL;', 48 | mysql: 'ALTER TABLE `users` CHANGE `level_id` `level_id` FLOAT NOT NULL;', 49 | postgres: 'ALTER TABLE "users" ALTER COLUMN "level_id" SET NOT NULL;ALTER TABLE "users" ALTER COLUMN "level_id" DROP DEFAULT;ALTER TABLE "users" ALTER COLUMN "level_id" TYPE FLOAT;' 50 | }); 51 | }); 52 | }); 53 | 54 | it('properly generate alter queries for foreign keys', () => { 55 | return current.getQueryInterface().changeColumn(Model.getTableName(), 'level_id', { 56 | type: DataTypes.INTEGER, 57 | references: { 58 | model: 'level', 59 | key: 'id' 60 | }, 61 | onUpdate: 'cascade', 62 | onDelete: 'cascade' 63 | }).then(sql => { 64 | expectsql(sql, { 65 | mssql: 'ALTER TABLE [users] ADD CONSTRAINT [level_id_foreign_idx] FOREIGN KEY ([level_id]) REFERENCES [level] ([id]) ON DELETE CASCADE;', 66 | mysql: 'ALTER TABLE `users` ADD CONSTRAINT `users_level_id_foreign_idx` FOREIGN KEY (`level_id`) REFERENCES `level` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;', 67 | postgres: 'ALTER TABLE "users" ADD CONSTRAINT "level_id_foreign_idx" FOREIGN KEY ("level_id") REFERENCES "level" ("id") ON DELETE CASCADE ON UPDATE CASCADE;' 68 | }); 69 | }); 70 | }); 71 | 72 | }); 73 | }); 74 | } 75 | -------------------------------------------------------------------------------- /test/unit/sql/create-schema.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Support = require(__dirname + '/../support'); 4 | const expectsql = Support.expectsql; 5 | const current = Support.sequelize; 6 | const sql = current.dialect.QueryGenerator; 7 | 8 | describe(Support.getTestDialectTeaser('SQL'), () => { 9 | if (current.dialect.name === 'postgres') { 10 | describe('dropSchema', () => { 11 | it('IF EXISTS', () => { 12 | expectsql(sql.dropSchema('foo'), { 13 | postgres: 'DROP SCHEMA IF EXISTS foo CASCADE;' 14 | }); 15 | }); 16 | }); 17 | 18 | describe('createSchema', () => { 19 | before(function() { 20 | this.version = current.options.databaseVersion; 21 | }); 22 | 23 | after(function() { 24 | current.options.databaseVersion = this.version; 25 | }); 26 | 27 | it('9.2.0 or above', () => { 28 | current.options.databaseVersion = '9.2.0'; 29 | expectsql(sql.createSchema('foo'), { 30 | postgres: 'CREATE SCHEMA IF NOT EXISTS foo;' 31 | }); 32 | }); 33 | 34 | it('below 9.2.0', () => { 35 | current.options.databaseVersion = '9.0.0'; 36 | expectsql(sql.createSchema('foo'), { 37 | postgres: 'CREATE SCHEMA foo;' 38 | }); 39 | }); 40 | }); 41 | } 42 | }); 43 | -------------------------------------------------------------------------------- /test/unit/sql/offset-limit.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Support = require(__dirname + '/../support'), 4 | util = require('util'), 5 | expectsql = Support.expectsql, 6 | current = Support.sequelize, 7 | sql = current.dialect.QueryGenerator; 8 | 9 | // Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation 10 | 11 | suite(Support.getTestDialectTeaser('SQL'), () => { 12 | suite('offset/limit', () => { 13 | const testsql = function(options, expectation) { 14 | const model = options.model; 15 | 16 | test(util.inspect(options, {depth: 2}), () => { 17 | return expectsql( 18 | sql.addLimitAndOffset( 19 | options, 20 | model 21 | ), 22 | expectation 23 | ); 24 | }); 25 | }; 26 | 27 | testsql({ 28 | limit: 10, //when no order by present, one is automagically prepended, test its existence 29 | model: {primaryKeyField: 'id', name: 'tableRef'} 30 | }, { 31 | default: ' LIMIT 10', 32 | mssql: ' ORDER BY [tableRef].[id] OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY' 33 | }); 34 | 35 | testsql({ 36 | limit: 10, 37 | order: [ 38 | ['email', 'DESC'] // for MSSQL 39 | ] 40 | }, { 41 | default: ' LIMIT 10', 42 | mssql: ' OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY' 43 | }); 44 | 45 | testsql({ 46 | limit: 10, 47 | offset: 20, 48 | order: [ 49 | ['email', 'DESC'] // for MSSQL 50 | ] 51 | }, { 52 | default: ' LIMIT 20, 10', 53 | postgres: ' LIMIT 10 OFFSET 20', 54 | mssql: ' OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY' 55 | }); 56 | 57 | testsql({ 58 | limit: "';DELETE FROM user", 59 | order: [ 60 | ['email', 'DESC'] // for MSSQL 61 | ] 62 | }, { 63 | default: " LIMIT ''';DELETE FROM user'", 64 | mysql: " LIMIT '\\';DELETE FROM user'", 65 | mssql: " OFFSET 0 ROWS FETCH NEXT N''';DELETE FROM user' ROWS ONLY" 66 | }); 67 | 68 | testsql({ 69 | limit: 10, 70 | offset: "';DELETE FROM user", 71 | order: [ 72 | ['email', 'DESC'] // for MSSQL 73 | ] 74 | }, { 75 | sqlite: " LIMIT ''';DELETE FROM user', 10", 76 | postgres: " LIMIT 10 OFFSET ''';DELETE FROM user'", 77 | mysql: " LIMIT '\\';DELETE FROM user', 10", 78 | mssql: " OFFSET N''';DELETE FROM user' ROWS FETCH NEXT 10 ROWS ONLY" 79 | }); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /test/unit/sql/remove-column.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const 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'), () => { 12 | suite('removeColumn', () => { 13 | test('schema', () => { 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 | -------------------------------------------------------------------------------- /test/unit/sql/remove-constraint.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Support = require(__dirname + '/../support'); 4 | const current = Support.sequelize; 5 | const expectsql = Support.expectsql; 6 | const sql = current.dialect.QueryGenerator; 7 | 8 | if (current.dialect.supports.constraints.dropConstraint) { 9 | describe(Support.getTestDialectTeaser('SQL'), () => { 10 | describe('removeConstraint', () => { 11 | it('naming', () => { 12 | expectsql(sql.removeConstraintQuery('myTable', 'constraint_name'), { 13 | default: 'ALTER TABLE [myTable] DROP CONSTRAINT [constraint_name]' 14 | }); 15 | }); 16 | }); 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /test/unit/sql/show-constraints.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Support = require(__dirname + '/../support'); 4 | const current = Support.sequelize; 5 | const expectsql = Support.expectsql; 6 | const sql = current.dialect.QueryGenerator; 7 | 8 | describe(Support.getTestDialectTeaser('SQL'), () => { 9 | describe('showConstraint', () => { 10 | it('naming', () => { 11 | expectsql(sql.showConstraintsQuery('myTable'), { 12 | mssql: "EXEC sp_helpconstraint @objname = N'[myTable]';", 13 | postgres: 'SELECT constraint_catalog AS "constraintCatalog", constraint_schema AS "constraintSchema", constraint_name AS "constraintName", table_catalog AS "tableCatalog", table_schema AS "tableSchema", table_name AS "tableName", constraint_type AS "constraintType", is_deferrable AS "isDeferrable", initially_deferred AS "initiallyDeferred" from INFORMATION_SCHEMA.table_constraints WHERE table_name=\'myTable\';', 14 | mysql: "SELECT CONSTRAINT_CATALOG AS constraintCatalog, CONSTRAINT_NAME AS constraintName, CONSTRAINT_SCHEMA AS constraintSchema, CONSTRAINT_TYPE AS constraintType, TABLE_NAME AS tableName, TABLE_SCHEMA AS tableSchema from INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE table_name='myTable';", 15 | default: "SELECT sql FROM sqlite_master WHERE tbl_name='myTable';" 16 | }); 17 | }); 18 | 19 | it('should add constraint_name to where clause if passed in case of mysql', () => { 20 | expectsql(sql.showConstraintsQuery('myTable', 'myConstraintName'), { 21 | mssql: "EXEC sp_helpconstraint @objname = N'[myTable]';", 22 | postgres: 'SELECT constraint_catalog AS "constraintCatalog", constraint_schema AS "constraintSchema", constraint_name AS "constraintName", table_catalog AS "tableCatalog", table_schema AS "tableSchema", table_name AS "tableName", constraint_type AS "constraintType", is_deferrable AS "isDeferrable", initially_deferred AS "initiallyDeferred" from INFORMATION_SCHEMA.table_constraints WHERE table_name=\'myTable\';', 23 | mysql: "SELECT CONSTRAINT_CATALOG AS constraintCatalog, CONSTRAINT_NAME AS constraintName, CONSTRAINT_SCHEMA AS constraintSchema, CONSTRAINT_TYPE AS constraintType, TABLE_NAME AS tableName, TABLE_SCHEMA AS tableSchema from INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE table_name='myTable' AND constraint_name = 'myConstraintName';", 24 | default: "SELECT sql FROM sqlite_master WHERE tbl_name='myTable' AND sql LIKE '%myConstraintName%';" 25 | }); 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/unit/sql/update.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Support = require(__dirname + '/../support'), 4 | DataTypes = require(__dirname + '/../../../lib/data-types'), 5 | expectsql = Support.expectsql, 6 | current = Support.sequelize, 7 | sql = current.dialect.QueryGenerator; 8 | 9 | // Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation 10 | 11 | describe(Support.getTestDialectTeaser('SQL'), () => { 12 | describe('update', () => { 13 | it('with temp table for trigger', () => { 14 | const User = Support.sequelize.define('user', { 15 | username: { 16 | type: DataTypes.STRING, 17 | field: 'user_name' 18 | } 19 | }, { 20 | timestamps: false, 21 | hasTrigger: true 22 | }); 23 | 24 | const options = { 25 | returning: true, 26 | hasTrigger: true 27 | }; 28 | expectsql(sql.updateQuery(User.tableName, {user_name: 'triggertest'}, {id: 2}, options, User.rawAttributes), 29 | { 30 | 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', 31 | postgres: 'UPDATE "users" SET "user_name"=\'triggertest\' WHERE "id" = 2 RETURNING *', 32 | default: "UPDATE `users` SET `user_name`=\'triggertest\' WHERE `id` = 2" 33 | }); 34 | }); 35 | 36 | 37 | it('Works with limit', () => { 38 | const User = Support.sequelize.define('User', { 39 | username: { 40 | type: DataTypes.STRING 41 | }, 42 | userId: { 43 | type: DataTypes.INTEGER 44 | } 45 | }, { 46 | timestamps: false 47 | }); 48 | 49 | expectsql(sql.updateQuery(User.tableName, { username: 'new.username' }, { username: 'username' }, { limit: 1 }), { 50 | mssql: "UPDATE TOP(1) [Users] SET [username]=N'new.username' OUTPUT INSERTED.* WHERE [username] = N'username'", 51 | mysql: "UPDATE `Users` SET `username`='new.username' WHERE `username` = 'username' LIMIT 1", 52 | sqlite: "UPDATE `Users` SET `username`='new.username' WHERE rowid IN (SELECT rowid FROM `Users` WHERE `username` = 'username' LIMIT 1)", 53 | default: "UPDATE [Users] SET [username]='new.username' WHERE [username] = 'username'" 54 | }); 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/unit/support.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('../support'); 4 | -------------------------------------------------------------------------------- /test/unit/transaction.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'); 4 | const expect = chai.expect; 5 | const sinon = require('sinon'); 6 | const Support = require(__dirname + '/support'); 7 | const Sequelize = Support.Sequelize; 8 | const dialect = Support.getTestDialect(); 9 | const current = Support.sequelize; 10 | 11 | describe('Transaction', function() { 12 | before(() => { 13 | this.stub = sinon.stub(current, 'query').returns(Sequelize.Promise.resolve({})); 14 | 15 | this.stubConnection = sinon.stub(current.connectionManager, 'getConnection') 16 | .returns(Sequelize.Promise.resolve({ 17 | uuid: 'ssfdjd-434fd-43dfg23-2d', 18 | close() {} 19 | })); 20 | 21 | this.stubRelease = sinon.stub(current.connectionManager, 'releaseConnection') 22 | .returns(Sequelize.Promise.resolve()); 23 | }); 24 | 25 | beforeEach(() => { 26 | this.stub.resetHistory(); 27 | this.stubConnection.resetHistory(); 28 | this.stubRelease.resetHistory(); 29 | }); 30 | 31 | after(() => { 32 | this.stub.restore(); 33 | this.stubConnection.restore(); 34 | }); 35 | 36 | it('should run auto commit query only when needed', () => { 37 | const expectations = { 38 | all: [ 39 | 'START TRANSACTION;' 40 | ], 41 | sqlite: [ 42 | 'BEGIN DEFERRED TRANSACTION;' 43 | ], 44 | mssql: [ 45 | 'BEGIN TRANSACTION;' 46 | ] 47 | }; 48 | return current.transaction(() => { 49 | expect(this.stub.args.map(arg => arg[0])).to.deep.equal(expectations[dialect] || expectations.all); 50 | return Sequelize.Promise.resolve(); 51 | }); 52 | }); 53 | }); 54 | --------------------------------------------------------------------------------