@sap/cds 5.4.6 → 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 +208 -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 +44 -55
  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 +4 -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 +6 -22
  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 +102 -101
  127. package/libx/_runtime/common/utils/csn.js +47 -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 +223 -171
  138. package/libx/_runtime/common/utils/rewriteAsterisk.js +46 -26
  139. package/libx/_runtime/common/utils/structured.js +6 -12
  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 +22 -30
  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 +13 -20
  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 +241 -189
  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 +2 -2
  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 +23 -24
  205. package/libx/_runtime/sqlite/localized.js +12 -7
  206. package/libx/_runtime/types/api.js +10 -0
  207. package/package.json +1 -1
  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
@@ -26,10 +26,10 @@ const _recursivelyAliasRefs = (something, newAlias, oldAlias, subselect = false)
26
26
  }
27
27
  }
28
28
 
29
- function _getSubWhereAndEntities(allBackLinks, links, draft, element, level) {
29
+ function _getSubWhereAndEntities(element, parentWhere, draft, level = 0, compositionTree = {}) {
30
+ const allBackLinks = [...element.backLinks, ...element.customBackLinks]
30
31
  let entity1, entity2
31
- const isBackLink = allBackLinks.length > 0
32
- const linksForWhere = isBackLink ? allBackLinks : links
32
+ const linksForWhere = allBackLinks.length ? allBackLinks : element.links
33
33
 
34
34
  const subWhere = linksForWhere.reduce((result, backLink) => {
35
35
  // exclude static values from subwhere
@@ -43,29 +43,55 @@ function _getSubWhereAndEntities(allBackLinks, links, draft, element, level) {
43
43
  entity1 = {
44
44
  alias: `ALIAS${level + 1}`,
45
45
  entityName: ctUtils.addDraftSuffix(draft, element.source),
46
- propertyName: isBackLink ? backLink.entityKey : backLink.targetKey
46
+ propertyName: backLink.entityKey
47
47
  }
48
48
 
49
- const res1 = backLink.entityKey
50
- ? { ref: [entity1.alias, entity1.propertyName] }
51
- : { val: isBackLink ? backLink.entityVal : backLink.targetVal }
49
+ const res1 = backLink.entityKey ? { ref: [entity1.alias, entity1.propertyName] } : { val: backLink.entityVal }
52
50
 
53
51
  entity2 = {
54
52
  alias: `ALIAS${level}`,
55
53
  entityName: ctUtils.addDraftSuffix(draft, element.target || element.source),
56
- propertyName: isBackLink ? backLink.targetKey : backLink.entityKey
54
+ propertyName: backLink.targetKey
57
55
  }
58
56
 
59
- const res2 = backLink.targetKey
60
- ? { ref: [entity2.alias, entity2.propertyName] }
61
- : { val: isBackLink ? backLink.targetVal : backLink.entityVal }
57
+ const res2 = backLink.targetKey ? { ref: [entity2.alias, entity2.propertyName] } : { val: backLink.targetVal }
62
58
 
63
59
  result.push(res1, '=', res2)
64
60
  return result
65
61
  }, [])
66
62
 
63
+ const where = []
64
+ if (!subWhere.length) return { where, entity1, entity2 }
65
+
66
+ let whereKeys = _getWhereKeys(allBackLinks, entity1)
67
+ const staticWhereValues = _getStaticWhere(allBackLinks, entity1)
68
+ if (whereKeys.length === 0 && element.links.length === 1) {
69
+ // add is null check for each unused backlink
70
+ for (const ce of compositionTree.compositionElements || []) {
71
+ if (ce.source !== element.source) continue
72
+ if (ce.name === element.name) continue
73
+ const wk = _getWhereKeys([...ce.backLinks, ...ce.customBackLinks], entity1, 'null')
74
+ if (whereKeys.length === 0) whereKeys = wk
75
+ else whereKeys.push('and', ...wk)
76
+ }
77
+ }
78
+
79
+ if (whereKeys.length > 0) {
80
+ where.push('(', ...whereKeys, ')', 'and')
81
+ }
82
+ if (staticWhereValues.length > 0) {
83
+ where.push('(', ...staticWhereValues, ')', 'and')
84
+ }
85
+ where.push('exists', {
86
+ SELECT: {
87
+ columns: [{ val: 1, as: '_exists' }],
88
+ from: { ref: [entity2.entityName], as: entity2.alias },
89
+ where: parentWhere ? ['(', ...parentWhere, ')', 'and', '(', ...subWhere, ')'] : subWhere
90
+ }
91
+ })
92
+
67
93
  return {
68
- subWhere,
94
+ where,
69
95
  entity1,
70
96
  entity2
71
97
  }
@@ -102,30 +128,25 @@ function _getStaticWhere(allBackLinks, entity1) {
102
128
  }, [])
103
129
  }
104
130
 
105
- const _csnElementFromTarget = (element, definitions) =>
106
- element.target && definitions[element.target].elements[element.name]
107
-
108
- const _is2OneManaged = element => {
109
- return element.is2one && ctUtils.isManaged(element)
131
+ const _is2oneComposition = (element, definitions) => {
132
+ const csnElement = element.target && definitions[element.target].elements[element.name]
133
+ return csnElement && csnElement.is2one && csnElement._isCompositionEffective
110
134
  }
111
135
 
112
136
  const _addToCQNs = (cqns, subCQN, element, definitions, level) => {
113
- if (cds.env.features._foreign_key_constraints) {
114
- // deep delete should be handled by database constraints
115
- // return [[cqn]]
116
- const csnElement = _csnElementFromTarget(element, definitions)
117
-
118
- // TODO: only managed to-one should be filtered, unmanaged with key in children can be ignored
119
- if (csnElement && _is2OneManaged(csnElement)) {
120
- cqns[level] = cqns[level] || []
121
- cqns[level].push(subCQN)
122
- }
123
- } else {
124
- cqns[level] = cqns[level] || []
137
+ cqns[level] = cqns[level] || []
138
+ // Since `>2.5.2` compiler generates constraints for compositions of one like for annotations
139
+ // Thus only single 2one case (`$self`-managed composition) has DELETE CASCADE
140
+ // Here it's ignored to simplify i.e. handle all "2ones" in a same manner
141
+ if (!cds.env.features._foreign_key_constraints || _is2oneComposition(element, definitions)) {
125
142
  cqns[level].push(subCQN)
126
143
  }
127
144
  }
128
145
 
146
+ // unofficial config!
147
+ const DEEP_DELETE_MAX_RECURSION_DEPTH =
148
+ (cds.env.features.recursion_depth && Number(cds.env.features.recursion_depth)) || 2
149
+
129
150
  const _addSubCascadeDeleteCQN = (
130
151
  definitions,
131
152
  compositionTree,
@@ -133,13 +154,14 @@ const _addSubCascadeDeleteCQN = (
133
154
  level,
134
155
  cqns,
135
156
  draft,
136
- elementSet = new Set()
157
+ elementMap = new Map()
137
158
  ) => {
138
159
  for (const element of compositionTree.compositionElements) {
139
160
  if (element.skipPersistence) continue
140
161
 
141
162
  const fqn = compositionTree.source + ':' + element.name
142
- if (elementSet.has(fqn)) {
163
+ const seen = elementMap.get(fqn)
164
+ if (seen && seen >= DEEP_DELETE_MAX_RECURSION_DEPTH) {
143
165
  // recursion -> abort
144
166
  continue
145
167
  }
@@ -147,47 +169,17 @@ const _addSubCascadeDeleteCQN = (
147
169
  // REVISIT: sometimes element.target is undefined which leads to self join
148
170
  if (!element.target) element.target = compositionTree.source
149
171
 
150
- const allBackLinks = [...element.backLinks, ...element.customBackLinks]
151
- const { entity1, entity2, subWhere } = _getSubWhereAndEntities(allBackLinks, element.links, draft, element, level)
152
-
153
- let whereKeys = _getWhereKeys(allBackLinks, entity1)
154
- const staticWhereValues = _getStaticWhere(allBackLinks, entity1)
155
- if (allBackLinks.length > 0 || element.links.length > 0) {
156
- const where = []
157
-
158
- if (whereKeys.length === 0 && element.links.length === 1) {
159
- // add is null check for each unused backlink
160
- for (const ce of compositionTree.compositionElements) {
161
- if (ce.source !== element.source) continue
162
- if (ce.name === element.name) continue
163
- const wk = _getWhereKeys([...ce.backLinks, ...ce.customBackLinks], entity1, 'null')
164
- if (whereKeys.length === 0) whereKeys = wk
165
- else whereKeys.push('and', ...wk)
166
- }
167
- }
168
-
169
- if (whereKeys.length > 0) {
170
- where.push('(', ...whereKeys, ')', 'and')
171
- }
172
- if (staticWhereValues.length > 0) {
173
- where.push('(', ...staticWhereValues, ')', 'and')
174
- }
175
-
176
- where.push('exists', {
177
- SELECT: {
178
- columns: [{ val: 1, as: '_exists' }],
179
- from: { ref: [entity2.entityName], as: entity2.alias },
180
- where: parentWhere ? ['(', ...parentWhere, ')', 'and', '(', ...subWhere, ')'] : subWhere
181
- }
182
- })
183
-
172
+ const { entity1, where } = _getSubWhereAndEntities(element, parentWhere, draft, level, compositionTree)
173
+ if (where.length) {
184
174
  const subCQN = { DELETE: { from: { ref: [entity1.entityName], as: entity1.alias }, where: where } }
185
175
 
186
176
  _addToCQNs(cqns, subCQN, element, definitions, level)
187
177
 
188
- elementSet.add(fqn)
189
-
190
- _addSubCascadeDeleteCQN(definitions, element, subCQN.DELETE.where, level + 1, cqns, draft, elementSet)
178
+ // Make a copy and do not share the same map among brother compositions
179
+ // as we're only interested in deep recursions, not wide recursions.
180
+ const newElementMap = new Map(elementMap)
181
+ newElementMap.set(fqn, (seen && seen + 1) || 1)
182
+ _addSubCascadeDeleteCQN(definitions, element, subCQN.DELETE.where, level + 1, cqns, draft, newElementMap)
191
183
  }
192
184
  }
193
185
 
@@ -202,12 +194,41 @@ const hasDeepDelete = (definitions, cqn) => {
202
194
  const from = getEntityNameFromDeleteCQN(cqn)
203
195
  if (!from) return false
204
196
 
197
+ // hidden flag for DELETEs on draft root, we have a separate mechanism that deletes the rows using the DraftUUID
198
+ // Hence, we do not need a deep delete in that case.
199
+ if (cqn._suppressDeepDelete) return false
200
+
205
201
  const entity = definitions && definitions[ensureNoDraftsSuffix(from)]
202
+
206
203
  if (entity) return !!Object.keys(entity.elements || {}).find(k => entity.elements[k]._isCompositionEffective)
207
204
 
208
205
  return false
209
206
  }
210
207
 
208
+ const _getSetNullParentForeignKeyCQNs = (definitions, entityName, parentWhere, draft) => {
209
+ const setNullCQNs = []
210
+ for (const { elements } of definitions[entityName].__oneCompositionParents.values()) {
211
+ for (const element of elements.values()) {
212
+ const data = element.links.reduce((d, fk) => {
213
+ d[fk.entityKey] = null
214
+ return d
215
+ }, {})
216
+ const { entity1, where } = _getSubWhereAndEntities(element, parentWhere, draft)
217
+ if (where.length) {
218
+ setNullCQNs.push({
219
+ UPDATE: {
220
+ entity: { ref: [entity1.entityName], as: entity1.alias },
221
+ data,
222
+ where,
223
+ _beforeDelete: true
224
+ }
225
+ })
226
+ }
227
+ }
228
+ }
229
+ return setNullCQNs
230
+ }
231
+
211
232
  const getDeepDeleteCQNs = (definitions, cqn) => {
212
233
  const from = getEntityNameFromDeleteCQN(cqn)
213
234
  if (!from) return [[cqn]]
@@ -219,15 +240,20 @@ const getDeepDeleteCQNs = (definitions, cqn) => {
219
240
  definitions,
220
241
  rootEntityName: entityName,
221
242
  checkRoot: false,
222
- resolveViews: !draft
243
+ resolveViews: !draft,
244
+ service: cds.db
223
245
  })
224
246
  const parentWhere = cqn.DELETE.where && JSON.parse(JSON.stringify(cqn.DELETE.where))
225
247
  if (parentWhere) {
226
248
  const parentAlias = cqn.DELETE.from.as || (cqn.DELETE.from.ref && cqn.DELETE.from.ref[0]) || cqn.DELETE.from // or however we get the table name...
227
249
  _recursivelyAliasRefs(parentWhere, 'ALIAS0', parentAlias)
228
250
  }
229
-
230
- return [[cqn], ..._addSubCascadeDeleteCQN(definitions, compositionTree, parentWhere, 0, [], draft)].reverse()
251
+ const setNullUpdates = []
252
+ if (cds.env.features._foreign_key_constraints && definitions[entityName].own('__oneCompositionParents')) {
253
+ setNullUpdates.push(..._getSetNullParentForeignKeyCQNs(definitions, entityName, parentWhere, draft))
254
+ }
255
+ const subCascadeDeletes = _addSubCascadeDeleteCQN(definitions, compositionTree, parentWhere, 0, [], draft)
256
+ return [[cqn], ...subCascadeDeletes, ...setNullUpdates].reverse()
231
257
  }
232
258
 
233
259
  module.exports = {
@@ -1,5 +1,5 @@
1
1
  const { getCompositionTree, getCompositionRoot } = require('./tree')
2
- const { hasDeepInsert, getDeepInsertCQNs } = require('./insert')
2
+ const { hasDeepInsert, getDeepInsertCQNs, cleanEmptyCompositionsOfMany } = require('./insert')
3
3
  const { hasDeepUpdate, getDeepUpdateCQNs } = require('./update')
4
4
  const { hasDeepDelete, getDeepDeleteCQNs } = require('./delete')
5
5
  const { selectDeepUpdateData } = require('./data')
@@ -11,6 +11,7 @@ module.exports = {
11
11
  // insert
12
12
  hasDeepInsert,
13
13
  getDeepInsertCQNs,
14
+ cleanEmptyCompositionsOfMany,
14
15
  // update
15
16
  hasDeepUpdate,
16
17
  getDeepUpdateCQNs,
@@ -1,3 +1,5 @@
1
+ const cds = require('../../cds')
2
+
1
3
  const { getCompositionTree } = require('./tree')
2
4
  const ctUtils = require('./utils')
3
5
 
@@ -25,13 +27,14 @@ const _addSubDeepInsertCQN = (definitions, compositionTree, data, cqns, draft) =
25
27
  const subData = data.reduce((result, entry) => {
26
28
  if (element.name in entry) {
27
29
  const elementValue = ctUtils.val(entry[element.name])
28
- // remove empty entries
29
- const subData = ctUtils.array(elementValue).filter(ele => Object.keys(ele).length > 0)
30
-
31
- if (subData.length > 0) {
32
- // REVISIT: this can make problems
33
- insertCQN.INSERT.entries.push(...ctUtils.cleanDeepData(subEntity, subData))
34
- result.push(...subData)
30
+ if (elementValue != null) {
31
+ // remove empty entries
32
+ const subData = ctUtils.array(elementValue).filter(ele => Object.keys(ele).length > 0)
33
+ if (subData.length > 0) {
34
+ // REVISIT: this can make problems
35
+ insertCQN.INSERT.entries.push(...ctUtils.cleanDeepData(subEntity, subData))
36
+ result.push(...subData)
37
+ }
35
38
  }
36
39
  }
37
40
  return result
@@ -50,10 +53,28 @@ const _addSubDeepInsertCQN = (definitions, compositionTree, data, cqns, draft) =
50
53
  * exports
51
54
  */
52
55
 
56
+ const _entityFromINSERT = (definitions, INSERT) => {
57
+ if (INSERT && INSERT.into) {
58
+ const entityName = ensureNoDraftsSuffix(INSERT.into.name || INSERT.into)
59
+ return definitions && definitions[entityName]
60
+ }
61
+ }
62
+
63
+ const cleanEmptyCompositionsOfMany = (definitions, cqn) => {
64
+ const entity = _entityFromINSERT(definitions, cqn.INSERT)
65
+ if (!entity) return
66
+ for (const entry of cqn.INSERT.entries || []) {
67
+ for (const elName in entry || {}) {
68
+ const el = entity.elements[elName]
69
+ if (!el) continue
70
+ if (el.is2many && !entry[elName].length) delete entry[elName]
71
+ }
72
+ }
73
+ }
74
+
53
75
  const hasDeepInsert = (definitions, cqn) => {
54
- if (cqn && cqn.INSERT && cqn.INSERT.into && cqn.INSERT.entries) {
55
- const entityName = ensureNoDraftsSuffix(cqn.INSERT.into.name || cqn.INSERT.into)
56
- const entity = definitions && definitions[entityName]
76
+ if (cqn.INSERT.entries) {
77
+ const entity = _entityFromINSERT(definitions, cqn.INSERT)
57
78
  if (entity) {
58
79
  return !!cqn.INSERT.entries.find(entry => {
59
80
  return !!Object.keys(entry || {}).find(k => {
@@ -75,7 +96,8 @@ const getDeepInsertCQNs = (definitions, cqn) => {
75
96
  definitions,
76
97
  rootEntityName: entityName,
77
98
  checkRoot: false,
78
- resolveViews: !draft
99
+ resolveViews: !draft,
100
+ service: cds.db
79
101
  })
80
102
 
81
103
  const flattenedCqn = { INSERT: Object.assign({}, cqn.INSERT) }
@@ -89,6 +111,7 @@ const getDeepInsertCQNs = (definitions, cqn) => {
89
111
  }
90
112
 
91
113
  module.exports = {
114
+ cleanEmptyCompositionsOfMany,
92
115
  hasDeepInsert,
93
116
  getDeepInsertCQNs
94
117
  }
@@ -1,23 +1,45 @@
1
1
  const cds = require('../../cds')
2
2
 
3
- const { getBackLinks, isSelfManaged, getOnCondElements, isBacklink } = require('../utils/backlinks')
4
- const ctUtils = require('./utils')
3
+ const { isSelfManaged, isBacklink } = require('../utils/backlinks')
5
4
 
6
5
  const { ensureNoDraftsSuffix } = require('../utils/draft')
7
6
  const { isRootEntity } = require('../utils/csn')
8
7
  const { getTransition, getDBTable } = require('../utils/resolveView')
9
8
 
9
+ const { foreignKeyPropagations } = require('../utils/foreignKeyPropagations')
10
+
10
11
  const getError = require('../../common/error')
11
12
 
12
13
  /*
13
14
  * own utils
14
15
  */
15
16
 
16
- const _resolvedElement = element => {
17
+ const _foreignKeysToLinks = (element, inverse) =>
18
+ foreignKeyPropagations(element).map(e => {
19
+ e = inverse
20
+ ? {
21
+ childFieldName: e.parentFieldName,
22
+ parentFieldName: e.childFieldName,
23
+ childFieldValue: e.parentFieldValue,
24
+ parentFieldValue: e.childFieldValue,
25
+ prefix: e.prefix
26
+ }
27
+ : e
28
+ const link = {
29
+ entityKey:
30
+ e.prefix && !e.parentFieldName.includes(e.prefix) ? e.prefix + '_' + e.parentFieldName : e.parentFieldName,
31
+ targetKey: e.prefix && !e.childFieldName.includes(e.prefix) ? e.prefix + '_' + e.childFieldName : e.childFieldName
32
+ }
33
+ if (e.parentFieldValue !== undefined) link.entityVal = e.parentFieldValue
34
+ if (e.childFieldValue !== undefined) link.targetVal = e.childFieldValue
35
+ return link
36
+ })
37
+
38
+ const _resolvedElement = (element, service) => {
17
39
  if (!element.target) return element
18
40
  // skip forbidden view check if association to view with foreign key in target
19
41
  const skipForbiddenViewCheck = element._isAssociationStrict && element.on && !element['@odata.contained']
20
- const { target, mapping } = getTransition(element._target, undefined, skipForbiddenViewCheck)
42
+ const { target, mapping } = getTransition(element._target, service, skipForbiddenViewCheck)
21
43
  const newElement = { target: target.name, _target: target }
22
44
  Object.setPrototypeOf(newElement, element)
23
45
  if (element.on) {
@@ -35,87 +57,22 @@ const _resolvedElement = element => {
35
57
  const _navigationExistsInCompositionMap = (element, compositionMap) =>
36
58
  compositionMap.has(element.target) && element._isCompositionEffective
37
59
 
38
- const _addNavigationToCompositionElements = (element, definitions, compositionTree, compositionMap, isManaged) => {
39
- const links = element.is2one ? getBackLinks(element) : []
40
-
41
- const compositionElement = Object.assign({}, compositionMap.get(element.target), { name: element.name, links })
42
- compositionElement.target = element.parent.name
43
- const backLinks = element.is2many ? getBackLinks(element) : []
44
-
45
- if (isManaged) {
46
- compositionElement.backLinks = backLinks
47
- } else {
48
- compositionElement.customBackLinks = backLinks
49
- }
50
-
51
- compositionTree.compositionElements.push(compositionElement)
52
- }
53
-
54
60
  const _isUnManaged = element => element.on && !isSelfManaged(element)
55
61
 
56
62
  const _isNonRecursiveNavigation = (element, rootEntityName) =>
57
63
  rootEntityName !== element.target && element._isCompositionEffective
58
64
 
59
- const _getLinks = element => (element.is2one && !isSelfManaged(element) ? getBackLinks(element) : [])
60
-
61
65
  const _skipPersistence = (element, definitions) => definitions[element.target]._hasPersistenceSkip
62
66
 
63
- const _createSubElement = (element, definitions, parentEntityName) => {
64
- const links = _getLinks(element, definitions)
65
- const backLinks = []
66
- const subObject = { name: element.name, backLinks, links }
67
-
67
+ const _createSubElement = (element, definitions) => {
68
+ const subObject = { name: element.name, customBackLinks: [], links: [], backLinks: [] }
68
69
  if (_skipPersistence(element, definitions)) {
69
70
  subObject.skipPersistence = true
70
71
  }
71
-
72
- if (_isUnManaged(element)) {
73
- subObject.customBackLinks = getBackLinks(element)
74
- }
75
-
76
72
  return subObject
77
73
  }
78
74
 
79
- const _isAssocComp = (element, parent) => {
80
- return (
81
- // REVISIT: are all three checks really required?
82
- element.target === parent.name && element.isAssociation && element.on
83
- )
84
- }
85
-
86
- const _checkIfBackLink = (element, definitions) => {
87
- const target = definitions[element.target]
88
- for (const elementName in target.elements) {
89
- const targetElement = target.elements[elementName]
90
- if (_isAssocComp(targetElement, element.parent)) {
91
- const onCondElements = getOnCondElements(targetElement.on)
92
- for (const el of onCondElements) {
93
- const { entityKey, targetKey } = el
94
- if (entityKey === `${elementName}.${element.name}` || targetKey === `${elementName}.${element.name}`) {
95
- return true
96
- }
97
- }
98
- return false
99
- }
100
- }
101
- }
102
-
103
- const _addBackLinksToCompositionTree = (element, definitions, compositionTree) => {
104
- if (_isUnManaged(element)) {
105
- if (_checkIfBackLink(element, definitions)) {
106
- const backLinks = getBackLinks(element).map(backLink => ({
107
- entityKey: backLink.targetKey,
108
- targetKey: backLink.entityKey,
109
- entityVal: backLink.targetVal,
110
- targetVal: backLink.entityVal
111
- }))
112
- compositionTree.customBackLinks.push(...backLinks)
113
- }
114
- } else {
115
- compositionTree.backLinks.push(...getBackLinks(element))
116
- }
117
- }
118
-
75
+ // eslint-disable-next-line complexity
119
76
  const _getCompositionTreeRec = ({
120
77
  rootEntityName,
121
78
  definitions,
@@ -123,7 +80,8 @@ const _getCompositionTreeRec = ({
123
80
  compositionTree,
124
81
  entityName,
125
82
  parentEntityName,
126
- resolveViews
83
+ resolveViews,
84
+ service
127
85
  }) => {
128
86
  compositionMap.set(parentEntityName, compositionTree)
129
87
  compositionTree.source = parentEntityName
@@ -135,14 +93,52 @@ const _getCompositionTreeRec = ({
135
93
  compositionTree.customBackLinks = compositionTree.customBackLinks || []
136
94
 
137
95
  const parentEntity = definitions[parentEntityName]
138
- const elements = Object.keys(parentEntity.elements).map(key => parentEntity.elements[key])
139
-
140
- for (const element of elements) {
141
- const el = resolveViews ? _resolvedElement(element) : element
142
- if (_navigationExistsInCompositionMap(el, compositionMap)) {
143
- _addNavigationToCompositionElements(el, definitions, compositionTree, compositionMap, ctUtils.isManaged(el))
144
- } else if (_isNonRecursiveNavigation(el, rootEntityName)) {
145
- const subObject = _createSubElement(el, definitions, parentEntityName)
96
+
97
+ for (const elementName in parentEntity.elements) {
98
+ const unresolvedEl = parentEntity.elements[elementName]
99
+ const element = resolveViews ? _resolvedElement(unresolvedEl, service) : unresolvedEl
100
+ if (!element.isAssociation) continue
101
+ if (_navigationExistsInCompositionMap(element, compositionMap)) {
102
+ const compositionElement = Object.assign({}, compositionMap.get(element.target), {
103
+ name: element.name,
104
+ target: parentEntityName,
105
+ links: [],
106
+ backLinks: [],
107
+ customBackLinks: []
108
+ })
109
+ if (!isSelfManaged(element)) {
110
+ const backLinks = _foreignKeysToLinks(element, true) || []
111
+ if (element.is2many) {
112
+ compositionElement.customBackLinks.push(...backLinks)
113
+ } else {
114
+ compositionElement.backLinks.push(...backLinks)
115
+ }
116
+ } else {
117
+ const targetEntity = definitions[element.target]
118
+ for (const backLinkName in targetEntity.elements) {
119
+ const _backLink = targetEntity.elements[backLinkName]
120
+ if (!_backLink._isAssociationEffective) continue
121
+ if (isBacklink(_backLink, definitions[compositionElement.target], false, element.name)) {
122
+ const backLinks = _foreignKeysToLinks(_backLink) || []
123
+ if (_isUnManaged(element)) {
124
+ compositionElement.customBackLinks.push(...backLinks)
125
+ } else {
126
+ compositionElement.backLinks.push(...backLinks)
127
+ }
128
+ }
129
+ }
130
+ }
131
+ compositionTree.compositionElements.push(compositionElement)
132
+ } else if (_isNonRecursiveNavigation(element, rootEntityName)) {
133
+ const subObject = _createSubElement(element, definitions)
134
+ if (!isSelfManaged(element)) {
135
+ const backLinks = _foreignKeysToLinks(element, true) || []
136
+ if (element.is2many) {
137
+ subObject.customBackLinks.push(...backLinks)
138
+ } else {
139
+ subObject.backLinks.push(...backLinks)
140
+ }
141
+ }
146
142
  compositionTree.compositionElements.push(subObject)
147
143
  _getCompositionTreeRec({
148
144
  rootEntityName,
@@ -150,15 +146,20 @@ const _getCompositionTreeRec = ({
150
146
  compositionMap,
151
147
  compositionTree: subObject,
152
148
  entityName: parentEntityName,
153
- parentEntityName: el.target
149
+ parentEntityName: element.target,
150
+ service
154
151
  })
155
152
  } else if (
156
- el._isAssociationEffective &&
157
- el.target === compositionTree.target &&
158
- isBacklink(el, el._target) &&
159
- compositionMap.has(el.target)
153
+ element._isAssociationEffective &&
154
+ isBacklink(element, definitions[compositionTree.target], false, compositionTree.name) &&
155
+ compositionMap.has(element.target)
160
156
  ) {
161
- _addBackLinksToCompositionTree(el, definitions, compositionTree)
157
+ const backLinks = _foreignKeysToLinks(element) || []
158
+ if (_isUnManaged(element)) {
159
+ compositionTree.customBackLinks.push(...backLinks)
160
+ } else {
161
+ compositionTree.backLinks.push(...backLinks)
162
+ }
162
163
  }
163
164
  }
164
165
  }
@@ -186,7 +187,7 @@ const _removeLocalizedTextsFromDraftTree = (compositionTree, definitions, checke
186
187
  }
187
188
  }
188
189
 
189
- const _getCompositionTree = ({ definitions, rootEntityName, checkRoot = true, resolveViews = false }) => {
190
+ const _getCompositionTree = ({ definitions, rootEntityName, checkRoot = true, resolveViews = false, service }) => {
190
191
  const rootName = resolveViews ? _resolvedEntityName(rootEntityName, definitions) : rootEntityName
191
192
 
192
193
  if (checkRoot && !isRootEntity(definitions, rootEntityName)) {
@@ -200,7 +201,8 @@ const _getCompositionTree = ({ definitions, rootEntityName, checkRoot = true, re
200
201
  compositionTree,
201
202
  entityName: rootName,
202
203
  parentEntityName: rootName,
203
- resolveViews
204
+ resolveViews,
205
+ service
204
206
  })
205
207
 
206
208
  if (definitions[rootEntityName]._isDraftEnabled) {
@@ -210,9 +212,34 @@ const _getCompositionTree = ({ definitions, rootEntityName, checkRoot = true, re
210
212
  return compositionTree
211
213
  }
212
214
 
215
+ const _cacheCompositionParentsOfOne = ({ definitions }) => {
216
+ for (const parentName in definitions) {
217
+ const parent = definitions[parentName]
218
+ if (!parent.kind === 'entity' || !parent.elements) continue
219
+ for (const elementName in parent.elements) {
220
+ const element = parent.elements[elementName]
221
+ if (element._isCompositionEffective && element.is2one && !isSelfManaged(element)) {
222
+ const targetName = element.target
223
+ const target = definitions[targetName]
224
+ if (!target) continue
225
+ const parentMap = (target.own('__oneCompositionParents') && target.__oneCompositionParents) || new Map()
226
+ const binding = parentMap.get(parentName) || {}
227
+ binding.elements = binding.elements || new Map()
228
+ const el = _createSubElement(element, definitions)
229
+ el.target = targetName
230
+ el.source = parentName
231
+ el.links = _foreignKeysToLinks(element)
232
+ binding.elements.set(element.name, el)
233
+ parentMap.set(parentName, binding)
234
+ target.set('__oneCompositionParents', parentMap)
235
+ }
236
+ }
237
+ }
238
+ }
239
+
213
240
  const _memoizeGetCompositionTree = fn => {
214
241
  const cache = new Map()
215
- return ({ definitions, rootEntityName, checkRoot = true, resolveViews = false }) => {
242
+ return ({ definitions, rootEntityName, checkRoot = true, resolveViews = false, service }) => {
216
243
  const key = [rootEntityName, checkRoot].join('#')
217
244
 
218
245
  // use ApplicationService as cache key for extensibility
@@ -222,8 +249,8 @@ const _memoizeGetCompositionTree = fn => {
222
249
  const map = cache.get(cacheKey)
223
250
  const cachedResult = map && map.get(key)
224
251
  if (cachedResult) return cachedResult
225
-
226
- const compTree = fn({ definitions, rootEntityName, checkRoot, resolveViews })
252
+ _cacheCompositionParentsOfOne({ definitions })
253
+ const compTree = fn({ definitions, rootEntityName, checkRoot, resolveViews, service })
227
254
 
228
255
  const _map = map || new Map()
229
256
  _map.set(key, compTree)
@@ -1,3 +1,5 @@
1
+ const cds = require('../../cds')
2
+
1
3
  const { getCompositionTree } = require('./tree')
2
4
  const { getDeepInsertCQNs } = require('./insert')
3
5
  const { getDeepDeleteCQNs } = require('./delete')
@@ -296,7 +298,8 @@ const getDeepUpdateCQNs = (definitions, cqn, selectData) => {
296
298
  definitions,
297
299
  rootEntityName: entityName,
298
300
  checkRoot: false,
299
- resolveViews: !draft
301
+ resolveViews: !draft,
302
+ service: cds.db
300
303
  })
301
304
 
302
305
  const subCQNs = _addSubDeepUpdateCQN({ definitions, compositionTree, data: [entry], selectData, cqns: [], draft })
@@ -9,9 +9,7 @@ const addDraftSuffix = (draft, name) => {
9
9
  const whereKey = key => {
10
10
  const where = []
11
11
  Object.keys(key).forEach(keyPart => {
12
- if (where.length > 0) {
13
- where.push('and')
14
- }
12
+ if (where.length > 0) where.push('and')
15
13
  where.push({ ref: [keyPart] }, '=', { val: key[keyPart] })
16
14
  })
17
15
  return where