@sap/cds 5.5.2 → 5.6.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.
Files changed (228) hide show
  1. package/CHANGELOG.md +150 -17
  2. package/apis/services.d.ts +27 -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/index.js +1 -1
  12. package/lib/compile/to/sql.js +22 -2
  13. package/lib/connect/bindings.js +2 -1
  14. package/lib/connect/index.js +1 -1
  15. package/lib/core/infer.js +1 -1
  16. package/lib/core/reflect.js +3 -1
  17. package/lib/env/index.js +175 -41
  18. package/lib/env/requires.js +24 -3
  19. package/lib/i18n/localize.js +31 -4
  20. package/lib/index.js +7 -6
  21. package/lib/log/format/kibana.js +6 -2
  22. package/lib/ql/DELETE.js +1 -1
  23. package/lib/ql/INSERT.js +1 -1
  24. package/lib/ql/Query.js +13 -10
  25. package/lib/ql/SELECT.js +15 -8
  26. package/lib/ql/UPDATE.js +1 -1
  27. package/lib/ql/Whereable.js +5 -0
  28. package/lib/req/context.js +87 -37
  29. package/lib/req/{impl.js → request.js} +1 -1
  30. package/lib/req/{res.js → response.js} +0 -0
  31. package/lib/serve/Service-api.js +1 -1
  32. package/lib/serve/Service-dispatch.js +12 -2
  33. package/lib/serve/Service-handlers.js +21 -7
  34. package/lib/serve/Service-methods.js +1 -1
  35. package/lib/serve/Transaction.js +7 -6
  36. package/lib/serve/index.js +1 -1
  37. package/lib/utils/axios.js +7 -0
  38. package/lib/utils/data.js +1 -1
  39. package/lib/utils/tests.js +5 -3
  40. package/libx/_runtime/audit/Service.js +18 -18
  41. package/libx/_runtime/audit/generic/personal/access.js +1 -1
  42. package/libx/_runtime/audit/generic/personal/modification.js +3 -2
  43. package/libx/_runtime/audit/generic/personal/utils.js +23 -63
  44. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +6 -0
  45. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +37 -35
  46. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -1
  47. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +3 -1
  48. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +5 -5
  49. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +1 -1
  50. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -7
  51. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +84 -34
  52. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +10 -4
  53. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +9 -3
  54. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +8 -6
  55. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -3
  56. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +13 -11
  57. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +11 -95
  58. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +17 -11
  59. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +2 -1
  60. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +6 -2
  61. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +3 -34
  62. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -3
  63. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +48 -18
  64. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +10 -5
  65. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +1 -1
  66. package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +1 -1
  67. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +1 -3
  68. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +9 -2
  69. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +14 -19
  70. package/libx/_runtime/cds-services/services/utils/columns.js +6 -1
  71. package/libx/_runtime/cds-services/services/utils/compareJson.js +1 -8
  72. package/libx/_runtime/cds-services/services/utils/differ.js +7 -26
  73. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -4
  74. package/libx/_runtime/cds-services/util/assert.js +29 -13
  75. package/libx/_runtime/cds.js +2 -1
  76. package/libx/_runtime/common/aspects/Association.js +72 -0
  77. package/libx/_runtime/common/aspects/any.js +8 -45
  78. package/libx/_runtime/common/aspects/entity.js +0 -1
  79. package/libx/_runtime/common/aspects/relation.js +40 -0
  80. package/libx/_runtime/common/aspects/utils.js +73 -1
  81. package/libx/_runtime/common/auth/strategies/utils/uaa.js +1 -12
  82. package/libx/_runtime/common/composition/data.js +3 -2
  83. package/libx/_runtime/common/composition/delete.js +3 -1
  84. package/libx/_runtime/common/composition/tree.js +23 -18
  85. package/libx/_runtime/common/composition/utils.js +34 -8
  86. package/libx/_runtime/common/error/frontend.js +6 -1
  87. package/libx/_runtime/common/generic/auth.js +15 -13
  88. package/libx/_runtime/common/generic/crud.js +2 -2
  89. package/libx/_runtime/common/generic/etag.js +11 -8
  90. package/libx/_runtime/common/generic/input.js +3 -3
  91. package/libx/_runtime/common/generic/paging.js +9 -5
  92. package/libx/_runtime/common/generic/put.js +3 -2
  93. package/libx/_runtime/common/generic/sorting.js +3 -3
  94. package/libx/_runtime/common/generic/temporal.js +3 -3
  95. package/libx/_runtime/common/toggles/alpha.js +1 -1
  96. package/libx/_runtime/common/utils/cqn.js +20 -1
  97. package/libx/_runtime/common/utils/cqn2cqn4sql.js +125 -139
  98. package/libx/_runtime/common/utils/csn.js +50 -52
  99. package/libx/_runtime/common/utils/foreignKeyPropagations.js +41 -176
  100. package/libx/_runtime/common/utils/generateOnCond.js +40 -70
  101. package/libx/_runtime/common/utils/{enrichWithKeysFromWhere.js → keys.js} +29 -28
  102. package/libx/_runtime/common/utils/postProcessing.js +3 -0
  103. package/libx/_runtime/common/utils/propagateForeignKeys.js +84 -0
  104. package/libx/_runtime/common/utils/resolveStructured.js +1 -1
  105. package/libx/_runtime/common/utils/resolveView.js +20 -10
  106. package/libx/_runtime/common/utils/rewriteAsterisks.js +94 -0
  107. package/libx/_runtime/common/utils/search2cqn4sql.js +9 -8
  108. package/libx/_runtime/common/utils/template.js +54 -46
  109. package/libx/_runtime/db/Service.js +9 -2
  110. package/libx/_runtime/db/expand/expandCQNToJoin.js +11 -25
  111. package/libx/_runtime/db/expand/rawToExpanded.js +2 -1
  112. package/libx/_runtime/db/generic/create.js +1 -0
  113. package/libx/_runtime/db/generic/input.js +7 -11
  114. package/libx/_runtime/db/generic/integrity.js +2 -2
  115. package/libx/_runtime/db/generic/rewrite.js +2 -5
  116. package/libx/_runtime/db/generic/update.js +1 -0
  117. package/libx/_runtime/db/query/read.js +10 -5
  118. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +6 -0
  119. package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -2
  120. package/libx/_runtime/db/sql-builder/annotations.js +1 -0
  121. package/libx/_runtime/db/utils/columns.js +14 -43
  122. package/libx/_runtime/db/utils/deep.js +5 -7
  123. package/libx/_runtime/fiori/generic/activate.js +3 -2
  124. package/libx/_runtime/fiori/generic/before.js +2 -2
  125. package/libx/_runtime/fiori/generic/cancel.js +3 -2
  126. package/libx/_runtime/fiori/generic/delete.js +3 -2
  127. package/libx/_runtime/fiori/generic/edit.js +2 -2
  128. package/libx/_runtime/fiori/generic/new.js +2 -2
  129. package/libx/_runtime/fiori/generic/patch.js +2 -2
  130. package/libx/_runtime/fiori/generic/prepare.js +2 -2
  131. package/libx/_runtime/fiori/generic/read.js +17 -63
  132. package/libx/_runtime/fiori/generic/readOverDraft.js +4 -4
  133. package/libx/_runtime/fiori/uiflex/extensibility/index.cds +15 -0
  134. package/libx/_runtime/fiori/uiflex/extensibility/index.js +148 -0
  135. package/libx/_runtime/fiori/uiflex/handler/transformREAD.js +119 -0
  136. package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +43 -0
  137. package/libx/_runtime/fiori/uiflex/handler/transformWRITE.js +62 -0
  138. package/libx/_runtime/fiori/uiflex/index.js +35 -0
  139. package/libx/_runtime/fiori/uiflex/utils.js +78 -0
  140. package/libx/_runtime/fiori/utils/handler.js +3 -13
  141. package/libx/_runtime/fiori/utils/where.js +6 -1
  142. package/libx/_runtime/hana/Service.js +5 -2
  143. package/libx/_runtime/hana/execute.js +1 -1
  144. package/libx/_runtime/hana/pool.js +12 -11
  145. package/libx/_runtime/hana/search2cqn4sql.js +34 -43
  146. package/libx/_runtime/hana/searchToContains.js +3 -3
  147. package/libx/_runtime/index.js +5 -2
  148. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  149. package/libx/_runtime/messaging/common-utils/AMQPClient.js +9 -1
  150. package/libx/_runtime/messaging/common-utils/connections.js +11 -14
  151. package/libx/_runtime/messaging/common-utils/naming-conventions.js +1 -1
  152. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -1
  153. package/libx/_runtime/messaging/enterprise-messaging.js +1 -1
  154. package/libx/_runtime/messaging/message-queuing.js +18 -0
  155. package/libx/_runtime/remote/Service.js +14 -2
  156. package/libx/_runtime/remote/utils/client-types.d.ts +7 -0
  157. package/libx/_runtime/remote/utils/client.js +117 -23
  158. package/libx/_runtime/sqlite/Service.js +4 -3
  159. package/libx/_runtime/sqlite/convertAssocToOneManaged.js +1 -3
  160. package/libx/_runtime/sqlite/execute.js +1 -1
  161. package/libx/gql/GraphQLAdapter.js +33 -0
  162. package/libx/gql/constants/adapter.js +69 -0
  163. package/libx/gql/constants/cds.js +18 -0
  164. package/libx/gql/constants/graphql.js +33 -0
  165. package/libx/gql/resolvers/crud/create.js +15 -0
  166. package/libx/gql/resolvers/crud/delete.js +24 -0
  167. package/libx/gql/resolvers/crud/index.js +6 -0
  168. package/libx/gql/resolvers/crud/read.js +25 -0
  169. package/libx/gql/resolvers/crud/update.js +31 -0
  170. package/libx/gql/resolvers/crud/utils/index.js +36 -0
  171. package/libx/gql/resolvers/field.js +5 -0
  172. package/libx/gql/resolvers/index.js +7 -0
  173. package/libx/gql/resolvers/mutation.js +23 -0
  174. package/libx/gql/resolvers/parse/ast/enrich.js +51 -0
  175. package/libx/gql/resolvers/parse/ast/fragment.js +11 -0
  176. package/libx/gql/resolvers/parse/ast/fromObject.js +39 -0
  177. package/libx/gql/resolvers/parse/ast/index.js +3 -0
  178. package/libx/gql/resolvers/parse/ast/meta.js +4 -0
  179. package/libx/gql/resolvers/parse/ast/variable.js +7 -0
  180. package/libx/gql/resolvers/parse/ast2cqn/columns.js +42 -0
  181. package/libx/gql/resolvers/parse/ast2cqn/entries.js +31 -0
  182. package/libx/gql/resolvers/parse/ast2cqn/index.js +8 -0
  183. package/libx/gql/resolvers/parse/ast2cqn/limit.js +6 -0
  184. package/libx/gql/resolvers/parse/ast2cqn/orderBy.js +24 -0
  185. package/libx/gql/resolvers/parse/ast2cqn/utils/index.js +3 -0
  186. package/libx/gql/resolvers/parse/ast2cqn/where.js +70 -0
  187. package/libx/gql/resolvers/parse/utils/index.js +8 -0
  188. package/libx/gql/resolvers/query.js +13 -0
  189. package/libx/gql/resolvers/root.js +34 -0
  190. package/libx/gql/schema/generate.js +18 -0
  191. package/libx/gql/schema/index.js +5 -0
  192. package/libx/gql/schema/mutation.js +76 -0
  193. package/libx/gql/schema/query.js +108 -0
  194. package/libx/gql/schema/typeDefMap.js +45 -0
  195. package/libx/gql/schema/utils/index.js +54 -0
  196. package/libx/gql/utils/index.js +12 -0
  197. package/libx/{_runtime/odata/cqn2odata.js → odata/cqn2odata/index.js} +39 -100
  198. package/libx/odata/index.js +80 -0
  199. package/libx/odata/odata2cqn/afterburner.js +170 -0
  200. package/libx/{_runtime/odata/odata2cqn.pegjs → odata/odata2cqn/grammar.pegjs} +102 -123
  201. package/libx/odata/odata2cqn/index.js +3 -0
  202. package/libx/odata/odata2cqn/parser.js +1 -0
  203. package/libx/odata/utils/index.js +64 -0
  204. package/libx/rest/RestAdapter.js +101 -0
  205. package/libx/rest/RestRequest.js +30 -0
  206. package/libx/rest/index.js +3 -0
  207. package/libx/rest/middleware/auth.js +22 -0
  208. package/libx/rest/middleware/content.js +15 -0
  209. package/libx/rest/middleware/create.js +40 -0
  210. package/libx/rest/middleware/delete.js +20 -0
  211. package/libx/rest/middleware/error.js +56 -0
  212. package/libx/rest/middleware/operation.js +39 -0
  213. package/libx/rest/middleware/parse.js +90 -0
  214. package/libx/rest/middleware/read.js +29 -0
  215. package/libx/rest/middleware/update.js +42 -0
  216. package/libx/rest/utils/data.js +65 -0
  217. package/package.json +4 -1
  218. package/server.js +42 -29
  219. package/lib/req/cls.js +0 -39
  220. package/libx/_runtime/cds-services/services/utils/diff.js +0 -53
  221. package/libx/_runtime/cds-services/util/auditlog.js +0 -247
  222. package/libx/_runtime/cds-services/util/xsenv.js +0 -51
  223. package/libx/_runtime/common/utils/backlinks.js +0 -83
  224. package/libx/_runtime/common/utils/rewriteAsterisk.js +0 -72
  225. package/libx/_runtime/odata/index.js +0 -55
  226. package/libx/_runtime/odata/odata2cqn.js +0 -1
  227. package/libx/_runtime/odata/readToCqn.js +0 -129
  228. package/libx/_runtime/remote/cqn2odata/index.js +0 -2
@@ -1,41 +1,21 @@
1
1
  const cds = require('../../cds')
2
2
  const { SELECT, INSERT, DELETE, UPDATE } = cds.ql
3
3
 
4
- const { getOnCond } = require('./generateOnCond')
5
4
  const { resolveView } = require('./resolveView')
6
5
  const { ensureNoDraftsSuffix } = require('./draft')
7
6
  const { flattenStructuredSelect } = require('./structured')
8
- const { foreignKeyPropagations } = require('./foreignKeyPropagations')
9
7
  const search2cqn4sql = require('./search2cqn4sql')
10
8
  const { getEntityNameFromCQN } = require('./entityFromCqn')
11
9
  const getError = require('../../common/error')
10
+ const { rewriteAsterisks } = require('./rewriteAsterisks')
12
11
  const { getPathFromRef, getEntityFromPath } = require('../../common/utils/path')
12
+ const { removeIsActiveEntityRecursively } = require('../../fiori/utils/where')
13
13
 
14
14
  const PARENT_ALIAS = '_parent_'
15
15
  const PARENT_ALIAS_REGEX = new RegExp('^' + PARENT_ALIAS + '\\d*$')
16
16
  const FOREIGN_ALIAS = '_foreign_'
17
17
  const OPERATIONS = ['=', '>', '<', '!=', '<>', '>=', '<=', 'like', 'between', 'in', 'not in']
18
18
 
19
- const _addOnCondToWhere = (cqn, entity, navigation, tableAlias, identifier, csn, prefix) => {
20
- const onConditionOptions = {
21
- associationNames: [...prefix, navigation],
22
- csn: csn,
23
- aliases: {
24
- select: tableAlias,
25
- join: identifier
26
- }
27
- }
28
-
29
- let element = csn.definitions[entity]
30
- for (let i = 0; i < onConditionOptions.associationNames.length; i++) {
31
- element = element.elements[onConditionOptions.associationNames[i]]
32
- }
33
-
34
- const onCond = getOnCond(element, onConditionOptions)
35
-
36
- cqn.where(onCond)
37
- }
38
-
39
19
  // special case in lambda functions
40
20
  const _addParentAlias = (where, alias) => {
41
21
  where.forEach(e => {
@@ -136,14 +116,9 @@ const convertPathExpressionToWhere = (fromClause, model, options) => {
136
116
  }
137
117
 
138
118
  if (previousSelect) {
139
- _addOnCondToWhere(
140
- previousSelect,
141
- previousEntityName,
142
- _getTargetFromRef(fromClause.ref[i]),
143
- tableAlias,
144
- previousTableAlias,
145
- model,
146
- prefix
119
+ const navigation = _getTargetFromRef(fromClause.ref[i])
120
+ previousSelect.where(
121
+ model.definitions[previousEntityName]._relations[[...prefix, navigation]].join(tableAlias, previousTableAlias)
147
122
  )
148
123
  _convertSelect(previousSelect, model, options)
149
124
  currentSelect.where('exists', previousSelect)
@@ -248,6 +223,7 @@ const _createWindowCQN = (SELECT, model) => {
248
223
  from: SELECT.from
249
224
  }
250
225
  }
226
+
251
227
  delete SELECT.groupBy
252
228
  }
253
229
 
@@ -260,6 +236,7 @@ const _isAll = element => {
260
236
  return last && last.id && last.where && last.where[0] === 'not' && last.where[1].xpr
261
237
  }
262
238
 
239
+ // eslint-disable-next-line complexity
263
240
  const _getLambdaSubSelect = (cqn, where, index, lambdaOp, model, options) => {
264
241
  const _unshiftRefsWithNavigation = nav => el => {
265
242
  if (el.ref) return { ref: [...nav, ...el.ref] }
@@ -268,12 +245,14 @@ const _getLambdaSubSelect = (cqn, where, index, lambdaOp, model, options) => {
268
245
  }
269
246
 
270
247
  if (!options.lambdaIteration) options.lambdaIteration = 1
271
-
272
- const outerAlias = cqn.SELECT.from.as || PARENT_ALIAS + options.lambdaIteration
248
+ const outerAlias =
249
+ (cqn.SELECT.from.ref && cqn.SELECT.from.as) ||
250
+ (cqn.SELECT.from.args && cqn.SELECT.from.args[0].ref && cqn.SELECT.from.args[0].as) ||
251
+ PARENT_ALIAS + options.lambdaIteration
273
252
  const innerAlias = FOREIGN_ALIAS + options.lambdaIteration
274
253
  cqn.SELECT.from.as = outerAlias
275
-
276
- const queryTarget = getEntityFromPath(getPathFromRef(cqn.SELECT.from.ref), model)
254
+ const ref = cqn.SELECT.from.ref || (cqn.SELECT.from.args && cqn.SELECT.from.args[0].ref)
255
+ const queryTarget = getEntityFromPath(getPathFromRef(ref), model)
277
256
 
278
257
  const nav = where[index].ref.map(el => (el.id ? el.id : el))
279
258
  const last = where[index].ref.slice(-1)[0]
@@ -296,15 +275,6 @@ const _getLambdaSubSelect = (cqn, where, index, lambdaOp, model, options) => {
296
275
  ? last.where.map(_unshiftRefsWithNavigation(nav.slice(1)))
297
276
  : last.where
298
277
  : undefined
299
- const onConditionOptions = {
300
- associationNames: navName,
301
- csn: model,
302
- aliases: {
303
- select: innerAlias,
304
- join: outerAlias
305
- }
306
- }
307
- const onCondition = getOnCond(navElement, onConditionOptions)
308
278
 
309
279
  const subSelect = SELECT.from({ ref: [navElement.target], as: innerAlias })
310
280
  if (condition) {
@@ -322,7 +292,7 @@ const _getLambdaSubSelect = (cqn, where, index, lambdaOp, model, options) => {
322
292
  }
323
293
  subSelect.where(condition)
324
294
  }
325
- subSelect.where(onCondition)
295
+ subSelect.where(queryTarget._relations[navName].join(innerAlias, outerAlias))
326
296
  if (cds.env.effective.odata.structs) {
327
297
  flattenStructuredSelect(subSelect, model)
328
298
  }
@@ -412,6 +382,7 @@ const _convertNotEqual = container => {
412
382
  })
413
383
  }
414
384
  }
385
+
415
386
  const _ifOrderByOrWhereSkip = (queryTarget, ref, model) => {
416
387
  if (!queryTarget.elements[ref[0]]) return
417
388
  return ref.some(el => {
@@ -474,6 +445,7 @@ const _convertExpand = expand => {
474
445
  }
475
446
  })
476
447
  }
448
+
477
449
  const _convertRefWhereInExpand = columns => {
478
450
  if (columns) {
479
451
  columns.forEach(col => {
@@ -484,83 +456,92 @@ const _convertRefWhereInExpand = columns => {
484
456
  }
485
457
  }
486
458
 
487
- const _defaultColumns4 = entity => {
488
- if (entity && entity.elements) {
489
- const addAliasToColumns = []
490
- for (const column in entity.elements) {
491
- if (column === 'DraftAdministrativeData_DraftUUID') continue
492
- const e = entity.elements[column]
493
- if (e.isAssociation || e.virtual) continue
494
- else if (entity._isDraftEnabled && column === 'IsActiveEntity') {
495
- addAliasToColumns.push({ val: true, as: 'IsActiveEntity', cast: { type: 'cds.Boolean' } })
496
- } else if (entity._isDraftEnabled && column === 'HasActiveEntity') {
497
- addAliasToColumns.push({ val: false, as: 'HasActiveEntity', cast: { type: 'cds.Boolean' } })
498
- } else if (entity._isDraftEnabled && column === 'HasDraftEntity') {
499
- const draftName = `${entity.name}_drafts`
500
- const subSelect = SELECT.from(draftName).columns([1])
501
- for (const k in entity.keys) {
502
- if (k !== 'IsActiveEntity') subSelect.where([{ ref: [entity.name, k] }, '=', { ref: [draftName, k] }])
503
- }
504
- addAliasToColumns.push({
505
- xpr: ['case', 'when', 'exists', subSelect, 'then', 'true', 'else', 'false', 'end'],
506
- as: 'HasDraftEntity',
507
- cast: { type: 'cds.Boolean' }
508
- })
509
- } else addAliasToColumns.push({ ref: [column], as: column })
510
- }
511
- return addAliasToColumns
459
+ const _flattenCQN = cqn => {
460
+ if (Array.isArray(cqn)) cqn.forEach(_flattenCQN)
461
+ else if (cqn) {
462
+ if (cqn.SELECT) _flattenCQN(cqn.SELECT)
463
+ if (cqn.from) _flattenCQN(cqn.from)
464
+ if (cqn.ref) _flattenCQN(cqn.ref)
465
+ if (cqn.SET) _flattenCQN(cqn.SET)
466
+ if (cqn.args) _flattenCQN(cqn.args)
467
+ if (cqn.columns) _flattenCQN(cqn.columns)
468
+ if (cqn.expand) _flattenCQN(cqn.expand)
469
+ if (cqn.where) _flattenXpr(cqn.where)
470
+ if (cqn.having) _flattenXpr(cqn.having)
512
471
  }
513
472
  }
514
473
 
515
- // eslint-disable-next-line complexity
516
- const _convertSelect = (cqn, model, options) => {
517
- // REVISIT: still needed?
518
- // Moved from old SELECT.from where this done much too eager, always for all queries
519
- if ((!cqn.SELECT.columns || (cqn.SELECT.columns && !cqn.SELECT.columns.length)) && cqn._target) {
520
- cqn.SELECT.columns = _defaultColumns4(cqn._target)
474
+ const _flattenXpr = cqn => {
475
+ if (!Array.isArray(cqn)) {
476
+ if (cqn.xpr) cqn = cqn.xpr
477
+ return
478
+ }
479
+
480
+ let idx = cqn.findIndex(el => el.xpr)
481
+ while (idx > -1) {
482
+ cqn.splice(idx, 1, '(', ...cqn[idx].xpr, ')')
483
+ idx = cqn.findIndex(el => el.xpr)
521
484
  }
522
485
 
486
+ cqn.forEach(_flattenCQN)
487
+ }
488
+
489
+ // eslint-disable-next-line complexity
490
+ const _convertSelect = (query, model, options) => {
491
+ const isDB = options.service instanceof cds.DatabaseService
492
+ const isDraft = options.draft
493
+ // REVISIT: a temporary workaround for xpr from new parser
494
+ if (cds.env.features.odata_new_parser) _flattenCQN(query)
495
+
523
496
  // lambda functions
524
- _convertLambda(cqn, model, options)
497
+ _convertLambda(query, model, options)
525
498
 
526
499
  // add 'or is null' in case of '!='
527
- if (cqn.SELECT._4odata) _convertNotEqual(cqn.SELECT)
528
-
529
- // extract where clause if it is in column expand ref
530
- _convertRefWhereInExpand(cqn.SELECT.columns)
500
+ if (query.SELECT._4odata) _convertNotEqual(query.SELECT)
531
501
 
532
- let searchOptions = cqn._searchOptions
533
- const cqnSelectFromRef = cqn.SELECT.from.ref
502
+ let searchOptions = query._searchOptions
503
+ const cqnSelectFromRef = query.SELECT.from.ref
534
504
 
535
505
  // no path expression
536
506
  if (!cqnSelectFromRef || (cqnSelectFromRef.length === 1 && !cqnSelectFromRef[0].where)) {
507
+ rewriteAsterisks(query, cqnSelectFromRef && model.definitions[cqnSelectFromRef[0]], isDB, isDraft)
508
+ // extract where clause if it is in column expand ref
509
+ _convertRefWhereInExpand(query.SELECT.columns)
537
510
  // remove virtual and with skip annotated fields in orderby and where
538
- _convertOrderByOrWhereIfSkip(cqn, getEntityNameFromCQN(cqn), model)
511
+ _convertOrderByOrWhereIfSkip(query, getEntityNameFromCQN(query), model)
539
512
 
540
- if (cqnSelectFromRef && cqn.SELECT.search && !options.suppressSearch) {
513
+ if (cqnSelectFromRef && query.SELECT.search && !options.suppressSearch) {
541
514
  searchOptions = { ...searchOptions, ...{ targetName: cqnSelectFromRef[0] } }
542
- search2cqn4sql(cqn, model, searchOptions)
515
+ search2cqn4sql(query, model, searchOptions)
543
516
  }
544
517
 
545
- if (cqn.SELECT.columns && cds.env.effective.odata.structs) {
546
- flattenStructuredSelect(cqn, model)
518
+ if (query.SELECT.columns && cds.env.effective.odata.structs) {
519
+ flattenStructuredSelect(query, model)
547
520
  }
548
521
 
549
522
  // topcount with groupby
550
- if (cqn.SELECT.columns && _isBottomTop(cqn.SELECT.columns)) {
551
- _createWindowCQN(cqn.SELECT, model)
523
+ if (query.SELECT.columns && _isBottomTop(query.SELECT.columns)) {
524
+ _createWindowCQN(query.SELECT, model)
552
525
  }
553
526
 
554
- return cqn
527
+ return query
555
528
  }
556
529
 
557
530
  // path expression handling
558
- const { target, alias, where, cardinality, columns } = convertPathExpressionToWhere(cqn.SELECT.from, model, options)
531
+ const { target, alias, where, cardinality, columns } = convertPathExpressionToWhere(query.SELECT.from, model, options)
532
+ rewriteAsterisks(query, model.definitions[target], isDB, isDraft, !!columns)
533
+ if (columns) {
534
+ if (query._streaming) query.SELECT.columns = columns
535
+ else query.SELECT.columns.push(...columns)
536
+ }
537
+
538
+ // extract where clause if it is in column expand ref
539
+ _convertRefWhereInExpand(query.SELECT.columns)
559
540
 
560
541
  // remove virtual and with skip annotated fields in orderby and where
561
- _convertOrderByOrWhereIfSkip(cqn, target, model)
542
+ _convertOrderByOrWhereIfSkip(query, target, model)
562
543
 
563
- const select = SELECT.from(target, columns || cqn.SELECT.columns)
544
+ const select = SELECT.from(target, query.SELECT.columns)
564
545
 
565
546
  if (alias) {
566
547
  select.SELECT.from.as = alias
@@ -568,25 +549,29 @@ const _convertSelect = (cqn, model, options) => {
568
549
 
569
550
  // TODO: REVISIT: We need to add alias to subselect in .where, .columns, .from, ... etc
570
551
  if (where) {
571
- select.where(where)
552
+ if (!isDraft) {
553
+ select.where(removeIsActiveEntityRecursively(where))
554
+ } else {
555
+ select.where(where)
556
+ }
572
557
  }
573
558
 
574
559
  if (cardinality && cardinality.max === 1) {
575
- cqn.SELECT.one = true
560
+ query.SELECT.one = true
576
561
  }
577
562
 
578
- if (cqn.SELECT.search) {
563
+ if (query.SELECT.search) {
579
564
  searchOptions = { ...searchOptions, ...{ targetName: target } }
580
- search2cqn4sql(cqn, model, searchOptions)
565
+ search2cqn4sql(query, model, searchOptions)
581
566
  }
582
567
 
583
- if (cqn.SELECT.where) {
584
- select.where(_addAliasToExpression(cqn.SELECT.where, select.SELECT.from.as))
568
+ if (query.SELECT.where) {
569
+ select.where(_addAliasToExpression(query.SELECT.where, select.SELECT.from.as))
585
570
  }
586
571
 
587
572
  // We add all previous properties ot the newly created query.
588
573
  // Reason is to not lose the query API functionality
589
- Object.assign(select.SELECT, cqn.SELECT, {
574
+ Object.assign(select.SELECT, query.SELECT, {
590
575
  columns: select.SELECT.columns,
591
576
  from: select.SELECT.from,
592
577
  where: select.SELECT.where
@@ -599,9 +584,9 @@ const _convertSelect = (cqn, model, options) => {
599
584
  return select
600
585
  }
601
586
 
602
- const _convertInsert = (cqn, model) => {
587
+ const _convertInsert = (query, model, options) => {
603
588
  // resolve path expression
604
- const resolvedIntoClause = _convertPathExpressionForInsertOrDelete(cqn.INSERT.into, model)
589
+ const resolvedIntoClause = _convertPathExpressionForInsertOrDelete(query.INSERT.into, model)
605
590
 
606
591
  // overwrite only .into, foreign keys are already set
607
592
  const insert = INSERT.into(resolvedIntoClause)
@@ -610,7 +595,7 @@ const _convertInsert = (cqn, model) => {
610
595
 
611
596
  // We add all previous properties ot the newly created query.
612
597
  // Reason is to not lose the query API functionality
613
- Object.assign(insert.INSERT, cqn.INSERT, { into: resolvedIntoClause })
598
+ Object.assign(insert.INSERT, query.INSERT, { into: resolvedIntoClause })
614
599
 
615
600
  const targetName = insert.INSERT.into.name || insert.INSERT.into
616
601
 
@@ -619,10 +604,10 @@ const _convertInsert = (cqn, model) => {
619
604
 
620
605
  const resolvedView = resolveView(insert, model, cds.db)
621
606
 
622
- if (cqn.INSERT.into.ref && cqn.INSERT.into.ref.length > 1) {
623
- const copyFrom = [...cqn.INSERT.into.ref]
607
+ if (query.INSERT.into.ref && query.INSERT.into.ref.length > 1) {
608
+ const copyFrom = [...query.INSERT.into.ref]
624
609
  copyFrom.pop()
625
- resolvedView._validationQuery = _convertSelect(SELECT.from({ ref: copyFrom }).columns([1]), model)
610
+ resolvedView._validationQuery = _convertSelect(SELECT.from({ ref: copyFrom }).columns([1]), model, options)
626
611
  }
627
612
 
628
613
  return resolvedView
@@ -634,11 +619,11 @@ function _modifyNavigationInWhere(whereClause, target) {
634
619
  if (e.ref && e.ref.length > 1 && target.elements[e.ref[0]]) {
635
620
  const element = target.elements[e.ref[0]]
636
621
  if (!element.isAssociation) return
637
- const foreignKeys = foreignKeyPropagations(element)
622
+ const foreignKeys = element._foreignKeys
638
623
  const joined = e.ref.join('_')
639
624
 
640
- for (const { parentFieldName } of foreignKeys) {
641
- if (parentFieldName === joined) {
625
+ for (const { parentElement } of foreignKeys) {
626
+ if (parentElement && parentElement.name === joined) {
642
627
  e.ref = [joined]
643
628
  }
644
629
  }
@@ -654,23 +639,23 @@ const _plainDelete = (cqn, model) => {
654
639
  return resolveView(cqn, model, cds.db)
655
640
  }
656
641
 
657
- const _convertDelete = (cqn, model, options) => {
642
+ const _convertDelete = (query, model, options) => {
658
643
  // .from is plain string or csn entity
659
644
  if (
660
- typeof cqn.DELETE.from === 'string' ||
661
- cqn.DELETE.from.name ||
662
- (cqn.DELETE.from.ref && typeof cqn.DELETE.from.ref[0] === 'string')
645
+ typeof query.DELETE.from === 'string' ||
646
+ query.DELETE.from.name ||
647
+ (query.DELETE.from.ref && typeof query.DELETE.from.ref[0] === 'string')
663
648
  ) {
664
- return _plainDelete(cqn, model)
649
+ return _plainDelete(query, model)
665
650
  }
666
651
 
667
- const { target, alias, where } = convertPathExpressionToWhere(cqn.DELETE.from, model, options)
652
+ const { target, alias, where } = convertPathExpressionToWhere(query.DELETE.from, model, options)
668
653
  const deleet = DELETE('x')
669
- Object.assign(deleet.DELETE, cqn.DELETE, { from: target, where: undefined })
654
+ Object.assign(deleet.DELETE, query.DELETE, { from: target, where: undefined })
670
655
 
671
656
  if (alias) deleet.DELETE.from = { ref: [target], as: alias }
672
657
  if (where) deleet.where(where)
673
- if (cqn.DELETE.where) deleet.where(_addAliasToExpression(cqn.DELETE.where, alias))
658
+ if (query.DELETE.where) deleet.where(_addAliasToExpression(query.DELETE.where, alias))
674
659
 
675
660
  const targetEntity = model.definitions[target]
676
661
  if (!targetEntity) return deleet
@@ -686,28 +671,28 @@ function _plainUpdate(cqn, model) {
686
671
  return resolveView(cqn, model, cds.db)
687
672
  }
688
673
 
689
- const _convertUpdate = (cqn, model, options) => {
674
+ const _convertUpdate = (query, model, options) => {
690
675
  // REVISIT flatten structured types, currently its done in SQL builder
691
676
 
692
677
  // .into is plain string or csn entity
693
678
  if (
694
- typeof cqn.UPDATE.entity === 'string' ||
695
- cqn.UPDATE.entity.name ||
696
- (cqn.UPDATE.entity.ref && typeof cqn.UPDATE.entity.ref[0] === 'string' && cqn.UPDATE.entity.ref.length === 1)
679
+ typeof query.UPDATE.entity === 'string' ||
680
+ query.UPDATE.entity.name ||
681
+ (query.UPDATE.entity.ref && typeof query.UPDATE.entity.ref[0] === 'string' && query.UPDATE.entity.ref.length === 1)
697
682
  ) {
698
- return _plainUpdate(cqn, model)
683
+ return _plainUpdate(query, model)
699
684
  }
700
685
 
701
- const { target, alias, where } = convertPathExpressionToWhere(cqn.UPDATE.entity, model, options)
686
+ const { target, alias, where } = convertPathExpressionToWhere(query.UPDATE.entity, model, options)
702
687
 
703
688
  // link .with and .data and set query target and remove current where clause
704
689
  // REVISIT: update statement does not accept cqn partial as input
705
690
  const update = UPDATE('x')
706
- Object.assign(update.UPDATE, cqn.UPDATE, { entity: target, where: undefined })
691
+ Object.assign(update.UPDATE, query.UPDATE, { entity: target, where: undefined })
707
692
 
708
693
  if (alias) update.UPDATE.entity = { ref: [target], as: alias }
709
694
  if (where) update.where(where)
710
- if (cqn.UPDATE.where) update.where(_addAliasToExpression(cqn.UPDATE.where, alias))
695
+ if (query.UPDATE.where) update.where(_addAliasToExpression(query.UPDATE.where, alias))
711
696
 
712
697
  const targetEntity = model.definitions[target]
713
698
  if (!targetEntity) return update
@@ -721,30 +706,31 @@ const _convertUpdate = (cqn, model, options) => {
721
706
  * REVISIT structured
722
707
  * REVISIT topcount when the additional layer for Analytics before SQLBuilder is ready
723
708
  *
724
- * @param {object} cqn - incoming query
709
+ * @param {object} query - incoming query
725
710
  * @param {object} model - csn model
726
711
  * @param {import('../../types/api').cqn2cqn4sqlOptions} [options] Additional options.
727
712
  */
728
- const cqn2cqn4sql = (cqn, model, options = { suppressSearch: false }) => {
729
- // REVISIT: query instead of cqn (the INSERT/SELECT/UPDATE/DELETE object is the actual cqn)
730
-
731
- if (cqn.SELECT) {
732
- return _convertSelect(cqn, model, options)
713
+ const cqn2cqn4sql = (query, model, options = { suppressSearch: false }) => {
714
+ if (query.SELECT) {
715
+ return _convertSelect(query, model, options)
733
716
  }
734
717
 
735
- if (cqn.UPDATE) {
736
- return _convertUpdate(cqn, model, options)
718
+ if (query.UPDATE) {
719
+ return _convertUpdate(query, model, options)
737
720
  }
738
721
 
739
- if (cqn.INSERT) {
740
- return _convertInsert(cqn, model, options)
722
+ if (query.INSERT) {
723
+ return _convertInsert(query, model, options)
741
724
  }
742
725
 
743
- if (cqn.DELETE) {
744
- return _convertDelete(cqn, model, options)
726
+ if (query.DELETE) {
727
+ return _convertDelete(query, model, options)
745
728
  }
746
729
 
747
- return cqn
730
+ return query
748
731
  }
749
732
 
750
- module.exports = cqn2cqn4sql
733
+ module.exports = {
734
+ cqn2cqn4sql,
735
+ convertPathExpressionToWhere
736
+ }
@@ -1,29 +1,11 @@
1
1
  const cds = require('../../cds')
2
2
 
3
3
  const { ensureNoDraftsSuffix } = require('./draft')
4
- const { getFlatArray } = require('../../db/utils/deep')
5
4
 
6
5
  const getEtagElement = entity => {
7
6
  return Object.values(entity.elements).find(element => element['@odata.etag'])
8
7
  }
9
8
 
10
- const _isBacklink = (assoc, parent, target) => {
11
- const comps = Object.values(target.associations || {})
12
- .filter(assoc => assoc._isCompositionEffective)
13
- .filter(assoc => assoc.target === parent.name)
14
- if (comps.length === 0) return false
15
-
16
- let backlink = false
17
- for (const comp of comps) {
18
- const on = comp.on.find(ele => typeof ele === 'object' && ele.ref[0] !== '$self')
19
- if (on.ref.length === 2 && on.ref[on.ref.length - 1] === assoc.name) {
20
- backlink = true
21
- break
22
- }
23
- }
24
- return backlink
25
- }
26
-
27
9
  const _isDependent = (assoc, parent, target) => {
28
10
  return (
29
11
  assoc._isAssociationStrict &&
@@ -33,7 +15,7 @@ const _isDependent = (assoc, parent, target) => {
33
15
  assoc['@assert.integrity'] !== false &&
34
16
  parent['@assert.integrity'] !== false &&
35
17
  (!parent._service || parent._service['@assert.integrity'] !== false) &&
36
- !_isBacklink(assoc, parent, target)
18
+ !assoc._isCompositionBacklink
37
19
  )
38
20
  }
39
21
 
@@ -65,18 +47,14 @@ const getDependents = (entity, model) => {
65
47
  return entity.set('__dependents', dependents)
66
48
  }
67
49
 
68
- const _getUps = (entity, model, previous = []) => {
50
+ const _getUps = (entity, model) => {
69
51
  const ups = []
70
52
  for (const def of Object.values(model.definitions)) {
71
53
  if (def.kind !== 'entity') continue
72
54
  if (!def.associations) continue
73
- for (const assoc of Object.values(def.associations)) {
74
- if (assoc.target !== entity.name) continue
75
- ups.push({
76
- entity: assoc.parent,
77
- assoc: assoc,
78
- previous
79
- })
55
+ for (const element of Object.values(def.associations)) {
56
+ if (element.target !== entity.name || element._isBacklink) continue
57
+ ups.push(element)
80
58
  }
81
59
  }
82
60
  return ups
@@ -86,31 +64,35 @@ const _ifDataSubject = (entity, role) => {
86
64
  return entity['@PersonalData.EntitySemantics'] === 'DataSubject' && entity['@PersonalData.DataSubjectRole'] === role
87
65
  }
88
66
 
89
- const getDataSubject = (entity, model, role) => {
90
- const hash = '__dataSubject4' + role
91
- if (entity.own(hash)) return entity[hash]
92
-
93
- if (_ifDataSubject(entity, role)) return entity.set(hash, { entity: entity, assoc: [], previous: [] })
94
-
95
- let dataSubject
96
- let ups = _getUps(entity, model)
97
- while (!dataSubject) {
98
- for (const { entity, assoc, previous } of ups) {
99
- if (_ifDataSubject(entity, role)) dataSubject = { entity, assoc, previous }
67
+ const _getDataSubjectUp = (role, model, element, first = element) => {
68
+ const upElements = _getUps(element.parent, model)
69
+ for (const element of upElements) {
70
+ if (_ifDataSubject(element.parent, role)) {
71
+ return { element: first, up: { element }, entity: element.parent }
72
+ }
73
+ // dfs is a must here
74
+ const dataSubject = _getDataSubjectUp(role, model, element, first)
75
+ if (dataSubject) {
76
+ dataSubject.up = { element, up: dataSubject.up }
77
+ return dataSubject
100
78
  }
101
- if (dataSubject) break
102
- ups = getFlatArray([
103
- ...ups.map(up => [..._getUps(up.entity, model, up.previous.concat({ entity: up.entity, assoc: up.assoc }))])
104
- ])
105
79
  }
80
+ }
106
81
 
107
- return entity.set(hash, dataSubject)
82
+ const getDataSubject = (entity, model, role, element) => {
83
+ const hash = '__dataSubject4' + role
84
+ if (entity.own(hash)) return entity[hash]
85
+ if (_ifDataSubject(element.parent, role)) return entity.set(hash, { element, entity: element.parent })
86
+ return entity.set(hash, _getDataSubjectUp(role, model, element))
108
87
  }
109
88
 
110
- const _findRootEntity = (entities, edmName) => {
89
+ const _resolve = (name, model, namespace) =>
90
+ model.entities(namespace)[name] || model.definitions[`${namespace}.${name}`]
91
+
92
+ const _findRootEntity = (model, edmName, namespace) => {
111
93
  const parts = edmName.split('_')
112
94
  let csnName = parts.shift()
113
- let target = entities[csnName]
95
+ let target = _resolve(csnName, model, namespace)
114
96
  const len = parts.length
115
97
  // try to find a correct entity "greedy" and count leftovers (x4 case below)
116
98
  // e.g. we have 2 entities: 'C_root_' and dependant 'C_root_.kid_'
@@ -124,10 +106,10 @@ const _findRootEntity = (entities, edmName) => {
124
106
  * if target in entities connect with .
125
107
  * if target not in entities connect with _
126
108
  */
127
- csnName = `${csnName}${entities[csnName] ? '.' : '_'}${parts[i]}`
109
+ csnName = `${csnName}${_resolve(csnName, model, namespace) ? '.' : '_'}${parts[i]}`
128
110
  ++acc
129
- if (entities[csnName]) {
130
- target = entities[csnName]
111
+ if (_resolve(csnName, model, namespace)) {
112
+ target = _resolve(csnName, model, namespace)
131
113
  left -= acc
132
114
  acc = 0
133
115
  }
@@ -145,12 +127,13 @@ const findCsnTargetFor = (edmName, model, namespace) => {
145
127
 
146
128
  if (mapping[edmName]) return mapping[edmName]
147
129
 
148
- const entities = model.entities(namespace)
149
130
  // simple cases
150
- let target = entities[edmName] || entities[edmName.replace(/_/g, '.')]
131
+ let target = _resolve(edmName, model, namespace) || _resolve(edmName.replace(/_/g, '.'), model, namespace)
132
+
133
+ // hard cases
151
134
  if (!target) {
152
135
  // probably, a combination of '_' and '.', resolving
153
- const finding = _findRootEntity(entities, edmName)
136
+ const finding = _findRootEntity(model, edmName, namespace)
154
137
  target = finding.target
155
138
  // something left in navigation path => x4 navigation
156
139
  // resolving within found entity
@@ -203,11 +186,26 @@ const isRootEntity = (definitions, entityName) => {
203
186
  return true
204
187
  }
205
188
 
189
+ function alias2ref(service, edm) {
190
+ const defs = edm[service.definition.name]
191
+ for (const each of Object.values(service.entities)) {
192
+ const def = defs[each.name.replace(service.definition.name + '.', '').replace(/\./g, '_')]
193
+ if (!def || !def.$Key || def.$Key.every(ele => typeof ele === 'string')) continue
194
+ each._alias2ref = {}
195
+ for (const mapping of def.$Key.filter(ele => typeof ele !== 'string')) {
196
+ for (const [key, value] of Object.entries(mapping)) {
197
+ each._alias2ref[key] = value.split('/')
198
+ }
199
+ }
200
+ }
201
+ }
202
+
206
203
  module.exports = {
207
204
  getEtagElement,
208
205
  findCsnTargetFor,
209
206
  getElementDeep,
210
207
  getDependents,
211
208
  isRootEntity,
212
- getDataSubject
209
+ getDataSubject,
210
+ alias2ref
213
211
  }