@sap/cds 7.0.2 → 7.1.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 +60 -3
- package/_i18n/i18n_ar.properties +3 -0
- package/_i18n/i18n_cs.properties +4 -1
- package/_i18n/i18n_da.properties +3 -0
- package/_i18n/i18n_de.properties +3 -0
- package/_i18n/i18n_en.properties +3 -0
- package/_i18n/i18n_es.properties +3 -0
- package/_i18n/i18n_fi.properties +3 -0
- package/_i18n/i18n_fr.properties +3 -0
- package/_i18n/i18n_it.properties +3 -0
- package/_i18n/i18n_ja.properties +3 -0
- package/_i18n/i18n_ko.properties +3 -0
- package/_i18n/i18n_ms.properties +3 -0
- package/_i18n/i18n_nl.properties +3 -0
- package/_i18n/i18n_no.properties +3 -0
- package/_i18n/i18n_pl.properties +3 -0
- package/_i18n/i18n_pt.properties +3 -0
- package/_i18n/i18n_ro.properties +3 -0
- package/_i18n/i18n_ru.properties +3 -0
- package/_i18n/i18n_sv.properties +3 -0
- package/_i18n/i18n_th.properties +3 -0
- package/_i18n/i18n_zh_CN.properties +3 -0
- package/_i18n/i18n_zh_TW.properties +3 -0
- package/apis/core.d.ts +26 -30
- package/apis/cqn.d.ts +1 -0
- package/apis/ql.d.ts +2 -0
- package/apis/serve.d.ts +9 -0
- package/apis/services.d.ts +3 -2
- package/lib/compile/for/lean_drafts.js +22 -19
- package/lib/compile/to/srvinfo.js +7 -19
- package/lib/dbs/cds-deploy.js +11 -6
- package/lib/env/cds-env.js +3 -4
- package/lib/env/presets.js +14 -9
- package/lib/env/schemas/cds-package.json +3 -1
- package/lib/env/schemas/cds-rc.json +0 -4
- package/lib/linked/classes.js +112 -12
- package/lib/linked/entities.js +3 -0
- package/lib/linked/models.js +2 -1
- package/lib/ql/SELECT.js +1 -0
- package/lib/ql/Whereable.js +1 -0
- package/lib/srv/cds-serve.js +2 -1
- package/lib/srv/protocols/_legacy.js +7 -6
- package/lib/srv/protocols/index.js +30 -55
- package/lib/utils/tar.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/format/ResponseContentNegotiator.js +12 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/SerializerFactory.js +3 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/MetadataCache.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +10 -4
- package/libx/_runtime/cds-services/services/utils/columns.js +8 -2
- package/libx/_runtime/cds-services/services/utils/differ.js +1 -1
- package/libx/_runtime/common/composition/data.js +49 -29
- package/libx/_runtime/common/composition/update.js +0 -1
- package/libx/_runtime/common/composition/utils.js +1 -1
- package/libx/_runtime/common/generic/crud.js +1 -1
- package/libx/_runtime/common/generic/input.js +18 -13
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +1 -1
- package/libx/_runtime/common/utils/resolveView.js +115 -35
- package/libx/_runtime/common/utils/rewriteAsterisks.js +21 -0
- package/libx/_runtime/common/utils/search2cqn4sql.js +1 -1
- package/libx/_runtime/db/generic/rewrite.js +5 -4
- package/libx/_runtime/db/query/read.js +10 -9
- package/libx/_runtime/db/query/update.js +9 -18
- package/libx/_runtime/db/utils/deep.js +6 -5
- package/libx/_runtime/db/utils/normalizeTimeData.js +1 -1
- package/libx/_runtime/fiori/generic/activate.js +14 -19
- package/libx/_runtime/fiori/generic/edit.js +2 -5
- package/libx/_runtime/hana/streaming.js +3 -4
- package/libx/_runtime/remote/utils/client.js +9 -5
- package/libx/odata/afterburner.js +5 -2
- package/libx/rest/RestAdapter.js +2 -2
- package/libx/rest/middleware/error.js +4 -1
- package/libx/rest/middleware/operation.js +1 -1
- package/package.json +3 -3
- package/lib/srv/protocols/graphql.js +0 -30
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
const { getCompositionTree } = require('./tree')
|
|
2
2
|
const ctUtils = require('./utils')
|
|
3
3
|
const { getEntityNameFromUpdateCQN } = require('../utils/cqn')
|
|
4
|
-
|
|
5
4
|
const { ensureNoDraftsSuffix } = require('../utils/draft')
|
|
6
5
|
const { getDBTable } = require('../utils/resolveView')
|
|
7
6
|
const { cqn2cqn4sql } = require('../utils/cqn2cqn4sql')
|
|
8
7
|
const cds = require('../../cds')
|
|
8
|
+
const { DRAFT_COLUMNS_MAP } = require('../constants/draft')
|
|
9
9
|
const { SELECT } = cds.ql
|
|
10
10
|
|
|
11
|
-
const CHUNK_SIZE = cds.env.features.chunk_deep
|
|
11
|
+
const CHUNK_SIZE = cds.env.features.chunk_deep ?? Number.MAX_VALUE
|
|
12
12
|
|
|
13
13
|
/*
|
|
14
14
|
* own utils
|
|
@@ -21,9 +21,11 @@ const _isSameEntityInWhere = (where, target, persistentObj) => {
|
|
|
21
21
|
if (!res) return res
|
|
22
22
|
continue
|
|
23
23
|
}
|
|
24
|
+
|
|
24
25
|
if (!where[i] || !where[i].ref || !target.elements[where[i].ref]) {
|
|
25
26
|
continue
|
|
26
27
|
}
|
|
28
|
+
|
|
27
29
|
const key = where[i].ref
|
|
28
30
|
const val = where[i + 2].val
|
|
29
31
|
const sign = where[i + 1]
|
|
@@ -32,6 +34,7 @@ const _isSameEntityInWhere = (where, target, persistentObj) => {
|
|
|
32
34
|
return false
|
|
33
35
|
}
|
|
34
36
|
}
|
|
37
|
+
|
|
35
38
|
return true
|
|
36
39
|
}
|
|
37
40
|
|
|
@@ -40,27 +43,33 @@ const _isSameEntity = (cqn, req) => {
|
|
|
40
43
|
const persistentObj = Array.isArray(req._.partialPersistentState)
|
|
41
44
|
? req._.partialPersistentState[0]
|
|
42
45
|
: req._.partialPersistentState
|
|
46
|
+
|
|
43
47
|
if (!persistentObj) {
|
|
44
48
|
// If no data was found we don't know if it is the same entity
|
|
45
49
|
return false
|
|
46
50
|
}
|
|
51
|
+
|
|
47
52
|
const target = getDBTable(req.target)
|
|
48
53
|
if (target.name !== (cqn.UPDATE.entity.ref && cqn.UPDATE.entity.ref[0]) && target.name !== cqn.UPDATE.entity) {
|
|
49
54
|
return false
|
|
50
55
|
}
|
|
56
|
+
|
|
51
57
|
return _isSameEntityInWhere(where, target, persistentObj)
|
|
52
58
|
}
|
|
53
59
|
|
|
54
60
|
const _getLinksOfCompTree = compositionTree => {
|
|
55
61
|
const links = []
|
|
62
|
+
|
|
56
63
|
for (const link of [...compositionTree.backLinks, ...compositionTree.customBackLinks]) {
|
|
57
64
|
links.push(link.entityKey)
|
|
58
65
|
}
|
|
66
|
+
|
|
59
67
|
for (const compElement of compositionTree.compositionElements || []) {
|
|
60
68
|
for (const link of [...compElement.backLinks, ...compElement.customBackLinks]) {
|
|
61
69
|
links.push(link.targetKey)
|
|
62
70
|
}
|
|
63
71
|
}
|
|
72
|
+
|
|
64
73
|
return links
|
|
65
74
|
}
|
|
66
75
|
|
|
@@ -74,12 +83,14 @@ const _whereKeys = keySet => {
|
|
|
74
83
|
list: keys0.map(k => ({ val: row[k] }))
|
|
75
84
|
}))
|
|
76
85
|
}
|
|
86
|
+
|
|
77
87
|
return [keys, 'in', values]
|
|
78
88
|
}
|
|
79
89
|
|
|
80
90
|
const _parentKey = (element, key) => {
|
|
81
91
|
let links = [...element.customBackLinks, ...element.backLinks]
|
|
82
92
|
if (element.is2one && links.some(l => l.for2one)) links = links.filter(l => l.for2one)
|
|
93
|
+
|
|
83
94
|
return links.reduce((parentKey, customBackLink) => {
|
|
84
95
|
// TODO: why Object.prototype.hasOwnProperty?
|
|
85
96
|
parentKey[customBackLink.entityKey] = Object.prototype.hasOwnProperty.call(key, customBackLink.targetKey)
|
|
@@ -111,11 +122,7 @@ const _findWhere = (data, where) => {
|
|
|
111
122
|
})
|
|
112
123
|
}
|
|
113
124
|
|
|
114
|
-
const _keys = (entity, data) =>
|
|
115
|
-
return data.map(entry => {
|
|
116
|
-
return ctUtils.key(entity, entry)
|
|
117
|
-
})
|
|
118
|
-
}
|
|
125
|
+
const _keys = (entity, data) => data.map(entry => ctUtils.key(entity, entry))
|
|
119
126
|
|
|
120
127
|
const _parentKeys = (element, keys) => {
|
|
121
128
|
return keys.map(key => _parentKey(element, key)).filter(ele => Object.keys(ele).length)
|
|
@@ -143,12 +150,16 @@ const _getWhereObj = (row, links) => {
|
|
|
143
150
|
const _subWhere = (result, element) => {
|
|
144
151
|
let where
|
|
145
152
|
let links = [...element.backLinks, ...element.customBackLinks]
|
|
153
|
+
|
|
146
154
|
if (element.is2one && links.some(l => l.for2one)) links = links.filter(l => l.for2one)
|
|
155
|
+
|
|
147
156
|
if (result.length && links && links.length > 0) {
|
|
148
157
|
where = {}
|
|
149
158
|
const keys0 = Object.keys(_getWhereObj(result[0], links))
|
|
159
|
+
|
|
150
160
|
if (keys0.length) {
|
|
151
161
|
const keys = { list: keys0.map(pk => ({ ref: [pk] })) }
|
|
162
|
+
|
|
152
163
|
for (let i = 0; i < result.length; i += CHUNK_SIZE) {
|
|
153
164
|
const values = {
|
|
154
165
|
list: result.slice(i, i + CHUNK_SIZE).map(row => ({
|
|
@@ -161,39 +172,46 @@ const _subWhere = (result, element) => {
|
|
|
161
172
|
}
|
|
162
173
|
}
|
|
163
174
|
}
|
|
175
|
+
|
|
164
176
|
return where
|
|
165
177
|
}
|
|
166
178
|
|
|
167
179
|
const _mergeResults = (result, selectData, root, model, compositionTree, entityName) => {
|
|
168
180
|
if (root) {
|
|
169
181
|
return [...selectData, ...result]
|
|
170
|
-
} else {
|
|
171
|
-
const parent = model.definitions[compositionTree.target] || model.definitions[entityName]
|
|
172
|
-
const assoc = (parent && parent.elements[compositionTree.name]) || {}
|
|
173
|
-
return selectData.map(selectEntry => {
|
|
174
|
-
if (assoc.is2one) {
|
|
175
|
-
selectEntry[compositionTree.name] = selectEntry[compositionTree.name] || {}
|
|
176
|
-
} else if (assoc.is2many) {
|
|
177
|
-
selectEntry[compositionTree.name] = selectEntry[compositionTree.name] || []
|
|
178
|
-
}
|
|
179
|
-
const newData = _findWhere(result, _parentKey(compositionTree, selectEntry))
|
|
180
|
-
if (assoc.is2one) {
|
|
181
|
-
if (newData[0]) selectEntry[compositionTree.name] = Object.assign(selectEntry[compositionTree.name], newData[0])
|
|
182
|
-
else selectEntry[compositionTree.name] = null
|
|
183
|
-
} else if (assoc.is2many) {
|
|
184
|
-
selectEntry[compositionTree.name].push(...newData)
|
|
185
|
-
}
|
|
186
|
-
return selectEntry
|
|
187
|
-
})
|
|
188
182
|
}
|
|
183
|
+
|
|
184
|
+
const parent = model.definitions[compositionTree.target] || model.definitions[entityName]
|
|
185
|
+
const assoc = (parent && parent.elements[compositionTree.name]) || {}
|
|
186
|
+
|
|
187
|
+
return selectData.map(selectEntry => {
|
|
188
|
+
if (assoc.is2one) {
|
|
189
|
+
selectEntry[compositionTree.name] = selectEntry[compositionTree.name] || {}
|
|
190
|
+
} else if (assoc.is2many) {
|
|
191
|
+
selectEntry[compositionTree.name] = selectEntry[compositionTree.name] || []
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const newData = _findWhere(result, _parentKey(compositionTree, selectEntry))
|
|
195
|
+
if (assoc.is2one) {
|
|
196
|
+
if (newData[0]) selectEntry[compositionTree.name] = Object.assign(selectEntry[compositionTree.name], newData[0])
|
|
197
|
+
else selectEntry[compositionTree.name] = null
|
|
198
|
+
} else if (assoc.is2many) {
|
|
199
|
+
selectEntry[compositionTree.name].push(...newData)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return selectEntry
|
|
203
|
+
})
|
|
189
204
|
}
|
|
190
205
|
|
|
191
|
-
const _columns = (entity, data, compositionTree, selectAllColumns) => {
|
|
206
|
+
const _columns = (entity, data, compositionTree, selectAllColumns, draft) => {
|
|
192
207
|
const backLinkKeys = _getLinksOfCompTree(compositionTree)
|
|
193
208
|
const columns = []
|
|
209
|
+
|
|
194
210
|
for (const elementName in entity.elements) {
|
|
195
211
|
const element = entity.elements[elementName]
|
|
196
212
|
if (element.virtual || element.isAssociation) continue
|
|
213
|
+
if (!draft && elementName in DRAFT_COLUMNS_MAP) continue
|
|
214
|
+
|
|
197
215
|
if (
|
|
198
216
|
selectAllColumns ||
|
|
199
217
|
element.key ||
|
|
@@ -203,6 +221,7 @@ const _columns = (entity, data, compositionTree, selectAllColumns) => {
|
|
|
203
221
|
columns.push({ ref: [element.name] })
|
|
204
222
|
}
|
|
205
223
|
}
|
|
224
|
+
|
|
206
225
|
return columns
|
|
207
226
|
}
|
|
208
227
|
|
|
@@ -223,7 +242,7 @@ const _select = ({
|
|
|
223
242
|
const from = ctUtils.addDraftSuffix(draft, entity.name)
|
|
224
243
|
const selectCQN = SELECT.from(from)
|
|
225
244
|
if (alias) selectCQN.SELECT.from.as = alias
|
|
226
|
-
selectCQN.SELECT.columns = _columns(entity, data, compositionTree, selectAllColumns)
|
|
245
|
+
selectCQN.SELECT.columns = _columns(entity, data, compositionTree, selectAllColumns, draft)
|
|
227
246
|
if (where) selectCQN.SELECT.where = where
|
|
228
247
|
else if (parentKeys) selectCQN.SELECT.where = _whereKeys(parentKeys)
|
|
229
248
|
if (orderBy) selectCQN.SELECT.orderBy = orderBy
|
|
@@ -256,6 +275,7 @@ const _selectDeepUpdateData = async args => {
|
|
|
256
275
|
const selectCQN = _select(args)
|
|
257
276
|
result = await tx.run(selectCQN)
|
|
258
277
|
}
|
|
278
|
+
|
|
259
279
|
if (!result.length) return Promise.resolve(result)
|
|
260
280
|
|
|
261
281
|
const keys = _keys(model.definitions[entityName], result)
|
|
@@ -312,7 +332,7 @@ const selectDeepUpdateData = (service, model, req, selectAllColumns = false) =>
|
|
|
312
332
|
const where = sqlQuery.UPDATE.where || []
|
|
313
333
|
const entityName = ensureNoDraftsSuffix(from)
|
|
314
334
|
const draft = entityName !== from
|
|
315
|
-
const orderBy = req
|
|
335
|
+
const orderBy = req?.target?.query?.SELECT?.orderBy
|
|
316
336
|
_resolveOrderBy(orderBy, sqlQuery.UPDATE._transitions)
|
|
317
337
|
const data = Object.assign({}, sqlQuery.UPDATE.data || {}, query.UPDATE.with || {})
|
|
318
338
|
const compositionTree = getCompositionTree({
|
|
@@ -331,7 +351,7 @@ const selectDeepUpdateData = (service, model, req, selectAllColumns = false) =>
|
|
|
331
351
|
where,
|
|
332
352
|
orderBy,
|
|
333
353
|
draft,
|
|
334
|
-
singleton: req
|
|
354
|
+
singleton: req?.target?._isSingleton,
|
|
335
355
|
alias,
|
|
336
356
|
selectAllColumns,
|
|
337
357
|
root: true,
|
|
@@ -282,7 +282,6 @@ const getDeepUpdateCQNs = async (model, req, selectData) => {
|
|
|
282
282
|
if (!Array.isArray(selectData)) selectData = [selectData]
|
|
283
283
|
|
|
284
284
|
if (selectData.length === 0) return []
|
|
285
|
-
|
|
286
285
|
if (selectData.length > 1) throw getError('Deep update can only be performed on a single instance')
|
|
287
286
|
|
|
288
287
|
const cqns = []
|
|
@@ -22,7 +22,7 @@ const keyElements = entity => {
|
|
|
22
22
|
|
|
23
23
|
const key = (entity, data) => {
|
|
24
24
|
return keyElements(entity).reduce((result, element) => {
|
|
25
|
-
if (element.name === 'IsActiveEntity' && !Object.
|
|
25
|
+
if (element.name === 'IsActiveEntity' && !Object.hasOwn(data, element.name)) return result
|
|
26
26
|
result[element.name] = data[element.name]
|
|
27
27
|
return result
|
|
28
28
|
}, {})
|
|
@@ -74,6 +74,7 @@ exports.impl = cds.service.impl(function () {
|
|
|
74
74
|
const res = await pathExistsQuery
|
|
75
75
|
if (res.length === 0) req.reject(404)
|
|
76
76
|
}
|
|
77
|
+
|
|
77
78
|
if (result == null && req._etagValidationType === 'if-match') req.reject(412)
|
|
78
79
|
return result
|
|
79
80
|
}
|
|
@@ -92,7 +93,6 @@ exports.impl = cds.service.impl(function () {
|
|
|
92
93
|
|
|
93
94
|
// flag to trigger read after write in protocol adapter
|
|
94
95
|
req._.readAfterWrite = true
|
|
95
|
-
|
|
96
96
|
return req.data
|
|
97
97
|
})
|
|
98
98
|
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
const cds = require('../../cds')
|
|
11
11
|
const LOG = cds.log('app')
|
|
12
|
+
|
|
12
13
|
const { enrichDataWithKeysFromWhere } = require('../utils/keys')
|
|
13
14
|
const { DRAFT_COLUMNS_MAP } = require('../../common/constants/draft')
|
|
14
15
|
const propagateForeignKeys = require('../utils/propagateForeignKeys')
|
|
@@ -35,13 +36,6 @@ const _getSimpleCategory = category => {
|
|
|
35
36
|
return category
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
const _isDraftCoreComputed = (req, element, event) =>
|
|
39
|
-
element['@Core.Computed'] &&
|
|
40
|
-
cds.env.features.preserve_computed !== false &&
|
|
41
|
-
req._ &&
|
|
42
|
-
req._.event === 'draftActivate' &&
|
|
43
|
-
!((event === 'CREATE' && element['@cds.on.insert']) || element['@cds.on.update'])
|
|
44
|
-
|
|
45
39
|
const _preProcessAssertTarget = (assocInfo, assertMap) => {
|
|
46
40
|
const { element: assoc, row } = assocInfo
|
|
47
41
|
const assocTarget = assoc._target
|
|
@@ -95,6 +89,7 @@ const _preProcessAssertTarget = (assocInfo, assertMap) => {
|
|
|
95
89
|
})
|
|
96
90
|
}
|
|
97
91
|
|
|
92
|
+
// eslint-disable-next-line complexity
|
|
98
93
|
const _processCategory = (req, category, value, elementInfo, assertMap) => {
|
|
99
94
|
const { row, key, element, isRoot } = elementInfo
|
|
100
95
|
category = _getSimpleCategory(category)
|
|
@@ -112,12 +107,22 @@ const _processCategory = (req, category, value, elementInfo, assertMap) => {
|
|
|
112
107
|
|
|
113
108
|
const event = req.event
|
|
114
109
|
|
|
115
|
-
// remove readonly
|
|
116
|
-
if (category === 'readonly'
|
|
117
|
-
if
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
110
|
+
// remove readonly (can also be complex, so do first)
|
|
111
|
+
if (category === 'readonly') {
|
|
112
|
+
// preserve computed values if triggered by draftActivate and not managed
|
|
113
|
+
const managed = `@cds.on.${event === 'CREATE' ? 'insert' : 'update'}`
|
|
114
|
+
if (cds.env.features.preserve_computed !== false && req._?.event === 'draftActivate' && !element[managed]) return
|
|
115
|
+
|
|
116
|
+
// Always take over the values from active entities
|
|
117
|
+
if (cds.env.fiori?.lean_draft && req.context?.event === 'EDIT') return
|
|
118
|
+
|
|
119
|
+
delete row[key]
|
|
120
|
+
value.val = undefined
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// remove immutable (can also be complex, so do first)
|
|
125
|
+
if (category === 'immutable' && event === 'UPDATE') {
|
|
121
126
|
// Always take over the values from active entities
|
|
122
127
|
if (cds.env.fiori?.lean_draft && req.context?.event === 'EDIT') return
|
|
123
128
|
|
|
@@ -837,7 +837,7 @@ const _convertUpsert = (query, model) => {
|
|
|
837
837
|
Object.assign(upsert.UPSERT, { into: { ref: [resolvedIntoClause], as: query.UPSERT.into.as } })
|
|
838
838
|
|
|
839
839
|
const resolved = resolveView(upsert, model, cds.db)
|
|
840
|
-
// required for
|
|
840
|
+
// required for deploying of extensions, not used anywhere else except UpsertBuilder
|
|
841
841
|
resolved._target = resolved.UPSERT?._transitions?.[0].target || query._target
|
|
842
842
|
// resolved._target = query._target
|
|
843
843
|
return resolved
|