knex 3.2.10 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,4 +1,38 @@
1
- # [Master (Unreleased)](https://github.com/knex/knex/compare/3.2.10...master)
1
+ # [Master (Unreleased)](https://github.com/knex/knex/compare/3.3.0...master)
2
+
3
+ # 3.3.0 - 26 June, 2026
4
+
5
+ ### New features
6
+
7
+ - feat: add support for returning in mariadb [#4572](https://github.com/knex/knex/pull/4572)
8
+ - feat: mariadb driver support [#6415](https://github.com/knex/knex/pull/6415)
9
+ - Fixes \_setNullableState not respecting schema [#6025](https://github.com/knex/knex/pull/6025)
10
+ - feat: add connectionPool option for bringing an external pool [#6414](https://github.com/knex/knex/pull/6414)
11
+ - Added knex.migrate.to and knex.migrate.before [#6420](https://github.com/knex/knex/pull/6420)
12
+ - feat: set error.cause for tarn acquire connection error [#5681](https://github.com/knex/knex/pull/5681)
13
+
14
+ ### Bug fixes
15
+
16
+ - Fix FOR UPDATE OF with explicit schema [#5791](https://github.com/knex/knex/pull/5791)
17
+ - Fix sqlite conditional insert/merge when inserting multiple rows [#6185](https://github.com/knex/knex/pull/6185)
18
+ - Fix: Stream postProcessResponse error is not catchable with .on('error') [#6033](https://github.com/knex/knex/pull/6033)
19
+ - fix(pg): preserve updateFrom binding order [#6454](https://github.com/knex/knex/pull/6454)
20
+ - fix: #6451, support token-credential in mssql auth [#6465](https://github.com/knex/knex/pull/6465)
21
+ - fix(types): #6452 - .where type regression for invalid types [#6463](https://github.com/knex/knex/pull/6463)
22
+ - fix: #6460 unhandled error on connection timeout with stream [#6462](https://github.com/knex/knex/pull/6462)
23
+ - fix: #6455, correctly state tedious as dependency needed for mssql [#6464](https://github.com/knex/knex/pull/6464)
24
+ - fix(pg,mssql): preserve binding order in delete and update queries [#6438](https://github.com/knex/knex/pull/6438)
25
+
26
+ ### Misc
27
+
28
+ - chore: bump tarn@3.1.0 [#6492](https://github.com/knex/knex/pull/6492)
29
+ - micro-optimization in wrappingFormatter [#6456](https://github.com/knex/knex/pull/6456)
30
+ - cleanup flake in the cancellation tests [#6486](https://github.com/knex/knex/pull/6486)
31
+ - Update docs: timeout section [#6471](https://github.com/knex/knex/pull/6471)
32
+ - ci: make npm install resilient to transient network failures [#6468](https://github.com/knex/knex/pull/6468)
33
+ - Update homepage urls [#6450](https://github.com/knex/knex/pull/6450)
34
+ - chore: add mariadb to docker-compose [#6466](https://github.com/knex/knex/pull/6466)
35
+ - chore: set codecov to default coverage provider [#6448](https://github.com/knex/knex/pull/6448)
2
36
 
3
37
  # 3.2.10 - 2 May, 2026
4
38
 
package/README.md CHANGED
@@ -1,9 +1,9 @@
1
- # [knex.js](https://knex.github.io/documentation/)
1
+ # [knex.js](https://knexjs.org/)
2
2
 
3
3
  [![npm version](http://img.shields.io/npm/v/knex.svg)](https://npmjs.org/package/knex)
4
4
  [![npm downloads](https://img.shields.io/npm/dm/knex.svg)](https://npmjs.org/package/knex)
5
5
  ![](https://github.com/knex/knex/workflows/CI/badge.svg)
6
- [![Coverage Status](https://coveralls.io/repos/knex/knex/badge.svg?branch=master)](https://coveralls.io/r/knex/knex?branch=master)
6
+ [![codecov](https://codecov.io/gh/knex/knex/graph/badge.svg?token=bH39w8lIA9)](https://codecov.io/gh/knex/knex)
7
7
  [![Dependencies Status](https://img.shields.io/librariesio/github/knex/knex)](https://libraries.io/npm/knex)
8
8
  [![Gitter chat](https://badges.gitter.im/tgriesser/knex.svg)](https://gitter.im/tgriesser/knex)
9
9
 
package/bin/cli.js CHANGED
@@ -280,19 +280,13 @@ function invoke() {
280
280
  }
281
281
  });
282
282
 
283
- commander
284
- .command('migrate:up [<name>]')
285
- .option(
286
- '--disable-transactions',
287
- 'run migrations without an implicit transaction'
288
- )
289
- .description(
290
- ' Run the next or the specified migration that has not yet been run.'
291
- )
292
- .action((name, cmd) => {
283
+ function migrateUpAction(method) {
284
+ return (name, cmd) => {
293
285
  const disableTransactions = !!cmd.disableTransactions;
294
286
  initKnex(env, commander.opts())
295
- .then((instance) => instance.migrate.up({ name, disableTransactions }))
287
+ .then((instance) =>
288
+ instance.migrate[method]({ name, disableTransactions })
289
+ )
296
290
  .then(([batchNo, log]) => {
297
291
  if (log.length === 0) {
298
292
  success(color.cyan('Already up to date'));
@@ -307,7 +301,19 @@ function invoke() {
307
301
  );
308
302
  })
309
303
  .catch(exit);
310
- });
304
+ };
305
+ }
306
+
307
+ commander
308
+ .command('migrate:up [<name>]')
309
+ .option(
310
+ '--disable-transactions',
311
+ 'run migrations without an implicit transaction'
312
+ )
313
+ .description(
314
+ ' Run the next or the specified migration that has not yet been run.'
315
+ )
316
+ .action(migrateUpAction('up'));
311
317
 
312
318
  commander
313
319
  .command('migrate:rollback')
@@ -371,6 +377,28 @@ function invoke() {
371
377
  .catch(exit);
372
378
  });
373
379
 
380
+ commander
381
+ .command('migrate:to <name>')
382
+ .option(
383
+ '--disable-transactions',
384
+ 'run migrations without an implicit transaction'
385
+ )
386
+ .description(
387
+ ' Run all migrations up to and including the specified migration.'
388
+ )
389
+ .action(migrateUpAction('to'));
390
+
391
+ commander
392
+ .command('migrate:before <name>')
393
+ .option(
394
+ '--disable-transactions',
395
+ 'run migrations without an implicit transaction'
396
+ )
397
+ .description(
398
+ ' Run all migrations before the specified migration (exclusive).'
399
+ )
400
+ .action(migrateUpAction('before'));
401
+
374
402
  commander
375
403
  .command('migrate:currentVersion')
376
404
  .description(' View the current version for the migration.')
package/lib/client.js CHANGED
@@ -1,8 +1,9 @@
1
- const { Pool, TimeoutError } = require('tarn');
1
+ const { TimeoutError } = require('tarn');
2
+ const { KnexPool, isTarnPool } = require('./pool');
2
3
  const { EventEmitter } = require('events');
3
4
  const { promisify } = require('util');
4
5
  const { makeEscape } = require('./util/string');
5
- const cloneDeep = require('lodash/cloneDeep');
6
+ const cloneDeepWith = require('lodash/cloneDeepWith');
6
7
  const defaults = require('lodash/defaults');
7
8
  const uniqueId = require('lodash/uniqueId');
8
9
 
@@ -35,6 +36,17 @@ const { setHiddenProperty } = require('./util/security.js');
35
36
 
36
37
  const debug = require('debug')('knex:client');
37
38
 
39
+ function preserveClassInstances(value) {
40
+ if (
41
+ value !== null &&
42
+ typeof value === 'object' &&
43
+ !Array.isArray(value) &&
44
+ !isPlainObject(value)
45
+ ) {
46
+ return value;
47
+ }
48
+ }
49
+
38
50
  // The base client provides the general structure
39
51
  // for a dialect specific client object.
40
52
 
@@ -72,11 +84,14 @@ class Client extends EventEmitter {
72
84
  this.version = config.version;
73
85
  }
74
86
 
75
- if (config.connection && config.connection instanceof Function) {
87
+ if (this.config.connection && this.config.connection instanceof Function) {
76
88
  this.connectionConfigProvider = this.config.connection;
77
89
  this.connectionConfigExpirationChecker = () => true; // causes the provider to be called on first use
78
90
  } else {
79
- this.connectionSettings = cloneDeep(this.config.connection || {});
91
+ this.connectionSettings = cloneDeepWith(
92
+ this.config.connection || {},
93
+ preserveClassInstances
94
+ );
80
95
  if (config.connection && config.connection.password) {
81
96
  setHiddenProperty(this.connectionSettings, this.config.connection);
82
97
  }
@@ -84,6 +99,10 @@ class Client extends EventEmitter {
84
99
  }
85
100
  if (this.driverName && config.connection) {
86
101
  this.initializeDriver();
102
+ }
103
+ const shouldInitPool =
104
+ this.driverName && (config.connection || config.connectionPool);
105
+ if (shouldInitPool) {
87
106
  if (!config.pool || (config.pool && config.pool.max !== 0)) {
88
107
  this.initializePool(this.config);
89
108
  }
@@ -200,7 +219,8 @@ class Client extends EventEmitter {
200
219
  try {
201
220
  this.driver = this._driver();
202
221
  } catch (e) {
203
- const message = `Knex: run\n$ npm install ${this.driverName} --save`;
222
+ const packageName = this._packageName || this.driverName;
223
+ const message = `Knex: run\n$ npm install ${packageName} --save`;
204
224
  this.logger.error(`${message}\n${e.message}\n${e.stack}`);
205
225
  throw new Error(`${message}\n${e.message}`);
206
226
  }
@@ -390,8 +410,46 @@ class Client extends EventEmitter {
390
410
  this.logger.warn('The pool has already been initialized');
391
411
  return;
392
412
  }
413
+ if (config.connectionPool) {
414
+ // External pool mode — delegate to a tarn-compatible or native pool
415
+ this.connectionSettings = {};
416
+ this.connectionConfigExpirationChecker = null;
417
+ if (this.driverName) {
418
+ this.initializeDriver();
419
+ }
420
+ this.initializeExternalPool(config.connectionPool);
421
+ } else {
422
+ this.pool = new KnexPool(this.getPoolSettings(config.pool));
423
+ this._ownsPool = true;
424
+ }
425
+ }
393
426
 
394
- this.pool = new Pool(this.getPoolSettings(config.pool));
427
+ // Accept an external pool either a tarn-compatible pool (KnexPool, tarn Pool)
428
+ // or a native driver pool (pg.Pool, mysql.createPool(), etc.)
429
+ initializeExternalPool(externalPool) {
430
+ if (this.pool) {
431
+ this.logger.warn('The pool has already been initialized');
432
+ return;
433
+ }
434
+ if (isTarnPool(externalPool)) {
435
+ // Tarn-compatible pool — use directly
436
+ this.pool = externalPool;
437
+ } else {
438
+ // Native driver pool — wrap via dialect-specific adapter
439
+ this.pool = this.wrapNativePool(externalPool);
440
+ }
441
+ this._ownsPool = false;
442
+ }
443
+
444
+ // Override in dialects that support native driver pools.
445
+ // Must return an object with { acquire(), release(), destroy() } matching tarn's interface.
446
+ wrapNativePool(_nativePool) {
447
+ throw new Error(
448
+ `The '${
449
+ this.driverName || 'base'
450
+ }' dialect does not support native driver pools in 'connectionPool'. ` +
451
+ `Pass a tarn Pool or KnexPool instance instead.`
452
+ );
395
453
  }
396
454
 
397
455
  validateConnection(connection) {
@@ -420,12 +478,23 @@ class Client extends EventEmitter {
420
478
  }
421
479
  return connection;
422
480
  } catch (error) {
481
+ // Tarn only gives us the propagated error's message, not its stack. However, for the "original"
482
+ // promise that rejected, the stack will be present when Tarn passes us the error directly (not
483
+ // as a timeout). The stack is likely of little use, but let's give all the information we can.
484
+ const withStack =
485
+ error instanceof Error ? error.stack ?? error.message : String(error);
486
+
487
+ // Errors like this can be noisy; use logger.warn to make it easier for operators to opt out of
488
+ // noise but still surface more critical problems
489
+ this.logger.warn(`Acquire connection error: ${withStack}`);
490
+
423
491
  let convertedError = error;
424
492
  if (error instanceof TimeoutError) {
425
493
  convertedError = new KnexTimeoutError(
426
494
  'Knex: Timeout acquiring a connection. The pool is probably full. ' +
427
495
  'Are you missing a .transacting(trx) call?'
428
496
  );
497
+ convertedError.cause = error;
429
498
  }
430
499
  throw convertedError;
431
500
  }
@@ -445,9 +514,11 @@ class Client extends EventEmitter {
445
514
  }
446
515
 
447
516
  // Destroy the current connection pool for the client.
517
+ // When the pool is externally provided (_ownsPool === false), we only drop
518
+ // our reference — the caller is responsible for tearing it down.
448
519
  async destroy(callback) {
449
520
  try {
450
- if (this.pool && this.pool.destroy) {
521
+ if (this.pool && this.pool.destroy && this._ownsPool !== false) {
451
522
  await this.pool.destroy();
452
523
  }
453
524
  this.pool = undefined;
package/lib/constants.js CHANGED
@@ -7,6 +7,7 @@ const CLIENT_ALIASES = Object.freeze({
7
7
 
8
8
  const SUPPORTED_CLIENTS = Object.freeze(
9
9
  [
10
+ 'mariadb',
10
11
  'mssql',
11
12
  'mysql',
12
13
  'mysql2',
@@ -21,6 +22,7 @@ const SUPPORTED_CLIENTS = Object.freeze(
21
22
  );
22
23
 
23
24
  const DRIVER_NAMES = Object.freeze({
25
+ MariaDB: 'mariadb',
24
26
  MsSQL: 'mssql',
25
27
  MySQL: 'mysql',
26
28
  MySQL2: 'mysql2',
@@ -7,6 +7,7 @@ const dbNameToDialectLoader = Object.freeze({
7
7
  cockroachdb: () => require('./cockroachdb'),
8
8
  mssql: () => require('./mssql'),
9
9
  mysql: () => require('./mysql'),
10
+ mariadb: () => require('./mariadb'),
10
11
  mysql2: () => require('./mysql2'),
11
12
  oracle: () => require('./oracle'),
12
13
  oracledb: () => require('./oracledb'),
@@ -0,0 +1,55 @@
1
+ // MariaDB Client
2
+ // -------
3
+ const Client_MySQL = require('../mysql');
4
+ const Transaction = require('./transaction');
5
+ const QueryCompiler = require('./query/mariadb-querycompiler');
6
+ const ColumnCompiler = require('./schema/mariadb-columncompiler');
7
+
8
+ class Client_MariaDB extends Client_MySQL {
9
+ transaction() {
10
+ return new Transaction(this, ...arguments);
11
+ }
12
+
13
+ queryCompiler(builder, formatter) {
14
+ return new QueryCompiler(this, builder, formatter);
15
+ }
16
+
17
+ columnCompiler() {
18
+ return new ColumnCompiler(this, ...arguments);
19
+ }
20
+
21
+ _driver() {
22
+ return require('mariadb/callback');
23
+ }
24
+
25
+ processResponse(obj, runner) {
26
+ if (obj == null) return;
27
+ if (obj.returning) {
28
+ const rows = obj.response[0];
29
+ const fields = obj.response[1];
30
+ if (obj.output) {
31
+ return obj.output.call(runner, rows, fields);
32
+ }
33
+ return rows;
34
+ }
35
+ return super.processResponse(obj, runner);
36
+ }
37
+
38
+ validateConnection(connection) {
39
+ if (typeof connection.isValid === 'function') {
40
+ return connection.isValid();
41
+ }
42
+ return (
43
+ connection &&
44
+ !connection._fatalError &&
45
+ !connection._protocolError &&
46
+ !connection._closing
47
+ );
48
+ }
49
+ }
50
+
51
+ Object.assign(Client_MariaDB.prototype, {
52
+ driverName: 'mariadb',
53
+ });
54
+
55
+ module.exports = Client_MariaDB;
@@ -0,0 +1,98 @@
1
+ // MariaDB Query Compiler
2
+ // -------
3
+ const QueryCompiler_MySQL = require('../../mysql/query/mysql-querycompiler');
4
+ const { wrap: wrap_ } = require('../../../formatter/wrappingFormatter');
5
+
6
+ class QueryCompiler_MariaDB extends QueryCompiler_MySQL {
7
+ // On MariaDB, json_extract() yields a quoted JSON scalar (e.g. "cold") whose
8
+ // values do not compare equal with `=`. json_unquote() is required for the
9
+ // comparison to behave like MySQL's native JSON type
10
+ onJsonPathEquals(clause) {
11
+ const extract = (column, path) =>
12
+ `json_unquote(json_extract(${wrap_(
13
+ column,
14
+ undefined,
15
+ this.builder,
16
+ this.client,
17
+ this.bindingsHolder
18
+ )}, ${this.client.parameter(path, this.builder, this.bindingsHolder)}))`;
19
+
20
+ return `${extract(clause.columnFirst, clause.jsonPathFirst)} = ${extract(
21
+ clause.columnSecond,
22
+ clause.jsonPathSecond
23
+ )}`;
24
+ }
25
+
26
+ insert() {
27
+ const sql = super.insert();
28
+ if (sql === '') return sql;
29
+ const { returning } = this.single;
30
+ if (returning && this._supportsReturning('insert')) {
31
+ return { sql: sql + this._returning(returning), returning };
32
+ }
33
+ return sql;
34
+ }
35
+
36
+ update() {
37
+ const sql = super.update();
38
+ if (sql === '') return sql;
39
+ const { returning } = this.single;
40
+ if (returning && this._supportsReturning('update')) {
41
+ return { sql: sql + this._returning(returning), returning };
42
+ }
43
+ return sql;
44
+ }
45
+
46
+ del() {
47
+ const sql = super.del();
48
+ if (sql === '') return sql;
49
+ const { returning } = this.single;
50
+ if (returning && this._supportsReturning('del')) {
51
+ return { sql: sql + this._returning(returning), returning };
52
+ }
53
+ return sql;
54
+ }
55
+
56
+ _returning(value) {
57
+ return value ? ` returning ${this.formatter.columnize(value)}` : '';
58
+ }
59
+
60
+ _supportsReturning(method) {
61
+ // Assume that if the version doesn't exist, we're on the latest
62
+ // (fail open and assume the user is building queries correctly)
63
+ const version = parseFloat(this.client.version) ?? 13;
64
+ if (version < 10.5) {
65
+ return false;
66
+ }
67
+ if (method === 'update') {
68
+ return version >= 13 && (this.grouped.join || []).length === 0;
69
+ }
70
+ if (method === 'del') {
71
+ return (this.grouped.join || []).length === 0;
72
+ }
73
+ return true;
74
+ }
75
+
76
+ _returningCheck() {
77
+ const { returning } = this.single;
78
+ if (!returning || this._supportsReturning(this.method)) {
79
+ return;
80
+ }
81
+ let reason;
82
+ const hasJoin = (this.grouped.join || []).length;
83
+ if (this.method === 'update') {
84
+ reason = (this.grouped.join || []).length
85
+ ? 'multi-table update statements'
86
+ : 'mariadb versions older than 13.0';
87
+ } else if (this.method === 'del' && hasJoin) {
88
+ reason = 'multi-table delete statements';
89
+ } else {
90
+ reason = 'mariadb versions older than 10.5';
91
+ }
92
+ this.client.logger.warn(
93
+ `.returning() is not supported for ${reason} and will not have any effect.`
94
+ );
95
+ }
96
+ }
97
+
98
+ module.exports = QueryCompiler_MariaDB;
@@ -0,0 +1,17 @@
1
+ // MariaDB Column Compiler
2
+ // -------
3
+ const ColumnCompiler_MySQL = require('../../mysql/schema/mysql-columncompiler');
4
+
5
+ class ColumnCompiler_MariaDB extends ColumnCompiler_MySQL {
6
+ // MariaDB does not accept a named CHECK constraint inline in a column
7
+ // definition
8
+ _check(checkPredicate, constraintName) {
9
+ if (constraintName && this.columnBuilder._method !== 'alter') {
10
+ this._pushAlterCheckQuery(checkPredicate, constraintName);
11
+ return '';
12
+ }
13
+ return super._check(checkPredicate, constraintName);
14
+ }
15
+ }
16
+
17
+ module.exports = ColumnCompiler_MariaDB;
@@ -0,0 +1,44 @@
1
+ const Transaction = require('../../execution/transaction');
2
+ const debug = require('debug')('knex:tx');
3
+
4
+ class Transaction_MariaDB extends Transaction {
5
+ query(conn, sql, status, value) {
6
+ const t = this;
7
+ const q = this.trxClient
8
+ .query(conn, sql)
9
+ .catch((err) => {
10
+ if (err.code === 'ER_SP_DOES_NOT_EXIST') {
11
+ this.trxClient.logger.warn(
12
+ 'Transaction was implicitly committed, do not mix transactions and ' +
13
+ 'DDL with MySQL (#805)'
14
+ );
15
+ return;
16
+ }
17
+
18
+ status = 2;
19
+ value = err;
20
+ t._completed = true;
21
+ debug('%s error running transaction query', t.txid);
22
+ })
23
+ .then(function (res) {
24
+ if (status === 1) t._resolver(value);
25
+ if (status === 2) {
26
+ if (value === undefined) {
27
+ if (t.doNotRejectOnRollback && /^ROLLBACK\b/i.test(sql)) {
28
+ t._resolver();
29
+ return;
30
+ }
31
+ value = new Error(`Transaction rejected with non-error: ${value}`);
32
+ }
33
+ t._rejecter(value);
34
+ return res;
35
+ }
36
+ });
37
+ if (status === 1 || status === 2) {
38
+ t._completed = true;
39
+ }
40
+ return q;
41
+ }
42
+ }
43
+
44
+ module.exports = Transaction_MariaDB;
@@ -46,6 +46,7 @@ class Client_MSSQL extends Client {
46
46
  clientSecret: settings.clientSecret,
47
47
  tenantId: settings.tenantId,
48
48
  msiEndpoint: settings.msiEndpoint,
49
+ credential: settings.credential,
49
50
  },
50
51
  },
51
52
  server: settings.server || settings.host,
@@ -493,6 +494,8 @@ Object.assign(Client_MSSQL.prototype, {
493
494
  dialect: 'mssql',
494
495
 
495
496
  driverName: 'mssql',
497
+
498
+ _packageName: 'tedious',
496
499
  });
497
500
 
498
501
  module.exports = Client_MSSQL;
@@ -179,8 +179,8 @@ class QueryCompiler_MSSQL extends QueryCompiler {
179
179
  }
180
180
 
181
181
  updateWithTriggers() {
182
- const top = this.top();
183
182
  const withSQL = this.with();
183
+ const top = this.top();
184
184
  const updates = this._prepUpdate(this.single.update);
185
185
  const join = this.join();
186
186
  const where = this.where();
@@ -221,8 +221,8 @@ class QueryCompiler_MSSQL extends QueryCompiler {
221
221
  }
222
222
 
223
223
  standardUpdate() {
224
- const top = this.top();
225
224
  const withSQL = this.with();
225
+ const top = this.top();
226
226
  const updates = this._prepUpdate(this.single.update);
227
227
  const join = this.join();
228
228
  const where = this.where();
@@ -261,8 +261,8 @@ class QueryCompiler_MSSQL extends QueryCompiler {
261
261
  // Make sure tableName is processed by the formatter first.
262
262
  const withSQL = this.with();
263
263
  const { tableName } = this;
264
- const wheres = this.where();
265
264
  const joins = this.join();
265
+ const wheres = this.where();
266
266
  const { returning } = this.single;
267
267
  const returningStr = returning
268
268
  ? ` ${this._returning('del', returning, true)}`
@@ -288,8 +288,8 @@ class QueryCompiler_MSSQL extends QueryCompiler {
288
288
  // Make sure tableName is processed by the formatter first.
289
289
  const withSQL = this.with();
290
290
  const { tableName } = this;
291
- const wheres = this.where();
292
291
  const joins = this.join();
292
+ const wheres = this.where();
293
293
  const { returning } = this.single;
294
294
  const returningStr = returning
295
295
  ? ` ${this._returning('del', returning)}`
@@ -2,6 +2,7 @@
2
2
  // -------
3
3
  const defer = require('lodash/defer');
4
4
  const map = require('lodash/map');
5
+ const uniqueId = require('lodash/uniqueId');
5
6
  const { promisify } = require('util');
6
7
  const Client = require('../../client');
7
8
 
@@ -109,6 +110,47 @@ class Client_MySQL extends Client {
109
110
  );
110
111
  }
111
112
 
113
+ // Wraps a native mysql pool with the interface knex expects from this.pool.
114
+ /**
115
+ * @param {import('mysql').Pool | import('mysql2').Pool} nativePool
116
+ */
117
+ wrapNativePool(nativePool) {
118
+ return {
119
+ acquire() {
120
+ return {
121
+ promise: new Promise((resolve, reject) => {
122
+ nativePool.getConnection((err, connection) => {
123
+ if (err) return reject(err);
124
+ connection.__knexUid = uniqueId('__knexUid');
125
+ resolve(connection);
126
+ });
127
+ }),
128
+ };
129
+ },
130
+ /**
131
+ * @param {import('mysql').Connection | import('mysql2').Connection} connection
132
+ */
133
+ release(connection) {
134
+ if (connection.__knex__disposed) {
135
+ // Disposed connection — remove from pool without sending COM_QUIT
136
+ if (typeof connection.destroy === 'function') {
137
+ connection.destroy();
138
+ }
139
+ return true;
140
+ }
141
+ if (typeof connection.release === 'function') {
142
+ connection.release();
143
+ return true;
144
+ }
145
+ return false;
146
+ },
147
+ destroy() {
148
+ // No-op: knex does not own the native pool
149
+ return Promise.resolve();
150
+ },
151
+ };
152
+ }
153
+
112
154
  // Grab a connection, run the query via the MySQL streaming interface,
113
155
  // and pass that through to the stream we've sent back to the client.
114
156
  _stream(connection, obj, stream, options) {
@@ -17,16 +17,10 @@ const isPlainObjectOrArray = (value) =>
17
17
  class QueryCompiler_MySQL extends QueryCompiler {
18
18
  constructor(client, builder, formatter) {
19
19
  super(client, builder, formatter);
20
-
21
- const { returning } = this.single;
22
- if (returning) {
23
- this.client.logger.warn(
24
- '.returning() is not supported by mysql and will not have any effect.'
25
- );
26
- }
27
-
20
+ this._returningCheck();
28
21
  this._emptyInsertValue = '() values ()';
29
22
  }
23
+
30
24
  // Compiles a `delete` query, allowing comments and LIMIT.
31
25
  del() {
32
26
  const sql = super.del();
@@ -288,6 +282,15 @@ class QueryCompiler_MySQL extends QueryCompiler {
288
282
  onJsonPathEquals(clause) {
289
283
  return this._onJsonPathEquals('json_extract', clause);
290
284
  }
285
+
286
+ _returningCheck() {
287
+ const { returning } = this.single;
288
+ if (returning) {
289
+ this.client.logger.warn(
290
+ '.returning() is not supported by mysql and will not have any effect.'
291
+ );
292
+ }
293
+ }
291
294
  }
292
295
 
293
296
  // MySQL supports LIMIT on single-table DELETE statements.
@@ -34,6 +34,13 @@ class Client_MySQL2 extends Client_MySQL {
34
34
  }
35
35
  }
36
36
 
37
+ // Support both mysql2.createPool() and mysql2/promise.createPool().
38
+ // The promise variant wraps a callback pool and exposes it as .pool.
39
+ wrapNativePool(nativePool) {
40
+ const callbackPool = nativePool.pool || nativePool;
41
+ return super.wrapNativePool(callbackPool);
42
+ }
43
+
37
44
  validateConnection(connection) {
38
45
  return (
39
46
  connection &&