@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.
- package/CHANGELOG.md +38 -2
- package/README.md +5 -0
- package/apis/services.d.ts +5 -0
- package/bin/build/buildTaskEngine.js +0 -2
- package/bin/build/buildTaskFactory.js +1 -1
- package/bin/build/buildTaskHandler.js +1 -1
- package/bin/build/provider/buildTaskProviderInternal.js +10 -6
- package/bin/build/provider/fiori/index.js +5 -10
- package/bin/build/provider/hana/2migration.js +11 -2
- package/bin/build/provider/hana/index.js +17 -14
- package/bin/build/provider/hana/template/.hdiconfig-hanacloud +137 -0
- package/bin/build/provider/mtx-extension/index.js +18 -1
- package/bin/build/provider/mtx-sidecar/index.js +1 -1
- package/bin/build/util.js +1 -1
- package/bin/cds.js +1 -5
- package/bin/deploy/to-hana/hana.js +10 -3
- package/bin/serve.js +32 -20
- package/lib/auth/jwt-auth.js +4 -4
- package/lib/compile/for/lean_drafts.js +55 -6
- package/lib/dbs/cds-deploy.js +6 -8
- package/lib/index.js +4 -2
- package/lib/req/cds-context.js +3 -3
- package/lib/srv/bindings.js +1 -2
- package/lib/srv/cds-serve.js +2 -1
- package/lib/srv/middlewares/trace.js +31 -15
- package/lib/srv/protocols/odata-v2-proxy.js +8 -8
- package/lib/srv/srv-handlers.js +26 -7
- package/lib/srv/srv-methods.js +2 -2
- package/lib/srv/srv-models.js +3 -3
- package/lib/utils/cds-test.js +7 -5
- package/libx/_runtime/auth/strategies/ias-auth.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +6 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +26 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +8 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +11 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +8 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/validator/ValueValidator.js +14 -14
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +1 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ResourceJsonSerializer.js +3 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/UriHelper.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +3 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +7 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +0 -3
- package/libx/_runtime/cds-services/services/Service.js +8 -19
- package/libx/_runtime/cds-services/services/utils/columns.js +7 -4
- package/libx/_runtime/cds-services/util/assert.js +7 -1
- package/libx/_runtime/common/code-ext/WorkerReq.js +3 -1
- package/libx/_runtime/common/code-ext/execute.js +9 -2
- package/libx/_runtime/common/code-ext/handlers.js +2 -2
- package/libx/_runtime/common/code-ext/worker.js +9 -5
- package/libx/_runtime/common/code-ext/workerQueryExecutor.js +5 -2
- package/libx/_runtime/common/composition/data.js +5 -2
- package/libx/_runtime/common/composition/tree.js +2 -0
- package/libx/_runtime/common/generic/auth/restrict.js +1 -1
- package/libx/_runtime/common/generic/etag.js +3 -1
- package/libx/_runtime/common/generic/input.js +12 -14
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +31 -11
- package/libx/_runtime/common/utils/path.js +0 -1
- package/libx/_runtime/common/utils/search2cqn4sql.js +4 -1
- package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +19 -13
- package/libx/_runtime/db/data-conversion/post-processing.js +1 -1
- package/libx/_runtime/db/expand/expandCQNToJoin.js +5 -3
- package/libx/_runtime/db/expand/rawToExpanded.js +3 -2
- package/libx/_runtime/db/generic/input.js +2 -2
- package/libx/_runtime/db/generic/integrity.js +1 -0
- package/libx/_runtime/db/generic/virtual.js +1 -0
- package/libx/_runtime/db/query/read.js +3 -2
- package/libx/_runtime/fiori/generic/activate.js +3 -1
- package/libx/_runtime/fiori/generic/before.js +1 -0
- package/libx/_runtime/fiori/generic/edit.js +3 -1
- package/libx/_runtime/fiori/generic/new.js +2 -0
- package/libx/_runtime/fiori/generic/patch.js +2 -0
- package/libx/_runtime/fiori/generic/prepare.js +2 -0
- package/libx/_runtime/fiori/generic/read.js +8 -2
- package/libx/_runtime/fiori/generic/readOverDraft.js +2 -0
- package/libx/_runtime/fiori/lean-draft.js +498 -245
- package/libx/_runtime/fiori/utils/delete.js +2 -0
- package/libx/_runtime/messaging/Outbox.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +1 -0
- package/libx/_runtime/messaging/enterprise-messaging.js +2 -6
- package/libx/_runtime/messaging/file-based.js +1 -2
- package/libx/_runtime/messaging/outbox/OutboxRunner.js +1 -1
- package/libx/_runtime/messaging/outbox/utils.js +1 -1
- package/libx/_runtime/messaging/service.js +0 -1
- package/libx/_runtime/remote/Service.js +1 -0
- package/libx/_runtime/sqlite/convertDraftAdminPathExpression.js +19 -3
- package/libx/_runtime/sqlite/customBuilder/CustomExpressionBuilder.js +0 -18
- package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +0 -18
- package/libx/_runtime/sqlite/customBuilder/CustomSelectBuilder.js +0 -24
- package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +2 -1
- package/libx/_runtime/sqlite/customBuilder/index.js +47 -32
- package/libx/odata/afterburner.js +17 -5
- package/libx/odata/grammar.pegjs +3 -4
- package/libx/odata/index.js +5 -1
- package/libx/odata/parseToCqn.js +3 -3
- package/libx/odata/parser.js +1 -1
- package/libx/odata/utils.js +58 -1
- package/package.json +1 -1
- package/server.js +1 -1
- package/libx/_runtime/sqlite/customBuilder/CustomDeleteBuilder.js +0 -17
- package/libx/_runtime/sqlite/customBuilder/CustomReferenceBuilder.js +0 -11
- package/libx/_runtime/sqlite/customBuilder/CustomUpdateBuilder.js +0 -17
- /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
|
|
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
|
-
|
|
254
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
@@ -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
|
|
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
|
|
3
|
-
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
11
|
+
switch (elements[key].type) {
|
|
12
|
+
case 'cds.String':
|
|
13
|
+
quote = "'"
|
|
14
|
+
break
|
|
9
15
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
default:
|
|
17
|
+
quote = ''
|
|
18
|
+
break
|
|
19
|
+
}
|
|
14
20
|
|
|
15
|
-
|
|
16
|
-
|
|
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 &&
|
|
45
|
-
|
|
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
|
|
@@ -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
|
|
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)
|
|
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),
|