From 0db36a44ac745818039bb29a11f8ba181930d8bc Mon Sep 17 00:00:00 2001 From: Dan Hensby Date: Fri, 29 May 2026 19:46:39 +0100 Subject: [PATCH] fix: prevent TypeError in batch with output parameters when sql errors When a batch with declared output parameters fails before the appended `select 1 as [___return___], ...` trailer can run (e.g. a compile error or a runtime error under SET XACT_ABORT ON), `recordsets` is empty and `recordsets.pop()[0]` throws synchronously inside the tedious request callback, preventing the user callback from being invoked and leaving the request promise unsettled. Guard the access with optional chaining so the SQL error propagates through the existing callback path and rejects the promise normally. Fixes #1863 Co-Authored-By: Claude Opus 4.7 (1M context) --- lib/tedious/request.js | 2 +- test/common/tests.js | 16 ++++++++++++++++ test/msnodesqlv8/msnodesqlv8.js | 1 + test/tedious/tedious.js | 1 + 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/tedious/request.js b/lib/tedious/request.js index 4cdee636..d1a0104e 100644 --- a/lib/tedious/request.js +++ b/lib/tedious/request.js @@ -477,7 +477,7 @@ class Request extends BaseRequest { // process batch outputs if (batchHasOutput) { - if (!this.stream) batchLastRow = recordsets.pop()[0] + if (!this.stream) batchLastRow = recordsets.pop()?.[0] for (const name in batchLastRow) { const value = batchLastRow[name] diff --git a/test/common/tests.js b/test/common/tests.js index 844eaae7..90474e72 100644 --- a/test/common/tests.js +++ b/test/common/tests.js @@ -644,6 +644,22 @@ module.exports = (sql, driver) => { }).catch(done) }, + 'batch with output parameters and sql error' (done) { + const req = new TestRequest() + req.output('out', sql.Int) + req.batch('select * from notexistingtable').then(() => { + done(new Error('expected batch to reject with sql error')) + }).catch(err => { + try { + assert.ok(err instanceof sql.RequestError, `expected RequestError, got ${err && err.constructor.name}: ${err && err.message}`) + assert.strictEqual(err.code, 'EREQUEST') + done() + } catch (assertionError) { + done(assertionError) + } + }) + }, + 'create procedure batch' (done) { let req = new TestRequest() req.batch('create procedure #temporary as select 1 as num').then(result => { diff --git a/test/msnodesqlv8/msnodesqlv8.js b/test/msnodesqlv8/msnodesqlv8.js index 2ab7b1ff..849a5377 100644 --- a/test/msnodesqlv8/msnodesqlv8.js +++ b/test/msnodesqlv8/msnodesqlv8.js @@ -74,6 +74,7 @@ describe('msnodesqlv8', function () { it('query with pipe and back pressure', (done) => TESTS['query with pipe and back pressure'](done)) it('batch', done => TESTS.batch(done)) it('batch (stream)', done => TESTS.batch(done, true)) + it('batch with output parameters and sql error', done => TESTS['batch with output parameters and sql error'](done)) it('create procedure batch', done => TESTS['create procedure batch'](done)) it('prepared statement', done => TESTS['prepared statement'](done)) it('prepared statement that fails to prepare throws', done => TESTS['prepared statement that fails to prepare throws'](done)) diff --git a/test/tedious/tedious.js b/test/tedious/tedious.js index 2680effc..a0bf0690 100644 --- a/test/tedious/tedious.js +++ b/test/tedious/tedious.js @@ -81,6 +81,7 @@ describe('tedious', () => { it('query with pipe and back pressure', (done) => TESTS['query with pipe and back pressure'](done)) it('query with duplicate output column names', done => TESTS['query with duplicate output column names'](done)) it('batch', done => TESTS.batch(done)) + it('batch with output parameters and sql error', done => TESTS['batch with output parameters and sql error'](done)) it('create procedure batch', done => TESTS['create procedure batch'](done)) it('prepared statement', done => TESTS['prepared statement'](done)) it('prepared statement that fails to prepare throws', done => TESTS['prepared statement that fails to prepare throws'](done))