@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.
Files changed (77) hide show
  1. package/CHANGELOG.md +60 -3
  2. package/_i18n/i18n_ar.properties +3 -0
  3. package/_i18n/i18n_cs.properties +4 -1
  4. package/_i18n/i18n_da.properties +3 -0
  5. package/_i18n/i18n_de.properties +3 -0
  6. package/_i18n/i18n_en.properties +3 -0
  7. package/_i18n/i18n_es.properties +3 -0
  8. package/_i18n/i18n_fi.properties +3 -0
  9. package/_i18n/i18n_fr.properties +3 -0
  10. package/_i18n/i18n_it.properties +3 -0
  11. package/_i18n/i18n_ja.properties +3 -0
  12. package/_i18n/i18n_ko.properties +3 -0
  13. package/_i18n/i18n_ms.properties +3 -0
  14. package/_i18n/i18n_nl.properties +3 -0
  15. package/_i18n/i18n_no.properties +3 -0
  16. package/_i18n/i18n_pl.properties +3 -0
  17. package/_i18n/i18n_pt.properties +3 -0
  18. package/_i18n/i18n_ro.properties +3 -0
  19. package/_i18n/i18n_ru.properties +3 -0
  20. package/_i18n/i18n_sv.properties +3 -0
  21. package/_i18n/i18n_th.properties +3 -0
  22. package/_i18n/i18n_zh_CN.properties +3 -0
  23. package/_i18n/i18n_zh_TW.properties +3 -0
  24. package/apis/core.d.ts +26 -30
  25. package/apis/cqn.d.ts +1 -0
  26. package/apis/ql.d.ts +2 -0
  27. package/apis/serve.d.ts +9 -0
  28. package/apis/services.d.ts +3 -2
  29. package/lib/compile/for/lean_drafts.js +22 -19
  30. package/lib/compile/to/srvinfo.js +7 -19
  31. package/lib/dbs/cds-deploy.js +11 -6
  32. package/lib/env/cds-env.js +3 -4
  33. package/lib/env/presets.js +14 -9
  34. package/lib/env/schemas/cds-package.json +3 -1
  35. package/lib/env/schemas/cds-rc.json +0 -4
  36. package/lib/linked/classes.js +112 -12
  37. package/lib/linked/entities.js +3 -0
  38. package/lib/linked/models.js +2 -1
  39. package/lib/ql/SELECT.js +1 -0
  40. package/lib/ql/Whereable.js +1 -0
  41. package/lib/srv/cds-serve.js +2 -1
  42. package/lib/srv/protocols/_legacy.js +7 -6
  43. package/lib/srv/protocols/index.js +30 -55
  44. package/lib/utils/tar.js +2 -2
  45. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +2 -1
  46. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -4
  47. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/format/ResponseContentNegotiator.js +12 -0
  48. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/SerializerFactory.js +3 -1
  49. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/MetadataCache.js +1 -1
  50. package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +1 -4
  51. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +10 -4
  52. package/libx/_runtime/cds-services/services/utils/columns.js +8 -2
  53. package/libx/_runtime/cds-services/services/utils/differ.js +1 -1
  54. package/libx/_runtime/common/composition/data.js +49 -29
  55. package/libx/_runtime/common/composition/update.js +0 -1
  56. package/libx/_runtime/common/composition/utils.js +1 -1
  57. package/libx/_runtime/common/generic/crud.js +1 -1
  58. package/libx/_runtime/common/generic/input.js +18 -13
  59. package/libx/_runtime/common/utils/cqn2cqn4sql.js +1 -1
  60. package/libx/_runtime/common/utils/resolveView.js +115 -35
  61. package/libx/_runtime/common/utils/rewriteAsterisks.js +21 -0
  62. package/libx/_runtime/common/utils/search2cqn4sql.js +1 -1
  63. package/libx/_runtime/db/generic/rewrite.js +5 -4
  64. package/libx/_runtime/db/query/read.js +10 -9
  65. package/libx/_runtime/db/query/update.js +9 -18
  66. package/libx/_runtime/db/utils/deep.js +6 -5
  67. package/libx/_runtime/db/utils/normalizeTimeData.js +1 -1
  68. package/libx/_runtime/fiori/generic/activate.js +14 -19
  69. package/libx/_runtime/fiori/generic/edit.js +2 -5
  70. package/libx/_runtime/hana/streaming.js +3 -4
  71. package/libx/_runtime/remote/utils/client.js +9 -5
  72. package/libx/odata/afterburner.js +5 -2
  73. package/libx/rest/RestAdapter.js +2 -2
  74. package/libx/rest/middleware/error.js +4 -1
  75. package/libx/rest/middleware/operation.js +1 -1
  76. package/package.json +3 -3
  77. 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 || Number.MAX_VALUE
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 && req.target && req.target.query && req.target.query.SELECT && req.target.query.SELECT.orderBy
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 && req.target && req.target._isSingleton,
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.prototype.hasOwnProperty.call(data, element.name)) return result
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 & immutable (can also be complex, so do first)
116
- if (category === 'readonly' || (category === 'immutable' && event === 'UPDATE')) {
117
- if (_isDraftCoreComputed(req, element, event)) {
118
- // > preserve computed values if triggered by draftActivate and not managed
119
- return
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 deplyoing of extensions, not used anywhere else except UpsertBuilder
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