@sap/cds 5.7.5 → 5.8.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.
- package/CHANGELOG.md +72 -0
- package/app/fiori/routes.js +1 -1
- package/bin/deploy/to-hana/cfUtil.js +251 -138
- package/bin/deploy/to-hana/gitUtil.js +55 -0
- package/bin/deploy/to-hana/hana.js +92 -93
- package/bin/deploy/to-hana/hdiDeployUtil.js +42 -27
- package/bin/deploy/to-hana/index.js +14 -13
- package/bin/mtx/in-cds.js +1 -0
- package/bin/serve.js +1 -1
- package/bin/version.js +1 -0
- package/lib/compile/cdsc.js +0 -6
- package/lib/compile/resolve.js +1 -1
- package/lib/compile/to/srvinfo.js +1 -1
- package/lib/core/classes.js +21 -1
- package/lib/env/index.js +3 -2
- package/lib/env/requires.js +4 -0
- package/lib/i18n/localize.js +5 -8
- package/lib/index.js +1 -0
- package/lib/log/errors.js +1 -1
- package/lib/ql/SELECT.js +2 -2
- package/lib/req/cds-context.js +1 -1
- package/lib/req/context.js +1 -1
- package/lib/serve/Transaction.js +9 -5
- package/lib/serve/index.js +13 -21
- package/lib/utils/tests.js +90 -66
- package/libx/_runtime/audit/generic/personal/modification.js +0 -8
- package/libx/_runtime/auth/index.js +7 -6
- package/libx/_runtime/auth/strategies/dwc.js +43 -0
- package/libx/_runtime/auth/utils.js +24 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +11 -32
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +12 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +7 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +24 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +43 -38
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +11 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/boundToCQN.js +1 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/deleteToCQN.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/orderByToCQN.js +9 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +17 -30
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +12 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/AbstractEdmStructuredType.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriHelper.js +7 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriTokenizer.js +2 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +19 -47
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +4 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +7 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/CommandExecutor.js +0 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/ConditionalRequestControlCommand.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ContextURLFactory.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/TrustedResourceJsonSerializer.js +2 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +6 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +41 -17
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +1 -17
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +60 -18
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +7 -5
- package/libx/_runtime/cds-services/adapter/rest/Rest.js +22 -1
- package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +8 -3
- package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +3 -0
- package/libx/_runtime/cds-services/services/utils/columns.js +5 -1
- package/libx/_runtime/cds-services/services/utils/compareJson.js +15 -16
- package/libx/_runtime/cds-services/services/utils/differ.js +2 -8
- package/libx/_runtime/common/aspects/Association.js +16 -0
- package/libx/_runtime/common/composition/data.js +28 -37
- package/libx/_runtime/common/composition/delete.js +107 -58
- package/libx/_runtime/common/composition/index.js +2 -1
- package/libx/_runtime/common/composition/insert.js +13 -13
- package/libx/_runtime/common/composition/update.js +39 -34
- package/libx/_runtime/common/error/frontend.js +17 -2
- package/libx/_runtime/common/generic/auth.js +20 -85
- package/libx/_runtime/common/generic/crud.js +22 -1
- package/libx/_runtime/common/i18n/messages.properties +2 -1
- package/libx/_runtime/common/utils/cqn.js +2 -6
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +95 -122
- package/libx/_runtime/common/utils/csn.js +14 -3
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +18 -1
- package/libx/_runtime/common/utils/keys.js +2 -1
- package/libx/_runtime/common/utils/path.js +1 -1
- package/libx/_runtime/common/utils/resolveView.js +12 -4
- package/libx/_runtime/common/utils/rewriteAsterisks.js +27 -13
- package/libx/_runtime/common/utils/search2cqn4sql.js +11 -6
- package/libx/_runtime/common/utils/vcap.js +27 -10
- package/libx/_runtime/db/data-conversion/post-processing.js +20 -13
- package/libx/_runtime/db/expand/expand-v2.js +21 -12
- package/libx/_runtime/db/expand/expandCQNToJoin.js +8 -6
- package/libx/_runtime/db/expand/index.js +3 -0
- package/libx/_runtime/db/generic/create.js +0 -10
- package/libx/_runtime/db/generic/index.js +3 -0
- package/libx/_runtime/db/generic/read.js +2 -24
- package/libx/_runtime/db/generic/rewrite.js +1 -3
- package/libx/_runtime/db/generic/update.js +1 -1
- package/libx/_runtime/db/query/delete.js +10 -4
- package/libx/_runtime/db/query/insert.js +3 -3
- package/libx/_runtime/db/query/read.js +4 -1
- package/libx/_runtime/db/query/update.js +5 -5
- package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +9 -2
- package/libx/_runtime/db/sql-builder/FunctionBuilder.js +3 -0
- package/libx/_runtime/db/sql-builder/index.js +3 -0
- package/libx/_runtime/db/utils/columns.js +5 -2
- package/libx/_runtime/db/utils/deep.js +6 -8
- package/libx/_runtime/db/utils/generateAliases.js +56 -6
- package/libx/_runtime/fiori/generic/before.js +73 -49
- package/libx/_runtime/fiori/generic/edit.js +14 -18
- package/libx/_runtime/fiori/generic/patch.js +8 -11
- package/libx/_runtime/fiori/generic/read.js +19 -16
- package/libx/_runtime/fiori/generic/readOverDraft.js +1 -4
- package/libx/_runtime/hana/Service.js +1 -1
- package/libx/_runtime/hana/conversion.js +10 -0
- package/libx/_runtime/hana/execute.js +33 -16
- package/libx/_runtime/hana/search.js +3 -3
- package/libx/_runtime/hana/search2cqn4sql.js +22 -21
- package/libx/_runtime/hana/searchToContains.js +1 -1
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +0 -1
- package/libx/_runtime/messaging/enterprise-messaging.js +1 -0
- package/libx/_runtime/messaging/file-based.js +3 -1
- package/libx/_runtime/messaging/service.js +4 -1
- package/libx/_runtime/remote/utils/client.js +33 -20
- package/libx/_runtime/remote/utils/data.js +52 -11
- package/libx/_runtime/sqlite/Service.js +1 -1
- package/libx/_runtime/sqlite/conversion.js +10 -0
- package/libx/_runtime/types/api.js +2 -2
- package/libx/gql/resolvers/parse/ast/enrich.js +1 -0
- package/libx/odata/afterburner.js +29 -6
- package/libx/odata/cqn2odata.js +9 -0
- package/libx/odata/grammar.pegjs +49 -21
- package/libx/odata/index.js +2 -2
- package/libx/odata/parser.js +1 -1
- package/libx/odata/utils.js +2 -2
- package/libx/rest/RestAdapter.js +29 -1
- package/libx/rest/middleware/auth.js +1 -3
- package/libx/rest/middleware/parse.js +1 -0
- package/package.json +1 -1
- package/server.js +1 -1
- package/bin/deploy/to-hana/logger.js +0 -27
- package/bin/deploy/to-hana/runCommand.js +0 -113
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +0 -37
- package/libx/_runtime/common/utils/auth.js +0 -16
|
@@ -17,7 +17,10 @@ const { DRAFT_COLUMNS_UNION } = require('../../../common/constants/draft')
|
|
|
17
17
|
* @param [options.filterVirtual=false]
|
|
18
18
|
* @returns {Array<object>} - array of columns
|
|
19
19
|
*/
|
|
20
|
-
const getColumns = (
|
|
20
|
+
const getColumns = (
|
|
21
|
+
entity,
|
|
22
|
+
{ onlyNames = false, removeIgnore = false, filterDraft = true, filterVirtual = false, keysOnly = false }
|
|
23
|
+
) => {
|
|
21
24
|
const skipDraft = filterDraft && entity._isDraftEnabled
|
|
22
25
|
const columns = []
|
|
23
26
|
const elements = entity.elements
|
|
@@ -28,6 +31,7 @@ const getColumns = (entity, { onlyNames = false, removeIgnore = false, filterDra
|
|
|
28
31
|
if (filterVirtual && element.virtual) continue
|
|
29
32
|
if (removeIgnore && element['@cds.api.ignore']) continue
|
|
30
33
|
if (skipDraft && DRAFT_COLUMNS_UNION.includes(each)) continue
|
|
34
|
+
if (keysOnly && !element.key) continue
|
|
31
35
|
columns.push(onlyNames ? each : element)
|
|
32
36
|
}
|
|
33
37
|
|
|
@@ -285,30 +285,29 @@ const _mergeArrays = (entity, oldValue, newValue) => {
|
|
|
285
285
|
return merged
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
-
const mergeJsonDeep = (entity, oldValue,
|
|
288
|
+
const mergeJsonDeep = (entity, oldValue, value) => {
|
|
289
|
+
// REVISIT readAfterWrite -> commit -> postProcessing
|
|
290
|
+
// Detach result from req.data
|
|
291
|
+
const newValue = value ? Object.assign({}, value) : oldValue // if newValue === undefined
|
|
289
292
|
if (_isObject(oldValue) && _isObject(newValue)) {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
if (
|
|
299
|
-
else {
|
|
300
|
-
const target = entity && entity.elements[key] && entity.elements[key]._target
|
|
293
|
+
// append to newValue to keep an order of attributes as might be defined in custom handler
|
|
294
|
+
Object.keys(oldValue).forEach(key => {
|
|
295
|
+
if (!(key in newValue)) {
|
|
296
|
+
Object.assign(newValue, { [key]: oldValue[key] })
|
|
297
|
+
} else {
|
|
298
|
+
const target = entity && entity.elements[key] && entity.elements[key]._target
|
|
299
|
+
if (_isObject(newValue[key])) {
|
|
300
|
+
newValue[key] = mergeJsonDeep(target, oldValue[key], newValue[key])
|
|
301
|
+
} else if (Array.isArray(newValue[key])) {
|
|
301
302
|
if (target) {
|
|
302
|
-
|
|
303
|
+
newValue[key] = _mergeArrays(target, oldValue[key], newValue[key])
|
|
303
304
|
}
|
|
304
305
|
// Can't merge items without target
|
|
305
306
|
}
|
|
306
|
-
} else {
|
|
307
|
-
Object.assign(oldValue, { [key]: newValue[key] })
|
|
308
307
|
}
|
|
309
308
|
})
|
|
310
309
|
}
|
|
311
|
-
return
|
|
310
|
+
return newValue
|
|
312
311
|
}
|
|
313
312
|
|
|
314
313
|
// Signature similar to Object.assign(oldValue, newValue)
|
|
@@ -45,14 +45,8 @@ module.exports = class {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
async _addPartialPersistentState(req) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
req.query,
|
|
51
|
-
req,
|
|
52
|
-
true,
|
|
53
|
-
true,
|
|
54
|
-
this._srv
|
|
55
|
-
)
|
|
48
|
+
// REVISIT: cds.context.model?
|
|
49
|
+
const deepUpdateData = await selectDeepUpdateData(this._srv, this._srv.model, req, true)
|
|
56
50
|
req._.partialPersistentState = deepUpdateData
|
|
57
51
|
}
|
|
58
52
|
|
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
// global.cds is used on purpose here!
|
|
2
2
|
const cds = global.cds
|
|
3
3
|
|
|
4
|
+
// requesting logger without module on purpose!
|
|
5
|
+
const LOG = cds.log()
|
|
6
|
+
|
|
4
7
|
const ODATA_CONTAINED = '@odata.contained'
|
|
5
8
|
|
|
6
9
|
const { isSelfManaged, isBacklink, getAnchor, getBacklink } = require('./utils')
|
|
7
10
|
const { foreignKeyPropagations } = require('../utils/foreignKeyPropagations')
|
|
8
11
|
|
|
12
|
+
const _logDeprecationForODataContained = () => {
|
|
13
|
+
if (!cds._deprecationWarningForODataContained) {
|
|
14
|
+
LOG._warn &&
|
|
15
|
+
LOG.warn(
|
|
16
|
+
'Annotation "@odata.contained" is deprecated and will be removed in an upcoming release. Use compositions instead of associations.'
|
|
17
|
+
)
|
|
18
|
+
cds._deprecationWarningForODataContained = true
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
9
22
|
module.exports = class {
|
|
10
23
|
get _isAssociationStrict() {
|
|
11
24
|
return (
|
|
@@ -15,6 +28,7 @@ module.exports = class {
|
|
|
15
28
|
}
|
|
16
29
|
|
|
17
30
|
get _isAssociationEffective() {
|
|
31
|
+
this[ODATA_CONTAINED] && _logDeprecationForODataContained()
|
|
18
32
|
return (
|
|
19
33
|
this.own('__isAssociationEffective') ||
|
|
20
34
|
this.set(
|
|
@@ -25,6 +39,7 @@ module.exports = class {
|
|
|
25
39
|
}
|
|
26
40
|
|
|
27
41
|
get _isCompositionEffective() {
|
|
42
|
+
this[ODATA_CONTAINED] && _logDeprecationForODataContained()
|
|
28
43
|
return (
|
|
29
44
|
this.own('__isCompositionEffective') ||
|
|
30
45
|
this.set(
|
|
@@ -36,6 +51,7 @@ module.exports = class {
|
|
|
36
51
|
}
|
|
37
52
|
|
|
38
53
|
get _isContained() {
|
|
54
|
+
this[ODATA_CONTAINED] && _logDeprecationForODataContained()
|
|
39
55
|
return (
|
|
40
56
|
this.own('__isContained') ||
|
|
41
57
|
this.set(
|
|
@@ -120,26 +120,26 @@ const _subWhere = (result, element) => {
|
|
|
120
120
|
if (links && links.length > 0) {
|
|
121
121
|
where = []
|
|
122
122
|
for (const row of result) {
|
|
123
|
-
if (where.length > 0) {
|
|
124
|
-
where.push('or')
|
|
125
|
-
}
|
|
126
123
|
const whereObj = links.reduce((res, currentLink) => {
|
|
127
|
-
if (Object.prototype.hasOwnProperty.call(row, currentLink.targetKey))
|
|
124
|
+
if (Object.prototype.hasOwnProperty.call(row, currentLink.targetKey) && row[currentLink.targetKey] !== null)
|
|
128
125
|
res[currentLink.entityKey] = row[currentLink.targetKey]
|
|
129
126
|
return res
|
|
130
127
|
}, {})
|
|
131
128
|
const whereCQN = ctUtils.whereKey(whereObj)
|
|
132
|
-
if (whereCQN.length)
|
|
129
|
+
if (whereCQN.length) {
|
|
130
|
+
if (where.length > 0) where.push('or')
|
|
131
|
+
where.push('(', ...whereCQN, ')')
|
|
132
|
+
}
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
return where
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
const _mergeResults = (result, selectData, root,
|
|
138
|
+
const _mergeResults = (result, selectData, root, model, compositionTree, entityName) => {
|
|
139
139
|
if (root) {
|
|
140
140
|
return [...selectData, ...result]
|
|
141
141
|
} else {
|
|
142
|
-
const parent = definitions[compositionTree.target] || definitions[entityName]
|
|
142
|
+
const parent = model.definitions[compositionTree.target] || model.definitions[entityName]
|
|
143
143
|
const assoc = (parent && parent.elements[compositionTree.name]) || {}
|
|
144
144
|
return selectData.map(selectEntry => {
|
|
145
145
|
if (assoc.is2one) {
|
|
@@ -148,8 +148,9 @@ const _mergeResults = (result, selectData, root, definitions, compositionTree, e
|
|
|
148
148
|
selectEntry[compositionTree.name] = selectEntry[compositionTree.name] || []
|
|
149
149
|
}
|
|
150
150
|
const newData = _findWhere(result, _parentKey(compositionTree, selectEntry))
|
|
151
|
-
if (assoc.is2one
|
|
152
|
-
selectEntry[compositionTree.name] = Object.assign(selectEntry[compositionTree.name], newData[0])
|
|
151
|
+
if (assoc.is2one) {
|
|
152
|
+
if (newData[0]) selectEntry[compositionTree.name] = Object.assign(selectEntry[compositionTree.name], newData[0])
|
|
153
|
+
else selectEntry[compositionTree.name] = null
|
|
153
154
|
} else if (assoc.is2many) {
|
|
154
155
|
selectEntry[compositionTree.name].push(...newData)
|
|
155
156
|
}
|
|
@@ -158,14 +159,14 @@ const _mergeResults = (result, selectData, root, definitions, compositionTree, e
|
|
|
158
159
|
}
|
|
159
160
|
}
|
|
160
161
|
|
|
161
|
-
const _columns = (entity, data, compositionTree,
|
|
162
|
+
const _columns = (entity, data, compositionTree, selectAllColumns) => {
|
|
162
163
|
const backLinkKeys = _getLinksOfCompTree(compositionTree)
|
|
163
164
|
const columns = []
|
|
164
165
|
for (const elementName in entity.elements) {
|
|
165
166
|
const element = entity.elements[elementName]
|
|
166
167
|
if (element.virtual || element.isAssociation) continue
|
|
167
168
|
if (
|
|
168
|
-
|
|
169
|
+
selectAllColumns ||
|
|
169
170
|
element.key ||
|
|
170
171
|
backLinkKeys.includes(element.name) ||
|
|
171
172
|
(Array.isArray(data) && data.find(entry => element.name in entry))
|
|
@@ -177,45 +178,42 @@ const _columns = (entity, data, compositionTree, selectAll) => {
|
|
|
177
178
|
}
|
|
178
179
|
|
|
179
180
|
const _select = ({
|
|
180
|
-
|
|
181
|
+
model,
|
|
181
182
|
entityName,
|
|
182
183
|
draft,
|
|
183
184
|
alias,
|
|
184
185
|
compositionTree,
|
|
185
186
|
data,
|
|
186
|
-
|
|
187
|
-
includeAllRootColumns,
|
|
188
|
-
includeAllColumns,
|
|
187
|
+
selectAllColumns,
|
|
189
188
|
where,
|
|
190
189
|
parentKeys,
|
|
191
190
|
orderBy,
|
|
192
191
|
singleton
|
|
193
192
|
}) => {
|
|
194
|
-
const entity = definitions
|
|
193
|
+
const entity = model.definitions[entityName]
|
|
195
194
|
const from = ctUtils.addDraftSuffix(draft, entity.name)
|
|
196
195
|
const selectCQN = SELECT.from(from)
|
|
197
196
|
if (alias) selectCQN.SELECT.from.as = alias
|
|
198
|
-
|
|
199
|
-
selectCQN.SELECT.columns = _columns(entity, data, compositionTree, selectAll)
|
|
197
|
+
selectCQN.SELECT.columns = _columns(entity, data, compositionTree, selectAllColumns)
|
|
200
198
|
if (where) selectCQN.SELECT.where = where
|
|
201
199
|
else if (parentKeys) selectCQN.SELECT.where = _whereKeys(parentKeys)
|
|
202
200
|
if (orderBy) selectCQN.SELECT.orderBy = orderBy
|
|
203
201
|
if (singleton) selectCQN.SELECT.limit = { rows: { val: 1 } }
|
|
204
202
|
// REVISIT: remove once SELECT builder does flattening!
|
|
205
|
-
return cqn2cqn4sql(selectCQN,
|
|
203
|
+
return cqn2cqn4sql(selectCQN, model)
|
|
206
204
|
}
|
|
207
205
|
|
|
208
206
|
const _selectDeepUpdateData = async args => {
|
|
209
|
-
const {
|
|
207
|
+
const { model, compositionTree, entityName, data, root, selectData, tx, selectAllColumns } = args
|
|
210
208
|
const selectCQN = _select(args)
|
|
211
209
|
const result = await tx.run(selectCQN)
|
|
212
210
|
if (!result.length) return Promise.resolve(result)
|
|
213
211
|
|
|
214
|
-
const keys = _keys(definitions[entityName], result)
|
|
212
|
+
const keys = _keys(model.definitions[entityName], result)
|
|
215
213
|
await Promise.all(
|
|
216
214
|
compositionTree.compositionElements.map(element => {
|
|
217
215
|
if (element.skipPersistence) return Promise.resolve()
|
|
218
|
-
if (data !== undefined && !data.find(entry => element.name in entry) && !(
|
|
216
|
+
if (data !== undefined && !data.find(entry => element.name in entry) && !(selectAllColumns && result.length))
|
|
219
217
|
return Promise.resolve()
|
|
220
218
|
const subs = {
|
|
221
219
|
compositionTree: element,
|
|
@@ -235,23 +233,17 @@ const _selectDeepUpdateData = async args => {
|
|
|
235
233
|
})
|
|
236
234
|
)
|
|
237
235
|
|
|
238
|
-
return _mergeResults(result, selectData || [], root,
|
|
236
|
+
return _mergeResults(result, selectData || [], root, model, compositionTree, entityName)
|
|
239
237
|
}
|
|
240
238
|
|
|
241
239
|
/*
|
|
242
240
|
* exports
|
|
243
241
|
*/
|
|
244
242
|
|
|
245
|
-
const selectDeepUpdateData = (
|
|
246
|
-
|
|
247
|
-
cqn,
|
|
248
|
-
req,
|
|
249
|
-
includeAllRootColumns = false,
|
|
250
|
-
includeAllColumns = false,
|
|
251
|
-
service
|
|
252
|
-
) => {
|
|
243
|
+
const selectDeepUpdateData = (service, model, req, selectAllColumns = false) => {
|
|
244
|
+
const query = req.query
|
|
253
245
|
// REVISIT this should be done somewhere before, so it is not done twice for deep updates
|
|
254
|
-
const sqlQuery = cqn2cqn4sql(
|
|
246
|
+
const sqlQuery = cqn2cqn4sql(query, model)
|
|
255
247
|
|
|
256
248
|
if (req && _isSameEntity(sqlQuery, req)) {
|
|
257
249
|
return Promise.resolve(req._.partialPersistentState)
|
|
@@ -263,9 +255,9 @@ const selectDeepUpdateData = (
|
|
|
263
255
|
const entityName = ensureNoDraftsSuffix(from)
|
|
264
256
|
const draft = entityName !== from
|
|
265
257
|
const orderBy = req && req.target && req.target.query && req.target.query.SELECT && req.target.query.SELECT.orderBy
|
|
266
|
-
const data = Object.assign({}, sqlQuery.UPDATE.data || {},
|
|
258
|
+
const data = Object.assign({}, sqlQuery.UPDATE.data || {}, query.UPDATE.with || {})
|
|
267
259
|
const compositionTree = getCompositionTree({
|
|
268
|
-
definitions,
|
|
260
|
+
definitions: model.definitions,
|
|
269
261
|
rootEntityName: entityName, // REVISIT: drafts are resolved too eagerly
|
|
270
262
|
checkRoot: false,
|
|
271
263
|
resolveViews: !draft,
|
|
@@ -274,17 +266,16 @@ const selectDeepUpdateData = (
|
|
|
274
266
|
|
|
275
267
|
return _selectDeepUpdateData({
|
|
276
268
|
tx: cds.tx(req),
|
|
277
|
-
|
|
269
|
+
model,
|
|
278
270
|
compositionTree,
|
|
279
271
|
entityName,
|
|
280
272
|
data: [data],
|
|
281
273
|
where,
|
|
282
274
|
orderBy,
|
|
283
275
|
draft,
|
|
284
|
-
includeAllRootColumns,
|
|
285
276
|
singleton: req && req.target && req.target._isSingleton,
|
|
286
277
|
alias,
|
|
287
|
-
|
|
278
|
+
selectAllColumns,
|
|
288
279
|
root: true,
|
|
289
280
|
service
|
|
290
281
|
})
|
|
@@ -5,6 +5,9 @@ const ctUtils = require('./utils')
|
|
|
5
5
|
|
|
6
6
|
const { ensureNoDraftsSuffix } = require('../utils/draft')
|
|
7
7
|
const { getEntityNameFromDeleteCQN } = require('../utils/cqn')
|
|
8
|
+
const { cqn2cqn4sql } = require('../utils/cqn2cqn4sql')
|
|
9
|
+
const { getComp2oneParents } = require('../utils/csn')
|
|
10
|
+
const getColumns = require('../../db/utils/columns')
|
|
8
11
|
|
|
9
12
|
/*
|
|
10
13
|
* own utils
|
|
@@ -13,19 +16,21 @@ const { getEntityNameFromDeleteCQN } = require('../utils/cqn')
|
|
|
13
16
|
// Poor man's alias algorithm
|
|
14
17
|
// REVISIT: Extract and adapt the alias functionality from `expandCQNToJoin.js`: _adaptWhereOrderBy
|
|
15
18
|
const _recursivelyAliasRefs = (something, newAlias, oldAlias, subselect = false) => {
|
|
16
|
-
if (Array.isArray(something))
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
if (Array.isArray(something)) return something.map(s => _recursivelyAliasRefs(s, newAlias, oldAlias, subselect))
|
|
20
|
+
if (typeof something === 'object') {
|
|
21
|
+
if (something.ref) {
|
|
22
|
+
something = { ref: [...something.ref] }
|
|
20
23
|
if (oldAlias && something.ref[0] === oldAlias) something.ref[0] = newAlias
|
|
21
24
|
else if (!subselect) something.ref.unshift(newAlias)
|
|
22
25
|
} else {
|
|
26
|
+
something = Object.assign({}, something)
|
|
23
27
|
for (const key in something) {
|
|
24
|
-
if (key === 'from') continue // Workaround: Deep delete to be rewritten
|
|
25
|
-
_recursivelyAliasRefs(something[key], newAlias, oldAlias, subselect || key === 'SELECT')
|
|
28
|
+
if (key === 'from' || key === 'val') continue // Workaround: Deep delete to be rewritten
|
|
29
|
+
something[key] = _recursivelyAliasRefs(something[key], newAlias, oldAlias, subselect || key === 'SELECT')
|
|
26
30
|
}
|
|
27
31
|
}
|
|
28
32
|
}
|
|
33
|
+
return something
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
function _getSubWhereAndEntities(element, parentWhere, draft, level = 0, compositionTree = {}) {
|
|
@@ -88,7 +93,7 @@ function _getSubWhereAndEntities(element, parentWhere, draft, level = 0, composi
|
|
|
88
93
|
SELECT: {
|
|
89
94
|
columns: [{ val: 1, as: '_exists' }],
|
|
90
95
|
from: { ref: [entity2.entityName], as: entity2.alias },
|
|
91
|
-
where: parentWhere ? ['(', ...parentWhere, ')', 'and', '(', ...subWhere, ')'] : subWhere
|
|
96
|
+
where: parentWhere && parentWhere.length ? ['(', ...parentWhere, ')', 'and', '(', ...subWhere, ')'] : subWhere
|
|
92
97
|
}
|
|
93
98
|
})
|
|
94
99
|
|
|
@@ -130,18 +135,18 @@ function _getStaticWhere(allBackLinks, entity1) {
|
|
|
130
135
|
}, [])
|
|
131
136
|
}
|
|
132
137
|
|
|
133
|
-
const _is2oneComposition = (element,
|
|
134
|
-
const csnElement = element.target && definitions[element.target].elements[element.name]
|
|
138
|
+
const _is2oneComposition = (element, model) => {
|
|
139
|
+
const csnElement = element.target && model.definitions[element.target].elements[element.name]
|
|
135
140
|
return csnElement && csnElement.is2one && csnElement._isCompositionEffective
|
|
136
141
|
}
|
|
137
142
|
|
|
138
|
-
const _addToCQNs = (cqns, subCQN, element,
|
|
143
|
+
const _addToCQNs = (cqns, subCQN, element, model, level) => {
|
|
139
144
|
cqns[level] = cqns[level] || []
|
|
140
145
|
// Since `>2.5.2` compiler generates constraints for compositions of one like for annotations
|
|
141
146
|
// Thus only single 2one case (`$self`-managed composition) has DELETE CASCADE
|
|
142
147
|
// Here it's ignored to simplify i.e. handle all "2ones" in a same manner
|
|
143
148
|
// REVISIT: why is _db_foreign_key_constraints necessary?
|
|
144
|
-
if (!cds.env.features._db_foreign_key_constraints || _is2oneComposition(element,
|
|
149
|
+
if (!cds.env.features._db_foreign_key_constraints || _is2oneComposition(element, model)) {
|
|
145
150
|
cqns[level].push(subCQN)
|
|
146
151
|
}
|
|
147
152
|
}
|
|
@@ -150,15 +155,7 @@ const _addToCQNs = (cqns, subCQN, element, definitions, level) => {
|
|
|
150
155
|
const DEEP_DELETE_MAX_RECURSION_DEPTH =
|
|
151
156
|
(cds.env.features.recursion_depth && Number(cds.env.features.recursion_depth)) || 2
|
|
152
157
|
|
|
153
|
-
const _addSubCascadeDeleteCQN = (
|
|
154
|
-
definitions,
|
|
155
|
-
compositionTree,
|
|
156
|
-
parentWhere,
|
|
157
|
-
level,
|
|
158
|
-
cqns,
|
|
159
|
-
draft,
|
|
160
|
-
elementMap = new Map()
|
|
161
|
-
) => {
|
|
158
|
+
const _addSubCascadeDeleteCQN = (model, compositionTree, parentWhere, level, cqns, draft, elementMap = new Map()) => {
|
|
162
159
|
for (const element of compositionTree.compositionElements) {
|
|
163
160
|
if (element.skipPersistence) continue
|
|
164
161
|
|
|
@@ -176,13 +173,13 @@ const _addSubCascadeDeleteCQN = (
|
|
|
176
173
|
if (where.length) {
|
|
177
174
|
const subCQN = { DELETE: { from: { ref: [entity1.entityName], as: entity1.alias }, where: where } }
|
|
178
175
|
|
|
179
|
-
_addToCQNs(cqns, subCQN, element,
|
|
176
|
+
_addToCQNs(cqns, subCQN, element, model, level)
|
|
180
177
|
|
|
181
178
|
// Make a copy and do not share the same map among brother compositions
|
|
182
179
|
// as we're only interested in deep recursions, not wide recursions.
|
|
183
180
|
const newElementMap = new Map(elementMap)
|
|
184
181
|
newElementMap.set(fqn, (seen && seen + 1) || 1)
|
|
185
|
-
_addSubCascadeDeleteCQN(
|
|
182
|
+
_addSubCascadeDeleteCQN(model, element, subCQN.DELETE.where, level + 1, cqns, draft, newElementMap)
|
|
186
183
|
}
|
|
187
184
|
}
|
|
188
185
|
|
|
@@ -193,7 +190,7 @@ const _addSubCascadeDeleteCQN = (
|
|
|
193
190
|
* exports
|
|
194
191
|
*/
|
|
195
192
|
|
|
196
|
-
const hasDeepDelete = (
|
|
193
|
+
const hasDeepDelete = (model, cqn) => {
|
|
197
194
|
const from = getEntityNameFromDeleteCQN(cqn)
|
|
198
195
|
if (!from) return false
|
|
199
196
|
|
|
@@ -201,66 +198,118 @@ const hasDeepDelete = (definitions, cqn) => {
|
|
|
201
198
|
// Hence, we do not need a deep delete in that case.
|
|
202
199
|
if (cqn._suppressDeepDelete) return false
|
|
203
200
|
|
|
204
|
-
const entity = definitions
|
|
201
|
+
const entity = model.definitions[ensureNoDraftsSuffix(from)]
|
|
205
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
|
|
|
211
|
-
const
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
208
|
+
const resolveNavigationTarget = (cqn, ref, model) => {
|
|
209
|
+
const lastTransition = cqn.UPDATE._transitions && cqn.UPDATE._transitions[cqn.UPDATE._transitions.length - 1]
|
|
210
|
+
const target = lastTransition
|
|
211
|
+
? lastTransition.target
|
|
212
|
+
: model.definitions[(cqn.UPDATE.entity.ref && cqn.UPDATE.entity.ref[0]) || cqn.UPDATE.entity]
|
|
213
|
+
let elementName = ref[ref.length - 1].id || ref[ref.length - 1]
|
|
214
|
+
const resolved = lastTransition && lastTransition.mapping && lastTransition.mapping.get(elementName)
|
|
215
|
+
if (resolved) elementName = resolved.ref[0]
|
|
216
|
+
return { target, elementName }
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// eslint-disable-next-line complexity
|
|
220
|
+
const getSetNullParentForeignKeyCQNs = async (model, req, dbQuery) => {
|
|
221
|
+
const cqns = []
|
|
222
|
+
const query = dbQuery || req.query
|
|
223
|
+
const origQuery = (req._tx instanceof cds.DatabaseService && req._ && req._.query) || req.query
|
|
224
|
+
if (!dbQuery && origQuery && origQuery.DELETE && origQuery.DELETE.from.ref && origQuery.DELETE.from.ref.length > 1) {
|
|
225
|
+
// delete via 2one navigation => parent is known => no need to SELECT
|
|
226
|
+
const ref = origQuery.DELETE.from.ref
|
|
227
|
+
const cqn = cqn2cqn4sql(UPDATE.entity({ ref: ref.slice(0, ref.length - 1) }), model)
|
|
228
|
+
const { target: parent, elementName } = resolveNavigationTarget(cqn, ref, model)
|
|
229
|
+
const element = parent.elements[elementName]
|
|
230
|
+
if (element && element._isCompositionEffective && element.is2one) {
|
|
231
|
+
const onCond = element && parent._relations[element.name].join('$$whatever', '$$parent')
|
|
232
|
+
const data = onCond.reduce((res, e) => {
|
|
233
|
+
if (!e.ref || e.ref[0] !== '$$parent') return res
|
|
234
|
+
const fk = e.ref.slice(1).join('_')
|
|
235
|
+
if (!parent.keys[fk]) res[fk] = null
|
|
236
|
+
return res
|
|
218
237
|
}, {})
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
238
|
+
cqn.data(data)
|
|
239
|
+
cqn.__4delete = true
|
|
240
|
+
if (Object.keys(data).length) cqns.push(cqn)
|
|
241
|
+
}
|
|
242
|
+
} else if (query.DELETE.where && query.DELETE.where.length) {
|
|
243
|
+
// direct or internal request => parent is uknown => need to SELECT
|
|
244
|
+
const entityName = getEntityNameFromDeleteCQN(query)
|
|
245
|
+
const target = model.definitions[entityName]
|
|
246
|
+
const comp2oneParents = getComp2oneParents(target, model)
|
|
247
|
+
for (const element of comp2oneParents) {
|
|
248
|
+
const parent = element.parent
|
|
249
|
+
const onCond = parent._relations[element.name].join('$$child', '$$parent')
|
|
250
|
+
const data = onCond.reduce((res, e) => {
|
|
251
|
+
if (!e.ref || e.ref[0] !== '$$parent') return res
|
|
252
|
+
const fk = e.ref.slice(1).join('_')
|
|
253
|
+
if (!parent.keys[fk]) res[fk] = null
|
|
254
|
+
return res
|
|
255
|
+
}, {})
|
|
256
|
+
const columns = getColumns(parent, { _4db: true, onlyKeys: true }).map(c => ({ ref: ['$$parent', c.name] }))
|
|
257
|
+
const as = query.DELETE.from.as || (query.DELETE.from.ref && query.DELETE.from.ref[0]) || query.DELETE.from
|
|
258
|
+
const selectCQN = SELECT.from(`${parent.name} as $$parent`)
|
|
259
|
+
.join(`${element.target} as $$child`)
|
|
260
|
+
.on(onCond)
|
|
261
|
+
.columns(columns)
|
|
262
|
+
.where(_recursivelyAliasRefs(query.DELETE.where, '$$child', as))
|
|
263
|
+
const results = await cds.tx(req).run(selectCQN)
|
|
264
|
+
if (results.length && Object.keys(data).length) {
|
|
265
|
+
const cqn = UPDATE.entity(parent).data(data)
|
|
266
|
+
for (const result of results) {
|
|
267
|
+
const where = []
|
|
268
|
+
for (const col of columns) {
|
|
269
|
+
if (where.length) where.push('and')
|
|
270
|
+
const ref = col.ref.slice(1)
|
|
271
|
+
where.push({ ref }, '=', { val: result[ref[0]] })
|
|
227
272
|
}
|
|
228
|
-
|
|
273
|
+
cqn.UPDATE.where = where
|
|
274
|
+
cqn.__4delete = true
|
|
275
|
+
cqns.push(cqn)
|
|
276
|
+
}
|
|
229
277
|
}
|
|
230
278
|
}
|
|
231
279
|
}
|
|
232
|
-
return
|
|
280
|
+
return cqns
|
|
233
281
|
}
|
|
234
282
|
|
|
235
|
-
const getDeepDeleteCQNs = (
|
|
236
|
-
const
|
|
237
|
-
|
|
283
|
+
const getDeepDeleteCQNs = async (model, req, dbQuery) => {
|
|
284
|
+
const query = dbQuery || req.query
|
|
285
|
+
const from = getEntityNameFromDeleteCQN(query)
|
|
286
|
+
if (!from) return [[query]]
|
|
238
287
|
|
|
239
288
|
const entityName = ensureNoDraftsSuffix(from)
|
|
240
289
|
// REVISIT: baaad check!
|
|
241
290
|
const draft = entityName !== from
|
|
242
291
|
const compositionTree = getCompositionTree({
|
|
243
|
-
definitions,
|
|
292
|
+
definitions: model.definitions,
|
|
244
293
|
rootEntityName: entityName,
|
|
245
294
|
checkRoot: false,
|
|
246
295
|
resolveViews: !draft,
|
|
247
296
|
service: cds.db
|
|
248
297
|
})
|
|
249
|
-
const parentWhere =
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const setNullUpdates =
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
return [[cqn], ...subCascadeDeletes, ...setNullUpdates].reverse()
|
|
298
|
+
const parentWhere = _recursivelyAliasRefs(
|
|
299
|
+
query.DELETE.where,
|
|
300
|
+
'ALIAS0',
|
|
301
|
+
query.DELETE.from.as || (query.DELETE.from.ref && query.DELETE.from.ref[0]) || query.DELETE.from
|
|
302
|
+
)
|
|
303
|
+
const setNullUpdates =
|
|
304
|
+
(model.definitions[entityName].own('__oneCompositionParents') &&
|
|
305
|
+
(await getSetNullParentForeignKeyCQNs(model, req, dbQuery))) ||
|
|
306
|
+
[]
|
|
307
|
+
const subCascadeDeletes = _addSubCascadeDeleteCQN(model, compositionTree, parentWhere, 0, [], draft)
|
|
308
|
+
return [[query], ...subCascadeDeletes, setNullUpdates].reverse()
|
|
261
309
|
}
|
|
262
310
|
|
|
263
311
|
module.exports = {
|
|
264
312
|
hasDeepDelete,
|
|
265
|
-
getDeepDeleteCQNs
|
|
313
|
+
getDeepDeleteCQNs,
|
|
314
|
+
getSetNullParentForeignKeyCQNs
|
|
266
315
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const { getCompositionTree, getCompositionRoot } = require('./tree')
|
|
2
2
|
const { hasDeepInsert, getDeepInsertCQNs, cleanEmptyCompositionsOfMany } = require('./insert')
|
|
3
3
|
const { hasDeepUpdate, getDeepUpdateCQNs } = require('./update')
|
|
4
|
-
const { hasDeepDelete, getDeepDeleteCQNs } = require('./delete')
|
|
4
|
+
const { hasDeepDelete, getDeepDeleteCQNs, getSetNullParentForeignKeyCQNs } = require('./delete')
|
|
5
5
|
const { selectDeepUpdateData } = require('./data')
|
|
6
6
|
|
|
7
7
|
module.exports = {
|
|
@@ -18,6 +18,7 @@ module.exports = {
|
|
|
18
18
|
// delete
|
|
19
19
|
hasDeepDelete,
|
|
20
20
|
getDeepDeleteCQNs,
|
|
21
|
+
getSetNullParentForeignKeyCQNs,
|
|
21
22
|
// data
|
|
22
23
|
selectDeepUpdateData
|
|
23
24
|
}
|