@sap/cds 5.8.4 → 5.9.2
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 +198 -77
- package/app/fiori/preview.js +16 -11
- package/app/fiori/routes.js +15 -8
- package/app/index.js +1 -1
- package/bin/build/buildTaskFactory.js +3 -3
- package/bin/build/buildTaskProviderFactory.js +1 -1
- package/bin/build/constants.js +1 -1
- package/bin/build/provider/buildTaskHandlerEdmx.js +12 -7
- package/bin/build/provider/buildTaskHandlerInternal.js +1 -1
- package/bin/build/provider/buildTaskProviderInternal.js +8 -2
- package/bin/build/provider/hana/2migration.js +27 -24
- package/bin/build/provider/hana/index.js +17 -18
- package/bin/build/provider/hana/migrationtable.js +9 -10
- package/bin/build/provider/java-cf/index.js +4 -5
- package/bin/build/provider/node-cf/index.js +99 -6
- package/bin/cds.js +17 -18
- package/bin/deploy/to-hana/cfUtil.js +16 -19
- package/bin/deploy/to-hana/hana.js +7 -24
- package/bin/deploy/to-hana/hdiDeployUtil.js +8 -4
- package/bin/mtx/in-cds.js +2 -2
- package/bin/serve.js +10 -3
- package/bin/utils/modules.js +7 -0
- package/bin/version.js +56 -3
- package/lib/compile/cdsc.js +7 -2
- package/lib/compile/etc/_localized.js +37 -25
- package/lib/compile/etc/csv.js +8 -8
- package/lib/compile/for/drafts.js +9 -0
- package/lib/compile/for/java.js +16 -0
- package/lib/compile/for/nodejs.js +12 -0
- package/lib/compile/index.js +3 -0
- package/lib/compile/minify.js +16 -2
- package/lib/compile/parse.js +2 -2
- package/lib/compile/resolve.js +35 -18
- package/lib/compile/to/json.js +3 -1
- package/lib/compile/to/sql.js +2 -2
- package/lib/compile/to/srvinfo.js +4 -2
- package/lib/connect/bindings.js +1 -1
- package/lib/connect/index.js +3 -4
- package/lib/core/entities.js +15 -14
- package/lib/core/index.js +39 -36
- package/lib/core/reflect.js +4 -2
- package/lib/deploy.js +114 -127
- package/lib/env/defaults.js +1 -0
- package/lib/env/index.js +165 -165
- package/lib/env/presets.js +1 -0
- package/lib/env/requires.js +121 -50
- package/lib/index.js +2 -0
- package/lib/log/format/kibana.js +2 -2
- package/lib/ql/SELECT.js +10 -0
- package/lib/ql/parse.js +1 -0
- package/lib/req/cds-context.js +4 -1
- package/lib/req/context.js +50 -56
- package/lib/req/event.js +1 -6
- package/lib/req/locale.js +6 -5
- package/lib/req/request.js +2 -0
- package/lib/req/user.js +7 -5
- package/lib/serve/Service-api.js +10 -7
- package/lib/serve/Service-dispatch.js +9 -11
- package/lib/serve/Service-methods.js +30 -41
- package/lib/serve/Transaction.js +10 -7
- package/lib/serve/adapters.js +11 -9
- package/lib/serve/factory.js +14 -9
- package/lib/serve/index.js +28 -15
- package/lib/utils/data.js +1 -1
- package/lib/utils/index.js +27 -30
- package/lib/utils/resources/index.js +101 -0
- package/lib/utils/resources/tar.js +71 -0
- package/lib/utils/resources/utils.js +11 -0
- package/libx/_runtime/audit/Service.js +36 -39
- package/libx/_runtime/audit/generic/personal/access.js +3 -4
- package/libx/_runtime/audit/generic/personal/modification.js +3 -4
- package/libx/_runtime/audit/utils/v2.js +1 -2
- package/libx/_runtime/auth/index.js +126 -84
- package/libx/_runtime/auth/strategies/JWT.js +12 -19
- package/libx/_runtime/auth/strategies/dummy.js +1 -5
- package/libx/_runtime/auth/strategies/dwc.js +11 -9
- package/libx/_runtime/auth/strategies/mock.js +0 -4
- package/libx/_runtime/auth/strategies/{utils/xssec.js → xssecUtils.js} +7 -4
- package/libx/_runtime/auth/strategies/xsuaa.js +12 -19
- package/libx/_runtime/auth/utils.js +22 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +104 -98
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +8 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/language.js +2 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +4 -29
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +3 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +4 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +24 -21
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +8 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +2 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/DispatcherCommand.js +2 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -12
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +33 -9
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/dispatcherUtils.js +56 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +10 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +9 -11
- package/libx/_runtime/cds-services/adapter/rest/RestRequest.js +6 -3
- package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +4 -2
- package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/utils.js +1 -1
- package/libx/_runtime/cds-services/adapter/rest/utils/binary.js +1 -1
- package/libx/_runtime/cds-services/adapter/rest/utils/key-value-utils.js +2 -3
- package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +6 -4
- package/libx/_runtime/cds-services/adapter/rest/utils/result.js +1 -0
- package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +8 -5
- package/libx/_runtime/cds-services/services/Service.js +40 -0
- package/libx/_runtime/cds-services/services/utils/columns.js +4 -3
- package/libx/_runtime/cds-services/services/utils/compareJson.js +4 -4
- package/libx/_runtime/cds-services/services/utils/differ.js +3 -3
- package/libx/_runtime/cds-services/services/utils/handlerUtils.js +4 -4
- package/libx/_runtime/cds-services/util/assert.js +20 -14
- package/libx/_runtime/cds.js +9 -1
- package/libx/_runtime/common/aspects/any.js +5 -0
- package/libx/_runtime/common/aspects/entity.js +25 -7
- package/libx/_runtime/common/aspects/utils.js +2 -2
- package/libx/_runtime/common/composition/data.js +6 -0
- package/libx/_runtime/common/composition/insert.js +3 -2
- package/libx/_runtime/common/composition/tree.js +4 -10
- package/libx/_runtime/common/composition/update.js +4 -4
- package/libx/_runtime/common/constants/draft.js +29 -26
- package/libx/_runtime/common/error/constants.js +2 -2
- package/libx/_runtime/common/error/frontend.js +7 -15
- package/libx/_runtime/common/generic/auth/capabilities.js +59 -0
- package/libx/_runtime/common/generic/auth/constants.js +20 -0
- package/libx/_runtime/common/generic/auth/expand.js +54 -0
- package/libx/_runtime/common/generic/auth/index.js +32 -0
- package/libx/_runtime/common/generic/auth/insertOnly.js +15 -0
- package/libx/_runtime/common/generic/auth/readOnly.js +26 -0
- package/libx/_runtime/common/generic/auth/requires.js +34 -0
- package/libx/_runtime/common/generic/auth/restrict.js +298 -0
- package/libx/_runtime/common/generic/auth/restrictions.js +85 -0
- package/libx/_runtime/common/generic/auth/utils.js +213 -0
- package/libx/_runtime/common/generic/crud.js +8 -6
- package/libx/_runtime/common/generic/etag.js +1 -1
- package/libx/_runtime/common/generic/input.js +35 -35
- package/libx/_runtime/common/generic/sorting.js +2 -3
- package/libx/_runtime/common/generic/temporal.js +2 -2
- package/libx/_runtime/common/i18n/messages.properties +1 -1
- package/libx/_runtime/common/toggles/handler.js +21 -0
- package/libx/_runtime/common/utils/copy.js +10 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +111 -35
- package/libx/_runtime/common/utils/csn.js +63 -1
- package/libx/_runtime/common/utils/dollar.js +10 -1
- package/libx/_runtime/common/utils/draft.js +46 -7
- package/libx/_runtime/common/utils/entityFromCqn.js +13 -9
- package/libx/_runtime/common/utils/extensibilityUtils.js +18 -0
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +88 -104
- package/libx/_runtime/common/utils/generateOnCond.js +4 -1
- package/libx/_runtime/common/utils/quotingStyles.js +2 -0
- package/libx/_runtime/common/utils/resolveStructured.js +25 -9
- package/libx/_runtime/common/utils/resolveView.js +4 -1
- package/libx/_runtime/common/utils/rewriteAsterisks.js +3 -16
- package/libx/_runtime/common/utils/structured.js +33 -37
- package/libx/_runtime/common/utils/template.js +17 -8
- package/libx/_runtime/common/utils/templateProcessor.js +28 -28
- package/libx/_runtime/db/data-conversion/post-processing.js +118 -412
- package/libx/_runtime/db/expand/expandCQNToJoin.js +45 -41
- package/libx/_runtime/db/expand/rawToExpanded.js +29 -8
- package/libx/_runtime/db/generic/index.js +1 -3
- package/libx/_runtime/db/generic/input.js +5 -10
- package/libx/_runtime/db/generic/rewrite.js +5 -2
- package/libx/_runtime/db/generic/structured.js +2 -2
- package/libx/_runtime/db/query/delete.js +2 -2
- package/libx/_runtime/db/query/insert.js +1 -1
- package/libx/_runtime/db/query/update.js +9 -14
- package/libx/_runtime/db/sql-builder/CreateBuilder.js +4 -3
- package/libx/_runtime/db/sql-builder/FunctionBuilder.js +8 -8
- package/libx/_runtime/db/sql-builder/InsertBuilder.js +14 -1
- package/libx/_runtime/db/sql-builder/SelectBuilder.js +3 -2
- package/libx/_runtime/db/sql-builder/dataTypes.js +3 -3
- package/libx/_runtime/db/utils/columns.js +3 -3
- package/libx/_runtime/db/utils/normalizeTimeData.js +2 -2
- package/libx/_runtime/db/utils/propagateForeignKeys.js +6 -2
- package/libx/_runtime/extensibility/mps/index.js +5 -0
- package/libx/_runtime/extensibility/mps/service.js +111 -0
- package/libx/_runtime/extensibility/mps/tar.js +42 -0
- package/libx/_runtime/extensibility/mps/utils.js +11 -0
- package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformREAD.js +0 -0
- package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformRESULT.js +17 -5
- package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformWRITE.js +1 -0
- package/libx/_runtime/extensibility/uiflex/index.js +54 -0
- package/libx/_runtime/extensibility/uiflex/service.js +276 -0
- package/libx/_runtime/{fiori → extensibility}/uiflex/utils.js +22 -7
- package/libx/_runtime/fiori/generic/activate.js +2 -2
- package/libx/_runtime/fiori/generic/before.js +4 -4
- package/libx/_runtime/fiori/generic/new.js +3 -3
- package/libx/_runtime/fiori/generic/patch.js +1 -1
- package/libx/_runtime/fiori/generic/read.js +58 -66
- package/libx/_runtime/fiori/generic/readOverDraft.js +74 -16
- package/libx/_runtime/fiori/utils/handler.js +6 -13
- package/libx/_runtime/fiori/utils/where.js +6 -5
- package/libx/_runtime/hana/Service.js +4 -10
- package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +1 -1
- package/libx/_runtime/hana/driver.js +2 -2
- package/libx/_runtime/hana/execute.js +45 -75
- package/libx/_runtime/hana/pool.js +1 -1
- package/libx/_runtime/hana/streaming.js +2 -1
- package/libx/_runtime/index.js +6 -6
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +5 -21
- package/libx/_runtime/messaging/Outbox.js +2 -2
- package/libx/_runtime/messaging/common-utils/AMQPClient.js +4 -14
- package/libx/_runtime/messaging/common-utils/connections.js +5 -7
- package/libx/_runtime/messaging/common-utils/normalizeIncomingMessage.js +30 -0
- package/libx/_runtime/messaging/enterprise-messaging-shared.js +2 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +36 -30
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +19 -12
- package/libx/_runtime/messaging/enterprise-messaging.js +8 -8
- package/libx/_runtime/messaging/file-based.js +5 -5
- package/libx/_runtime/messaging/message-queuing.js +14 -12
- package/libx/_runtime/messaging/outbox/utils.js +18 -19
- package/libx/_runtime/messaging/redis-messaging.js +91 -0
- package/libx/_runtime/messaging/service.js +8 -6
- package/libx/_runtime/remote/Service.js +44 -8
- package/libx/_runtime/remote/utils/client.js +24 -19
- package/libx/_runtime/remote/utils/data.js +11 -11
- package/libx/_runtime/sqlite/Service.js +6 -9
- package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +5 -2
- package/libx/_runtime/types/api.js +10 -2
- package/libx/common/utils/ucsn.js +109 -0
- package/libx/gql/resolvers/crud/update.js +5 -0
- package/libx/gql/resolvers/parse/ast2cqn/columns.js +3 -1
- package/libx/gql/schema/typeDefMap.js +2 -2
- package/libx/odata/afterburner.js +110 -16
- package/libx/odata/cqn2odata.js +24 -27
- package/libx/odata/grammar.pegjs +9 -1
- package/libx/odata/parseToCqn.js +39 -0
- package/libx/odata/parser.js +1 -1
- package/libx/rest/RestAdapter.js +9 -1
- package/libx/rest/middleware/input.js +54 -0
- package/libx/rest/middleware/operation.js +14 -1
- package/libx/rest/middleware/parse.js +11 -7
- package/package.json +2 -2
- package/server.js +34 -19
- package/srv/audit-log.cds +2 -2
- package/srv/flex.cds +8 -2
- package/srv/flex.js +1 -1
- package/srv/mps.cds +23 -0
- package/srv/mps.js +1 -0
- package/libx/_runtime/auth/strategies/utils/uaa.js +0 -21
- package/libx/_runtime/common/generic/auth.js +0 -874
- package/libx/_runtime/common/toggles/alpha.js +0 -43
- package/libx/_runtime/db/generic/arrayed.js +0 -33
- package/libx/_runtime/fiori/uiflex/index.js +0 -35
- package/libx/_runtime/fiori/uiflex/service.js +0 -150
- package/libx/rest/utils/data.js +0 -60
|
@@ -34,9 +34,8 @@ const getSimpleCategory = category => {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
const rowKeysGenerator = eventName => {
|
|
37
|
+
if (eventName === 'UPDATE') return
|
|
37
38
|
return (keyNames, row, template) => {
|
|
38
|
-
if (eventName === 'UPDATE') return
|
|
39
|
-
|
|
40
39
|
for (const keyName of keyNames) {
|
|
41
40
|
if (Object.prototype.hasOwnProperty.call(row, keyName)) {
|
|
42
41
|
continue
|
|
@@ -59,7 +58,10 @@ const _isDraftCoreComputed = (req, element, event) =>
|
|
|
59
58
|
element['@Core.Computed'] &&
|
|
60
59
|
!((event === 'CREATE' && element['@cds.on.insert']) || element['@cds.on.update'])
|
|
61
60
|
|
|
62
|
-
const
|
|
61
|
+
const _isStreamingProperty = (elements, row, property) =>
|
|
62
|
+
Object.values(elements).some(
|
|
63
|
+
element => element['@Core.MediaType'] && element['@Core.MediaType']['='] === property && row[element.name]
|
|
64
|
+
)
|
|
63
65
|
|
|
64
66
|
const _getMediaTypeValue = req =>
|
|
65
67
|
req._.req &&
|
|
@@ -101,15 +103,15 @@ const _processCategory = ({ row, key, category, isRoot, event, value, req, eleme
|
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
// set media type from content-type header if streaming
|
|
104
|
-
const
|
|
106
|
+
const isStreaming = _isStreamingProperty(element.parent.elements, row, key)
|
|
105
107
|
const mtValue = _getMediaTypeValue(req)
|
|
106
|
-
if (category === 'stream' &&
|
|
108
|
+
if (category === 'stream' && isStreaming && mtValue) row[key] = mtValue
|
|
107
109
|
}
|
|
108
110
|
|
|
109
111
|
const processorFn = (errors, req) => {
|
|
110
112
|
const { event } = req
|
|
111
113
|
|
|
112
|
-
return ({ row, key, element, plain, isRoot,
|
|
114
|
+
return ({ row, key, element, plain, isRoot, path }) => {
|
|
113
115
|
const categories = plain.categories
|
|
114
116
|
// ugly pointer passing for sonar
|
|
115
117
|
const value = { mandatory: false, val: row && row[key] }
|
|
@@ -123,7 +125,7 @@ const processorFn = (errors, req) => {
|
|
|
123
125
|
}
|
|
124
126
|
|
|
125
127
|
// REVISIT: Convert checkInputConstraints to template mechanism
|
|
126
|
-
checkInputConstraints({ element, value: value.val, errors,
|
|
128
|
+
checkInputConstraints({ element, value: value.val, errors, path, event })
|
|
127
129
|
}
|
|
128
130
|
}
|
|
129
131
|
|
|
@@ -153,11 +155,11 @@ const _pick = element => {
|
|
|
153
155
|
categories.push('associationEffective')
|
|
154
156
|
}
|
|
155
157
|
|
|
156
|
-
if (element.key && !DRAFT_COLUMNS_MAP[element.name] && element.
|
|
158
|
+
if (element.key && !DRAFT_COLUMNS_MAP[element.name] && element.isUUID) {
|
|
157
159
|
categories.push('uuid')
|
|
158
160
|
}
|
|
159
161
|
|
|
160
|
-
if (element['@Core.
|
|
162
|
+
if (element['@Core.IsMediaType']) categories.push('stream')
|
|
161
163
|
|
|
162
164
|
if (categories.length) return { categories }
|
|
163
165
|
}
|
|
@@ -188,38 +190,34 @@ function _handler(req) {
|
|
|
188
190
|
if (template.elements.size === 0) return
|
|
189
191
|
|
|
190
192
|
const errors = []
|
|
193
|
+
const args = {
|
|
194
|
+
processFn: processorFn(errors, req),
|
|
195
|
+
template,
|
|
196
|
+
pathOptions: {
|
|
197
|
+
rowKeysGenerator: rowKeysGenerator(req.event),
|
|
198
|
+
includeKeyValues: true,
|
|
199
|
+
path: []
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (_isBoundAction(req)) {
|
|
203
|
+
const pathSegment = _getBoundActionBindingParameter(req)
|
|
204
|
+
const keys = req._ && req._.params && req._.params[0]
|
|
205
|
+
if (pathSegment) {
|
|
206
|
+
args.pathOptions.path.push({ key: pathSegment, url: pathSegment })
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (keys && 'IsActiveEntity' in keys) {
|
|
210
|
+
args.pathOptions.extraKeys = { IsActiveEntity: keys.IsActiveEntity }
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
191
214
|
const data = getDataFromCQN(req.query) // REVISIT: req.data should point into req.query
|
|
192
215
|
|
|
193
216
|
enrichDataWithKeysFromWhere(data, req, this)
|
|
194
217
|
|
|
195
218
|
const arrayData = Array.isArray(data) ? data : [data]
|
|
196
219
|
for (const row of arrayData) {
|
|
197
|
-
|
|
198
|
-
let extraKeys
|
|
199
|
-
|
|
200
|
-
if (_isBoundAction(req)) {
|
|
201
|
-
const pathSegment = _getBoundActionBindingParameter(req)
|
|
202
|
-
const keys = req._ && req._.params && req._.params[0]
|
|
203
|
-
pathSegments = pathSegment ? [pathSegment] : []
|
|
204
|
-
|
|
205
|
-
if (keys && 'IsActiveEntity' in keys) {
|
|
206
|
-
extraKeys = { IsActiveEntity: keys.IsActiveEntity }
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const args = {
|
|
211
|
-
processFn: processorFn(errors, req),
|
|
212
|
-
row,
|
|
213
|
-
template,
|
|
214
|
-
pathOptions: {
|
|
215
|
-
extraKeys,
|
|
216
|
-
rowKeysGenerator: rowKeysGenerator(req.event),
|
|
217
|
-
segments: pathSegments,
|
|
218
|
-
includeKeyValues: true
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
templateProcessor(args)
|
|
220
|
+
templateProcessor(Object.assign(args, { row }))
|
|
223
221
|
}
|
|
224
222
|
|
|
225
223
|
setDataFromCQN(req) // REVISIT: req.data should point into req.query
|
|
@@ -257,6 +255,7 @@ const _processActionFunctionRow = (row, param, key, errors, event, service) => {
|
|
|
257
255
|
const _processActionFunction = (row, eventParams, errors, event, service) => {
|
|
258
256
|
for (const key in eventParams) {
|
|
259
257
|
let param = eventParams[key]
|
|
258
|
+
// .type of action/function behaves different to .type of other csn elements
|
|
260
259
|
const _type = param.type
|
|
261
260
|
if (!_type && param.items) param = param.items
|
|
262
261
|
_processActionFunctionRow(row, param, key, errors, event, service)
|
|
@@ -292,6 +291,7 @@ function _actionFunctionHandler(req) {
|
|
|
292
291
|
// REVISIT: find better solution, maybe compiler?
|
|
293
292
|
// resolve enums like format, range, etc.
|
|
294
293
|
for (const param of Object.values(eventParams)) {
|
|
294
|
+
// .type of action/function behaves different to .type of other csn elements
|
|
295
295
|
const _type = param.type && this.model && this.model.definitions[param.type]
|
|
296
296
|
if (_type) {
|
|
297
297
|
param.enum = _type.enum
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
const cds = require('../../cds')
|
|
2
2
|
const LOG = cds.log('app')
|
|
3
3
|
const { getAllKeys } = require('../../cds-services/adapter/odata-v4/odata-to-cqn/utils')
|
|
4
|
-
|
|
5
|
-
const DRAFT_COLUMNS = ['IsActiveEntity', 'HasDraftEntity', 'HasActiveEntity']
|
|
4
|
+
const { DRAFT_COLUMNS_MAP } = require('../../common/constants/draft')
|
|
6
5
|
|
|
7
6
|
const _getStaticOrders = req => {
|
|
8
7
|
const { target: entity, query } = req
|
|
@@ -20,7 +19,7 @@ const _getStaticOrders = req => {
|
|
|
20
19
|
if (cds.env.features.implicit_sorting !== false && (req.target._isSingleton || query.SELECT.limit)) {
|
|
21
20
|
const keys = getAllKeys(entity, true)
|
|
22
21
|
for (const key of keys) {
|
|
23
|
-
if (!
|
|
22
|
+
if (!(key in DRAFT_COLUMNS_MAP) && !defaultOrders.some(o => o.by['='] === key)) {
|
|
24
23
|
ordersFromKeys.push({ by: { '=': key } })
|
|
25
24
|
}
|
|
26
25
|
}
|
|
@@ -21,14 +21,14 @@ const _getTimeDelta = (target, queryOption) => {
|
|
|
21
21
|
|
|
22
22
|
if (
|
|
23
23
|
_isDate(queryOption) ||
|
|
24
|
-
Object.values(target.elements).some(el => el['@cds.valid.from'] && el.
|
|
24
|
+
Object.values(target.elements).some(el => el['@cds.valid.from'] && el._type === 'cds.Date')
|
|
25
25
|
) {
|
|
26
26
|
return 1000 * 60 * 60 * 24
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
if (
|
|
30
30
|
_isTimestamp(queryOption) &&
|
|
31
|
-
Object.values(target.elements).some(el => el['@cds.valid.from'] && el.
|
|
31
|
+
Object.values(target.elements).some(el => el['@cds.valid.from'] && el._type === 'cds.Timestamp')
|
|
32
32
|
) {
|
|
33
33
|
return 1
|
|
34
34
|
}
|
|
@@ -71,7 +71,7 @@ ENTITY_IS_INSERT_ONLY=Entity "{0}" is insert-only
|
|
|
71
71
|
ENTITY_IS_READ_ONLY=Entity "{0}" is read-only
|
|
72
72
|
ENTITY_IS_NOT_CRUD=Entity "{0}" is not {1}
|
|
73
73
|
ENTITY_IS_NOT_CRUD_VIA_NAVIGATION=Entity "{0}" is not {1} via association "{2}"
|
|
74
|
-
ENTITY_IS_AUTOEXPOSED=Entity "{0}" is not
|
|
74
|
+
ENTITY_IS_AUTOEXPOSED=Entity "{0}" is not explicitly exposed as part of the service
|
|
75
75
|
EXPAND_IS_RESTRICTED=Navigation property "{0}" is not allowed for expand operation
|
|
76
76
|
EXPAND_COUNT_UNSUPPORTED="$count" is not supported for expand operation
|
|
77
77
|
ORDERBY_LAMBDA_UNSUPPORTED="$orderby" does not support lambda
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* addition for feature toggles
|
|
3
|
+
*/
|
|
4
|
+
module.exports = cds => {
|
|
5
|
+
if (!cds.requires.toggles) return (req, res, next) => next()
|
|
6
|
+
|
|
7
|
+
return (req, res, next) => {
|
|
8
|
+
// inject features from dwc header
|
|
9
|
+
const fth = req.headers['dwc-product-configuration']
|
|
10
|
+
if (fth) {
|
|
11
|
+
const { features } = JSON.parse(Buffer.from(fth, 'base64').toString('utf-8'))
|
|
12
|
+
req.features = features
|
|
13
|
+
.filter(f => f.enabled)
|
|
14
|
+
.map(f => f.name)
|
|
15
|
+
.sort((a, b) => a.localeCompare(b))
|
|
16
|
+
Object.freeze(req.features)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
next()
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -29,7 +29,16 @@ const deepCopyObject = obj => {
|
|
|
29
29
|
return clone
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
const deepCopy = data => {
|
|
33
|
+
if (Array.isArray(data)) {
|
|
34
|
+
return deepCopyArray(data)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return deepCopyObject(data)
|
|
38
|
+
}
|
|
39
|
+
|
|
32
40
|
module.exports = {
|
|
33
41
|
deepCopyObject,
|
|
34
|
-
deepCopyArray
|
|
42
|
+
deepCopyArray,
|
|
43
|
+
deepCopy
|
|
35
44
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
/* eslint-disable complexity */
|
|
2
|
+
|
|
1
3
|
const cds = require('../../cds')
|
|
2
4
|
const { SELECT, INSERT, DELETE, UPDATE } = cds.ql
|
|
3
5
|
const Query = require('../../../../lib/ql/Query')
|
|
4
6
|
|
|
5
7
|
const { resolveView } = require('./resolveView')
|
|
6
|
-
const { ensureNoDraftsSuffix } = require('./draft')
|
|
8
|
+
const { ensureNoDraftsSuffix, getDraftColumnsCQNForDraft } = require('./draft')
|
|
7
9
|
const { flattenStructuredSelect } = require('./structured')
|
|
8
10
|
const search2cqn4sql = require('./search2cqn4sql')
|
|
9
11
|
const { getEntityNameFromCQN } = require('./entityFromCqn')
|
|
@@ -14,8 +16,12 @@ const { addToWhere } = require('../../common/utils/cqn')
|
|
|
14
16
|
const { removeIsActiveEntityRecursively } = require('../../fiori/utils/where')
|
|
15
17
|
const { addRefToWhereIfNecessary } = require('../../../odata/afterburner')
|
|
16
18
|
const { addAliasToExpression, PARENT_ALIAS, FOREIGN_ALIAS } = require('../../db/utils/generateAliases')
|
|
19
|
+
const { getColumns } = require('../../cds-services/services/utils/columns')
|
|
17
20
|
|
|
18
|
-
const
|
|
21
|
+
const OPERATIONS_MAP = ['=', '>', '<', '!=', '<>', '>=', '<=', 'like', 'between', 'in', 'not in'].reduce((acc, cur) => {
|
|
22
|
+
acc[cur] = 1
|
|
23
|
+
return acc
|
|
24
|
+
}, {})
|
|
19
25
|
|
|
20
26
|
const _elementFromRef = (name, entity) => {
|
|
21
27
|
if (!entity) return
|
|
@@ -104,7 +110,7 @@ const convertPathExpressionToWhere = (fromClause, model, options) => {
|
|
|
104
110
|
}
|
|
105
111
|
}
|
|
106
112
|
|
|
107
|
-
const
|
|
113
|
+
const _convertPathExpressionForInsert = (intoClause, model) => {
|
|
108
114
|
// .into is plain string or csn entity
|
|
109
115
|
if (typeof intoClause === 'string' || intoClause.name) {
|
|
110
116
|
return intoClause
|
|
@@ -177,10 +183,7 @@ const _getWindColumns = (columns, groupBy, bottomTop) => {
|
|
|
177
183
|
return [].concat(columns, _getWindowXpr(groupBy, bottomTop))
|
|
178
184
|
}
|
|
179
185
|
|
|
180
|
-
const _convertCountNavigation = (SELECT,
|
|
181
|
-
const entityName = SELECT.from.ref[0].id || SELECT.from.ref[0]
|
|
182
|
-
const entity = model.definitions[entityName]
|
|
183
|
-
|
|
186
|
+
const _convertCountNavigation = (SELECT, target) => {
|
|
184
187
|
const newWhere = []
|
|
185
188
|
for (let i = 0; i < SELECT.where.length; i++) {
|
|
186
189
|
const element = SELECT.where[i]
|
|
@@ -192,8 +195,8 @@ const _convertCountNavigation = (SELECT, model) => {
|
|
|
192
195
|
|
|
193
196
|
const navigations = element.args[0].ref
|
|
194
197
|
const navigationName = navigations[0].id || navigations[0]
|
|
195
|
-
if (
|
|
196
|
-
let currentEntity =
|
|
198
|
+
if (target.elements[navigationName]) {
|
|
199
|
+
let currentEntity = target
|
|
197
200
|
let topQuery
|
|
198
201
|
let lastQuery
|
|
199
202
|
|
|
@@ -344,7 +347,7 @@ const _getWhereExistsSubSelect = (queryTarget, outerAlias, innerAlias, ref, mode
|
|
|
344
347
|
}
|
|
345
348
|
|
|
346
349
|
subSelect.where(queryTarget._relations[navName].join(innerAlias, outerAlias))
|
|
347
|
-
if (cds.env.effective.odata.structs) {
|
|
350
|
+
if (cds.env.effective.odata.structs || cds.env.features.ucsn_struct_conversion) {
|
|
348
351
|
flattenStructuredSelect(subSelect, model)
|
|
349
352
|
}
|
|
350
353
|
subSelect.columns([{ val: 1 }])
|
|
@@ -459,7 +462,7 @@ const convertWhereExists = (query, model, options, currentTarget) => {
|
|
|
459
462
|
}
|
|
460
463
|
|
|
461
464
|
if (element.xpr) {
|
|
462
|
-
convertWhereExists({ ...query, where: element.xpr }, model, options) // > recursing into nested {xpr}
|
|
465
|
+
convertWhereExists({ ...query, where: element.xpr }, model, options, currentTarget) // > recursing into nested {xpr}
|
|
463
466
|
} else if (element === 'exists' && where[i + 1].ref) {
|
|
464
467
|
if (query.from) {
|
|
465
468
|
query.from.as = outerAlias
|
|
@@ -498,8 +501,9 @@ const _convertNotEqual = (container, partName = 'where') => {
|
|
|
498
501
|
}
|
|
499
502
|
}
|
|
500
503
|
|
|
501
|
-
if (el
|
|
502
|
-
_convertNotEqual(el.SELECT, partName)
|
|
504
|
+
if (el) {
|
|
505
|
+
if (el.SELECT) _convertNotEqual(el.SELECT, partName)
|
|
506
|
+
if (el.xpr) _convertNotEqual(el, 'xpr')
|
|
503
507
|
}
|
|
504
508
|
})
|
|
505
509
|
|
|
@@ -551,7 +555,7 @@ const _convertOrderByIfSkip = (orderByCQN, index) => {
|
|
|
551
555
|
|
|
552
556
|
const _convertWhereIfSkip = (whereCQN, index) => {
|
|
553
557
|
whereCQN.splice(index, 1, '1 = 1')
|
|
554
|
-
|
|
558
|
+
whereCQN[index + 1] in OPERATIONS_MAP ? whereCQN.splice(index + 1, 2) : whereCQN.splice(index - 2, 2)
|
|
555
559
|
}
|
|
556
560
|
|
|
557
561
|
const _convertOrderByOrWhereCQN = (orderByOrWhereCQN, target, model, alias, processFn) => {
|
|
@@ -564,7 +568,7 @@ const _convertOrderByOrWhereCQN = (orderByOrWhereCQN, target, model, alias, proc
|
|
|
564
568
|
|
|
565
569
|
const _convertOrderByOrWhereIfSkip = (cqn, target, model) => {
|
|
566
570
|
const alias = cqn.SELECT.from.as
|
|
567
|
-
if (cqn.SELECT.orderBy && cqn.SELECT.orderBy.length
|
|
571
|
+
if (cqn.SELECT.orderBy && cqn.SELECT.orderBy.length) {
|
|
568
572
|
_convertOrderByOrWhereCQN(cqn.SELECT.orderBy, target, model, alias, _convertOrderByIfSkip)
|
|
569
573
|
}
|
|
570
574
|
|
|
@@ -659,7 +663,11 @@ const _convertPathExpression = (SELECT, model, options = {}) => {
|
|
|
659
663
|
// Okra always wants to have the key values, remove once we relax this requirement
|
|
660
664
|
if (model.definitions[target] && model.definitions[target].keys) {
|
|
661
665
|
SELECT.columns = Object.keys(model.definitions[target].keys)
|
|
662
|
-
.filter(
|
|
666
|
+
.filter(
|
|
667
|
+
k =>
|
|
668
|
+
!model.definitions[target].keys[k].isAssociation &&
|
|
669
|
+
!columns.find(element => element.ref && element.ref[element.ref.length - 1] === k)
|
|
670
|
+
)
|
|
663
671
|
.map(k => ({ ref: [k] }))
|
|
664
672
|
} else SELECT.columns = []
|
|
665
673
|
}
|
|
@@ -679,8 +687,39 @@ const _convertPathExpression = (SELECT, model, options = {}) => {
|
|
|
679
687
|
}
|
|
680
688
|
}
|
|
681
689
|
|
|
690
|
+
const _convertToOneEqNullInFilter = (query, target) => {
|
|
691
|
+
// we do not handle join or union
|
|
692
|
+
if (!target) return
|
|
693
|
+
|
|
694
|
+
for (let i = 0; i < query.where.length; i++) {
|
|
695
|
+
const w = query.where[i]
|
|
696
|
+
const w2 = query.where[i + 2]
|
|
697
|
+
if (!w2 || !w.ref || w2.val !== null) {
|
|
698
|
+
continue
|
|
699
|
+
}
|
|
700
|
+
const element = target.elements[w.ref[w.ref.length - 1]]
|
|
701
|
+
if (element && element.is2one && !element.on) {
|
|
702
|
+
const foreignKeys = Object.values(element.parent.elements).filter(e => e._foreignKey4 === element.name)
|
|
703
|
+
const replacedKeys = foreignKeys.reduce((arr, e, idx) => {
|
|
704
|
+
arr.push({ ref: [...w.ref.slice(0, w.ref.length - 1), e.name] }, query.where[i + 1], query.where[i + 2])
|
|
705
|
+
if (idx < foreignKeys.length - 1) {
|
|
706
|
+
arr.push('and')
|
|
707
|
+
}
|
|
708
|
+
return arr
|
|
709
|
+
}, [])
|
|
710
|
+
|
|
711
|
+
query.where.splice(i, 3, '(', ...replacedKeys, ')')
|
|
712
|
+
i += replacedKeys.length + 2
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
682
717
|
// eslint-disable-next-line complexity
|
|
683
718
|
const _convertSelect = (query, model, _options) => {
|
|
719
|
+
const { _initial } = _options
|
|
720
|
+
if (_initial) {
|
|
721
|
+
delete _options._initial
|
|
722
|
+
}
|
|
684
723
|
const options = Object.assign(
|
|
685
724
|
{
|
|
686
725
|
_4db: _options.service instanceof cds.DatabaseService,
|
|
@@ -688,6 +727,7 @@ const _convertSelect = (query, model, _options) => {
|
|
|
688
727
|
},
|
|
689
728
|
_options
|
|
690
729
|
)
|
|
730
|
+
|
|
691
731
|
// ensure query is ql enabled
|
|
692
732
|
if (!(query instanceof Query)) Object.setPrototypeOf(query, Object.getPrototypeOf(SELECT()))
|
|
693
733
|
if (query.SELECT.from && query.SELECT.from.SELECT) {
|
|
@@ -695,9 +735,6 @@ const _convertSelect = (query, model, _options) => {
|
|
|
695
735
|
query.SELECT.from = _convertSelect(query.SELECT.from, model, options)
|
|
696
736
|
}
|
|
697
737
|
|
|
698
|
-
// REVISIT: a temporary workaround for xpr from new parser
|
|
699
|
-
if (cds.env.features.odata_new_parser) _flattenCQN(query)
|
|
700
|
-
|
|
701
738
|
// lambda functions
|
|
702
739
|
convertWhereExists(query.SELECT, model, options)
|
|
703
740
|
|
|
@@ -709,8 +746,14 @@ const _convertSelect = (query, model, _options) => {
|
|
|
709
746
|
|
|
710
747
|
_convertPathExpression(query.SELECT, model, options)
|
|
711
748
|
rewriteAsterisks(query, model, options)
|
|
712
|
-
if (query.SELECT.where
|
|
713
|
-
|
|
749
|
+
if (query.SELECT.where) {
|
|
750
|
+
const entityName =
|
|
751
|
+
(query.SELECT.from.ref && (query.SELECT.from.ref[0].id || query.SELECT.from.ref[0])) || query.SELECT.from
|
|
752
|
+
const target = model.definitions[entityName]
|
|
753
|
+
if (_isCountNavigation(query.SELECT.where)) {
|
|
754
|
+
_convertCountNavigation(query.SELECT, target)
|
|
755
|
+
}
|
|
756
|
+
_convertToOneEqNullInFilter(query.SELECT, target)
|
|
714
757
|
}
|
|
715
758
|
|
|
716
759
|
// extract where clause if it is in column expand ref
|
|
@@ -727,7 +770,7 @@ const _convertSelect = (query, model, _options) => {
|
|
|
727
770
|
search2cqn4sql(query, model, { ...query._searchOptions, ...{ entityName, alias } })
|
|
728
771
|
}
|
|
729
772
|
|
|
730
|
-
if (query.SELECT.columns && cds.env.effective.odata.structs) {
|
|
773
|
+
if (query.SELECT.columns && (cds.env.effective.odata.structs || cds.env.features.ucsn_struct_conversion)) {
|
|
731
774
|
flattenStructuredSelect(query, model)
|
|
732
775
|
}
|
|
733
776
|
|
|
@@ -736,30 +779,46 @@ const _convertSelect = (query, model, _options) => {
|
|
|
736
779
|
_createWindowCQN(query.SELECT, model)
|
|
737
780
|
}
|
|
738
781
|
|
|
782
|
+
// best-effort ensure columns
|
|
783
|
+
if (options._4db && !query.SELECT.columns) {
|
|
784
|
+
let target = query._target
|
|
785
|
+
if (target && target._unresolved && typeof target.name === 'string') {
|
|
786
|
+
target = model.definitions[ensureNoDraftsSuffix(target.name)] || target
|
|
787
|
+
}
|
|
788
|
+
if (target && !Object.prototype.hasOwnProperty.call(target, '_unresolved')) {
|
|
789
|
+
const cols = getColumns(target, { onlyNames: true, filterVirtual: true })
|
|
790
|
+
query.columns(cols)
|
|
791
|
+
if (target._isDraftEnabled && query._target._unresolved) {
|
|
792
|
+
query.SELECT.columns.push(...getDraftColumnsCQNForDraft(target))
|
|
793
|
+
query.SELECT.columns.push({ ref: ['DraftAdministrativeData_DraftUUID'] })
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// temporary workaround for xpr - cds v5.9.2 only
|
|
799
|
+
if (_initial) _flattenCQN(query)
|
|
800
|
+
|
|
739
801
|
return query
|
|
740
802
|
}
|
|
741
803
|
|
|
742
804
|
const _convertInsert = (query, model, options) => {
|
|
743
805
|
// resolve path expression
|
|
744
|
-
const resolvedIntoClause =
|
|
806
|
+
const resolvedIntoClause = _convertPathExpressionForInsert(query.INSERT.into, model)
|
|
745
807
|
|
|
746
808
|
// overwrite only .into, foreign keys are already set
|
|
747
|
-
|
|
809
|
+
// 'a' added as placeholder since its overwritten by Object.assign below
|
|
810
|
+
const insert = INSERT.into('a')
|
|
748
811
|
|
|
749
812
|
// REVISIT flatten structured types, currently its done in SQL builder
|
|
750
813
|
|
|
751
814
|
// We add all previous properties ot the newly created query.
|
|
752
815
|
// Reason is to not lose the query API functionality
|
|
753
|
-
Object.assign(insert.INSERT, query.INSERT, { into: resolvedIntoClause })
|
|
754
|
-
|
|
755
|
-
const targetName = insert.INSERT.into.name || insert.INSERT.into
|
|
816
|
+
Object.assign(insert.INSERT, query.INSERT, { into: { ref: [resolvedIntoClause], as: query.INSERT.into.as } })
|
|
756
817
|
|
|
757
|
-
const target = model.definitions[
|
|
818
|
+
const target = model.definitions[resolvedIntoClause]
|
|
758
819
|
if (!target) return insert
|
|
759
820
|
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
return resolvedView
|
|
821
|
+
return resolveView(insert, model, cds.db)
|
|
763
822
|
}
|
|
764
823
|
|
|
765
824
|
function _modifyNavigationInWhere(whereClause, target) {
|
|
@@ -783,6 +842,11 @@ function _modifyNavigationInWhere(whereClause, target) {
|
|
|
783
842
|
const _plainDelete = (cqn, model) => {
|
|
784
843
|
const name = cqn.DELETE.from.name || (cqn.DELETE.from.ref && cqn.DELETE.from.ref[0]) || cqn.DELETE.from
|
|
785
844
|
const target = model.definitions[name]
|
|
845
|
+
|
|
846
|
+
if (cqn.DELETE.where) {
|
|
847
|
+
_convertToOneEqNullInFilter(cqn.DELETE, target)
|
|
848
|
+
}
|
|
849
|
+
|
|
786
850
|
if (!target) return cqn
|
|
787
851
|
|
|
788
852
|
return resolveView(cqn, model, cds.db)
|
|
@@ -804,9 +868,13 @@ const _convertDelete = (query, model, options) => {
|
|
|
804
868
|
|
|
805
869
|
if (alias) deleet.DELETE.from = { ref: [target], as: alias }
|
|
806
870
|
if (where) deleet.where(where)
|
|
807
|
-
if (query.DELETE.where) deleet.where(addAliasToExpression(query.DELETE.where, alias))
|
|
808
871
|
|
|
809
872
|
const targetEntity = model.definitions[target]
|
|
873
|
+
if (query.DELETE.where) {
|
|
874
|
+
deleet.where(addAliasToExpression(query.DELETE.where, alias))
|
|
875
|
+
_convertToOneEqNullInFilter(deleet.DELETE, targetEntity)
|
|
876
|
+
}
|
|
877
|
+
|
|
810
878
|
if (!targetEntity) return deleet
|
|
811
879
|
|
|
812
880
|
return resolveView(deleet, model, cds.db)
|
|
@@ -815,6 +883,11 @@ const _convertDelete = (query, model, options) => {
|
|
|
815
883
|
function _plainUpdate(cqn, model) {
|
|
816
884
|
const name = cqn.UPDATE.entity.name || (cqn.UPDATE.entity.ref && cqn.UPDATE.entity.ref[0]) || cqn.UPDATE.entity
|
|
817
885
|
const target = model.definitions[name]
|
|
886
|
+
|
|
887
|
+
if (cqn.UPDATE.where) {
|
|
888
|
+
_convertToOneEqNullInFilter(cqn.UPDATE, target)
|
|
889
|
+
}
|
|
890
|
+
|
|
818
891
|
if (!target) return cqn
|
|
819
892
|
|
|
820
893
|
return resolveView(cqn, model, cds.db)
|
|
@@ -841,9 +914,12 @@ const _convertUpdate = (query, model, options) => {
|
|
|
841
914
|
|
|
842
915
|
if (alias) update.UPDATE.entity = { ref: [target], as: alias }
|
|
843
916
|
if (where) update.where(where)
|
|
844
|
-
if (query.UPDATE.where) update.where(addAliasToExpression(query.UPDATE.where, alias))
|
|
845
|
-
|
|
846
917
|
const targetEntity = model.definitions[target]
|
|
918
|
+
if (query.UPDATE.where) {
|
|
919
|
+
update.where(addAliasToExpression(query.UPDATE.where, alias))
|
|
920
|
+
_convertToOneEqNullInFilter(update.UPDATE, targetEntity)
|
|
921
|
+
}
|
|
922
|
+
|
|
847
923
|
if (!targetEntity) return update
|
|
848
924
|
|
|
849
925
|
return resolveView(update, model, cds.db)
|
|
@@ -861,7 +937,7 @@ const _convertUpdate = (query, model, options) => {
|
|
|
861
937
|
*/
|
|
862
938
|
const cqn2cqn4sql = (query, model, options = { suppressSearch: false }) => {
|
|
863
939
|
if (query.SELECT) {
|
|
864
|
-
return _convertSelect(query, model, options)
|
|
940
|
+
return _convertSelect(query, model, Object.assign(options, { _initial: true }))
|
|
865
941
|
}
|
|
866
942
|
|
|
867
943
|
if (query.UPDATE) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const cds = require('../../cds')
|
|
2
|
+
const resolveStructured = require('../../common/utils/resolveStructured')
|
|
2
3
|
|
|
3
4
|
const { ensureNoDraftsSuffix } = require('./draft')
|
|
4
5
|
|
|
@@ -140,6 +141,7 @@ const findCsnTargetFor = (edmName, model, namespace) => {
|
|
|
140
141
|
// probably, a combination of '_' and '.', resolving
|
|
141
142
|
const finding = _findRootEntity(model, edmName, namespace)
|
|
142
143
|
target = finding.target
|
|
144
|
+
|
|
143
145
|
// something left in navigation path => x4 navigation
|
|
144
146
|
// resolving within found entity
|
|
145
147
|
if (target && finding.left > 0) {
|
|
@@ -151,10 +153,12 @@ const findCsnTargetFor = (edmName, model, namespace) => {
|
|
|
151
153
|
}
|
|
152
154
|
}
|
|
153
155
|
}
|
|
156
|
+
|
|
154
157
|
// remember edm <-> csn
|
|
155
158
|
if (target) {
|
|
156
159
|
mapping[edmName] = target
|
|
157
160
|
}
|
|
161
|
+
|
|
158
162
|
return mapping[edmName]
|
|
159
163
|
}
|
|
160
164
|
|
|
@@ -191,7 +195,40 @@ const isRootEntity = (definitions, entityName) => {
|
|
|
191
195
|
return true
|
|
192
196
|
}
|
|
193
197
|
|
|
198
|
+
function _alias2RefRest(service) {
|
|
199
|
+
for (const each of Object.values(service.entities)) {
|
|
200
|
+
each._alias2ref = {}
|
|
201
|
+
const keys = each.keys
|
|
202
|
+
for (const key in keys) {
|
|
203
|
+
if (keys[key].elements) {
|
|
204
|
+
const structKeys = resolveStructured({ structName: key, structProperties: [] }, keys[key].elements, false, true)
|
|
205
|
+
for (const structKey of structKeys) {
|
|
206
|
+
if (each._alias2ref[structKey.key] != null) {
|
|
207
|
+
// key clash, aliasing not possible
|
|
208
|
+
each._alias2ref = {}
|
|
209
|
+
return
|
|
210
|
+
}
|
|
211
|
+
each._alias2ref[structKey.key] = structKey.resolved
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const prefixForStruct = element => {
|
|
219
|
+
const prefixes = []
|
|
220
|
+
let parent = element.parent
|
|
221
|
+
while (parent && parent.kind !== 'entity') {
|
|
222
|
+
prefixes.push(parent.name)
|
|
223
|
+
parent = parent.parent
|
|
224
|
+
}
|
|
225
|
+
return prefixes.length ? prefixes.reverse().join('_') + '_' : ''
|
|
226
|
+
}
|
|
227
|
+
|
|
194
228
|
function alias2ref(service, edm) {
|
|
229
|
+
if (!edm) {
|
|
230
|
+
return _alias2RefRest(service)
|
|
231
|
+
}
|
|
195
232
|
const defs = edm[service.definition.name]
|
|
196
233
|
for (const each of Object.values(service.entities)) {
|
|
197
234
|
const def = defs[each.name.replace(service.definition.name + '.', '').replace(/\./g, '_')]
|
|
@@ -205,6 +242,29 @@ function alias2ref(service, edm) {
|
|
|
205
242
|
}
|
|
206
243
|
}
|
|
207
244
|
|
|
245
|
+
function getDraftTreeRoot(entity, model) {
|
|
246
|
+
if (entity.own('__draftTreeRoot')) return entity.__draftTreeRoot
|
|
247
|
+
|
|
248
|
+
let parent
|
|
249
|
+
let current = entity
|
|
250
|
+
while (current && !current['@Common.DraftRoot.ActivationAction']) {
|
|
251
|
+
const parents = []
|
|
252
|
+
for (const k in model.definitions) {
|
|
253
|
+
const e = model.definitions[k]
|
|
254
|
+
if (e.kind !== 'entity' || !e.compositions) continue
|
|
255
|
+
for (const c in e.compositions) if (e.compositions[c].target === current.name) parents.push(e)
|
|
256
|
+
}
|
|
257
|
+
if (parents.length > 1 && parents.some(p => p !== parents[0])) {
|
|
258
|
+
// > unable to determine single parent
|
|
259
|
+
parent = undefined
|
|
260
|
+
break
|
|
261
|
+
}
|
|
262
|
+
current = parent = parents[0]
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return entity.set('__draftTreeRoot', parent)
|
|
266
|
+
}
|
|
267
|
+
|
|
208
268
|
module.exports = {
|
|
209
269
|
getEtagElement,
|
|
210
270
|
findCsnTargetFor,
|
|
@@ -212,5 +272,7 @@ module.exports = {
|
|
|
212
272
|
isRootEntity,
|
|
213
273
|
getDataSubject,
|
|
214
274
|
alias2ref,
|
|
215
|
-
getComp2oneParents
|
|
275
|
+
getComp2oneParents,
|
|
276
|
+
prefixForStruct,
|
|
277
|
+
getDraftTreeRoot
|
|
216
278
|
}
|
|
@@ -11,7 +11,16 @@ module.exports = (entryOrRow, keyOrIndex, user, timestamp) => {
|
|
|
11
11
|
else if (entryOrRow[keyOrIndex] === '$now') entryOrRow[keyOrIndex] = timestamp
|
|
12
12
|
else if (entryOrRow[keyOrIndex] === '$uuid') entryOrRow[keyOrIndex] = cds.utils.uuid()
|
|
13
13
|
else if (typeof entryOrRow[keyOrIndex] === 'string') {
|
|
14
|
+
// NOTE: with xsuaa, user attributes are always arrays
|
|
14
15
|
const attr = entryOrRow[keyOrIndex].match(/^\$user\.(.*)/)
|
|
15
|
-
if (attr && attr.length > 1)
|
|
16
|
+
if (attr && attr.length > 1) {
|
|
17
|
+
const val = (user.attr && user.attr[attr[1]]) || null
|
|
18
|
+
if (Array.isArray(val)) {
|
|
19
|
+
if (val.length > 1) entryOrRow[keyOrIndex] = JSON.stringify(val)
|
|
20
|
+
else entryOrRow[keyOrIndex] = val.length > 0 ? val[0] : null
|
|
21
|
+
} else {
|
|
22
|
+
entryOrRow[keyOrIndex] = val
|
|
23
|
+
}
|
|
24
|
+
}
|
|
16
25
|
}
|
|
17
26
|
}
|