@sap/cds 6.5.0 → 6.6.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 (105) hide show
  1. package/CHANGELOG.md +38 -2
  2. package/README.md +5 -0
  3. package/apis/services.d.ts +5 -0
  4. package/bin/build/buildTaskEngine.js +0 -2
  5. package/bin/build/buildTaskFactory.js +1 -1
  6. package/bin/build/buildTaskHandler.js +1 -1
  7. package/bin/build/provider/buildTaskProviderInternal.js +10 -6
  8. package/bin/build/provider/fiori/index.js +5 -10
  9. package/bin/build/provider/hana/2migration.js +11 -2
  10. package/bin/build/provider/hana/index.js +17 -14
  11. package/bin/build/provider/hana/template/.hdiconfig-hanacloud +137 -0
  12. package/bin/build/provider/mtx-extension/index.js +18 -1
  13. package/bin/build/provider/mtx-sidecar/index.js +1 -1
  14. package/bin/build/util.js +1 -1
  15. package/bin/cds.js +1 -5
  16. package/bin/deploy/to-hana/hana.js +10 -3
  17. package/bin/serve.js +32 -20
  18. package/lib/auth/jwt-auth.js +4 -4
  19. package/lib/compile/for/lean_drafts.js +55 -6
  20. package/lib/dbs/cds-deploy.js +6 -8
  21. package/lib/index.js +4 -2
  22. package/lib/req/cds-context.js +3 -3
  23. package/lib/srv/bindings.js +1 -2
  24. package/lib/srv/cds-serve.js +2 -1
  25. package/lib/srv/middlewares/trace.js +31 -15
  26. package/lib/srv/protocols/odata-v2-proxy.js +8 -8
  27. package/lib/srv/srv-handlers.js +26 -7
  28. package/lib/srv/srv-methods.js +2 -2
  29. package/lib/srv/srv-models.js +3 -3
  30. package/lib/utils/cds-test.js +7 -5
  31. package/libx/_runtime/auth/strategies/ias-auth.js +1 -1
  32. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +6 -2
  33. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +26 -1
  34. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +8 -0
  35. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +1 -1
  36. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +11 -2
  37. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +8 -8
  38. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +1 -1
  39. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/validator/ValueValidator.js +14 -14
  40. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +1 -0
  41. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ResourceJsonSerializer.js +3 -0
  42. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/UriHelper.js +2 -1
  43. package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +3 -2
  44. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +7 -0
  45. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +0 -3
  46. package/libx/_runtime/cds-services/services/Service.js +8 -19
  47. package/libx/_runtime/cds-services/services/utils/columns.js +7 -4
  48. package/libx/_runtime/cds-services/util/assert.js +7 -1
  49. package/libx/_runtime/common/code-ext/WorkerReq.js +3 -1
  50. package/libx/_runtime/common/code-ext/execute.js +9 -2
  51. package/libx/_runtime/common/code-ext/handlers.js +2 -2
  52. package/libx/_runtime/common/code-ext/worker.js +9 -5
  53. package/libx/_runtime/common/code-ext/workerQueryExecutor.js +5 -2
  54. package/libx/_runtime/common/composition/data.js +5 -2
  55. package/libx/_runtime/common/composition/tree.js +2 -0
  56. package/libx/_runtime/common/generic/auth/restrict.js +1 -1
  57. package/libx/_runtime/common/generic/etag.js +3 -1
  58. package/libx/_runtime/common/generic/input.js +12 -14
  59. package/libx/_runtime/common/utils/cqn2cqn4sql.js +31 -11
  60. package/libx/_runtime/common/utils/path.js +0 -1
  61. package/libx/_runtime/common/utils/search2cqn4sql.js +4 -1
  62. package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +19 -13
  63. package/libx/_runtime/db/data-conversion/post-processing.js +1 -1
  64. package/libx/_runtime/db/expand/expandCQNToJoin.js +5 -3
  65. package/libx/_runtime/db/expand/rawToExpanded.js +3 -2
  66. package/libx/_runtime/db/generic/input.js +2 -2
  67. package/libx/_runtime/db/generic/integrity.js +1 -0
  68. package/libx/_runtime/db/generic/virtual.js +1 -0
  69. package/libx/_runtime/db/query/read.js +3 -2
  70. package/libx/_runtime/fiori/generic/activate.js +3 -1
  71. package/libx/_runtime/fiori/generic/before.js +1 -0
  72. package/libx/_runtime/fiori/generic/edit.js +3 -1
  73. package/libx/_runtime/fiori/generic/new.js +2 -0
  74. package/libx/_runtime/fiori/generic/patch.js +2 -0
  75. package/libx/_runtime/fiori/generic/prepare.js +2 -0
  76. package/libx/_runtime/fiori/generic/read.js +8 -2
  77. package/libx/_runtime/fiori/generic/readOverDraft.js +2 -0
  78. package/libx/_runtime/fiori/lean-draft.js +498 -245
  79. package/libx/_runtime/fiori/utils/delete.js +2 -0
  80. package/libx/_runtime/messaging/Outbox.js +1 -1
  81. package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +1 -0
  82. package/libx/_runtime/messaging/enterprise-messaging.js +2 -6
  83. package/libx/_runtime/messaging/file-based.js +1 -2
  84. package/libx/_runtime/messaging/outbox/OutboxRunner.js +1 -1
  85. package/libx/_runtime/messaging/outbox/utils.js +1 -1
  86. package/libx/_runtime/messaging/service.js +0 -1
  87. package/libx/_runtime/remote/Service.js +1 -0
  88. package/libx/_runtime/sqlite/convertDraftAdminPathExpression.js +19 -3
  89. package/libx/_runtime/sqlite/customBuilder/CustomExpressionBuilder.js +0 -18
  90. package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +0 -18
  91. package/libx/_runtime/sqlite/customBuilder/CustomSelectBuilder.js +0 -24
  92. package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +2 -1
  93. package/libx/_runtime/sqlite/customBuilder/index.js +47 -32
  94. package/libx/odata/afterburner.js +17 -5
  95. package/libx/odata/grammar.pegjs +3 -4
  96. package/libx/odata/index.js +5 -1
  97. package/libx/odata/parseToCqn.js +3 -3
  98. package/libx/odata/parser.js +1 -1
  99. package/libx/odata/utils.js +58 -1
  100. package/package.json +1 -1
  101. package/server.js +1 -1
  102. package/libx/_runtime/sqlite/customBuilder/CustomDeleteBuilder.js +0 -17
  103. package/libx/_runtime/sqlite/customBuilder/CustomReferenceBuilder.js +0 -11
  104. package/libx/_runtime/sqlite/customBuilder/CustomUpdateBuilder.js +0 -17
  105. /package/bin/build/provider/hana/template/{.hdiconfig → .hdiconfig-haas} +0 -0
@@ -36,10 +36,10 @@ const _getSimpleCategory = category => {
36
36
  }
37
37
 
38
38
  const _isDraftCoreComputed = (req, element, event) =>
39
+ element['@Core.Computed'] &&
39
40
  cds.env.features.preserve_computed !== false &&
40
41
  req._ &&
41
42
  req._.event === 'draftActivate' &&
42
- element['@Core.Computed'] &&
43
43
  !((event === 'CREATE' && element['@cds.on.insert']) || element['@cds.on.update'])
44
44
 
45
45
  const _isStreamingProperty = (elements, row, property) =>
@@ -217,16 +217,8 @@ const _callError = (req, errors) => {
217
217
  for (const error of errors) req.error(error)
218
218
  }
219
219
 
220
- const _isBoundAction = req => !!(req.getUriInfo && req.getUriInfo().getLastSegment().getKind() === 'BOUND.ACTION')
221
-
222
- const _getBoundActionBindingParameter = req => {
223
- // REVISIT: req._ gets set in onDraftActivate to original req
224
- const action = (req._ && req._.event) || req.event
225
- const actions = req.target.actions
226
-
227
- // 'in' is the default binding parameter name for bound actions/functions
228
- return (actions && actions[action] && actions[action]['@cds.odata.bindingparameter.name']) || 'in'
229
- }
220
+ const _getBoundAction = req => req.target.actions?.[req.context?.event]
221
+ const _getBoundActionBindingParameter = action => action['@cds.odata.bindingparameter.name'] || 'in'
230
222
 
231
223
  async function commonGenericInput(req) {
232
224
  if (!req.query) return // FIXME: the code below expects req.query to be defined
@@ -250,8 +242,10 @@ async function commonGenericInput(req) {
250
242
  pathSegments: []
251
243
  }
252
244
 
253
- if (_isBoundAction(req)) {
254
- const pathSegment = _getBoundActionBindingParameter(req)
245
+ const boundAction = _getBoundAction(req)
246
+
247
+ if (boundAction) {
248
+ const pathSegment = _getBoundActionBindingParameter(boundAction)
255
249
  const keys = req._ && req._.params && req._.params[0]
256
250
  if (pathSegment) pathOptions.pathSegments.push(pathSegment)
257
251
 
@@ -370,7 +364,11 @@ commonGenericInput._initial = true
370
364
  _actionFunctionHandler._initial = true
371
365
 
372
366
  module.exports = cds.service.impl(function () {
373
- this.before(['CREATE', 'UPDATE', 'NEW', 'PATCH'], '*', commonGenericInput)
367
+ if (cds.env.features.lean_draft) {
368
+ this.before(['CREATE', 'UPDATE'], '*', commonGenericInput)
369
+ } else {
370
+ this.before(['CREATE', 'UPDATE', 'NEW', 'PATCH'], '*', commonGenericInput)
371
+ }
374
372
  const operationNames = []
375
373
 
376
374
  for (const operation of this.operations) {
@@ -142,14 +142,16 @@ const _getWindowWhere = (where, bottomTop) => {
142
142
 
143
143
  const _getOrderByForWindowFn = bottomTop => {
144
144
  const orderBy = _getBottomTopRefOrVal(bottomTop[0], 'ref')[0]
145
- orderBy.sort = bottomTop[0].func === 'topcount' ? 'desc' : 'asc'
146
- return orderBy
145
+ const sort = bottomTop[0].func === 'topcount' ? 'desc' : 'asc'
146
+ return [orderBy, sort]
147
147
  }
148
148
 
149
149
  const _getWindowXpr = (groupBy, bottomTop) => {
150
- const xpr = [{ func: 'ROW_NUMBER', args: [] }, 'OVER', '(']
150
+ const overXpr = { xpr: [] }
151
+ const xpr = [{ func: 'ROW_NUMBER', args: [] }, 'OVER', overXpr]
152
+
151
153
  if (groupBy)
152
- xpr.push(
154
+ overXpr.xpr.push(
153
155
  'PARTITION BY',
154
156
  ...groupBy.reduce((acc, el, i) => {
155
157
  if (i < groupBy.length - 1) {
@@ -163,9 +165,8 @@ const _getWindowXpr = (groupBy, bottomTop) => {
163
165
  }, [])
164
166
  )
165
167
 
166
- xpr.push('ORDER BY', _getOrderByForWindowFn(bottomTop))
167
- xpr.push(')')
168
- return { xpr: xpr, as: 'rowNumber' }
168
+ overXpr.xpr.push('ORDER BY', ..._getOrderByForWindowFn(bottomTop))
169
+ return { xpr, as: 'rowNumber' }
169
170
  }
170
171
 
171
172
  const _isNavCountFunc = el => el.func && el.func === 'count' && el.args[0] !== '*'
@@ -586,6 +587,7 @@ const _convertOrderByOrWhereIfSkip = (cqn, target, model) => {
586
587
  }
587
588
  }
588
589
 
590
+ /* REVISIT: Unused
589
591
  const _convertExpand = expand => {
590
592
  expand.forEach(expandElement => {
591
593
  if (expandElement.ref && expandElement.ref[0]) {
@@ -603,6 +605,7 @@ const _convertExpand = expand => {
603
605
  }
604
606
  })
605
607
  }
608
+ */
606
609
 
607
610
  const _simplifyWhere = col => {
608
611
  if (col.ref?.[0].where) {
@@ -827,7 +830,11 @@ const _convertUpsert = (query, model) => {
827
830
 
828
831
  // We add all previous properties ot the newly created query.
829
832
  // Reason is to not lose the query API functionality
830
- Object.assign(upsert.UPSERT, query.UPSERT, { into: { ref: [resolvedIntoClause], as: query.UPSERT.into.as } })
833
+
834
+ for (const key in query.UPSERT) {
835
+ upsert.UPSERT[key] = query.UPSERT[key]
836
+ }
837
+ Object.assign(upsert.UPSERT, { into: { ref: [resolvedIntoClause], as: query.UPSERT.into.as } })
831
838
 
832
839
  const resolved = resolveView(upsert, model, cds.db)
833
840
  // required for deplyoing of extensions, not used anywhere else except UpsertBuilder
@@ -848,7 +855,11 @@ const _convertInsert = (query, model) => {
848
855
 
849
856
  // We add all previous properties ot the newly created query.
850
857
  // Reason is to not lose the query API functionality
851
- Object.assign(insert.INSERT, query.INSERT, { into: { ref: [resolvedIntoClause], as: query.INSERT.into.as } })
858
+ insert.INSERT = {}
859
+ for (const prop in query.INSERT) {
860
+ insert.INSERT[prop] = query.INSERT[prop]
861
+ }
862
+ Object.assign(insert.INSERT, { into: { ref: [resolvedIntoClause], as: query.INSERT.into.as } })
852
863
 
853
864
  const target = model.definitions[resolvedIntoClause]
854
865
  if (!target) return insert
@@ -900,7 +911,12 @@ const _convertDelete = (query, model, options) => {
900
911
 
901
912
  const { target, alias, where } = convertPathExpressionToWhere(query.DELETE.from, model, options)
902
913
  const deleet = DELETE('x')
903
- Object.assign(deleet.DELETE, query.DELETE, { from: target, where: undefined })
914
+
915
+ for (const key in query.DELETE) {
916
+ deleet.DELETE[key] = query.DELETE[key]
917
+ }
918
+
919
+ Object.assign(deleet.DELETE, { from: target, where: undefined })
904
920
 
905
921
  if (alias) deleet.DELETE.from = { ref: [target], as: alias }
906
922
  if (where) deleet.where(where)
@@ -945,7 +961,11 @@ const _convertUpdate = (query, model, options) => {
945
961
  // link .with and .data and set query target and remove current where clause
946
962
  // REVISIT: update statement does not accept cqn partial as input
947
963
  const update = UPDATE('x')
948
- Object.assign(update.UPDATE, query.UPDATE, { entity: target, where: undefined })
964
+ // Object.assign(update.UPDATE, query.UPDATE, { entity: target, where: undefined })
965
+ for (const key in query.UPDATE) {
966
+ update.UPDATE[key] = query.UPDATE[key]
967
+ }
968
+ Object.assign(update.UPDATE, { entity: target, where: undefined })
949
969
 
950
970
  if (alias) update.UPDATE.entity = { ref: [target], as: alias }
951
971
  if (where) update.where(where)
@@ -1,4 +1,3 @@
1
- const cds = require('../../cds')
2
1
  const { ensureNoDraftsSuffix } = require('./draft')
3
2
 
4
3
  /*
@@ -28,7 +28,10 @@ const search2cqn4sql = (query, model, options = {}) => {
28
28
  const search2cqnOptions = { columns: computeColumnsToBeSearched(query, entity), locale: options.locale }
29
29
  return search2cqn4sql(query, entity, search2cqnOptions)
30
30
  } else {
31
- const expression = searchToLike(cqnSearchPhrase, computeColumnsToBeSearched(query, entity, alias))
31
+ const columnsToBeSearched = computeColumnsToBeSearched(query, entity, alias)
32
+ const expression = columnsToBeSearched?.length
33
+ ? searchToLike(cqnSearchPhrase, columnsToBeSearched)
34
+ : [{ val: 0 }, '=', { val: 1 }]
32
35
 
33
36
  // REVISIT: find out here if where or having must be used
34
37
  query._aggregated || /* if new parser */ query.SELECT.groupBy ? query.having(expression) : query.where(expression)
@@ -1,20 +1,26 @@
1
+ const cds = require('../../cds')
1
2
  const templatePathSerializer = (tKey, keyNames, row, elements, draftKeys) => {
2
- const keyValuePairs = keyNames.map(key => {
3
- let quote
3
+ const keyValuePairs = keyNames
4
+ .filter(key => {
5
+ if (cds.env.features.lean_draft && key === 'IsActiveEntity') return false
6
+ return true
7
+ })
8
+ .map(key => {
9
+ let quote
4
10
 
5
- switch (elements[key].type) {
6
- case 'cds.String':
7
- quote = "'"
8
- break
11
+ switch (elements[key].type) {
12
+ case 'cds.String':
13
+ quote = "'"
14
+ break
9
15
 
10
- default:
11
- quote = ''
12
- break
13
- }
16
+ default:
17
+ quote = ''
18
+ break
19
+ }
14
20
 
15
- const keyValue = row[key] ?? draftKeys?.[key]
16
- return `${key}=${quote}${keyValue}${quote}`
17
- })
21
+ const keyValue = row[key] ?? draftKeys?.[key]
22
+ return `${key}=${quote}${keyValue}${quote}`
23
+ })
18
24
 
19
25
  const keyValuePairsSerialized = keyValuePairs.join(',')
20
26
  return `${tKey}(${keyValuePairsSerialized})`
@@ -91,7 +91,7 @@ const _getMapperForListedElements = (conversionMap, csn, cqn) => {
91
91
  if (col.cast) {
92
92
  const name = col.as ? col.as : col.ref[col.ref.length - 1]
93
93
  _addConverter(mapper, name, (val, key, row, unaliasedKey) => {
94
- row[unaliasedKey || key] = _getCastFunction(col.cast)(val)
94
+ row[unaliasedKey || key] = val === null ? null : _getCastFunction(col.cast)(val)
95
95
  })
96
96
  continue
97
97
  }
@@ -867,7 +867,7 @@ class JoinCQNFromExpanded {
867
867
  const subWhere = []
868
868
 
869
869
  for (const key in entity.keys) {
870
- if (key === 'IsActiveEntity') continue
870
+ if (key === 'IsActiveEntity' || entity.keys[key]._isAssociationStrict) continue
871
871
  if (subWhere.length) {
872
872
  subWhere.push('and')
873
873
  }
@@ -982,7 +982,7 @@ class JoinCQNFromExpanded {
982
982
  ...column,
983
983
  xpr: column.xpr.map(x => {
984
984
  if (x.ref) {
985
- const res = this._buildNewAliasColumn(x, entity, tableAlias, mappings)
985
+ const res = this._buildNewAliasColumn(x, entity, tableAlias, mappings, true)
986
986
  delete res.as
987
987
  return res
988
988
  } else return x
@@ -1013,7 +1013,7 @@ class JoinCQNFromExpanded {
1013
1013
  return column.ref && typeof column.ref[column.ref.length - 1] !== 'string'
1014
1014
  }
1015
1015
 
1016
- _buildNewAliasColumn(column, entity, tableAlias, mappings) {
1016
+ _buildNewAliasColumn(column, entity, tableAlias, mappings, skipMapping = false) {
1017
1017
  // Casted name, vs column name
1018
1018
  const identifier = this._getIdentifier(column, tableAlias)
1019
1019
  const as = column[SKIP_MAPPING] ? column.as : `${tableAlias}_${identifier}`
@@ -1027,6 +1027,8 @@ class JoinCQNFromExpanded {
1027
1027
  aliasedElement.ref = alias ? [alias, column.ref[0]] : [column.ref[0]]
1028
1028
  }
1029
1029
 
1030
+ if (skipMapping) return aliasedElement
1031
+
1030
1032
  if (!column[SKIP_MAPPING]) {
1031
1033
  mappings[column[IDENTIFIER] || identifier] = as
1032
1034
  if (column[CLEANUP_KEYS]) {
@@ -1,5 +1,6 @@
1
1
  const { DRAFT_COLUMNS_MAP } = require('../../common/constants/draft')
2
2
  const DRAFT_COLUMNS_ARRAY = Object.keys(DRAFT_COLUMNS_MAP)
3
+ const cds = require('../../cds')
3
4
 
4
5
  const EXPAND = Symbol.for('sap.cds.expand')
5
6
  const GET_KEY_VALUE = Symbol.for('sap.cds.getKeyValue')
@@ -110,9 +111,9 @@ class RawToExpanded {
110
111
  let expandedItems = this._getResultCache(toManyTree.concat(key))[mapping[GET_KEY_VALUE](false, entry)] || []
111
112
 
112
113
  // the expanded items may include the actives of the deleted drafts -> filter out
113
- if (rootIsActiveEntity !== null) {
114
+ if (!cds.env.features.lean_draft && rootIsActiveEntity !== null) {
114
115
  if (mapping[TO_ACTIVE]) expandedItems = expandedItems.filter(ele => ele.IsActiveEntity !== false)
115
- else expandedItems = expandedItems.filter(ele => ele.IsActiveEntity === rootIsActiveEntity)
116
+ else expandedItems = expandedItems.filter(ele => !!ele.IsActiveEntity === rootIsActiveEntity)
116
117
  }
117
118
 
118
119
  row[key] = !mapping[CLEANUP_KEYS]
@@ -197,11 +197,11 @@ function dbGenericInput(req) {
197
197
  // call with this for this.model
198
198
  normalizeTimeData.call(this, req)
199
199
 
200
- const draft = req.target.name && req.target.name.match(/_drafts$/)
200
+ const draft = req.target.name && (req.target.name.match(/_drafts$/) || req.target.name.match(/\.drafts$/))
201
201
 
202
202
  const target =
203
203
  req.target._unresolved && req.target.name
204
- ? this.model.definitions[req.target.name.replace(/_drafts$/, '')]
204
+ ? this.model.definitions[req.target.name] || this.model.definitions[req.target.name.replace(/_drafts$/, '')]
205
205
  : req.target
206
206
  if (!target || target._unresolved) return
207
207
 
@@ -333,6 +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
337
  const errors = await _checkReferenceIntegrity(entity, data, req, csn, run)
337
338
  if (errors && errors.length !== 0) for (const err of errors) req.error(err)
338
339
  }
@@ -18,6 +18,7 @@ const _convert = (columns, target, model, alias) => {
18
18
  if (element) {
19
19
  if (element.virtual) {
20
20
  col.as = col.as || col.ref[col.ref.length - 1]
21
+ col.cast = { type: element._type }
21
22
  delete col.ref
22
23
  col.val = (element.default && element.default.val) || null
23
24
  }
@@ -1,5 +1,6 @@
1
1
  const { timestampToISO } = require('../data-conversion/timestamp')
2
2
  const { deepCopyObject } = require('../../common/utils/copy')
3
+ const getError = require('../../common/error')
3
4
 
4
5
  function _arrayWithCount(a, count) {
5
6
  const _map = a.map
@@ -41,8 +42,8 @@ const read = (executeSelectCQN, executeStreamCQN) => (model, dbc, query, req) =>
41
42
  const isoTs = timestampToISO(timestamp)
42
43
 
43
44
  if (query._streaming) {
44
- if (!query.SELECT || (query.SELECT && (!query.SELECT.columns || query.SELECT.columns.length !== 1))) {
45
- req.reject(400)
45
+ if (!query.SELECT || (query.SELECT && !query.SELECT.columns)) {
46
+ throw getError(500, 'Invalid SELECT statement for streaming')
46
47
  }
47
48
  return executeStreamCQN(model, dbc, query, user, locale, isoTs)
48
49
  }
@@ -112,9 +112,11 @@ const fioriGenericActivate = async function (req) {
112
112
  req.query.SELECT.from.ref.length > 2 ||
113
113
  !isDraftRootEntity(this.model.definitions, ensureNoDraftsSuffix(req.target.name))
114
114
  ) {
115
- req.reject(400)
115
+ req.reject(400, 'Action "draftActivate" can only be called on the root draft entity')
116
116
  }
117
117
 
118
+ if (!cds.db) req.reject('NO_DATABASE_CONNECTION')
119
+
118
120
  const { draftData, activeData, adminData } = await _draftCompositionTree(this, req)
119
121
 
120
122
  if (!draftData) req.reject(404)
@@ -78,6 +78,7 @@ const _getRoot = req => {
78
78
  }
79
79
 
80
80
  const _getDraftDataFromExistingDraft = async (req, root, isBoundAction) => {
81
+ if (!cds.db) req.reject('NO_DATABASE_CONNECTION')
81
82
  if (!root) return []
82
83
  if (root?.IsActiveEntity === false) {
83
84
  const query = _getSelectDraftDataCqn(root.entityName, root.where)
@@ -70,9 +70,11 @@ const _select = async (lockRecordCQN, draftExistsCQN, selectCQNs, req, dbtx) =>
70
70
  */
71
71
  const fioriGenericEdit = async function (req) {
72
72
  if (!isActiveEntityRequested(req.query.SELECT.where || [])) {
73
- req.reject(400)
73
+ req.reject(400, 'Action "draftEdit" can only be called on the active entity')
74
74
  }
75
75
 
76
+ if (!cds.db) req.reject('NO_DATABASE_CONNECTION')
77
+
76
78
  const { definitions } = this.model
77
79
 
78
80
  // TODO replace with generic where filter
@@ -54,6 +54,8 @@ const fioriGenericNew = async function (req, next) {
54
54
  return onDraftActivate(req, next)
55
55
  }
56
56
 
57
+ if (!cds.db) req.reject('NO_DATABASE_CONNECTION')
58
+
57
59
  const navigationToMany = isNavigationToMany(req)
58
60
 
59
61
  const adminDataCQN = navigationToMany
@@ -61,6 +61,8 @@ const _joinDraftAdministrativeData = (selectResolved, target) => {
61
61
  const fioriGenericPatch = async function (req) {
62
62
  if (req.data.IsActiveEntity === true) req.reject(400, 'Patch can only be applied to a draft entity')
63
63
 
64
+ if (!cds.db) req.reject('NO_DATABASE_CONNECTION')
65
+
64
66
  const dbtx = cds.tx(req)
65
67
 
66
68
  const selectResolved = cqn2cqn4sql(_getSelectCQN(req), this.model)
@@ -16,6 +16,8 @@ const fioriGenericPrepare = async function (req) {
16
16
  req.reject(400, 'Action "draftPrepare" can only be called on a draft entity')
17
17
  }
18
18
 
19
+ if (!cds.db) req.reject('NO_DATABASE_CONNECTION')
20
+
19
21
  const target = ensureDraftsSuffix(req.target.name)
20
22
  const columns = getColumns(this.model.definitions[ensureNoDraftsSuffix(req.target.name)], {
21
23
  keysOnly: true,
@@ -18,6 +18,7 @@ const {
18
18
  } = require('../utils/handler')
19
19
  const { deleteCondition, readAndDeleteKeywords, removeIsActiveEntityRecursively } = require('../utils/where')
20
20
  const { adaptStreamCQN } = require('../../cds-services/adapter/odata-v4/utils/stream')
21
+ const getError = require('../../common/error')
21
22
 
22
23
  const _findSubselect = where => {
23
24
  return where.find((e, i) => {
@@ -1182,7 +1183,7 @@ const _getOriginalColumns = req => {
1182
1183
  const originalColumns = {}
1183
1184
  // expanded columns are handled generically in db
1184
1185
  for (const c of req.query.SELECT.columns) {
1185
- originalColumns[c.ref ? c.ref[c.ref.length - 1] : c.as || c] = true
1186
+ originalColumns[c.as || (c.ref && c.ref[c.ref.length - 1]) || c] = true
1186
1187
  }
1187
1188
 
1188
1189
  return originalColumns
@@ -1226,6 +1227,9 @@ const _postProcess = (result, req, cqnScenario, deleteLastChangeDateTime) => {
1226
1227
  }
1227
1228
  }
1228
1229
 
1230
+ if (result.HasActiveEntity === null) result.HasActiveEntity = false
1231
+ if (result.HasDraftEntity === null) result.HasDraftEntity = false
1232
+ if (result.IsActiveEntity === null) result.IsActiveEntity = false
1229
1233
  return result
1230
1234
  }
1231
1235
 
@@ -1266,6 +1270,8 @@ const _adaptColumns4readAfterWrite = (req, cqnScenario, query4sql) => {
1266
1270
  * @param req
1267
1271
  */
1268
1272
  const fioriGenericRead = async function (req) {
1273
+ if (!cds.db) req.reject('NO_DATABASE_CONNECTION')
1274
+
1269
1275
  const query = req.query
1270
1276
  const originalFrom = _copyCQNPartial(query.SELECT.from)
1271
1277
 
@@ -1298,7 +1304,7 @@ const fioriGenericRead = async function (req) {
1298
1304
  cqnScenario = _generateCQN(reqClone, nonDraftColumns, originalFrom, this.model)
1299
1305
  }
1300
1306
 
1301
- if (!cqnScenario) req.reject(400)
1307
+ if (!cqnScenario) throw getError(501)
1302
1308
 
1303
1309
  // ensure base columns for calculation are selected in draft admin expand
1304
1310
  _adaptDraftAdminExpand(cqnScenario.cqn)
@@ -109,6 +109,8 @@ const _shouldReadOverDraft = (req, definitions) => {
109
109
  * @returns {Promise<Array>}
110
110
  */
111
111
  const _readOverDraftHandler = async function (req, next) {
112
+ if (!cds.db) req.reject('NO_DATABASE_CONNECTION')
113
+
112
114
  const definitions = this.model.definitions
113
115
 
114
116
  // determine whether the request is handled here (read over draft handler),