@cap-js/db-service 1.0.0 → 1.1.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/lib/cqn4sql.js CHANGED
@@ -70,13 +70,14 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
70
70
  // Transform the from clause: association path steps turn into `WHERE EXISTS` subqueries.
71
71
  // The already transformed `where` clause is then glued together with the resulting subqueries.
72
72
  const { transformedWhere, transformedFrom } = getTransformedFrom(from || entity, transformedProp.where)
73
+ const queryNeedsJoins = inferred.joinTree && !inferred.joinTree.isInitial
73
74
 
74
75
  if (inferred.SELECT) {
75
76
  transformedQuery = transformSelectQuery(queryProp, transformedFrom, transformedWhere, transformedQuery)
76
77
  } else {
77
78
  if (from) {
78
79
  transformedProp.from = transformedFrom
79
- } else {
80
+ } else if (!queryNeedsJoins) {
80
81
  transformedProp.entity = transformedFrom
81
82
  }
82
83
 
@@ -94,7 +95,7 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
94
95
  }
95
96
  }
96
97
 
97
- if (inferred.joinTree && !inferred.joinTree.isInitial) {
98
+ if (queryNeedsJoins) {
98
99
  transformedQuery[kind].from = translateAssocsToJoins(transformedQuery[kind].from)
99
100
  }
100
101
  }
@@ -119,7 +120,7 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
119
120
  if (columns) {
120
121
  transformedQuery.SELECT.columns = getTransformedColumns(columns)
121
122
  } else {
122
- transformedQuery.SELECT.columns = getColumnsForWildcard()
123
+ transformedQuery.SELECT.columns = getColumnsForWildcard(originalQuery.SELECT?.excluding)
123
124
  }
124
125
 
125
126
  // Like the WHERE clause, aliases from the SELECT list are not accessible for `group by`/`having` (in most DB's)
@@ -158,7 +159,7 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
158
159
  *
159
160
  * @param {string} kind - The type of operation: "INSERT" or "UPSERT".
160
161
  *
161
- * @returns {Object} - The transformed query with updated `into` clause.
162
+ * @returns {object} - The transformed query with updated `into` clause.
162
163
  */
163
164
  function transformQueryForInsertUpsert(kind) {
164
165
  const { as } = transformedQuery[kind].into
@@ -170,10 +171,10 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
170
171
  /**
171
172
  * Transforms a stream query, replacing the `where` and `into` clauses after processing.
172
173
  *
173
- * @param {Object} inferred - The inferred object containing the STREAM query.
174
- * @param {Object} transformedQuery - The query object to be transformed.
174
+ * @param {object} inferred - The inferred object containing the STREAM query.
175
+ * @param {object} transformedQuery - The query object to be transformed.
175
176
  *
176
- * @returns {Object} - The transformed query with updated STREAM clauses.
177
+ * @returns {object} - The transformed query with updated STREAM clauses.
177
178
  */
178
179
  function transformStreamQuery() {
179
180
  const { into, where } = inferred.STREAM
@@ -193,8 +194,8 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
193
194
  /**
194
195
  * Transforms a search expression to a WHERE clause for a SELECT operation.
195
196
  *
196
- * @param {Object} search - The search expression which shall be applied to the searchable columns on the query source.
197
- * @param {Object} from - The FROM clause of the CQN statement.
197
+ * @param {object} search - The search expression which shall be applied to the searchable columns on the query source.
198
+ * @param {object} from - The FROM clause of the CQN statement.
198
199
  *
199
200
  * @returns {(Object|Array|undefined)} - If the target of the query contains searchable elements, the function returns an array that represents the WHERE clause.
200
201
  * If the SELECT query already contains a WHERE clause, this array includes the existing clause and appends an AND condition with the new 'contains' clause.
@@ -293,6 +294,10 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
293
294
  }
294
295
  }
295
296
 
297
+ function isCalculatedOnRead(def) {
298
+ return def?.value && !def.value.stored
299
+ }
300
+
296
301
  /**
297
302
  * Walks over a list of columns (ref's, xpr, subqueries, val), applies flattening on structured types and expands wildcards.
298
303
  *
@@ -301,15 +306,27 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
301
306
  */
302
307
  function getTransformedColumns(columns) {
303
308
  const transformedColumns = []
304
-
305
309
  for (let i = 0; i < columns.length; i++) {
306
310
  const col = columns[i]
307
311
 
308
- if (col.expand) {
312
+ if (isCalculatedOnRead(col.$refLinks?.[col.$refLinks.length - 1].definition)) {
313
+ const calcElement = resolveCalculatedElement(col)
314
+ transformedColumns.push(calcElement)
315
+ } else if (col.expand) {
316
+ if (col.ref?.length > 1 && col.ref[0] === '$self' && !col.$refLinks[0].definition.kind) {
317
+ const dollarSelfReplacement = calculateDollarSelfColumn(col)
318
+ transformedColumns.push(...getTransformedColumns([dollarSelfReplacement]))
319
+ continue
320
+ }
309
321
  handleExpand(col)
310
322
  } else if (col.inline) {
311
323
  handleInline(col)
312
324
  } else if (col.ref) {
325
+ if (col.ref.length > 1 && col.ref[0] === '$self' && !col.$refLinks[0].definition.kind) {
326
+ const dollarSelfReplacement = calculateDollarSelfColumn(col)
327
+ transformedColumns.push(...getTransformedColumns([dollarSelfReplacement]))
328
+ continue
329
+ }
313
330
  handleRef(col)
314
331
  } else if (col === '*') {
315
332
  handleWildcard(columns)
@@ -391,7 +408,7 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
391
408
  if (replaceWith === -1) transformedColumns.push(transformedColumn)
392
409
  else transformedColumns.splice(replaceWith, 1, transformedColumn)
393
410
 
394
- Object.defineProperty(transformedColumn, 'element', { value: originalQuery.elements[col.as] })
411
+ setElementOnColumns(transformedColumn, originalQuery.elements[col.as])
395
412
  }
396
413
 
397
414
  function getTransformedColumn(col) {
@@ -424,6 +441,88 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
424
441
  }
425
442
  }
426
443
 
444
+ function resolveCalculatedElement(column, omitAlias = false, baseLink = null) {
445
+ let value
446
+
447
+ if (column.$refLinks) {
448
+ const { $refLinks } = column
449
+ value = $refLinks[$refLinks.length - 1].definition.value
450
+ baseLink = [...column.$refLinks].reverse().find(link => link.definition.isAssociation) || baseLink
451
+ } else {
452
+ value = column.value
453
+ }
454
+ const { ref, val, xpr, func } = value
455
+
456
+ let res
457
+ if (ref) {
458
+ res = getTransformedTokenStream([value], baseLink)[0]
459
+ } else if (xpr) {
460
+ res = { xpr: getTransformedTokenStream(value.xpr, baseLink) }
461
+ } else if (val) {
462
+ res = { val }
463
+ } else if (func) res = { args: getTransformedTokenStream(value.args), func: value.func }
464
+ if (!omitAlias) res.as = column.as || column.name || column.flatName
465
+ return res
466
+ }
467
+
468
+ /**
469
+ * This function resolves a `ref` starting with a `$self`.
470
+ * Such a path targets another element of the query by it's implicit, or explicit alias.
471
+ *
472
+ * A `$self` reference may also target another `$self` path. In this case, this function
473
+ * recursively resolves the tail of the `$self` references (`$selfPath.ref.slice(2)`) onto it's
474
+ * new base.
475
+ *
476
+ * @param {object} col with a ref like `[ '$self', <target column>, <optional further path navigation> ]`
477
+ * @param {boolean} omitAlias if we replace a $self reference in an aggregation or a token stream, we must not add an "as" to the result
478
+ */
479
+ function calculateDollarSelfColumn(col, omitAlias = false) {
480
+ const dummyColumn = buildDummyColumnForDollarSelf({ ...col }, col.$refLinks)
481
+
482
+ return dummyColumn
483
+
484
+ function buildDummyColumnForDollarSelf(dollarSelfColumn, $refLinks) {
485
+ const { ref, as } = dollarSelfColumn
486
+ const stepToFind = ref[1]
487
+ let referencedColumn = inferred.SELECT.columns.find(
488
+ otherColumn =>
489
+ otherColumn !== dollarSelfColumn &&
490
+ (otherColumn.as
491
+ ? stepToFind === otherColumn.as
492
+ : stepToFind === otherColumn.ref?.[otherColumn.ref.length - 1]),
493
+ )
494
+ if (referencedColumn.ref?.[0] === '$self') {
495
+ referencedColumn = buildDummyColumnForDollarSelf({ ...referencedColumn }, referencedColumn.$refLinks)
496
+ }
497
+
498
+ if (referencedColumn.ref) {
499
+ dollarSelfColumn.ref = [...referencedColumn.ref, ...dollarSelfColumn.ref.slice(2)]
500
+ Object.defineProperties(dollarSelfColumn, {
501
+ flatName: {
502
+ value: dollarSelfColumn.ref.join('_'),
503
+ },
504
+ isJoinRelevant: {
505
+ value: referencedColumn.isJoinRelevant,
506
+ },
507
+ $refLinks: {
508
+ value: [...referencedColumn.$refLinks, ...$refLinks.slice(2)],
509
+ },
510
+ })
511
+ } else {
512
+ // target column is `val` or `xpr`, destructure and throw away the ref with the $self
513
+ // eslint-disable-next-line no-unused-vars
514
+ const { xpr, val, ref, as: _as, ...rest } = referencedColumn
515
+ if (xpr) rest.xpr = xpr
516
+ else rest.val = val
517
+ dollarSelfColumn = { ...rest } // reassign dummyColumn without 'ref'
518
+ if (!omitAlias) dollarSelfColumn.as = as
519
+ }
520
+ return dollarSelfColumn.ref?.[0] === '$self'
521
+ ? buildDummyColumnForDollarSelf({ ...dollarSelfColumn }, $refLinks)
522
+ : dollarSelfColumn
523
+ }
524
+ }
525
+
427
526
  /**
428
527
  * Calculates the columns for a nested projection on a structure.
429
528
  *
@@ -565,7 +664,7 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
565
664
  *
566
665
  * @param {CSN.column} column - The column with the 'expand' property to be transformed into a subquery.
567
666
  *
568
- * @returns {Object} Returns a subquery correlated with the enclosing query, with added properties `expand:true` and `one:true|false`.
667
+ * @returns {object} Returns a subquery correlated with the enclosing query, with added properties `expand:true` and `one:true|false`.
569
668
  */
570
669
  function expandColumn(column) {
571
670
  let outerAlias
@@ -622,7 +721,7 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
622
721
  if (isLocalized(inferred.target)) subquery.SELECT.localized = true
623
722
  const expanded = transformSubquery(subquery)
624
723
  const correlated = _correlate({ ...expanded, as: columnAlias }, outerAlias)
625
- Object.defineProperty(correlated, 'elements', { value: subquery.elements })
724
+ Object.defineProperty(correlated, 'elements', { value: subquery.elements, writable: true })
626
725
  return correlated
627
726
 
628
727
  function _correlate(subq, outer) {
@@ -659,7 +758,10 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
659
758
  const res = []
660
759
  for (let i = 0; i < columns.length; i++) {
661
760
  const col = columns[i]
662
- if (col.isJoinRelevant) {
761
+ if (isCalculatedOnRead(col.$refLinks?.[col.$refLinks.length - 1].definition)) {
762
+ const calcElement = resolveCalculatedElement(col, true)
763
+ res.push(calcElement)
764
+ } else if (col.isJoinRelevant) {
663
765
  const tableAlias$refLink = getQuerySourceName(col)
664
766
  const transformedColumn = {
665
767
  ref: [tableAlias$refLink, getFullName(col.$refLinks[col.$refLinks.length - 1].definition)],
@@ -671,6 +773,11 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
671
773
  res.push({ ...col })
672
774
  } else if (col.ref) {
673
775
  if (col.$refLinks.some(link => link.definition._target?.['@cds.persistence.skip'] === true)) continue
776
+ if (col.ref.length > 1 && col.ref[0] === '$self' && !col.$refLinks[0].definition.kind) {
777
+ const dollarSelfReplacement = calculateDollarSelfColumn(col)
778
+ res.push(...getTransformedOrderByGroupBy([dollarSelfReplacement], inOrderBy))
779
+ continue
780
+ }
674
781
  const { target } = col.$refLinks[0]
675
782
  const tableAlias = target.SELECT ? null : getQuerySourceName(col) // do not prepend TA if orderBy column addresses element of query
676
783
  const leaf = col.$refLinks[col.$refLinks.length - 1].definition
@@ -754,6 +861,8 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
754
861
  // for wildcard on subquery in from, just reference the elements
755
862
  if (tableAlias.SELECT && !element.elements && !element.target) {
756
863
  wildcardColumns.push(index ? { ref: [index, k] } : { ref: [k] })
864
+ } else if (isCalculatedOnRead(element)) {
865
+ wildcardColumns.push(resolveCalculatedElement(element))
757
866
  } else {
758
867
  const flatColumns = getFlatColumnsFor(element, { tableAlias: index }, [], { exclude, replace }, true)
759
868
  wildcardColumns.push(...flatColumns)
@@ -903,8 +1012,9 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
903
1012
  if (columnAlias) flatColumn = { ref: [fkBaseName], as: `${columnAlias}_${fk.ref.join('_')}` }
904
1013
  else flatColumn = { ref: [fkBaseName] }
905
1014
  if (tableAlias) flatColumn.ref.unshift(tableAlias)
906
- Object.defineProperty(flatColumn, 'element', { value: fkElement })
907
- Object.defineProperty(flatColumn, '_csnPath', { value: csnPath })
1015
+
1016
+ setElementOnColumns(flatColumn, fkElement)
1017
+ Object.defineProperty(flatColumn, '_csnPath', { value: csnPath, writable: true })
908
1018
  flatColumns.push(flatColumn)
909
1019
  }
910
1020
  })
@@ -934,8 +1044,8 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
934
1044
  }
935
1045
  if (column.sort) flatRef.sort = column.sort
936
1046
  if (columnAlias) flatRef.as = columnAlias
937
- Object.defineProperty(flatRef, 'element', { value: element })
938
- Object.defineProperty(flatRef, '_csnPath', { value: csnPath })
1047
+ setElementOnColumns(flatRef, element)
1048
+ Object.defineProperty(flatRef, '_csnPath', { value: csnPath, writable: true })
939
1049
  return [flatRef]
940
1050
 
941
1051
  function getReplacement(from) {
@@ -962,11 +1072,11 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
962
1072
  * @returns {object[]} - The transformed token stream.
963
1073
  */
964
1074
  function getTransformedTokenStream(tokenStream, $baseLink = null) {
965
- const transformedWhere = []
1075
+ const transformedTokenStream = []
966
1076
  for (let i = 0; i < tokenStream.length; i++) {
967
1077
  const token = tokenStream[i]
968
1078
  if (token === 'exists') {
969
- transformedWhere.push(token)
1079
+ transformedTokenStream.push(token)
970
1080
  const whereExistsSubSelects = []
971
1081
  const { ref, $refLinks } = tokenStream[i + 1]
972
1082
  if (!ref) continue
@@ -1008,7 +1118,7 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
1008
1118
  }
1009
1119
 
1010
1120
  const whereExists = { SELECT: whereExistsSubqueries(whereExistsSubSelects) }
1011
- transformedWhere[i + 1] = whereExists
1121
+ transformedTokenStream[i + 1] = whereExists
1012
1122
  // skip newly created subquery from being iterated
1013
1123
  i += 1
1014
1124
  } else if (token.list) {
@@ -1021,14 +1131,14 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
1021
1131
  typeof precedingTwoTokens[1] === 'string' ? precedingTwoTokens[1].toLowerCase() : ''
1022
1132
 
1023
1133
  if (firstPrecedingToken === 'not') {
1024
- transformedWhere.splice(i - 2, 2, 'is', 'not', 'null')
1134
+ transformedTokenStream.splice(i - 2, 2, 'is', 'not', 'null')
1025
1135
  } else if (secondPrecedingToken === 'in') {
1026
- transformedWhere.splice(i - 1, 1, '=', { val: null })
1136
+ transformedTokenStream.splice(i - 1, 1, '=', { val: null })
1027
1137
  } else {
1028
- transformedWhere.push({ list: [] })
1138
+ transformedTokenStream.push({ list: [] })
1029
1139
  }
1030
1140
  } else {
1031
- transformedWhere.push({ list: getTransformedTokenStream(token.list) })
1141
+ transformedTokenStream.push({ list: getTransformedTokenStream(token.list) })
1032
1142
  }
1033
1143
  } else if (tokenStream.length === 1 && token.val && $baseLink) {
1034
1144
  // infix filter - OData variant w/o mentioning key --> flatten out and compare each leaf to token.val
@@ -1046,12 +1156,12 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
1046
1156
  throw new Error('Filters can only be applied to managed associations which result in a single foreign key')
1047
1157
  flatKeys.forEach(c => keyValComparisons.push([...[c, '=', token]]))
1048
1158
  keyValComparisons.forEach((kv, j) =>
1049
- transformedWhere.push(...kv) && keyValComparisons[j + 1] ? transformedWhere.push('and') : null,
1159
+ transformedTokenStream.push(...kv) && keyValComparisons[j + 1] ? transformedTokenStream.push('and') : null,
1050
1160
  )
1051
1161
  } else if (token.ref && token.param) {
1052
- transformedWhere.push({ ...token })
1162
+ transformedTokenStream.push({ ...token })
1053
1163
  } else if (pseudos.elements[token.ref?.[0]]) {
1054
- transformedWhere.push({ ...token })
1164
+ transformedTokenStream.push({ ...token })
1055
1165
  } else {
1056
1166
  // expand `struct = null | struct2`
1057
1167
  const { definition } = token.$refLinks?.[token.$refLinks.length - 1] || {}
@@ -1075,7 +1185,7 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
1075
1185
  cds.error(`The operator "${next}" is not supported for structure comparison`)
1076
1186
  const newTokens = expandComparison(token, ops, rhs)
1077
1187
  const needXpr = Boolean(tokenStream[i - 1] || tokenStream[indexRhs + 1])
1078
- transformedWhere.push(...(needXpr ? [asXpr(newTokens)] : newTokens))
1188
+ transformedTokenStream.push(...(needXpr ? [asXpr(newTokens)] : newTokens))
1079
1189
  i = indexRhs // jump to next relevant index
1080
1190
  }
1081
1191
  } else {
@@ -1084,6 +1194,17 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
1084
1194
 
1085
1195
  let result = is_regexp(token?.val) ? token : copy(token) // REVISIT: too expensive! //
1086
1196
  if (token.ref) {
1197
+ const { definition } = token.$refLinks[token.$refLinks.length - 1]
1198
+ if (isCalculatedOnRead(definition)) {
1199
+ const calculatedElement = resolveCalculatedElement(token, true, $baseLink)
1200
+ transformedTokenStream.push(calculatedElement)
1201
+ continue
1202
+ }
1203
+ if (token.ref.length > 1 && token.ref[0] === '$self' && !token.$refLinks[0].definition.kind) {
1204
+ const dollarSelfReplacement = [calculateDollarSelfColumn(token, true)]
1205
+ transformedTokenStream.push(...getTransformedTokenStream(dollarSelfReplacement))
1206
+ continue
1207
+ }
1087
1208
  const tableAlias = getQuerySourceName(token, $baseLink)
1088
1209
  if (!$baseLink && token.isJoinRelevant) {
1089
1210
  result.ref = [tableAlias, getFullName(token.$refLinks[token.$refLinks.length - 1].definition)]
@@ -1106,11 +1227,11 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
1106
1227
  })
1107
1228
  }
1108
1229
 
1109
- transformedWhere.push(result)
1230
+ transformedTokenStream.push(result)
1110
1231
  }
1111
1232
  }
1112
1233
  }
1113
- return transformedWhere
1234
+ return transformedTokenStream
1114
1235
  }
1115
1236
 
1116
1237
  /**
@@ -1394,11 +1515,6 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
1394
1515
  each.ref[0] in { $self: true, $projection: true } ? getParentEntity(assoc) : target,
1395
1516
  ),
1396
1517
  )
1397
-
1398
- function getParentEntity(element) {
1399
- if (element.kind === 'entity') return element
1400
- else return getParentEntity(element.parent)
1401
- }
1402
1518
  }
1403
1519
 
1404
1520
  /**
@@ -1797,7 +1913,7 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
1797
1913
  }
1798
1914
 
1799
1915
  function getCombinedElementAlias(node) {
1800
- return getLastStringSegment(inferred.$combinedElements[node.ref[0].id || node.ref[0]][0].index)
1916
+ return getLastStringSegment(inferred.$combinedElements[node.ref[0].id || node.ref[0]]?.[0].index)
1801
1917
  }
1802
1918
  }
1803
1919
  }
@@ -1842,5 +1958,23 @@ function getLastStringSegment(str) {
1842
1958
  return index != -1 ? str.substring(index + 1) : str
1843
1959
  }
1844
1960
 
1961
+ function getParentEntity(element) {
1962
+ if (element.kind === 'entity') return element
1963
+ else return getParentEntity(element.parent)
1964
+ }
1965
+
1966
+ /**
1967
+ * Assigns the given `element` as non-enumerable property 'element' onto `col`.
1968
+ *
1969
+ * @param {object} col
1970
+ * @param {csn.Element} element
1971
+ */
1972
+ function setElementOnColumns(col, element) {
1973
+ Object.defineProperty(col, 'element', {
1974
+ value: element,
1975
+ writable: true,
1976
+ })
1977
+ }
1978
+
1845
1979
  const idOnly = ref => ref.id || ref
1846
1980
  const is_regexp = x => x?.constructor?.name === 'RegExp' // NOTE: x instanceof RegExp doesn't work in repl
@@ -4,6 +4,17 @@ const { _target_name4 } = require('./SQLService')
4
4
 
5
5
  const handledDeep = Symbol('handledDeep')
6
6
 
7
+ /**
8
+ * @callback nextCallback
9
+ * @param {Error|undefined} error
10
+ * @returns {Promise<unknown>}
11
+ */
12
+
13
+ /**
14
+ * @param {import('@sap/cds/apis/services').Request} req
15
+ * @param {nextCallback} next
16
+ * @returns {Promise<number>}
17
+ */
7
18
  async function onDeep(req, next) {
8
19
  const { query } = req
9
20
  // REVISIT: req.target does not match the query.INSERT target for path insert
@@ -136,11 +147,16 @@ const _calculateExpandColumns = (target, data, expandColumns = [], elementMap =
136
147
  }
137
148
  }
138
149
 
150
+ /**
151
+ * @param {import('@sap/cds/apis/cqn').Query} query
152
+ * @param {import('@sap/cds/apis/csn').Definition} target
153
+ */
139
154
  const getExpandForDeep = (query, target) => {
140
155
  const from = query.DELETE?.from || query.UPDATE?.entity
141
156
  const data = query.UPDATE?.data || null
142
157
  const where = query.DELETE?.where || query.UPDATE?.where
143
158
 
159
+ /** @type {import("@sap/cds/apis/ql").SELECT<unknown>} */
144
160
  const cqn = SELECT.from(from)
145
161
  if (where) cqn.SELECT.where = where
146
162
 
@@ -150,6 +166,12 @@ const getExpandForDeep = (query, target) => {
150
166
  return cqn
151
167
  }
152
168
 
169
+ /**
170
+ * @param {import('@sap/cds/apis/cqn').Query} query
171
+ * @param {unknown[]} dbData
172
+ * @param {import('@sap/cds/apis/csn').Definition} target
173
+ * @returns
174
+ */
153
175
  const getDeepQueries = (query, dbData, target) => {
154
176
  let queryData
155
177
  if (query.INSERT) {
@@ -174,6 +196,11 @@ const _hasManagedElements = target => {
174
196
  return Object.keys(target.elements).filter(elementName => target.elements[elementName]['@cds.on.update']).length > 0
175
197
  }
176
198
 
199
+ /**
200
+ * @param {unknown[]} diff
201
+ * @param {import('@sap/cds/apis/csn').Definition} target
202
+ * @returns {import('@sap/cds/apis/cqn').Query[]}
203
+ */
177
204
  const _getDeepQueries = (diff, target) => {
178
205
  const queries = []
179
206
 
@@ -41,6 +41,16 @@ const generateUUIDandPropagateKeys = (target, data, event) => {
41
41
  }
42
42
  }
43
43
 
44
+ /**
45
+ * @callback nextCallback
46
+ * @param {Error|undefined} error
47
+ * @returns {Promise<unknown>}
48
+ */
49
+
50
+ /**
51
+ * @param {import('@sap/cds/apis/services').Request} req
52
+ * @param {nextCallback} next
53
+ */
44
54
  module.exports = async function fill_in_keys(req, next) {
45
55
  // REVISIT dummy handler until we have input processing
46
56
  if (!req.target || !this.model || req.target._unresolved) return next()
@@ -0,0 +1,45 @@
1
+ import * as cqn from '@sap/cds/apis/cqn'
2
+ import * as csn from '@sap/cds/apis/csn'
3
+
4
+ type linkedQuery = {
5
+ target: csn.Definition
6
+ elements: elements
7
+ }
8
+ export type SELECT = cqn.SELECT & linkedQuery
9
+ export type INSERT = cqn.INSERT & linkedQuery
10
+ export type UPSERT = cqn.UPSERT & linkedQuery
11
+ export type UPDATE = cqn.UPDATE & linkedQuery
12
+ export type STREAM = {
13
+ STREAM: { into: cqn.name | ref; data: ReadableStream<Buffer>; from: cqn.source; column?: ref; where: predicate }
14
+ } & linkedQuery
15
+ export type DELETE = cqn.DELETE & linkedQuery
16
+ export type CREATE = cqn.CREATE & linkedQuery
17
+ export type DROP = cqn.DROP & linkedQuery
18
+
19
+ export type Query = SELECT | INSERT | UPSERT | UPDATE | DELETE | CREATE | DROP
20
+
21
+ export type element = csn.Element & {
22
+ key?: boolean
23
+ virtual?: boolean
24
+ unique?: boolean
25
+ notNull?: boolean
26
+ }
27
+ export type elements = {
28
+ [name: string]: element
29
+ }
30
+
31
+ export type col = cqn.column_expr & { element: element }
32
+
33
+ export type list = {
34
+ list: cqn.expr[]
35
+ }
36
+ // Passthrough
37
+ export type source = cqn.source
38
+ export type ref = cqn.ref
39
+ export type val = cqn.val
40
+ export type xpr = cqn.xpr
41
+ export type expr = cqn.expr
42
+ export type func = cqn.function_call
43
+ export type predicate = cqn.predicate
44
+ export type ordering_term = cqn.ordering_term
45
+ export type limit = { rows: val; offset: val }