@sap/cds 6.6.1 → 6.7.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 (132) hide show
  1. package/CHANGELOG.md +67 -3
  2. package/README.md +1 -1
  3. package/apis/connect.d.ts +11 -4
  4. package/apis/core.d.ts +1 -1
  5. package/apis/csn.d.ts +1 -0
  6. package/apis/internal/inference.d.ts +15 -2
  7. package/apis/log.d.ts +10 -0
  8. package/apis/serve.d.ts +4 -9
  9. package/apis/services.d.ts +86 -19
  10. package/bin/build/buildTaskEngine.js +16 -42
  11. package/bin/build/constants.js +4 -2
  12. package/bin/build/provider/buildTaskProviderInternal.js +117 -85
  13. package/bin/build/provider/hana/index.js +6 -1
  14. package/bin/build/provider/mtx-extension/index.js +74 -34
  15. package/bin/build/provider/mtx-sidecar/index.js +3 -3
  16. package/bin/build/provider/nodejs/index.js +2 -2
  17. package/bin/build/util.js +63 -14
  18. package/bin/cds-serve.js +6 -0
  19. package/bin/cds.js +20 -4
  20. package/bin/deploy/to-hana/cfUtil.js +15 -1
  21. package/bin/deploy/to-hana/hana.js +1 -1
  22. package/bin/deploy/to-hana/hdiDeployUtil.js +1 -1
  23. package/bin/mtx/in-cds.js +2 -9
  24. package/bin/plugins.js +31 -0
  25. package/bin/serve.js +12 -12
  26. package/lib/compile/etc/_localized.js +1 -1
  27. package/lib/compile/for/lean_drafts.js +22 -6
  28. package/lib/compile/for/nodejs.js +4 -1
  29. package/lib/compile/load.js +4 -2
  30. package/lib/core/index.js +35 -15
  31. package/lib/dbs/cds-deploy.js +129 -133
  32. package/lib/env/cds-env.js +25 -17
  33. package/lib/env/cds-requires.js +10 -40
  34. package/lib/env/compat.js +12 -0
  35. package/lib/env/defaults.js +17 -9
  36. package/lib/env/plugins.js +29 -0
  37. package/lib/env/schemas/cds-rc.json +14 -0
  38. package/lib/index.js +3 -0
  39. package/lib/log/cds-log.js +7 -4
  40. package/lib/ql/CREATE.js +1 -1
  41. package/lib/ql/DELETE.js +1 -1
  42. package/lib/ql/DROP.js +3 -3
  43. package/lib/ql/INSERT.js +1 -1
  44. package/lib/ql/Query.js +14 -6
  45. package/lib/ql/SELECT.js +8 -2
  46. package/lib/ql/UPDATE.js +1 -1
  47. package/lib/ql/Whereable.js +1 -1
  48. package/lib/ql/cds-ql.js +1 -9
  49. package/lib/req/cds-context.js +1 -4
  50. package/lib/req/request.js +63 -2
  51. package/lib/req/response.js +3 -2
  52. package/lib/srv/bindings.js +69 -71
  53. package/lib/srv/cds-connect.js +4 -1
  54. package/lib/srv/cds-serve.js +4 -0
  55. package/lib/srv/middlewares/index.js +37 -6
  56. package/lib/srv/protocols/_legacy.js +1 -1
  57. package/lib/srv/protocols/index.js +1 -1
  58. package/lib/srv/srv-api.js +4 -6
  59. package/lib/srv/srv-dispatch.js +4 -3
  60. package/lib/srv/srv-handlers.js +1 -1
  61. package/lib/srv/srv-methods.js +8 -2
  62. package/lib/utils/cds-test.js +4 -1
  63. package/libx/_runtime/audit/Service.js +8 -9
  64. package/libx/_runtime/audit/generic/personal/index.js +1 -1
  65. package/libx/_runtime/audit/generic/personal/utils.js +1 -1
  66. package/libx/_runtime/audit/utils/v2.js +17 -20
  67. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +2 -0
  68. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +11 -4
  69. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +0 -1
  70. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +3 -3
  71. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +1 -1
  72. package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +4 -4
  73. package/libx/_runtime/cds-services/adapter/odata-v4/utils/oDataConfiguration.js +2 -2
  74. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +1 -1
  75. package/libx/_runtime/cds-services/services/Service.js +1 -1
  76. package/libx/_runtime/cds-services/util/assert.js +41 -65
  77. package/libx/_runtime/common/code-ext/WorkerPool.js +90 -0
  78. package/libx/_runtime/common/code-ext/WorkerReq.js +0 -4
  79. package/libx/_runtime/common/code-ext/execute.js +28 -18
  80. package/libx/_runtime/common/code-ext/handlers.js +5 -4
  81. package/libx/_runtime/common/code-ext/worker.js +45 -3
  82. package/libx/_runtime/common/code-ext/workerQueryExecutor.js +8 -7
  83. package/libx/_runtime/common/composition/delete.js +1 -1
  84. package/libx/_runtime/common/composition/update.js +3 -5
  85. package/libx/_runtime/common/generic/auth/expand.js +1 -1
  86. package/libx/_runtime/common/generic/auth/readOnly.js +5 -4
  87. package/libx/_runtime/common/generic/auth/restrict.js +7 -2
  88. package/libx/_runtime/common/generic/crud.js +12 -1
  89. package/libx/_runtime/common/generic/etag.js +11 -3
  90. package/libx/_runtime/common/generic/input.js +8 -6
  91. package/libx/_runtime/common/generic/paging.js +25 -8
  92. package/libx/_runtime/common/generic/put.js +1 -1
  93. package/libx/_runtime/common/generic/sorting.js +0 -1
  94. package/libx/_runtime/common/i18n/messages.properties +1 -0
  95. package/libx/_runtime/common/utils/cqn.js +5 -1
  96. package/libx/_runtime/common/utils/cqn2cqn4sql.js +2 -2
  97. package/libx/_runtime/common/utils/resolveView.js +14 -10
  98. package/libx/_runtime/common/utils/rewriteAsterisks.js +2 -3
  99. package/libx/_runtime/common/utils/templateProcessor.js +15 -17
  100. package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +18 -6
  101. package/libx/_runtime/db/Service.js +1 -0
  102. package/libx/_runtime/db/data-conversion/post-processing.js +0 -18
  103. package/libx/_runtime/db/expand/expand-v2.js +2 -2
  104. package/libx/_runtime/db/expand/rawToExpanded.js +6 -6
  105. package/libx/_runtime/db/generic/integrity.js +1 -1
  106. package/libx/_runtime/db/utils/columns.js +5 -5
  107. package/libx/_runtime/fiori/generic/activate.js +3 -3
  108. package/libx/_runtime/fiori/generic/edit.js +1 -1
  109. package/libx/_runtime/fiori/generic/new.js +4 -0
  110. package/libx/_runtime/fiori/lean-draft.js +138 -46
  111. package/libx/_runtime/hana/execute.js +3 -1
  112. package/libx/_runtime/hana/pool.js +10 -2
  113. package/libx/_runtime/messaging/common-utils/AMQPClient.js +6 -1
  114. package/libx/_runtime/messaging/enterprise-messaging.js +1 -0
  115. package/libx/_runtime/remote/Service.js +16 -13
  116. package/libx/_runtime/remote/utils/client.js +6 -1
  117. package/libx/_runtime/sqlite/Service.js +5 -59
  118. package/libx/_runtime/sqlite/convertDraftAdminPathExpression.js +1 -0
  119. package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +2 -2
  120. package/libx/_runtime/sqlite/execute.js +3 -1
  121. package/libx/_runtime/types/api.js +12 -3
  122. package/libx/odata/afterburner.js +36 -0
  123. package/libx/odata/cqn2odata.js +1 -1
  124. package/libx/odata/grammar.pegjs +5 -3
  125. package/libx/odata/parser.js +1 -1
  126. package/libx/odata/utils.js +1 -1
  127. package/libx/rest/RestAdapter.js +1 -1
  128. package/libx/rest/RestRequest.js +1 -0
  129. package/package.json +5 -2
  130. package/libx/_runtime/common/code-ext/workerQuery.js +0 -45
  131. package/libx/_runtime/common/constants/limit.js +0 -12
  132. package/libx/_runtime/common/utils/page.js +0 -39
@@ -1,19 +1,17 @@
1
1
  const DELIMITER = require('./templateDelimiter')
2
- const pathSerializer = require('./templateProcessorPathSerializer')
3
2
 
4
- const _processElement = (processFn, row, key, target, picked = {}, isRoot, pathSegments) => {
3
+ const _processElement = (processFn, row, key, target, picked = {}, isRoot, pathSegmentsInfo) => {
5
4
  const element = (target.elements || target.params)[key]
6
5
  const { plain } = picked
7
6
 
8
7
  if (!plain) return
9
8
 
10
- /**
11
- * @type import('../../types/api').templateElementInfo
12
- */
13
- const elementInfo = { row, key, element, target, plain, isRoot, pathSegments }
14
- if (!element && target._flat2struct && target._flat2struct[key] && elementInfo.pathSegments) {
15
- elementInfo.pathSegments = pathSegments.slice(0)
16
- elementInfo.pathSegments.push(...target._flat2struct[key])
9
+ /** @type import('../../types/api').templateElementInfo */
10
+ const elementInfo = { row, key, element, target, plain, isRoot, pathSegmentsInfo }
11
+
12
+ if (!element && target._flat2struct?.[key] && elementInfo.pathSegmentsInfo) {
13
+ elementInfo.pathSegmentsInfo = pathSegmentsInfo.slice(0)
14
+ elementInfo.pathSegmentsInfo.push(...target._flat2struct[key])
17
15
  }
18
16
 
19
17
  processFn(elementInfo)
@@ -23,7 +21,7 @@ const _processRow = (processFn, row, template, tKey, tValue, isRoot, pathOptions
23
21
  const { template: subTemplate, picked } = tValue
24
22
  const key = tKey.split(DELIMITER).pop()
25
23
 
26
- _processElement(processFn, row, key, template.target, picked, isRoot, pathOptions.pathSegments)
24
+ _processElement(processFn, row, key, template.target, picked, isRoot, pathOptions.pathSegmentsInfo)
27
25
 
28
26
  // process deep
29
27
  if (subTemplate && typeof row === 'object' && row) {
@@ -48,20 +46,20 @@ const _processComplex = (processFn, row, template, key, pathOptions) => {
48
46
  if (rows.length === 0) return
49
47
  const keyNames = pathOptions.includeKeyValues && _getTargetKeyNames(template.target)
50
48
 
51
- for (let idx = 0; idx < rows.length; idx++) {
52
- const row = rows[idx]
49
+ for (const row of rows) {
53
50
  if (row == null) continue
54
51
  const args = { processFn, row, template, isRoot: false, pathOptions }
55
52
 
56
- let pathSegment
53
+ let pathSegmentInfo
57
54
  if (pathOptions.includeKeyValues) {
58
- if (pathOptions.rowUUIDGenerator) pathOptions.rowUUIDGenerator(keyNames, row, template)
59
- pathSegment = pathSerializer(key, keyNames, row, template.target.elements, pathOptions.draftKeys)
55
+ pathOptions.rowUUIDGenerator?.(keyNames, row, template)
56
+ /** @type import('../../types/api').pathSegmentInfo */
57
+ pathSegmentInfo = { key, keyNames, row, elements: template.target.elements, draftKeys: pathOptions.draftKeys }
60
58
  }
61
59
 
62
- if (pathOptions.pathSegments) pathOptions.pathSegments.push(pathSegment || key)
60
+ if (pathOptions.pathSegmentsInfo) pathOptions.pathSegmentsInfo.push(pathSegmentInfo || key)
63
61
  templateProcessor(args)
64
- if (pathOptions.pathSegments) pathOptions.pathSegments.pop()
62
+ if (pathOptions.pathSegmentsInfo) pathOptions.pathSegmentsInfo.pop()
65
63
  }
66
64
  }
67
65
 
@@ -1,10 +1,10 @@
1
1
  const cds = require('../../cds')
2
- const templatePathSerializer = (tKey, keyNames, row, elements, draftKeys) => {
2
+
3
+ const segmentSerializer = pathSegmentInfo => {
4
+ const { key: tKey, row, elements, draftKeys } = pathSegmentInfo
5
+ let keyNames = pathSegmentInfo.keyNames
6
+
3
7
  const keyValuePairs = keyNames
4
- .filter(key => {
5
- if (cds.env.features.lean_draft && key === 'IsActiveEntity') return false
6
- return true
7
- })
8
8
  .map(key => {
9
9
  let quote
10
10
 
@@ -19,11 +19,23 @@ const templatePathSerializer = (tKey, keyNames, row, elements, draftKeys) => {
19
19
  }
20
20
 
21
21
  const keyValue = row[key] ?? draftKeys?.[key]
22
+ if (keyValue == null) return
22
23
  return `${key}=${quote}${keyValue}${quote}`
23
24
  })
25
+ .filter(c => c)
24
26
 
25
27
  const keyValuePairsSerialized = keyValuePairs.join(',')
26
- return `${tKey}(${keyValuePairsSerialized})`
28
+ const pathSegment = `${tKey}(${keyValuePairsSerialized})`
29
+ return pathSegment
30
+ }
31
+
32
+ const templatePathSerializer = (elementName, pathSegmentsInfo) => {
33
+ const pathSegments = pathSegmentsInfo.map(pathSegmentInfo => {
34
+ if (typeof pathSegmentInfo === 'string') return pathSegmentInfo
35
+ return segmentSerializer(pathSegmentInfo)
36
+ })
37
+ const path = `${pathSegments.join('/')}${pathSegments.length ? '/' : ''}${elementName}`
38
+ return path
27
39
  }
28
40
 
29
41
  module.exports = templatePathSerializer
@@ -138,4 +138,5 @@ class DatabaseService extends cds.Service {
138
138
  }
139
139
  }
140
140
 
141
+ DatabaseService.prototype.isDatabaseService = true
141
142
  module.exports = DatabaseService
@@ -1,7 +1,5 @@
1
1
  const { getEntityNameFromCQN, traverseFroms } = require('../../common/utils/entityFromCqn')
2
2
  const { ensureNoDraftsSuffix } = require('../../common/utils/draft')
3
- const { proxifyIfFlattened } = require('../../../common/utils/ucsn')
4
- const cds = require('../../../../lib')
5
3
 
6
4
  const _getCastFunction = ({ type }) => {
7
5
  switch (type) {
@@ -34,13 +32,6 @@ const _getNestedElement = (entity, key) => {
34
32
  return _structElement(entity, structPath)
35
33
  }
36
34
 
37
- const _structure = csnEntity => (value, key, row, unaliasedKey) => {
38
- proxifyIfFlattened(csnEntity, row)
39
- const effectiveKey = unaliasedKey || key
40
- delete row[effectiveKey]
41
- row[effectiveKey] = value
42
- }
43
-
44
35
  const _addConverter = (mapper, name, converter) => {
45
36
  if (mapper.has(name)) {
46
37
  const oldConverter = mapper.get(name)
@@ -126,15 +117,6 @@ const _getMapperForListedElements = (conversionMap, csn, cqn) => {
126
117
  row[effectiveKey] = JSON.parse(val)
127
118
  }) // > arrayed elements
128
119
  }
129
-
130
- if (
131
- cds.env.features.ucsn_struct_conversion &&
132
- element.parent &&
133
- element.parent._isStructured &&
134
- !element._isStructured
135
- ) {
136
- _addConverter(mapper, col.as ? col.as : name, _structure(entity))
137
- }
138
120
  }
139
121
  }
140
122
 
@@ -119,7 +119,7 @@ const _addForeignKeys = (columns, entity, options) => {
119
119
  * @returns object
120
120
  */
121
121
  const expandV2 = async (model, dbc, query, user, locale, txTimestamp, executeSelectCQN) => {
122
- const expandColumn = query.SELECT.columns.find(c => c.expand && typeof c.expand === 'string')
122
+ const expandColumn = query.SELECT.columns.find(c => c.expand && typeof c.expand[0] === 'string')
123
123
  const options = Object.assign({ onlyKeys: false, onlyCompositions: false }, expandColumn._options)
124
124
  // remove expand columns from query without modifying
125
125
  const topLevelSelect = query.clone().columns(query.SELECT.columns.filter(c => !c.expand))
@@ -137,7 +137,7 @@ const expandV2 = async (model, dbc, query, user, locale, txTimestamp, executeSel
137
137
 
138
138
  // _associations contains compositions and associations
139
139
  if (entity._associations) {
140
- const depth = expandColumn.expand === '**' ? -1 : Number(expandColumn.expand.replace('*', ''))
140
+ const depth = expandColumn.expand[0] === '**' ? -1 : Number(expandColumn.expand[0].replace('*', ''))
141
141
  await _autoExpandNavsAndAttachToResult(entity, Array.isArray(result) ? result : [result], depth, options)
142
142
  }
143
143
 
@@ -111,7 +111,7 @@ class RawToExpanded {
111
111
  let expandedItems = this._getResultCache(toManyTree.concat(key))[mapping[GET_KEY_VALUE](false, entry)] || []
112
112
 
113
113
  // the expanded items may include the actives of the deleted drafts -> filter out
114
- if (!cds.env.features.lean_draft && rootIsActiveEntity !== null) {
114
+ if (!cds.env.fiori.lean_draft && rootIsActiveEntity !== null) {
115
115
  if (mapping[TO_ACTIVE]) expandedItems = expandedItems.filter(ele => ele.IsActiveEntity !== false)
116
116
  else expandedItems = expandedItems.filter(ele => !!ele.IsActiveEntity === rootIsActiveEntity)
117
117
  }
@@ -144,7 +144,7 @@ class RawToExpanded {
144
144
  else if (rootIsActiveEntity) row[key] = parsed && parsed.IsActiveEntity !== false ? parsed : null
145
145
  else row[key] = parsed && parsed.IsActiveEntity === rootIsActiveEntity ? parsed : null
146
146
  }
147
- if (mapping[CLEANUP_KEYS]) {
147
+ if (parsed && mapping[CLEANUP_KEYS]) {
148
148
  for (const key in mapping[CLEANUP_KEYS]) delete parsed[key]
149
149
  }
150
150
  } else {
@@ -153,7 +153,7 @@ class RawToExpanded {
153
153
  // Assume a DB will not return undefined, but always null
154
154
  this._convertValue(rawValue, conversionMapper.get(mapping), mapping, row, key)
155
155
 
156
- isEntityNull = this._isNull(isEntityNull, rawValue)
156
+ isEntityNull = this._isNull(isEntityNull, rawValue, key)
157
157
  }
158
158
  }
159
159
 
@@ -173,12 +173,12 @@ class RawToExpanded {
173
173
  * @returns {boolean}
174
174
  * @private
175
175
  */
176
- _isNull(isEntityNull, value) {
176
+ _isNull(isEntityNull, value, key) {
177
177
  if (isEntityNull === undefined) {
178
- return value === null || value === undefined
178
+ return value === null || value === undefined || key === 'IsActiveEntity'
179
179
  }
180
180
 
181
- return isEntityNull === true && (value === null || value === undefined)
181
+ return isEntityNull === true && (value === null || value === undefined || key === 'IsActiveEntity')
182
182
  }
183
183
 
184
184
  /**
@@ -333,7 +333,7 @@ const _checkReferenceIntegrity = (entity, data, req, csn, run) => {
333
333
  }
334
334
 
335
335
  const _checkIntegrityWrapper = (req, csn, run) => async (data, entity) => {
336
- if (cds.env.features.lean_draft && entity.name?.endsWith('.drafts')) return
336
+ if (cds.env.fiori.lean_draft && entity.name?.endsWith('.drafts')) return
337
337
  const errors = await _checkReferenceIntegrity(entity, data, req, csn, run)
338
338
  if (errors && errors.length !== 0) for (const err of errors) req.error(err)
339
339
  }
@@ -16,15 +16,15 @@ const getColumns = (entity, { _4db, onlyKeys } = { _4db: true, onlyKeys: false }
16
16
  if (!(entity && entity.elements)) return []
17
17
  const columnNames = []
18
18
  // REVISIT!!!
19
- const elements = cds.env.features.lean_draft
20
- ? entity.elements
21
- : Object.getPrototypeOf(entity.elements) || entity.elements
19
+ const { structs = cds.env.features.ucsn_struct_conversion } = cds.env.effective.odata
20
+ const { lean_draft } = cds.env.fiori
21
+ const elements = lean_draft ? entity.elements : Object.getPrototypeOf(entity.elements) || entity.elements
22
22
  for (const elementName in elements) {
23
23
  const element = elements[elementName]
24
24
  if (onlyKeys && !element.key) continue
25
25
  if (element.isAssociation) continue
26
- if (!cds.env.features.lean_draft && _4db && entity._isDraftEnabled && elementName in DRAFT_COLUMNS_MAP) continue
27
- if ((cds.env.effective.odata.structs || cds.env.features.ucsn_struct_conversion) && element.elements) {
26
+ if (!lean_draft && _4db && entity._isDraftEnabled && elementName in DRAFT_COLUMNS_MAP) continue
27
+ if (structs && element.elements) {
28
28
  columnNames.push(...resolveStructured({ element, structProperties: [] }, false))
29
29
  continue
30
30
  }
@@ -146,8 +146,8 @@ const fioriGenericActivate = async function (req) {
146
146
 
147
147
  // REVISIT: should not be necessary
148
148
  r._ = Object.assign(r._, req._)
149
- r.getUriInfo = () => req.getUriInfo()
150
- r.getUrlObject = () => req.getUrlObject()
149
+ if (req.getUriInfo) r.getUriInfo = () => req.getUriInfo()
150
+ if (req.getUrlObject) r.getUrlObject = () => req.getUrlObject()
151
151
  r._.params = req.params
152
152
  r._.query = req.query
153
153
 
@@ -178,7 +178,7 @@ const fioriGenericActivate = async function (req) {
178
178
 
179
179
  // REVISIT: we need to use okra API here because it must be set in the batched request
180
180
  // status code must be set in handler to allow overriding for FE V2
181
- req?._?.odataRes.setStatusCode(201)
181
+ req?._?.odataRes?.setStatusCode(201)
182
182
 
183
183
  return result
184
184
  }
@@ -164,7 +164,7 @@ const fioriGenericEdit = async function (req) {
164
164
 
165
165
  // REVISIT: we need to use okra API here because it must be set in the batched request
166
166
  // status code must be set in handler to allow overriding for FE V2
167
- req?._?.odataRes.setStatusCode(201)
167
+ req?._?.odataRes?.setStatusCode(201)
168
168
 
169
169
  return results[0][0]
170
170
  }
@@ -56,6 +56,10 @@ const fioriGenericNew = async function (req, next) {
56
56
 
57
57
  if (!cds.db) req.reject('NO_DATABASE_CONNECTION')
58
58
 
59
+ const isRoot = typeof req.query.INSERT.into === 'string' || req.query.INSERT.into.ref?.length === 1
60
+ // Only allowed for pseudo draft roots (entities with this action)
61
+ if (isRoot && !req.target['@Common.DraftRoot.ActivationAction']) req.reject(403, 'DRAFT_MODIFICATION_ONLY_VIA_ROOT')
62
+
59
63
  const navigationToMany = isNavigationToMany(req)
60
64
 
61
65
  const adminDataCQN = navigationToMany