@sap/cds 5.5.5 → 5.6.3

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 (207) hide show
  1. package/CHANGELOG.md +139 -1
  2. package/apis/services.d.ts +31 -1
  3. package/app/index.js +22 -11
  4. package/bin/build/buildTaskFactory.js +1 -1
  5. package/bin/build/provider/buildTaskProviderInternal.js +1 -1
  6. package/bin/build/provider/fiori/index.js +1 -1
  7. package/bin/build/provider/hana/2migration.js +8 -7
  8. package/bin/build/provider/java-cf/index.js +1 -1
  9. package/bin/deploy/to-hana/hana.js +1 -17
  10. package/common.cds +8 -0
  11. package/lib/compile/to/sql.js +22 -2
  12. package/lib/connect/bindings.js +2 -1
  13. package/lib/core/reflect.js +4 -1
  14. package/lib/env/index.js +180 -42
  15. package/lib/env/requires.js +16 -1
  16. package/lib/i18n/localize.js +33 -5
  17. package/lib/index.js +3 -3
  18. package/lib/log/format/kibana.js +6 -2
  19. package/lib/ql/Query.js +1 -0
  20. package/lib/ql/SELECT.js +15 -8
  21. package/lib/ql/Whereable.js +5 -0
  22. package/lib/req/context.js +13 -5
  23. package/lib/serve/Service-dispatch.js +8 -1
  24. package/lib/utils/axios.js +7 -0
  25. package/lib/utils/data.js +1 -1
  26. package/lib/utils/tests.js +1 -1
  27. package/libx/_runtime/audit/Service.js +18 -18
  28. package/libx/_runtime/audit/generic/personal/access.js +1 -1
  29. package/libx/_runtime/audit/generic/personal/modification.js +3 -2
  30. package/libx/_runtime/audit/generic/personal/utils.js +23 -63
  31. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +4 -0
  32. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +37 -35
  33. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +3 -1
  34. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +5 -5
  35. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -7
  36. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +84 -34
  37. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +12 -4
  38. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +9 -3
  39. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +8 -6
  40. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -3
  41. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +13 -11
  42. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +11 -95
  43. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +17 -11
  44. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +2 -1
  45. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/SetResponseHeadersCommand.js +1 -0
  46. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +6 -2
  47. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +3 -34
  48. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -3
  49. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +64 -31
  50. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +10 -5
  51. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +1 -1
  52. package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +1 -1
  53. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +1 -3
  54. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +20 -21
  55. package/libx/_runtime/cds-services/services/utils/columns.js +6 -1
  56. package/libx/_runtime/cds-services/services/utils/compareJson.js +1 -8
  57. package/libx/_runtime/cds-services/services/utils/differ.js +7 -26
  58. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -4
  59. package/libx/_runtime/cds-services/util/assert.js +29 -13
  60. package/libx/_runtime/cds.js +2 -1
  61. package/libx/_runtime/common/aspects/Association.js +72 -0
  62. package/libx/_runtime/common/aspects/any.js +8 -45
  63. package/libx/_runtime/common/aspects/entity.js +0 -1
  64. package/libx/_runtime/common/aspects/relation.js +40 -0
  65. package/libx/_runtime/common/aspects/utils.js +73 -1
  66. package/libx/_runtime/common/auth/strategies/utils/uaa.js +10 -14
  67. package/libx/_runtime/common/composition/data.js +3 -2
  68. package/libx/_runtime/common/composition/delete.js +3 -1
  69. package/libx/_runtime/common/composition/tree.js +23 -18
  70. package/libx/_runtime/common/composition/update.js +9 -1
  71. package/libx/_runtime/common/composition/utils.js +34 -8
  72. package/libx/_runtime/common/error/frontend.js +6 -1
  73. package/libx/_runtime/common/generic/auth.js +5 -9
  74. package/libx/_runtime/common/generic/crud.js +2 -2
  75. package/libx/_runtime/common/generic/etag.js +11 -8
  76. package/libx/_runtime/common/generic/input.js +3 -3
  77. package/libx/_runtime/common/generic/paging.js +9 -5
  78. package/libx/_runtime/common/generic/put.js +3 -2
  79. package/libx/_runtime/common/generic/sorting.js +3 -3
  80. package/libx/_runtime/common/generic/temporal.js +3 -3
  81. package/libx/_runtime/common/utils/cqn.js +20 -1
  82. package/libx/_runtime/common/utils/cqn2cqn4sql.js +125 -139
  83. package/libx/_runtime/common/utils/csn.js +50 -52
  84. package/libx/_runtime/common/utils/foreignKeyPropagations.js +41 -176
  85. package/libx/_runtime/common/utils/generateOnCond.js +40 -70
  86. package/libx/_runtime/common/utils/{enrichWithKeysFromWhere.js → keys.js} +29 -28
  87. package/libx/_runtime/common/utils/postProcessing.js +3 -0
  88. package/libx/_runtime/common/utils/propagateForeignKeys.js +84 -0
  89. package/libx/_runtime/common/utils/resolveStructured.js +1 -1
  90. package/libx/_runtime/common/utils/resolveView.js +7 -5
  91. package/libx/_runtime/common/utils/rewriteAsterisks.js +94 -0
  92. package/libx/_runtime/common/utils/search2cqn4sql.js +9 -8
  93. package/libx/_runtime/common/utils/template.js +54 -46
  94. package/libx/_runtime/db/Service.js +9 -2
  95. package/libx/_runtime/db/expand/expandCQNToJoin.js +54 -33
  96. package/libx/_runtime/db/expand/rawToExpanded.js +2 -1
  97. package/libx/_runtime/db/generic/arrayed.js +13 -28
  98. package/libx/_runtime/db/generic/create.js +1 -0
  99. package/libx/_runtime/db/generic/input.js +7 -11
  100. package/libx/_runtime/db/generic/integrity.js +2 -2
  101. package/libx/_runtime/db/generic/rewrite.js +2 -5
  102. package/libx/_runtime/db/generic/update.js +1 -0
  103. package/libx/_runtime/db/query/read.js +9 -4
  104. package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -2
  105. package/libx/_runtime/db/sql-builder/annotations.js +1 -0
  106. package/libx/_runtime/db/utils/columns.js +14 -43
  107. package/libx/_runtime/fiori/generic/activate.js +3 -2
  108. package/libx/_runtime/fiori/generic/before.js +2 -2
  109. package/libx/_runtime/fiori/generic/cancel.js +3 -2
  110. package/libx/_runtime/fiori/generic/delete.js +3 -2
  111. package/libx/_runtime/fiori/generic/edit.js +3 -3
  112. package/libx/_runtime/fiori/generic/new.js +2 -2
  113. package/libx/_runtime/fiori/generic/patch.js +2 -2
  114. package/libx/_runtime/fiori/generic/prepare.js +2 -2
  115. package/libx/_runtime/fiori/generic/read.js +45 -63
  116. package/libx/_runtime/fiori/generic/readOverDraft.js +4 -4
  117. package/libx/_runtime/fiori/uiflex/extensibility/index.cds +15 -0
  118. package/libx/_runtime/fiori/uiflex/extensibility/index.js +148 -0
  119. package/libx/_runtime/fiori/uiflex/handler/transformREAD.js +119 -0
  120. package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +43 -0
  121. package/libx/_runtime/fiori/uiflex/handler/transformWRITE.js +62 -0
  122. package/libx/_runtime/fiori/uiflex/index.js +35 -0
  123. package/libx/_runtime/fiori/uiflex/utils.js +78 -0
  124. package/libx/_runtime/fiori/utils/handler.js +3 -13
  125. package/libx/_runtime/fiori/utils/where.js +6 -1
  126. package/libx/_runtime/hana/pool.js +12 -11
  127. package/libx/_runtime/hana/search2cqn4sql.js +34 -43
  128. package/libx/_runtime/hana/searchToContains.js +3 -3
  129. package/libx/_runtime/index.js +5 -2
  130. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  131. package/libx/_runtime/messaging/common-utils/AMQPClient.js +16 -3
  132. package/libx/_runtime/messaging/common-utils/connections.js +11 -14
  133. package/libx/_runtime/messaging/common-utils/naming-conventions.js +1 -1
  134. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -1
  135. package/libx/_runtime/messaging/message-queuing.js +18 -0
  136. package/libx/_runtime/remote/Service.js +20 -4
  137. package/libx/_runtime/remote/utils/client-types.d.ts +7 -0
  138. package/libx/_runtime/remote/utils/client.js +117 -23
  139. package/libx/_runtime/sqlite/Service.js +2 -2
  140. package/libx/_runtime/sqlite/convertAssocToOneManaged.js +1 -3
  141. package/libx/gql/GraphQLAdapter.js +33 -0
  142. package/libx/gql/constants/adapter.js +69 -0
  143. package/libx/gql/constants/cds.js +18 -0
  144. package/libx/gql/constants/graphql.js +33 -0
  145. package/libx/gql/resolvers/crud/create.js +15 -0
  146. package/libx/gql/resolvers/crud/delete.js +24 -0
  147. package/libx/gql/resolvers/crud/index.js +6 -0
  148. package/libx/gql/resolvers/crud/read.js +25 -0
  149. package/libx/gql/resolvers/crud/update.js +31 -0
  150. package/libx/gql/resolvers/crud/utils/index.js +36 -0
  151. package/libx/gql/resolvers/field.js +5 -0
  152. package/libx/gql/resolvers/index.js +7 -0
  153. package/libx/gql/resolvers/mutation.js +23 -0
  154. package/libx/gql/resolvers/parse/ast/enrich.js +51 -0
  155. package/libx/gql/resolvers/parse/ast/fragment.js +11 -0
  156. package/libx/gql/resolvers/parse/ast/fromObject.js +39 -0
  157. package/libx/gql/resolvers/parse/ast/index.js +3 -0
  158. package/libx/gql/resolvers/parse/ast/meta.js +4 -0
  159. package/libx/gql/resolvers/parse/ast/variable.js +7 -0
  160. package/libx/gql/resolvers/parse/ast2cqn/columns.js +42 -0
  161. package/libx/gql/resolvers/parse/ast2cqn/entries.js +31 -0
  162. package/libx/gql/resolvers/parse/ast2cqn/index.js +8 -0
  163. package/libx/gql/resolvers/parse/ast2cqn/limit.js +6 -0
  164. package/libx/gql/resolvers/parse/ast2cqn/orderBy.js +24 -0
  165. package/libx/gql/resolvers/parse/ast2cqn/utils/index.js +3 -0
  166. package/libx/gql/resolvers/parse/ast2cqn/where.js +70 -0
  167. package/libx/gql/resolvers/parse/utils/index.js +8 -0
  168. package/libx/gql/resolvers/query.js +13 -0
  169. package/libx/gql/resolvers/root.js +34 -0
  170. package/libx/gql/schema/generate.js +18 -0
  171. package/libx/gql/schema/index.js +5 -0
  172. package/libx/gql/schema/mutation.js +76 -0
  173. package/libx/gql/schema/query.js +108 -0
  174. package/libx/gql/schema/typeDefMap.js +45 -0
  175. package/libx/gql/schema/utils/index.js +54 -0
  176. package/libx/gql/utils/index.js +12 -0
  177. package/libx/{_runtime/odata/cqn2odata.js → odata/cqn2odata/index.js} +39 -100
  178. package/libx/odata/index.js +80 -0
  179. package/libx/odata/odata2cqn/afterburner.js +170 -0
  180. package/libx/{_runtime/odata/odata2cqn.pegjs → odata/odata2cqn/grammar.pegjs} +102 -123
  181. package/libx/odata/odata2cqn/index.js +3 -0
  182. package/libx/odata/odata2cqn/parser.js +1 -0
  183. package/libx/odata/utils/index.js +64 -0
  184. package/libx/rest/RestAdapter.js +101 -0
  185. package/libx/rest/RestRequest.js +30 -0
  186. package/libx/rest/index.js +3 -0
  187. package/libx/rest/middleware/auth.js +22 -0
  188. package/libx/rest/middleware/content.js +15 -0
  189. package/libx/rest/middleware/create.js +40 -0
  190. package/libx/rest/middleware/delete.js +20 -0
  191. package/libx/rest/middleware/error.js +56 -0
  192. package/libx/rest/middleware/operation.js +39 -0
  193. package/libx/rest/middleware/parse.js +90 -0
  194. package/libx/rest/middleware/read.js +29 -0
  195. package/libx/rest/middleware/update.js +42 -0
  196. package/libx/rest/utils/data.js +65 -0
  197. package/package.json +4 -1
  198. package/server.js +29 -7
  199. package/libx/_runtime/cds-services/services/utils/diff.js +0 -53
  200. package/libx/_runtime/cds-services/util/auditlog.js +0 -247
  201. package/libx/_runtime/cds-services/util/xsenv.js +0 -51
  202. package/libx/_runtime/common/utils/backlinks.js +0 -83
  203. package/libx/_runtime/common/utils/rewriteAsterisk.js +0 -72
  204. package/libx/_runtime/odata/index.js +0 -55
  205. package/libx/_runtime/odata/odata2cqn.js +0 -1
  206. package/libx/_runtime/odata/readToCqn.js +0 -129
  207. package/libx/_runtime/remote/cqn2odata/index.js +0 -2
@@ -1,3 +1,5 @@
1
+ const { formatVal } = require('../utils')
2
+
1
3
  const OPERATORS = {
2
4
  '=': 'eq',
3
5
  '!=': 'ne',
@@ -10,23 +12,13 @@ const OPERATORS = {
10
12
 
11
13
  const LAMBDA_VARIABLE = 'd'
12
14
 
13
- const getSafeNumber = str => {
14
- const n = Number(str)
15
- return Number.isSafeInteger(n) || String(n) === str ? n : str
16
- }
17
-
18
15
  const needArrayProps = Object.fromEntries(
19
- ['where', 'search', 'xpr', 'columns', 'expand', 'orderBy', 'ref', 'args'].map(propName => [
16
+ ['where', 'search', 'xpr', 'columns', 'orderBy', 'ref', 'args'].map(propName => [
20
17
  propName,
21
- cur =>
22
- (Array.isArray(cur) && (cur.length !== 0 || propName === 'expand' || propName === 'ref')) ||
23
- (propName === 'expand' && cur === '*')
18
+ cur => Array.isArray(cur) && (cur.length !== 0 || propName === 'expand' || propName === 'ref')
24
19
  ])
25
20
  )
26
21
 
27
- const V4UUIDREGEXP = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
28
- const isV4UUID = val => V4UUIDREGEXP.test(val)
29
-
30
22
  const validators = {
31
23
  SELECT: SELECT => SELECT && SELECT.from,
32
24
  INSERT: INSERT => {
@@ -50,63 +42,13 @@ const validators = {
50
42
  func: func => typeof func === 'string',
51
43
  one: count => typeof count === 'boolean',
52
44
  as: any => typeof any === 'string',
45
+ expand: any => any === '*' || Array.isArray(any),
53
46
  ...needArrayProps
54
47
  }
55
48
 
56
49
  // strip service & namespace prefixes
57
50
  const _entityUrl = path => path.match(/^(\w*\.)*(.*)$/)[2]
58
51
 
59
- const formatVal = (val, element, csnTarget, kind) => {
60
- if (val === null || val === 'null') return 'null'
61
- if (typeof val === 'boolean') return val
62
- if (typeof val === 'number') return getSafeNumber(val)
63
- if (!csnTarget && typeof val === 'string' && isV4UUID(val)) return kind === 'odata-v2' ? `guid'${val}'` : val
64
-
65
- const csnElement = (csnTarget && csnTarget.elements && csnTarget.elements[element]) || { type: undefined }
66
- return kind === 'odata-v2' ? _odataV2Val(val, csnElement.type) : _val(val, csnElement.type)
67
- }
68
-
69
- const _isTimestamp = val =>
70
- /^\d+-\d\d-\d\d(T\d\d:\d\d(:\d\d(\.\d+)?)?(Z|([+-]{1}\d\d:\d\d))?)?$/.test(val) && !isNaN(Date.parse(val))
71
-
72
- const _odataV2Val = (val, type) => {
73
- switch (type) {
74
- case 'cds.Binary':
75
- case 'cds.LargeBinary':
76
- return `binary'${val}'`
77
- case 'cds.Date':
78
- case 'cds.DateTime':
79
- return `datetime'${val}'`
80
- case 'cds.Time':
81
- // eslint-disable-next-line no-case-declarations
82
- const [hh, mm, ss] = val.split(':')
83
- return `time'PT${hh}H${mm}M${ss}S'`
84
- case 'cds.Timestamp':
85
- return `datetimeoffset'${val}'`
86
- case 'cds.UUID':
87
- return `guid'${val}'`
88
- default:
89
- return `'${val}'`
90
- }
91
- }
92
-
93
- const _val = (val, type) => {
94
- switch (type) {
95
- case 'cds.Decimal':
96
- case 'cds.Integer64':
97
- return getSafeNumber(val)
98
- case 'cds.Boolean':
99
- case 'cds.DateTime':
100
- case 'cds.Date':
101
- case 'cds.Timestamp':
102
- case 'cds.Time':
103
- case 'cds.UUID':
104
- return val
105
- default:
106
- return _isTimestamp(val) ? val : `'${val}'`
107
- }
108
- }
109
-
110
52
  function getProp(obj, propName) {
111
53
  const validate = validators[propName]
112
54
  const isValid = validate && validate(obj[propName])
@@ -166,13 +108,25 @@ const _in = (column, /* in */ collection, target, kind, isLambda) => {
166
108
  }
167
109
  }
168
110
 
111
+ const _odataV2Func = (func, args) => {
112
+ switch (func) {
113
+ case 'contains':
114
+ // this doesn't support the contains signature with two collections as args, introduced in odata v4.01
115
+ return `substringof(${_args([args[1], args[0]])})`
116
+ default:
117
+ return `${func}(${_args(args)})`
118
+ }
119
+ }
120
+
169
121
  const _format = (cur, element, target, kind, isLambda) => {
170
122
  if (typeof cur !== 'object') return formatVal(cur, element, target, kind)
171
123
  if (hasValidProps(cur, 'ref')) return isLambda ? [LAMBDA_VARIABLE, ...cur.ref].join('/') : cur.ref.join('/')
172
124
  if (hasValidProps(cur, 'val')) return formatVal(cur.val, element, target, kind)
173
125
  if (hasValidProps(cur, 'xpr')) return `(${_xpr(cur.xpr, target, kind, isLambda)})`
174
126
  // REVISIT: How to detect the types for all functions?
175
- if (hasValidProps(cur, 'func', 'args')) return `${cur.func}(${_args(cur.args)})`
127
+ if (hasValidProps(cur, 'func', 'args')) {
128
+ return kind === 'odata-v2' ? _odataV2Func(cur.func, cur.args) : `${cur.func}(${_args(cur.args)})`
129
+ }
176
130
  }
177
131
 
178
132
  const _isLambda = (cur, next) => {
@@ -328,62 +282,48 @@ const _parseColumnsV2 = (columns, prefix = []) => {
328
282
  return { select, expand }
329
283
  }
330
284
 
331
- const _parseColumns = (columns, options) => {
332
- const isExpand = options.expand
285
+ const _parseColumns = columns => {
333
286
  const select = []
334
287
  const expand = []
335
288
 
336
- if (columns === '*') {
337
- return { select, expand }
338
- }
339
-
340
- const isSelectAll = select =>
341
- select.length === 1 && (select[0] === '*' || (select[0].ref && select[0].ref[0] === '*'))
342
-
343
289
  for (const column of columns) {
344
290
  if (hasValidProps(column, 'ref')) {
345
- const refName = column.ref.join('/')
346
- let refNameWithOptions = refName
347
-
291
+ let refName = column.ref.join('/')
348
292
  if (hasValidProps(column, 'expand')) {
293
+ // REVISIT: incomplete, see test Foo?$expand=invoices($count=true;$expand=item($search="some"))
294
+ if (!columns.some(c => !c.expand)) select.push(refName)
349
295
  const curOptions = getOptions(column).join(';')
350
- refNameWithOptions += curOptions ? `(${curOptions})` : ''
351
- expand.push(refNameWithOptions)
352
- if (!isSelectAll(select) && !isExpand) select.push(refName)
296
+ refName += curOptions ? `(${curOptions})` : ''
297
+ expand.push(refName)
353
298
  // REVISIT: expand to one & limit in options
354
- // > const expanded = $expand(column.expand)
355
- // > expand.push(expanded ? `${refNameWithOptions}(${expanded})` : ref)
299
+ // > const expanded = $expand(col.expand)
300
+ // > expand.push(expanded ? `${ref}(${expanded})` : ref)
356
301
  // see xtest('READ with expand'... in custom handler test
357
302
  } else {
358
- select.push(refNameWithOptions)
303
+ select.push(refName)
359
304
  }
305
+ } else if (hasValidProps(column, 'expand') && column.expand === '*') {
306
+ expand.push('*')
360
307
  }
361
-
362
308
  if (column === '*') {
363
309
  select.push(column)
364
310
  }
365
311
  }
366
-
367
- // omit $select query option if it only contains '*'
368
- if (isSelectAll(select)) {
312
+ // omit '$select' option if contains only '*'
313
+ if (select.length === 1 && (select[0] === '*' || (select[0].ref && select[0].ref[0] === '*'))) {
369
314
  select.pop()
370
315
  }
371
-
372
- const uniqueSelect = [...new Set(select)]
373
- return { select: uniqueSelect, expand }
316
+ return { select, expand }
374
317
  }
375
318
 
376
- function $select(columns, kind, separator = '&', isExpand = false) {
377
- const { select, expand } =
378
- kind === 'odata-v2' ? _parseColumnsV2(columns) : _parseColumns(columns, { expand: isExpand })
379
-
319
+ function $select(columns, kind, separator = '&') {
320
+ const { select, expand } = kind === 'odata-v2' ? _parseColumnsV2(columns) : _parseColumns(columns)
380
321
  const res = []
381
322
  if (expand.length) res.unshift('$expand=' + expand.join(','))
382
323
  if (select.length) res.unshift('$select=' + select.join(','))
383
324
  return res.join(separator)
384
325
  }
385
-
386
- const $expand = columns => $select(columns, 'odata', ';', true)
326
+ const $expand = columns => $select(columns, 'odata', ';')
387
327
 
388
328
  function $count(count, kind) {
389
329
  if (count !== true) return ''
@@ -476,7 +416,9 @@ const _isOdataUrlWithKeys = (url, kind) => kind !== 'rest' && /^[\w\.]+\(.*\)/.t
476
416
  const parsers = {
477
417
  columns: (cqnPart, url, kind, target, isCount) => !isCount && $select(cqnPart, kind),
478
418
  expand: (cqnPart, url, kind, target, isCount) => !isCount && $expand(cqnPart),
419
+ // eslint-disable-next-line no-unused-vars
479
420
  where: (cqnPart, url, kind, target, isCount) => $where(cqnPart, target, kind),
421
+ // eslint-disable-next-line no-unused-vars
480
422
  search: (cqnPart, url, kind, target, isCount) => $search(cqnPart, kind),
481
423
  orderBy: (cqnPart, url, kind, target, isCount) => !isCount && $orderBy(cqnPart),
482
424
  count: (cqnPart, url, kind, target, isCount) => !isCount && $count(cqnPart, kind),
@@ -503,9 +445,7 @@ function getOptions(cqnPart, url, kind, target, isCount) {
503
445
  const _isCount = SELECT => {
504
446
  if (SELECT.columns) {
505
447
  const columns = getProp(SELECT, 'columns')
506
- return columns.some(
507
- c => c.func === 'count' && c.as === '$count' && c.args && c.args.length === 1 && c.args[0] === '*'
508
- )
448
+ return columns.some(c => c.func === 'count' && c.as === '$count')
509
449
  }
510
450
  return false
511
451
  }
@@ -535,7 +475,6 @@ const _copyData = data => {
535
475
  ? data[property].val
536
476
  : data[property]
537
477
  }
538
-
539
478
  return copied
540
479
  }
541
480
 
@@ -581,4 +520,4 @@ function cqn2odata(cqn, kind, model) {
581
520
  throw new Error('Unknown CQN object cannot be translated to URL: ' + JSON.stringify(cqn))
582
521
  }
583
522
 
584
- module.exports = { cqn2odata, formatVal, getSafeNumber }
523
+ module.exports = cqn2odata
@@ -0,0 +1,80 @@
1
+ const cds = require('../_runtime/cds')
2
+ const { SELECT } = cds.ql
3
+
4
+ const odata2cqn = require('./odata2cqn')
5
+ const cqn2odata = require('./cqn2odata')
6
+
7
+ const afterburner = require('./odata2cqn/afterburner')
8
+ const { getSafeNumber: safeNumber } = require('./utils')
9
+
10
+ const strict = {
11
+ functions: {
12
+ contains: 1,
13
+ startswith: 1,
14
+ endswith: 1,
15
+ tolower: 1,
16
+ toupper: 1,
17
+ length: 1,
18
+ indexof: 1,
19
+ substring: 1,
20
+ trim: 1,
21
+ concat: 1,
22
+ year: 1,
23
+ month: 1,
24
+ day: 1,
25
+ hour: 1,
26
+ minute: 1,
27
+ second: 1,
28
+ time: 1,
29
+ now: 1
30
+ }
31
+ }
32
+
33
+ /*
34
+ * cds.odata API
35
+ */
36
+ module.exports = {
37
+ parse: (url, options = {}) => {
38
+ // first arg may also be req
39
+ if (url.url) url = url.url
40
+ // REVISIT: for okra, remove when no longer needed
41
+ else if (url.getIncomingRequest) url = url.getIncomingRequest().url
42
+ url = decodeURIComponent(url)
43
+
44
+ options = options === 'strict' ? { strict } : options.strict ? { ...options, strict } : options
45
+ if (options.service) Object.assign(options, { minimal: true, afterburner: afterburner.for(options.service) })
46
+ options.safeNumber = safeNumber
47
+
48
+ let cqn
49
+ try {
50
+ cqn = odata2cqn(url, options)
51
+ } catch (e) {
52
+ // REVISIT: additional try in catch isn't nice -> find better way
53
+ // known gaps -> e.message is a stringified error -> use that
54
+ // unknown errors -> e is the error to keep
55
+ let err = e
56
+ try {
57
+ err = JSON.parse(e.message)
58
+ } catch {
59
+ /* nothing to do */
60
+ }
61
+ err.message = 'Parsing URL failed with error: ' + err.message
62
+ err.statusCode = err.statusCode || 400
63
+ throw err
64
+ }
65
+
66
+ if (typeof options.afterburner === 'function') cqn = options.afterburner(cqn)
67
+
68
+ const query = cqn.SELECT.one ? SELECT.one(cqn.SELECT.from) : SELECT.from(cqn.SELECT.from)
69
+ Object.assign(query.SELECT, cqn.SELECT)
70
+
71
+ // REVISIT: _target vs __target, i.e., pseudo csn vs actual csn
72
+ // DO NOT USE __target outside of libx/rest!!!
73
+ query.__target = cqn.__target
74
+
75
+ return query
76
+ },
77
+ urlify: (cqn, options = {}) => {
78
+ return cqn2odata(cqn, options.kind, options.model)
79
+ }
80
+ }
@@ -0,0 +1,170 @@
1
+ const cds = require('../../_runtime/cds')
2
+
3
+ const { where2obj } = require('../../_runtime/common/utils/cqn')
4
+ const { findCsnTargetFor } = require('../../_runtime/common/utils/csn')
5
+
6
+ function _keysOf(entity) {
7
+ return entity && entity.keys
8
+ ? Object.keys(entity.keys).filter(
9
+ k => entity.elements[k].type !== 'cds.Association' && entity.elements[k]['@odata.foreignKey4'] !== 'up_'
10
+ )
11
+ : []
12
+ }
13
+
14
+ function _getDefinition(definition, name) {
15
+ return (
16
+ (definition.definitions && definition.definitions[name]) ||
17
+ (definition.elements && definition.elements[name]) ||
18
+ (definition.actions && definition.actions[name]) ||
19
+ definition[name]
20
+ )
21
+ }
22
+
23
+ function _resolveAliasInWhere(where, entity) {
24
+ if (!entity._alias2ref) return
25
+ for (let i = 0; i < where.length; i++) {
26
+ if (!where[i].ref || where[i].ref.length > 1 || entity.keys[where[i].ref[0]]) continue
27
+ where[i].ref = entity._alias2ref[where[i].ref[0]] || where[i].ref
28
+ }
29
+ }
30
+
31
+ // case: single key without name, e.g., Foo(1)
32
+ function _addRefToWhereIfNecessary(where, entity) {
33
+ if (!where || where.length !== 1) return 0
34
+ const keys = _keysOf(entity)
35
+ if (keys.length !== 1) return 0
36
+ where.unshift(...[{ ref: [keys[0]] }, '='])
37
+ return 1
38
+ }
39
+
40
+ function _processSegments(cqn, model) {
41
+ const { ref } = cqn.SELECT.from
42
+
43
+ let current = model
44
+ let path
45
+ let keys = null
46
+ let keyCount = 0
47
+ let incompleteKeys
48
+ let one
49
+ for (let i = 0; i < ref.length; i++) {
50
+ const seg = ref[i].id || ref[i]
51
+ const params = ref[i].where && where2obj(ref[i].where)
52
+
53
+ if (incompleteKeys) {
54
+ // > key
55
+ keys = keys || _keysOf(current)
56
+ const key = keys[keyCount++]
57
+ one = true
58
+ const element = current.elements[key]
59
+ let base = ref[i - keyCount]
60
+ if (!base.id) base = { id: base, where: [] }
61
+ if (base.where.length) base.where.push('and')
62
+ base.where.push({ ref: [key] }, '=', { val: element.type === 'cds.Integer' ? Number(seg) : seg })
63
+ ref[i] = null
64
+ ref[i - keyCount] = base
65
+ incompleteKeys = keyCount < keys.length
66
+ } else {
67
+ // > entity or property (incl. nested) or navigation or action or function
68
+ keys = null
69
+ keyCount = 0
70
+ one = false
71
+
72
+ path = path ? path + `${path.match(/:/) ? '.' : ':'}${seg}` : seg
73
+ // REVISIT: replace use case: <namespace>.<entity>_history is at <namespace>.<entity>.history
74
+ current = _getDefinition(current, seg) || _getDefinition(current, seg.replace(/_/g, '.'))
75
+ // REVISIT: 404 or 400?
76
+ if (!current) cds.error(`Invalid resource path "${path}"`, { code: 404 })
77
+
78
+ if (current.kind === 'entity') {
79
+ // > entity
80
+ one = !!(ref[i].where || current._isSingleton)
81
+ incompleteKeys = ref[i].where ? false : i === ref.length - 1 || one ? false : true
82
+ if (ref[i].where) {
83
+ keyCount += _addRefToWhereIfNecessary(ref[i].where, current)
84
+ _resolveAliasInWhere(ref[i].where, current)
85
+ }
86
+ } else if ({ action: 1, function: 1 }[current.kind]) {
87
+ // > action or function
88
+ if (i !== ref.length - 1) {
89
+ const msg = `${i ? 'Unbound' : 'Bound'} ${current.kind} are only supported as the last path segment.`
90
+ throw Object.assign(new Error(msg), { statusCode: 501 })
91
+ }
92
+ ref[i] = { operation: current.name }
93
+ if (params) ref[i].args = params
94
+ if (current.returns && current.returns.type) one = true
95
+ } else if (current.isAssociation) {
96
+ // > navigation
97
+ one = !!(current.is2one || ref[i].where)
98
+ incompleteKeys = one || i === ref.length - 1 ? false : true
99
+ current = model.definitions[current.target]
100
+ if (ref[i].where) {
101
+ keyCount += _addRefToWhereIfNecessary(ref[i].where, current)
102
+ _resolveAliasInWhere(ref[i].where, current)
103
+ }
104
+ } else if (current._isStructured) {
105
+ // > nested property
106
+ one = true
107
+ current = current.elements
108
+ } else {
109
+ // > property
110
+ one = true
111
+ }
112
+ }
113
+ }
114
+
115
+ if (incompleteKeys) {
116
+ // > last segment not fully qualified
117
+ throw Object.assign(
118
+ new Error(
119
+ `Entity "${current.name}" has ${_keysOf(current).length} keys. Only ${keyCount} ${
120
+ keyCount === 1 ? 'was' : 'were'
121
+ } provided.`
122
+ ),
123
+ { status: 400 }
124
+ )
125
+ }
126
+
127
+ // remove all nulled refs
128
+ cqn.SELECT.from.ref = ref.filter(r => r)
129
+
130
+ // one?
131
+ if (one) cqn.SELECT.one = true
132
+
133
+ // REVISIT: better
134
+ // set target (csn definition) for later retrieval
135
+ cqn.__target = current
136
+ }
137
+
138
+ function _4service(service) {
139
+ const { namespace, model } = service
140
+
141
+ return cqn => {
142
+ const { ref } = cqn.SELECT.from
143
+
144
+ // REVISIT: shouldn't be necessary
145
+ /*
146
+ * make first path segment fully qualified
147
+ */
148
+ const root = findCsnTargetFor(ref[0].id || ref[0], model, namespace)
149
+ // REVISIT: 404 or 400?
150
+ if (!root) cds.error(`Invalid resource path "${namespace}.${ref[0].id || ref[0]}"`, { code: 404 })
151
+ if (ref[0].id) ref[0].id = root.name
152
+ else ref[0] = root.name
153
+
154
+ /*
155
+ * key vs. path segments (/Books/1/author/books/2/...) and more
156
+ */
157
+ _processSegments(cqn, model)
158
+
159
+ return cqn
160
+ }
161
+ }
162
+
163
+ const cache = new WeakMap()
164
+
165
+ module.exports = {
166
+ for: service => {
167
+ if (!cache.has(service)) cache.set(service, _4service(service))
168
+ return cache.get(service)
169
+ }
170
+ }