@cap-js/db-service 1.19.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,40 @@
4
4
  - The format is based on [Keep a Changelog](http://keepachangelog.com/).
5
5
  - This project adheres to [Semantic Versioning](http://semver.org/).
6
6
 
7
+ ## [2.0.0](https://github.com/cap-js/cds-dbs/compare/db-service-v1.20.0...db-service-v2.0.0) (2025-05-07)
8
+
9
+
10
+ ### ⚠ BREAKING CHANGES
11
+
12
+ * update peer dependency to @sap/cds@9 ([#1178](https://github.com/cap-js/cds-dbs/issues/1178))
13
+
14
+
15
+ ### Fixed
16
+
17
+ * Adopt to recurse `DistanceTo` cqn format ([#1093](https://github.com/cap-js/cds-dbs/issues/1093)) ([246e0b3](https://github.com/cap-js/cds-dbs/commit/246e0b38840f7e132ea49cae335b6be7a55354b3))
18
+ * current_utctimestamp as default ([#1161](https://github.com/cap-js/cds-dbs/issues/1161)) ([7c6b2f5](https://github.com/cap-js/cds-dbs/commit/7c6b2f5a6837afbeb1e24daef9a49e25cf7e92f0))
19
+ * exists within expression is properly detected ([#1156](https://github.com/cap-js/cds-dbs/issues/1156)) ([5a7b50c](https://github.com/cap-js/cds-dbs/commit/5a7b50cb02776cf6052c79bd276421dd87161882))
20
+ * resilience for query re-use scenarios ([#1175](https://github.com/cap-js/cds-dbs/issues/1175)) ([2352767](https://github.com/cap-js/cds-dbs/commit/2352767465ea88db77dc89bcaa76e268583146e1))
21
+
22
+
23
+ ### Changed
24
+
25
+ * update peer dependency to @sap/cds@9 ([#1178](https://github.com/cap-js/cds-dbs/issues/1178)) ([0507edd](https://github.com/cap-js/cds-dbs/commit/0507edd4e1dcb98983b1fb65ade1344d978b7524))
26
+
27
+ ## [1.20.0](https://github.com/cap-js/cds-dbs/compare/db-service-v1.19.1...db-service-v1.20.0) (2025-04-17)
28
+
29
+
30
+ ### Added
31
+
32
+ * Result set streaming ([#702](https://github.com/cap-js/cds-dbs/issues/702)) ([2fe02ea](https://github.com/cap-js/cds-dbs/commit/2fe02eafd02993e5697efbdab90ad997fb2c9e00))
33
+
34
+
35
+ ### Fixed
36
+
37
+ * **`expand`:** proper subquery `from` construction ([#1126](https://github.com/cap-js/cds-dbs/issues/1126)) ([e343e79](https://github.com/cap-js/cds-dbs/commit/e343e7978acc0c181a012f61b6181b7c558aa178)), closes [#1114](https://github.com/cap-js/cds-dbs/issues/1114) [#1112](https://github.com/cap-js/cds-dbs/issues/1112)
38
+ * Improved support for special characters in column names ([#1141](https://github.com/cap-js/cds-dbs/issues/1141)) ([ba04697](https://github.com/cap-js/cds-dbs/commit/ba046971921d645e8571a80c27ef07988c8c01ad))
39
+ * **infer:** for localized queries, use `localized.<entity>` as `_target` ([#1140](https://github.com/cap-js/cds-dbs/issues/1140)) ([b08707b](https://github.com/cap-js/cds-dbs/commit/b08707b76a53c74f1c4388a8be4d0818506388c5))
40
+
7
41
  ## [1.19.1](https://github.com/cap-js/cds-dbs/compare/db-service-v1.19.0...db-service-v1.19.1) (2025-04-01)
8
42
 
9
43
 
@@ -26,9 +26,9 @@ module.exports = class InsertResult {
26
26
  * Lazy access to auto-generated keys.
27
27
  */
28
28
  get [iterator]() {
29
- // For INSERT.as(SELECT.from(...)) return a dummy iterator with correct length
29
+ // For INSERT.from(SELECT.from(...)) return a dummy iterator with correct length
30
30
  const { INSERT } = this.query
31
- if (INSERT.as) {
31
+ if (INSERT.from || INSERT.as) {
32
32
  return (super[iterator] = function* () {
33
33
  for (let i = 0; i < this.affectedRows; i++) yield {}
34
34
  })
@@ -81,7 +81,7 @@ module.exports = class InsertResult {
81
81
  */
82
82
  get affectedRows() {
83
83
  const { INSERT: _ } = this.query
84
- if (_.as) return (super.affectedRows = this.affectedRows4(this.results[0] || this.results))
84
+ if (_.from || _.as) return (super.affectedRows = this.affectedRows4(this.results[0] || this.results))
85
85
  else return (super.affectedRows = _.entries?.length || _.rows?.length || this.results.length || 1)
86
86
  }
87
87
 
package/lib/SQLService.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const cds = require('@sap/cds'),
2
2
  DEBUG = cds.debug('sql|db')
3
- const { Readable } = require('stream')
3
+ const { Readable, Transform } = require('stream')
4
+ const { pipeline } = require('stream/promises')
4
5
  const { resolveView, getDBTable, getTransition } = require('@sap/cds/libx/_runtime/common/utils/resolveView')
5
6
  const DatabaseService = require('./common/DatabaseService')
6
7
  const cqn4sql = require('./cqn4sql')
@@ -117,7 +118,7 @@ class SQLService extends DatabaseService {
117
118
  * Handler for SELECT
118
119
  * @type {Handler}
119
120
  */
120
- async onSELECT({ query, data }) {
121
+ async onSELECT({ query, data, iterator, objectMode }) {
121
122
  // REVISIT: for custom joins, infer is called twice, which is bad
122
123
  // --> make cds.infer properly work with custom joins and remove this
123
124
  if (!(query._target instanceof cds.entity)) {
@@ -131,35 +132,56 @@ class SQLService extends DatabaseService {
131
132
  const { sql, values, cqn } = this.cqn2sql(query, data)
132
133
  const expand = query.SELECT.expand
133
134
  delete query.SELECT.expand
135
+ const isOne = cqn.SELECT.one || query.SELECT.from?.ref?.[0].cardinality?.max === 1
134
136
 
135
137
  let ps = await this.prepare(sql)
136
- let rows = await ps.all(values)
137
- if (rows.length)
138
- if (expand) rows = rows.map(r => (typeof r._json_ === 'string' ? JSON.parse(r._json_) : r._json_ || r))
139
-
140
- if (cds.env.features.stream_compat) {
141
- if (query._streaming) {
142
- this._changeToStreams(cqn.SELECT.columns, rows, true, true)
143
- if (!rows.length) return
144
-
145
- const result = rows[0]
146
- // stream is always on position 0. Further properties like etag are inserted later.
147
- let [key, val] = Object.entries(result)[0]
148
- result.value = val
149
- delete result[key]
150
-
151
- return result
138
+ let rows = iterator ? await ps.stream(values, isOne, objectMode) : await ps.all(values)
139
+ try {
140
+ if (rows.length)
141
+ if (expand) rows = rows.map(r => (typeof r._json_ === 'string' ? JSON.parse(r._json_) : r._json_ || r))
142
+
143
+ if (!iterator) {
144
+ // REVISIT: remove after removing stream_compat feature flag
145
+ if (cds.env.features.stream_compat) {
146
+ if (query._streaming) {
147
+ if (!rows.length) return
148
+ this._changeToStreams(cqn.SELECT.columns, rows, true, true)
149
+ const result = rows[0]
150
+
151
+ // stream is always on position 0. Further properties like etag are inserted later.
152
+ let [key, val] = Object.entries(result)[0]
153
+ result.value = val
154
+ delete result[key]
155
+
156
+ return result
157
+ }
158
+ } else {
159
+ this._changeToStreams(cqn.SELECT.columns, rows, query.SELECT.one, false)
160
+ }
161
+ } else if (objectMode) {
162
+ const converter = (row) => this._changeToStreams(cqn.SELECT.columns, row, true)
163
+ const changeToStreams = new Transform({
164
+ objectMode: true,
165
+ transform(row, enc, cb) {
166
+ converter(row)
167
+ cb(null, row)
168
+ }
169
+ })
170
+ pipeline(rows, changeToStreams)
171
+ rows = changeToStreams
152
172
  }
153
- } else {
154
- this._changeToStreams(cqn.SELECT.columns, rows, query.SELECT.one, false)
155
- }
156
173
 
157
- if (cqn.SELECT.count) {
158
- // REVISIT: the runtime always expects that the count is preserved with .map, required for renaming in mocks
159
- return SQLService._arrayWithCount(rows, await this.count(query, rows))
160
- }
174
+ if (cqn.SELECT.count) {
175
+ // REVISIT: the runtime always expects that the count is preserved with .map, required for renaming in mocks
176
+ return SQLService._arrayWithCount(rows, await this.count(query, rows))
177
+ }
161
178
 
162
- return cqn.SELECT.one || query.SELECT.from?.ref?.[0].cardinality?.max === 1 ? rows[0] : rows
179
+ return iterator !== false && isOne ? rows[0] : rows
180
+ } catch (err) {
181
+ // Ensure that iterators receive pre stream errors
182
+ if (iterator) rows.emit('error', err)
183
+ throw err
184
+ }
163
185
  }
164
186
 
165
187
  /**
@@ -314,7 +336,7 @@ class SQLService extends DatabaseService {
314
336
  * @returns {Promise<number>}
315
337
  */
316
338
  async count(query, ret) {
317
- if (ret) {
339
+ if (ret?.length) {
318
340
  const { one, limit: _ } = query.SELECT,
319
341
  n = ret.length
320
342
  const [max, offset = 0] = one ? [1] : _ ? [_.rows?.val, _.offset?.val] : []
@@ -355,6 +377,7 @@ class SQLService extends DatabaseService {
355
377
  // preserves $count for .map calls on array
356
378
  static _arrayWithCount = function (a, count) {
357
379
  const _map = a.map
380
+
358
381
  const map = function (..._) {
359
382
  return SQLService._arrayWithCount(_map.call(a, ..._), count)
360
383
  }
@@ -472,17 +495,16 @@ class PreparedStatement {
472
495
  }
473
496
  SQLService.prototype.PreparedStatement = PreparedStatement
474
497
 
498
+ /** @param {import('@sap/cds').ql.Query} q */
475
499
  const _target_name4 = q => {
476
- const target =
477
- q._target_ref ||
478
- q.from_into_ntt ||
479
- q.SELECT?.from ||
480
- q.INSERT?.into ||
481
- q.UPSERT?.into ||
482
- q.UPDATE?.entity ||
483
- q.DELETE?.from ||
484
- q.CREATE?.entity ||
485
- q.DROP?.entity
500
+ const target = q._subject
501
+ || q.SELECT?.from
502
+ || q.INSERT?.into
503
+ || q.UPSERT?.into
504
+ || q.UPDATE?.entity
505
+ || q.DELETE?.from
506
+ || q.CREATE?.entity
507
+ || q.DROP?.entity
486
508
  if (target?.SET?.op === 'union') throw new cds.error('UNION-based queries are not supported')
487
509
  if (!target?.ref) return target
488
510
  const [first] = target.ref
@@ -1,5 +1,7 @@
1
1
  'use strict'
2
2
 
3
+ const cds = require('@sap/cds')
4
+
3
5
  // OData: https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_CanonicalFunctions
4
6
  const StandardFunctions = {
5
7
  /**
@@ -18,10 +20,15 @@ const StandardFunctions = {
18
20
  } catch {
19
21
  val = sub[2] || sub[3] || ''
20
22
  }
21
- arg.val = arg.__proto__.val = val
23
+ arg.val = val
22
24
  const refs = ref.list
23
- const { toString } = ref
24
- return '(' + refs.map(ref2 => this.contains(this.tolower(toString(ref2)), this.tolower(arg))).join(' or ') + ')'
25
+ return `(${refs.map(ref => this.expr({
26
+ func: 'contains',
27
+ args: [
28
+ { func: 'tolower', args: [ref] },
29
+ { func: 'tolower', args: [arg] },
30
+ ]
31
+ })).join(' or ')})`
25
32
  },
26
33
 
27
34
  // ==============================
@@ -141,7 +148,7 @@ const StandardFunctions = {
141
148
  * @returns {string} - SQL statement
142
149
  */
143
150
  now: function () {
144
- return this.session_context({ val: '$now' })
151
+ return this.expr({ func: 'session_context', args: [{ val: '$now' }] })
145
152
  },
146
153
 
147
154
  /**
@@ -184,6 +191,226 @@ const HANAFunctions = {
184
191
  * @returns {string} - SQL statement
185
192
  */
186
193
  current_timestamp: p => (p ? `current_timestamp(${p})` : 'current_timestamp'),
194
+
195
+ /**
196
+ * Generates SQL statement for the hierarchy function
197
+ * @param {string} [p] -
198
+ * @returns {string} - SQL statement
199
+ */
200
+ HIERARCHY: function (args) {
201
+ let uniqueCounter = this._with?.length ?? 0
202
+ let src = args.xpr[1]
203
+
204
+ // Ensure that the orderBy column are exposed by the source for hierarchy sorting
205
+ const orderBy = args.xpr.find((_, i, arr) => /ORDER/i.test(arr[i - 2]) && /BY/i.test(arr[i - 1]))
206
+
207
+ const passThroughColumns = src.SELECT.columns.map(c => ({ ref: ['Source', this.column_name(c)] }))
208
+ src.as = 'H' + (uniqueCounter++)
209
+ src = this.expr(this.with(src))
210
+
211
+ let recursive = cds.ql(`
212
+ SELECT
213
+ 1 as HIERARCHY_LEVEL,
214
+ NODE_ID as HIERARCHY_ROOT_ID
215
+ FROM ${src} AS Source
216
+ WHERE parent_ID IS NULL
217
+ UNION ALL
218
+ SELECT
219
+ Parent.HIERARCHY_LEVEL + 1,
220
+ Parent.HIERARCHY_ROOT_ID
221
+ FROM ${src} AS Source
222
+ JOIN H${uniqueCounter} AS Parent ON Source.PARENT_ID=Parent.NODE_ID
223
+ ORDER BY HIERARCHY_LEVEL DESC${orderBy ? `,${orderBy}` : ''}`)
224
+ recursive.as = 'H' + (uniqueCounter++)
225
+ recursive.SET.args[0].SELECT.columns = [...recursive.SET.args[0].SELECT.columns, ...passThroughColumns]
226
+ recursive.SET.args[1].SELECT.columns = [...recursive.SET.args[1].SELECT.columns, ...passThroughColumns]
227
+ recursive = this.expr(this.with(recursive))
228
+
229
+ let ranked = cds.ql(`
230
+ SELECT
231
+ HIERARCHY_LEVEL,
232
+ row_number() over () as HIERARCHY_RANK,
233
+ HIERARCHY_ROOT_ID
234
+ FROM ${recursive} AS Source`)
235
+ ranked.as = 'H' + (uniqueCounter++)
236
+ ranked.SELECT.columns = [...ranked.SELECT.columns, ...passThroughColumns]
237
+ ranked = this.expr(this.with(ranked))
238
+
239
+ let Hierarchy = cds.ql(`
240
+ SELECT
241
+ HIERARCHY_LEVEL,
242
+ HIERARCHY_RANK,
243
+ (SELECT HIERARCHY_RANK FROM ${ranked} AS Ranked WHERE Ranked.NODE_ID = Source.PARENT_ID) AS HIERARCHY_PARENT_RANK,
244
+ (SELECT HIERARCHY_RANK FROM ${ranked} AS Ranked WHERE Ranked.NODE_ID = Source.HIERARCHY_ROOT_ID) AS HIERARCHY_ROOT_RANK,
245
+ coalesce(
246
+ (SELECT MIN(HIERARCHY_RANK) FROM ${ranked} AS Ranked WHERE Ranked.HIERARCHY_RANK > Source.HIERARCHY_RANK AND Ranked.HIERARCHY_LEVEL <= Source.HIERARCHY_LEVEL),
247
+ (SELECT MAX(HIERARCHY_RANK) + 1 FROM ${ranked})
248
+ ) - Source.HIERARCHY_RANK AS HIERARCHY_TREE_SIZE
249
+ FROM ${ranked} AS Source`)
250
+ Hierarchy.as = 'H' + (uniqueCounter++)
251
+ Hierarchy.SELECT.columns = [...Hierarchy.SELECT.columns, ...passThroughColumns]
252
+ Hierarchy = this.expr(this.with(Hierarchy))
253
+
254
+ return Hierarchy
255
+ },
256
+
257
+ /**
258
+ * Generates SQL statement for the hierarchy_descendants function
259
+ * @param {string} [p] -
260
+ * @returns {string} - SQL statement
261
+ */
262
+ HIERARCHY_DESCENDANTS: function (args) {
263
+ // Find Hierarchy function call source query
264
+ const passThroughColumns = args.xpr[1].args[0].xpr[1].SELECT.columns.map(c => ({ ref: [this.column_name(c)] }))
265
+ // REVISIT: currently only supports func: HIERARCHY as source
266
+ const src = this.expr(args.xpr[1])
267
+
268
+ let uniqueCounter = this._with?.length ?? 0
269
+
270
+ let alias = args.xpr.find((_, i, arr) => /AS/i.test(arr[i - 1]))
271
+ const where = args.xpr.find((a, i, arr) => a.xpr && /WHERE/i.test(arr[i - 1]) && /START/i.test(arr[i - 2]))
272
+ const distance = args.xpr.find((a, i, arr) => typeof a.val === 'number' && (/DISTANCE/i.test(arr[i - 1]) || /DISTANCE/i.test(arr[i - 2])))
273
+ const distanceFrom = args.xpr.find((a, i, arr) => /FROM/.test(a) && /DISTANCE/i.test(arr[i - 1]))
274
+
275
+ if (alias.startsWith('"') && alias.endsWith('"')) alias = alias.slice(1, -1).replace(/""/g, '"')
276
+
277
+ let HierarchyDescendants = cds.ql(`
278
+ SELECT
279
+ HIERARCHY_LEVEL,
280
+ HIERARCHY_PARENT_RANK,
281
+ HIERARCHY_RANK,
282
+ HIERARCHY_ROOT_RANK,
283
+ HIERARCHY_TREE_SIZE,
284
+ 0 as HIERARCHY_DISTANCE
285
+ FROM ${src} AS ![${alias}]
286
+ UNION ALL
287
+ SELECT
288
+ Source.HIERARCHY_LEVEL,
289
+ Source.HIERARCHY_PARENT_RANK,
290
+ Source.HIERARCHY_RANK,
291
+ Source.HIERARCHY_ROOT_RANK,
292
+ Source.HIERARCHY_TREE_SIZE,
293
+ Child.HIERARCHY_DISTANCE + 1
294
+ FROM ${src} AS Source
295
+ JOIN H${uniqueCounter} AS Child ON Source.PARENT_ID=Child.NODE_ID`)
296
+ HierarchyDescendants.as = 'H' + uniqueCounter
297
+ HierarchyDescendants.SET.args[0].SELECT.where = where.xpr
298
+ HierarchyDescendants.SET.args[0].SELECT.columns = [...HierarchyDescendants.SET.args[0].SELECT.columns, ...passThroughColumns.map(r => ({ ref: [alias, r.ref[0]] }))]
299
+ HierarchyDescendants.SET.args[1].SELECT.columns = [...HierarchyDescendants.SET.args[1].SELECT.columns, ...passThroughColumns.map(r => ({ ref: ['Source', r.ref[0]] }))]
300
+
301
+ HierarchyDescendants = this.with(HierarchyDescendants)
302
+ HierarchyDescendants.as = 'HierarchyDescendants'
303
+
304
+ return this.expr({
305
+ SELECT: {
306
+ columns: [
307
+ { ref: ['HIERARCHY_LEVEL'] },
308
+ { ref: ['HIERARCHY_PARENT_RANK'] },
309
+ { ref: ['HIERARCHY_RANK'] },
310
+ { ref: ['HIERARCHY_ROOT_RANK'] },
311
+ { ref: ['HIERARCHY_TREE_SIZE'] },
312
+ {
313
+ SELECT: {
314
+ columns: [{ func: 'MAX', args: [{ ref: ['HIERARCHY_DISTANCE'] }] }],
315
+ from: HierarchyDescendants,
316
+ where: [{ ref: [HierarchyDescendants.as, 'HIERARCHY_RANK'] }, '=', { ref: [src, 'HIERARCHY_RANK'] }]
317
+ },
318
+ as: 'HIERARCHY_DISTANCE',
319
+ },
320
+ ...passThroughColumns,
321
+ ],
322
+ from: { ref: [src] },
323
+ where: [
324
+ { ref: ['HIERARCHY_RANK'] },
325
+ 'IN',
326
+ {
327
+ SELECT: {
328
+ columns: [{ ref: ['HIERARCHY_RANK'] }],
329
+ from: HierarchyDescendants,
330
+ where: [{ ref: ['HIERARCHY_DISTANCE'] }, distanceFrom ? '>=' : '=', distance]
331
+ }
332
+ }
333
+ ]
334
+ }
335
+ })
336
+ },
337
+
338
+ /**
339
+ * Generates SQL statement for the hierarchy_ancestors function
340
+ * @param {string} [p] -
341
+ * @returns {string} - SQL statement
342
+ */
343
+ HIERARCHY_ANCESTORS: function (args) {
344
+ // Find Hierarchy function call source query
345
+ const passThroughColumns = args.xpr[1].args[0].xpr[1].SELECT.columns.map(c => ({ ref: [this.column_name(c)] }))
346
+ // REVISIT: currently only supports func: HIERARCHY as source
347
+ const src = this.expr(args.xpr[1])
348
+
349
+ let uniqueCounter = this._with?.length ?? 0
350
+
351
+ let alias = args.xpr.find((_, i, arr) => /AS/i.test(arr[i - 1]))
352
+ const where = args.xpr.find((a, i, arr) => a.xpr && /WHERE/i.test(arr[i - 1]) && /START/i.test(arr[i - 2]))
353
+
354
+ if (alias.startsWith('"') && alias.endsWith('"')) alias = alias.slice(1, -1).replace(/""/g, '"')
355
+
356
+ let HierarchyAncestors = cds.ql(`
357
+ SELECT
358
+ HIERARCHY_LEVEL,
359
+ HIERARCHY_PARENT_RANK,
360
+ HIERARCHY_RANK,
361
+ HIERARCHY_ROOT_RANK,
362
+ HIERARCHY_TREE_SIZE,
363
+ 0 as HIERARCHY_DISTANCE
364
+ FROM ${src} AS ![${alias}]
365
+ UNION ALL
366
+ SELECT
367
+ Source.HIERARCHY_LEVEL,
368
+ Source.HIERARCHY_PARENT_RANK,
369
+ Source.HIERARCHY_RANK,
370
+ Source.HIERARCHY_ROOT_RANK,
371
+ Source.HIERARCHY_TREE_SIZE,
372
+ Child.HIERARCHY_DISTANCE - 1
373
+ FROM ${src} AS Source
374
+ JOIN H${uniqueCounter} AS Child ON Source.NODE_ID=Child.PARENT_ID`)
375
+ HierarchyAncestors.as = 'H' + uniqueCounter
376
+ HierarchyAncestors.SET.args[0].SELECT.where = where.xpr
377
+ HierarchyAncestors.SET.args[0].SELECT.columns = [...HierarchyAncestors.SET.args[0].SELECT.columns, ...passThroughColumns.map(r => ({ ref: [alias, r.ref[0]] }))]
378
+ HierarchyAncestors.SET.args[1].SELECT.columns = [...HierarchyAncestors.SET.args[1].SELECT.columns, ...passThroughColumns.map(r => ({ ref: ['Source', r.ref[0]] }))]
379
+
380
+ HierarchyAncestors = this.with(HierarchyAncestors)
381
+ HierarchyAncestors.as = 'HierarchyAncestors'
382
+ return this.expr({
383
+ SELECT: {
384
+ columns: [
385
+ { ref: ['HIERARCHY_LEVEL'] },
386
+ { ref: ['HIERARCHY_PARENT_RANK'] },
387
+ { ref: ['HIERARCHY_RANK'] },
388
+ { ref: ['HIERARCHY_ROOT_RANK'] },
389
+ { ref: ['HIERARCHY_TREE_SIZE'] },
390
+ {
391
+ SELECT: {
392
+ columns: [{ func: 'MIN', args: [{ ref: ['HIERARCHY_DISTANCE'] }] }],
393
+ from: HierarchyAncestors,
394
+ where: [{ ref: [HierarchyAncestors.as, 'HIERARCHY_RANK'] }, '=', { ref: [src, 'HIERARCHY_RANK'] }]
395
+ },
396
+ as: 'HIERARCHY_DISTANCE',
397
+ },
398
+ ...passThroughColumns,
399
+ ],
400
+ from: { ref: [src] },
401
+ where: [
402
+ { ref: ['HIERARCHY_RANK'] },
403
+ 'IN',
404
+ {
405
+ SELECT: {
406
+ columns: [{ ref: ['HIERARCHY_RANK'] }],
407
+ from: HierarchyAncestors,
408
+ }
409
+ }
410
+ ]
411
+ }
412
+ })
413
+ },
187
414
  }
188
415
 
189
416
  for (let each in HANAFunctions) HANAFunctions[each.toUpperCase()] = HANAFunctions[each]