From 6e342abd4e9921f506a81e762c05228f302185ca Mon Sep 17 00:00:00 2001 From: Bob den Os Date: Thu, 25 Sep 2025 13:57:46 +0200 Subject: [PATCH 1/2] call hana-client async functions when required --- hana/lib/drivers/hana-client.js | 8 +++++--- hana/test/run.test.js | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/hana/lib/drivers/hana-client.js b/hana/lib/drivers/hana-client.js index 16db4b6a9..8db235a9c 100644 --- a/hana/lib/drivers/hana-client.js +++ b/hana/lib/drivers/hana-client.js @@ -172,7 +172,7 @@ class HANAClientDriver extends driver { return this._native.state() === 'connected' } - _getResultForProcedure(rows, outParameters, stmt) { + async _getResultForProcedure(rows, outParameters, stmt) { const result = {} // build result from scalar params const paramInfo = stmt.getParameterInfo() @@ -183,6 +183,8 @@ class HANAClientDriver extends driver { } const resultSet = Array.isArray(rows) ? rows[0] : rows + const nextAsync = prom(resultSet, 'next') + const nextResultAsync = prom(resultSet, 'nextResult') // merge table output params into scalar params const params = Array.isArray(outParameters) && outParameters.filter(md => !(md.PARAMETER_NAME in result)) @@ -190,10 +192,10 @@ class HANAClientDriver extends driver { for (let i = 0; i < params.length; i++) { const parameterName = params[i].PARAMETER_NAME result[parameterName] = [] - while (resultSet.next()) { + while (resultSet.nextCanBlock() ? await nextAsync() : resultSet.next()) { result[parameterName].push(resultSet.getValues()) } - resultSet.nextResult() + await nextResultAsync() } } diff --git a/hana/test/run.test.js b/hana/test/run.test.js index 53c6fbf91..8218c93ba 100644 --- a/hana/test/run.test.js +++ b/hana/test/run.test.js @@ -111,7 +111,7 @@ describe('stored procedures', () => { await cds.run(`INSERT INTO sap_capire_TestEntity (ID,title) VALUES (20,'FROM TX')`) // Call the procedure with ASYNC - const { ASYNC_CALL_ID } = await cds.run(`CALL WRITE_PROC() ASYNC`, []) + const { ASYNC_CALL_ID } = await cds.run(`CALL WRITE_PROC() ASYNC`) const status = await cds.run(`DO (IN ID INTEGER => ?) BEGIN USING SQLSCRIPT_SYNC AS SYNCLIB; @@ -125,7 +125,7 @@ BEGIN SELECT ERROR_CODE, ERROR_TEXT, :dur AS WAITED_SECONDS FROM M_PROCEDURE_ASYNC_EXECUTIONS WHERE ASYNC_CALL_ID = :ID; END;`, [ASYNC_CALL_ID]) // Ensure that the procedure succeeded - expect(status.changes[1][0].ERROR_CODE).to.eq(0) + expect(status.changes?.[0]?.ERROR_CODE ?? status.changes?.[1]?.[0]?.ERROR_CODE).to.eq(0) throw new Error('ROLLBACK') }).catch((err) => { if (err.message !== 'ROLLBACK') throw err }) From 1871435d2c636b98d324c0e2061a1d0211b583d6 Mon Sep 17 00:00:00 2001 From: Bob den Os Date: Thu, 25 Sep 2025 22:13:15 +0200 Subject: [PATCH 2/2] Adopt new hana-client procedure output format --- hana/lib/HANAService.js | 3 +-- hana/lib/drivers/base.js | 9 ++++++++ hana/lib/drivers/hana-client.js | 41 +++++++++++---------------------- hana/lib/drivers/hdb.js | 5 ++++ hana/package.json | 8 +++---- 5 files changed, 33 insertions(+), 33 deletions(-) diff --git a/hana/lib/HANAService.js b/hana/lib/HANAService.js index c4de2f1d9..7c44c1b5f 100644 --- a/hana/lib/HANAService.js +++ b/hana/lib/HANAService.js @@ -1297,8 +1297,7 @@ SELECT ${mixing} FROM JSON_TABLE(SRC.JSON, '$' COLUMNS(${extraction}) ERROR ON E async onCall({ query, data }, name, schema) { const isAsync = /\sASYNC\s*$/.test(query) const outParameters = isAsync ? [{ PARAMETER_NAME: 'ASYNC_CALL_ID' }] : await this._getProcedureMetadata(name, schema) - const ps = await this.prepare(query) - const ret = this.ensureDBC() && await ps.proc(data, outParameters) + const ret = await this.ensureDBC().proc(query, data, outParameters) return isAsync ? ret.ASYNC_CALL_ID[0] : ret } diff --git a/hana/lib/drivers/base.js b/hana/lib/drivers/base.js index 428986cf2..f0321af4d 100644 --- a/hana/lib/drivers/base.js +++ b/hana/lib/drivers/base.js @@ -88,6 +88,15 @@ class HANADriver { return module.exports.prom(this._native, 'exec')(sql) } + /** + * Used to execute procedure SQL statement like CALL, DO BEGIN, CALL ASYNC + * @param {string} sql The SQL String to be executed + * @returns {Promise} The result from the database driver + */ + async proc(sql, data = []) { + throw new Error('Implementation missing "proc"') + } + set(variables) { this._native.set(variables) } diff --git a/hana/lib/drivers/hana-client.js b/hana/lib/drivers/hana-client.js index 8db235a9c..79e91f8f6 100644 --- a/hana/lib/drivers/hana-client.js +++ b/hana/lib/drivers/hana-client.js @@ -56,6 +56,12 @@ class HANAClientDriver extends driver { this._native.setAutoCommit(false) } + async proc(sql, data, outParameters) { + await this.connected + const rows = await prom(this._native, 'exec')(sql, data, {returnMultipleResultSets:true}) + return this._getResultForProcedure(rows, outParameters) + } + async prepare(sql, hasBlobs) { const ret = await super.prepare(sql) // hana-client ResultSet API does not allow for deferred streaming of blobs @@ -132,12 +138,6 @@ class HANAClientDriver extends driver { return { changes } } - ret.proc = async (data, outParameters) => { - const stmt = await ret._prep - const rows = await prom(stmt, 'execQuery')(data) - return this._getResultForProcedure(rows, outParameters, stmt) - } - ret.stream = async (values, one, objectMode) => { const stmt = await ret._prep values = Array.isArray(values) ? values : [] @@ -172,30 +172,17 @@ class HANAClientDriver extends driver { return this._native.state() === 'connected' } - async _getResultForProcedure(rows, outParameters, stmt) { - const result = {} - // build result from scalar params - const paramInfo = stmt.getParameterInfo() - for (let i = 0; i < paramInfo.length; i++) { - if (paramInfo[i].direction > 1) { - result[paramInfo[i].name] = stmt.getParameterValue(i) - } - } - - const resultSet = Array.isArray(rows) ? rows[0] : rows - const nextAsync = prom(resultSet, 'next') - const nextResultAsync = prom(resultSet, 'nextResult') + _getResultForProcedure(rows, outParameters) { + // on hdb, rows already contains results for scalar params + const isArray = Array.isArray(rows) + const result = isArray ? { ...rows[0] } : { ...rows } // merge table output params into scalar params - const params = Array.isArray(outParameters) && outParameters.filter(md => !(md.PARAMETER_NAME in result)) - if (params && params.length) { + const args = isArray ? rows.slice(1) : [] + if (args && args.length && outParameters) { + const params = outParameters.filter(md => !(md.PARAMETER_NAME in (isArray ? rows[0] : rows))) for (let i = 0; i < params.length; i++) { - const parameterName = params[i].PARAMETER_NAME - result[parameterName] = [] - while (resultSet.nextCanBlock() ? await nextAsync() : resultSet.next()) { - result[parameterName].push(resultSet.getValues()) - } - await nextResultAsync() + result[params[i].PARAMETER_NAME] = args[i] } } diff --git a/hana/lib/drivers/hdb.js b/hana/lib/drivers/hdb.js index 5c956c11f..6af26233b 100644 --- a/hana/lib/drivers/hdb.js +++ b/hana/lib/drivers/hdb.js @@ -82,6 +82,11 @@ class HDBDriver extends driver { }) } + async proc(sql, data, outParameters) { + const stmt = await this.prepare(sql) + return stmt.proc(data, outParameters) + } + async prepare(sql, hasBlobs) { const ret = await super.prepare(sql) diff --git a/hana/package.json b/hana/package.json index 546fa885a..739ca08c2 100644 --- a/hana/package.json +++ b/hana/package.json @@ -26,11 +26,11 @@ }, "dependencies": { "@cap-js/db-service": "^2.1.1", - "hdb": "^0.19.5" + "hdb": "^2.26.1" }, "peerDependencies": { - "@sap/hana-client": "^2", - "@sap/cds": ">=9" + "@sap/cds": ">=9", + "@sap/hana-client": "^2.27.8" }, "peerDependenciesMeta": { "@sap/hana-client": { @@ -38,7 +38,7 @@ } }, "devDependencies": { - "@sap/hana-client": ">=2" + "@sap/hana-client": ">=2.27.8" }, "cds": { "requires": {