@sap/cds 6.1.2 → 6.2.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 (212) hide show
  1. package/CHANGELOG.md +92 -8
  2. package/apis/cds.d.ts +18 -6
  3. package/apis/connect.d.ts +1 -1
  4. package/apis/cqn.d.ts +1 -1
  5. package/apis/log.d.ts +23 -5
  6. package/apis/ql.d.ts +128 -61
  7. package/apis/services.d.ts +11 -0
  8. package/apis/test.d.ts +61 -0
  9. package/apis/utils.d.ts +15 -0
  10. package/app/fiori/preview.js +1 -0
  11. package/bin/build/buildTaskEngine.js +70 -22
  12. package/bin/build/buildTaskFactory.js +18 -11
  13. package/bin/build/buildTaskHandler.js +1 -1
  14. package/bin/build/buildTaskProviderFactory.js +3 -13
  15. package/bin/build/constants.js +0 -1
  16. package/bin/build/index.js +14 -6
  17. package/bin/build/provider/buildTaskHandlerEdmx.js +2 -3
  18. package/bin/build/provider/buildTaskHandlerFeatureToggles.js +2 -2
  19. package/bin/build/provider/buildTaskHandlerInternal.js +3 -6
  20. package/bin/build/provider/buildTaskProviderInternal.js +51 -39
  21. package/bin/build/provider/fiori/index.js +3 -3
  22. package/bin/build/provider/hana/2migration.js +1 -1
  23. package/bin/build/provider/hana/index.js +34 -27
  24. package/bin/build/provider/java/index.js +6 -7
  25. package/bin/build/provider/mtx/index.js +20 -18
  26. package/bin/build/provider/mtx/resourcesTarBuilder.js +8 -11
  27. package/bin/build/provider/mtx-sidecar/index.js +13 -17
  28. package/bin/build/provider/nodejs/index.js +8 -7
  29. package/bin/build/util.js +22 -4
  30. package/bin/cds.js +8 -4
  31. package/bin/deploy/to-hana/cfUtil.js +53 -18
  32. package/bin/mtx/in-cds.js +1 -0
  33. package/bin/serve.js +37 -30
  34. package/lib/auth/basic-auth.js +33 -0
  35. package/lib/auth/dummy-auth.js +7 -0
  36. package/lib/auth/ias-auth.js +2 -0
  37. package/lib/auth/index.js +31 -0
  38. package/lib/auth/jwt-auth.js +3 -0
  39. package/lib/auth/mocked-users.js +72 -0
  40. package/lib/auth/passport-basic.js +12 -0
  41. package/lib/auth/passport-digest.js +14 -0
  42. package/lib/auth/xsuaa-auth.js +3 -0
  43. package/lib/compile/cds-compile.js +3 -3
  44. package/lib/compile/to/cdl.js +5 -1
  45. package/lib/compile/to/edm.js +8 -0
  46. package/lib/compile/to/gql.js +1 -0
  47. package/lib/compile/to/json.js +30 -5
  48. package/lib/compile/to/sql.js +3 -1
  49. package/lib/core/index.js +5 -1
  50. package/lib/dbs/cds-deploy.js +36 -6
  51. package/lib/env/cds-env.js +15 -5
  52. package/lib/env/cds-requires.js +51 -58
  53. package/lib/env/defaults.js +1 -0
  54. package/lib/env/schemas/cds-package.json +4 -0
  55. package/lib/env/schemas/cds-rc.json +63 -77
  56. package/lib/i18n/localize.js +16 -5
  57. package/lib/index.js +9 -4
  58. package/lib/log/cds-error.js +4 -6
  59. package/lib/log/cds-log.js +89 -53
  60. package/lib/log/service/index.js +1 -0
  61. package/lib/ql/CREATE.js +2 -5
  62. package/lib/ql/DELETE.js +1 -1
  63. package/lib/ql/DROP.js +1 -3
  64. package/lib/ql/INSERT.js +3 -3
  65. package/lib/ql/Query.js +10 -23
  66. package/lib/ql/SELECT.js +1 -2
  67. package/lib/ql/UPDATE.js +2 -2
  68. package/lib/ql/Whereable.js +7 -15
  69. package/lib/ql/cds-ql.js +9 -3
  70. package/lib/req/cds-context.js +11 -3
  71. package/lib/req/context.js +29 -23
  72. package/lib/req/locale.js +9 -5
  73. package/lib/req/request.js +1 -0
  74. package/lib/req/user.js +2 -1
  75. package/lib/srv/cds-connect.js +1 -1
  76. package/lib/srv/cds-serve.js +21 -14
  77. package/lib/srv/middlewares/cds-context.js +29 -0
  78. package/lib/srv/middlewares/ctx-model.js +24 -0
  79. package/lib/srv/middlewares/errors.js +9 -0
  80. package/lib/srv/middlewares/index.js +22 -0
  81. package/lib/srv/middlewares/sap-statistics.js +13 -0
  82. package/lib/srv/middlewares/trace.js +102 -0
  83. package/lib/srv/protocols/_legacy.js +42 -0
  84. package/lib/srv/protocols/graphql.js +39 -0
  85. package/lib/srv/protocols/hcql.js +37 -0
  86. package/lib/srv/protocols/index.js +86 -0
  87. package/lib/srv/protocols/odata-v2-proxy.js +3767 -0
  88. package/lib/srv/protocols/odata-v2.js +26 -0
  89. package/lib/srv/protocols/odata-v4.js +16 -0
  90. package/lib/srv/protocols/rest.js +13 -0
  91. package/lib/srv/srv-api.js +5 -0
  92. package/lib/srv/srv-models.js +4 -6
  93. package/lib/utils/axios.js +3 -2
  94. package/lib/utils/cds-test.js +27 -21
  95. package/lib/utils/cds-utils.js +19 -20
  96. package/lib/utils/tar.js +175 -0
  97. package/libx/_runtime/audit/generic/personal/utils.js +18 -7
  98. package/libx/_runtime/audit/utils/v2.js +1 -0
  99. package/libx/_runtime/auth/index.js +4 -0
  100. package/libx/_runtime/auth/strategies/ias-auth.js +76 -0
  101. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +8 -3
  102. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +15 -4
  103. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -1
  104. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/orderByToCQN.js +1 -1
  105. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +1 -1
  106. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +9 -0
  107. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriInfo.js +5 -1
  108. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +12 -0
  109. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/BufferedWriter.js +6 -2
  110. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/RequestValidator.js +47 -7
  111. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
  112. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +0 -2
  113. package/libx/_runtime/cds-services/services/utils/compareJson.js +3 -1
  114. package/libx/_runtime/cds-services/util/assert.js +7 -0
  115. package/libx/_runtime/common/aspects/relation.js +1 -1
  116. package/libx/_runtime/common/composition/data.js +61 -15
  117. package/libx/_runtime/common/composition/delete.js +0 -1
  118. package/libx/_runtime/common/composition/insert.js +0 -1
  119. package/libx/_runtime/common/composition/tree.js +4 -10
  120. package/libx/_runtime/common/composition/update.js +44 -21
  121. package/libx/_runtime/common/generic/auth/capabilities.js +8 -10
  122. package/libx/_runtime/common/generic/crud.js +1 -2
  123. package/libx/_runtime/common/generic/etag.js +4 -4
  124. package/libx/_runtime/common/generic/input.js +21 -6
  125. package/libx/_runtime/common/generic/paging.js +3 -3
  126. package/libx/_runtime/common/generic/put.js +7 -4
  127. package/libx/_runtime/common/generic/sorting.js +4 -4
  128. package/libx/_runtime/common/generic/temporal.js +3 -6
  129. package/libx/_runtime/common/i18n/messages.properties +0 -7
  130. package/libx/_runtime/common/utils/cqn2cqn4sql.js +11 -6
  131. package/libx/_runtime/common/utils/csn.js +0 -28
  132. package/libx/_runtime/common/utils/draft.js +8 -1
  133. package/libx/_runtime/common/utils/path.js +7 -1
  134. package/libx/_runtime/common/utils/propagateForeignKeys.js +122 -0
  135. package/libx/_runtime/common/utils/resolveView.js +2 -3
  136. package/libx/_runtime/common/utils/template.js +2 -3
  137. package/libx/_runtime/db/data-conversion/post-processing.js +3 -44
  138. package/libx/_runtime/db/generic/input.js +6 -6
  139. package/libx/_runtime/db/sql-builder/dataTypes.js +4 -0
  140. package/libx/_runtime/fiori/generic/activate.js +2 -2
  141. package/libx/_runtime/fiori/generic/before.js +40 -72
  142. package/libx/_runtime/fiori/generic/cancel.js +2 -2
  143. package/libx/_runtime/fiori/generic/delete.js +2 -2
  144. package/libx/_runtime/fiori/generic/edit.js +2 -2
  145. package/libx/_runtime/fiori/generic/new.js +3 -5
  146. package/libx/_runtime/fiori/generic/patch.js +49 -43
  147. package/libx/_runtime/fiori/generic/prepare.js +2 -2
  148. package/libx/_runtime/fiori/generic/read.js +27 -37
  149. package/libx/_runtime/fiori/utils/where.js +4 -2
  150. package/libx/_runtime/hana/Service.js +1 -3
  151. package/libx/_runtime/hana/conversion.js +3 -0
  152. package/libx/_runtime/hana/driver.js +33 -3
  153. package/libx/_runtime/hana/dynatrace.js +1 -0
  154. package/libx/_runtime/hana/search2Contains.js +12 -1
  155. package/libx/_runtime/hana/search2cqn4sql.js +10 -27
  156. package/libx/_runtime/hana/streaming.js +1 -0
  157. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +4 -2
  158. package/libx/_runtime/messaging/common-utils/AMQPClient.js +1 -0
  159. package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +5 -2
  160. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -0
  161. package/libx/_runtime/messaging/enterprise-messaging.js +62 -3
  162. package/libx/_runtime/messaging/outbox/utils.js +1 -1
  163. package/libx/_runtime/messaging/redis-messaging.js +1 -0
  164. package/libx/_runtime/remote/Service.js +2 -2
  165. package/libx/_runtime/remote/utils/client.js +35 -11
  166. package/libx/_runtime/remote/utils/data.js +7 -2
  167. package/libx/_runtime/sqlite/Service.js +18 -7
  168. package/libx/_runtime/sqlite/conversion.js +3 -0
  169. package/libx/_runtime/sqlite/convertAssocToOneManaged.js +3 -3
  170. package/libx/_runtime/sqlite/localized.js +8 -8
  171. package/libx/odata/afterburner.js +39 -7
  172. package/libx/odata/cqn2odata.js +6 -3
  173. package/libx/odata/grammar.pegjs +66 -18
  174. package/libx/odata/index.js +3 -2
  175. package/libx/odata/parser.js +1 -1
  176. package/libx/odata/utils.js +2 -0
  177. package/libx/rest/RestAdapter.js +62 -43
  178. package/libx/rest/middleware/input.js +2 -3
  179. package/libx/rest/middleware/parse.js +2 -1
  180. package/libx/rest/middleware/update.js +1 -1
  181. package/package.json +2 -2
  182. package/server.js +5 -4
  183. package/srv/mtx.cds +1 -1
  184. package/srv/mtx.js +4 -24
  185. package/lib/srv/adapters.js +0 -85
  186. package/lib/utils/resources/index.js +0 -48
  187. package/lib/utils/resources/tar.js +0 -49
  188. package/lib/utils/resources/utils.js +0 -11
  189. package/libx/_runtime/db/utils/propagateForeignKeys.js +0 -93
  190. package/libx/_runtime/extensibility/activate.js +0 -69
  191. package/libx/_runtime/extensibility/add.js +0 -50
  192. package/libx/_runtime/extensibility/addExtension.js +0 -72
  193. package/libx/_runtime/extensibility/defaults.js +0 -34
  194. package/libx/_runtime/extensibility/handler/transformREAD.js +0 -121
  195. package/libx/_runtime/extensibility/handler/transformRESULT.js +0 -51
  196. package/libx/_runtime/extensibility/handler/transformWRITE.js +0 -64
  197. package/libx/_runtime/extensibility/linter/allowlist_checker.js +0 -373
  198. package/libx/_runtime/extensibility/linter/annotations_checker.js +0 -113
  199. package/libx/_runtime/extensibility/linter/checker_base.js +0 -20
  200. package/libx/_runtime/extensibility/linter/namespace_checker.js +0 -180
  201. package/libx/_runtime/extensibility/linter.js +0 -32
  202. package/libx/_runtime/extensibility/push.js +0 -118
  203. package/libx/_runtime/extensibility/service.js +0 -38
  204. package/libx/_runtime/extensibility/token.js +0 -57
  205. package/libx/_runtime/extensibility/utils.js +0 -131
  206. package/libx/_runtime/extensibility/validation.js +0 -50
  207. package/libx/_runtime/extensibility/views.js +0 -12
  208. package/srv/extensibility-service.cds +0 -59
  209. package/srv/extensibility-service.js +0 -1
  210. package/srv/extensions.cds +0 -8
  211. package/srv/model-provider.cds +0 -61
  212. package/srv/model-provider.js +0 -143
@@ -37,7 +37,7 @@ const _convert = (refEntries, req) => {
37
37
  }
38
38
 
39
39
  // REVISIT once sql can handle structured keys properly, this handler should not be required anymore
40
- const _handler = function (req) {
40
+ const sqliteConvertAssocToOneManaged = function (req) {
41
41
  // do simple checks upfront and exit early
42
42
  if (!(req.query && req.query.SELECT) || typeof req.query === 'string') return
43
43
  if (!req.query.SELECT.orderBy && !req.query.SELECT.groupBy && !req.query.SELECT.where && !req.query.SELECT.having) {
@@ -49,6 +49,6 @@ const _handler = function (req) {
49
49
  _convert(_getConvertibleEntries(req), req)
50
50
  }
51
51
 
52
- _handler._initial = true
52
+ sqliteConvertAssocToOneManaged._initial = true
53
53
 
54
- module.exports = _handler
54
+ module.exports = sqliteConvertAssocToOneManaged
@@ -7,6 +7,8 @@ if (cds.env.i18n && Array.isArray(cds.env.i18n.for_sqlite) && !cds.env.i18n.for_
7
7
  LOG._warn && LOG.warn('No language configuration found in cds.env.i18n.for_sqlite')
8
8
  }
9
9
 
10
+ const _translations = cds.env.i18n.for_sqlite.reduce((all, l) => ((all[l] = true), all), {})
11
+
10
12
  // REVISIT: this is actually configurable
11
13
  // there is no localized.en.<name>
12
14
  const getLocalize = (locale, model) => name => {
@@ -15,15 +17,13 @@ const getLocalize = (locale, model) => name => {
15
17
  // if we get here via onReadDraft, target is already localized
16
18
  // because of subrequest using SELECT.from as new target
17
19
  const target = model.definitions[ensureUnlocalized(name)]
18
- const localizedView =
19
- target &&
20
- target['@cds.localized'] !== false &&
21
- model.definitions[`localized.${locale !== 'en' ? locale + '.' : ''}${name}`]
20
+ if (target?.['@cds.localized'] === false) return name
22
21
 
23
- return localizedView ? localizedView.name : name
22
+ const view = model.definitions[`localized${locale in _translations ? '.' + locale : ''}.${name}`]
23
+ return view?.name || name
24
24
  }
25
25
 
26
- const _handler = function (req) {
26
+ const sqliteLocalized = function (req) {
27
27
  const { query } = req
28
28
 
29
29
  // do simple checks upfront and exit early
@@ -44,6 +44,6 @@ const _handler = function (req) {
44
44
  redirect(query, getLocalize(req.locale, this.model))
45
45
  }
46
46
 
47
- _handler._initial = true
47
+ sqliteLocalized._initial = true
48
48
 
49
- module.exports = _handler
49
+ module.exports = sqliteLocalized
@@ -163,20 +163,27 @@ function _convertVal(element, value) {
163
163
  if (value === null) return value
164
164
  switch (element._type) {
165
165
  case 'cds.Integer':
166
+ case 'cds.UInt8':
167
+ case 'cds.Int16':
168
+ case 'cds.Int32':
166
169
  // eslint-disable-next-line no-case-declarations
167
170
  const n = Number(value)
168
171
  if (Number.isSafeInteger(n)) return n
169
172
  throw new Error('Not a valid integer') // TODO
173
+
170
174
  case 'cds.String':
171
175
  case 'cds.LargeString':
172
176
  case 'cds.Decimal':
173
177
  case 'cds.DecimalFloat':
174
178
  case 'cds.Double':
179
+ case 'cds.Int64':
175
180
  case 'cds.Integer64':
176
181
  if (typeof value === 'string') return value
177
182
  return String(value)
183
+
178
184
  case 'cds.Boolean':
179
185
  return typeof value === 'string' ? value === 'true' : value
186
+
180
187
  default:
181
188
  return value
182
189
  }
@@ -189,12 +196,13 @@ function _processSegments(from, model, namespace) {
189
196
  let path
190
197
  let keys = null
191
198
  let keyCount = 0
192
- let incompleteKeys
199
+ let incompleteKeys = false
193
200
  let one
194
201
  let target
195
202
  for (let i = 0; i < ref.length; i++) {
196
203
  const seg = ref[i].id || ref[i]
197
- let params = ref[i].where && where2obj(ref[i].where)
204
+ const whereRef = ref[i].where
205
+ let params = whereRef && where2obj(whereRef)
198
206
 
199
207
  if (incompleteKeys) {
200
208
  // > key
@@ -234,7 +242,7 @@ function _processSegments(from, model, namespace) {
234
242
  if (current.params && current.kind === 'entity') {
235
243
  // > View with params
236
244
  target = current
237
- if (ref[i].where) {
245
+ if (whereRef) {
238
246
  keyCount += addRefToWhereIfNecessary(ref[i].where, current)
239
247
  _resolveAliasesInXpr(ref[i].where, current)
240
248
  _resolveAliasInParams(params, current)
@@ -265,10 +273,28 @@ function _processSegments(from, model, namespace) {
265
273
  // > entity
266
274
  target = current
267
275
  one = !!(ref[i].where || current._isSingleton)
276
+
277
+ let action
278
+ if (current.actions) {
279
+ const nextRef = typeof ref[i + 1] === 'string' && ref[i + 1]
280
+ const shortName = nextRef && nextRef.replace(namespace + '.', '')
281
+ action = shortName && current.actions[shortName]
282
+ }
283
+
268
284
  incompleteKeys = ref[i].where ? false : i === ref.length - 1 || one ? false : true
269
- if (ref[i].where) {
270
- keyCount += addRefToWhereIfNecessary(ref[i].where, current)
271
- _resolveAliasesInXpr(ref[i].where, current)
285
+
286
+ if (incompleteKeys && action) {
287
+ if (action['@cds.odata.bindingparameter.collection']) {
288
+ incompleteKeys = false
289
+ } else {
290
+ const msg = `Bound operations are not supported on entity collections.`
291
+ throw Object.assign(new Error(msg), { statusCode: 501 })
292
+ }
293
+ }
294
+
295
+ if (whereRef) {
296
+ keyCount += addRefToWhereIfNecessary(whereRef, current)
297
+ _resolveAliasesInXpr(whereRef, current)
272
298
  _resolveAliasInParams(params, current)
273
299
  // in case of Foo(1), params will be {} (before addRefToWhereIfNecessary was called)
274
300
  if (!Object.keys(params).length) params = where2obj(ref[i].where)
@@ -277,6 +303,11 @@ function _processSegments(from, model, namespace) {
277
303
  }
278
304
  } else if ({ action: 1, function: 1 }[current.kind]) {
279
305
  // > action or function
306
+ if (current.kind === 'action' && ref && ref[ref.length - 1]?.where?.length === 0) {
307
+ const msg = `Round brackets (parentheses) are not allowed for action calls.`
308
+ throw Object.assign(new Error(msg), { statusCode: 400 })
309
+ }
310
+
280
311
  if (i !== ref.length - 1) {
281
312
  const msg = `${i ? 'Unbound' : 'Bound'} ${current.kind} are only supported as the last path segment.`
282
313
  throw Object.assign(new Error(msg), { statusCode: 501 })
@@ -401,7 +432,8 @@ function _4service(service) {
401
432
  const { ref } = from
402
433
 
403
434
  // REVISIT: shouldn't be necessary
404
- //Second findCsnTargetFor is required for concat query, where the root is already identified with the first query and subsequent queries already have correct root
435
+ // Second findCsnTargetFor is required for concat query, where the root is already identified with the first query
436
+ // and subsequent queries already have correct root
405
437
  /*
406
438
  * make first path segment fully qualified
407
439
  */
@@ -121,8 +121,11 @@ const _format = (cur, elementName, target, kind, isLambda) => {
121
121
  if (hasValidProps(cur, 'val')) return encodeURIComponent(formatVal(cur.val, elementName, target, kind))
122
122
  if (hasValidProps(cur, 'xpr')) return `(${_xpr(cur.xpr, target, kind, isLambda)})`
123
123
  // REVISIT: How to detect the types for all functions?
124
- if (hasValidProps(cur, 'func', 'args')) {
125
- return kind === 'odata-v2' ? _odataV2Func(cur.func, cur.args) : `${cur.func}(${_args(cur.args)})`
124
+ if (hasValidProps(cur, 'func')) {
125
+ if (cur.args?.length) {
126
+ return kind === 'odata-v2' ? _odataV2Func(cur.func, cur.args) : `${cur.func}(${_args(cur.args)})`
127
+ }
128
+ return `${cur.func}()`
126
129
  }
127
130
  }
128
131
 
@@ -474,7 +477,7 @@ const parsers = {
474
477
  count: (cqnPart, url, kind, target, isCount) => !isCount && $count(cqnPart, kind),
475
478
  limit: (cqnPart, url, kind, target, isCount) => !isCount && $limit(cqnPart),
476
479
  one: (cqnPart, url, kind, target, isCount) => !isCount && $one(cqnPart, url, kind),
477
- ref: (cqnPart, url, kind, target, isCount) => cqnPart[0].where && $where(cqnPart[0].where, target, kind)
480
+ ref: (cqnPart, url, kind, target, _isCount) => cqnPart[0].where && $where(cqnPart[0].where, target, kind)
478
481
  }
479
482
 
480
483
  function getOptions(cqnPart, url, kind, target, isCount) {
@@ -238,6 +238,35 @@
238
238
  }
239
239
  return elements
240
240
  }
241
+
242
+ const _replaceAliasedInWhere = (where, alias, value, isFromWhere = false) => {
243
+ where?.forEach(element => {
244
+ if (element.val === alias) {
245
+ // TODO check if we want to store replaced aliases/values for req.data in actions/functions in CQN
246
+ element.val = value.val
247
+ } else if (element.list === alias) {
248
+ element.list = value.list
249
+ } else if (element.func) {
250
+ element.args.forEach((arg, i) => {
251
+ if (arg.val === alias) {
252
+ arg.val = value.val
253
+ } else if (arg.func) {
254
+ _replaceAliasedInWhere(arg.args, alias, value, isFromWhere)
255
+ }
256
+ })
257
+ } else if (element.SELECT) {
258
+ _replaceAliased(element.SELECT, alias, value, isFromWhere)
259
+ }
260
+ });
261
+ }
262
+ const _replaceAliased = (select, alias, value, isFromWhere = false) => {
263
+ const {where, from} = select
264
+ _replaceAliasedInWhere(where, alias, value);
265
+
266
+ from?.ref?.forEach(element => {
267
+ _replaceAliasedInWhere(element.where, alias, value, true);
268
+ })
269
+ }
241
270
  }
242
271
 
243
272
  // ---------- Entity Paths ---------------
@@ -334,11 +363,13 @@
334
363
 
335
364
  QueryOption = option:ExpandOption { if(option && option.apply) SELECT.apply = option.apply} /
336
365
  "$skiptoken=" o skiptoken /
366
+ temporal /
337
367
  format /
338
368
  custom /
339
369
  aliasedParamEqualsVal
340
370
  // @OData spec for $expand:
341
- // "Allowed system query options are $filter, $select, $orderby, $skip, $top, $count, $search, $expand and $apply (http://docs.oasis-open.org/odata/odata-data-aggregation-ext/v4.0/cs02/odata-data-aggregation-ext-v4.0-cs02.html#_The_expand_Transformation)."
371
+ // "Allowed system query options are $filter, $select, $orderby, $skip, $top, $count, $search, $expand and
372
+ // $apply (https://go.sap.corp/0jzs)."
342
373
  ExpandOption =
343
374
  "$select=" o select ( COMMA select )* /
344
375
  "$expand=" o expand ( COMMA expand )* expandCount? /
@@ -350,6 +381,7 @@
350
381
  "$count=" o count /
351
382
  "$apply=" o trafos:transformations { return trafos }
352
383
 
384
+ temporal = ("$at" / "$from" / "$toInclusive" / "$to") "=" date
353
385
 
354
386
  select
355
387
  = col:('*' / ref) {
@@ -360,7 +392,7 @@
360
392
 
361
393
  expandCount
362
394
  = "/$count" {
363
- const err = new Error("EXPAND_COUNT_UNSUPPORTED");
395
+ const err = new Error('"/$count" is not supported for expand operation');
364
396
  err.statusCode=501;
365
397
  throw err;
366
398
  }
@@ -370,7 +402,7 @@
370
402
  expandOptions:(option:ExpandOption o ";"? { return option })*
371
403
  {
372
404
  if (expandOptions.find(option => option && option.apply !== undefined)) {
373
- const err = new Error("EXPAND_APPLY_UNSUPPORTED");
405
+ const err = new Error('"$apply" is not supported for expand operation');
374
406
  err.statusCode=501;
375
407
  throw err;
376
408
  }
@@ -468,12 +500,16 @@
468
500
  for (let i=0, k=0; i<any.length; ++i) {
469
501
  let each = any[i]
470
502
  if (each.ref && each.ref.length === 0 && any[i+1] === '=') {
471
- xpr[k++] = { func:'contains', args:[{ref:id}, any[i+=2]] }
503
+ xpr[k++] = { func:'contains', args:[{ref:[id]}, any[i+=2]] }
472
504
  } else {
473
505
  xpr[k++] = each
474
506
  }
475
507
  }
476
508
  if (xpr.length < any.length) {
509
+ if (!nav.length) {
510
+ // no navigation
511
+ return xpr
512
+ }
477
513
  id = nav.pop()
478
514
  return ['exists', { ref: [...nav, { id, where: xpr }] }]
479
515
  } else {
@@ -493,7 +529,7 @@
493
529
  / comp:comparison { p.push(...comp) }
494
530
  / func:function { p.push(func) }
495
531
  / lambda:lambda { p.push(...lambda)}
496
- / list:listFilter {p.push(...list)}
532
+ / list:listFilter { p.push(...list) }
497
533
  )
498
534
  ( ao:(AND/OR) more:inner_lambda { p.push(ao, ...more) } )*
499
535
  { return p }
@@ -512,7 +548,7 @@
512
548
  orderby
513
549
  = ref:(
514
550
  lambda {
515
- const err = new Error("ORDERBY_LAMBDA_UNSUPPORTED");
551
+ const err = new Error('"$orderby" does not support lambda');
516
552
  err.statusCode=501;
517
553
  throw err;
518
554
  } /
@@ -560,14 +596,18 @@
560
596
  return {apply: mainTransformation}
561
597
  }
562
598
 
599
+ aliasedParamVal = val / jsonObject / jsonArray / "[" list:innerList "]" { return { list } }
600
+
563
601
  custom
564
602
  = [a-zA-Z0-9-_.~]+ "=" [^&]*
565
603
  aliasedParam "an aliased parameter (@param)" = "@" i:identifier { return "@" + i }
566
- aliasedParamEqualsVal "@alias=value" = a:aliasedParam "=" v:([^&]*) {return a + "=" + v}
604
+ aliasedParamEqualsVal = alias:aliasedParam "=" !aliasedParam value:aliasedParamVal {
605
+ _replaceAliased(SELECT, alias, value);
606
+ }
567
607
 
568
608
  format = "$format=" f:$([^&]*) {
569
609
  if (f.toLowerCase() !== "json") {
570
- const err = new Error("ONLY_QUERY_PARAM_FORMAT_JSON_ALLOWED")
610
+ const err = new Error('Only query parameter "json" is allowed in "$format".')
571
611
  err.statusCode = 501
572
612
  throw err;
573
613
  }
@@ -581,10 +621,13 @@
581
621
  return [ a, op, b ]
582
622
  }
583
623
 
624
+ listFilterParam = aliased:aliasedParam { return { list: aliased } } / listRoundBrackets
625
+
584
626
  listFilter
585
- = a:operand _ "in" _ b:listRoundBrackets {
627
+ = a:operand _ "in" _ b:listFilterParam {
586
628
  return [ a, "in", b ]
587
629
  }
630
+
588
631
  mathCalc
589
632
  = operand (_ ("add" / "sub" / "mul" / "div" / "mod") _ operand)*
590
633
 
@@ -621,36 +664,39 @@
621
664
 
622
665
  null "null" = "null" {return {val: null }}
623
666
 
667
+ // REVISIT why not JSON.parse() and return JS object?
624
668
  jsonObject "a json object"
625
669
  = val:$("{" (jsonObject / [^}])* "}") {return {val}}
626
670
 
671
+ // REVISIT why not JSON.parse() and return JS array?
627
672
  jsonArray "a json array"
628
673
  = val:$("[" o "]" / "[" o "{" (jsonArray / [^\]])* "]") {return {val}}
629
674
 
675
+ // REVISIT: only used for contains(identifier, ["searchterm"]) <- use innerList instead?
630
676
  list "a list"
631
677
  = "[" any:$([^\]])* "]" // > needs improvment
632
678
  { return { list: any.replace(/"/g,'').split(',').map(ele => ({ val: ele })) } }
633
679
 
680
+ innerList = (val1:val val2:("," v:val { return v })* { return [val1, ...val2] })
681
+
634
682
  listRoundBrackets "a list"
635
- = OPEN list:(val1:val val2:("," v:val { return v })* { return [val1, ...val2] }) CLOSE // > needs improvment
636
- {
637
- return { list }
638
- }
683
+ = OPEN list:innerList CLOSE // > needs improvment
684
+ { return {list} }
639
685
 
640
686
 
641
687
  functionName "a function name"
642
688
  = $[a-zA-Z]+
643
689
 
644
690
  function
645
- = func:functionName OPEN fnArgs:functionArgs CLOSE {
691
+ = func:functionName OPEN args:functionArgs CLOSE {
646
692
  if (strict && !(func.toLowerCase() in strict.functions)) {
647
693
  throw Object.assign(new Error(`"${func}" is an unknown function in OData URL spec (strict mode)`), { statusCode: 400 })
648
694
  }
649
- return { func: func.toLowerCase(), args:[fnArgs.a,...fnArgs.more] }
695
+ return { func: func.toLowerCase(), args }
650
696
  }
651
697
 
652
698
  functionArgs
653
- = a:operand more:( COMMA o:operand {return o} )* {return { a, more }}
699
+ = args:(a:operand more:( COMMA o:operand { return o } )* { return [ a, ...more ] })* { return args.length ? args[0] : args }
654
700
 
655
701
  boolish
656
702
  = func:("contains"i/"endswith"i/"startswith"i) OPEN a:operand COMMA b:operand CLOSE
@@ -758,13 +804,15 @@
758
804
  return {concat: [trafo1, ...trafo2]}
759
805
  }
760
806
 
761
- computeTrafo = OPEN o computeExpr (o COMMA o computeExpr)* o CLOSE //REVISIT: support compute - current implementation is deviating from odata
807
+ // REVISIT: support compute - current implementation is deviating from odata
808
+ computeTrafo = OPEN o computeExpr (o COMMA o computeExpr)* o CLOSE
762
809
 
763
810
  computeExpr = where_clause asAlias
764
811
 
765
812
  commonFuncTrafo = OPEN o first:operand o COMMA o second:operand o CLOSE { return [first, second] }
766
813
 
767
- identityTrafo = "identity" {return {identity: true }} //REVISIT: support identity
814
+ // REVISIT: support identity
815
+ identityTrafo = "identity" {return {identity: true }}
768
816
 
769
817
  topTrafo
770
818
  = OPEN o val:top o CLOSE {
@@ -1,4 +1,4 @@
1
- const cds = require('../_runtime/cds')
1
+ const cds = require('../_runtime/cds'), { decodeURIComponent } = cds.utils
2
2
  const { SELECT } = cds.ql
3
3
 
4
4
  const odata2cqn = require('./parser').parse
@@ -64,7 +64,7 @@ module.exports = {
64
64
  // REVISIT: for okra, remove when no longer needed
65
65
  else if (url.getIncomingRequest) url = url.getIncomingRequest().url
66
66
 
67
- url = decodeURIComponent(url)
67
+ url = decodeURIComponent(url) // REVISIT: do we need that?
68
68
 
69
69
  options = options === 'strict' ? { strict } : options.strict ? { ...options, strict } : options
70
70
  if (options.service) Object.assign(options, { minimal: true, afterburner: afterburner.for(options.service) })
@@ -101,6 +101,7 @@ module.exports = {
101
101
 
102
102
  return cqn
103
103
  },
104
+
104
105
  urlify: (cqn, options = {}) => {
105
106
  return cqn2odata(cqn, options)
106
107
  }