From 7d8521af8ca6e01d9a521272d0409bce52d1ce7c Mon Sep 17 00:00:00 2001 From: Bob den Os <108393871+BobdenOs@users.noreply.github.com> Date: Wed, 29 Nov 2023 09:18:34 +0100 Subject: [PATCH] fix: large amounts of expands in HANA Service (#355) Co-authored-by: Johannes Vogel <31311694+johannes-vogel@users.noreply.github.com> --- hana/lib/HANAService.js | 3 ++- hana/lib/drivers/base.js | 17 ++++++++++------- hana/lib/drivers/hdb.js | 30 ++++++++++++++++++++---------- 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/hana/lib/HANAService.js b/hana/lib/HANAService.js index 3d8cb2ecb..03e45f402 100644 --- a/hana/lib/HANAService.js +++ b/hana/lib/HANAService.js @@ -180,7 +180,8 @@ class HANAService extends SQLService { }) break } else { - level.data.push(data) + // REVISIT: identify why sometimes not all parent rows are returned + level.data.push?.(data) levels.push({ data: data, path: row._path_, diff --git a/hana/lib/drivers/base.js b/hana/lib/drivers/base.js index b2dd83d06..127afff21 100644 --- a/hana/lib/drivers/base.js +++ b/hana/lib/drivers/base.js @@ -222,10 +222,11 @@ const handleLevel = function (levels, path, expands) { path: path.slice(0, -6), expands, }) + } else { + // Current row is on the same level now so incrementing the index + // If the index was not 0 it should add a comma + if (level.index++) buffer += ',' } - // Current row is on the same level now so incrementing the index - // If the index was not 0 it should add a comma - if (level.index++) buffer += ',' levels.push({ index: 0, suffix: '}', @@ -236,10 +237,12 @@ const handleLevel = function (levels, path, expands) { } else { // Step up if it is not a child of the current level const level = levels.pop() - const leftOverExpands = Object.keys(level.expands) - // Fill in all missing expands - if (leftOverExpands.length) { - buffer += leftOverExpands.map(p => `${JSON.stringify(p)}:${JSON.stringify(level.expands[p])}`).join(',') + if (level.suffix === '}') { + const leftOverExpands = Object.keys(level.expands) + // Fill in all missing expands + if (leftOverExpands.length) { + buffer += (level.hasProperties ? ',' : '') + leftOverExpands.map(p => `${JSON.stringify(p)}:${JSON.stringify(level.expands[p])}`).join(',') + } } if (level.suffix) buffer += level.suffix } diff --git a/hana/lib/drivers/hdb.js b/hana/lib/drivers/hdb.js index fcf2543b7..6c7e4e441 100644 --- a/hana/lib/drivers/hdb.js +++ b/hana/lib/drivers/hdb.js @@ -55,7 +55,7 @@ class HDBDriver extends driver { const ret = await super.prepare(sql) ret.stream = async (values, one) => { const stmt = await ret._prep - const rs = await prom(stmt, 'execute')(values) + const rs = await prom(stmt, 'execute')(values || []) const cols = rs.metadata // If the query only returns a single row with a single blob it is the final stream if (cols.length === 1 && cols[0].length === -1) { @@ -129,6 +129,10 @@ async function* rsIterator(rs, one) { this.reading = 0 this.writing = 0 }) + .catch(e => { + // TODO: check whether the error is early close + return true + }) } }, ensure(size) { @@ -251,8 +255,9 @@ async function* rsIterator(rs, one) { state.inject(handleLevel(levels, path, expands)) + // REVISIT: allow streaming with both NVARCHAR and NCLOB // Read and write JSON blob data - const jsonLength = readBlob(state) + const jsonLength = readString(state, true) let hasProperties = (typeof jsonLength === 'number' ? jsonLength : await jsonLength) > 2 for (const blobColumn of nativeBlobs) { @@ -304,9 +309,9 @@ async function* rsIterator(rs, one) { rs.close() } -const readString = function (state) { +const readString = function (state, isJson = false) { let ens = state.ensure(1) - if (ens) return ens.then(() => readString(state)) + if (ens) return ens.then(() => readString(state, isJson)) let length = state.buffer[state.reading] let offset = 1 @@ -315,21 +320,26 @@ const readString = function (state) { throw new Error('Missing stream metadata') case 0xf6: ens = state.ensure(2) - if (ens) return ens.then(() => readString(state)) - length = state.buffer.readInt16LE(state.reading) - offset = 2 + if (ens) return ens.then(() => readString(state, isJson)) + length = state.buffer.readInt16LE(state.reading + offset) + offset += 2 break case 0xf7: ens = state.ensure(4) - if (ens) return ens.then(() => readString(state)) - length = state.buffer.readInt32LE(state.reading) - offset = 4 + if (ens) return ens.then(() => readString(state, isJson)) + length = state.buffer.readInt32LE(state.reading + offset) + offset += 4 break default: } // Read the string value state.read(offset) + if (isJson) { + state.write(length - 1) + state.read(1) + return length + } return state.slice(length) }