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 16db4b6a9..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,28 +172,17 @@ class HANAClientDriver extends driver { return this._native.state() === 'connected' } - _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 + _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.next()) { - result[parameterName].push(resultSet.getValues()) - } - resultSet.nextResult() + 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": { 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 })