2014-09-01 17:18:45 +02:00
'use strict' ;
2020-03-08 19:48:23 -04:00
const { expect } = require ( 'chai' ) ;
2018-10-15 22:29:53 -04:00
const Knex = require ( '../../../knex' ) ;
const _ = require ( 'lodash' ) ;
const sinon = require ( 'sinon' ) ;
2020-02-12 23:42:15 +03:00
const { KnexTimeoutError } = require ( '../../../lib/util/timeout' ) ;
2020-12-28 16:55:08 +02:00
const delay = require ( '../../../lib/execution/internal/delay' ) ;
2021-10-10 01:33:20 +03:00
const {
isRedshift ,
isOracle ,
isMssql ,
isPostgreSQL ,
2021-10-15 18:02:55 +03:00
isCockroachDB ,
2021-10-10 01:33:20 +03:00
} = require ( '../../util/db-helpers' ) ;
2021-03-08 07:16:07 -05:00
const { DRIVER _NAMES : drivers } = require ( '../../util/constants' ) ;
2021-10-10 17:16:47 +03:00
const {
dropTables ,
createAccounts ,
createTestTableTwo ,
} = require ( '../../util/tableCreatorHelper' ) ;
const {
insertTestTableTwoData ,
insertAccounts ,
} = require ( '../../util/dataInsertHelper' ) ;
2021-10-13 01:19:56 +03:00
const { assertNumber } = require ( '../../util/assertHelper' ) ;
2013-09-11 23:36:55 -04:00
2020-04-19 00:40:23 +02:00
module . exports = function ( knex ) {
2018-02-03 08:33:02 -05:00
// Certain dialects do not have proper insert with returning, so if this is true
// then pick an id to use as the "foreign key" just for testing transactions.
2021-03-08 07:16:07 -05:00
const constid = isRedshift ( knex ) ;
2018-02-03 08:33:02 -05:00
let fkid = 1 ;
2020-04-19 00:40:23 +02:00
describe ( 'Transactions' , function ( ) {
2021-10-10 17:16:47 +03:00
before ( async ( ) => {
await dropTables ( knex ) ;
await createAccounts ( knex ) ;
await createTestTableTwo ( knex ) ;
await insertAccounts ( knex ) ;
await insertTestTableTwoData ( knex ) ;
} ) ;
2020-04-19 00:40:23 +02:00
it ( 'can run with asCallback' , function ( ok ) {
2018-07-09 08:10:34 -04:00
knex
2020-04-19 00:40:23 +02:00
. transaction ( function ( t ) {
2018-07-09 08:10:34 -04:00
t . commit ( ) ;
} )
. asCallback ( ok ) ;
2014-03-19 11:54:18 -04:00
} ) ;
2018-07-09 08:10:34 -04:00
2020-04-19 00:40:23 +02:00
it ( 'should throw when undefined transaction is sent to transacting' , function ( ) {
2018-07-09 08:10:34 -04:00
return knex
2020-04-19 00:40:23 +02:00
. transaction ( function ( t ) {
2018-07-09 08:10:34 -04:00
knex ( 'accounts' ) . transacting ( undefined ) ;
} )
. catch ( function handle ( error ) {
expect ( error . message ) . to . equal (
'Invalid transacting value (null, undefined or empty object)'
) ;
} ) ;
2018-02-20 06:41:21 -03:00
} ) ;
2018-07-09 08:10:34 -04:00
2019-05-26 18:03:07 -07:00
it ( 'supports direct retrieval of a transaction without a callback' , ( ) => {
const trxPromise = knex . transaction ( ) ;
2021-03-08 07:16:07 -05:00
const query = isOracle ( knex ) ? '1 as "result" from DUAL' : '1 as result' ;
2019-05-26 18:03:07 -07:00
let transaction ;
return trxPromise
. then ( ( trx ) => {
transaction = trx ;
expect ( trx . client . transacting ) . to . equal ( true ) ;
return knex . transacting ( trx ) . select ( knex . raw ( query ) ) ;
} )
. then ( ( rows ) => {
2021-10-13 01:19:56 +03:00
assertNumber ( knex , rows [ 0 ] . result , 1 ) ;
2019-05-26 18:03:07 -07:00
return transaction . commit ( ) ;
} ) ;
} ) ;
2020-04-19 00:40:23 +02:00
it ( 'should throw when null transaction is sent to transacting' , function ( ) {
2018-07-09 08:10:34 -04:00
return knex
2020-04-19 00:40:23 +02:00
. transaction ( function ( t ) {
2018-07-09 08:10:34 -04:00
knex ( 'accounts' ) . transacting ( null ) ;
} )
. catch ( function handle ( error ) {
expect ( error . message ) . to . equal (
'Invalid transacting value (null, undefined or empty object)'
) ;
} ) ;
2018-02-20 06:41:21 -03:00
} ) ;
2018-07-09 08:10:34 -04:00
2020-04-19 00:40:23 +02:00
it ( 'should throw when empty object transaction is sent to transacting' , function ( ) {
2018-07-09 08:10:34 -04:00
return knex
2020-04-19 00:40:23 +02:00
. transaction ( function ( t ) {
2018-07-09 08:10:34 -04:00
knex ( 'accounts' ) . transacting ( { } ) ;
} )
. catch ( function handle ( error ) {
expect ( error . message ) . to . equal (
'Invalid transacting value (null, undefined or empty object)'
) ;
} ) ;
2018-02-20 06:41:21 -03:00
} ) ;
2014-03-19 11:54:18 -04:00
2020-04-19 00:40:23 +02:00
it ( 'should be able to commit transactions' , function ( ) {
2018-10-15 22:29:53 -04:00
let id = null ;
2018-07-09 08:10:34 -04:00
return knex
2020-04-19 00:40:23 +02:00
. transaction ( function ( t ) {
2018-07-09 08:10:34 -04:00
knex ( 'accounts' )
. transacting ( t )
. returning ( 'id' )
. insert ( {
first _name : 'Transacting' ,
last _name : 'User' ,
email : 'transaction-test1@example.com' ,
logins : 1 ,
about : 'Lorem ipsum Dolore labore incididunt enim.' ,
created _at : new Date ( ) ,
updated _at : new Date ( ) ,
} )
2020-04-19 00:40:23 +02:00
. then ( function ( resp ) {
2018-07-09 08:10:34 -04:00
return knex ( 'test_table_two' )
. transacting ( t )
. insert ( {
account _id : constid ? ++ fkid : ( id = resp [ 0 ] ) ,
details : '' ,
status : 1 ,
} ) ;
} )
2020-04-19 00:40:23 +02:00
. then ( function ( ) {
2018-07-09 08:10:34 -04:00
t . commit ( 'Hello world' ) ;
2013-09-11 23:36:55 -04:00
} ) ;
2018-07-09 08:10:34 -04:00
} )
2020-04-19 00:40:23 +02:00
. then ( function ( commitMessage ) {
2018-07-09 08:10:34 -04:00
expect ( commitMessage ) . to . equal ( 'Hello world' ) ;
2020-04-19 00:40:23 +02:00
return knex ( 'accounts' ) . where ( 'id' , id ) . select ( 'first_name' ) ;
2018-07-09 08:10:34 -04:00
} )
2020-04-19 00:40:23 +02:00
. then ( function ( resp ) {
2018-07-09 08:10:34 -04:00
if ( ! constid ) {
expect ( resp ) . to . have . length ( 1 ) ;
}
} ) ;
2013-09-11 23:36:55 -04:00
} ) ;
2020-04-19 00:40:23 +02:00
it ( 'should be able to rollback transactions' , function ( ) {
2018-10-15 22:29:53 -04:00
let id = null ;
const err = new Error ( 'error message' ) ;
2018-07-09 08:10:34 -04:00
return knex
2020-04-19 00:40:23 +02:00
. transaction ( function ( t ) {
2018-07-09 08:10:34 -04:00
knex ( 'accounts' )
. transacting ( t )
. returning ( 'id' )
. insert ( {
first _name : 'Transacting' ,
last _name : 'User2' ,
email : 'transaction-test2@example.com' ,
logins : 1 ,
about : 'Lorem ipsum Dolore labore incididunt enim.' ,
created _at : new Date ( ) ,
updated _at : new Date ( ) ,
} )
2020-04-19 00:40:23 +02:00
. then ( function ( resp ) {
2018-07-09 08:10:34 -04:00
return knex ( 'test_table_two' )
. transacting ( t )
. insert ( {
account _id : constid ? ++ fkid : ( id = resp [ 0 ] ) ,
details : '' ,
status : 1 ,
} ) ;
} )
2020-04-19 00:40:23 +02:00
. then ( function ( ) {
2018-07-09 08:10:34 -04:00
t . rollback ( err ) ;
2013-09-11 23:36:55 -04:00
} ) ;
2018-07-09 08:10:34 -04:00
} )
2020-04-19 00:40:23 +02:00
. catch ( function ( msg ) {
2018-07-09 08:10:34 -04:00
expect ( msg ) . to . equal ( err ) ;
2020-04-19 00:40:23 +02:00
return knex ( 'accounts' ) . where ( 'id' , id ) . select ( 'first_name' ) ;
2018-07-09 08:10:34 -04:00
} )
2020-04-19 00:40:23 +02:00
. then ( function ( resp ) {
2018-07-09 08:10:34 -04:00
expect ( resp . length ) . to . equal ( 0 ) ;
} ) ;
2014-05-08 18:04:55 -04:00
} ) ;
2020-04-19 00:40:23 +02:00
it ( 'should be able to commit transactions with a resolved trx query' , function ( ) {
2018-10-15 22:29:53 -04:00
let id = null ;
2018-07-09 08:10:34 -04:00
return knex
2020-04-19 00:40:23 +02:00
. transaction ( function ( trx ) {
2018-07-09 08:10:34 -04:00
return trx ( 'accounts' )
. returning ( 'id' )
. insert ( {
first _name : 'Transacting' ,
last _name : 'User' ,
email : 'transaction-test3@example.com' ,
logins : 1 ,
about : 'Lorem ipsum Dolore labore incididunt enim.' ,
created _at : new Date ( ) ,
updated _at : new Date ( ) ,
} )
2020-04-19 00:40:23 +02:00
. then ( function ( resp ) {
2018-07-09 08:10:34 -04:00
return trx ( 'test_table_two' ) . insert ( {
account _id : constid ? ++ fkid : ( id = resp [ 0 ] ) ,
details : '' ,
status : 1 ,
} ) ;
} )
2020-04-19 00:40:23 +02:00
. then ( function ( ) {
2018-07-09 08:10:34 -04:00
return 'Hello World' ;
2014-05-08 18:04:55 -04:00
} ) ;
2018-07-09 08:10:34 -04:00
} )
2020-04-19 00:40:23 +02:00
. then ( function ( commitMessage ) {
2018-07-09 08:10:34 -04:00
expect ( commitMessage ) . to . equal ( 'Hello World' ) ;
2020-04-19 00:40:23 +02:00
return knex ( 'accounts' ) . where ( 'id' , id ) . select ( 'first_name' ) ;
2018-07-09 08:10:34 -04:00
} )
2020-04-19 00:40:23 +02:00
. then ( function ( resp ) {
2018-07-09 08:10:34 -04:00
if ( ! constid ) {
expect ( resp ) . to . have . length ( 1 ) ;
}
} ) ;
2014-05-08 18:04:55 -04:00
} ) ;
2013-09-11 23:36:55 -04:00
2020-04-19 00:40:23 +02:00
it ( 'should be able to rollback transactions with rejected trx query' , function ( ) {
2018-10-15 22:29:53 -04:00
let id = null ;
const err = new Error ( 'error message' ) ;
let _ _knexUid ,
2018-07-09 08:10:34 -04:00
count = 0 ;
return knex
2020-04-19 00:40:23 +02:00
. transaction ( function ( trx ) {
2018-07-09 08:10:34 -04:00
return trx ( 'accounts' )
. returning ( 'id' )
. insert ( {
first _name : 'Transacting' ,
last _name : 'User2' ,
email : 'transaction-test4@example.com' ,
logins : 1 ,
about : 'Lorem ipsum Dolore labore incididunt enim.' ,
created _at : new Date ( ) ,
updated _at : new Date ( ) ,
} )
2020-04-19 00:40:23 +02:00
. then ( function ( resp ) {
2018-07-09 08:10:34 -04:00
return trx
. insert ( {
account _id : constid ? ++ fkid : ( id = resp [ 0 ] ) ,
details : '' ,
status : 1 ,
} )
. into ( 'test_table_two' ) ;
} )
2020-04-19 00:40:23 +02:00
. then ( function ( ) {
2018-07-09 08:10:34 -04:00
throw err ;
} ) ;
2014-06-05 17:01:02 -07:00
} )
2020-04-19 00:40:23 +02:00
. on ( 'query' , function ( obj ) {
2014-06-05 17:01:02 -07:00
count ++ ;
2015-04-17 15:00:08 -04:00
if ( ! _ _knexUid ) _ _knexUid = obj . _ _knexUid ;
expect ( _ _knexUid ) . to . equal ( obj . _ _knexUid ) ;
2014-06-05 17:01:02 -07:00
} )
2020-04-19 00:40:23 +02:00
. catch ( function ( msg ) {
2018-07-09 08:10:34 -04:00
// oracle & mssql: BEGIN & ROLLBACK not reported as queries
2021-03-08 07:16:07 -05:00
const expectedCount = isOracle ( knex ) || isMssql ( knex ) ? 2 : 4 ;
2018-07-09 08:10:34 -04:00
expect ( count ) . to . equal ( expectedCount ) ;
2014-06-05 17:01:02 -07:00
expect ( msg ) . to . equal ( err ) ;
2020-04-19 00:40:23 +02:00
return knex ( 'accounts' ) . where ( 'id' , id ) . select ( 'first_name' ) ;
2014-06-05 17:01:02 -07:00
} )
2020-04-19 00:40:23 +02:00
. then ( function ( resp ) {
2018-07-09 08:10:34 -04:00
expect ( resp ) . to . eql ( [ ] ) ;
2014-06-05 17:01:02 -07:00
} ) ;
2018-07-09 08:10:34 -04:00
} ) ;
2021-10-15 18:02:55 +03:00
it ( 'should be able to run schema methods' , async function ( ) {
// CockroachDB requires schema changes to happen before any writes, so trying to execute migrations in transaction directly fails due to attempt to get lock first
if ( isCockroachDB ( knex ) ) {
return this . skip ( ) ;
}
2018-10-15 22:29:53 -04:00
let _ _knexUid ,
2018-07-09 08:10:34 -04:00
count = 0 ;
2018-10-15 22:29:53 -04:00
const err = new Error ( 'error message' ) ;
2021-03-08 07:16:07 -05:00
if ( isPostgreSQL ( knex ) ) {
2018-07-09 08:10:34 -04:00
return knex
2020-04-19 00:40:23 +02:00
. transaction ( function ( trx ) {
2018-07-09 08:10:34 -04:00
return trx . schema
2020-04-19 00:40:23 +02:00
. createTable ( 'test_schema_transactions' , function ( table ) {
2014-06-05 17:01:02 -07:00
table . increments ( ) ;
table . string ( 'name' ) ;
table . timestamps ( ) ;
2018-07-09 08:10:34 -04:00
} )
2020-04-19 00:40:23 +02:00
. then ( function ( ) {
2018-07-09 08:10:34 -04:00
return trx ( 'test_schema_transactions' ) . insert ( { name : 'bob' } ) ;
} )
2020-04-19 00:40:23 +02:00
. then ( function ( ) {
2018-07-09 08:10:34 -04:00
return trx ( 'test_schema_transactions' ) . count ( '*' ) ;
} )
2020-04-19 00:40:23 +02:00
. then ( function ( resp ) {
2018-10-15 22:29:53 -04:00
const _count = parseInt ( resp [ 0 ] . count , 10 ) ;
2018-07-09 08:10:34 -04:00
expect ( _count ) . to . equal ( 1 ) ;
throw err ;
2014-06-05 17:01:02 -07:00
} ) ;
2018-07-09 08:10:34 -04:00
} )
2020-04-19 00:40:23 +02:00
. on ( 'query' , function ( obj ) {
2018-07-09 08:10:34 -04:00
count ++ ;
if ( ! _ _knexUid ) _ _knexUid = obj . _ _knexUid ;
expect ( _ _knexUid ) . to . equal ( obj . _ _knexUid ) ;
} )
2020-04-19 00:40:23 +02:00
. catch ( function ( msg ) {
2018-07-09 08:10:34 -04:00
expect ( msg ) . to . equal ( err ) ;
2015-12-09 17:53:53 -06:00
expect ( count ) . to . equal ( 5 ) ;
2018-07-09 08:10:34 -04:00
return knex ( 'test_schema_migrations' ) . count ( '*' ) ;
} )
2020-02-25 03:24:30 +03:00
. then ( ( ) => expect . fail ( 'should never reach this line' ) )
2020-04-19 00:40:23 +02:00
. catch ( function ( e ) {
2018-07-09 08:10:34 -04:00
// https://www.postgresql.org/docs/8.2/static/errcodes-appendix.html
expect ( e . code ) . to . equal ( '42P01' ) ;
} ) ;
} else {
2018-10-15 22:29:53 -04:00
let id = null ;
2020-02-25 03:24:30 +03:00
const promise = knex
2020-04-19 00:40:23 +02:00
. transaction ( function ( trx ) {
2018-07-09 08:10:34 -04:00
return trx ( 'accounts' )
. returning ( 'id' )
. insert ( {
first _name : 'Transacting' ,
last _name : 'User3' ,
email : 'transaction-test5@example.com' ,
logins : 1 ,
about : 'Lorem ipsum Dolore labore incididunt enim.' ,
created _at : new Date ( ) ,
updated _at : new Date ( ) ,
} )
2020-04-19 00:40:23 +02:00
. then ( function ( resp ) {
2018-07-09 08:10:34 -04:00
return trx ( 'test_table_two' ) . insert ( {
account _id : constid ? ++ fkid : ( id = resp [ 0 ] ) ,
details : '' ,
status : 1 ,
} ) ;
} )
2020-04-19 00:40:23 +02:00
. then ( function ( ) {
2018-07-09 08:10:34 -04:00
return trx . schema . createTable (
'test_schema_transactions' ,
2020-04-19 00:40:23 +02:00
function ( table ) {
2018-07-09 08:10:34 -04:00
table . increments ( ) ;
table . string ( 'name' ) ;
table . timestamps ( ) ;
}
) ;
} ) ;
} )
2020-04-19 00:40:23 +02:00
. on ( 'query' , function ( obj ) {
2018-07-09 08:10:34 -04:00
count ++ ;
if ( ! _ _knexUid ) _ _knexUid = obj . _ _knexUid ;
expect ( _ _knexUid ) . to . equal ( obj . _ _knexUid ) ;
} )
2020-04-19 00:40:23 +02:00
. then ( function ( ) {
2021-03-08 07:16:07 -05:00
if ( isMssql ( knex ) ) {
2018-07-09 08:10:34 -04:00
expect ( count ) . to . equal ( 3 ) ;
2021-03-08 07:16:07 -05:00
} else if ( isOracle ( knex ) ) {
2018-07-09 08:10:34 -04:00
expect ( count ) . to . equal ( 4 ) ;
} else {
expect ( count ) . to . equal ( 5 ) ;
}
2020-04-19 00:40:23 +02:00
return knex ( 'accounts' ) . where ( 'id' , id ) . select ( 'first_name' ) ;
2018-07-09 08:10:34 -04:00
} )
2020-04-19 00:40:23 +02:00
. then ( function ( resp ) {
2018-07-09 08:10:34 -04:00
if ( ! constid ) {
expect ( resp ) . to . have . length ( 1 ) ;
}
} ) ;
2020-02-25 03:24:30 +03:00
try {
await promise ;
} finally {
await knex . schema . dropTableIfExists ( 'test_schema_transactions' ) ;
}
2014-06-06 17:27:59 -04:00
}
} ) ;
2020-04-19 00:40:23 +02:00
it ( 'should resolve with the correct value, #298' , function ( ) {
2018-07-09 08:10:34 -04:00
return knex
2020-04-19 00:40:23 +02:00
. transaction ( function ( trx ) {
2018-07-09 08:10:34 -04:00
trx . debugging = true ;
return Promise . resolve ( null ) ;
} )
2020-04-19 00:40:23 +02:00
. then ( function ( result ) {
2018-07-09 08:10:34 -04:00
expect ( result ) . to . equal ( null ) ;
} ) ;
2014-06-06 17:27:59 -04:00
} ) ;
2014-06-05 17:01:02 -07:00
2019-06-21 12:56:00 +02:00
it ( 'does not reject promise when rolling back a transaction' , async ( ) => {
const trxProvider = knex . transactionProvider ( ) ;
2019-07-04 23:05:23 +03:00
const trx = await trxProvider ( ) ;
2019-06-21 12:56:00 +02:00
2019-07-04 23:05:23 +03:00
await trx . rollback ( ) ;
await trx . executionPromise ;
2019-06-21 12:56:00 +02:00
} ) ;
2020-04-19 00:40:23 +02:00
it ( 'should allow for nested transactions' , function ( ) {
2021-03-08 07:16:07 -05:00
if ( isRedshift ( knex ) ) {
2018-07-09 08:10:34 -04:00
return Promise . resolve ( ) ;
}
2020-04-19 00:40:23 +02:00
return knex . transaction ( function ( trx ) {
2018-07-09 08:10:34 -04:00
return trx
. select ( '*' )
. from ( 'accounts' )
2020-04-19 00:40:23 +02:00
. then ( function ( ) {
return trx . transaction ( function ( ) {
2018-07-09 08:10:34 -04:00
return trx . select ( '*' ) . from ( 'accounts' ) ;
} ) ;
} ) ;
} ) ;
} ) ;
2015-04-23 16:04:48 -04:00
2020-04-19 00:40:23 +02:00
it ( '#2213 - should wait for sibling transactions to finish' , function ( ) {
2021-03-08 07:16:07 -05:00
if ( isRedshift ( knex ) ) {
2020-03-05 21:40:33 +01:00
return this . skip ( ) ;
2019-09-20 15:37:58 +03:00
}
2020-03-05 21:40:33 +01:00
2020-02-25 03:24:30 +03:00
const first = delay ( 50 ) ;
const second = first . then ( ( ) => delay ( 50 ) ) ;
2020-04-19 00:40:23 +02:00
return knex . transaction ( function ( trx ) {
2019-09-20 15:37:58 +03:00
return Promise . all ( [
2020-04-19 00:40:23 +02:00
trx . transaction ( function ( trx2 ) {
2019-09-20 15:37:58 +03:00
return first ;
} ) ,
2020-04-19 00:40:23 +02:00
trx . transaction ( function ( trx3 ) {
2019-09-20 15:37:58 +03:00
return second ;
} ) ,
] ) ;
} ) ;
} ) ;
2020-04-19 00:40:23 +02:00
it ( '#2213 - should not evaluate a Transaction container until all previous siblings have completed' , async function ( ) {
2021-03-08 07:16:07 -05:00
if ( isRedshift ( knex ) ) {
2020-03-05 21:40:33 +01:00
return this . skip ( ) ;
}
const TABLE _NAME = 'test_sibling_transaction_order' ;
await knex . schema . dropTableIfExists ( TABLE _NAME ) ;
2020-04-19 00:40:23 +02:00
await knex . schema . createTable ( TABLE _NAME , function ( t ) {
2020-03-05 21:40:33 +01:00
t . string ( 'username' ) ;
} ) ;
2020-04-19 00:40:23 +02:00
await knex . transaction ( async function ( trx ) {
2020-03-05 21:40:33 +01:00
await Promise . all ( [
2020-04-19 00:40:23 +02:00
trx . transaction ( async function ( trx1 ) {
2020-03-05 21:40:33 +01:00
// This delay provides `trx2` with an opportunity to run first.
await delay ( 200 ) ;
await trx1 ( TABLE _NAME ) . insert ( { username : 'bob' } ) ;
} ) ,
2020-04-19 00:40:23 +02:00
trx . transaction ( async function ( trx2 ) {
2020-03-05 21:40:33 +01:00
const rows = await trx2 ( TABLE _NAME ) ;
// Even though `trx1` was delayed, `trx2` waited patiently for `trx1`
// to finish. Therefore, `trx2` discovers that there is already 1 row.
expect ( rows . length ) . to . equal ( 1 ) ;
} ) ,
] ) ;
} ) ;
} ) ;
2020-04-19 00:40:23 +02:00
it ( '#855 - Query Event should trigger on Transaction Client AND main Client' , function ( ) {
2018-10-15 22:29:53 -04:00
let queryEventTriggered = false ;
2015-09-20 15:29:07 +02:00
2020-04-19 00:40:23 +02:00
knex . once ( 'query' , function ( queryData ) {
2015-09-20 15:29:07 +02:00
queryEventTriggered = true ;
return queryData ;
} ) ;
function expectQueryEventToHaveBeenTriggered ( ) {
expect ( queryEventTriggered ) . to . equal ( true ) ;
}
2018-07-09 08:10:34 -04:00
return knex
2020-04-19 00:40:23 +02:00
. transaction ( function ( trx ) {
trx . select ( '*' ) . from ( 'accounts' ) . then ( trx . commit ) . catch ( trx . rollback ) ;
2018-07-09 08:10:34 -04:00
} )
. then ( expectQueryEventToHaveBeenTriggered )
. catch ( expectQueryEventToHaveBeenTriggered ) ;
2015-09-20 15:29:07 +02:00
} ) ;
2020-12-08 07:49:41 -05:00
it ( '#1040, #1171 - When pool is filled with transaction connections, Non-transaction queries should not hang the application, but instead throw a timeout error' , async ( ) => {
2016-02-01 20:40:06 +01:00
//To make this test easier, I'm changing the pool settings to max 1.
2018-10-15 22:29:53 -04:00
const knexConfig = _ . clone ( knex . client . config ) ;
2016-02-01 20:40:06 +01:00
knexConfig . pool . min = 0 ;
knexConfig . pool . max = 1 ;
2016-02-03 22:50:45 +01:00
knexConfig . acquireConnectionTimeout = 1000 ;
2016-02-01 20:40:06 +01:00
2018-10-15 22:29:53 -04:00
const knexDb = new Knex ( knexConfig ) ;
2016-02-01 20:40:06 +01:00
//Create a transaction that will occupy the only available connection, and avoid trx.commit.
2016-10-14 17:00:39 +02:00
2020-12-08 07:49:41 -05:00
await knexDb . transaction ( function ( trx ) {
2018-10-15 22:29:53 -04:00
let sql = 'SELECT 1' ;
2021-03-08 07:16:07 -05:00
if ( isOracle ( knex ) ) {
2016-10-14 17:00:39 +02:00
sql = 'SELECT 1 FROM DUAL' ;
2021-03-08 07:16:07 -05:00
} else if ( isMssql ( knex ) ) {
2021-02-08 16:46:35 +10:00
// MSSQL does not have a boolean type.
sql = 'SELECT CASE WHEN 1 = 1 THEN 1 ELSE 0 END' ;
2016-10-14 17:00:39 +02:00
}
2018-06-29 10:47:06 +03:00
2018-07-09 08:10:34 -04:00
trx
. raw ( sql )
2020-04-19 00:40:23 +02:00
. then ( function ( ) {
2018-07-09 08:10:34 -04:00
//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.
return knexDb . raw ( 'select * FROM accounts WHERE username = ?' , [
'Test' ,
] ) ;
} )
2020-04-19 00:40:23 +02:00
. then ( function ( ) {
2018-07-09 08:10:34 -04:00
//Should never reach this point
expect ( false ) . to . be . ok ( ) ;
} )
2020-04-19 00:40:23 +02:00
. catch ( function ( error ) {
2018-07-09 08:10:34 -04:00
expect ( error . bindings ) . to . be . an ( 'array' ) ;
expect ( error . bindings [ 0 ] ) . to . equal ( 'Test' ) ;
expect ( error . sql ) . to . equal (
'select * FROM accounts WHERE username = ?'
) ;
expect ( error . message ) . to . equal (
'Knex: Timeout acquiring a connection. The pool is probably full. Are you missing a .transacting(trx) call?'
) ;
trx . commit ( ) ; //Test done
} ) ;
2016-02-03 22:50:45 +01:00
} ) ;
2020-12-08 07:49:41 -05:00
await knexDb . destroy ( ) ;
2016-02-01 20:40:06 +01:00
} ) ;
2020-04-19 00:40:23 +02:00
it ( '#1694, #1703 it should return connections to pool if acquireConnectionTimeout is triggered' , async function ( ) {
2018-02-03 08:33:02 -05:00
const knexConfig = _ . clone ( knex . client . config ) ;
knexConfig . pool = {
min : 0 ,
max : 1 ,
} ;
knexConfig . acquireConnectionTimeout = 300 ;
const db = new Knex ( knexConfig ) ;
2020-02-25 03:24:30 +03:00
await expect (
db . transaction ( ( ) => db . transaction ( ( ) => ( { } ) ) )
) . to . be . rejectedWith ( KnexTimeoutError ) ;
2020-12-08 07:49:41 -05:00
await db . destroy ( ) ;
2017-01-30 16:00:03 -05:00
} ) ;
/ * *
* In mssql , certain classes of failures will "abort" a transaction , which
* causes the subsequent ROLLBACK to fail ( because the transaction has
* been rolled back automatically ) .
* An example of this type of auto - aborting error is creating a table with
* a foreign key that references a non - existent table .
* /
2021-03-08 07:16:07 -05:00
if ( isMssql ( knex ) ) {
2020-04-19 00:40:23 +02:00
it ( 'should rollback when transaction aborts' , function ( ) {
2018-10-15 22:29:53 -04:00
let insertedId = null ;
let originalError = null ;
2017-01-30 16:00:03 -05:00
function transactionAbortingQuery ( transaction ) {
2018-07-09 08:10:34 -04:00
return transaction . schema . createTable (
'test_schema_transaction_fails' ,
2020-04-19 00:40:23 +02:00
function ( table ) {
table . string ( 'name' ) . references ( 'id' ) . on ( 'non_exist_table' ) ;
2018-07-09 08:10:34 -04:00
}
) ;
2017-01-30 16:00:03 -05:00
}
function insertSampleRow ( transaction ) {
return transaction ( 'accounts' )
. returning ( 'id' )
. insert ( {
first _name : 'Transacting' ,
last _name : 'User2' ,
2018-07-09 08:10:34 -04:00
email : 'transaction-test2@example.com' ,
2017-01-30 16:00:03 -05:00
logins : 1 ,
about : 'Lorem ipsum Dolore labore incididunt enim.' ,
created _at : new Date ( ) ,
2018-07-09 08:10:34 -04:00
updated _at : new Date ( ) ,
2017-01-30 16:00:03 -05:00
} )
2020-04-19 00:40:23 +02:00
. then ( function ( res0 ) {
2017-01-30 16:00:03 -05:00
insertedId = res0 [ 0 ] ;
} ) ;
}
function querySampleRow ( ) {
2020-04-19 00:40:23 +02:00
return knex ( 'accounts' ) . where ( 'id' , insertedId ) . select ( 'first_name' ) ;
2017-01-30 16:00:03 -05:00
}
function captureAndRethrowOriginalError ( err ) {
originalError = err ;
throw err ;
}
2018-07-09 08:10:34 -04:00
return knex
2020-04-19 00:40:23 +02:00
. transaction ( function ( t ) {
2018-07-09 08:10:34 -04:00
return insertSampleRow ( t )
2020-04-19 00:40:23 +02:00
. then ( function ( ) {
2018-07-09 08:10:34 -04:00
return transactionAbortingQuery ( t ) ;
} )
. catch ( captureAndRethrowOriginalError ) ;
} )
2020-04-19 00:40:23 +02:00
. then ( function ( ) {
2018-07-09 08:10:34 -04:00
//Should never reach this point
expect ( false ) . to . be . ok ;
} )
2020-04-19 00:40:23 +02:00
. catch ( function ( err ) {
2020-03-09 10:10:33 -04:00
expect ( err ) . to . exist ;
2018-07-09 08:10:34 -04:00
expect ( err . originalError ) . to . equal ( originalError ) ;
// confirm transaction rolled back
2020-04-19 00:40:23 +02:00
return querySampleRow ( ) . then ( function ( resp ) {
2018-07-09 08:10:34 -04:00
expect ( resp ) . to . be . empty ;
} ) ;
2017-01-30 16:00:03 -05:00
} ) ;
} ) ;
}
2013-09-11 23:36:55 -04:00
2020-04-19 00:40:23 +02:00
it ( 'Rollback without an error should not reject with undefined #1966' , function ( ) {
2018-07-09 08:10:34 -04:00
return knex
2021-01-02 14:23:40 +02:00
. transaction (
function ( tr ) {
tr . rollback ( ) ;
} ,
{
doNotRejectOnRollback : false ,
}
)
2020-04-19 00:40:23 +02:00
. then ( function ( ) {
2018-07-09 08:10:34 -04:00
expect ( true ) . to . equal ( false , 'Transaction should not have commited' ) ;
} )
2020-04-19 00:40:23 +02:00
. catch ( function ( error ) {
2018-07-09 08:10:34 -04:00
expect ( error instanceof Error ) . to . equal ( true ) ;
expect ( error . message ) . to . equal (
'Transaction rejected with non-error: undefined'
) ;
} ) ;
2017-03-14 21:56:32 +01:00
} ) ;
2020-04-19 00:40:23 +02:00
it ( '#1052 - transaction promise mutating' , function ( ) {
const transactionReturning = knex . transaction ( function ( trx ) {
2018-07-09 08:10:34 -04:00
return trx
. insert ( {
first _name : 'foo' ,
last _name : 'baz' ,
email : 'fbaz@example.com' ,
logins : 1 ,
about : 'Lorem ipsum Dolore labore incididunt enim.' ,
created _at : new Date ( ) ,
updated _at : new Date ( ) ,
} )
. into ( 'accounts' ) ;
2017-03-26 19:43:36 +03:00
} ) ;
2019-06-17 02:14:17 +02:00
return Promise . all ( [ transactionReturning , transactionReturning ] ) . then (
( [ ret1 , ret2 ] ) => {
2017-03-26 19:43:36 +03:00
expect ( ret1 ) . to . equal ( ret2 ) ;
2019-06-17 02:14:17 +02:00
}
) ;
2018-02-01 23:41:01 +01:00
} ) ;
2020-04-19 00:40:23 +02:00
it ( 'should pass the query context to wrapIdentifier' , function ( ) {
2018-02-01 23:41:01 +01:00
const originalWrapIdentifier = knex . client . config . wrapIdentifier ;
const spy = sinon . spy ( ) . named ( 'calledWithContext' ) ;
function restoreWrapIdentifier ( ) {
knex . client . config . wrapIdentifier = originalWrapIdentifier ;
}
2017-03-26 19:43:36 +03:00
2018-02-01 23:41:01 +01:00
knex . client . config . wrapIdentifier = ( value , wrap , queryContext ) => {
spy ( queryContext ) ;
return wrap ( value ) ;
} ;
2018-07-09 08:10:34 -04:00
return knex
2020-04-19 00:40:23 +02:00
. transaction ( function ( trx ) {
return trx . select ( ) . from ( 'accounts' ) . queryContext ( { foo : 'bar' } ) ;
2018-07-09 08:10:34 -04:00
} )
2020-04-19 00:40:23 +02:00
. then ( function ( ) {
2018-07-09 08:10:34 -04:00
expect ( spy . callCount ) . to . equal ( 1 ) ;
expect ( spy . calledWith ( { foo : 'bar' } ) ) . to . equal ( true ) ;
} )
2020-04-19 00:40:23 +02:00
. then ( function ( ) {
2018-07-09 08:10:34 -04:00
restoreWrapIdentifier ( ) ;
} )
2020-04-19 00:40:23 +02:00
. catch ( function ( e ) {
2018-07-09 08:10:34 -04:00
restoreWrapIdentifier ( ) ;
throw e ;
} ) ;
2018-02-01 23:41:01 +01:00
} ) ;
2018-02-21 13:26:59 +01:00
2020-04-19 00:40:23 +02:00
it ( 'connection should contain __knexTxId which is also exposed in query event' , function ( ) {
return knex . transaction ( function ( trx ) {
2018-10-15 22:29:53 -04:00
const builder = trx . select ( ) . from ( 'accounts' ) ;
2018-02-21 13:26:59 +01:00
2020-04-19 00:40:23 +02:00
trx . on ( 'query' , function ( obj ) {
2018-02-21 13:26:59 +01:00
expect ( typeof obj . _ _knexTxId ) . to . equal ( typeof '' ) ;
} ) ;
2020-04-19 00:40:23 +02:00
builder . on ( 'query' , function ( obj ) {
2018-02-21 13:26:59 +01:00
expect ( typeof obj . _ _knexTxId ) . to . equal ( typeof '' ) ;
} ) ;
2018-07-09 08:10:34 -04:00
return builder ;
} ) ;
2018-02-21 13:26:59 +01:00
} ) ;
2020-02-29 02:16:07 +03:00
2020-04-19 00:40:23 +02:00
it ( '#3690 does not swallow exception when error during transaction occurs' , async function ( ) {
2020-02-29 02:16:07 +03:00
const mysqlKillConnection = async ( connection ) =>
knex . raw ( 'KILL ?' , [ connection . threadId ] ) ;
const killConnectionMap = {
2021-03-08 07:16:07 -05:00
[ drivers . MySQL ] : mysqlKillConnection ,
[ drivers . MySQL2 ] : mysqlKillConnection ,
[ drivers . PostgreSQL ] : async ( connection ) =>
2020-02-29 02:16:07 +03:00
knex . raw ( 'SELECT pg_terminate_backend(?)' , [ connection . processID ] ) ,
/ * T O D O F I X
mssql : async ( connection , trx ) => {
const id = ( await trx . raw ( 'select @@SPID as id' ) ) [ 0 ] . id ;
return knex . raw ( ` KILL ${ id } ` ) ;
} ,
oracledb : async ( connection , trx ) => {
// https://stackoverflow.com/a/28815685
const row = ( await trx . raw (
` SELECT * FROM V $ SESSION WHERE AUDSID = Sys_Context('USERENV', 'SESSIONID') `
) ) [ 0 ] ;
return knex . raw (
` Alter System Kill Session ' ${ row . SID } , ${
row [ 'SERIAL#' ]
} ' immediate `
) ;
} ,
* /
} ;
const killConnection = killConnectionMap [ knex . client . driverName ] ;
if ( ! killConnection ) {
this . skip ( ) ;
return ;
}
await expect (
knex . transaction ( async ( trx2 ) => {
await killConnection ( await trx2 . client . acquireConnection ( ) , trx2 ) ;
await trx2 . transaction ( async ( ) => 2 ) ;
} )
) . to . be . rejected ;
} ) ;
2020-03-06 19:53:33 -05:00
2021-10-10 17:16:47 +03:00
it ( 'handles promise rejections in nested Transactions (#3706)' , async function ( ) {
const fn = sinon . stub ( ) ;
process . on ( 'unhandledRejection' , fn ) ;
try {
await knex . transaction ( async function ( trx1 ) {
// These two lines together will cause the underlying Transaction
// to be rejected. Prior to #3706, this rejection would be unhandled.
const trx2 = await trx1 . transaction ( undefined , {
doNotRejectOnRollback : false ,
} ) ;
await trx2 . rollback ( ) ;
2020-03-06 19:53:33 -05:00
2021-10-10 17:16:47 +03:00
await expect ( trx2 . executionPromise ) . to . have . been . rejected ;
} ) ;
2020-03-15 15:30:40 -04:00
2021-10-10 17:16:47 +03:00
expect ( fn ) . have . not . been . called ;
} finally {
process . removeListener ( 'unhandledRejection' , fn ) ;
}
2020-03-15 15:30:40 -04:00
} ) ;
2021-10-10 17:16:47 +03:00
context ( 'when a `connection` is passed in explicitly' , function ( ) {
beforeEach ( function ( ) {
this . sandbox = sinon . createSandbox ( ) ;
} ) ;
2020-03-15 15:30:40 -04:00
2021-10-10 17:16:47 +03:00
afterEach ( function ( ) {
this . sandbox . restore ( ) ;
} ) ;
it ( 'assumes the caller will release the connection' , async function ( ) {
this . sandbox . spy ( knex . client , 'releaseConnection' ) ;
const conn = await knex . client . acquireConnection ( ) ;
try {
await knex . transaction (
async function ( trx ) {
// Do nothing!
} ,
{ connection : conn }
) ;
} catch ( err ) {
// Do nothing. The transaction could have failed due to some other
// bug, and it might have still released the connection in the process.
}
2020-03-15 15:30:40 -04:00
2021-10-10 17:16:47 +03:00
expect ( knex . client . releaseConnection ) . to . have . not . been . calledWith ( conn ) ;
2020-03-15 15:30:40 -04:00
2021-10-10 17:16:47 +03:00
// By design, this line will only be reached if the connection
// was never released.
knex . client . releaseConnection ( conn ) ;
2020-03-15 15:30:40 -04:00
2021-10-10 17:16:47 +03:00
// Note: It's still possible that the test might fail due to a Timeout
// even after concluding. This is because the underlying implementation
// might have opened another connection by mistake, but never actually
// closed it. (Ex: this was the case for OracleDB before fixing #3721)
} ) ;
2020-03-15 15:30:40 -04:00
} ) ;
} ) ;
2014-08-21 23:39:12 +02:00
} ;