diff --git a/hana/lib/HANAService.js b/hana/lib/HANAService.js index e84c5be23..109b01520 100644 --- a/hana/lib/HANAService.js +++ b/hana/lib/HANAService.js @@ -102,9 +102,9 @@ class HANAService extends SQLService { // REVISIT: disable this for queries like (SELECT 1) // Will return multiple rows with objects inside query.SELECT.expand = 'root' - const { cqn, temporary, blobs, values } = this.cqn2sql(query, data) + const { cqn, temporary, blobs, withclause, values } = this.cqn2sql(query, data) // REVISIT: add prepare options when param:true is used - const sqlScript = this.wrapTemporary(temporary, blobs) + const sqlScript = this.wrapTemporary(temporary, withclause, blobs) let rows = values?.length ? await (await this.prepare(sqlScript)).all(values) : await this.exec(sqlScript) if (rows.length) { rows = this.parseRows(rows) @@ -131,7 +131,7 @@ class HANAService extends SQLService { } async onSTREAM(req) { - let { cqn, sql, values, temporary, blobs } = this.cqn2sql(req.query) + let { cqn, sql, values, temporary, withclause, blobs } = this.cqn2sql(req.query) // writing stream if (req.query.STREAM.into) { const ps = await this.prepare(sql) @@ -140,7 +140,7 @@ class HANAService extends SQLService { // reading stream if (temporary?.length) { // Full SELECT CQN support streaming - sql = this.wrapTemporary(temporary, blobs) + sql = this.wrapTemporary(temporary, withclause, blobs) } const ps = await this.prepare(sql) const stream = await ps.stream(values, cqn.SELECT?.one) @@ -149,20 +149,17 @@ class HANAService extends SQLService { } // Allow for running complex expand queries in a single statement - wrapTemporary(temporary, blobs) { + wrapTemporary(temporary, withclauses, blobs) { const blobColumn = b => `"${b.replace(/"/g, '""')}"` const values = temporary .map(t => { - if (blobs.length) { - const localBlobs = t.blobs - const blobColumns = blobs.filter(b => !(b in localBlobs)).map(b => `NULL AS ${blobColumn(b)}`) - if (blobColumns.length) return `SELECT ${blobColumns},${t.select}` - } - return `SELECT ${t.select}` + const blobColumns = blobs.map(b => (b in t.blobs) ? blobColumn(b) : `NULL AS ${blobColumn(b)}`) + return `SELECT "_path_","_blobs_","_expands_","_json_"${blobColumns.length ? ',' : ''}${blobColumns} FROM (${t.select})` }) - const ret = values.length === 1 ? values[0] : 'SELECT * FROM ' + values.map(v => `(${v})`).join(' UNION ALL ') + ' ORDER BY "_path_" ASC' + const withclause = withclauses.length ? `WITH ${withclauses} ` : '' + const ret = withclause + (values.length === 1 ? values[0] : 'SELECT * FROM ' + values.map(v => `(${v})`).join(' UNION ALL ') + ' ORDER BY "_path_" ASC') DEBUG?.(ret) return ret } @@ -265,9 +262,17 @@ class HANAService extends SQLService { SELECT(q) { // Collect all queries and blob columns of all queries this.blobs = this.blobs || [] + this.withclause = this.withclause || [] this.temporary = this.temporary || [] this.temporaryValues = this.temporaryValues || [] + const walkAlias = q => { + if (q.args) return q.as || walkAlias(q.args[0]) + if (q.SELECT?.from) return walkAlias(q.SELECT?.from) + return q.as + } + const alias = walkAlias(q) + q.as = alias const src = q const { limit, one, orderBy, expand, columns, localized, count, from, parent } = q.SELECT @@ -289,7 +294,7 @@ class HANAService extends SQLService { if (parent) { // Track parent _path_ for later concatination if (!columns.find(c => this.column_name(c) === '_path_')) - columns.push({ ref: [parent.as, '_path_'], as: '_path_' }) + columns.push({ ref: [parent.as, '_path_'], as: '_parent_path_' }) } if (orderBy) { @@ -309,7 +314,7 @@ class HANAService extends SQLService { // Insert row number column for reducing or sorting the final result const over = { xpr: [] } // TODO: replace with full path partitioning - if (parent) over.xpr.push(`PARTITION BY ${this.ref({ ref: ['_path_'] })}`) + if (parent) over.xpr.push(`PARTITION BY ${this.ref({ ref: ['_parent_path_'] })}`) if (orderBy) over.xpr.push(` ORDER BY ${this.orderBy(orderBy, localized)}`) const rn = { xpr: [{ func: 'ROW_NUMBER', args: [] }, 'OVER', over], as: '$$RN$$' } q.as = q.SELECT.from.as @@ -318,6 +323,7 @@ class HANAService extends SQLService { q.as = q.SELECT.from.as q = cds.ql.SELECT(outputColumns.map(c => (c.elements ? c : { __proto__: c, ref: [this.column_name(c)] }))).from(q) + q.as = q.SELECT.from.as Object.defineProperty(q, 'elements', { value: elements }) Object.defineProperty(q, 'element', { value: element }) @@ -330,7 +336,7 @@ class HANAService extends SQLService { ? [ { func: 'concat', - args: [{ ref: ['_path_'] }, { val: `].${q.element.name}[`, param: false }], + args: [{ ref: ['_parent_path_'] }, { val: `].${q.element.name}[`, param: false }], }, { func: 'lpad', args: [{ ref: ['$$RN$$'] }, { val: 6, param: false }, { val: '0', param: false }] }, ] @@ -375,7 +381,8 @@ class HANAService extends SQLService { if (expand === 'root') { this.cqn = q - this.temporary.unshift({ blobs: this._blobs, select: this.sql.substring(7) }) + this.withclause.unshift(`${this.quote(alias)} as (${this.sql})`) + this.temporary.unshift({ blobs: this._blobs, select: `SELECT ${this._outputColumns} FROM ${this.quote(alias)}` }) if (this.values) { this.temporaryValues.unshift(this.values) this.values = this.temporaryValues.flat() @@ -401,9 +408,7 @@ class HANAService extends SQLService { if (x.elements) { expands[this.column_name(x)] = x.SELECT.one ? null : [] - const parent = cds.ql.clone(src) - parent.as = parent.SELECT.from.as || parent.SELECT.from.args[0].as - parent.SELECT.expand = true + const parent = src x.element._foreignKeys.forEach(k => { if (!parent.SELECT.columns.find(c => this.column_name(c) === k.parentElement.name)) { parent.SELECT.columns.push({ ref: [parent.as, k.parentElement.name] }) @@ -412,7 +417,7 @@ class HANAService extends SQLService { x.SELECT.from = { join: 'inner', - args: [parent, x.SELECT.from], + args: [{ ref: [parent.as], as: parent.as }, x.SELECT.from], on: x.SELECT.where, as: x.SELECT.from.as, } @@ -485,28 +490,16 @@ class HANAService extends SQLService { // Calculate final output columns once let outputColumns = '' - outputColumns = `${path} as "_path_",${blobs} as "_blobs_",${expands} as "_expands_",${jsonColumn}` + outputColumns = `_path_ as "_path_",${blobs} as "_blobs_",${expands} as "_expands_",${jsonColumn}` if (blobColumns.length) outputColumns = `${outputColumns},${blobColumns.map(b => `${this.quote(b)} as "${b.replace(/"/g, '""')}"`)}` - if (this.foreignKeys?.length) { - outputColumns += ',' + this.foreignKeys.map(c => this.column_expr({ ref: c.ref.slice(-1) })) - } - - if (structures.length && sql.length) { - this._outputColumns = outputColumns - // Select all columns to be able to use the _outputColumns in the outer select - sql = `*,${rawJsonColumn}` - } else { - sql = outputColumns - } + this._outputColumns = outputColumns + sql = `*,${path} as _path_,${rawJsonColumn}` } return sql } SELECT_expand(_, sql) { - if (this._outputColumns) { - return `SELECT ${this._outputColumns} FROM (${sql})` - } return sql }