@sap/cds 5.6.2 → 5.7.2

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 (183) hide show
  1. package/CHANGELOG.md +133 -0
  2. package/_i18n/i18n_fr.properties +4 -4
  3. package/apis/cds.d.ts +7 -10
  4. package/apis/connect.d.ts +3 -3
  5. package/apis/core.d.ts +2 -4
  6. package/apis/models.d.ts +2 -3
  7. package/apis/ql.d.ts +0 -1
  8. package/apis/services.d.ts +7 -3
  9. package/bin/build/buildTaskFactory.js +16 -10
  10. package/bin/build/buildTaskProviderFactory.js +3 -3
  11. package/bin/build/constants.js +2 -1
  12. package/bin/build/provider/buildTaskProviderInternal.js +14 -14
  13. package/bin/build/provider/hana/2migration.js +2 -3
  14. package/bin/build/provider/hana/index.js +34 -0
  15. package/bin/build/provider/hana/migrationtable.js +90 -22
  16. package/bin/build/provider/hana/template/undeploy.json +5 -0
  17. package/bin/build/provider/node-cf/index.js +9 -2
  18. package/bin/serve.js +16 -18
  19. package/lib/compile/cdsc.js +15 -5
  20. package/lib/compile/etc/_localized.js +4 -4
  21. package/lib/compile/extend.js +8 -0
  22. package/lib/compile/index.js +3 -1
  23. package/lib/compile/minify.js +61 -0
  24. package/lib/compile/resolve.js +4 -1
  25. package/lib/compile/to/gql.js +9 -0
  26. package/lib/compile/to/sql.js +26 -30
  27. package/lib/connect/index.js +1 -1
  28. package/lib/core/entities.js +0 -3
  29. package/lib/core/infer.js +1 -0
  30. package/lib/core/reflect.js +0 -34
  31. package/lib/deploy.js +25 -17
  32. package/lib/env/defaults.js +3 -1
  33. package/lib/env/index.js +13 -4
  34. package/lib/env/presets.js +38 -0
  35. package/lib/env/requires.js +16 -11
  36. package/lib/index.js +13 -11
  37. package/lib/log/format/kibana.js +4 -2
  38. package/lib/log/index.js +2 -2
  39. package/lib/ql/Whereable.js +1 -0
  40. package/lib/req/cds-context.js +79 -0
  41. package/lib/req/context.js +5 -77
  42. package/lib/req/request.js +1 -1
  43. package/lib/serve/Service-api.js +8 -4
  44. package/lib/serve/Service-dispatch.js +0 -7
  45. package/lib/serve/Service-methods.js +6 -8
  46. package/lib/serve/Transaction.js +35 -30
  47. package/lib/serve/adapters.js +1 -4
  48. package/lib/utils/axios.js +1 -1
  49. package/libx/_runtime/audit/Service.js +44 -20
  50. package/libx/_runtime/audit/generic/personal/access.js +16 -11
  51. package/libx/_runtime/audit/generic/personal/modification.js +5 -5
  52. package/libx/_runtime/audit/generic/personal/utils.js +46 -37
  53. package/libx/_runtime/{common/auth → auth}/index.js +21 -7
  54. package/libx/_runtime/{common/auth → auth}/strategies/JWT.js +2 -2
  55. package/libx/_runtime/{common/auth → auth}/strategies/basic.js +2 -2
  56. package/libx/_runtime/{common/auth → auth}/strategies/dummy.js +1 -1
  57. package/libx/_runtime/{common/auth → auth}/strategies/mock.js +2 -2
  58. package/libx/_runtime/{common/auth → auth}/strategies/utils/uaa.js +1 -1
  59. package/libx/_runtime/{common/auth → auth}/strategies/utils/xssec.js +0 -0
  60. package/libx/_runtime/{common/auth → auth}/strategies/xsuaa.js +2 -2
  61. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +7 -2
  62. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +0 -7
  63. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +0 -8
  64. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +3 -4
  65. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +6 -7
  66. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +3 -4
  67. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +2 -11
  68. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +16 -6
  69. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +26 -65
  70. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +0 -7
  71. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +3 -66
  72. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +26 -0
  73. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +5 -5
  74. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +2 -2
  75. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +13 -10
  76. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/ConditionalRequestControlCommand.js +0 -7
  77. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +0 -8
  78. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +54 -76
  79. package/libx/_runtime/cds-services/adapter/rest/RestRequest.js +0 -7
  80. package/libx/_runtime/cds-services/adapter/rest/handlers/create.js +3 -6
  81. package/libx/_runtime/cds-services/adapter/rest/handlers/delete.js +3 -6
  82. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +3 -6
  83. package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +3 -6
  84. package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +3 -6
  85. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +2 -2
  86. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +8 -4
  87. package/libx/_runtime/cds-services/services/Service.js +0 -6
  88. package/libx/_runtime/cds-services/services/utils/columns.js +10 -3
  89. package/libx/_runtime/cds-services/services/utils/compareJson.js +4 -7
  90. package/libx/_runtime/cds-services/services/utils/differ.js +4 -1
  91. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +1 -41
  92. package/libx/_runtime/cds-services/util/assert.js +1 -262
  93. package/libx/_runtime/cds.js +6 -9
  94. package/libx/_runtime/common/aspects/entity.js +1 -1
  95. package/libx/_runtime/common/composition/delete.js +4 -2
  96. package/libx/_runtime/common/composition/update.js +22 -35
  97. package/libx/_runtime/common/composition/utils.js +3 -7
  98. package/libx/_runtime/common/error/standardError.js +11 -0
  99. package/libx/_runtime/common/generic/auth.js +63 -33
  100. package/libx/_runtime/common/generic/crud.js +11 -23
  101. package/libx/_runtime/common/generic/input.js +20 -0
  102. package/libx/_runtime/common/generic/paging.js +2 -2
  103. package/libx/_runtime/common/generic/put.js +4 -10
  104. package/libx/_runtime/common/generic/sorting.js +12 -30
  105. package/libx/_runtime/common/perf/index.js +24 -0
  106. package/libx/_runtime/common/utils/cqn.js +58 -1
  107. package/libx/_runtime/common/utils/cqn2cqn4sql.js +297 -121
  108. package/libx/_runtime/common/utils/csn.js +38 -56
  109. package/libx/_runtime/common/utils/entityFromCqn.js +6 -6
  110. package/libx/_runtime/common/utils/resolveView.js +4 -5
  111. package/libx/_runtime/common/utils/rewriteAsterisks.js +46 -5
  112. package/libx/_runtime/common/utils/search2cqn4sql.js +21 -9
  113. package/libx/_runtime/common/utils/structured.js +35 -25
  114. package/libx/_runtime/db/Service.js +0 -6
  115. package/libx/_runtime/db/expand/expand-v2.js +130 -0
  116. package/libx/_runtime/db/expand/expandCQNToJoin.js +38 -52
  117. package/libx/_runtime/db/expand/index.js +3 -1
  118. package/libx/_runtime/db/generic/arrayed.js +3 -1
  119. package/libx/_runtime/db/generic/input.js +52 -10
  120. package/libx/_runtime/db/generic/integrity.js +367 -26
  121. package/libx/_runtime/db/generic/virtual.js +51 -13
  122. package/libx/_runtime/db/query/update.js +9 -3
  123. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +8 -9
  124. package/libx/_runtime/{common → db}/utils/propagateForeignKeys.js +11 -14
  125. package/libx/_runtime/fiori/generic/activate.js +1 -0
  126. package/libx/_runtime/fiori/generic/before.js +2 -1
  127. package/libx/_runtime/fiori/generic/edit.js +1 -0
  128. package/libx/_runtime/fiori/generic/patch.js +1 -1
  129. package/libx/_runtime/fiori/generic/read.js +155 -57
  130. package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +0 -4
  131. package/libx/_runtime/fiori/uiflex/index.js +1 -1
  132. package/libx/_runtime/fiori/uiflex/{extensibility/index.js → service.js} +6 -4
  133. package/libx/_runtime/fiori/utils/delete.js +7 -1
  134. package/libx/_runtime/hana/Service.js +1 -8
  135. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -14
  136. package/libx/_runtime/hana/execute.js +10 -4
  137. package/libx/_runtime/hana/pool.js +55 -45
  138. package/libx/_runtime/hana/search.js +7 -6
  139. package/libx/_runtime/hana/search2cqn4sql.js +8 -5
  140. package/libx/_runtime/hana/searchToContains.js +3 -1
  141. package/libx/_runtime/index.js +5 -5
  142. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +3 -3
  143. package/libx/_runtime/messaging/Outbox.js +53 -0
  144. package/libx/_runtime/messaging/common-utils/AMQPClient.js +17 -10
  145. package/libx/_runtime/messaging/common-utils/connections.js +14 -9
  146. package/libx/_runtime/messaging/common-utils/waitingTime.js +2 -0
  147. package/libx/_runtime/messaging/enterprise-messaging-shared.js +2 -3
  148. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -2
  149. package/libx/_runtime/messaging/enterprise-messaging.js +21 -15
  150. package/libx/_runtime/messaging/file-based.js +5 -5
  151. package/libx/_runtime/messaging/message-queuing.js +2 -3
  152. package/libx/_runtime/messaging/outbox/OutboxRunner.js +75 -0
  153. package/libx/_runtime/messaging/outbox/utils.js +192 -0
  154. package/libx/_runtime/messaging/service.js +16 -30
  155. package/libx/_runtime/remote/Service.js +15 -0
  156. package/libx/_runtime/remote/utils/client.js +15 -3
  157. package/libx/_runtime/remote/utils/{dataConversion.js → data.js} +12 -2
  158. package/libx/_runtime/sqlite/Service.js +7 -10
  159. package/libx/_runtime/sqlite/customBuilder/CustomExpressionBuilder.js +19 -0
  160. package/libx/_runtime/sqlite/execute.js +18 -12
  161. package/libx/_runtime/types/api.js +2 -1
  162. package/libx/gql/resolvers/parse/ast2cqn/columns.js +1 -1
  163. package/libx/odata/{odata2cqn/afterburner.js → afterburner.js} +28 -16
  164. package/libx/odata/{cqn2odata/index.js → cqn2odata.js} +1 -1
  165. package/libx/odata/{odata2cqn/grammar.pegjs → grammar.pegjs} +182 -118
  166. package/libx/odata/index.js +18 -15
  167. package/libx/odata/parser.js +1 -0
  168. package/libx/odata/utils.js +57 -0
  169. package/libx/rest/RestAdapter.js +2 -6
  170. package/libx/rest/utils/data.js +1 -6
  171. package/package.json +4 -3
  172. package/server.js +4 -5
  173. package/srv/audit-log.cds +87 -0
  174. package/{libx/_runtime/fiori/uiflex/extensibility/index.cds → srv/flex.cds} +0 -0
  175. package/srv/flex.js +1 -0
  176. package/srv/outbox.cds +11 -0
  177. package/srv/outbox.js +0 -0
  178. package/libx/_runtime/cds-services/adapter/perf/performance.js +0 -104
  179. package/libx/_runtime/cds-services/adapter/perf/performanceMeasurement.js +0 -33
  180. package/libx/odata/odata2cqn/index.js +0 -3
  181. package/libx/odata/odata2cqn/parser.js +0 -1
  182. package/libx/odata/readme.md +0 -1
  183. package/libx/odata/utils/index.js +0 -64
@@ -1,32 +1,375 @@
1
1
  const cds = require('../../cds')
2
2
  const { SELECT } = cds.ql
3
3
 
4
+ const crypto = require('crypto')
5
+
4
6
  const { cqn2cqn4sql } = require('../../common/utils/cqn2cqn4sql')
5
- const { getDependents } = require('../../common/utils/csn')
7
+ const { all, resolve } = require('../../common/utils/thenable')
8
+ const { assertError } = require('../../cds-services/util/assert')
9
+ const { processDeepAsync } = require('../../cds-services/util/dataProcessUtils')
10
+
11
+ const ASSERT_REFERENCE_INTEGRITY = 'ASSERT_REFERENCE_INTEGRITY'
12
+ const ASSERT_INTEGRITY_ANNOTATION = '@assert.integrity'
13
+
14
+ const CRUD = { CREATE: 1, READ: 1, UPDATE: 1, DELETE: 1 }
15
+ const C_UD = { CREATE: 1, INSERT: 1, UPDATE: 1, DELETE: 1 }
6
16
 
7
- const CRUD = {
8
- CREATE: 1,
9
- READ: 1,
10
- UPDATE: 1,
11
- DELETE: 1
17
+ /*
18
+ * UTILS
19
+ */
20
+
21
+ const _requiresRuntimeCheck = def => {
22
+ const val = String(def['@assert.integrity']).toLowerCase()
23
+ if (val === 'rt') return true
24
+ if (val === 'db' || val === 'false') return false
25
+ if (val === 'true') {
26
+ const { assert_integrity_type: ait } = cds.env.features
27
+ if (ait && ait.match(/rt/i)) return true
28
+ }
12
29
  }
13
30
 
14
- const _skipIntegrityCheck = (req, tx) => {
15
- // REVISIT: remove skipIntegrity after grace period
16
- if (
17
- (cds.env.features && cds.env.features.assert_integrity === false) ||
18
- (cds.env.runtime && cds.env.runtime.skipIntegrity)
19
- ) {
20
- return true
31
+ const _runtimeShallCheckIntegrityFor = (assoc, inclComps) => {
32
+ const { parent } = assoc
33
+
34
+ // disqualifications
35
+ if (!assoc._isAssociationStrict && !inclComps) return
36
+ if (!assoc.is2one) return
37
+ if (assoc.on) return
38
+ if (assoc._isCompositionBacklink) return
39
+ if (parent['@cds.persistence.skip']) return
40
+
41
+ // check @assert.integrity bottom-up and break on value (i.e., lowest spec wins)
42
+ if ('@assert.integrity' in assoc) {
43
+ const val = _requiresRuntimeCheck(assoc)
44
+ if (val !== undefined) return val
45
+ }
46
+ if ('@assert.integrity' in parent) {
47
+ const val = _requiresRuntimeCheck(parent)
48
+ if (val !== undefined) return val
49
+ }
50
+ if (parent._service && '@assert.integrity' in parent._service) {
51
+ const val = _requiresRuntimeCheck(parent._service)
52
+ if (val !== undefined) return val
21
53
  }
22
54
 
23
- if (!tx.model) return true
55
+ // here, no disqualification and no @assert.integrity specified -> global setting
56
+ const { assert_integrity: ai, assert_integrity_type: ait } = cds.env.features
57
+ if (typeof ai === 'string' && ai.match(/individual/i)) return false
58
+ if (ait && ait.match(/db/i)) return false
59
+ return true
60
+ }
61
+
62
+ /*
63
+ * this modifies the csn on purpose for caching effect!
64
+ * doing as aspect is difficult due to no global definitons per tenant
65
+ */
66
+ const _getDependents = (entity, model) => {
67
+ if (entity.own('__dependents')) return entity.__dependents
68
+
69
+ /** @type {Array|boolean} */
70
+ let dependents = []
71
+ for (const def of Object.values(model.definitions)) {
72
+ if (def.kind !== 'entity') continue
73
+ if (!def.associations) continue
74
+
75
+ for (const assoc of Object.values(def.associations)) {
76
+ if (assoc.target !== entity.name) continue
77
+
78
+ if (_runtimeShallCheckIntegrityFor(assoc)) {
79
+ dependents.push({ /* element: assoc, */ parent: assoc.parent, target: model.definitions[assoc.target] })
80
+ }
81
+ }
82
+ }
83
+
84
+ if (dependents.length === 0) dependents = false
85
+ return entity.set('__dependents', dependents)
86
+ }
87
+
88
+ const _getElement = (entity, ref) => {
89
+ if (ref.length > 1) return _getElement(entity.elements[ref[0]], ref.slice(1))
90
+ return entity.elements[ref[0]]
91
+ }
92
+
93
+ const _getFullForeignKeyName = (elementName, foreignKeyName) => `${elementName}_${foreignKeyName}`
94
+
95
+ const _getDataFromRef = (row, ref) => {
96
+ if (row === undefined) return
97
+ if (ref.length > 1) return _getDataFromRef(row[ref[0]], ref.slice(1))
98
+ return row[ref[0]]
99
+ }
24
100
 
101
+ const _foreignKeyReducer = (key, foreignKeyName, row, element, ref) => {
102
+ const fullForeignKeyName = _getFullForeignKeyName(element.name, foreignKeyName)
103
+
104
+ if (ref.length > 1) {
105
+ // ref includes assoc name, so we need to replace it by foreign key name
106
+ const refWithFlatForeignKey = [...ref.slice(0, ref.length - 1), fullForeignKeyName]
107
+ key[foreignKeyName] = _getDataFromRef(row, refWithFlatForeignKey)
108
+ } else {
109
+ key[foreignKeyName] = Object.prototype.hasOwnProperty.call(row, fullForeignKeyName) ? row[fullForeignKeyName] : null
110
+ }
111
+
112
+ return key
113
+ }
114
+
115
+ const _buildForeignKey = (element, row, ref) => {
116
+ let foreignKey
117
+
118
+ if (element.keys) {
119
+ foreignKey = element.keys
120
+ .map(obj => obj.ref[obj.ref.length - 1])
121
+ .reduce((key, foreignKeyName) => {
122
+ return _foreignKeyReducer(key, foreignKeyName, row, element, ref)
123
+ }, {})
124
+ }
125
+
126
+ return foreignKey
127
+ }
128
+
129
+ const _checkExists = (entity, data, req, run) => {
130
+ if (!Array.isArray(data)) {
131
+ return _checkExists(entity, [data], req, run).then(result => {
132
+ return result[0]
133
+ })
134
+ }
135
+
136
+ const where = data.map(row => {
137
+ return Object.keys(entity.keys).reduce((where, name) => {
138
+ if (row[name] !== undefined && row[name] !== null) {
139
+ if (where.length > 0) {
140
+ where.push('and')
141
+ }
142
+ where.push({ ref: [name] }, '=', { val: row[name] })
143
+ }
144
+
145
+ return where
146
+ }, [])
147
+ })
148
+ return _checkExistsWhere(entity, where, run)
149
+ }
150
+
151
+ const _checkCreateUpdate = (result, ref, rootEntity, checks, data, req, run) => {
152
+ const resolvedElement = _getElement(rootEntity, ref)
153
+
154
+ // REVISIT: incl comps?!
155
+ if (!_runtimeShallCheckIntegrityFor(resolvedElement /* true */)) return result
156
+
157
+ // REVISIT: reduce should only be used to build object from array, not for loops!
158
+ return data.reduce((result, row) => {
159
+ const foreignKey = _buildForeignKey(resolvedElement, row, ref)
160
+ if (foreignKey === undefined || Object.values(foreignKey).every(v => v === null)) return result
161
+
162
+ // REVISIT: if incl comps, skip check if target is in payload
163
+ // if (resolvedElement.isComposition && resolvedElement.is2one && row[resolvedElement.name]) return result
164
+
165
+ checks.push(
166
+ _checkExists(resolvedElement._target, foreignKey, req, run).then(exists => {
167
+ if (!exists) {
168
+ result.push(assertError(ASSERT_REFERENCE_INTEGRITY, resolvedElement, foreignKey))
169
+ }
170
+ })
171
+ )
172
+
173
+ return result
174
+ }, result)
175
+ }
176
+
177
+ const _buildWhereDelete = (result, key, element, data) => {
178
+ return data
179
+ .map(d => {
180
+ return Object.keys(d).reduce((result, name) => {
181
+ if (key.ref[0] === name) {
182
+ if (result.length > 0) {
183
+ result.push('and')
184
+ }
185
+ result.push({ ref: [_getFullForeignKeyName(element.name, key.ref[0])] }, '=', { val: d[name] })
186
+ }
187
+
188
+ return result
189
+ }, result)
190
+ })
191
+ .reduce((accumulatedWhere, currentWhere, i) => {
192
+ if (i > 0) accumulatedWhere.push('or')
193
+ accumulatedWhere.push(...currentWhere)
194
+ return accumulatedWhere
195
+ }, [])
196
+ }
197
+
198
+ const _checkExistsWhere = (entity, whereList, run) => {
199
+ const checks = whereList.map(where => {
200
+ if (where.length === 0) return true
201
+
202
+ const query = {
203
+ SELECT: {
204
+ columns: [{ val: 1, as: '_exists' }],
205
+ from: { ref: [entity.name || entity] },
206
+ where: where,
207
+ limit: { rows: { val: 1 } }
208
+ }
209
+ }
210
+
211
+ if (cds.context) {
212
+ const hash = crypto.createHash('sha1').update(JSON.stringify(query)).digest('base64') // fastest hash
213
+ if (!cds.context.__alreadyExecutedIntegrityChecks) cds.context.__alreadyExecutedIntegrityChecks = new Map()
214
+
215
+ if (cds.context.__alreadyExecutedIntegrityChecks.has(hash)) {
216
+ return cds.context.__alreadyExecutedIntegrityChecks.get(hash)
217
+ }
218
+
219
+ const promise = run(query).then(exists => exists.length !== 0)
220
+
221
+ // we store the promise object in the map, it won't get executed twice when calling await Promise.all([promise, promise])
222
+ cds.context.__alreadyExecutedIntegrityChecks.set(hash, promise)
223
+ return promise
224
+ }
225
+
226
+ return run(query).then(exists => exists.length !== 0)
227
+ })
228
+
229
+ return all(checks)
230
+ }
231
+
232
+ const _checkDelete = (result, key, entity, checks, req, csn, run, data) => {
233
+ const elements = csn.definitions[key].elements
234
+ const source = csn.definitions[key].name
235
+
236
+ const dependents = _getDependents(req.target, csn) || []
237
+
238
+ const sourceDependent = dependents.find(dep => dep.parent.name === source)
239
+ if (!sourceDependent) return result
240
+
241
+ return Object.keys(elements).reduce((result, assoc) => {
242
+ if (!elements[assoc].target || !elements[assoc].keys) return result
243
+
244
+ const targetDependent = dependents.find(dep => dep.target.name === elements[assoc].target)
245
+ if (!targetDependent) return result
246
+
247
+ const where = elements[assoc].keys.reduce((buildWhere, key) => {
248
+ return _buildWhereDelete(buildWhere, key, elements[assoc], data)
249
+ }, [])
250
+ checks.push(
251
+ _checkExistsWhere(source, [where], run).then(exists => {
252
+ if (exists.includes(true)) {
253
+ result.push(assertError(ASSERT_REFERENCE_INTEGRITY, elements[assoc], req.data))
254
+ }
255
+ })
256
+ )
257
+ return result
258
+ }, result)
259
+ }
260
+
261
+ function _filterStructured(element, structuredAssocs, prefix) {
262
+ const elements = element.elements
263
+ for (const subElement in elements) {
264
+ if (_filterAssocs(elements[subElement], structuredAssocs, prefix)) {
265
+ structuredAssocs.push([...prefix, elements[subElement].name])
266
+ }
267
+ }
268
+ }
269
+
270
+ const _filterAssocs = (element, structuredAssocs, prefix = []) => {
271
+ if (element.elements) {
272
+ _filterStructured(element, structuredAssocs, [...prefix, element.name])
273
+ }
274
+
275
+ return (
276
+ // element._isAssociationStrict && // > comps handled by deep crud logic
277
+ element.isAssociation &&
278
+ !element.virtual &&
279
+ !element.abstract &&
280
+ element[ASSERT_INTEGRITY_ANNOTATION] !== false &&
281
+ !element._target._hasPersistenceSkip
282
+ )
283
+ }
284
+
285
+ const _checkReferenceIntegrity = (entity, data, req, csn, run) => {
286
+ const service = entity._service
287
+ if (entity[ASSERT_INTEGRITY_ANNOTATION] === false || (service && service[ASSERT_INTEGRITY_ANNOTATION] === false)) {
288
+ return
289
+ }
290
+
291
+ if (!Array.isArray(data)) data = [data]
292
+
293
+ const checks = []
294
+ let result
295
+ if (req.event === 'CREATE' || req.event === 'UPDATE') {
296
+ const structuredAssocRefs = []
297
+ const associationRefs = Object.keys(entity.elements)
298
+ .filter(elementName => _filterAssocs(entity.elements[elementName], structuredAssocRefs))
299
+ .map(name => [name])
300
+ result = [...associationRefs, ...structuredAssocRefs].reduce((createUpdateResult, ref) => {
301
+ return _checkCreateUpdate(createUpdateResult, ref, entity, checks, data, req, run)
302
+ }, [])
303
+ }
304
+ if (req.event === 'DELETE') {
305
+ // we are only interested in table-level references not all derived ones on view levels
306
+ // TODO: why?
307
+ while (entity.query && entity.query._target) {
308
+ entity = csn.definitions[entity.query._target.name]
309
+ }
310
+
311
+ result = Object.keys(csn.definitions)
312
+ .filter(
313
+ key =>
314
+ !csn.definitions[key]['@cds.persistence.skip'] &&
315
+ csn.definitions[key].elements !== undefined &&
316
+ // skip check for events, aspects and localized tables
317
+ csn.definitions[key].kind !== 'event' &&
318
+ csn.definitions[key].kind !== 'aspect' &&
319
+ csn.definitions[key].kind !== 'type' &&
320
+ !csn.definitions[key].name.startsWith('localized.')
321
+ )
322
+ .reduce((deleteResult, key) => {
323
+ return _checkDelete(deleteResult, key, entity, checks, req, csn, run, data)
324
+ }, [])
325
+ }
326
+
327
+ if (checks.length) {
328
+ return Promise.all(checks).then(() => {
329
+ return result
330
+ })
331
+ }
332
+
333
+ return resolve(result || [])
334
+ }
335
+
336
+ const _checkIntegrityWrapper = (req, csn, run) => async (data, entity) => {
337
+ const errors = await _checkReferenceIntegrity(entity, data, req, csn, run)
338
+ if (errors && errors.length !== 0) for (const err of errors) req.error(err)
339
+ }
340
+
341
+ const _isUncheckableInsert = query => query.INSERT && (query.INSERT.rows || query.INSERT.values || query.INSERT.as)
342
+
343
+ const _checkIntegrityUtil = async (req, csn, run) => {
344
+ if (!run) return
345
+ if (typeof req.query === 'string' || req.target._unresolved) return
346
+ if (_isUncheckableInsert(req.query)) return
347
+
348
+ // REVISIT: integrity check needs context.data
349
+ if (Object.keys(req.data).length === 0) {
350
+ // REVISIT: We may need to double-check re req.data being undefined or empty
351
+ if (req.query.DELETE) {
352
+ req.data = req._beforeDeleteData || {}
353
+ } else if (req.context && req.context.data && Object.keys(req.context.data).length > 0) {
354
+ req.data = req.context.data
355
+ }
356
+ }
357
+ if (Object.keys(req.data).length === 0) return
358
+
359
+ await processDeepAsync(_checkIntegrityWrapper(req, csn, run), req.data, req.target, false, true)
360
+ }
361
+
362
+ /*
363
+ * HANDLERS
364
+ */
365
+
366
+ const _skipIntegrityCheck = (req, tx) => {
367
+ if (cds.env.features.assert_integrity === false) return true
368
+ if (!tx.model) return true
25
369
  if (req.event in CRUD) {
26
370
  if (typeof req.query === 'string') return true
27
371
  if (!req.target || req.target._unresolved) return true
28
372
  }
29
-
30
373
  return false
31
374
  }
32
375
 
@@ -45,18 +388,18 @@ async function beforeDelete(req) {
45
388
  if (!target) return
46
389
 
47
390
  // only if target has dependents (i.e., is the target of a managed to one association)
48
- const dependents = getDependents(target, this.model)
391
+ const dependents = _getDependents(target, this.model)
49
392
  if (!dependents) return
50
393
 
51
394
  const keys = Object.keys(target.keys).filter(k => _isPrimitiveKey(target.elements[k]) && k !== 'IsActiveEntity')
52
- let select = SELECT(keys).from(req.target.name)
395
+ let selectQuery = SELECT(keys).from(req.target.name)
396
+
53
397
  if (req.query.DELETE.where) {
54
- select = select.where(req.query.DELETE.where)
398
+ selectQuery = selectQuery.where(req.query.DELETE.where)
55
399
  }
56
400
 
57
- select = cqn2cqn4sql(select, this.model, { service: this })
58
-
59
- req._beforeDeleteData = await this._read(this.model, this.dbc, select, req.context || req)
401
+ selectQuery = cqn2cqn4sql(selectQuery, this.model, { service: this })
402
+ req._beforeDeleteData = await this._read(this.model, this.dbc, selectQuery, req.context || req)
60
403
  }
61
404
 
62
405
  beforeDelete._initial = true
@@ -64,9 +407,6 @@ beforeDelete._initial = true
64
407
  /*
65
408
  * perform check
66
409
  */
67
- const { checkIntegrityUtil } = require('../../cds-services/services/utils/handlerUtils')
68
- const C_UD = { CREATE: 1, INSERT: 1, UPDATE: 1, DELETE: 1 }
69
-
70
410
  const _performCheck = async (req, cur, csn, run) => {
71
411
  const prev = (cur.errors && cur.errors.length) || 0
72
412
 
@@ -74,11 +414,11 @@ const _performCheck = async (req, cur, csn, run) => {
74
414
  for (const each of cur.query) {
75
415
  const r = { query: each, target: each._target, event: each.cmd === 'INSERT' ? 'CREATE' : each.cmd }
76
416
  Object.setPrototypeOf(r, cur)
77
- await checkIntegrityUtil(r, csn, run)
417
+ await _checkIntegrityUtil(r, csn, run)
78
418
  if (r.errors && r.errors.length) r.errors.forEach(e => req.error(e))
79
419
  }
80
420
  } else {
81
- await checkIntegrityUtil(cur, csn, run)
421
+ await _checkIntegrityUtil(cur, csn, run)
82
422
  }
83
423
 
84
424
  // only additional errors
@@ -99,6 +439,7 @@ function performCheck(req) {
99
439
  if (Array.isArray(r.query)) return r.query.some(q => q.cmd in C_UD)
100
440
  return r.query && r.query.cmd in C_UD
101
441
  })
442
+
102
443
  if (relevant.length === 0) return
103
444
 
104
445
  return Promise.all(
@@ -3,11 +3,18 @@
3
3
  */
4
4
 
5
5
  const { ensureNoDraftsSuffix } = require('../../common/utils/draft')
6
+ const { getEntityNameFromCQN } = require('../../common/utils/entityFromCqn')
6
7
 
7
- const _convert = (columns, target, model) => {
8
+ const _getElement = (ref, target, alias) =>
9
+ alias
10
+ ? (ref[0] === alias && target.elements[ref[1]]) || target.elements[ref[0]]
11
+ : target.elements[ref[0]] || target.elements[ref[1]]
12
+
13
+ const _convert = (columns, target, model, alias) => {
8
14
  if (!target) return
9
15
  for (const col of columns) {
10
- const element = col.ref && target.elements[col.ref[col.ref.length - 1]]
16
+ if (!col.ref) continue
17
+ const element = _getElement(col.ref, target, alias)
11
18
  if (element) {
12
19
  if (element.virtual) {
13
20
  col.as = col.as || col.ref[col.ref.length - 1]
@@ -21,22 +28,53 @@ const _convert = (columns, target, model) => {
21
28
  }
22
29
  }
23
30
 
24
- const convertVirtuals = function (req, _model) {
25
- const model = this.model || _model
26
- // target.name ensures it is not a union or join
27
- if (typeof req.query === 'string' || !req.target || typeof req.target.name !== 'string' || !model) return
28
- const target = (!req.target._unresolved && req.target) || model.definitions[ensureNoDraftsSuffix(req.target.name)]
29
- const columns = (req.query && req.query.SELECT && req.query.SELECT.columns) || []
30
- _convert(columns, target, model)
31
- if (req.query.SELECT.from && req.query.SELECT.from.SET) {
32
- for (const arg of req.query.SELECT.from.SET.args) {
33
- const target = model.definitions[ensureNoDraftsSuffix(arg._target.name)]
31
+ const _convert4ref = (query, model) => {
32
+ const { entityName, alias } = getEntityNameFromCQN(query)
33
+ const columns = query.SELECT.columns || []
34
+ const target = entityName && model.definitions[ensureNoDraftsSuffix(entityName)]
35
+ if (target) _convert(columns, target, model, alias)
36
+ }
37
+
38
+ const _convert4union = (SELECT, model) => {
39
+ for (const arg of SELECT.from.SET.args) {
40
+ const { entityName, alias } = getEntityNameFromCQN(arg)
41
+ const target = entityName && model.definitions[ensureNoDraftsSuffix(entityName)]
42
+ if (target) {
34
43
  const columns = (arg.SELECT && arg.SELECT.columns) || []
35
- _convert(columns, target, model, true)
44
+ _convert(columns, target, model, alias)
45
+ }
46
+ }
47
+ }
48
+
49
+ const _convert4join = (SELECT, model) => {
50
+ const columns = SELECT.columns || []
51
+ for (const arg of SELECT.from.args) {
52
+ const { entityName, alias } = getEntityNameFromCQN(arg)
53
+ const target = entityName && model.definitions[ensureNoDraftsSuffix(entityName)]
54
+ if (target) {
55
+ _convert(columns, target, model, alias)
36
56
  }
37
57
  }
38
58
  }
39
59
 
60
+ // REVISIT: Refactor to have a generic 'columns processor'
61
+ const _convertViruals = (query, model) => {
62
+ if (!query.SELECT.from) return
63
+ if (query.SELECT.from.SET) return _convert4union(query.SELECT, model)
64
+ if (query.SELECT.from.args) return _convert4join(query.SELECT, model)
65
+ if (query.SELECT.from.SELECT) {
66
+ _convert4ref(query, model)
67
+ return _convertViruals(query.SELECT.from, model)
68
+ }
69
+ return _convert4ref(query, model)
70
+ }
71
+
72
+ const convertVirtuals = function (req, _model) {
73
+ const model = this.model || _model
74
+ if (!model) return
75
+ if (req.query.SELECT) _convertViruals(req.query, model)
76
+ }
77
+
40
78
  convertVirtuals._initial = true
41
79
 
42
80
  module.exports = {
@@ -26,11 +26,13 @@ const _getFilteredCqns = (cqns, model) => {
26
26
  let moreThanManaged = Object.keys(cqn.UPDATE.data).some(
27
27
  k => entity.elements[k]['@cds.on.update'] === undefined || !cqn.UPDATE.data[k].startsWith('$')
28
28
  )
29
+
29
30
  if (moreThanManaged) continue
30
31
 
31
32
  // REVISIT: remove feature flag update_header_item after grace period of at least two months (> April release)
32
33
  if (cds.env.features.update_header_item !== false) {
33
34
  const comps = Object.values(entity.associations || {}).filter(assoc => assoc._isCompositionEffective)
35
+
34
36
  for (const comp of comps) {
35
37
  if (_includesCompositionTarget(cqns, comp.target)) {
36
38
  moreThanManaged = true
@@ -38,6 +40,7 @@ const _getFilteredCqns = (cqns, model) => {
38
40
  }
39
41
  }
40
42
  }
43
+
41
44
  if (moreThanManaged) continue
42
45
 
43
46
  // remove current cqn
@@ -54,11 +57,12 @@ const update = (executeUpdateCQN, executeSelectCQN) => async (model, dbc, query,
54
57
  if (hasDeepUpdate(model && model.definitions, query)) {
55
58
  // REVISIT: _activeData gets set in case of draftActivate for performance, but this is a layer violation
56
59
  let selectData = req._ && req._.query && req._.query._activeData
57
- if (!selectData) {
60
+
61
+ if (selectData) {
62
+ selectData = [selectData]
63
+ } else {
58
64
  // REVISIT: avoid additional read
59
65
  selectData = await selectDeepUpdateData(model && model.definitions, query, req, false, false, cds.db)
60
- } else {
61
- selectData = [selectData]
62
66
  }
63
67
 
64
68
  let cqns = getDeepUpdateCQNs(model && model.definitions, query, selectData)
@@ -71,9 +75,11 @@ const update = (executeUpdateCQN, executeSelectCQN) => async (model, dbc, query,
71
75
  cqns = _getFilteredCqns(getFlatArray(cqns), model)
72
76
 
73
77
  if (cqns.length === 0) return 0
78
+
74
79
  const results = await processCQNs(executeUpdateCQN, cqns, model, dbc, user, locale, isoTs, chunks)
75
80
  // return number of affected rows of "root cqn", if an update, 1 otherwise (as not update of root but its children)
76
81
  if (cqns[0].UPDATE) return results[0]
82
+
77
83
  return 1
78
84
  }
79
85
 
@@ -87,15 +87,9 @@ class ExpressionBuilder extends BaseBuilder {
87
87
  }
88
88
 
89
89
  _isStructured(op1, comp, op2) {
90
- if (op1.ref && comp === '=' && op2.val && typeof op2.val === 'object') {
91
- return true
92
- }
90
+ if (op1.ref && comp === '=' && op2.val && typeof op2.val === 'object' && !(op2.val instanceof Buffer)) return true
93
91
  // also check reverse
94
- if (op1.val && typeof op1.val === 'object' && comp === '=' && op2.ref) {
95
- return true
96
- }
97
-
98
- return false
92
+ if (op1.val && typeof op1.val === 'object' && comp === '=' && op2.ref && !(op1.val instanceof Buffer)) return true
99
93
  }
100
94
 
101
95
  _expressionObjectsToSQL(objects) {
@@ -120,6 +114,7 @@ class ExpressionBuilder extends BaseBuilder {
120
114
  i += 3
121
115
  continue
122
116
  }
117
+
123
118
  this._expressionElementToSQL(objects[i])
124
119
  i++
125
120
  }
@@ -239,7 +234,7 @@ class ExpressionBuilder extends BaseBuilder {
239
234
  }
240
235
 
241
236
  if (Array.isArray(values.list)) {
242
- this._addToOutputObj(new this.ReferenceBuilder(reference, this._options, this._csn).build(), false)
237
+ this._expressionElementToSQL(reference)
243
238
  this._outputObj.sql.push(operator)
244
239
  this._addListToOutputObj(values.list)
245
240
  return true
@@ -283,6 +278,10 @@ class ExpressionBuilder extends BaseBuilder {
283
278
  )
284
279
  }
285
280
 
281
+ /**
282
+ * This method is overridden in sqlite Expression Builder in order to add the required "VALUES" keyword if needed
283
+ * Make sure to adapt the overridden function as well, if adapting this.
284
+ */
286
285
  _addListToOutputObj(list) {
287
286
  this._outputObj.sql.push('(')
288
287
 
@@ -1,5 +1,7 @@
1
1
  const cds = require('../../cds')
2
2
 
3
+ const _autoGenerate = e => e && e.type === 'cds.UUID' && e.key
4
+
3
5
  const _generateParentField = ({ parentElement }, row) => {
4
6
  if (_autoGenerate(parentElement) && !row[parentElement.name]) {
5
7
  row[parentElement.name] = cds.utils.uuid()
@@ -14,8 +16,6 @@ const _generateChildField = ({ deep, childElement }, childRow) => {
14
16
  }
15
17
  }
16
18
 
17
- const _autoGenerate = e => e && e.type === 'cds.UUID' && e.key
18
-
19
19
  const _getNestedVal = (row, prefix) => {
20
20
  let val = row
21
21
  const splitted = prefix.split('_')
@@ -52,12 +52,12 @@ const _propagateToParent = ({ parentElement, childElement, deep }, childRow, row
52
52
  if (deep) {
53
53
  _propagateToParent(deep.propagation, childRow[deep.targetName], childRow)
54
54
  }
55
- if (parentElement && childElement && childRow && Object.prototype.hasOwnProperty.call(childRow, childElement.name)) {
55
+ if (parentElement && childElement && childRow && childElement.name in childRow) {
56
56
  row[parentElement.name] = childRow[childElement.name]
57
57
  }
58
58
  }
59
59
 
60
- const propagateForeignKeys = (tKey, row, foreignKeyPropagations, isCompositionEffective) => {
60
+ module.exports = (tKey, row, foreignKeyPropagations, isCompositionEffective) => {
61
61
  const childRows = Array.isArray(row[tKey]) ? row[tKey] : [row[tKey]]
62
62
 
63
63
  for (const childRow of childRows) {
@@ -65,12 +65,13 @@ const propagateForeignKeys = (tKey, row, foreignKeyPropagations, isCompositionEf
65
65
 
66
66
  for (const foreignKeyPropagation of foreignKeyPropagations) {
67
67
  if (foreignKeyPropagation.fillChild) {
68
- _generateParentField(foreignKeyPropagation, row)
69
- if (!isCompositionEffective) {
70
- delete row[tKey]
71
- } else {
72
- _propagateToChid(foreignKeyPropagation, row, childRow)
73
- }
68
+ // propagate or generate in parent
69
+ const pk = foreignKeyPropagation.parentElement && foreignKeyPropagation.parentElement.name
70
+ if (pk && !(pk in row)) _propagateToParent(foreignKeyPropagation, childRow, row)
71
+ if (!(pk in row)) _generateParentField(foreignKeyPropagation, row)
72
+
73
+ if (!isCompositionEffective) delete row[tKey]
74
+ else _propagateToChid(foreignKeyPropagation, row, childRow)
74
75
  } else {
75
76
  _generateChildField(foreignKeyPropagation, childRow)
76
77
  _propagateToParent(foreignKeyPropagation, childRow, row)
@@ -78,7 +79,3 @@ const propagateForeignKeys = (tKey, row, foreignKeyPropagations, isCompositionEf
78
79
  }
79
80
  }
80
81
  }
81
-
82
- module.exports = {
83
- propagateForeignKeys
84
- }