From b349ac4a8c3ddd6e26b327ae78e465de9ef7923d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20Lepist=C3=B6?= Date: Fri, 29 Jun 2018 10:47:06 +0300 Subject: [PATCH] Various fixes to mssql dialect (#2653) * Fixed float type of mssql to be float * Many tests where postgres test was not actually ran at all * Migrations to be mssql compatible Mssql driver doesn't handle if multiple queries are sent to same transaction concurrently. * Prevented mssql failing when invalid schema builder was executed by accident Instead of trying to generate sql from broken schema calls, just make exception to leak before query compiling is started * Fixed mssql trx rollback to always throw an error Also modified some connection test query to be mssql compatible * Fixed various bugs from MSSQL driver to make tests run * Fixed mssql unique index to be compatible with other dialect implementations * Enable running mssql tests on CI * Test for #2588 * Updated tests to not be dependend on tables left from previous test rans * Trying to make mssql server work on travis --- .travis.yml | 11 +- package.json | 1 + scripts/stress-test/knex-stress-test.js | 2 +- scripts/wait-for-mssql-server.js | 51 +++++++++ src/dialects/mssql/index.js | 9 +- src/dialects/mssql/query/compiler.js | 7 +- src/dialects/mssql/schema/columncompiler.js | 7 +- src/dialects/mssql/schema/tablecompiler.js | 17 ++- src/dialects/mssql/transaction.js | 10 +- src/migrate/index.js | 1 - test/integration/builder/additional.js | 21 ++-- test/integration/builder/aggregate.js | 22 ++-- test/integration/builder/deletes.js | 8 +- test/integration/builder/inserts.js | 22 ++-- test/integration/builder/joins.js | 12 +- test/integration/builder/selects.js | 79 ++++++++++--- test/integration/builder/transaction.js | 3 +- test/integration/builder/updates.js | 8 +- test/integration/migrate/index.js | 2 +- .../test/20131019235306_migration_2.js | 18 ++- .../20131019235306_migration_2.js | 22 ++-- test/integration/schema/index.js | 24 ++-- test/tape/crossdb-compatibility.js | 90 +++++++++++++++ test/tape/index.js | 1 + test/tape/invalid-db-setup.js | 5 +- test/unit/query/builder.js | 106 ++++++------------ test/unit/schema/mssql.js | 14 +-- 27 files changed, 381 insertions(+), 192 deletions(-) create mode 100644 scripts/wait-for-mssql-server.js create mode 100644 test/tape/crossdb-compatibility.js diff --git a/.travis.yml b/.travis.yml index 6d998a21d..6755782a3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,13 +10,13 @@ cache: matrix: include: - node_js: "10" - env: TEST_ORACLEDB=true DB="maria mysql mysql2 postgres sqlite3 oracledb" CXX=g++-4.8 KNEX_TEST_TIMEOUT=60000 ORACLE_HOME=/u01/app/oracle/product/11.2.0/xe ORACLE_SID=XE OCI_LIB_DIR=/u01/app/oracle/product/11.2.0/xe/lib LD_LIBRARY_PATH=/u01/app/oracle/product/11.2.0/xe/lib + env: TEST_ORACLEDB=true DB="mssql mysql mysql2 postgres sqlite3 oracledb" CXX=g++-4.8 KNEX_TEST_TIMEOUT=60000 ORACLE_HOME=/u01/app/oracle/product/11.2.0/xe ORACLE_SID=XE OCI_LIB_DIR=/u01/app/oracle/product/11.2.0/xe/lib LD_LIBRARY_PATH=/u01/app/oracle/product/11.2.0/xe/lib - node_js: "8" - env: TEST_ORACLEDB=true DB="maria mysql mysql2 postgres sqlite3 oracledb" CXX=g++-4.8 KNEX_TEST_TIMEOUT=60000 ORACLE_HOME=/u01/app/oracle/product/11.2.0/xe ORACLE_SID=XE OCI_LIB_DIR=/u01/app/oracle/product/11.2.0/xe/lib LD_LIBRARY_PATH=/u01/app/oracle/product/11.2.0/xe/lib + env: TEST_ORACLEDB=true DB="mssql mysql mysql2 postgres sqlite3 oracledb" CXX=g++-4.8 KNEX_TEST_TIMEOUT=60000 ORACLE_HOME=/u01/app/oracle/product/11.2.0/xe ORACLE_SID=XE OCI_LIB_DIR=/u01/app/oracle/product/11.2.0/xe/lib LD_LIBRARY_PATH=/u01/app/oracle/product/11.2.0/xe/lib - node_js: "6" - env: TEST_ORACLEDB=true DB="maria mysql mysql2 postgres sqlite3 oracledb" CXX=g++-4.8 KNEX_TEST_TIMEOUT=60000 ORACLE_HOME=/u01/app/oracle/product/11.2.0/xe ORACLE_SID=XE OCI_LIB_DIR=/u01/app/oracle/product/11.2.0/xe/lib LD_LIBRARY_PATH=/u01/app/oracle/product/11.2.0/xe/lib + env: TEST_ORACLEDB=true DB="mssql mysql mysql2 postgres sqlite3 oracledb" CXX=g++-4.8 KNEX_TEST_TIMEOUT=60000 ORACLE_HOME=/u01/app/oracle/product/11.2.0/xe ORACLE_SID=XE OCI_LIB_DIR=/u01/app/oracle/product/11.2.0/xe/lib LD_LIBRARY_PATH=/u01/app/oracle/product/11.2.0/xe/lib - node_js: "7" - env: DB="maria mysql mysql2 postgres sqlite3" CXX=g++-4.8 KNEX_TEST_TIMEOUT=60000 + env: DB="mssql mysql mysql2 postgres sqlite3" CXX=g++-4.8 KNEX_TEST_TIMEOUT=60000 install: npm i @@ -28,6 +28,9 @@ before_install: before_script: - psql -c 'create database knex_test;' -U postgres - mysql -e 'create database knex_test;' + - npm run mssql:init + - docker ps -a + - netstat -tulpn after_script: - npm run-script coveralls diff --git a/package.json b/package.json index afb0cbe67..11140fd5d 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "test": "npm run pre_test && istanbul --config=test/.istanbul.yml cover node_modules/mocha/bin/_mocha -- --check-leaks -t 10000 -b -R spec test/index.js && npm run tape", "oracledb:test": "docker rmi -f --no-prune knex-test-oracledb && docker build -f scripts/oracle-tests-Dockerfile --tag knex-test-oracledb . && docker run -i -t knex-test-oracledb", "mssql:init": "docker-compose -f scripts/mssql-docker-compose.yml up --no-start && docker-compose -f scripts/mssql-docker-compose.yml start", + "postmssql:init": "node scripts/wait-for-mssql-server.js", "mssql:test": "DB=mssql npm test", "mssql:destroy": "docker-compose -f scripts/mssql-docker-compose.yml stop", "stress:init": "docker-compose -f scripts/stress-test/docker-compose.yml up --no-start && docker-compose -f scripts/stress-test/docker-compose.yml start", diff --git a/scripts/stress-test/knex-stress-test.js b/scripts/stress-test/knex-stress-test.js index b1be938e4..71c1df604 100644 --- a/scripts/stress-test/knex-stress-test.js +++ b/scripts/stress-test/knex-stress-test.js @@ -37,7 +37,7 @@ const mssql = Knex({ }); /* TODO: figure out how to nicely install oracledb node driver on osx -const mysql = Knex({ +const oracledb = Knex({ client: 'oracledb', connection: { user : "travis", diff --git a/scripts/wait-for-mssql-server.js b/scripts/wait-for-mssql-server.js new file mode 100644 index 000000000..cb029f226 --- /dev/null +++ b/scripts/wait-for-mssql-server.js @@ -0,0 +1,51 @@ +var Connection = require('tedious').Connection; + +var config = { + userName: "sa", + password: "S0meVeryHardPassword", + server: "localhost", + options: { + database: "knex_test", + } +}; + +let didConnect = false; +let tryCount = 0; + +function tryToConnect() { + tryCount++; + if (tryCount > 50) { + console.log("Giving up... it fails if it fails"); + process.exit(0); + } + + console.log("Connecting... to mssql"); + + var connection = new Connection(config); + + connection.on('end', () => { + if (!didConnect) { + console.log("Couldnt connnect yet... try again in two secs..."); + setTimeout(tryToConnect, 2000); + } + }); + + connection.on('error', () => { + // prevent leaking errors.. driver seems to sometimes emit error event, + // sometimes connect event with error + // and some times just closes connection without error / connect events + // (debug event says that socket was ended and thats it...) + }); + + connection.on('connect', (err) => { + if (!err) { + console.log("Connecting mssql server was a great success!"); + didConnect = true; + } else { + console.log("Error was passed to connect event."); + } + connection.close(); + }); +} + +tryToConnect(); \ No newline at end of file diff --git a/src/dialects/mssql/index.js b/src/dialects/mssql/index.js index 53b676ddb..8b0831dd1 100644 --- a/src/dialects/mssql/index.js +++ b/src/dialects/mssql/index.js @@ -29,10 +29,10 @@ function Client_MSSQL(config = {}) { } // mssql always creates pool :( lets try to unpool it as much as possible - config.pool = { + this.mssqlPoolSettings = { min: 1, max: 1, - idleTimeoutMillis: Number.MAX_SAFE_INTEGER, + idleTimeoutMillis: Number.MAX_SAFE_INTEGER, evictionRunIntervalMillis: 0 }; @@ -199,7 +199,10 @@ assign(Client_MSSQL.prototype, { // connection needs to be added to the pool. acquireRawConnection() { return new Promise((resolver, rejecter) => { - const connection = new this.driver.ConnectionPool(this.connectionSettings); + const settings = Object.assign({}, this.connectionSettings); + settings.pool = this.mssqlPoolSettings; + + const connection = new this.driver.ConnectionPool(settings); connection.connect((err) => { if (err) { return rejecter(err) diff --git a/src/dialects/mssql/query/compiler.js b/src/dialects/mssql/query/compiler.js index f3aa72c80..e31407e88 100644 --- a/src/dialects/mssql/query/compiler.js +++ b/src/dialects/mssql/query/compiler.js @@ -165,11 +165,14 @@ assign(QueryCompiler_MSSQL.prototype, { }, forUpdate() { - return 'with (READCOMMITTEDLOCK)'; + // this doesn't work exacltly as it should, one should also mention index while locking + // https://stackoverflow.com/a/9818448/360060 + return 'with (UPDLOCK)'; }, forShare() { - return 'with (NOLOCK)'; + // http://www.sqlteam.com/article/introduction-to-locking-in-sql-server + return 'with (HOLDLOCK)'; }, // Compiles a `columnInfo` query. diff --git a/src/dialects/mssql/schema/columncompiler.js b/src/dialects/mssql/schema/columncompiler.js index f156189af..db00b40e9 100644 --- a/src/dialects/mssql/schema/columncompiler.js +++ b/src/dialects/mssql/schema/columncompiler.js @@ -24,13 +24,12 @@ assign(ColumnCompiler_MSSQL.prototype, { bigint: 'bigint', double(precision, scale) { - if (!precision) return 'decimal' - return `decimal(${this._num(precision, 8)}, ${this._num(scale, 2)})` + return 'float'; }, floating(precision, scale) { - if (!precision) return 'decimal' - return `decimal(${this._num(precision, 8)}, ${this._num(scale, 2)})` + // ignore precicion / scale which is mysql specific stuff + return `float`; }, integer(length) { diff --git a/src/dialects/mssql/schema/tablecompiler.js b/src/dialects/mssql/schema/tablecompiler.js index 74d6152b7..4f8632e45 100644 --- a/src/dialects/mssql/schema/tablecompiler.js +++ b/src/dialects/mssql/schema/tablecompiler.js @@ -19,7 +19,7 @@ inherits(TableCompiler_MSSQL, TableCompiler); assign(TableCompiler_MSSQL.prototype, { - createAlterTableMethods: ['foreign', 'primary', 'unique'], + createAlterTableMethods: ['foreign', 'primary'], createQuery (columns, ifNot) { const createStatement = ifNot ? `if object_id('${this.tableName()}', 'U') is null CREATE TABLE ` : 'CREATE TABLE '; const sql = createStatement + this.tableName() + (this._formatting ? ' (\n ' : ' (') + columns.sql.join(this._formatting ? ',\n ' : ', ') + ')'; @@ -118,11 +118,16 @@ assign(TableCompiler_MSSQL.prototype, { unique (columns, indexName) { indexName = indexName ? this.formatter.wrap(indexName) : this._indexCommand('unique', this.tableNameRaw, columns); - if (!this.forCreate) { - this.pushQuery(`CREATE UNIQUE INDEX ${indexName} ON ${this.tableName()} (${this.formatter.columnize(columns)})`); - } else { - this.pushQuery(`CONSTRAINT ${indexName} UNIQUE (${this.formatter.columnize(columns)})`); + + if (!Array.isArray(columns)) { + columns = [columns]; } + + const whereAllTheColumnsAreNotNull = columns.map(column => this.formatter.columnize(column) + ' IS NOT NULL').join(' AND '); + + // make unique constraint that allows null https://stackoverflow.com/a/767702/360060 + // to be more or less compatible with other DBs (if any of the columns is NULL then "duplicates" are allowed) + this.pushQuery(`CREATE UNIQUE INDEX ${indexName} ON ${this.tableName()} (${this.formatter.columnize(columns)}) WHERE ${whereAllTheColumnsAreNotNull}`); }, // Compile a drop index command. @@ -146,7 +151,7 @@ assign(TableCompiler_MSSQL.prototype, { // Compile a drop unique key command. dropUnique (column, indexName) { indexName = indexName ? this.formatter.wrap(indexName) : this._indexCommand('unique', this.tableNameRaw, column); - this.pushQuery(`ALTER TABLE ${this.tableName()} DROP CONSTRAINT ${indexName}`); + this.pushQuery(`DROP INDEX ${indexName} ON ${this.tableName()}`); } }) diff --git a/src/dialects/mssql/transaction.js b/src/dialects/mssql/transaction.js index 9869feba8..3ece9b1d7 100644 --- a/src/dialects/mssql/transaction.js +++ b/src/dialects/mssql/transaction.js @@ -1,5 +1,6 @@ import Promise from 'bluebird'; import Transaction from '../../transaction'; +import { isUndefined } from 'lodash' const debug = require('debug')('knex:tx') export default class Transaction_MSSQL extends Transaction { @@ -32,7 +33,13 @@ export default class Transaction_MSSQL extends Transaction { debug('%s: rolling back', this.txid) return conn.tx_.rollback() .then( - () => this._rejecter(error), + () => { + let err = error; + if(isUndefined(error)) { + err = new Error(`Transaction rejected with non-error: ${error}`) + } + this._rejecter(err) + }, err => { if (error) err.originalError = error; return this._rejecter(err); @@ -58,6 +65,7 @@ export default class Transaction_MSSQL extends Transaction { configConnection || t.client.acquireConnection(); }).tap(function(conn) { + conn.__knexTxId = t.txid; if (!t.outerTx) { t.conn = conn conn.tx_ = conn.transaction() diff --git a/src/migrate/index.js b/src/migrate/index.js index 1854586e1..dceaef647 100644 --- a/src/migrate/index.js +++ b/src/migrate/index.js @@ -198,7 +198,6 @@ export default class Migrator { _getLock(trx) { const transact = trx ? fn => fn(trx) : fn => this.knex.transaction(fn); - return transact(trx => { return this._isLocked(trx) .then(isLocked => { diff --git a/test/integration/builder/additional.js b/test/integration/builder/additional.js index 3dd309f2c..091d6fdb9 100644 --- a/test/integration/builder/additional.js +++ b/test/integration/builder/additional.js @@ -213,7 +213,7 @@ module.exports = function(knex) { .truncate() .testSql(function(tester) { tester('mysql', 'truncate `test_table_two`'); - tester('postgresql', 'truncate "test_table_two" restart identity'); + tester('pg', 'truncate "test_table_two" restart identity'); tester('pg-redshift', 'truncate "test_table_two"'); tester('sqlite3', "delete from `test_table_two`"); tester('oracle', "truncate table \"test_table_two\""); @@ -289,7 +289,7 @@ module.exports = function(knex) { "type": "char" } }); - tester('postgresql', 'select * from information_schema.columns where table_name = ? and table_catalog = ? and table_schema = current_schema', + tester('pg', 'select * from information_schema.columns where table_name = ? and table_catalog = ? and table_schema = current_schema()', null, { "enum_value": { "defaultValue": null, @@ -369,7 +369,7 @@ module.exports = function(knex) { }); }); - it('gets the columnInfo', function() { + it('gets the columnInfo with columntype', function() { return knex('datatype_test').columnInfo('uuid').testSql(function(tester) { tester('mysql', 'select * from information_schema.columns where table_name = ? and table_schema = ?', @@ -379,7 +379,7 @@ module.exports = function(knex) { "nullable": false, "type": "char" }); - tester('postgresql', 'select * from information_schema.columns where table_name = ? and table_catalog = ? and table_schema = current_schema', + tester('pg', 'select * from information_schema.columns where table_name = ? and table_catalog = ? and table_schema = current_schema()', null, { "defaultValue": null, "maxLength": null, @@ -465,7 +465,7 @@ module.exports = function(knex) { t.renameColumn('about', 'about_col'); }).testSql(function(tester) { tester('mysql', ["show fields from `accounts` where field = ?"]); - tester('postgresql', ["alter table \"accounts\" rename \"about\" to \"about_col\""]); + tester('pg', ["alter table \"accounts\" rename \"about\" to \"about_col\""]); tester('pg-redshift', ["alter table \"accounts\" rename \"about\" to \"about_col\""]); tester('sqlite3', ["PRAGMA table_info(`accounts`)"]); tester('oracle', ["alter table \"accounts\" rename column \"about\" to \"about_col\""]); @@ -503,7 +503,7 @@ module.exports = function(knex) { t.dropColumn('first_name'); }).testSql(function(tester) { tester('mysql', ["alter table `accounts` drop `first_name`"]); - tester('postgresql', ['alter table "accounts" drop column "first_name"']); + tester('pg', ['alter table "accounts" drop column "first_name"']); tester('pg-redshift', ['alter table "accounts" drop column "first_name"']); tester('sqlite3', ["PRAGMA table_info(`accounts`)"]); tester('oracle', ['alter table "accounts" drop ("first_name")']); @@ -720,10 +720,8 @@ module.exports = function(knex) { }); it('Event: start', function() { - // On redshift, cannot set an identity column to a value - if (/redshift/i.test(knex.client.dialect)) { return; } return knex('accounts') - .insert({id: '999', last_name: 'Start'}) + .insert({last_name: 'Start event test'}) .then(function() { var queryBuilder = knex('accounts').select(); @@ -731,7 +729,7 @@ module.exports = function(knex) { //Alter builder prior to compilation //Select only one row builder - .where('id', '999') + .where('last_name', 'Start event test') .first(); }); @@ -739,8 +737,7 @@ module.exports = function(knex) { }) .then(function(row) { expect(row).to.exist; - expect(String(row.id)).to.equal('999'); - expect(row.last_name).to.equal('Start'); + expect(row.last_name).to.equal('Start event test'); }); }); diff --git a/test/integration/builder/aggregate.js b/test/integration/builder/aggregate.js index 0d95cc4e4..51f401c71 100644 --- a/test/integration/builder/aggregate.js +++ b/test/integration/builder/aggregate.js @@ -26,7 +26,7 @@ module.exports = function(knex) { }] ); tester( - 'postgresql', + 'pg', 'select sum("logins") from "accounts"', [], [{ @@ -84,7 +84,7 @@ module.exports = function(knex) { // sqlite: 1.6666666666666667 tester('sqlite3', 'select avg(`logins`) from `accounts`', [], checkResRange.bind(null, 'avg(`logins`)')); // postgres: '1.6666666666666667' - tester('postgresql', 'select avg("logins") from "accounts"', [], checkResRange.bind(null, 'avg')); + tester('pg', 'select avg("logins") from "accounts"', [], checkResRange.bind(null, 'avg')); // postgres: '1.6666666666666667' tester('pg-redshift', 'select avg("logins") from "accounts"', [], checkResRangeMssql.bind(null, 'avg')); // oracle: 1.66666666666667 @@ -107,7 +107,7 @@ module.exports = function(knex) { }] ); tester( - 'postgresql', + 'pg', 'select count("id") from "accounts"', [], [{ @@ -164,7 +164,7 @@ module.exports = function(knex) { }] ); tester( - 'postgresql', + 'pg', 'select count("id"), max("logins"), min("logins") from "accounts"', [], [{ @@ -229,13 +229,13 @@ module.exports = function(knex) { }] ); tester( - 'postgresql', + 'pg', 'select count(distinct "id"), sum(distinct "logins"), avg(distinct "logins") from "accounts"', [], [{ count: '6', - sum: 3, - avg: 1.5 + sum: "3", + avg: "1.5000000000000000" }] ); tester( @@ -298,7 +298,7 @@ module.exports = function(knex) { }] ); tester( - 'postgresql', + 'pg', 'select count(distinct("id", "logins")) from "accounts"', [], [{ @@ -325,7 +325,7 @@ module.exports = function(knex) { }] ); tester( - 'postgresql', + 'pg', 'select count(distinct("id", "logins")) as "count" from "accounts"', [], [{ @@ -350,7 +350,7 @@ module.exports = function(knex) { }] ); tester( - 'postgresql', + 'pg', 'select count("id") from "accounts" group by "logins" order by "logins" asc', [], [{ @@ -411,7 +411,7 @@ module.exports = function(knex) { }] ); tester( - 'postgresql', + 'pg', 'select count("id") from "accounts" group by "first_name"', [], [{ diff --git a/test/integration/builder/deletes.js b/test/integration/builder/deletes.js index 9b0530f8f..4e7fc0ea1 100644 --- a/test/integration/builder/deletes.js +++ b/test/integration/builder/deletes.js @@ -18,7 +18,7 @@ module.exports = function(knex) { 1 ); tester( - 'postgresql', + 'pg', 'delete from "accounts" where "id" = ?', [1], 1 @@ -50,7 +50,7 @@ module.exports = function(knex) { }); }); - it('should allow returning for deletes in postgresql', function() { + it('should allow returning for deletes in postgresql and mssql', function() { return knex('accounts') .where('id', 2) .del('*') @@ -62,7 +62,7 @@ module.exports = function(knex) { 1 ); tester( - 'postgresql', + 'pg', 'delete from "accounts" where "id" = ? returning *', [2], [{ @@ -71,6 +71,7 @@ module.exports = function(knex) { last_name: 'User', email: 'test2@example.com', logins: 1, + balance: 0, about: 'Lorem ipsum Dolore labore incididunt enim.', created_at: d, updated_at: d, @@ -105,6 +106,7 @@ module.exports = function(knex) { last_name: 'User', email: 'test2@example.com', logins: 1, + balance: 0, about: 'Lorem ipsum Dolore labore incididunt enim.', created_at: d, updated_at: d, diff --git a/test/integration/builder/inserts.js b/test/integration/builder/inserts.js index 1316f84bc..f2dbdd554 100644 --- a/test/integration/builder/inserts.js +++ b/test/integration/builder/inserts.js @@ -28,7 +28,7 @@ module.exports = function(knex) { [1] ); tester( - 'postgresql', + 'pg', 'insert into "accounts" ("about", "created_at", "email", "first_name", "last_name", "logins", "updated_at") values (?, ?, ?, ?, ?, ?, ?) returning "id"', ['Lorem ipsum Dolore labore incididunt enim.', d,'test@example.com','Test','User', 1, d], ['1'] @@ -87,7 +87,7 @@ module.exports = function(knex) { [2] ); tester( - 'postgresql', + 'pg', 'insert into "accounts" ("about", "created_at", "email", "first_name", "last_name", "logins", "updated_at") values (?, ?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?, ?) returning "id"', ['Lorem ipsum Dolore labore incididunt enim.', d,'test2@example.com','Test','User',1, d,'Lorem ipsum Dolore labore incididunt enim.', d,'test3@example.com','Test','User',2, d], ['2','3'] @@ -189,7 +189,7 @@ module.exports = function(knex) { [4] ); tester( - 'postgresql', + 'pg', 'insert into "accounts" ("about", "created_at", "email", "first_name", "last_name", "logins", "updated_at") values (?, ?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?, ?) returning "id"', ['Lorem ipsum Dolore labore incididunt enim.', d,'test4@example.com','Test','User',2, d,'Lorem ipsum Dolore labore incididunt enim.', d,'test5@example.com','Test','User',2, d], ['4','5'] @@ -260,7 +260,7 @@ module.exports = function(knex) { ['Lorem ipsum Dolore labore incididunt enim.', d, 'test5@example.com','Test','User', 2, d] ); tester( - 'postgresql', + 'pg', 'insert into "accounts" ("about", "created_at", "email", "first_name", "last_name", "logins", "updated_at") values (?, ?, ?, ?, ?, ?, ?) returning "id"', ['Lorem ipsum Dolore labore incididunt enim.', d, 'test5@example.com','Test','User', 2, d] ); @@ -307,7 +307,7 @@ module.exports = function(knex) { [7] ); tester( - 'postgresql', + 'pg', 'insert into "accounts" ("about", "created_at", "email", "first_name", "last_name", "logins", "updated_at") values (?, ?, ?, ?, ?, ?, ?) returning "id"', ['Lorem ipsum Dolore labore incididunt enim.', d, 'test6@example.com','Test','User',2, d], ['7'] @@ -351,7 +351,7 @@ module.exports = function(knex) { ['d'] ); tester( - 'postgresql', + 'pg', 'insert into "datatype_test" ("enum_value") values (?)', ['d'] ); @@ -435,7 +435,7 @@ module.exports = function(knex) { [1] ); tester( - 'postgresql', + 'pg', 'insert into "test_default_table" default values returning "id"', [], [1] @@ -486,7 +486,7 @@ module.exports = function(knex) { [1] ); tester( - 'postgresql', + 'pg', 'insert into "test_default_table2" default values returning "id"', [], [1] @@ -541,7 +541,7 @@ module.exports = function(knex) { // [1] // ); // tester( - // 'postgresql', + // 'pg', // 'insert into "test_default_table3" ("id") values (default), (default) returning "id"', // [], // [1, 2] @@ -586,7 +586,7 @@ module.exports = function(knex) { [4] ); tester( - 'postgresql', + 'pg', 'insert into "test_table_two" ("account_id", "details", "status") values (?, ?, ?) returning "account_id", "details"', [10,'Lorem ipsum Minim nostrud Excepteur consectetur enim ut qui sint in veniam in nulla anim do cillum sunt voluptate Duis non incididunt.',0], [{account_id: 10, details: 'Lorem ipsum Minim nostrud Excepteur consectetur enim ut qui sint in veniam in nulla anim do cillum sunt voluptate Duis non incididunt.'}] @@ -641,7 +641,7 @@ module.exports = function(knex) { var returningColumn = '*'; return knex('test_table_two').insert(insertData, returningColumn).testSql(function(tester) { tester( - 'postgresql', + 'pg', 'insert into "test_table_two" ("account_id", "details", "status") values (?, ?, ?) returning *', [10,'Lorem ipsum Minim nostrud Excepteur consectetur enim ut qui sint in veniam in nulla anim do cillum sunt voluptate Duis non incididunt.',0], [{ diff --git a/test/integration/builder/joins.js b/test/integration/builder/joins.js index 91f6503eb..1fc1db393 100644 --- a/test/integration/builder/joins.js +++ b/test/integration/builder/joins.js @@ -52,7 +52,7 @@ module.exports = function(knex) { details: '' }] ); - tester('postgresql', + tester('pg', 'select "accounts".*, "test_table_two"."details" from "accounts" inner join "test_table_two" on "accounts"."id" = "test_table_two"."account_id" order by "accounts"."id" asc', [], [ @@ -348,7 +348,7 @@ module.exports = function(knex) { details: null }] ); - tester('postgresql', + tester('pg', 'select "accounts".*, "test_table_two"."details" from "accounts" left join "test_table_two" on "accounts"."id" = "test_table_two"."account_id" order by "accounts"."id" asc', [], [ @@ -849,7 +849,7 @@ module.exports = function(knex) { json_data: null }] ); - tester('postgresql', + tester('pg', 'select * from "accounts" left join "test_table_two" on "accounts"."id" = "test_table_two"."account_id" or "accounts"."email" = "test_table_two"."details" order by "accounts"."id" asc', [], [ @@ -1274,7 +1274,7 @@ module.exports = function(knex) { e2: 'test2@example.com' }] ); - tester('postgresql', + tester('pg', 'select "accounts"."email" as "e1", "a2"."email" as "e2" from "accounts" inner join "accounts" as "a2" on "a2"."email" <> "accounts"."email" where "a2"."email" = ? order by "e1" asc limit ?', ['test2@example.com', 5], [ @@ -1425,7 +1425,7 @@ module.exports = function(knex) { e2: 'test2@example.com' }] ); - tester('postgresql', + tester('pg', 'select "accounts"."email" as "e1", "a2"."email" as "e2" from "accounts" inner join "accounts" as "a2" on "accounts"."email" <> "a2"."email" or "accounts"."id" = 2 where "a2"."email" = ? order by "e1" asc limit ?', ['test2@example.com', 5], [{ @@ -1543,7 +1543,7 @@ module.exports = function(knex) { } ); tester( - 'postgresql', + 'pg', 'select "account_id" from "accounts" cross join "test_table_two" order by "account_id" asc', [], function (res) { diff --git a/test/integration/builder/selects.js b/test/integration/builder/selects.js index 6316e6da5..6c88f35c0 100644 --- a/test/integration/builder/selects.js +++ b/test/integration/builder/selects.js @@ -26,7 +26,7 @@ module.exports = function(knex) { [1, 2, 3, 4, 5, 7] ); tester( - 'postgresql', + 'pg', 'select "id" from "accounts" order by "id" asc', [], ['1', '2', '3', '4', '5', '7'] @@ -68,7 +68,7 @@ module.exports = function(knex) { [1, 2, 3, 4, 5, 7] ); tester( - 'postgresql', + 'pg', 'select "accounts"."id" from "accounts" order by "accounts"."id" asc', [], ['1', '2', '3', '4', '5', '7'] @@ -110,7 +110,7 @@ module.exports = function(knex) { [3, 4, 5, 7] ); tester( - 'postgresql', + 'pg', 'select "id" from "accounts" order by "id" asc offset ?', [2], ['3', '4', '5', '7'] @@ -152,7 +152,7 @@ module.exports = function(knex) { { id: 1, first_name: 'Test' } ); tester( - 'postgresql', + 'pg', 'select "id", "first_name" from "accounts" order by "id" asc limit ?', [1], { id: '1', first_name: 'Test' } @@ -350,7 +350,7 @@ module.exports = function(knex) { }] ); tester( - 'postgresql', + 'pg', 'select "first_name", "last_name" from "accounts" where "id" = ?', [1], [{ @@ -413,7 +413,7 @@ module.exports = function(knex) { }] ); tester( - 'postgresql', + 'pg', 'select "first_name", "last_name" from "accounts" where "id" = ?', [1], [{ @@ -472,7 +472,7 @@ module.exports = function(knex) { [1] ); tester( - 'postgresql', + 'pg', 'select "email", "logins" from "accounts" where "id" > ?', [1] ); @@ -523,7 +523,7 @@ module.exports = function(knex) { }] ); tester( - 'postgresql', + 'pg', 'select * from "accounts" where "id" = ?', [1], [{ @@ -623,7 +623,7 @@ module.exports = function(knex) { [] ); tester( - 'postgresql', + 'pg', 'select "first_name", "email" from "accounts" where "id" is null', [], [] @@ -669,7 +669,7 @@ module.exports = function(knex) { [] ); tester( - 'postgresql', + 'pg', 'select * from "accounts" where "id" = ?', [0], [] @@ -761,8 +761,8 @@ module.exports = function(knex) { details: 'One, Two, Zero', status: 0 }]); - tester('postgresql', - 'select * from "composite_key_test" where ("column_a", "column_b") in ((?, ?),(?, ?)) order by "status" desc', + tester('pg', + 'select * from "composite_key_test" where ("column_a", "column_b") in ((?, ?), (?, ?)) order by "status" desc', [1,1,1,2], [{ column_a: 1, @@ -826,8 +826,8 @@ module.exports = function(knex) { details: 'One, One, One', status: 1 }]); - tester('postgresql', - 'select * from "composite_key_test" where "status" = ? and ("column_a", "column_b") in ((?, ?),(?, ?))', + tester('pg', + 'select * from "composite_key_test" where "status" = ? and ("column_a", "column_b") in ((?, ?), (?, ?))', [1,1,1,1,2], [{ column_a: 1, @@ -956,6 +956,55 @@ module.exports = function(knex) { }) }); - }); + it('select for update locks selected row', function() { + if (knex.client.dialect === 'sqlite3') { + return; + } + return knex('test_default_table').insert({ string: 'making sure there is a row to lock'}) + .then(() => { + return knex.transaction(trx => { + // select all from test table and lock + return trx('test_default_table').forUpdate().then((res) => { + // try to select stuff from table in other connection should just hang... + return knex('test_default_table').forUpdate().timeout(100); + }); + }).then(res => { + expect("Second query should have timed out").to.be.false; + }).catch(err => { + expect(err.message).to.be.contain('Defined query timeout of 100ms exceeded when running query'); + }); + }); + }); + + it('select for share prevents updating in other transaction', function() { + if (knex.client.dialect === 'sqlite3' || knex.client.dialect === 'oracle') { + return; + } + + return knex('test_default_table').insert({ string: 'making sure there is a row to lock'}) + .then(() => { + return knex.transaction(trx => { + // select all from test table and lock + return trx('test_default_table').forShare().then((res) => { + // try to update row that was selected for share should just hang... + return knex.transaction(trx2 => { + return trx2('test_default_table').update({ string: 'foo' }).timeout(100); + }); + }); + }).then(res => { + expect("Second query should have timed out").to.be.false; + }).catch(err => { + // mssql fails because it tires to rollback at the same time when update query is running + // hopefully for share really works though... + if (knex.client.dialect == 'mssql') { + expect(err.message).to.be.contain("Can't rollback transaction. There is a request in progress"); + } else { + expect(err.message).to.be.contain('Defined query timeout of 100ms exceeded when running query'); + } + }); + }); + }); + + }); }; diff --git a/test/integration/builder/transaction.js b/test/integration/builder/transaction.js index 0ef45406b..75828b161 100644 --- a/test/integration/builder/transaction.js +++ b/test/integration/builder/transaction.js @@ -340,10 +340,11 @@ module.exports = function(knex) { //Create a transaction that will occupy the only available connection, and avoid trx.commit. return knexDb.transaction(function(trx) { - var sql = 'SELECT 1 = 1'; + var sql = 'SELECT 1'; if (knex.client.dialect === 'oracle') { sql = 'SELECT 1 FROM DUAL'; } + trx.raw(sql).then(function () { //No connection is available, so try issuing a query without transaction. //Since there is no available connection, it should throw a timeout error based on `aquireConnectionTimeout` from the knex config. diff --git a/test/integration/builder/updates.js b/test/integration/builder/updates.js index d262c29ac..4fca656e5 100644 --- a/test/integration/builder/updates.js +++ b/test/integration/builder/updates.js @@ -21,7 +21,7 @@ module.exports = function(knex) { 1 ); tester( - 'postgresql', + 'pg', 'update "accounts" set "first_name" = ?, "last_name" = ?, "email" = ? where "id" = ?', ['User','Test','test100@example.com',1], 1 @@ -137,7 +137,6 @@ module.exports = function(knex) { }); it('should allow returning for updates in postgresql', function() { - return knex('accounts').where('id', 1).update({ email:'test100@example.com', first_name: 'UpdatedUser', @@ -150,7 +149,7 @@ module.exports = function(knex) { 1 ); tester( - 'postgresql', + 'pg', 'update "accounts" set "email" = ?, "first_name" = ?, "last_name" = ? where "id" = ? returning *', ['test100@example.com','UpdatedUser','UpdatedTest',1], [{ @@ -159,6 +158,7 @@ module.exports = function(knex) { last_name: 'UpdatedTest', email: 'test100@example.com', logins: 1, + balance: 12.24, about: 'Lorem ipsum Dolore labore incididunt enim.', created_at: d, updated_at: d, @@ -193,6 +193,7 @@ module.exports = function(knex) { last_name: 'UpdatedTest', email: 'test100@example.com', logins: 1, + balance: 12.240000000000002, about: 'Lorem ipsum Dolore labore incididunt enim.', created_at: d, updated_at: d, @@ -200,7 +201,6 @@ module.exports = function(knex) { }] ); }); - }); }); diff --git a/test/integration/migrate/index.js b/test/integration/migrate/index.js index b48075b4d..13185ffd4 100644 --- a/test/integration/migrate/index.js +++ b/test/integration/migrate/index.js @@ -262,7 +262,7 @@ module.exports = function(knex) { // But the other should succeed: return knex('knex_migrations').select('*').then(function(data) { expect(data.length).to.equal(2); - }) + }); }); }); }); diff --git a/test/integration/migrate/test/20131019235306_migration_2.js b/test/integration/migrate/test/20131019235306_migration_2.js index b88b55fe4..dfcf23337 100644 --- a/test/integration/migrate/test/20131019235306_migration_2.js +++ b/test/integration/migrate/test/20131019235306_migration_2.js @@ -2,20 +2,18 @@ exports.up = function(knex, promise) { - return promise.all([ - knex.schema + return knex.schema .createTable('migration_test_2', function(t) { t.increments(); t.string('name'); - }), - knex.schema - .createTable('migration_test_2_1', function(t) { - t.increments(); - t.string('name'); - }) - ]); + }) + .then(() => knex.schema.createTable('migration_test_2_1', function(t) { + t.increments(); + t.string('name'); + })); }; exports.down = function(knex, promise) { - return promise.all([knex.schema.dropTable('migration_test_2'), knex.schema.dropTable('migration_test_2_1')]); + return knex.schema.dropTable('migration_test_2') + .then(() => knex.schema.dropTable('migration_test_2_1')); }; \ No newline at end of file diff --git a/test/integration/migrate/test_with_invalid/20131019235306_migration_2.js b/test/integration/migrate/test_with_invalid/20131019235306_migration_2.js index b88b55fe4..40e349fa8 100644 --- a/test/integration/migrate/test_with_invalid/20131019235306_migration_2.js +++ b/test/integration/migrate/test_with_invalid/20131019235306_migration_2.js @@ -2,20 +2,20 @@ exports.up = function(knex, promise) { - return promise.all([ - knex.schema - .createTable('migration_test_2', function(t) { + return knex.schema + .createTable('migration_test_2', function(t) { + t.increments(); + t.string('name'); + }) + .then(() => knex.schema + .createTable('migration_test_2_1', function(t) { t.increments(); t.string('name'); - }), - knex.schema - .createTable('migration_test_2_1', function(t) { - t.increments(); - t.string('name'); - }) - ]); + }) + ); }; exports.down = function(knex, promise) { - return promise.all([knex.schema.dropTable('migration_test_2'), knex.schema.dropTable('migration_test_2_1')]); + return knex.schema.dropTable('migration_test_2') + .then(() => knex.schema.dropTable('migration_test_2_1')); }; \ No newline at end of file diff --git a/test/integration/schema/index.js b/test/integration/schema/index.js index 4da1aeff9..d2e2ebd97 100644 --- a/test/integration/schema/index.js +++ b/test/integration/schema/index.js @@ -273,8 +273,10 @@ module.exports = function(knex) { 'create index "NkZo/dGRI9O73/NE2fHo+35d4jk" on "test_table_one" ("first_name")', 'alter table "test_table_one" add constraint "test_table_one_email_unique" unique ("email")', 'create index "test_table_one_logins_index" on "test_table_one" ("logins")']); - tester('mssql', ['CREATE TABLE [test_table_one] ([id] bigint identity(1,1) not null primary key, [first_name] nvarchar(255), [last_name] nvarchar(255), [email] nvarchar(255) null, [logins] int default \'1\', [balance] decimal default \'0\', [about] nvarchar(max), [created_at] datetime, [updated_at] datetime, CONSTRAINT [test_table_one_email_unique] UNIQUE ([email]))', + tester('mssql', [ + 'CREATE TABLE [test_table_one] ([id] bigint identity(1,1) not null primary key, [first_name] nvarchar(255), [last_name] nvarchar(255), [email] nvarchar(255) null, [logins] int default \'1\', [balance] float default \'0\', [about] nvarchar(max), [created_at] datetime, [updated_at] datetime)', 'CREATE INDEX [test_table_one_first_name_index] ON [test_table_one] ([first_name])', + 'CREATE UNIQUE INDEX [test_table_one_email_unique] ON [test_table_one] ([email]) WHERE [email] IS NOT NULL', 'CREATE INDEX [test_table_one_logins_index] ON [test_table_one] ([logins])']); }); }); @@ -388,7 +390,7 @@ module.exports = function(knex) { }); it('rejects setting foreign key where tableName is not typeof === string', function() { - return knex.schema.createTable('invalid_inTable_param_test', function(table) { + let builder = knex.schema.createTable('invalid_inTable_param_test', function(table) { const createInvalidUndefinedInTableSchema = function() { table.increments('id').references('id').inTable() }; @@ -397,7 +399,11 @@ module.exports = function(knex) { }; expect(createInvalidUndefinedInTableSchema).to.throw(TypeError); expect(createInvalidObjectInTableSchema).to.throw(TypeError); - }) + + table.integer('yet_another_id').references('id').inTable({tableName: 'this_should_fail_too'}) + }); + + expect(() => builder.toSQL()).to.throw(TypeError); }); @@ -415,7 +421,10 @@ module.exports = function(knex) { tester('pg-redshift', ['create table "composite_key_test" ("column_a" integer, "column_b" integer, "details" varchar(max), "status" smallint)','alter table "composite_key_test" add constraint "composite_key_test_column_a_column_b_unique" unique ("column_a", "column_b")']); tester('sqlite3', ['create table `composite_key_test` (`column_a` integer, `column_b` integer, `details` text, `status` tinyint)','create unique index `composite_key_test_column_a_column_b_unique` on `composite_key_test` (`column_a`, `column_b`)']); tester('oracle', ['create table "composite_key_test" ("column_a" integer, "column_b" integer, "details" clob, "status" smallint)','alter table "composite_key_test" add constraint "zYmMt0VQwlLZ20XnrMicXZ0ufZk" unique ("column_a", "column_b")']); - tester('mssql', ['CREATE TABLE [composite_key_test] ([column_a] int, [column_b] int, [details] nvarchar(max), [status] tinyint, CONSTRAINT [composite_key_test_column_a_column_b_unique] UNIQUE ([column_a], [column_b]))']); + tester('mssql', [ + 'CREATE TABLE [composite_key_test] ([column_a] int, [column_b] int, [details] nvarchar(max), [status] tinyint)', + 'CREATE UNIQUE INDEX [composite_key_test_column_a_column_b_unique] ON [composite_key_test] ([column_a], [column_b]) WHERE [column_a] IS NOT NULL AND [column_b] IS NOT NULL' + ]); }).then(function() { return knex('composite_key_test').insert([{ column_a: 1, @@ -556,9 +565,10 @@ module.exports = function(knex) { }); it('allows alter column syntax', function () { - if (knex.client.dialect.match('sqlite') !== null || - knex.client.dialect.match('redshift') !== null || - knex.client.dialect.match('oracle') !== null) { + if (knex.client.dialect.match('sqlite') || + knex.client.dialect.match('redshift') || + knex.client.dialect.match('mssql') || + knex.client.dialect.match('oracle')) { return; } diff --git a/test/tape/crossdb-compatibility.js b/test/tape/crossdb-compatibility.js new file mode 100644 index 000000000..d5d10020d --- /dev/null +++ b/test/tape/crossdb-compatibility.js @@ -0,0 +1,90 @@ +'use strict'; +const tape = require('tape') + +/** + * Collection of tests for making sure that certain features are cross database compatible + */ +module.exports = function(knex) { + + const dialect = knex.client.dialect; + + if (dialect === 'oracle') { + // TODO: FIX ORACLE TO WORK THE SAME WAY WITH OTHER DIALECTS IF POSSIBLE + return; + } + + tape(dialect + ' - crossdb compatibility: setup test table', function(t) { + knex.schema.dropTableIfExists('test_table') + .createTable('test_table', function(t) { + t.integer('id'); + t.string('first'); + t.string('second'); + t.string('third').unique(); + t.unique(['first', 'second']); + }) + .finally(function() { + t.end(); + }); + }); + + tape(dialect + ' - crossdb compatibility: table may have multiple nulls in unique constrainted column', function (t) { + t.plan(3); + + knex('test_table').insert([ + { third: 'foo' }, + { third: 'foo' } + ]).catch(err => { + t.assert(true, 'unique constraint prevents adding rows'); + return knex('test_table').insert([ + { first: 'foo2', second: 'bar2' }, + { first: 'foo2', second: 'bar2' } + ]); + }).catch(err => { + t.assert(true, 'two column unique constraint prevents adding rows'); + + // even one null makes index to not match, thus allows adding the row + return knex('test_table').insert([ + { first: 'fo', second: null, third: null }, + { first: 'fo', second: null, third: null }, + { first: null, second: 'fo', third: null }, + { first: null, second: 'fo', third: null }, + { first: null, second: null, third: null }, + ]); + }).then(() => { + return knex('test_table'); + }).then(res => { + t.assert(res.length == 5, 'multiple rows with nulls could be added despite of unique constraints') + }).finally(() => { + t.end(); + }); + }); + + tape(dialect + ' - create and drop index works in different cases', t => { + t.plan(1); + knex.schema.dropTableIfExists('test_table_drop_unique') + .createTable('test_table_drop_unique', t => { + t.integer('id'); + t.string('first'); + t.string('second'); + t.string('third').unique(); + t.string('fourth'); + t.unique(['first', 'second']); + t.unique('fourth'); + }).alterTable('test_table_drop_unique', t => { + t.dropUnique('third'); + t.dropUnique('fourth'); + t.dropUnique(['first', 'second']); + }).alterTable('test_table_drop_unique', t => { + t.unique(['first', 'second']); + t.unique('third'); + t.unique('fourth'); + }) + .then(() => { + t.assert(true, 'Creating / dropping / creating unique constraint was a success'); + }) + .finally(() => { + t.end(); + }) + }); + +} diff --git a/test/tape/index.js b/test/tape/index.js index ef0706461..eaf053b29 100644 --- a/test/tape/index.js +++ b/test/tape/index.js @@ -20,6 +20,7 @@ Object.keys(knexfile).forEach(function(key) { require('./transactions')(knex) require('./stream')(knex) + require('./crossdb-compatibility')(knex) // Tear down the knex connection tape(knex.client.driverName + ' - transactions: after', function(t) { diff --git a/test/tape/invalid-db-setup.js b/test/tape/invalid-db-setup.js index c55ba9028..7a3258262 100644 --- a/test/tape/invalid-db-setup.js +++ b/test/tape/invalid-db-setup.js @@ -9,7 +9,8 @@ module.exports = (knexfile) => { Object.keys(knexfile).forEach((key) => { const dialect = knexfile[key].dialect || knexfile[key].client; - if (dialect !== 'sqlite3' && dialect !== 'oracledb') { + // TODO: FIX ORACLE AND MSSQL TO WORK THE SAME WAY WITH OTHER DIALECTS IF POSSIBLE + if (dialect !== 'sqlite3' && dialect !== 'oracledb' && dialect !== 'mssql') { const knexConf = _.cloneDeep(knexfile[key]); knexConf.connection.database = knexConf.connection.db = 'i-refuse-to-exist'; knexConf.acquireConnectionTimeout = 4000; @@ -19,7 +20,7 @@ module.exports = (knexfile) => { t.plan(1); t.timeoutAfter(1000); knex('accounts').select(1) - .then(res => { + .then(res => { t.fail(`Query should have failed, got: ${JSON.stringify(res)}`); }) .catch(Bluebird.TimeoutError, e => { diff --git a/test/unit/query/builder.js b/test/unit/query/builder.js index 85fac9c97..083f45235 100644 --- a/test/unit/query/builder.js +++ b/test/unit/query/builder.js @@ -4335,71 +4335,43 @@ describe("QueryBuilder", function() { }); }); - // it("lock for update", function (){ - // testsql(tb().select('*').from('foo').where('bar', '=', 'baz').forUpdate(), { - // mysql: { - // sql: 'select * from `foo` where `bar` = ? for update', - // bindings: ['baz'] - // }, - // postgres: { - // sql: 'select * from "foo" where "bar" = ? for update', - // bindings: ['baz'] - // }, - // redshift: { - // sql: 'select * from "foo" where "bar" = ? for update', - // bindings: ['baz'] - // }, - // oracle: { - // sql: 'select * from "foo" where "bar" = ? for update', - // bindings: ['baz'] - // }, - // mssql: { - // sql: 'select * from [foo] where [bar] = ? with (READCOMMITTEDLOCK)', - // bindings: ['baz'] - // }, - // oracledb: { - // sql: 'select * from "foo" where "bar" = ? for update', - // bindings: ['baz'] - // }, - // postgres: { - // sql: 'select * from "foo" where "bar" = ?', - // bindings: ['baz'] - // }, - // redshift: { - // sql: 'select * from "foo" where "bar" = ?', - // bindings: ['baz'] - // }, - // }); - // }); + it("lock for update", function (){ + testsql(qb().select('*').from('foo').where('bar', '=', 'baz').forUpdate(), { + mysql: { + sql: 'select * from `foo` where `bar` = ? for update', + bindings: ['baz'] + }, + postgres: { + sql: 'select * from "foo" where "bar" = ? for update', + bindings: ['baz'] + }, + mssql: { + sql: 'select * from [foo] with (UPDLOCK) where [bar] = ?', + bindings: ['baz'] + }, + oracledb: { + sql: 'select * from "foo" where "bar" = ? for update', + bindings: ['baz'] + } + }); + }); - // it("lock in share mode", function() { - // testsql(qb().transacting({}).select('*').from('foo').where('bar', '=', 'baz').forShare(), { - // mysql: { - // sql: 'select * from `foo` where `bar` = ? lock in share mode', - // bindings: ['baz'] - // }, - // postgres: { - // sql: "select * from \"foo\" where \"bar\" = ? for share", - // bindings: ['baz'] - // }, - // redshift: { - // sql: "select * from \"foo\" where \"bar\" = ? for share", - // bindings: ['baz'] - // }, - // mssql: { - // sql: 'select * from [foo] where [bar] = ? with (NOLOCK)', - // bindings: ['baz'] - // }, - // postgres: { - // sql: 'select * from "foo" where "bar" = ?', - // bindings: ['baz'] - // }, - // redshift: { - // sql: 'select * from "foo" where "bar" = ?', - // bindings: ['baz'] - // }, - // }); - // }); + it("lock in share mode", function() { + testsql(qb().select('*').from('foo').where('bar', '=', 'baz').forShare(), { + mysql: { + sql: 'select * from `foo` where `bar` = ? lock in share mode', + bindings: ['baz'] + }, + postgres: { + sql: "select * from \"foo\" where \"bar\" = ? for share", + bindings: ['baz'] + }, + mssql: { + sql: 'select * from [foo] with (HOLDLOCK) where [bar] = ?', + bindings: ['baz'] + }, + }); + }); it("should allow lock (such as forUpdate) outside of a transaction", function() { testsql(qb().select('*').from('foo').where('bar', '=', 'baz').forUpdate(), { @@ -4408,17 +4380,13 @@ describe("QueryBuilder", function() { bindings: ['baz'] }, mssql: { - sql: 'select * from [foo] with (READCOMMITTEDLOCK) where [bar] = ?', + sql: 'select * from [foo] with (UPDLOCK) where [bar] = ?', bindings: ['baz'] }, postgres: { sql: 'select * from "foo" where "bar" = ? for update', bindings: ['baz'] }, - redshift: { - sql: 'select * from "foo" where "bar" = ?', - bindings: ['baz'] - }, }); }); diff --git a/test/unit/schema/mssql.js b/test/unit/schema/mssql.js index 82beab99d..2049ba0cd 100644 --- a/test/unit/schema/mssql.js +++ b/test/unit/schema/mssql.js @@ -90,7 +90,7 @@ describe("MSSQL SchemaBuilder", function() { }).toSQL(); equal(1, tableSql.length); - expect(tableSql[0].sql).to.equal('ALTER TABLE [users] DROP CONSTRAINT [users_foo_unique]'); + expect(tableSql[0].sql).to.equal('DROP INDEX [users_foo_unique] ON [users]'); }); it('should alter columns with the alter flag', function() { @@ -110,7 +110,7 @@ describe("MSSQL SchemaBuilder", function() { }).toSQL(); equal(1, tableSql.length); - expect(tableSql[0].sql).to.equal('ALTER TABLE [users] DROP CONSTRAINT [foo]'); + expect(tableSql[0].sql).to.equal('DROP INDEX [foo] ON [users]'); }); it('test drop index', function() { @@ -182,7 +182,7 @@ describe("MSSQL SchemaBuilder", function() { }).toSQL(); equal(1, tableSql.length); - expect(tableSql[0].sql).to.equal('CREATE UNIQUE INDEX [bar] ON [users] ([foo])'); + expect(tableSql[0].sql).to.equal('CREATE UNIQUE INDEX [bar] ON [users] ([foo]) WHERE [foo] IS NOT NULL'); }); it('test adding index', function() { @@ -368,7 +368,7 @@ describe("MSSQL SchemaBuilder", function() { }).toSQL(); equal(1, tableSql.length); - expect(tableSql[0].sql).to.equal('ALTER TABLE [users] ADD [foo] decimal(5, 2)'); + expect(tableSql[0].sql).to.equal('ALTER TABLE [users] ADD [foo] float'); }); it('test adding double', function() { @@ -377,7 +377,7 @@ describe("MSSQL SchemaBuilder", function() { }).toSQL(); equal(1, tableSql.length); - expect(tableSql[0].sql).to.equal('ALTER TABLE [users] ADD [foo] decimal'); + expect(tableSql[0].sql).to.equal('ALTER TABLE [users] ADD [foo] float'); }); it('test adding double specifying precision', function() { @@ -386,7 +386,7 @@ describe("MSSQL SchemaBuilder", function() { }).toSQL(); equal(1, tableSql.length); - expect(tableSql[0].sql).to.equal('ALTER TABLE [users] ADD [foo] decimal(15, 8)'); + expect(tableSql[0].sql).to.equal('ALTER TABLE [users] ADD [foo] float'); }); it('test adding decimal', function() { @@ -530,7 +530,7 @@ describe("MSSQL SchemaBuilder", function() { }).toSQL(); equal(1, tableSql.length); - expect(tableSql[0].sql).to.equal('ALTER TABLE [composite_key_test] DROP CONSTRAINT [composite_key_test_column_a_column_b_unique]'); + expect(tableSql[0].sql).to.equal('DROP INDEX [composite_key_test_column_a_column_b_unique] ON [composite_key_test]'); }); it('allows default as alias for defaultTo', function() {