@sap/cds 5.4.3 → 5.5.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 +239 -2
  2. package/apis/ql.d.ts +17 -15
  3. package/app/index.js +1 -1
  4. package/bin/build/buildTaskEngine.js +26 -42
  5. package/bin/build/buildTaskFactory.js +6 -10
  6. package/bin/build/buildTaskHandler.js +2 -4
  7. package/bin/build/buildTaskProvider.js +3 -1
  8. package/bin/build/buildTaskProviderFactory.js +9 -15
  9. package/bin/build/constants.js +15 -3
  10. package/bin/build/index.js +5 -4
  11. package/bin/build/mtaUtil.js +8 -11
  12. package/bin/build/provider/buildTaskHandlerEdmx.js +63 -6
  13. package/bin/build/provider/buildTaskHandlerInternal.js +2 -34
  14. package/bin/build/provider/buildTaskProviderInternal.js +16 -42
  15. package/bin/build/provider/fiori/index.js +13 -24
  16. package/bin/build/provider/hana/2migration.js +17 -15
  17. package/bin/build/provider/hana/2tabledata.js +52 -48
  18. package/bin/build/provider/hana/index.js +27 -25
  19. package/bin/build/provider/hana/migrationtable.js +91 -67
  20. package/bin/build/provider/java-cf/index.js +14 -24
  21. package/bin/build/provider/mtx/index.js +12 -14
  22. package/bin/build/provider/node-cf/index.js +18 -32
  23. package/bin/cds.js +5 -5
  24. package/bin/serve.js +29 -23
  25. package/bin/version.js +0 -1
  26. package/lib/compile/etc/_localized.js +4 -9
  27. package/lib/compile/for/sql.js +5 -2
  28. package/lib/compile/parse.js +25 -17
  29. package/lib/compile/to/srvinfo.js +2 -1
  30. package/lib/connect/bindings.js +2 -1
  31. package/lib/connect/index.js +48 -49
  32. package/lib/core/classes.js +1 -1
  33. package/lib/core/reflect.js +10 -2
  34. package/lib/deploy.js +26 -23
  35. package/lib/env/defaults.js +13 -6
  36. package/lib/env/index.js +73 -78
  37. package/lib/env/requires.js +38 -19
  38. package/lib/index.js +9 -10
  39. package/lib/lazy.js +2 -2
  40. package/lib/log/index.js +33 -45
  41. package/lib/log/service/index.js +2 -2
  42. package/lib/ql/CREATE.js +14 -9
  43. package/lib/ql/DELETE.js +6 -5
  44. package/lib/ql/DROP.js +12 -9
  45. package/lib/ql/INSERT.js +40 -16
  46. package/lib/ql/Query.js +67 -40
  47. package/lib/ql/SELECT.js +162 -127
  48. package/lib/ql/UPDATE.js +74 -42
  49. package/lib/ql/Whereable.js +77 -87
  50. package/lib/ql/index.js +36 -24
  51. package/lib/ql/parse.js +35 -0
  52. package/lib/req/context.js +44 -8
  53. package/lib/req/locale.js +7 -7
  54. package/lib/serve/Service-api.js +21 -14
  55. package/lib/serve/Service-dispatch.js +28 -12
  56. package/lib/serve/Transaction.js +22 -10
  57. package/lib/serve/index.js +16 -11
  58. package/lib/utils/axios.js +23 -16
  59. package/lib/utils/data.js +35 -0
  60. package/lib/utils/tests.js +27 -18
  61. package/libx/_runtime/audit/generic/personal/access.js +81 -0
  62. package/libx/_runtime/audit/generic/personal/constants.js +4 -0
  63. package/libx/_runtime/audit/generic/personal/index.js +50 -0
  64. package/libx/_runtime/audit/generic/personal/modification.js +138 -0
  65. package/libx/_runtime/audit/generic/personal/utils.js +186 -0
  66. package/libx/_runtime/audit/utils/v2.js +10 -4
  67. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +5 -5
  68. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +6 -7
  69. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +5 -7
  70. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +5 -7
  71. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +2 -3
  72. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +4 -0
  73. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +7 -4
  74. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +59 -8
  75. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +11 -1
  76. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +6 -10
  77. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +3 -46
  78. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +2 -5
  79. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/createToCQN.js +2 -2
  80. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/deleteToCQN.js +4 -3
  81. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -2
  82. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +0 -1
  83. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +1 -1
  84. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/updateToCQN.js +2 -2
  85. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +16 -18
  86. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/EdmEntityType.js +6 -3
  87. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/format/RepresentationKind.js +4 -1
  88. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/core/OdataRequest.js +1 -0
  89. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/SerializerFactory.js +15 -2
  90. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/OperationValidator.js +1 -0
  91. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
  92. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +8 -1
  93. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +6 -1
  94. package/libx/_runtime/cds-services/adapter/odata-v4/utils/omitValues.js +12 -5
  95. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +1 -7
  96. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +7 -7
  97. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +14 -18
  98. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +13 -13
  99. package/libx/_runtime/cds-services/adapter/rest/handlers/create.js +0 -1
  100. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +2 -1
  101. package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +2 -2
  102. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +2 -4
  103. package/libx/_runtime/cds-services/adapter/rest/utils/result.js +4 -2
  104. package/libx/_runtime/cds-services/services/Service.js +40 -5
  105. package/libx/_runtime/cds-services/services/utils/columns.js +13 -7
  106. package/libx/_runtime/cds-services/services/utils/compareJson.js +88 -4
  107. package/libx/_runtime/cds-services/services/utils/differ.js +24 -6
  108. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -2
  109. package/libx/_runtime/common/composition/data.js +66 -63
  110. package/libx/_runtime/common/composition/delete.js +97 -71
  111. package/libx/_runtime/common/composition/index.js +2 -1
  112. package/libx/_runtime/common/composition/insert.js +34 -11
  113. package/libx/_runtime/common/composition/tree.js +119 -92
  114. package/libx/_runtime/common/composition/update.js +12 -1
  115. package/libx/_runtime/common/composition/utils.js +1 -3
  116. package/libx/_runtime/common/constants/draft.js +12 -1
  117. package/libx/_runtime/common/generic/auth.js +53 -31
  118. package/libx/_runtime/common/generic/crud.js +14 -13
  119. package/libx/_runtime/common/generic/input.js +23 -26
  120. package/libx/_runtime/common/generic/put.js +1 -1
  121. package/libx/_runtime/common/generic/sorting.js +16 -16
  122. package/libx/_runtime/common/i18n/index.js +1 -1
  123. package/libx/_runtime/common/i18n/messages.properties +4 -0
  124. package/libx/_runtime/common/utils/backlinks.js +12 -5
  125. package/libx/_runtime/common/utils/cqn.js +6 -1
  126. package/libx/_runtime/common/utils/cqn2cqn4sql.js +123 -108
  127. package/libx/_runtime/common/utils/csn.js +56 -4
  128. package/libx/_runtime/common/utils/data.js +0 -37
  129. package/libx/_runtime/common/utils/enrichWithKeysFromWhere.js +1 -1
  130. package/libx/_runtime/common/utils/entityFromCqn.js +7 -24
  131. package/libx/_runtime/common/utils/foreignKeyPropagations.js +39 -7
  132. package/libx/_runtime/common/utils/generateOnCond.js +11 -12
  133. package/libx/_runtime/common/utils/onlyKeysRemain.js +10 -0
  134. package/libx/_runtime/common/utils/path.js +35 -0
  135. package/libx/_runtime/common/utils/postProcessing.js +86 -0
  136. package/libx/_runtime/common/utils/quotingStyles.js +37 -26
  137. package/libx/_runtime/common/utils/resolveView.js +227 -173
  138. package/libx/_runtime/common/utils/rewriteAsterisk.js +46 -26
  139. package/libx/_runtime/common/utils/structured.js +13 -13
  140. package/libx/_runtime/common/utils/template.js +10 -5
  141. package/libx/_runtime/common/utils/templateDelimiter.js +1 -0
  142. package/libx/_runtime/common/utils/templateProcessor.js +28 -72
  143. package/libx/_runtime/common/utils/union.js +31 -0
  144. package/libx/_runtime/common/utils/unionCqnTemplate.js +184 -0
  145. package/libx/_runtime/db/Service.js +1 -1
  146. package/libx/_runtime/db/data-conversion/timestamp.js +2 -9
  147. package/libx/_runtime/db/expand/expandCQNToJoin.js +204 -297
  148. package/libx/_runtime/db/expand/index.js +3 -3
  149. package/libx/_runtime/db/expand/rawToExpanded.js +36 -7
  150. package/libx/_runtime/db/generic/index.js +1 -1
  151. package/libx/_runtime/db/generic/input.js +5 -7
  152. package/libx/_runtime/db/generic/integrity.js +1 -1
  153. package/libx/_runtime/db/generic/rewrite.js +2 -10
  154. package/libx/_runtime/db/generic/update.js +13 -5
  155. package/libx/_runtime/db/generic/virtual.js +22 -58
  156. package/libx/_runtime/db/query/delete.js +7 -4
  157. package/libx/_runtime/db/query/insert.js +6 -4
  158. package/libx/_runtime/db/query/read.js +21 -8
  159. package/libx/_runtime/db/query/run.js +4 -1
  160. package/libx/_runtime/db/query/update.js +5 -4
  161. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +35 -2
  162. package/libx/_runtime/db/sql-builder/FunctionBuilder.js +17 -2
  163. package/libx/_runtime/db/sql-builder/InsertBuilder.js +6 -5
  164. package/libx/_runtime/db/sql-builder/ReferenceBuilder.js +10 -0
  165. package/libx/_runtime/db/sql-builder/SelectBuilder.js +35 -24
  166. package/libx/_runtime/db/sql-builder/UpdateBuilder.js +14 -4
  167. package/libx/_runtime/db/sql-builder/arrayed.js +4 -0
  168. package/libx/_runtime/db/utils/deep.js +8 -0
  169. package/libx/_runtime/db/utils/generateAliases.js +2 -1
  170. package/libx/_runtime/fiori/generic/activate.js +19 -15
  171. package/libx/_runtime/fiori/generic/before.js +3 -11
  172. package/libx/_runtime/fiori/generic/cancel.js +1 -1
  173. package/libx/_runtime/fiori/generic/delete.js +3 -1
  174. package/libx/_runtime/fiori/generic/edit.js +12 -2
  175. package/libx/_runtime/fiori/generic/new.js +5 -5
  176. package/libx/_runtime/fiori/generic/patch.js +0 -18
  177. package/libx/_runtime/fiori/generic/read.js +261 -205
  178. package/libx/_runtime/fiori/utils/delete.js +36 -7
  179. package/libx/_runtime/fiori/utils/handler.js +43 -44
  180. package/libx/_runtime/fiori/utils/where.js +30 -15
  181. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +4 -6
  182. package/libx/_runtime/hana/execute.js +3 -3
  183. package/libx/_runtime/hana/localized.js +4 -4
  184. package/libx/_runtime/hana/pool.js +29 -14
  185. package/libx/_runtime/hana/search2cqn4sql.js +2 -1
  186. package/libx/_runtime/hana/searchToContains.js +18 -14
  187. package/libx/_runtime/index.js +0 -5
  188. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +13 -5
  189. package/libx/_runtime/messaging/common-utils/naming-conventions.js +4 -1
  190. package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +31 -19
  191. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +1 -2
  192. package/libx/_runtime/messaging/enterprise-messaging.js +6 -4
  193. package/libx/_runtime/messaging/service.js +7 -6
  194. package/libx/_runtime/odata/cqn2odata.js +110 -43
  195. package/libx/_runtime/odata/index.js +26 -48
  196. package/libx/_runtime/odata/odata2cqn.js +1 -6154
  197. package/libx/_runtime/odata/odata2cqn.pegjs +559 -0
  198. package/libx/_runtime/odata/readToCqn.js +94 -64
  199. package/libx/_runtime/remote/Service.js +74 -21
  200. package/libx/_runtime/remote/cqn2odata/index.js +1 -5
  201. package/libx/_runtime/remote/utils/client.js +24 -101
  202. package/libx/_runtime/remote/utils/dataConversion.js +27 -12
  203. package/libx/_runtime/sqlite/Service.js +3 -5
  204. package/libx/_runtime/sqlite/execute.js +33 -27
  205. package/libx/_runtime/sqlite/localized.js +12 -7
  206. package/libx/_runtime/types/api.js +10 -0
  207. package/package.json +2 -2
  208. package/server.js +16 -2
  209. package/lib/ql/grammar.pegjs +0 -208
  210. package/lib/ql/parser.js +0 -1
  211. package/lib/ql/rt/DELETE.js +0 -29
  212. package/lib/ql/rt/INSERT.js +0 -23
  213. package/lib/ql/rt/Query.js +0 -84
  214. package/lib/ql/rt/SELECT.js +0 -174
  215. package/lib/ql/rt/UPDATE.js +0 -119
  216. package/lib/ql/rt/_helpers.js +0 -91
  217. package/lib/ql/rt/index.js +0 -32
  218. package/libx/_runtime/audit/generic/personal.js +0 -260
  219. package/libx/_runtime/cds-services/statements/BaseStatement.js +0 -72
  220. package/libx/_runtime/cds-services/statements/Create.js +0 -57
  221. package/libx/_runtime/cds-services/statements/Delete.js +0 -33
  222. package/libx/_runtime/cds-services/statements/Drop.js +0 -42
  223. package/libx/_runtime/cds-services/statements/Insert.js +0 -201
  224. package/libx/_runtime/cds-services/statements/Select.js +0 -826
  225. package/libx/_runtime/cds-services/statements/Update.js +0 -181
  226. package/libx/_runtime/cds-services/statements/Where.js +0 -726
  227. package/libx/_runtime/cds-services/statements/index.js +0 -25
  228. package/libx/_runtime/common/generic/resolve-mock.js +0 -9
@@ -9,11 +9,11 @@ const { foreignKeyPropagations } = require('./foreignKeyPropagations')
9
9
  const search2cqn4sql = require('./search2cqn4sql')
10
10
  const { getEntityNameFromCQN } = require('./entityFromCqn')
11
11
  const getError = require('../../common/error')
12
-
13
- const SYMBOL_FROM_ANNOTATION = Symbol.for('sap.cds.FROM_ANNOTATION')
12
+ const { getPathFromRef, getEntityFromPath } = require('../../common/utils/path')
14
13
 
15
14
  const PARENT_ALIAS = '_parent_'
16
- const FOREIGN_ALIAS = 'foreign'
15
+ const PARENT_ALIAS_REGEX = new RegExp('^' + PARENT_ALIAS + '\\d*$')
16
+ const FOREIGN_ALIAS = '_foreign_'
17
17
  const OPERATIONS = ['=', '>', '<', '!=', '<>', '>=', '<=', 'like', 'between', 'in', 'not in']
18
18
 
19
19
  const _addOnCondToWhere = (cqn, entity, navigation, tableAlias, identifier, csn, prefix) => {
@@ -39,7 +39,7 @@ const _addOnCondToWhere = (cqn, entity, navigation, tableAlias, identifier, csn,
39
39
  // special case in lambda functions
40
40
  const _addParentAlias = (where, alias) => {
41
41
  where.forEach(e => {
42
- if (e.ref && e.ref[0] === PARENT_ALIAS) {
42
+ if (e.ref && e.ref[0].match(PARENT_ALIAS_REGEX)) {
43
43
  e.ref[0] = alias
44
44
  }
45
45
  })
@@ -47,9 +47,7 @@ const _addParentAlias = (where, alias) => {
47
47
 
48
48
  const _addAliasToElement = (e, alias) => {
49
49
  if (e.ref) {
50
- const aliased = { ref: [alias, ...e.ref] }
51
- if (e[SYMBOL_FROM_ANNOTATION]) aliased[SYMBOL_FROM_ANNOTATION] = true
52
- return aliased
50
+ return { ref: [alias, ...e.ref] }
53
51
  }
54
52
 
55
53
  if (e.list) {
@@ -94,10 +92,10 @@ const _getTargetFromRef = ref => {
94
92
 
95
93
  const _getEntityName = (fromClause, entity, i) => {
96
94
  const targetName = _getTargetFromRef(fromClause.ref[i])
97
- return i === 0 ? targetName : entity.elements[targetName].target
95
+ return i === 0 ? targetName : entity && entity.elements[targetName] && entity.elements[targetName].target
98
96
  }
99
97
 
100
- const convertPathExpressionToWhere = (fromClause, model) => {
98
+ const convertPathExpressionToWhere = (fromClause, model, options) => {
101
99
  if (fromClause.ref.length === 1) {
102
100
  const target = _getTargetFromRef(fromClause.ref[0])
103
101
  const alias = fromClause.as
@@ -107,6 +105,7 @@ const convertPathExpressionToWhere = (fromClause, model) => {
107
105
 
108
106
  let previousSelect, previousEntityName, previousTableAlias, structParent
109
107
  let prefix = []
108
+ let columns
110
109
  for (let i = 0; i < fromClause.ref.length; i++) {
111
110
  const entity = structParent || model.definitions[previousEntityName]
112
111
  const element = _elementFromRef(fromClause.ref[i], entity)
@@ -115,14 +114,16 @@ const convertPathExpressionToWhere = (fromClause, model) => {
115
114
  prefix.push(element.name)
116
115
  structParent = element
117
116
  continue
118
- }
119
-
120
- if (element && element.isAssociation && fromClause.ref[i].where) {
121
- const target = element._target
122
- _modifyNavigationInWhere(fromClause.ref[i].where, target)
117
+ } else if (element && element.isAssociation) {
118
+ _modifyNavigationInWhere(fromClause.ref[i].where, element._target)
119
+ } else if (element && previousSelect && i === fromClause.ref.length - 1) {
120
+ columns = [{ ref: [...prefix, element.name] }]
121
+ fromClause.ref.splice(-1)
122
+ continue
123
123
  }
124
124
 
125
125
  const currentEntityName = _getEntityName(fromClause, entity, i)
126
+ if (!currentEntityName) continue
126
127
  const tableAlias = `T${i}`
127
128
  const currentSelect = SELECT.from(`${currentEntityName} as ${tableAlias}`)
128
129
 
@@ -144,7 +145,7 @@ const convertPathExpressionToWhere = (fromClause, model) => {
144
145
  model,
145
146
  prefix
146
147
  )
147
-
148
+ _convertSelect(previousSelect, model, options)
148
149
  currentSelect.where('exists', previousSelect)
149
150
  }
150
151
 
@@ -158,7 +159,8 @@ const convertPathExpressionToWhere = (fromClause, model) => {
158
159
  return {
159
160
  target: previousEntityName,
160
161
  alias: previousTableAlias,
161
- where: previousSelect && previousSelect.SELECT && previousSelect.SELECT.where
162
+ where: previousSelect && previousSelect.SELECT && previousSelect.SELECT.where,
163
+ columns
162
164
  }
163
165
  }
164
166
 
@@ -258,23 +260,30 @@ const _isAll = element => {
258
260
  return last && last.id && last.where && last.where[0] === 'not' && last.where[1].xpr
259
261
  }
260
262
 
261
- const _getLambdaSubSelect = (cqn, index, lambdaOp, model) => {
263
+ const _getLambdaSubSelect = (cqn, index, lambdaOp, model, options) => {
262
264
  const _unshiftRefsWithNavigation = nav => el => {
263
265
  if (el.ref) return { ref: [...nav, ...el.ref] }
264
266
  if (el.xpr) return { xpr: el.xpr.map(_unshiftRefsWithNavigation(nav)) }
265
267
  return el
266
268
  }
267
- cqn.SELECT.from.as = PARENT_ALIAS
269
+
270
+ if (!options.lambdaIteration) options.lambdaIteration = 1
271
+
272
+ const outerAlias = cqn.SELECT.from.as || PARENT_ALIAS + options.lambdaIteration
273
+ const innerAlias = FOREIGN_ALIAS + options.lambdaIteration
274
+ cqn.SELECT.from.as = outerAlias
275
+
276
+ const queryTarget = getEntityFromPath(getPathFromRef(cqn.SELECT.from.ref), model)
277
+
268
278
  const nav = cqn.SELECT.where[index].ref.map(el => (el.id ? el.id : el))
269
- const navName = nav[0]
270
279
  const last = cqn.SELECT.where[index].ref.slice(-1)[0]
271
- // REVISIT: for some reason cqn._target is not the csn entity here
272
- const queryTarget = model.definitions[cqn._target.name] || cqn._target
280
+ const navName = queryTarget.elements[nav[0]] ? nav[0] : nav[nav.length - 1]
273
281
  const navElement = queryTarget.elements[navName]
274
282
  const lastElement = nav.reduce((csn, segment) => {
275
283
  if (csn.items) return csn // arrayed not supported
276
284
  if (csn.elements) {
277
285
  const next = csn.elements[segment]
286
+ if (!next) return csn
278
287
  if (next.target) return model.definitions[next.target]
279
288
  return next
280
289
  }
@@ -286,38 +295,54 @@ const _getLambdaSubSelect = (cqn, index, lambdaOp, model) => {
286
295
  ? nav.length > 1
287
296
  ? last.where.map(_unshiftRefsWithNavigation(nav.slice(1)))
288
297
  : last.where
289
- : [{ val: 1 }, '=', { val: 1 }]
298
+ : undefined
290
299
  const onConditionOptions = {
291
300
  associationNames: navName,
292
301
  csn: model,
293
302
  aliases: {
294
- select: FOREIGN_ALIAS,
295
- join: PARENT_ALIAS
303
+ select: innerAlias,
304
+ join: outerAlias
296
305
  }
297
306
  }
298
307
  const onCondition = getOnCond(navElement, onConditionOptions)
299
308
 
300
- const subSelect = SELECT.from({ ref: [navElement.target], as: 'foreign' })
301
- if (condition) subSelect.where(condition)
309
+ const subSelect = SELECT.from({ ref: [navElement.target], as: innerAlias })
310
+ if (condition) {
311
+ if (subSelect.SELECT.from.as) {
312
+ for (let i = 0; i < condition.length; i++) {
313
+ if (
314
+ condition[i].ref &&
315
+ condition[i].ref.length > 1 &&
316
+ condition[i].ref.every(r => typeof r === 'string') &&
317
+ condition[i].ref[0] === navName
318
+ ) {
319
+ condition[i].ref[0] = subSelect.SELECT.from.as
320
+ }
321
+ }
322
+ }
323
+ subSelect.where(condition)
324
+ }
302
325
  subSelect.where(onCondition)
303
326
  if (cds.env.effective.odata.structs) {
304
327
  flattenStructuredSelect(subSelect, model)
305
328
  }
306
329
  subSelect.columns([{ val: 1 }])
307
330
 
308
- return subSelect
331
+ // nested where exists needs recursive conversion
332
+ options.lambdaIteration++
333
+ return _convertSelect(subSelect, model, options)
309
334
  }
310
335
 
311
- const _convertLambda = (cqn, model) => {
336
+ const _convertLambda = (cqn, model, options) => {
312
337
  const where = cqn.SELECT.where
313
338
 
314
339
  if (where) {
315
340
  where.forEach((element, index) => {
316
341
  if (element === 'exists' && _isAny(where[index + 1])) {
317
- where[index + 1] = _getLambdaSubSelect(cqn, index + 1, 'any', model)
342
+ where[index + 1] = _getLambdaSubSelect(cqn, index + 1, 'any', model, options)
318
343
  }
319
344
  if (element === 'not' && where[index + 1] === 'exists' && _isAll(where[index + 2])) {
320
- where[index + 2] = _getLambdaSubSelect(cqn, index + 2, 'all', model)
345
+ where[index + 2] = _getLambdaSubSelect(cqn, index + 2, 'all', model, options)
321
346
  }
322
347
  })
323
348
  }
@@ -415,22 +440,24 @@ const _convertWhereIfSkip = (whereCQN, index) => {
415
440
  OPERATIONS.includes(whereCQN[index + 1]) ? whereCQN.splice(index + 1, 2) : whereCQN.splice(index - 2, 2)
416
441
  }
417
442
 
418
- const _convertOrderByOrWhereCQN = (orderByOrWhereCQN, cqn, model, processFn) => {
419
- const queryTarget = model.definitions[ensureNoDraftsSuffix(getEntityNameFromCQN(cqn))]
443
+ const _convertOrderByOrWhereCQN = (orderByOrWhereCQN, target, model, processFn) => {
444
+ const queryTarget = model.definitions[ensureNoDraftsSuffix(target)]
445
+
420
446
  orderByOrWhereCQN.forEach((el, index) => {
421
447
  if (el.ref && _skip(queryTarget, el.ref, model)) processFn(orderByOrWhereCQN, index)
422
448
  })
423
449
  }
424
450
 
425
- const _convertOrderByOrWhereIfSkip = (cqn, model) => {
451
+ const _convertOrderByOrWhereIfSkip = (cqn, target, model) => {
426
452
  if (cqn.SELECT.orderBy && cqn.SELECT.orderBy.length > 1) {
427
- _convertOrderByOrWhereCQN(cqn.SELECT.orderBy, cqn, model, _convertOrderByIfSkip)
453
+ _convertOrderByOrWhereCQN(cqn.SELECT.orderBy, target, model, _convertOrderByIfSkip)
428
454
  }
429
455
 
430
456
  if (cqn.SELECT.where) {
431
- _convertOrderByOrWhereCQN(cqn.SELECT.where, cqn, model, _convertWhereIfSkip)
457
+ _convertOrderByOrWhereCQN(cqn.SELECT.where, target, model, _convertWhereIfSkip)
432
458
  }
433
459
  }
460
+
434
461
  const _convertExpand = expand => {
435
462
  expand.forEach(expandElement => {
436
463
  if (expandElement.ref && expandElement.ref[0]) {
@@ -456,12 +483,42 @@ const _convertRefWhereInExpand = columns => {
456
483
  }
457
484
  }
458
485
 
486
+ const _defaultColumns4 = entity => {
487
+ if (entity && entity.elements) {
488
+ const addAliasToColumns = []
489
+ for (const column in entity.elements) {
490
+ if (column === 'DraftAdministrativeData_DraftUUID') continue
491
+ const e = entity.elements[column]
492
+ if (e.isAssociation || e.virtual) continue
493
+ else if (entity._isDraftEnabled && column === 'IsActiveEntity') {
494
+ addAliasToColumns.push({ val: true, as: 'IsActiveEntity', cast: { type: 'cds.Boolean' } })
495
+ } else if (entity._isDraftEnabled && column === 'HasActiveEntity') {
496
+ addAliasToColumns.push({ val: false, as: 'HasActiveEntity', cast: { type: 'cds.Boolean' } })
497
+ } else if (entity._isDraftEnabled && column === 'HasDraftEntity') {
498
+ const draftName = `${entity.name}_drafts`
499
+ const subSelect = SELECT.from(draftName).columns([1])
500
+ for (const k in entity.keys) {
501
+ if (k !== 'IsActiveEntity') subSelect.where([{ ref: [entity.name, k] }, '=', { ref: [draftName, k] }])
502
+ }
503
+ addAliasToColumns.push({
504
+ xpr: ['case', 'when', 'exists', subSelect, 'then', 'true', 'else', 'false', 'end'],
505
+ as: 'HasDraftEntity',
506
+ cast: { type: 'cds.Boolean' }
507
+ })
508
+ } else addAliasToColumns.push({ ref: [column], as: column })
509
+ }
510
+ return addAliasToColumns
511
+ }
512
+ }
513
+
514
+ // eslint-disable-next-line complexity
459
515
  const _convertSelect = (cqn, model, options) => {
460
- // remove virtual and with skip annotated fields in orderby and where
461
- _convertOrderByOrWhereIfSkip(cqn, model)
516
+ // REVISIT: still needed?
517
+ // Moved from old SELECT.from where this done much too eager, always for all queries
518
+ if (!cqn.SELECT.columns && cqn._target) cqn.SELECT.columns = _defaultColumns4(cqn._target)
462
519
 
463
520
  // lambda functions
464
- _convertLambda(cqn, model)
521
+ _convertLambda(cqn, model, options)
465
522
 
466
523
  // add 'or is null' in case of '!='
467
524
  if (cqn.SELECT._4odata) _convertNotEqual(cqn.SELECT)
@@ -474,6 +531,9 @@ const _convertSelect = (cqn, model, options) => {
474
531
 
475
532
  // no path expression
476
533
  if (!cqnSelectFromRef || (cqnSelectFromRef.length === 1 && !cqnSelectFromRef[0].where)) {
534
+ // remove virtual and with skip annotated fields in orderby and where
535
+ _convertOrderByOrWhereIfSkip(cqn, getEntityNameFromCQN(cqn), model)
536
+
477
537
  if (cqnSelectFromRef && cqn.SELECT.search && !options.suppressSearch) {
478
538
  searchOptions = { ...searchOptions, ...{ targetName: cqnSelectFromRef[0] } }
479
539
  search2cqn4sql(cqn, model, searchOptions)
@@ -492,8 +552,12 @@ const _convertSelect = (cqn, model, options) => {
492
552
  }
493
553
 
494
554
  // path expression handling
495
- const { target, alias, where, cardinality } = convertPathExpressionToWhere(cqn.SELECT.from, model)
496
- const select = SELECT.from(target)
555
+ const { target, alias, where, cardinality, columns } = convertPathExpressionToWhere(cqn.SELECT.from, model, options)
556
+
557
+ // remove virtual and with skip annotated fields in orderby and where
558
+ _convertOrderByOrWhereIfSkip(cqn, target, model)
559
+
560
+ const select = SELECT.from(target, columns || cqn.SELECT.columns)
497
561
 
498
562
  if (alias) {
499
563
  select.SELECT.from.as = alias
@@ -519,7 +583,11 @@ const _convertSelect = (cqn, model, options) => {
519
583
 
520
584
  // We add all previous properties ot the newly created query.
521
585
  // Reason is to not lose the query API functionality
522
- Object.assign(select.SELECT, cqn.SELECT, { from: select.SELECT.from, where: select.SELECT.where })
586
+ Object.assign(select.SELECT, cqn.SELECT, {
587
+ columns: select.SELECT.columns,
588
+ from: select.SELECT.from,
589
+ where: select.SELECT.where
590
+ })
523
591
 
524
592
  if (select.SELECT.columns && cds.env.effective.odata.structs) {
525
593
  flattenStructuredSelect(select, model)
@@ -528,35 +596,6 @@ const _convertSelect = (cqn, model, options) => {
528
596
  return select
529
597
  }
530
598
 
531
- const _getElement = (column, columns, target) => {
532
- if (!target) return
533
-
534
- if (columns) {
535
- // if columns is defined, column is index and row[column] should contain value that belongs to name in columns with same index
536
- return target.elements[columns[column]]
537
- }
538
-
539
- return target.elements[column]
540
- }
541
-
542
- const _handleArrayedElements = (rows, target, columns) => {
543
- for (const row of rows) {
544
- for (const column in row) {
545
- const element = _getElement(column, columns, target)
546
-
547
- if (element && element.is2one) {
548
- _handleArrayedElements([row[column]], element._target, columns)
549
- } else if (element && element.is2many) {
550
- _handleArrayedElements(row[column], element._target, columns)
551
- } else if (element && element._isStructured) {
552
- _handleArrayedElements([row[column]], element, columns)
553
- } else if (Array.isArray(row[column])) {
554
- row[column] = JSON.stringify(row[column])
555
- }
556
- }
557
- }
558
- }
559
-
560
599
  const _convertInsert = (cqn, model) => {
561
600
  // resolve path expression
562
601
  const resolvedIntoClause = _convertPathExpressionForInsertOrDelete(cqn.INSERT.into, model)
@@ -571,22 +610,11 @@ const _convertInsert = (cqn, model) => {
571
610
  Object.assign(insert.INSERT, cqn.INSERT, { into: resolvedIntoClause })
572
611
 
573
612
  const targetName = insert.INSERT.into.name || insert.INSERT.into
574
- const queryTarget = model.definitions[ensureNoDraftsSuffix(targetName)]
575
-
576
- if (cds.env.effective.odata.version !== 'v2') {
577
- if (cqn.INSERT.entries) {
578
- _handleArrayedElements(cqn.INSERT.entries, queryTarget)
579
- } else if (cqn.INSERT.rows) {
580
- _handleArrayedElements(cqn.INSERT.rows, queryTarget, cqn.INSERT.columns)
581
- } else if (cqn.INSERT.values) {
582
- _handleArrayedElements([cqn.INSERT.values], queryTarget, cqn.INSERT.columns)
583
- }
584
- }
585
613
 
586
614
  const target = model.definitions[targetName]
587
615
  if (!target) return insert
588
616
 
589
- const resolvedView = resolveView(insert, model, 'db')
617
+ const resolvedView = resolveView(insert, model, cds.db)
590
618
 
591
619
  if (cqn.INSERT.into.ref && cqn.INSERT.into.ref.length > 1) {
592
620
  const copyFrom = [...cqn.INSERT.into.ref]
@@ -598,6 +626,7 @@ const _convertInsert = (cqn, model) => {
598
626
  }
599
627
 
600
628
  function _modifyNavigationInWhere(whereClause, target) {
629
+ if (!whereClause) return
601
630
  whereClause.forEach(e => {
602
631
  if (e.ref && e.ref.length > 1 && target.elements[e.ref[0]]) {
603
632
  const element = target.elements[e.ref[0]]
@@ -619,10 +648,10 @@ const _plainDelete = (cqn, model) => {
619
648
  const target = model.definitions[name]
620
649
  if (!target) return cqn
621
650
 
622
- return resolveView(cqn, model, 'db')
651
+ return resolveView(cqn, model, cds.db)
623
652
  }
624
653
 
625
- const _convertDelete = (cqn, model) => {
654
+ const _convertDelete = (cqn, model, options) => {
626
655
  // .from is plain string or csn entity
627
656
  if (
628
657
  typeof cqn.DELETE.from === 'string' ||
@@ -632,7 +661,7 @@ const _convertDelete = (cqn, model) => {
632
661
  return _plainDelete(cqn, model)
633
662
  }
634
663
 
635
- const { target, alias, where } = convertPathExpressionToWhere(cqn.DELETE.from, model)
664
+ const { target, alias, where } = convertPathExpressionToWhere(cqn.DELETE.from, model, options)
636
665
  const deleet = DELETE('x')
637
666
  Object.assign(deleet.DELETE, cqn.DELETE, { from: target, where: undefined })
638
667
 
@@ -643,37 +672,30 @@ const _convertDelete = (cqn, model) => {
643
672
  const targetEntity = model.definitions[target]
644
673
  if (!targetEntity) return deleet
645
674
 
646
- return resolveView(deleet, model, 'db')
675
+ return resolveView(deleet, model, cds.db)
647
676
  }
648
677
 
649
678
  function _plainUpdate(cqn, model) {
650
679
  const name = cqn.UPDATE.entity.name || (cqn.UPDATE.entity.ref && cqn.UPDATE.entity.ref[0]) || cqn.UPDATE.entity
651
- const queryTarget = model.definitions[ensureNoDraftsSuffix(name)]
652
-
653
- if (cds.env.effective.odata.version !== 'v2') {
654
- cqn.UPDATE.data && _handleArrayedElements([cqn.UPDATE.data], queryTarget)
655
- cqn.UPDATE.with && _handleArrayedElements([cqn.UPDATE.with], queryTarget)
656
- }
657
-
658
680
  const target = model.definitions[name]
659
681
  if (!target) return cqn
660
682
 
661
- return resolveView(cqn, model, 'db')
683
+ return resolveView(cqn, model, cds.db)
662
684
  }
663
685
 
664
- const _convertUpdate = (cqn, model) => {
686
+ const _convertUpdate = (cqn, model, options) => {
665
687
  // REVISIT flatten structured types, currently its done in SQL builder
666
688
 
667
689
  // .into is plain string or csn entity
668
690
  if (
669
691
  typeof cqn.UPDATE.entity === 'string' ||
670
692
  cqn.UPDATE.entity.name ||
671
- (cqn.UPDATE.entity.ref && typeof cqn.UPDATE.entity.ref[0] === 'string')
693
+ (cqn.UPDATE.entity.ref && typeof cqn.UPDATE.entity.ref[0] === 'string' && cqn.UPDATE.entity.ref.length === 1)
672
694
  ) {
673
695
  return _plainUpdate(cqn, model)
674
696
  }
675
697
 
676
- const { target, alias, where } = convertPathExpressionToWhere(cqn.UPDATE.entity, model)
698
+ const { target, alias, where } = convertPathExpressionToWhere(cqn.UPDATE.entity, model, options)
677
699
 
678
700
  // link .with and .data and set query target and remove current where clause
679
701
  // REVISIT: update statement does not accept cqn partial as input
@@ -684,17 +706,10 @@ const _convertUpdate = (cqn, model) => {
684
706
  if (where) update.where(where)
685
707
  if (cqn.UPDATE.where) update.where(_addAliasToExpression(cqn.UPDATE.where, alias))
686
708
 
687
- const queryTarget = model.definitions[target]
688
-
689
- if (cds.env.effective.odata.version !== 'v2') {
690
- cqn.UPDATE.data && _handleArrayedElements([cqn.UPDATE.data], queryTarget)
691
- cqn.UPDATE.with && _handleArrayedElements([cqn.UPDATE.with], queryTarget)
692
- }
693
-
694
709
  const targetEntity = model.definitions[target]
695
710
  if (!targetEntity) return update
696
711
 
697
- return resolveView(update, model, 'db')
712
+ return resolveView(update, model, cds.db)
698
713
  }
699
714
 
700
715
  /**
@@ -715,15 +730,15 @@ const cqn2cqn4sql = (cqn, model, options = { suppressSearch: false }) => {
715
730
  }
716
731
 
717
732
  if (cqn.UPDATE) {
718
- return _convertUpdate(cqn, model)
733
+ return _convertUpdate(cqn, model, options)
719
734
  }
720
735
 
721
736
  if (cqn.INSERT) {
722
- return _convertInsert(cqn, model)
737
+ return _convertInsert(cqn, model, options)
723
738
  }
724
739
 
725
740
  if (cqn.DELETE) {
726
- return _convertDelete(cqn, model)
741
+ return _convertDelete(cqn, model, options)
727
742
  }
728
743
 
729
744
  return cqn
@@ -1,6 +1,7 @@
1
1
  const cds = require('../../cds')
2
2
 
3
3
  const { ensureNoDraftsSuffix } = require('./draft')
4
+ const { getFlatArray } = require('../../db/utils/deep')
4
5
 
5
6
  const getEtagElement = entity => {
6
7
  return Object.values(entity.elements).find(element => element['@odata.etag'])
@@ -41,7 +42,7 @@ const _isDependent = (assoc, parent, target) => {
41
42
  * doing as aspect is difficult due to no global definitons per tenant
42
43
  */
43
44
  const getDependents = (entity, model) => {
44
- if (entity._dependents !== undefined) return entity._dependents
45
+ if (entity.own('__dependents')) return entity.__dependents
45
46
 
46
47
  /** @type {Array|boolean} */
47
48
  let dependents = []
@@ -61,8 +62,49 @@ const getDependents = (entity, model) => {
61
62
  }
62
63
 
63
64
  if (dependents.length === 0) dependents = false
64
- Object.defineProperty(entity, '_dependents', { value: dependents })
65
- return dependents
65
+ return entity.set('__dependents', dependents)
66
+ }
67
+
68
+ const _getUps = (entity, model, previous = []) => {
69
+ const ups = []
70
+ for (const def of Object.values(model.definitions)) {
71
+ if (def.kind !== 'entity') continue
72
+ 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
+ })
80
+ }
81
+ }
82
+ return ups
83
+ }
84
+
85
+ const _ifDataSubject = (entity, role) => {
86
+ return entity['@PersonalData.EntitySemantics'] === 'DataSubject' && entity['@PersonalData.DataSubjectRole'] === role
87
+ }
88
+
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 }
100
+ }
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
+ }
106
+
107
+ return entity.set(hash, dataSubject)
66
108
  }
67
109
 
68
110
  const _findRootEntity = (entities, edmName) => {
@@ -128,6 +170,14 @@ const findCsnTargetFor = (edmName, model, namespace) => {
128
170
  return mapping[edmName]
129
171
  }
130
172
 
173
+ const getElementDeep = (entity, ref) => {
174
+ let current = entity
175
+ for (const r of ref) {
176
+ current = current && current.elements && current.elements[r]
177
+ }
178
+ return current
179
+ }
180
+
131
181
  const isRootEntity = (definitions, entityName) => {
132
182
  const entity = definitions[entityName]
133
183
  if (!entity) return false
@@ -156,6 +206,8 @@ const isRootEntity = (definitions, entityName) => {
156
206
  module.exports = {
157
207
  getEtagElement,
158
208
  findCsnTargetFor,
209
+ getElementDeep,
159
210
  getDependents,
160
- isRootEntity
211
+ isRootEntity,
212
+ getDataSubject
161
213
  }
@@ -1,39 +1,3 @@
1
- // REVISIT: use follow projection
2
-
3
- const getError = require('../error')
4
-
5
- const _renameData = (query, data) => {
6
- if (query.SELECT && query.SELECT.columns) {
7
- for (const col of query.SELECT.columns) {
8
- if (typeof col === 'object' && col.ref && col.as && col.ref[0] !== col.as) {
9
- if (data[col.as]) {
10
- data[col.ref[0]] = data[col.as]
11
- delete data[col.as]
12
- }
13
- }
14
- }
15
- }
16
- }
17
-
18
- const getTargetData = (target, data = {}) => {
19
- if (target.query) {
20
- _renameData(target.query, data)
21
-
22
- if (target.query._target) {
23
- return getTargetData(target.query._target, data)
24
- }
25
-
26
- if (!target.query.from || target.query.from.length > 1 || target.query.where) {
27
- // REVISIT: when is this the case?! getTargetData only used in deleteDraftUtils...
28
- throw getError(501, 'Insert, Update or Delete on views with join|union|where is not supported')
29
- }
30
-
31
- return { target: target.query.from[0].absolute, data }
32
- }
33
-
34
- return { target, data }
35
- }
36
-
37
1
  // REVISIT: remove once not needed anymore
38
2
  const getDataFromCQN = query => (query.INSERT && query.INSERT.entries) || (query.UPDATE && query.UPDATE.data)
39
3
 
@@ -47,7 +11,6 @@ const setDataFromCQN = req => {
47
11
  }
48
12
 
49
13
  module.exports = {
50
- getTargetData,
51
14
  getDataFromCQN,
52
15
  setDataFromCQN
53
16
  }
@@ -85,7 +85,7 @@ module.exports = (data, { query, target }, { model }) => {
85
85
  } else if (query.UPDATE) {
86
86
  const where = _getWhereFromUpdate(query, target, model)
87
87
  if (!where || !where.length) return
88
-
88
+ if (!data) data = query.UPDATE.data = {} // REVISIT: We should not expect data to be present always!
89
89
  _addKeysFromWhereToData(where, target, data)
90
90
  }
91
91
  }
@@ -1,38 +1,21 @@
1
1
  const { ensureNoDraftsSuffix } = require('../../common/utils/draft')
2
2
 
3
- const _entityFromRef = ref => {
4
- if (ref) return ref[0].id || ref[0]
5
- }
6
-
7
3
  const getEntityNameFromCQN = cqn => {
8
- while (cqn.SELECT) {
9
- cqn = cqn.SELECT.from
10
- }
4
+ while (cqn.SELECT) cqn = cqn.SELECT.from
11
5
 
12
- return _getEntityNameFromUnionCQN(cqn) || _entityFromRef(cqn.ref)
13
- }
6
+ // Do the most likely first -> {ref}
7
+ if (cqn.ref) {
8
+ return cqn.ref[0].id || cqn.ref[0]
9
+ }
14
10
 
15
- const _getEntityNameFromUnionCQN = cqn => {
16
11
  // TODO cleanup
17
12
  // REVISIT infer should do this for req.target
18
13
  // REVISIT2 No, req.target doesn't make sense for joins
19
14
  if (cqn.SET) {
20
- return cqn.SET.args
21
- .map(arg => {
22
- return getEntityNameFromCQN(arg)
23
- })
24
- .filter(name => {
25
- return name !== 'DRAFT.DraftAdministrativeData'
26
- })[0]
15
+ return cqn.SET.args.map(getEntityNameFromCQN).find(n => n !== 'DRAFT.DraftAdministrativeData')
27
16
  }
28
17
  if (cqn.join) {
29
- return cqn.args
30
- .map(arg => {
31
- return getEntityNameFromCQN(arg)
32
- })
33
- .filter(name => {
34
- return name !== 'DRAFT.DraftAdministrativeData'
35
- })[0]
18
+ return cqn.args.map(getEntityNameFromCQN).find(n => n !== 'DRAFT.DraftAdministrativeData')
36
19
  }
37
20
  }
38
21