@sap/cds 8.8.3 → 8.9.1

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.
Files changed (73) hide show
  1. package/CHANGELOG.md +48 -4
  2. package/_i18n/i18n_en_US_saptrc.properties +3 -0
  3. package/bin/colors.js +2 -0
  4. package/bin/test.js +103 -75
  5. package/eslint.config.mjs +16 -4
  6. package/lib/compile/for/lean_drafts.js +4 -0
  7. package/lib/compile/parse.js +26 -6
  8. package/lib/env/cds-env.js +3 -1
  9. package/lib/env/cds-requires.js +0 -3
  10. package/lib/env/schemas/cds-rc.js +11 -0
  11. package/lib/log/format/aspects/cls.js +2 -1
  12. package/lib/log/format/json.js +1 -1
  13. package/lib/plugins.js +2 -3
  14. package/lib/ql/SELECT.js +2 -1
  15. package/lib/ql/cds-ql.js +2 -0
  16. package/lib/ql/cds.ql-predicates.js +6 -4
  17. package/lib/ql/resolve.js +46 -0
  18. package/lib/req/validate.js +1 -0
  19. package/lib/srv/bindings.js +64 -43
  20. package/lib/srv/cds-connect.js +1 -1
  21. package/lib/srv/cds-serve.js +2 -2
  22. package/lib/srv/middlewares/auth/ias-auth.js +2 -0
  23. package/lib/srv/protocols/http.js +2 -2
  24. package/lib/srv/protocols/index.js +1 -1
  25. package/lib/srv/protocols/odata-v4.js +0 -1
  26. package/lib/srv/srv-tx.js +1 -1
  27. package/lib/test/cds-test.js +3 -4
  28. package/lib/utils/cds-utils.js +19 -19
  29. package/lib/utils/colors.js +46 -45
  30. package/lib/utils/csv-reader.js +5 -5
  31. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +1 -2
  32. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -1
  33. package/libx/_runtime/common/Service.js +4 -2
  34. package/libx/_runtime/common/composition/data.js +1 -2
  35. package/libx/_runtime/common/composition/tree.js +6 -4
  36. package/libx/_runtime/common/generic/sorting.js +6 -2
  37. package/libx/_runtime/common/utils/cqn2cqn4sql.js +6 -7
  38. package/libx/_runtime/common/utils/differ.js +1 -1
  39. package/libx/_runtime/common/utils/draft.js +1 -1
  40. package/libx/_runtime/common/utils/foreignKeyPropagations.js +6 -2
  41. package/libx/_runtime/common/utils/keys.js +13 -84
  42. package/libx/_runtime/common/utils/propagateForeignKeys.js +4 -3
  43. package/libx/_runtime/common/utils/resolveView.js +96 -102
  44. package/libx/_runtime/common/utils/rewriteAsterisks.js +1 -1
  45. package/libx/_runtime/common/utils/stream.js +2 -3
  46. package/libx/_runtime/db/utils/columns.js +1 -1
  47. package/libx/_runtime/fiori/lean-draft.js +11 -7
  48. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  49. package/libx/_runtime/messaging/common-utils/normalizeIncomingMessage.js +1 -2
  50. package/libx/_runtime/messaging/file-based.js +6 -6
  51. package/libx/_runtime/messaging/kafka.js +5 -7
  52. package/libx/_runtime/messaging/redis-messaging.js +1 -1
  53. package/libx/_runtime/messaging/service.js +11 -4
  54. package/libx/_runtime/remote/Service.js +13 -5
  55. package/libx/_runtime/remote/utils/client.js +1 -0
  56. package/libx/_runtime/ucl/Service.js +135 -126
  57. package/libx/common/utils/path.js +34 -22
  58. package/libx/odata/middleware/create.js +2 -0
  59. package/libx/odata/middleware/operation.js +8 -2
  60. package/libx/odata/middleware/parse.js +1 -1
  61. package/libx/odata/middleware/stream.js +1 -2
  62. package/libx/odata/middleware/update.js +2 -0
  63. package/libx/odata/parse/afterburner.js +17 -9
  64. package/libx/odata/parse/cqn2odata.js +43 -22
  65. package/libx/odata/parse/grammar.peggy +21 -19
  66. package/libx/odata/parse/parser.js +1 -1
  67. package/libx/odata/utils/metadata.js +8 -2
  68. package/libx/odata/utils/odataBind.js +36 -0
  69. package/libx/outbox/index.js +1 -0
  70. package/libx/rest/middleware/operation.js +9 -8
  71. package/libx/rest/middleware/parse.js +1 -0
  72. package/package.json +3 -3
  73. package/lib/i18n/resources.js +0 -150
@@ -74,7 +74,7 @@ function hasValidProps(obj, ...names) {
74
74
  return true
75
75
  }
76
76
 
77
- function _args(args, func) {
77
+ function _args(args, func, navPrefix) {
78
78
  const res = []
79
79
 
80
80
  for (const cur of args) {
@@ -84,9 +84,9 @@ function _args(args, func) {
84
84
  }
85
85
 
86
86
  if (hasValidProps(cur, 'func', 'args')) {
87
- res.push(`${cur.func}(${_args(cur.args, cur.func)})`)
87
+ res.push(`${cur.func}(${_args(cur.args, cur.func, navPrefix)})`)
88
88
  } else if (hasValidProps(cur, 'ref')) {
89
- res.push(_format(cur))
89
+ res.push(_format(cur, null, null, null, null, null, navPrefix))
90
90
  } else if (hasValidProps(cur, 'val')) {
91
91
  res.push(_format(cur, null, null, null, null, func))
92
92
  }
@@ -95,38 +95,44 @@ function _args(args, func) {
95
95
  return res.join(',')
96
96
  }
97
97
 
98
- const _in = (column, /* in */ collection, target, kind, isLambda) => {
99
- const ref = _format(column, null, target, kind, isLambda)
98
+ const _in = (column, /* in */ collection, target, kind, isLambda, navPrefix) => {
99
+ const ref = _format(column, null, target, kind, isLambda, null, navPrefix)
100
100
  // { list: [ { val: 1}, { val: 2}, { val: 3} ] }
101
101
  const values = collection.list
102
102
  if (values && values.length) {
103
103
  // REVISIT: what about OData `in` operator?
104
- const expressions = values.map(value => `${ref}%20eq%20${_format(value, ref, target, kind, isLambda)}`)
104
+ const expressions = values.map(
105
+ value => `${ref}%20eq%20${_format(value, ref, target, kind, isLambda, null, navPrefix)}`
106
+ )
105
107
  return expressions.join('%20or%20')
106
108
  }
107
109
  }
108
110
 
109
- const _odataV2Func = (func, args) => {
111
+ const _odataV2Func = (func, args, navPrefix) => {
110
112
  switch (func) {
111
113
  case 'contains':
112
114
  // this doesn't support the contains signature with two collections as args, introduced in odata v4.01
113
- return `substringof(${_args([args[1], args[0]])})`
115
+ return `substringof(${_args([args[1], args[0]], null, navPrefix)})`
114
116
  default:
115
- return `${func}(${_args(args, func)})`
117
+ return `${func}(${_args(args, func, navPrefix)})`
116
118
  }
117
119
  }
118
120
 
119
- const _format = (cur, elementName, target, kind, isLambda, func) => {
121
+ const _format = (cur, elementName, target, kind, isLambda, func, navPrefix = []) => {
120
122
  if (typeof cur !== 'object') return encodeURIComponent(formatVal(cur, elementName, target, kind))
121
123
  if (hasValidProps(cur, 'ref'))
122
- return encodeURIComponent(isLambda ? [LAMBDA_VARIABLE, ...cur.ref].join('/') : cur.ref[0].id || cur.ref.join('/'))
124
+ return encodeURIComponent(
125
+ isLambda ? [LAMBDA_VARIABLE, ...cur.ref].join('/') : cur.ref[0].id || [...navPrefix, ...cur.ref].join('/')
126
+ )
123
127
  if (hasValidProps(cur, 'val'))
124
128
  return encodeURIComponent(formatVal(cur.val, elementName, target, kind, func, cur.literal))
125
- if (hasValidProps(cur, 'xpr')) return `(${_xpr(cur.xpr, target, kind, isLambda)})`
129
+ if (hasValidProps(cur, 'xpr')) return `(${_xpr(cur.xpr, target, kind, isLambda, navPrefix)})`
126
130
  // REVISIT: How to detect the types for all functions?
127
131
  if (hasValidProps(cur, 'func')) {
128
132
  if (cur.args?.length) {
129
- return kind === 'odata-v2' ? _odataV2Func(cur.func, cur.args) : `${cur.func}(${_args(cur.args, cur.func)})`
133
+ return kind === 'odata-v2'
134
+ ? _odataV2Func(cur.func, cur.args, navPrefix)
135
+ : `${cur.func}(${_args(cur.args, cur.func)})`
130
136
  }
131
137
  return `${cur.func}()`
132
138
  }
@@ -138,7 +144,7 @@ const _isLambda = (cur, next) => {
138
144
  return last && hasValidProps(last, 'id')
139
145
  }
140
146
 
141
- function _xpr(expr, target, kind, isLambda) {
147
+ function _xpr(expr, target, kind, isLambda, navPrefix = []) {
142
148
  const res = []
143
149
  const openBrackets = []
144
150
 
@@ -165,10 +171,10 @@ function _xpr(expr, target, kind, isLambda) {
165
171
  const between = [expr[i - 1], 'gt', expr[i + 1], 'and', expr[i - 1], 'lt', expr[i + 3]]
166
172
  // cleanup previous ref
167
173
  res.pop()
168
- res.push(`(${_xpr(between, target, kind, isLambda)})`)
174
+ res.push(`(${_xpr(between, target, kind, isLambda, navPrefix)})`)
169
175
  i += 3
170
176
  } else if (cur === 'in') {
171
- const inExpr = _in(expr[i - 1], expr[i + 1], target, kind, isLambda)
177
+ const inExpr = _in(expr[i - 1], expr[i + 1], target, kind, isLambda, navPrefix)
172
178
  // cleanup previous ref
173
179
  res.pop()
174
180
  // when sending a where clause with "col in []" we currently ignore the where clause
@@ -179,20 +185,33 @@ function _xpr(expr, target, kind, isLambda) {
179
185
  } else if (_isLambda(cur, expr[i + 1])) {
180
186
  const { where } = expr[i + 1].ref.at(-1)
181
187
  const nav = expr[i + 1].ref.map(ref => ref?.id ?? ref).join('/')
182
- // odata-v2 does not support lambda expressions but successfactors allows filter like for to-one assocs
188
+
183
189
  if (kind === 'odata-v2') {
190
+ // odata-v2 does not support lambda expressions but successfactors allows filter like for to-one assocs
184
191
  cds.log('remote').info(`OData V2 does not support lambda expressions. Using path expression as best effort.`)
185
192
  isLambda = false
186
- res.push(`${nav}%2F${_xpr(where, target, kind)}`)
187
- } else if (!where) res.push(`${nav}/any()`)
188
- else res.push(`${nav}/any(${LAMBDA_VARIABLE}:${_xpr(where, target, kind, true)})`)
193
+ res.push(_xpr(where, target, kind, isLambda, [...navPrefix, nav]))
194
+ } else if (!where) {
195
+ res.push(`${nav}/any()`)
196
+ } else {
197
+ res.push(`${nav}/any(${LAMBDA_VARIABLE}:${_xpr(where, target, kind, true, navPrefix)})`)
198
+ }
199
+
189
200
  i++
190
201
  } else {
191
202
  res.push(OPERATORS[cur] || cur.toLowerCase())
192
203
  }
193
204
  } else {
194
205
  const ref = expr[i - 2]
195
- const formatted = _format(cur, ref?.ref && (ref.ref.length ? ref.ref : ref.ref[0]), target, kind, isLambda)
206
+ const formatted = _format(
207
+ cur,
208
+ ref?.ref && (ref.ref.length ? ref.ref : ref.ref[0]),
209
+ target,
210
+ kind,
211
+ isLambda,
212
+ null,
213
+ navPrefix
214
+ )
196
215
  if (formatted !== undefined) res.push(formatted)
197
216
  }
198
217
  }
@@ -393,6 +412,8 @@ function $orderBy(orderBy) {
393
412
  const res = []
394
413
 
395
414
  for (const cur of orderBy) {
415
+ if (cur.implicit) continue
416
+
396
417
  if (hasValidProps(cur, 'ref', 'sort')) {
397
418
  res.push(_format(cur) + '%20' + cur.sort)
398
419
  continue
@@ -412,7 +433,7 @@ function $orderBy(orderBy) {
412
433
  }
413
434
  }
414
435
 
415
- return '$orderby=' + res.join(',')
436
+ if (res.length) return '$orderby=' + res.join(',')
416
437
  }
417
438
 
418
439
  function parseSearch(search) {
@@ -210,13 +210,18 @@
210
210
 
211
211
  }
212
212
  if (apply.topLevels.levels) {
213
- cqn.recurse.where = [{ ref: ['DistanceFromRoot'] }, '<=', { val: apply.topLevels.levels }]
213
+ cqn.recurse.where = [{ ref: ['DistanceFromRoot'] }, '<=', { val: apply.topLevels.levels - 1 }]
214
214
  }
215
215
  if (apply.topLevels.expandLevels) {
216
216
  if (!cqn.recurse.where) cqn.recurse.where = []
217
217
  for (const expandLevel of apply.topLevels.expandLevels) {
218
218
  if (cqn.recurse.where.length !== 0) cqn.recurse.where.push('or')
219
- cqn.recurse.where.push({ ref: [apply.topLevels.nodeProperty] }, '=', { val: expandLevel.nodeID }, 'and', { ref: ['Distance'] }, 'between', { val: 0 }, 'and', { val: expandLevel.levels } )
219
+ cqn.recurse.where.push({ ref: [apply.topLevels.nodeProperty] }, '=', { val: expandLevel.nodeID })
220
+ if (Number.isInteger(expandLevel.levels)) {
221
+ cqn.recurse.where.push('and', { ref: ['Distance'] })
222
+ if (expandLevel.levels === 1) cqn.recurse.where.push('=', { val: 1 })
223
+ else cqn.recurse.where.push('between', { val: 1 }, 'and', { val: expandLevel.levels } )
224
+ }
220
225
  }
221
226
  }
222
227
  }
@@ -244,23 +249,16 @@
244
249
  }
245
250
 
246
251
  if (apply.ancestors && apply.topLevels) {
247
- const inner = {
248
- SELECT: {
249
- from: cqn.from,
250
- }
251
- }
252
- _ancestors(inner.SELECT)
253
- cqn.from = inner
254
- _toplevels(cqn)
252
+ _ancestors(cqn)
253
+ const extra = {...cqn}
254
+ _toplevels(extra)
255
+ cqn.recurse = extra.recurse
255
256
  } else if (apply.ancestors && apply.descendants) {
256
- const inner = {
257
- SELECT: {
258
- from: cqn.from
259
- }
260
- }
261
- _ancestors(inner.SELECT)
262
- cqn.from = inner
263
- _descendants(cqn)
257
+ _ancestors(cqn)
258
+ const extra = {...cqn}
259
+ _descendants(extra)
260
+ cqn.where = extra.where
261
+ cqn.recurse = extra.recurse
264
262
  } else if (apply.topLevels) {
265
263
  _toplevels(cqn)
266
264
  } else if (apply.ancestors) {
@@ -1019,7 +1017,11 @@
1019
1017
 
1020
1018
  expandItemProp
1021
1019
  = "\"NodeID\"" o ":" o s:doubleQuotedString{return { nodeID: s } } /
1022
- "\"Levels\"" o ":" o i:integer{return { levels: i } }
1020
+ "\"Levels\"" o ":" o l:expandItemLevel{return { levels: l } }
1021
+
1022
+ expandItemLevel
1023
+ = i:integer{ return i } /
1024
+ null{ return null }
1023
1025
 
1024
1026
  ansDescTrafo
1025
1027
  = OPEN o "$root/" p:simplePath o COMMA o h:identifier o COMMA o e:identifier o COMMA? t:ansDescGetNodes? o COMMA? d:integer? o COMMA? k:ansDescKeepStart? CLOSE {