diff --git a/lib/client.js b/lib/client.js index 2817a30947..7f10bb3510 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1,5 +1,3 @@ -const Bluebird = require('bluebird'); - const Raw = require('./raw'); const Ref = require('./ref'); const Runner = require('./runner'); @@ -335,28 +333,23 @@ Object.assign(Client.prototype, { }, // Acquire a connection from the pool. - acquireConnection() { + async acquireConnection() { if (!this.pool) { - return Bluebird.reject(new Error('Unable to acquire a connection')); + throw new Error('Unable to acquire a connection'); } try { - return Bluebird.try(() => this.pool.acquire().promise) - .then((connection) => { - debug('acquired connection from pool: %s', connection.__knexUid); - return connection; - }) - .catch((error) => { - let convertedError = error; - if (error instanceof TimeoutError) { - convertedError = new KnexTimeoutError( - 'Knex: Timeout acquiring a connection. The pool is probably full. ' + - 'Are you missing a .transacting(trx) call?' - ); - } - throw convertedError; - }); - } catch (e) { - return Bluebird.reject(e); + const connection = await this.pool.acquire().promise; + debug('acquired connection from pool: %s', connection.__knexUid); + return connection; + } catch (error) { + let convertedError = error; + if (error instanceof TimeoutError) { + convertedError = new KnexTimeoutError( + 'Knex: Timeout acquiring a connection. The pool is probably full. ' + + 'Are you missing a .transacting(trx) call?' + ); + } + throw convertedError; } }, @@ -370,14 +363,14 @@ Object.assign(Client.prototype, { debug('pool refused connection: %s', connection.__knexUid); } - return Bluebird.resolve(); + return Promise.resolve(); }, // Destroy the current connection pool for the client. destroy(callback) { const maybeDestroy = this.pool && this.pool.destroy(); - return Bluebird.resolve(maybeDestroy) + return Promise.resolve(maybeDestroy) .then(() => { this.pool = void 0; @@ -390,7 +383,7 @@ Object.assign(Client.prototype, { callback(err); } - return Bluebird.reject(err); + return Promise.reject(err); }); }, diff --git a/lib/dialects/mssql/index.js b/lib/dialects/mssql/index.js index f1f898cfed..17e1ec1c7a 100644 --- a/lib/dialects/mssql/index.js +++ b/lib/dialects/mssql/index.js @@ -4,7 +4,6 @@ const { map, flatten, values } = require('lodash'); const inherits = require('inherits'); const Client = require('../../client'); -const Bluebird = require('bluebird'); const Formatter = require('../../formatter'); const Transaction = require('./transaction'); @@ -217,7 +216,7 @@ Object.assign(Client_MSSQL.prototype, { // Get a raw connection, called by the `pool` whenever a new // connection needs to be added to the pool. acquireRawConnection() { - return new Bluebird((resolver, rejecter) => { + return new Promise((resolver, rejecter) => { const settings = Object.assign({}, this.connectionSettings); settings.pool = this.mssqlPoolSettings; @@ -264,7 +263,7 @@ Object.assign(Client_MSSQL.prototype, { // and pass that through to the stream we've sent back to the client. _stream(connection, obj, stream) { if (!obj || typeof obj === 'string') obj = { sql: obj }; - return new Bluebird((resolver, rejecter) => { + return new Promise((resolver, rejecter) => { stream.on('error', (err) => { rejecter(err); }); @@ -290,7 +289,7 @@ Object.assign(Client_MSSQL.prototype, { _query(connection, obj) { const client = this; if (!obj || typeof obj === 'string') obj = { sql: obj }; - return new Bluebird((resolver, rejecter) => { + return new Promise((resolver, rejecter) => { const { sql } = obj; if (!sql) return resolver(); const req = (connection.tx_ || connection).request(); diff --git a/lib/dialects/mssql/transaction.js b/lib/dialects/mssql/transaction.js index 2a348e7e7a..2963a3787f 100644 --- a/lib/dialects/mssql/transaction.js +++ b/lib/dialects/mssql/transaction.js @@ -1,4 +1,3 @@ -const Bluebird = require('bluebird'); const Transaction = require('../../transaction'); const { isUndefined } = require('lodash'); const debug = require('debug')('knex:tx'); @@ -9,11 +8,9 @@ module.exports = class Transaction_MSSQL extends Transaction { return conn.tx_.begin().then(this._resolver, this._rejecter); } - savepoint(conn) { + async savepoint(conn) { debug('%s: savepoint at', this.txid); - return Bluebird.resolve().then(() => - this.query(conn, `SAVE TRANSACTION ${this.txid}`) - ); + return this.query(conn, `SAVE TRANSACTION ${this.txid}`); } commit(conn, value) { @@ -48,13 +45,11 @@ module.exports = class Transaction_MSSQL extends Transaction { ); } - rollbackTo(conn, error) { + async rollbackTo(conn, error) { debug('%s: rolling backTo', this.txid); - return Bluebird.resolve() - .then(() => - this.query(conn, `ROLLBACK TRANSACTION ${this.txid}`, 2, error) - ) - .then(() => this._rejecter(error)); + await this.query(conn, `ROLLBACK TRANSACTION ${this.txid}`, 2, error); + + this._rejecter(error); } // Acquire a connection and create a disposer - either using the one passed @@ -62,7 +57,7 @@ module.exports = class Transaction_MSSQL extends Transaction { // the original promise is marked completed. acquireConnection(config, cb) { const configConnection = config && config.connection; - return new Bluebird((resolve, reject) => { + return new Promise((resolve, reject) => { try { resolve( (this.outerTx ? this.outerTx.conn : null) || diff --git a/lib/dialects/mysql/index.js b/lib/dialects/mysql/index.js index 33ea9a8787..256323f7e7 100644 --- a/lib/dialects/mysql/index.js +++ b/lib/dialects/mysql/index.js @@ -4,7 +4,6 @@ const inherits = require('inherits'); const { map, defer } = require('lodash'); const { promisify } = require('util'); const Client = require('../../client'); -const Bluebird = require('bluebird'); const Transaction = require('./transaction'); const QueryCompiler = require('./query/compiler'); @@ -61,7 +60,7 @@ Object.assign(Client_MySQL.prototype, { // Get a raw connection, called by the `pool` whenever a new // connection needs to be added to the pool. acquireRawConnection() { - return new Bluebird((resolver, rejecter) => { + return new Promise((resolver, rejecter) => { const connection = this.driver.createConnection(this.connectionSettings); connection.on('error', (err) => { connection.__knex__disposed = err; @@ -106,7 +105,7 @@ Object.assign(Client_MySQL.prototype, { _stream(connection, obj, stream, options) { options = options || {}; const queryOptions = Object.assign({ sql: obj.sql }, obj.options); - return new Bluebird((resolver, rejecter) => { + return new Promise((resolver, rejecter) => { stream.on('error', rejecter); stream.on('end', resolver); const queryStream = connection @@ -126,7 +125,7 @@ Object.assign(Client_MySQL.prototype, { // and any other necessary prep work. _query(connection, obj) { if (!obj || typeof obj === 'string') obj = { sql: obj }; - return new Bluebird(function(resolver, rejecter) { + return new Promise(function(resolver, rejecter) { if (!obj.sql) { resolver(); return; diff --git a/lib/dialects/oracle/index.js b/lib/dialects/oracle/index.js index 1af3b8826c..a317358d46 100644 --- a/lib/dialects/oracle/index.js +++ b/lib/dialects/oracle/index.js @@ -5,7 +5,6 @@ const { promisify } = require('util'); const inherits = require('inherits'); const Client = require('../../client'); -const Bluebird = require('bluebird'); const { bufferToString } = require('../../query/string'); const Formatter = require('./formatter'); @@ -80,10 +79,10 @@ Object.assign(Client_Oracle.prototype, { // Get a raw connection, called by the `pool` whenever a new // connection needs to be added to the pool. acquireRawConnection() { - return new Bluebird((resolver, rejecter) => { + return new Promise((resolver, rejecter) => { this.driver.connect(this.connectionSettings, (err, connection) => { if (err) return rejecter(err); - Bluebird.promisifyAll(connection); + connection.executeAsync = promisify(connection.execute); if (this.connectionSettings.prefetchRowCount) { connection.setPrefetchRowCount( this.connectionSettings.prefetchRowCount @@ -116,7 +115,7 @@ Object.assign(Client_Oracle.prototype, { }, _stream(connection, obj, stream, options) { - return new Bluebird(function(resolver, rejecter) { + return new Promise(function(resolver, rejecter) { stream.on('error', (err) => { if (isConnectionError(err)) { connection.__knex__disposed = err; diff --git a/lib/dialects/oracle/transaction.js b/lib/dialects/oracle/transaction.js index 0a823c5a34..ed6bc1fa63 100644 --- a/lib/dialects/oracle/transaction.js +++ b/lib/dialects/oracle/transaction.js @@ -1,4 +1,3 @@ -const Bluebird = require('bluebird'); const Transaction = require('../../transaction'); const { isUndefined } = require('lodash'); const debugTx = require('debug')('knex:tx'); @@ -6,7 +5,7 @@ const debugTx = require('debug')('knex:tx'); module.exports = class Oracle_Transaction extends Transaction { // disable autocommit to allow correct behavior (default is true) begin() { - return Bluebird.resolve(); + return Promise.resolve(); } commit(conn, value) { @@ -42,7 +41,7 @@ module.exports = class Oracle_Transaction extends Transaction { acquireConnection(config, cb) { const configConnection = config && config.connection; - return new Bluebird((resolve, reject) => { + return new Promise((resolve, reject) => { try { resolve(configConnection || this.client.acquireConnection()); } catch (e) { diff --git a/lib/dialects/oracledb/index.js b/lib/dialects/oracledb/index.js index f7f7fcb3b7..c2103f926b 100644 --- a/lib/dialects/oracledb/index.js +++ b/lib/dialects/oracledb/index.js @@ -5,7 +5,6 @@ const inherits = require('inherits'); const QueryCompiler = require('./query/compiler'); const ColumnCompiler = require('./schema/columncompiler'); const { BlobHelper, ReturningHelper, isConnectionError } = require('./utils'); -const Bluebird = require('bluebird'); const stream = require('stream'); const { promisify } = require('util'); const Transaction = require('./transaction'); @@ -77,7 +76,7 @@ Client_Oracledb.prototype.prepBindings = function(bindings) { // connection needs to be added to the pool. Client_Oracledb.prototype.acquireRawConnection = function() { const client = this; - const asyncConnection = new Bluebird(function(resolver, rejecter) { + const asyncConnection = new Promise(function(resolver, rejecter) { // If external authentication don't have to worry about username/password and // if not need to set the username and password const oracleDbConfig = client.connectionSettings.externalAuth @@ -107,7 +106,7 @@ Client_Oracledb.prototype.acquireRawConnection = function() { return rejecter(err); } connection.commitAsync = function() { - return new Bluebird((commitResolve, commitReject) => { + return new Promise((commitResolve, commitReject) => { this.commit(function(err) { if (err) { return commitReject(err); @@ -117,7 +116,7 @@ Client_Oracledb.prototype.acquireRawConnection = function() { }); }; connection.rollbackAsync = function() { - return new Bluebird((rollbackResolve, rollbackReject) => { + return new Promise((rollbackResolve, rollbackReject) => { this.rollback(function(err) { if (err) { return rollbackReject(err); @@ -254,96 +253,100 @@ Client_Oracledb.prototype._query = function(connection, obj) { if (obj.method === 'select') { options.resultSet = true; } - return Bluebird.resolve( - connection.executeAsync(obj.sql, obj.bindings, options) - ).then(async function(response) { - // Flatten outBinds - let outBinds = _.flatten(response.outBinds); - obj.response = response.rows || []; - obj.rowsAffected = response.rows - ? response.rows.rowsAffected - : response.rowsAffected; - - //added for outBind parameter - if (obj.method === 'raw' && outBinds.length > 0) { - return { - response: outBinds, - }; - } - - if (obj.method === 'update') { - const modifiedRowsCount = obj.rowsAffected.length || obj.rowsAffected; - const updatedObjOutBinding = []; - const updatedOutBinds = []; - const updateOutBinds = (i) => - function(value, index) { - const OutBindsOffset = index * modifiedRowsCount; - updatedOutBinds.push(outBinds[i + OutBindsOffset]); + return connection + .executeAsync(obj.sql, obj.bindings, options) + .then(async function(response) { + // Flatten outBinds + let outBinds = _.flatten(response.outBinds); + obj.response = response.rows || []; + obj.rowsAffected = response.rows + ? response.rows.rowsAffected + : response.rowsAffected; + + //added for outBind parameter + if (obj.method === 'raw' && outBinds.length > 0) { + return { + response: outBinds, }; + } - for (let i = 0; i < modifiedRowsCount; i++) { - updatedObjOutBinding.push(obj.outBinding[0]); - _.each(obj.outBinding[0], updateOutBinds(i)); + if (obj.method === 'update') { + const modifiedRowsCount = obj.rowsAffected.length || obj.rowsAffected; + const updatedObjOutBinding = []; + const updatedOutBinds = []; + const updateOutBinds = (i) => + function(value, index) { + const OutBindsOffset = index * modifiedRowsCount; + updatedOutBinds.push(outBinds[i + OutBindsOffset]); + }; + + for (let i = 0; i < modifiedRowsCount; i++) { + updatedObjOutBinding.push(obj.outBinding[0]); + _.each(obj.outBinding[0], updateOutBinds(i)); + } + outBinds = updatedOutBinds; + obj.outBinding = updatedObjOutBinding; } - outBinds = updatedOutBinds; - obj.outBinding = updatedObjOutBinding; - } - if (!obj.returning && outBinds.length === 0) { - if (!connection.isTransaction) { - await connection.commitAsync(); + if (!obj.returning && outBinds.length === 0) { + if (!connection.isTransaction) { + await connection.commitAsync(); + } + return obj; } - return obj; - } - const rowIds = []; - let offset = 0; + const rowIds = []; + let offset = 0; - for (let line = 0; line < obj.outBinding.length; line++) { - const ret = obj.outBinding[line]; + for (let line = 0; line < obj.outBinding.length; line++) { + const ret = obj.outBinding[line]; - offset = - offset + - (obj.outBinding[line - 1] ? obj.outBinding[line - 1].length : 0); + offset = + offset + + (obj.outBinding[line - 1] ? obj.outBinding[line - 1].length : 0); - for (let index = 0; index < ret.length; index++) { - const out = ret[index]; + for (let index = 0; index < ret.length; index++) { + const out = ret[index]; - await new Promise(function(bindResolver, bindRejecter) { - if (out instanceof BlobHelper) { - const blob = outBinds[index + offset]; - if (out.returning) { + await new Promise(function(bindResolver, bindRejecter) { + if (out instanceof BlobHelper) { + const blob = outBinds[index + offset]; + if (out.returning) { + obj.response[line] = obj.response[line] || {}; + obj.response[line][out.columnName] = out.value; + } + blob.on('error', function(err) { + bindRejecter(err); + }); + blob.on('finish', function() { + bindResolver(); + }); + blob.write(out.value); + blob.end(); + } else if (obj.outBinding[line][index] === 'ROWID') { + rowIds.push(outBinds[index + offset]); + bindResolver(); + } else { obj.response[line] = obj.response[line] || {}; - obj.response[line][out.columnName] = out.value; - } - blob.on('error', function(err) { - bindRejecter(err); - }); - blob.on('finish', function() { + obj.response[line][out] = outBinds[index + offset]; bindResolver(); - }); - blob.write(out.value); - blob.end(); - } else if (obj.outBinding[line][index] === 'ROWID') { - rowIds.push(outBinds[index + offset]); - bindResolver(); - } else { - obj.response[line] = obj.response[line] || {}; - obj.response[line][out] = outBinds[index + offset]; - bindResolver(); - } - }); + } + }); + } + } + if (connection.isTransaction) { + return obj; + } + await connection.commitAsync(); + if (obj.returningSql) { + const response = await connection.executeAsync( + obj.returningSql(), + rowIds, + { resultSet: true } + ); + obj.response = response.rows; } - } - if (connection.isTransaction) { return obj; - } - await connection.commitAsync(); - if (obj.returningSql) { - const response = await connection.executeAsync(obj.returningSql(), rowIds, { resultSet: true }) - obj.response = response.rows; - } - return obj; - }); + }); }; /** diff --git a/lib/dialects/oracledb/transaction.js b/lib/dialects/oracledb/transaction.js index 6d6828a309..9d1c633168 100644 --- a/lib/dialects/oracledb/transaction.js +++ b/lib/dialects/oracledb/transaction.js @@ -1,6 +1,5 @@ const { isUndefined } = require('lodash'); -const Bluebird = require('bluebird'); const Transaction = require('../../transaction'); const { timeout, KnexTimeoutError } = require('../../util/timeout'); const debugTx = require('debug')('knex:tx'); @@ -8,7 +7,7 @@ const debugTx = require('debug')('knex:tx'); module.exports = class Oracle_Transaction extends Transaction { // disable autocommit to allow correct behavior (default is true) begin() { - return Bluebird.resolve(); + return Promise.resolve(); } async commit(conn, value) { @@ -54,7 +53,7 @@ module.exports = class Oracle_Transaction extends Transaction { acquireConnection(config, cb) { const configConnection = config && config.connection; const t = this; - return new Bluebird((resolve, reject) => { + return new Promise((resolve, reject) => { try { this.client .acquireConnection() diff --git a/lib/dialects/postgres/index.js b/lib/dialects/postgres/index.js index 4d1856df77..0d43ea933e 100644 --- a/lib/dialects/postgres/index.js +++ b/lib/dialects/postgres/index.js @@ -4,7 +4,6 @@ const { map, extend, isString } = require('lodash'); const { promisify } = require('util'); const inherits = require('inherits'); const Client = require('../../client'); -const Bluebird = require('bluebird'); const QueryCompiler = require('./query/compiler'); const ColumnCompiler = require('./schema/columncompiler'); @@ -106,7 +105,7 @@ Object.assign(Client_PG.prototype, { // connection needs to be added to the pool. acquireRawConnection() { const client = this; - return new Bluebird(function(resolver, rejecter) { + return new Promise(function(resolver, rejecter) { const connection = new client.driver.Client(client.connectionSettings); connection.connect(function(err, connection) { if (err) { @@ -142,7 +141,7 @@ Object.assign(Client_PG.prototype, { // In PostgreSQL, we need to do a version check to do some feature // checking on the database. checkVersion(connection) { - return new Bluebird(function(resolver, rejecter) { + return new Promise(function(resolver, rejecter) { connection.query('select version();', function(err, resp) { if (err) return rejecter(err); resolver(/^PostgreSQL (.*?)( |$)/.exec(resp.rows[0].version)[1]); @@ -167,7 +166,7 @@ Object.assign(Client_PG.prototype, { setSchemaSearchPath(connection, searchPath) { let path = searchPath || this.searchPath; - if (!path) return Bluebird.resolve(true); + if (!path) return Promise.resolve(true); if (!Array.isArray(path) && !isString(path)) { throw new TypeError( @@ -191,7 +190,7 @@ Object.assign(Client_PG.prototype, { path = path.map((schemaName) => `"${schemaName}"`).join(','); - return new Bluebird(function(resolver, rejecter) { + return new Promise(function(resolver, rejecter) { connection.query(`set search_path to ${path}`, function(err) { if (err) return rejecter(err); resolver(true); @@ -205,7 +204,7 @@ Object.assign(Client_PG.prototype, { : require('pg-query-stream'); const sql = obj.sql; - return new Bluebird(function(resolver, rejecter) { + return new Promise(function(resolver, rejecter) { const queryStream = connection.query( new PGQueryStream(sql, obj.bindings, options) ); @@ -233,7 +232,7 @@ Object.assign(Client_PG.prototype, { queryConfig = extend(queryConfig, obj.options); } - return new Bluebird(function(resolver, rejecter) { + return new Promise(function(resolver, rejecter) { connection.query(queryConfig, function(err, response) { if (err) return rejecter(err); obj.response = response; @@ -273,22 +272,20 @@ Object.assign(Client_PG.prototype, { }, canCancelQuery: true, - cancelQuery(connectionToKill) { - const acquiringConn = this.acquireConnection(); - + async cancelQuery(connectionToKill) { // Error out if we can't acquire connection in time. // Purposely not putting timeout on `pg_cancel_backend` execution because erroring // early there would release the `connectionToKill` back to the pool with // a `KILL QUERY` command yet to finish. - return acquiringConn.then((conn) => { - return this._wrappedCancelQueryCall(conn, connectionToKill).finally( - () => { - // NOT returning this promise because we want to release the connection - // in a non-blocking fashion - this.releaseConnection(conn); - } - ); - }); + const conn = await this.acquireConnection(); + + try { + return await this._wrappedCancelQueryCall(conn, connectionToKill); + } finally { + // NOT returning this promise because we want to release the connection + // in a non-blocking fashion + this.releaseConnection(conn); + } }, _wrappedCancelQueryCall(conn, connectionToKill) { return this.query(conn, { diff --git a/lib/dialects/sqlite3/index.js b/lib/dialects/sqlite3/index.js index 05dfb94a69..c0408d9a31 100644 --- a/lib/dialects/sqlite3/index.js +++ b/lib/dialects/sqlite3/index.js @@ -1,7 +1,5 @@ // SQLite3 // ------- -const Bluebird = require('bluebird'); - const inherits = require('inherits'); const { isUndefined, map, defaults } = require('lodash'); const { promisify } = require('util'); @@ -63,7 +61,7 @@ Object.assign(Client_SQLite3.prototype, { // Get a raw connection from the database, returning a promise with the connection object. acquireRawConnection() { - return new Bluebird((resolve, reject) => { + return new Promise((resolve, reject) => { const db = new this.driver.Database( this.connectionSettings.filename, (err) => { @@ -98,7 +96,7 @@ Object.assign(Client_SQLite3.prototype, { default: callMethod = 'all'; } - return new Bluebird(function(resolver, rejecter) { + return new Promise(function(resolver, rejecter) { if (!connection || !connection[callMethod]) { return rejecter( new Error(`Error calling ${callMethod} on connection.`) @@ -118,7 +116,7 @@ Object.assign(Client_SQLite3.prototype, { _stream(connection, sql, stream) { const client = this; - return new Bluebird(function(resolver, rejecter) { + return new Promise(function(resolver, rejecter) { stream.on('error', rejecter); stream.on('end', resolver); return client diff --git a/lib/interface.js b/lib/interface.js index f18b5e3920..dafa7fd162 100644 --- a/lib/interface.js +++ b/lib/interface.js @@ -1,5 +1,6 @@ -const { isEmpty, map, clone, each } = require('lodash'); -const Bluebird = require('bluebird'); +const { availablePromiseMethods } = require('./util/promises'); +const { isEmpty, map, clone } = require('lodash'); +const { callbackify } = require('util'); module.exports = function(Target) { Target.prototype.toQuery = function(tz) { @@ -25,7 +26,7 @@ module.exports = function(Target) { }); } - return Bluebird.resolve(result.then.apply(result, arguments)); + return result.then.apply(result, arguments); }; // Add additional "options" to the builder. Typically used for client specific @@ -81,31 +82,18 @@ module.exports = function(Target) { ); }; + Target.prototype.asCallback = function(cb) { + const promise = this.then(); + callbackify(() => promise)(cb); + return promise; + }; + // Creates a method which "coerces" to a promise, by calling a // "then" method on the current `Target` - each( - [ - 'bind', - 'catch', - 'finally', - 'asCallback', - 'spread', - 'map', - 'reduce', - 'thenReturn', - 'return', - 'yield', - 'ensure', - 'reflect', - 'get', - 'mapSeries', - 'delay', - ], - function(method) { - Target.prototype[method] = function() { - const promise = this.then(); - return promise[method].apply(promise, arguments); - }; - } - ); + availablePromiseMethods().forEach(function(method) { + Target.prototype[method] = function() { + const promise = this.then(); + return promise[method].apply(promise, arguments); + }; + }); }; diff --git a/lib/migrate/migration-list-resolver.js b/lib/migrate/migration-list-resolver.js index 5b8ef37bcf..925f37fe32 100644 --- a/lib/migrate/migration-list-resolver.js +++ b/lib/migrate/migration-list-resolver.js @@ -1,4 +1,3 @@ -const Bluebird = require('bluebird'); const { getTableName } = require('./table-resolver'); const { ensureTable } = require('./table-creator'); @@ -27,7 +26,7 @@ function listCompleted(tableName, schemaName, trxOrKnex) { // Gets the migration list from the migration directory specified in config, as well as // the list of completed migrations to check what should be run. function listAllAndCompleted(config, trxOrKnex) { - return Bluebird.all([ + return Promise.all([ listAll(config.migrationSource, config.loadExtensions), listCompleted(config.tableName, config.schemaName, trxOrKnex), ]); diff --git a/lib/runner.js b/lib/runner.js index f3809e6855..23c6169020 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -1,4 +1,3 @@ -const Bluebird = require('bluebird'); const { KnexTimeoutError } = require('./util/timeout'); const { timeout } = require('./util/timeout'); @@ -105,7 +104,7 @@ Object.assign(Runner.prototype, { // and the promise will take care of itself. if (hasHandler) { handler(stream); - return Bluebird.resolve(promise); + return promise; } // Emit errors on the stream if the error occurred before a connection @@ -181,7 +180,7 @@ Object.assign(Runner.prototype, { // return the connection to the pool, it will be useless until the current operation // that timed out, finally finishes. this.connection.__knex__disposed = error; - cancelQuery = Bluebird.resolve(); + cancelQuery = Promise.resolve(); } return cancelQuery diff --git a/lib/transaction.js b/lib/transaction.js index e562de8d1d..96e71ee868 100644 --- a/lib/transaction.js +++ b/lib/transaction.js @@ -1,10 +1,11 @@ // Transaction // ------- -const Bluebird = require('bluebird'); const { EventEmitter } = require('events'); const Debug = require('debug'); const makeKnex = require('./util/make-knex'); +const { callbackify } = require('util'); +const { availablePromiseMethods } = require('./util/promises'); const { timeout, KnexTimeoutError } = require('./util/timeout'); const debug = Debug('knex:tx'); @@ -68,7 +69,7 @@ class Transaction extends EventEmitter { const init = client.transacting ? this.savepoint(connection) : this.begin(connection); - const executionPromise = new Bluebird((resolver, rejecter) => { + const executionPromise = new Promise((resolver, rejecter) => { this._resolver = resolver; this._rejecter = rejecter; }); @@ -87,7 +88,7 @@ class Transaction extends EventEmitter { try { result = container(transactor); } catch (err) { - result = Bluebird.reject(err); + result = Promise.reject(err); } if (result && result.then && typeof result.then === 'function') { result @@ -113,10 +114,9 @@ class Transaction extends EventEmitter { } }); - // FYI: This is the Promise Chain for EXTERNAL use. It ensures that the // caller must handle any exceptions that result from `basePromise`. - this._promise = basePromise.then((x)=> x); + this._promise = basePromise.then((x) => x); this._completed = false; @@ -124,14 +124,14 @@ class Transaction extends EventEmitter { // transactions to settle (commit or rollback) before we can start, and we // need to register ourselves with the parent transaction so any younger // siblings can wait for us to complete before they can start. - this._previousSibling = Bluebird.resolve(true); + this._previousSibling = Promise.resolve(true); if (outerTx) { if (outerTx._lastChild) this._previousSibling = outerTx._lastChild; // FYI: This is the Promise Chain for INTERNAL use. It serves as a signal // for when the next sibling should begin its execution. Therefore, // exceptions are caught and ignored. - outerTx._lastChild = basePromise.catch(()=> {}); + outerTx._lastChild = basePromise.catch(() => {}); } } @@ -222,7 +222,7 @@ class Transaction extends EventEmitter { // the original promise is marked completed. acquireConnection(config, cb) { const configConnection = config && config.connection; - return new Bluebird((resolve, reject) => { + return new Promise((resolve, reject) => { try { resolve(configConnection || this.client.acquireConnection()); } catch (e) { @@ -232,11 +232,13 @@ class Transaction extends EventEmitter { .then((connection) => { connection.__knexTxId = this.txid; - return this._previousSibling - // .catch(() => {}) // TODO: Investigate this line - .then(function() { - return connection; - }); + return ( + this._previousSibling + // .catch(() => {}) // TODO: Investigate this line + .then(function() { + return connection; + }) + ); }) .then(async (connection) => { try { @@ -251,6 +253,15 @@ class Transaction extends EventEmitter { } }); } + + then(onResolve, onReject) { + return this._promise.then(onResolve, onReject); + } + + asCallback(cb) { + callbackify(() => this._promise)(cb); + return this._promise; + } } // The transactor is a full featured knex object, with a "commit", a "rollback" @@ -337,7 +348,7 @@ function makeTxClient(trx, client, connection) { const _query = trxClient.query; trxClient.query = function(conn, obj) { const completed = trx.isCompleted(); - return new Bluebird(function(resolve, reject) { + return new Promise(function(resolve, reject) { try { if (conn !== connection) throw new Error('Invalid connection for transaction query.'); @@ -351,7 +362,7 @@ function makeTxClient(trx, client, connection) { const _stream = trxClient.stream; trxClient.stream = function(conn, obj, stream, options) { const completed = trx.isCompleted(); - return new Bluebird(function(resolve, reject) { + return new Promise(function(resolve, reject) { try { if (conn !== connection) throw new Error('Invalid connection for transaction query.'); @@ -363,10 +374,10 @@ function makeTxClient(trx, client, connection) { }); }; trxClient.acquireConnection = function() { - return Bluebird.resolve(connection); + return Promise.resolve(connection); }; trxClient.releaseConnection = function() { - return Bluebird.resolve(); + return Promise.resolve(); }; return trxClient; @@ -380,31 +391,11 @@ function completedError(trx, obj) { ); } -const promiseInterface = [ - 'then', - 'bind', - 'catch', - 'finally', - 'asCallback', - 'spread', - 'map', - 'reduce', - 'thenReturn', - 'return', - 'yield', - 'ensure', - 'exec', - 'reflect', - 'get', - 'mapSeries', - 'delay', -]; - // Creates methods which proxy promise interface methods to // internal transaction resolution promise -promiseInterface.forEach(function(method) { +availablePromiseMethods().forEach(function(method) { Transaction.prototype[method] = function() { - return this._promise[method].apply(this._promise, arguments); + return this._promise[method](...arguments); }; }); diff --git a/lib/util/promises.js b/lib/util/promises.js new file mode 100644 index 0000000000..02e70024eb --- /dev/null +++ b/lib/util/promises.js @@ -0,0 +1,5 @@ +function availablePromiseMethods() { + return Promise.prototype.finally ? ['catch', 'finally'] : ['catch']; +} + +module.exports = { availablePromiseMethods }; diff --git a/lib/util/timeout.js b/lib/util/timeout.js index f05cd5694a..64cb7be85d 100644 --- a/lib/util/timeout.js +++ b/lib/util/timeout.js @@ -1,4 +1,3 @@ -const Bluebird = require('bluebird'); const delay = require('./delay'); class KnexTimeoutError extends Error { @@ -10,11 +9,9 @@ class KnexTimeoutError extends Error { module.exports.KnexTimeoutError = KnexTimeoutError; module.exports.timeout = (promise, ms) => - Bluebird.resolve( - Promise.race([ - promise, - delay(ms).then(() => - Promise.reject(new KnexTimeoutError('operation timed out')) - ), - ]) - ); + Promise.race([ + promise, + delay(ms).then(() => + Promise.reject(new KnexTimeoutError('operation timed out')) + ), + ]); diff --git a/package.json b/package.json index 932e50e6c6..1857350744 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ "stress:destroy": "docker-compose -f scripts/stress-test/docker-compose.yml stop" }, "dependencies": { - "bluebird": "^3.7.2", "colorette": "1.1.0", "commander": "^4.1.1", "debug": "4.1.1", @@ -79,6 +78,7 @@ "@types/node": "^10.17.15", "JSONStream": "^1.3.5", "chai": "^4.2.0", + "chai-as-promised": "^7.1.1", "chai-subset-in-order": "^2.1.3", "cli-testlab": "^1.10.0", "coveralls": "^3.0.9", diff --git a/scripts/build.js b/scripts/build.js index d263c10fa0..6233e2e625 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -2,7 +2,6 @@ const fs = require('fs'); const path = require('path'); const child_process = require('child_process'); -const Promise = require('bluebird'); const _ = require('lodash'); const exec = function(cmd, args) { @@ -91,7 +90,7 @@ if (POSTINSTALL_BUILD_CWD !== CWD) { ? exec('npm install ' + installArgs, opts) : Promise.resolve(); dependenciesInstalledQ - .then(function(stdout, stderr) { + .then(function() { console.log('✓'); // Don't need the flag anymore as `postinstall` was already run. // Change it back so the environment is minimally changed for the @@ -104,7 +103,7 @@ if (POSTINSTALL_BUILD_CWD !== CWD) { console.error(err); process.exit(1); }) - .then(function(stdout, stderr) { + .then(function() { if (process.env.NODE_ENV === 'production') { console.log('✓'); console.log('Pruning dev dependencies for production build'); diff --git a/scripts/stress-test/knex-stress-test.js b/scripts/stress-test/knex-stress-test.js index 08dac98b93..7d049c20bf 100644 --- a/scripts/stress-test/knex-stress-test.js +++ b/scripts/stress-test/knex-stress-test.js @@ -1,9 +1,9 @@ const Knex = require('../../lib'); -const Bluebird = require('bluebird'); const toxiproxy = require('toxiproxy-node-client'); const toxicli = new toxiproxy.Toxiproxy('http://localhost:8474'); const rp = require('request-promise-native'); +const delay = require('../../lib/util/delay'); // init instances const pg = Knex({ @@ -178,7 +178,7 @@ async function main() { setInterval(recreateProxies, 2000); while (true) { - await Bluebird.delay(20); // kill everything every quite often from server side + await delay(20); // kill everything every quite often from server side try { await Promise.all([ killConnectionsPg(), diff --git a/scripts/stress-test/mysql2-random-hanging-every-now-and-then.js b/scripts/stress-test/mysql2-random-hanging-every-now-and-then.js index a2f051ff42..15c08b213a 100644 --- a/scripts/stress-test/mysql2-random-hanging-every-now-and-then.js +++ b/scripts/stress-test/mysql2-random-hanging-every-now-and-then.js @@ -2,7 +2,7 @@ * Test case for figuring out robust way to recognize if connection is dead * for mysql based drivers. */ -const Bluebird = require('bluebird'); +const delay = require('../../lib/util/delay'); const toxiproxy = require('toxiproxy-node-client'); const toxicli = new toxiproxy.Toxiproxy('http://localhost:8474'); const rp = require('request-promise-native'); @@ -122,7 +122,7 @@ async function main() { try { counts.queries += 1; // without this delay we might endup to busy failure loop - await Bluebird.delay(0); + await delay(0); await query(); counts.results += 1; } catch (err) { @@ -136,7 +136,7 @@ async function main() { // wait forever while (true) { - await Bluebird.delay(1000); + await delay(1000); } } diff --git a/scripts/stress-test/mysql2-sudden-exit-without-error.js b/scripts/stress-test/mysql2-sudden-exit-without-error.js index fcb2612ebc..b08d1b461b 100644 --- a/scripts/stress-test/mysql2-sudden-exit-without-error.js +++ b/scripts/stress-test/mysql2-sudden-exit-without-error.js @@ -3,7 +3,7 @@ * to dead connection. */ -const Bluebird = require('bluebird'); +const delay = require('../../lib/util/delay'); const toxiproxy = require('toxiproxy-node-client'); const toxicli = new toxiproxy.Toxiproxy('http://localhost:8474'); const rp = require('request-promise-native'); @@ -70,7 +70,7 @@ async function main() { // wait forever while (true) { - await Bluebird.delay(1000); + await delay(1000); try { await insanelyParanoidQuery(mysql2Con); } catch (err) { diff --git a/scripts/stress-test/reconnect-test-mysql-based-drivers.js b/scripts/stress-test/reconnect-test-mysql-based-drivers.js index b0816448c4..d2e6103879 100644 --- a/scripts/stress-test/reconnect-test-mysql-based-drivers.js +++ b/scripts/stress-test/reconnect-test-mysql-based-drivers.js @@ -2,7 +2,7 @@ * Test case for figuring out robust way to recognize if connection is dead * for mysql based drivers. */ -const Bluebird = require('bluebird'); +const delay = require('../../lib/util/delay'); const toxiproxy = require('toxiproxy-node-client'); const toxicli = new toxiproxy.Toxiproxy('http://localhost:8474'); const rp = require('request-promise-native'); @@ -158,7 +158,7 @@ async function main() { try { counts.queries += 1; // without this delay we endup to busy failure loop - await Bluebird.delay(0); + await delay(0); await query(); counts.results += 1; } catch (err) { @@ -175,7 +175,7 @@ async function main() { // wait forever while (true) { - await Bluebird.delay(1000); + await delay(1000); } } diff --git a/test/chai-setup.js b/test/chai-setup.js new file mode 100644 index 0000000000..a5b9b7edcd --- /dev/null +++ b/test/chai-setup.js @@ -0,0 +1,6 @@ +const chai = (global.chai = require('chai')); + +chai.should(); +chai.use(require('chai-as-promised')); + +global.expect = chai.expect; diff --git a/test/index.js b/test/index.js index 61afbb76ef..f465f800ec 100644 --- a/test/index.js +++ b/test/index.js @@ -4,18 +4,11 @@ require('source-map-support').install(); global.sinon = require('sinon'); +require('./chai-setup'); +global.chai.use(require('sinon-chai')); -const chai = (global.chai = require('chai')); - -chai.use(require('sinon-chai')); -chai.should(); - -const bluebird = require('bluebird'); -global.expect = chai.expect; global.d = new Date(); -bluebird.longStackTraces(); - // '.timeout(ms, {cancel: true}) should throw error if cancellation cannot acquire connection' produced unhandled rejection and it's unclear how to avoid that const EXPECTED_REJECTION_COUNT = 2; const rejectionLog = []; diff --git a/test/integration/builder/additional.js b/test/integration/builder/additional.js index ce9d68d758..d8f9d0e82c 100644 --- a/test/integration/builder/additional.js +++ b/test/integration/builder/additional.js @@ -4,7 +4,7 @@ const Knex = require('../../../knex'); const _ = require('lodash'); -const bluebird = require('bluebird'); +const delay = require('../../../lib/util/delay'); module.exports = function(knex) { describe('Additional', function() { @@ -192,44 +192,6 @@ module.exports = function(knex) { }); }); - it('should forward the .mapSeries() function from bluebird', function() { - const asyncTask = function() { - return new Promise(function(resolve, reject) { - const output = asyncTask.num++; - setTimeout(function() { - resolve(output); - }, Math.random() * 200); - }); - }; - asyncTask.num = 1; - - const returnedValues = []; - return knex('accounts') - .select() - .limit(3) - .mapSeries(function(account) { - return asyncTask().then(function(number) { - returnedValues.push(number); - }); - }) - .then(function() { - expect(returnedValues[0] == 1); - expect(returnedValues[1] == 2); - expect(returnedValues[2] == 3); - }); - }); - - it('should forward the .delay() function from bluebird', function() { - const startTime = new Date().valueOf(); - return knex('accounts') - .select() - .limit(1) - .delay(300) - .then(function(accounts) { - expect(new Date().valueOf() - startTime > 300); - }); - }); - it('should truncate a table with truncate', function() { return knex('test_table_two') .truncate() @@ -835,10 +797,7 @@ module.exports = function(knex) { // This query will hang if a connection gets released back to the pool // too early. // 50ms delay since killing query doesn't seem to have immediate effect to the process listing - return bluebird - .resolve() - .then() - .delay(50) + return delay(50) .then(function() { return getProcessesQuery; }) @@ -860,7 +819,7 @@ module.exports = function(knex) { }); }); - it('.timeout(ms, {cancel: true}) should throw error if cancellation cannot acquire connection', function() { + it('.timeout(ms, {cancel: true}) should throw error if cancellation cannot acquire connection', async () => { // Only mysql/postgres query cancelling supported for now const driverName = knex.client.driverName; if ( @@ -903,23 +862,21 @@ module.exports = function(knex) { const query = testQueries[driverName](); - return query - .timeout(1, { cancel: true }) - .then(function() { - throw new Error("Shouldn't have gotten here."); - }) - .catch(function(error) { - expect(_.pick(error, 'timeout', 'name', 'message')).to.deep.equal({ - timeout: 1, - name: 'KnexTimeoutError', - message: - 'After query timeout of 1ms exceeded, cancelling of query failed.', - }); - }) - .finally(() => knexDb.destroy()); + try { + await expect( + query.timeout(1, { cancel: true }) + ).to.eventually.be.rejected.and.to.deep.include({ + timeout: 1, + name: 'KnexTimeoutError', + message: + 'After query timeout of 1ms exceeded, cancelling of query failed.', + }); + } finally { + await knexDb.destroy(); + } }); - it('.timeout(ms, {cancel: true}) should release connections after failing if connection cancellation throws an error', function() { + it('.timeout(ms, {cancel: true}) should release connections after failing if connection cancellation throws an error', async function() { // Only mysql/postgres query cancelling supported for now const driverName = knex.client.driverName; if (!_.startsWith(driverName, 'pg')) { @@ -951,6 +908,7 @@ module.exports = function(knex) { const knexPrototype = Object.getPrototypeOf(knexDb.client); const originalWrappedCancelQueryCall = knexPrototype._wrappedCancelQueryCall; + knexPrototype._wrappedCancelQueryCall = (conn) => { return knexPrototype.query(conn, { method: 'raw', @@ -961,32 +919,27 @@ module.exports = function(knex) { const queryTimeout = 10; const secondQueryTimeout = 11; - return getTestQuery() - .timeout(queryTimeout, { cancel: true }) - .then(function() { - throw new Error("Shouldn't have gotten here."); - }) - .catch(function(error) { - expect(_.pick(error, 'timeout', 'name', 'message')).to.deep.equal({ - timeout: queryTimeout, - name: 'error', - message: `After query timeout of ${queryTimeout}ms exceeded, cancelling of query failed.`, - }); - }) - .then(() => { - knexPrototype._wrappedCancelQueryCall = originalWrappedCancelQueryCall; - return getTestQuery().timeout(secondQueryTimeout, { cancel: true }); - }) - .catch(function(error) { - expect(_.pick(error, 'timeout', 'name', 'message')).to.deep.equal({ - timeout: secondQueryTimeout, - name: 'KnexTimeoutError', - message: `Defined query timeout of ${secondQueryTimeout}ms exceeded when running query.`, - }); - }) - .finally(() => { - return knexDb.destroy(); + try { + await expect( + getTestQuery().timeout(queryTimeout, { cancel: true }) + ).to.be.eventually.rejected.and.deep.include({ + timeout: queryTimeout, + name: 'error', + message: `After query timeout of ${queryTimeout}ms exceeded, cancelling of query failed.`, }); + + knexPrototype._wrappedCancelQueryCall = originalWrappedCancelQueryCall; + + await expect( + getTestQuery().timeout(secondQueryTimeout, { cancel: true }) + ).to.be.eventually.rejected.and.deep.include({ + timeout: secondQueryTimeout, + name: 'KnexTimeoutError', + message: `Defined query timeout of ${secondQueryTimeout}ms exceeded when running query.`, + }); + } finally { + await knexDb.destroy(); + } }); it('Event: query-response', function() { diff --git a/test/integration/builder/inserts.js b/test/integration/builder/inserts.js index a95c5892ff..b6d02f783b 100644 --- a/test/integration/builder/inserts.js +++ b/test/integration/builder/inserts.js @@ -4,7 +4,6 @@ const uuid = require('uuid'); const _ = require('lodash'); -const bluebird = require('bluebird'); const sinon = require('sinon'); module.exports = function(knex) { @@ -1148,39 +1147,37 @@ module.exports = function(knex) { if (/redshift/i.test(knex.client.driverName)) { return; } + this.timeout(10000); + const fn = sinon.stub(); process.on('unhandledRejection', fn); - await new bluebird(function(resolve, reject) { - return knex.schema - .dropTableIfExists('batchInsertDuplicateKey') - .then(function() { - return knex.schema.createTable( - 'batchInsertDuplicateKey', - function(table) { - table.string('col'); - table.primary('col'); - } - ); - }) - .then(function() { - const rows = [{ col: 'a' }, { col: 'a' }]; - return knex.batchInsert( - 'batchInsertDuplicateKey', - rows, - rows.length - ); - }) - .then(function() { - return reject(new Error('Should not reach this point')); - }) - .catch(function(error) { - //Should reach this point before timeout of 10s - expect(error.message.toLowerCase()).to.include( - 'batchinsertduplicatekey' - ); - resolve(error); + await knex.schema + .dropTableIfExists('batchInsertDuplicateKey') + .then(function() { + return knex.schema.createTable('batchInsertDuplicateKey', function( + table + ) { + table.string('col'); + table.primary('col'); }); - }).timeout(10000); + }) + .then(function() { + const rows = [{ col: 'a' }, { col: 'a' }]; + return knex.batchInsert( + 'batchInsertDuplicateKey', + rows, + rows.length + ); + }) + .then(function() { + expect.fail('Should not reach this point'); + }) + .catch(function(error) { + //Should reach this point before timeout of 10s + expect(error.message.toLowerCase()).to.include( + 'batchinsertduplicatekey' + ); + }); expect(fn).have.not.been.called; process.removeListener('unhandledRejection', fn); }); diff --git a/test/integration/builder/transaction.js b/test/integration/builder/transaction.js index c5bae4ef02..acf70693ee 100644 --- a/test/integration/builder/transaction.js +++ b/test/integration/builder/transaction.js @@ -2,11 +2,11 @@ 'use strict'; -const Bluebird = require('bluebird'); const Knex = require('../../../knex'); const _ = require('lodash'); const sinon = require('sinon'); const { KnexTimeoutError } = require('../../../lib/util/timeout'); +const delay = require('../../../lib/util/delay'); module.exports = function(knex) { // Certain dialects do not have proper insert with returning, so if this is true @@ -255,7 +255,7 @@ module.exports = function(knex) { }); }); - it('should be able to run schema methods', function() { + it('should be able to run schema methods', async () => { let __knexUid, count = 0; const err = new Error('error message'); @@ -290,13 +290,14 @@ module.exports = function(knex) { expect(count).to.equal(5); return knex('test_schema_migrations').count('*'); }) + .then(() => expect.fail('should never reach this line')) .catch(function(e) { // https://www.postgresql.org/docs/8.2/static/errcodes-appendix.html expect(e.code).to.equal('42P01'); }); } else { let id = null; - return knex + const promise = knex .transaction(function(trx) { return trx('accounts') .returning('id') @@ -348,10 +349,13 @@ module.exports = function(knex) { if (!constid) { expect(resp).to.have.length(1); } - }) - .finally(function() { - return knex.schema.dropTableIfExists('test_schema_transactions'); }); + + try { + await promise; + } finally { + await knex.schema.dropTableIfExists('test_schema_transactions'); + } } }); @@ -397,8 +401,8 @@ module.exports = function(knex) { if (/mssql/i.test(knex.client.driverName)) { return Promise.resolve(); } - const first = Bluebird.delay(50); - const second = first.then(() => Bluebird.delay(50)); + const first = delay(50); + const second = first.then(() => delay(50)); return knex.transaction(function(trx) { return Promise.all([ trx.transaction(function(trx2) { @@ -479,7 +483,7 @@ module.exports = function(knex) { }); }); - it('#1694, #1703 it should return connections to pool if acquireConnectionTimeout is triggered', function() { + it('#1694, #1703 it should return connections to pool if acquireConnectionTimeout is triggered', async function() { const knexConfig = _.clone(knex.client.config); knexConfig.pool = { min: 0, @@ -489,14 +493,12 @@ module.exports = function(knex) { const db = new Knex(knexConfig); - return db - .transaction(function() { - return db.transaction(function() {}); - }) - .then(function() { - throw new Error('should not get here'); - }) - .catch(KnexTimeoutError, function(error) {}); + try { + await db.transaction(() => db.transaction(() => ({}))); + expect.fail('should not get here'); + } catch (error) { + expect(error).to.be.an.instanceof(KnexTimeoutError); + } }); /** diff --git a/test/integration/migrate/index.js b/test/integration/migrate/index.js index 496fbebdbb..086a0783f7 100644 --- a/test/integration/migrate/index.js +++ b/test/integration/migrate/index.js @@ -5,10 +5,10 @@ const equal = require('assert').equal; const fs = require('fs'); const path = require('path'); const rimraf = require('rimraf'); -const Bluebird = require('bluebird'); const knexLib = require('../../../knex'); const logger = require('../logger'); const config = require('../../knexfile'); +const delay = require('../../../lib/util/delay'); const _ = require('lodash'); const testMemoryMigrations = require('./memory-migrations'); @@ -23,30 +23,25 @@ module.exports = function(knex) { }); describe('knex.migrate', function() { - it('should not fail on null default for timestamp', () => { - return knex.schema - .dropTableIfExists('null_date') - .then(() => { - return knex.migrate.latest({ - directory: 'test/integration/migrate/null_timestamp_default', - }); - }) - .then(() => { - return knex.into('null_date').insert({ - dummy: 'cannot insert empty object', - }); - }) - .then(() => { - return knex('null_date').first(); - }) - .then((rows) => { - expect(rows.deleted_at).to.equal(null); - }) - .finally(() => { - return knex.migrate.rollback({ - directory: 'test/integration/migrate/null_timestamp_default', - }); + it('should not fail on null default for timestamp', async () => { + try { + await knex.schema.dropTableIfExists('null_date'); + + await knex.migrate.latest({ + directory: 'test/integration/migrate/null_timestamp_default', + }); + + await knex.into('null_date').insert({ + dummy: 'cannot insert empty object', + }); + + const rows = await knex('null_date').first(); + expect(rows.deleted_at).to.equal(null); + } finally { + await knex.migrate.rollback({ + directory: 'test/integration/migrate/null_timestamp_default', }); + } }); it('should not fail drop-and-recreate-column operation when using promise chain', () => { @@ -193,7 +188,7 @@ module.exports = function(knex) { }); it('should return a positive number if the DB is ahead', function() { - return Bluebird.all([ + return Promise.all([ knex('knex_migrations') .returning('id') .insert({ @@ -307,8 +302,12 @@ module.exports = function(knex) { // Save the promise, then wait a short time to ensure it's had time // to start its query and get blocked. const trx2Promise = migrator._lockMigrations(trx2); - await Bluebird.delay(100); - if (!trx2Promise.isPending()) { + await delay(100); + const isPending = await Promise.race([ + delay(10).then(() => true), + trx2Promise.catch(() => {}).then(() => false), + ]); + if (!isPending) { throw new Error('expected trx2 to be pending'); } await trx1.commit(); @@ -1033,23 +1032,22 @@ module.exports = function(knex) { }); describe('knex.migrate.latest with disableValidateMigrationList', function() { - it('should not fail if there is a missing migration', () => { - return knex.migrate - .latest({ + it('should not fail if there is a missing migration', async () => { + try { + await knex.migrate.latest({ + directory: 'test/integration/migrate/test', + }); + + await knex.migrate.latest({ + directory: + 'test/integration/migrate/test_with_missing_first_migration', + disableMigrationsListValidation: true, + }); + } finally { + await knex.migrate.rollback({ directory: 'test/integration/migrate/test', - }) - .then(() => { - return knex.migrate.latest({ - directory: - 'test/integration/migrate/test_with_missing_first_migration', - disableMigrationsListValidation: true, - }); - }) - .finally(() => { - return knex.migrate.rollback({ - directory: 'test/integration/migrate/test', - }); }); + } }); }); }; diff --git a/test/tape/crossdb-compatibility.js b/test/tape/crossdb-compatibility.js index 1b36fbbfb3..74fda2b078 100644 --- a/test/tape/crossdb-compatibility.js +++ b/test/tape/crossdb-compatibility.js @@ -1,5 +1,6 @@ 'use strict'; const tape = require('tape'); +const { expect } = require('chai'); /** * Collection of tests for making sure that certain features are cross database compatible @@ -12,97 +13,99 @@ module.exports = function(knex) { return; } - tape(driverName + ' - 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() { + tape( + driverName + ' - crossdb compatibility: setup test table', + async function(t) { + try { + await 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 { t.end(); - }); - }); + } + } + ); tape( driverName + ' - crossdb compatibility: table may have multiple nulls in unique constrainted column', - function(t) { + async (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([ + try { + await expect( + knex('test_table').insert([{ third: 'foo' }, { third: 'foo' }]) + ).to.be.eventually.rejected; + t.assert(true, 'unique constraint prevents adding rows'); + + await expect( + knex('test_table').insert([ { first: 'foo2', second: 'bar2' }, { first: 'foo2', second: 'bar2' }, - ]); - }) - .catch((err) => { - t.assert(true, 'two column unique constraint prevents adding rows'); + ]) + ).to.be.eventually.rejected; - // 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(); - }); + t.assert(true, 'two column unique constraint prevents adding rows'); + + // even one null makes index to not match, thus allows adding the row + await 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 }, + ]); + + const res = await knex('test_table'); + t.assert( + res.length === 5, + 'multiple rows with nulls could be added despite of unique constraints' + ); + } finally { + t.end(); + } } ); tape( driverName + ' - create and drop index works in different cases', - (t) => { + async (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(); - }); + try { + await 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'); + }); + t.assert( + true, + 'Creating / dropping / creating unique constraint was a success' + ); + } finally { + t.end(); + } } ); }; diff --git a/test/tape/harness.js b/test/tape/harness.js index 33ca44c2e9..feafe9e759 100644 --- a/test/tape/harness.js +++ b/test/tape/harness.js @@ -1,6 +1,5 @@ 'use strict'; const tape = require('tape'); -const Bluebird = require('bluebird'); const debug = require('debug')('knex:tests'); module.exports = function(tableName, knex) { @@ -17,24 +16,20 @@ module.exports = function(tableName, knex) { } } - return tape(name, function(t) { - const disposable = Bluebird.resolve(true).disposer(function() { - return knex.truncate(tableName).finally(function() { - t.end(); - }); - }); - - Bluebird.using(disposable, function() { - const val = cb(t); - if (val && typeof val.then === 'function') { - return val.catch(function(err) { - t.error(err); - }); - } else { - t.error(new Error('A promise should be returned to test ' + name)); - t.end(); + return tape(name, async function(t) { + const val = cb(t); + try { + if (!val || typeof val.then !== 'function') { + throw new Error('A promise should be returned to test ' + name); } - }); + + await val; + } catch (err) { + t.error(err); + } finally { + await knex.truncate(tableName).catch((e) => t.fail(e)); + t.end(); + } }); }; }; diff --git a/test/tape/index.js b/test/tape/index.js index aced900289..d3a6710863 100644 --- a/test/tape/index.js +++ b/test/tape/index.js @@ -1,6 +1,7 @@ /*eslint no-var:0*/ 'use strict'; // var wtf = require('wtfnode'); +require('../chai-setup'); var tape = require('tape'); var makeKnex = require('../../knex'); var knexfile = require('../knexfile'); diff --git a/test/tape/invalid-db-setup.js b/test/tape/invalid-db-setup.js index e15826390d..6585f1f21c 100644 --- a/test/tape/invalid-db-setup.js +++ b/test/tape/invalid-db-setup.js @@ -22,17 +22,18 @@ module.exports = (knexfile) => { const knex = makeKnex(knexConf); tape(dialect + ' - propagate error when DB does not exist', (t) => { - t.plan(1); + t.plan(2); t.timeoutAfter(1000); knex('accounts') .select(1) .then((res) => { t.fail(`Query should have failed, got: ${JSON.stringify(res)}`); }) - .catch(KnexTimeoutError, (e) => { - t.fail(`Query should have failed with non timeout error`); - }) .catch((e) => { + t.notOk( + e instanceof KnexTimeoutError, + `Query should have failed with non timeout error` + ); t.ok( e.message.indexOf('i-refuse-to-exist') > 0, `all good, failed as expected with msg: ${e.message}` @@ -43,7 +44,7 @@ module.exports = (knexfile) => { tape( dialect + ' - propagate error when DB does not exist for stream', (t) => { - t.plan(1); + t.plan(2); t.timeoutAfter(1000); knex @@ -54,12 +55,13 @@ module.exports = (knexfile) => { `Stream query should have failed, got: ${JSON.stringify(res)}` ); }) - .catch(KnexTimeoutError, (e) => { - t.fail(`Stream query should have failed with non timeout error`); - }) .catch((e) => { + t.notOk( + e instanceof KnexTimeoutError, + 'Stream query should have failed with non timeout error' + ); t.ok( - e.message.indexOf('i-refuse-to-exist') > 0, + e.message.includes('i-refuse-to-exist'), `all good, failed as expected with msg: ${e.message}` ); }); @@ -81,7 +83,7 @@ module.exports = (knexfile) => { knex.destroy(); }); - tape(dialect + ' - acquireConnectionTimeout works', (t) => { + tape(dialect + ' - acquireConnectionTimeout works', async (t) => { if (dialect === 'oracledb') { t.skip( '!!!!!!! acquireConnectionTimeout fails with oracledb! please fix. !!!!!!!!' @@ -94,32 +96,19 @@ module.exports = (knexfile) => { t.timeoutAfter(1000); // just hog the only connection. - knex - .transaction((trx) => { - // Don't return this promise! Also note that we use `knex` instead of `trx` - // here on purpose. The only reason this code is here, is that we can be - // certain `trx` has been created before this. - knex('accounts') - .select(1) - .then(() => { - t.fail('query should have stalled'); - }) - .catch(KnexTimeoutError, (e) => { - t.pass('Got acquireTimeout error'); - }) - .catch((e) => { - t.fail( - `should have got acquire timeout error, but got ${e.message} instead.` - ); - }) - .finally(() => { - trx.commit(); // release stuff - }); - }) - .then(() => { - t.pass('transaction was resolved'); - t.end(); - }); + const trx = await knex.transaction(); + + try { + await knex('accounts').select(1); + + t.fail('query should have stalled'); + } catch (e) { + t.ok(e instanceof KnexTimeoutError, 'Got acquireTimeout error'); + } finally { + trx.commit(); + } + t.pass('transaction was resolved'); + t.end(); }); } }); diff --git a/test/tape/pool.js b/test/tape/pool.js index 6e6935fbe4..7ca78eb167 100644 --- a/test/tape/pool.js +++ b/test/tape/pool.js @@ -6,9 +6,9 @@ const tarn = require('tarn'); const Pool = tarn.Pool; const knexfile = require('../knexfile'); const makeKnex = require('../../knex'); -const Bluebird = require('bluebird'); +const delay = require('../../lib/util/delay'); -test(`pool evicts dead resources when factory.validate rejects`, (t) => { +test(`pool evicts dead resources when factory.validate rejects`, async (t) => { t.plan(10); let i = 0; @@ -30,51 +30,37 @@ test(`pool evicts dead resources when factory.validate rejects`, (t) => { return true; }, }); - - Bluebird.resolve(Array.from(Array(5))) - .map(() => { - return pool.acquire().promise.catch((e) => { - t.fail('1# Could not get resource from pool'); - }); - }) - .map((con) => { - pool.release(con); - return con; - }) - .map((con) => { - // fake kill connections - con.error = 'connection lost'; - return con; - }) - .map((con) => { - return pool - .acquire() - .promise.then((con) => { - t.ok(con.id > 4, 'old dead connections were not reused'); - return con; + const fiveElements = Array.from(Array(5)); + const allocateConnections = () => + Promise.all( + fiveElements.map(() => + pool.acquire().promise.catch((e) => { + t.fail('Could not get resource from pool'); }) - .catch((e) => { - t.fail('2# Could not get resource from pool'); - }); - }) - .map((con) => { - pool.release(con); - return con; - }) - .map((con) => { - return pool.acquire().promise.then((con) => { - t.ok(con.id > 4 && con.id < 11, 'Released working connection was used'); - return con; - }); - }) - .map((con) => { - pool.release(con); - return con; - }) - .then(() => pool.destroy()) - .then(() => { - t.end(); - }); + ) + ); + + const connectionsGroup1 = await allocateConnections(); + connectionsGroup1.forEach((con) => pool.release(con)); + connectionsGroup1.forEach((con) => { + // fake kill connections + con.error = 'connection lost'; + }); + + const connectionsGroup2 = await allocateConnections(); + connectionsGroup2.forEach((con) => + t.ok(con.id > 4, 'old dead connections were not reused') + ); + connectionsGroup2.forEach((con) => pool.release(con)); + + const connectionsGroup3 = await allocateConnections(); + connectionsGroup3.forEach((con) => + t.ok(con.id > 4 && con.id < 11, 'Released working connection was used') + ); + connectionsGroup3.forEach((con) => pool.release(con)); + + await pool.destroy(); + t.end(); }); test('#822, pool config, max: 0 should skip pool construction', function(t) { @@ -101,39 +87,36 @@ test('#823, should not skip pool construction pool config is not defined', funct } }); -test('#2321 dead connections are not evicted from pool', (t) => { +test('#2321 dead connections are not evicted from pool', async (t) => { if (knexfile['mysql2']) { const knex = makeKnex(knexfile['mysql2']); t.plan(10); - Bluebird.all( - Array.from(Array(30)).map(() => { - // kill all connections in pool - return knex.raw(`KILL connection_id()`).catch(() => { - // just ignore errors - }); - }) - ) - .delay(50) // wait driver to notice connection errors (2ms was enough locally) - .then(() => { - // all connections are dead, so they should be evicted from pool and this should work - return Bluebird.all( - Array.from(Array(10)).map(() => - knex.select(1).then(() => t.pass('Read data')) - ) - ); - }) - .catch((e) => { - t.fail( - `Should have created new connection and execute the query, got : ${e}` - ); - }) - .then(() => { - t.end(); - }) - .finally(() => { - return knex.destroy(); - }); + try { + await Promise.all( + Array.from(Array(30)).map(() => { + // kill all connections in pool + return knex.raw(`KILL connection_id()`).catch(() => { + // just ignore errors + }); + }) + ); + await delay(50); // wait driver to notice connection errors (2ms was enough locally) + + // all connections are dead, so they should be evicted from pool and this should work + await Promise.all( + Array.from(Array(10)).map(() => + knex.select(1).then(() => t.pass('Read data')) + ) + ); + } catch (e) { + t.fail( + `Should have created new connection and execute the query, got : ${e}` + ); + } finally { + t.end(); + await knex.destroy(); + } } else { t.end(); } diff --git a/test/tape/transactions.js b/test/tape/transactions.js index 78df3ff88f..4118e06962 100644 --- a/test/tape/transactions.js +++ b/test/tape/transactions.js @@ -18,230 +18,221 @@ module.exports = function(knex) { const test = harness('test_table', knex); - test('transaction', function(t) { - return knex - .transaction(function(trx) { - return trx.insert({ id: 1, name: 'A' }).into('test_table'); - }) - .then(function() { - return knex - .select('*') - .from('test_table') - .then(function(results) { - t.equal(results.length, 1, 'One row inserted'); - }); - }); + test('transaction', async function(t) { + await knex.transaction((trx) => + trx.insert({ id: 1, name: 'A' }).into('test_table') + ); + + const results = await knex.select('*').from('test_table'); + + t.equal(results.length, 1, 'One row inserted'); }); - test('transaction rollback on returned rejected promise', function(t) { + test('transaction rollback on returned rejected promise', async function(t) { const testError = new Error('Not inserting'); let trxQueryCount = 0; let trxRejected; - return knex - .transaction(function(trx) { - return trx - .insert({ id: 1, name: 'A' }) - .into('test_table') - .then(function() { - throw testError; - }); - }) - .on('query', function() { - ++trxQueryCount; - }) - .catch(function(err) { - t.equal(err, testError, 'Expected error reported'); - trxRejected = true; - }) - .finally(function() { - // BEGIN, INSERT, ROLLBACK - // oracle & mssql: BEGIN & ROLLBACK not reported as queries - const expectedQueryCount = - knex.client.driverName === 'oracledb' || - knex.client.driverName === 'mssql' - ? 1 - : 3; - t.equal( - trxQueryCount, - expectedQueryCount, - 'Expected number of transaction SQL queries executed' - ); - t.equal(trxRejected, true, 'Transaction promise rejected'); - return knex - .select('*') - .from('test_table') - .then(function(results) { - t.equal(results.length, 0, 'No rows inserted'); - }); - }); + try { + await knex + .transaction(function(trx) { + return trx + .insert({ id: 1, name: 'A' }) + .into('test_table') + .then(function() { + throw testError; + }); + }) + .on('query', function() { + ++trxQueryCount; + }); + } catch (err) { + t.equal(err, testError, 'Expected error reported'); + trxRejected = true; + } finally { + // BEGIN, INSERT, ROLLBACK + // oracle & mssql: BEGIN & ROLLBACK not reported as queries + const expectedQueryCount = + knex.client.driverName === 'oracledb' || + knex.client.driverName === 'mssql' + ? 1 + : 3; + + t.equal( + trxQueryCount, + expectedQueryCount, + 'Expected number of transaction SQL queries executed' + ); + t.equal(trxRejected, true, 'Transaction promise rejected'); + + const results = await knex.select('*').from('test_table'); + t.equal(results.length, 0, 'No rows inserted'); + } }); - test('transaction rollback on error throw', function(t) { + test('transaction rollback on error throw', async function(t) { const testError = new Error('Boo!!!'); let trxQueryCount = 0; let trxRejected; - return knex - .transaction(function() { - throw testError; - }) - .on('query', function() { - ++trxQueryCount; - }) - .catch(function(err) { - t.equal(err, testError, 'Expected error reported'); - trxRejected = true; - }) - .finally(function() { - // BEGIN, ROLLBACK - // oracle & mssql: BEGIN & ROLLBACK not reported as queries - const expectedQueryCount = - knex.client.driverName === 'oracledb' || - knex.client.driverName === 'mssql' - ? 0 - : 2; - t.equal( - trxQueryCount, - expectedQueryCount, - 'Expected number of transaction SQL queries executed' - ); - t.equal(trxRejected, true, 'Transaction promise rejected'); - }); + try { + await knex + .transaction(function() { + throw testError; + }) + .on('query', function() { + ++trxQueryCount; + }); + } catch (err) { + t.equal(err, testError, 'Expected error reported'); + trxRejected = true; + } finally { + // BEGIN, ROLLBACK + // oracle & mssql: BEGIN & ROLLBACK not reported as queries + const expectedQueryCount = + knex.client.driverName === 'oracledb' || + knex.client.driverName === 'mssql' + ? 0 + : 2; + + t.equal( + trxQueryCount, + expectedQueryCount, + 'Expected number of transaction SQL queries executed' + ); + t.equal(trxRejected, true, 'Transaction promise rejected'); + } }); - test('transaction savepoint rollback on returned rejected promise', function(t) { + test('transaction savepoint rollback on returned rejected promise', async function(t) { const testError = new Error('Rolling Back Savepoint'); let trx1QueryCount = 0; let trx2QueryCount = 0; - let trx2Rejected; - return knex - .transaction(function(trx1) { - return trx1 - .insert({ id: 1, name: 'A' }) - .into('test_table') - .then(function() { - // Nested transaction (savepoint) - return trx1 - .transaction(function(trx2) { - // Insert and then roll back to savepoint - return trx2 - .table('test_table') - .insert({ id: 2, name: 'B' }) - .then(function() { - return trx2('test_table') - .then(function(results) { - t.equal(results.length, 2, 'Two rows inserted'); - }) - .throw(testError); - }); - }) - .on('query', function() { - ++trx2QueryCount; - }); - }) - .catch(function(err) { - t.equal(err, testError, 'Expected error reported'); - trx2Rejected = true; - }); - }) - .on('query', function() { - ++trx1QueryCount; - }) - .finally(function() { - // trx1: BEGIN, INSERT, ROLLBACK - // trx2: SAVEPOINT, INSERT, SELECT, ROLLBACK TO SAVEPOINT - // oracle & mssql: BEGIN & ROLLBACK not reported as queries - let expectedTrx1QueryCount = - knex.client.driverName === 'oracledb' || - knex.client.driverName === 'mssql' - ? 1 - : 3; - const expectedTrx2QueryCount = 4; - expectedTrx1QueryCount += expectedTrx2QueryCount; - t.equal( - trx1QueryCount, - expectedTrx1QueryCount, - 'Expected number of parent transaction SQL queries executed' - ); - t.equal( - trx2QueryCount, - expectedTrx2QueryCount, - 'Expected number of nested transaction SQL queries executed' - ); - t.equal(trx2Rejected, true, 'Nested transaction promise rejected'); - return knex - .select('*') - .from('test_table') - .then(function(results) { - t.equal(results.length, 1, 'One row inserted'); - }); - }); + let trx2Rejected = false; + try { + await knex + .transaction(function(trx1) { + return trx1 + .insert({ id: 1, name: 'A' }) + .into('test_table') + .then(function() { + // Nested transaction (savepoint) + return trx1 + .transaction(function(trx2) { + // Insert and then roll back to savepoint + return trx2 + .table('test_table') + .insert({ id: 2, name: 'B' }) + .then(function() { + return trx2('test_table') + .then(function(results) { + t.equal(results.length, 2, 'Two rows inserted'); + }) + .then(() => { + throw testError; + }); + }); + }) + .on('query', function() { + ++trx2QueryCount; + }); + }) + .catch(function(err) { + t.equal(err, testError, 'Expected error reported'); + trx2Rejected = true; + }); + }) + .on('query', function() { + ++trx1QueryCount; + }); + } finally { + // trx1: BEGIN, INSERT, ROLLBACK + // trx2: SAVEPOINT, INSERT, SELECT, ROLLBACK TO SAVEPOINT + // oracle & mssql: BEGIN & ROLLBACK not reported as queries + let expectedTrx1QueryCount = + knex.client.driverName === 'oracledb' || + knex.client.driverName === 'mssql' + ? 1 + : 3; + const expectedTrx2QueryCount = 4; + expectedTrx1QueryCount += expectedTrx2QueryCount; + t.equal( + trx1QueryCount, + expectedTrx1QueryCount, + 'Expected number of parent transaction SQL queries executed' + ); + t.equal( + trx2QueryCount, + expectedTrx2QueryCount, + 'Expected number of nested transaction SQL queries executed' + ); + t.equal(trx2Rejected, true, 'Nested transaction promise rejected'); + + const results = await knex.select('*').from('test_table'); + t.equal(results.length, 1, 'One row inserted'); + } }); - test('transaction savepoint rollback on error throw', function(t) { + test('transaction savepoint rollback on error throw', async function(t) { const testError = new Error('Rolling Back Savepoint'); let trx1QueryCount = 0; let trx2QueryCount = 0; - let trx2Rejected; - return knex - .transaction(function(trx1) { - return trx1 - .insert({ id: 1, name: 'A' }) - .into('test_table') - .then(function() { - // Nested transaction (savepoint) - return trx1 - .transaction(function() { - // trx2 - // Roll back to savepoint - throw testError; - }) - .on('query', function() { - ++trx2QueryCount; - }); - }) - .catch(function(err) { - t.equal(err, testError, 'Expected error reported'); - trx2Rejected = true; - }); - }) - .on('query', function() { - ++trx1QueryCount; - }) - .finally(function() { - // trx1: BEGIN, INSERT, ROLLBACK - // trx2: SAVEPOINT, ROLLBACK TO SAVEPOINT - // oracle & mssql: BEGIN & ROLLBACK not reported as queries - let expectedTrx1QueryCount = - knex.client.driverName === 'oracledb' || - knex.client.driverName === 'mssql' - ? 1 - : 3; - const expectedTrx2QueryCount = 2; - expectedTrx1QueryCount += expectedTrx2QueryCount; - t.equal( - trx1QueryCount, - expectedTrx1QueryCount, - 'Expected number of parent transaction SQL queries executed' - ); - t.equal( - trx2QueryCount, - expectedTrx2QueryCount, - 'Expected number of nested transaction SQL queries executed' - ); - t.equal(trx2Rejected, true, 'Nested transaction promise rejected'); - return knex - .select('*') - .from('test_table') - .then(function(results) { - t.equal(results.length, 1, 'One row inserted'); - }); - }); + let trx2Rejected = false; + try { + await knex + .transaction(function(trx1) { + return trx1 + .insert({ id: 1, name: 'A' }) + .into('test_table') + .then(function() { + // Nested transaction (savepoint) + return trx1 + .transaction(function() { + // trx2 + // Roll back to savepoint + throw testError; + }) + .on('query', function() { + ++trx2QueryCount; + }); + }) + .catch(function(err) { + t.equal(err, testError, 'Expected error reported'); + trx2Rejected = true; + }); + }) + .on('query', function() { + ++trx1QueryCount; + }); + } finally { + // trx1: BEGIN, INSERT, ROLLBACK + // trx2: SAVEPOINT, ROLLBACK TO SAVEPOINT + // oracle & mssql: BEGIN & ROLLBACK not reported as queries + let expectedTrx1QueryCount = + knex.client.driverName === 'oracledb' || + knex.client.driverName === 'mssql' + ? 1 + : 3; + const expectedTrx2QueryCount = 2; + expectedTrx1QueryCount += expectedTrx2QueryCount; + t.equal( + trx1QueryCount, + expectedTrx1QueryCount, + 'Expected number of parent transaction SQL queries executed' + ); + t.equal( + trx2QueryCount, + expectedTrx2QueryCount, + 'Expected number of nested transaction SQL queries executed' + ); + t.equal(trx2Rejected, true, 'Nested transaction promise rejected'); + const results = await knex.select('*').from('test_table'); + t.equal(results.length, 1, 'One row inserted'); + } }); - test('sibling nested transactions - second created after first one commits', function(t) { + test('sibling nested transactions - second created after first one commits', async function(t) { let secondTransactionCompleted = false; - return knex - .transaction(function(trx) { + try { + await knex.transaction(function(trx) { return trx .transaction(function(trx1) { return trx1 @@ -263,19 +254,19 @@ module.exports = function(knex) { }); }); }); - }) - .finally(function() { - t.equal( - secondTransactionCompleted, - true, - 'Second sibling transaction completed' - ); }); + } finally { + t.equal( + secondTransactionCompleted, + true, + 'Second sibling transaction completed' + ); + } }); - test('sibling nested transactions - both chained sibling transactions committed', function(t) { - return knex - .transaction(function(trx) { + test('sibling nested transactions - both chained sibling transactions committed', async function(t) { + try { + await knex.transaction(function(trx) { return trx .transaction(function(trx1) { return trx1.insert({ id: 1, name: 'A' }).into('test_table'); @@ -285,18 +276,17 @@ module.exports = function(knex) { return trx2.insert({ id: 2, name: 'B' }).into('test_table'); }); }); - }) - .finally(function() { - return knex('test_table').then(function(results) { - t.equal(results.length, 2, 'Parent transaction inserted 2 records'); - }); }); + } finally { + const results = await knex('test_table'); + t.equal(results.length, 2, 'Parent transaction inserted 2 records'); + } }); - test('sibling nested transactions - second created after first one rolls back by returning a rejected promise', function(t) { + test('sibling nested transactions - second created after first one rolls back by returning a rejected promise', async function(t) { let secondTransactionCompleted = false; - return knex - .transaction(function(trx) { + try { + await knex.transaction(function(trx) { return trx .transaction(function(trx1) { return trx1 @@ -318,27 +308,24 @@ module.exports = function(knex) { }); }); }); - }) - .finally(function() { - t.equal( - secondTransactionCompleted, - true, - 'Second sibling transaction completed' - ); }); + } finally { + t.equal( + secondTransactionCompleted, + true, + 'Second sibling transaction completed' + ); + } }); - test('sibling nested transactions - second commits data after first one rolls back by returning a rejected promise', function(t) { - return knex - .transaction(function(trx) { + test('sibling nested transactions - second commits data after first one rolls back by returning a rejected promise', async (t) => { + try { + await knex.transaction(function(trx) { return trx - .transaction(function(trx1) { - return trx1 - .insert({ id: 1, name: 'A' }) - .into('test_table') - .then(function() { - throw new Error('test rollback'); - }); + .transaction(async function(trx1) { + await trx1.insert({ id: 1, name: 'A' }).into('test_table'); + + throw new Error('test rollback'); }) .catch(function(err) { t.equal( @@ -352,18 +339,17 @@ module.exports = function(knex) { .into('test_table'); }); }); - }) - .finally(function() { - return knex('test_table').then(function(results) { - t.equal(results.length, 2, 'Parent transaction inserted two records'); - }); }); + } finally { + const results = await knex('test_table'); + t.equal(results.length, 2, 'Parent transaction inserted two records'); + } }); - test('sibling nested transactions - second created after first one rolls back by throwing', function(t) { + test('sibling nested transactions - second created after first one rolls back by throwing', async function(t) { let secondTransactionCompleted = false; - return knex - .transaction(function(trx) { + try { + await knex.transaction(function(trx) { return trx .transaction(function() { throw new Error('test rollback'); @@ -380,19 +366,19 @@ module.exports = function(knex) { }); }); }); - }) - .finally(function() { - t.equal( - secondTransactionCompleted, - true, - 'Second sibling transaction completed' - ); }); + } finally { + t.equal( + secondTransactionCompleted, + true, + 'Second sibling transaction completed' + ); + } }); - test('sibling nested transactions - second commits data after first one rolls back by throwing', function(t) { - return knex - .transaction(function(trx) { + test('sibling nested transactions - second commits data after first one rolls back by throwing', async function(t) { + try { + await knex.transaction(function(trx) { return trx .transaction(function() { throw new Error('test rollback'); @@ -407,18 +393,17 @@ module.exports = function(knex) { return trx2.insert([{ id: 1, name: 'A' }]).into('test_table'); }); }); - }) - .finally(function() { - return knex('test_table').then(function(results) { - t.equal(results.length, 1, 'Parent transaction inserted one record'); - }); }); + } finally { + const results = await knex('test_table'); + t.equal(results.length, 1, 'Parent transaction inserted one record'); + } }); - test('sibling nested transactions - first commits data even though second one rolls back by returning a rejected promise', function(t) { + test('sibling nested transactions - first commits data even though second one rolls back by returning a rejected promise', async (t) => { let secondTransactionCompleted = false; - return knex - .transaction(function(trx) { + try { + await knex.transaction(function(trx) { return trx .transaction(function(trx1) { return trx1.insert({ id: 1, name: 'A' }).into('test_table'); @@ -436,23 +421,22 @@ module.exports = function(knex) { }) .catch(function() {}); }); - }) - .finally(function() { - t.equal( - secondTransactionCompleted, - true, - 'Second sibling transaction completed' - ); - return knex('test_table').then(function(results) { - t.equal(results.length, 1, 'Parent transaction inserted one record'); - }); }); + } finally { + t.equal( + secondTransactionCompleted, + true, + 'Second sibling transaction completed' + ); + const results = await knex('test_table'); + t.equal(results.length, 1, 'Parent transaction inserted one record'); + } }); - test('sibling nested transactions - first commits data even though second one rolls back by throwing', function(t) { + test('sibling nested transactions - first commits data even though second one rolls back by throwing', async (t) => { let secondTransactionCompleted = false; - return knex - .transaction(function(trx) { + try { + await knex.transaction(function(trx) { return trx .transaction(function(trx1) { return trx1.insert({ id: 1, name: 'A' }).into('test_table'); @@ -465,20 +449,19 @@ module.exports = function(knex) { }) .catch(function() {}); }); - }) - .finally(function() { - t.equal( - secondTransactionCompleted, - true, - 'Second sibling transaction completed' - ); - return knex('test_table').then(function(results) { - t.equal(results.length, 1, 'Parent transaction inserted one record'); - }); }); + } finally { + t.equal( + secondTransactionCompleted, + true, + 'Second sibling transaction completed' + ); + const results = await knex('test_table'); + t.equal(results.length, 1, 'Parent transaction inserted one record'); + } }); - test('#625 - streams/transactions', 'postgresql', function(t) { + test('#625 - streams/transactions', 'postgresql', (t) => { let cid, queryCount = 0; @@ -515,55 +498,55 @@ module.exports = function(knex) { }); }); - test('#785 - skipping extra transaction statements after commit / rollback', function(t) { + test('#785 - skipping extra transaction statements after commit / rollback', async function(t) { let queryCount = 0; - return knex - .transaction(function(trx) { - knex('test_table') - .transacting(trx) - .insert({ name: 'Inserted before rollback called.' }) - .then(function() { - trx.rollback(new Error('Rolled back')); - }) - .then(function() { - return knex('test_table') - .transacting(trx) - .insert({ name: 'Inserted after rollback called.' }) - .then(function(resp) { - t.error(resp); - }) - .catch(function() {}); - }); - }) - .on('query', function() { - queryCount++; - }) - .catch(function(err) { - t.equal( - err.message, - 'Rolled back', - 'Transaction promise rejected with expected error' - ); - }) - .finally(function() { - // oracle & mssql: BEGIN & ROLLBACK not reported as queries - const expectedQueryCount = - knex.client.driverName === 'oracledb' || - knex.client.driverName === 'mssql' - ? 1 - : 3; - t.equal( - queryCount, - expectedQueryCount, - 'Expected number of transaction SQL queries executed' - ); - }); + try { + await knex + .transaction(function(trx) { + knex('test_table') + .transacting(trx) + .insert({ name: 'Inserted before rollback called.' }) + .then(function() { + trx.rollback(new Error('Rolled back')); + }) + .then(function() { + return knex('test_table') + .transacting(trx) + .insert({ name: 'Inserted after rollback called.' }) + .then(function(resp) { + t.error(resp); + }) + .catch(function() {}); + }); + }) + .on('query', function() { + queryCount++; + }); + } catch (err) { + t.equal( + err.message, + 'Rolled back', + 'Transaction promise rejected with expected error' + ); + } finally { + // oracle & mssql: BEGIN & ROLLBACK not reported as queries + const expectedQueryCount = + knex.client.driverName === 'oracledb' || + knex.client.driverName === 'mssql' + ? 1 + : 3; + t.equal( + queryCount, + expectedQueryCount, + 'Expected number of transaction SQL queries executed' + ); + } }); - test('#805 - nested ddl transaction', function() { - return knex - .transaction(function(knex) { + test('#805 - nested ddl transaction', async function() { + try { + await knex.transaction(function(knex) { return knex.transaction(function(trx) { return trx.schema.createTable('ages', function(t) { t.increments('id').primary(); @@ -572,91 +555,86 @@ module.exports = function(knex) { .notNull(); }); }); - }) - .finally(function() { - return knex.schema.dropTableIfExists('ages'); }); + } finally { + await knex.schema.dropTableIfExists('ages'); + } }); if (knex.client.driverName === 'pg') { // TODO: fix to work without old tables from mocha tests tape( 'allows postgres ? operator in knex.raw() if no bindings given #519 and #888', - function(t) { + async function(t) { t.plan(1); - knex - .from('test_table_two') - .whereRaw("(json_data->'me')::jsonb \\?& array['keyOne', 'keyTwo']") - .where('id', '>', 1) - .then(function(result) { - t.equal(result.length, 0, 'Table should have been empty'); - return result; - }) - .finally(function() { - t.end(); - }); + try { + const result = await knex + .from('test_table_two') + .whereRaw("(json_data->'me')::jsonb \\?& array['keyOne', 'keyTwo']") + .where('id', '>', 1); + t.equal(result.length, 0, 'Table should have been empty'); + } finally { + t.end(); + } } ); } - test('transaction savepoint do not rollback when instructed', function(t) { + test('transaction savepoint do not rollback when instructed', async function(t) { let trx1QueryCount = 0; let trx2QueryCount = 0; - let trx2Rejected; + let trx2Rejected = false; - return knex - .transaction(function(trx1) { - return trx1 - .insert({ id: 1, name: 'A' }) - .into('test_table') - .then(function() { - // Nested transaction (savepoint) - return trx1 - .transaction( - function(trx2) { - return trx2.rollback(); - }, - { doNotRejectOnRollback: true } - ) - .on('query', function() { - ++trx2QueryCount; - }); - }) - .then(function() { - trx2Rejected = true; - }); - }) - .on('query', function() { - ++trx1QueryCount; - }) - .finally(function() { - // trx1: BEGIN, INSERT, ROLLBACK - // trx2: SAVEPOINT, ROLLBACK TO SAVEPOINT - // oracle & mssql: BEGIN & ROLLBACK not reported as queries - let expectedTrx1QueryCount = - knex.client.driverName === 'oracledb' || - knex.client.driverName === 'mssql' - ? 1 - : 3; - const expectedTrx2QueryCount = 2; - expectedTrx1QueryCount += expectedTrx2QueryCount; - t.equal( - trx1QueryCount, - expectedTrx1QueryCount, - 'Expected number of parent transaction SQL queries executed' - ); - t.equal( - trx2QueryCount, - expectedTrx2QueryCount, - 'Expected number of nested transaction SQL queries executed' - ); - t.equal(trx2Rejected, true, 'Nested transaction promise rejected'); - return knex - .select('*') - .from('test_table') - .then(function(results) { - t.equal(results.length, 1, 'One row inserted'); - }); - }); + try { + await knex + .transaction(function(trx1) { + return trx1 + .insert({ id: 1, name: 'A' }) + .into('test_table') + .then(function() { + // Nested transaction (savepoint) + return trx1 + .transaction( + function(trx2) { + return trx2.rollback(); + }, + { doNotRejectOnRollback: true } + ) + .on('query', function() { + ++trx2QueryCount; + }); + }) + .then(function() { + trx2Rejected = true; + }); + }) + .on('query', function() { + ++trx1QueryCount; + }); + } finally { + // trx1: BEGIN, INSERT, ROLLBACK + // trx2: SAVEPOINT, ROLLBACK TO SAVEPOINT + // oracle & mssql: BEGIN & ROLLBACK not reported as queries + let expectedTrx1QueryCount = + knex.client.driverName === 'oracledb' || + knex.client.driverName === 'mssql' + ? 1 + : 3; + const expectedTrx2QueryCount = 2; + expectedTrx1QueryCount += expectedTrx2QueryCount; + t.equal( + trx1QueryCount, + expectedTrx1QueryCount, + 'Expected number of parent transaction SQL queries executed' + ); + t.equal( + trx2QueryCount, + expectedTrx2QueryCount, + 'Expected number of nested transaction SQL queries executed' + ); + t.equal(trx2Rejected, true, 'Nested transaction promise rejected'); + const results = await knex.select('*').from('test_table'); + t.equal(results.length, 1, 'One row inserted'); + } }); }; diff --git a/test/unit/knex.js b/test/unit/knex.js index 3d143ca1b9..16a30058ae 100644 --- a/test/unit/knex.js +++ b/test/unit/knex.js @@ -1,7 +1,6 @@ const Knex = require('../../lib/index'); const QueryBuilder = require('../../lib/query/builder'); const { expect } = require('chai'); -const bluebird = require('bluebird'); const sqliteConfig = require('../knexfile').sqlite3; const sqlite3 = require('sqlite3'); const { noop } = require('lodash'); @@ -285,11 +284,10 @@ describe('knex', () => { const knexWithParams = knex.withUserParams({ userParam: '451' }); - return knexWithParams.transaction((trx) => { + return knexWithParams.transaction(async (trx) => { expect(trx.userParams).to.deep.equal({ userParam: '451', }); - return bluebird.resolve(); }); }); @@ -478,13 +476,12 @@ describe('knex', () => { const knex = Knex(sqliteConfig); - return knex.transaction((trx) => { + return knex.transaction(async (trx) => { expect(() => { trx.withUserParams({ userParam: '451' }); }).to.throw( /Cannot set user params on a transaction - it can only inherit params from main knex instance/ ); - return bluebird.resolve(); }); }); diff --git a/test/unit/migrate/migration-list-resolver.js b/test/unit/migrate/migration-list-resolver.js index 4733b83a90..dcd09aa6f4 100644 --- a/test/unit/migrate/migration-list-resolver.js +++ b/test/unit/migrate/migration-list-resolver.js @@ -1,7 +1,6 @@ /*eslint no-var:0, indent:0, max-len:0 */ 'use strict'; -const Promise = require('bluebird'); const { expect } = require('chai'); const sinon = require('sinon'); const mockFs = require('mock-fs'); @@ -197,7 +196,7 @@ describe('migration-list-resolver', () => { const stub = sinon .stub(migrationSource, 'getMigrations') - .callsFake(() => Promise.resolve(true)); + .callsFake(async () => true); return migrationListResolver .listAll(migrationSource, ['.ts']) .then(() => { diff --git a/types/index.d.ts b/types/index.d.ts index ea633afb7a..f650abaf58 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1419,21 +1419,8 @@ declare namespace Knex { type ExposedPromiseKeys = | "then" - | "bind" | "catch" - | "finally" - | "asCallback" - | "spread" - | "map" - | "reduce" - | "thenReturn" - | "return" - | "yield" - | "ensure" - | "reflect" - | "get" - | "mapSeries" - | "delay"; + | "finally"; interface ChainableInterface extends Pick, keyof Promise & ExposedPromiseKeys> { toQuery(): string; @@ -1451,7 +1438,7 @@ declare namespace Knex { writable: T, options?: { [key: string]: any } ): stream.PassThrough; - asCallback(callback: Function): this; + asCallback(callback: Function): Promise; } interface Transaction