@sap/cds 5.8.3 → 5.9.1
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 +193 -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 +36 -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/index.js +1 -1
- 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 +120 -49
- package/lib/index.js +1 -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 +7 -5
- package/lib/serve/index.js +24 -12
- 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 +2 -2
- 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 +51 -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/services/utils/restrictions.js +78 -0
- 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 +296 -0
- package/libx/_runtime/common/generic/auth/utils.js +213 -0
- package/libx/_runtime/common/generic/crud.js +14 -10
- 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 +100 -29
- 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 +5 -2
- 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 +71 -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 +27 -74
- 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
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
const cds = require('../../_runtime/cds')
|
|
2
|
+
const getTemplate = require('../../_runtime/common/utils/template')
|
|
3
|
+
const templateProcessor = require('../../_runtime/common/utils/templateProcessor')
|
|
4
|
+
const IS_PROXY = Symbol('flat2structProxy')
|
|
5
|
+
|
|
6
|
+
const proxifyIfFlattened = (definition, payload) => {
|
|
7
|
+
if (!definition || !definition._flat2struct || payload == null || payload[IS_PROXY]) return payload
|
|
8
|
+
return Object.setPrototypeOf(
|
|
9
|
+
payload,
|
|
10
|
+
new Proxy(
|
|
11
|
+
{},
|
|
12
|
+
{
|
|
13
|
+
get: function (_, k, cur) {
|
|
14
|
+
if (k === IS_PROXY) return true
|
|
15
|
+
if (!definition._flat2struct[k]) return Reflect.get(...arguments)
|
|
16
|
+
const segments = definition._flat2struct[k]
|
|
17
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
18
|
+
cur = cur[segments[i]]
|
|
19
|
+
if (!cur) return cur
|
|
20
|
+
}
|
|
21
|
+
return cur[segments[segments.length - 1]]
|
|
22
|
+
},
|
|
23
|
+
set: function (_, k, v, o) {
|
|
24
|
+
let cur = o
|
|
25
|
+
if (definition._flat2struct[k]) {
|
|
26
|
+
const segments = definition._flat2struct[k]
|
|
27
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
28
|
+
if (!cur[segments[i]]) {
|
|
29
|
+
cur[segments[i]] = {}
|
|
30
|
+
}
|
|
31
|
+
cur = cur[segments[i]]
|
|
32
|
+
}
|
|
33
|
+
cur[segments[segments.length - 1]] = v
|
|
34
|
+
} else if (k === IS_PROXY) {
|
|
35
|
+
// do nothing
|
|
36
|
+
} else {
|
|
37
|
+
Reflect.set(...arguments)
|
|
38
|
+
}
|
|
39
|
+
return o
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const _picker = element => {
|
|
47
|
+
if (Array.isArray(element)) return { category: 'flat leaf' }
|
|
48
|
+
if (element.isAssociation) return { category: 'node' }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const _processor = ({ row, key, plain: { category }, element }) => {
|
|
52
|
+
if (!(key in row)) return
|
|
53
|
+
if (category === 'node') {
|
|
54
|
+
row[key] = Array.isArray(row[key])
|
|
55
|
+
? row[key].map(data => proxifyIfFlattened(element._target, data))
|
|
56
|
+
: proxifyIfFlattened(element._target, row[key])
|
|
57
|
+
} else if (category === 'flat leaf') {
|
|
58
|
+
const data = row[key]
|
|
59
|
+
delete row[key]
|
|
60
|
+
row[key] = data
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const _cleanup = (row, definition, cleanupNull) => {
|
|
65
|
+
if (!row || !definition) return
|
|
66
|
+
const elements = definition.elements || definition.params
|
|
67
|
+
for (const key of Object.keys(row)) {
|
|
68
|
+
const element = elements[key]
|
|
69
|
+
if (!element) {
|
|
70
|
+
if (!definition['@open']) delete row[key]
|
|
71
|
+
continue
|
|
72
|
+
}
|
|
73
|
+
if (!row[key]) continue
|
|
74
|
+
if (element.isAssociation) {
|
|
75
|
+
if (element.is2many) {
|
|
76
|
+
for (const r of row[key]) {
|
|
77
|
+
_cleanup(r, element._target, cleanupNull)
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
_cleanup(row[key], element._target, cleanupNull)
|
|
81
|
+
}
|
|
82
|
+
} else if (element.elements) {
|
|
83
|
+
_cleanup(row[key], element, cleanupNull)
|
|
84
|
+
if (cleanupNull && Object.values(row[key]).every(v => v == null)) row[key] = null
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function convertStructured(service, definition, data, { cleanupNull = false } = {}) {
|
|
90
|
+
if (!definition) return
|
|
91
|
+
// REVISIT check `structs` mode only for now as uCSN is not yet available
|
|
92
|
+
const flatAccess = cds.env.features.compat_flat_access
|
|
93
|
+
const template = getTemplate('universal-input', service, definition, { pick: _picker, flatAccess })
|
|
94
|
+
const arrayData = Array.isArray(data) ? data : [data]
|
|
95
|
+
if (template && template.elements.size) {
|
|
96
|
+
for (let i = 0; i < arrayData.length; i++) {
|
|
97
|
+
const row = proxifyIfFlattened(definition, arrayData[i])
|
|
98
|
+
templateProcessor({ processFn: _processor, row, template, pathOptions: { path: [] } })
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
for (const row of arrayData) {
|
|
102
|
+
_cleanup(row, definition, cleanupNull)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = {
|
|
107
|
+
convertStructured,
|
|
108
|
+
proxifyIfFlattened
|
|
109
|
+
}
|
|
@@ -29,6 +29,11 @@ module.exports = async (service, entityFQN, selection) => {
|
|
|
29
29
|
cds.context = tx
|
|
30
30
|
// read needs to be done before the update, otherwise the where clause might become invalid (case that properties in where clause are updated by the mutation)
|
|
31
31
|
resultBeforeUpdate = await tx.run(queryBeforeUpdate)
|
|
32
|
+
|
|
33
|
+
if (resultBeforeUpdate.length === 0) {
|
|
34
|
+
return []
|
|
35
|
+
}
|
|
36
|
+
|
|
32
37
|
return tx.run(query)
|
|
33
38
|
})
|
|
34
39
|
|
|
@@ -14,7 +14,9 @@ const astToColumns = selections => {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
if (selection.selectionSet && selection.selectionSet.selections) {
|
|
17
|
-
|
|
17
|
+
const columns = astToColumns(selection.selectionSet.selections)
|
|
18
|
+
// columns is empty if only __typename was selected (which was filtered out in the enriched AST)
|
|
19
|
+
column.expand = columns.length > 0 ? columns : ['*']
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
const filter = getArgumentByName(selection.arguments, ARGUMENT.FILTER)
|
|
@@ -30,8 +30,8 @@ const servicesToTypeDefMap = services => {
|
|
|
30
30
|
// TODO structured types
|
|
31
31
|
continue
|
|
32
32
|
} else {
|
|
33
|
-
if (CDS_TO_GRAPHQL_TYPES[ele.
|
|
34
|
-
def[ele.name] = CDS_TO_GRAPHQL_TYPES[ele.
|
|
33
|
+
if (CDS_TO_GRAPHQL_TYPES[ele._type]) {
|
|
34
|
+
def[ele.name] = CDS_TO_GRAPHQL_TYPES[ele._type]
|
|
35
35
|
}
|
|
36
36
|
// TODO aspects
|
|
37
37
|
}
|
|
@@ -3,10 +3,20 @@ const cds = require('../_runtime/cds')
|
|
|
3
3
|
const { where2obj } = require('../_runtime/common/utils/cqn')
|
|
4
4
|
const { findCsnTargetFor } = require('../_runtime/common/utils/csn')
|
|
5
5
|
|
|
6
|
-
const _addKeysDeep = (keys, keysCollector) => {
|
|
6
|
+
const _addKeysDeep = (keys, keysCollector, ignoreManagedBacklinks) => {
|
|
7
7
|
for (const keyName in keys) {
|
|
8
8
|
const key = keys[keyName]
|
|
9
|
-
|
|
9
|
+
const foreignKey = key._foreignKey4
|
|
10
|
+
if (key.isAssociation || foreignKey === 'up_' || key['@cds.api.ignore'] === true) continue
|
|
11
|
+
|
|
12
|
+
if (ignoreManagedBacklinks && foreignKey) {
|
|
13
|
+
const navigationElement = keys[foreignKey]
|
|
14
|
+
if (!navigationElement.on && navigationElement._isBacklink) {
|
|
15
|
+
// skip navigation elements that are backlinks
|
|
16
|
+
continue
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
10
20
|
if ('elements' in key) {
|
|
11
21
|
_addKeysDeep(key.elements, keysCollector)
|
|
12
22
|
continue
|
|
@@ -15,10 +25,10 @@ const _addKeysDeep = (keys, keysCollector) => {
|
|
|
15
25
|
}
|
|
16
26
|
}
|
|
17
27
|
|
|
18
|
-
function _keysOf(entity) {
|
|
28
|
+
function _keysOf(entity, ignoreManagedBacklinks) {
|
|
19
29
|
if (!entity || !entity.keys) return
|
|
20
30
|
const keysCollector = []
|
|
21
|
-
_addKeysDeep(entity.keys, keysCollector)
|
|
31
|
+
_addKeysDeep(entity.keys, keysCollector, ignoreManagedBacklinks)
|
|
22
32
|
return keysCollector
|
|
23
33
|
}
|
|
24
34
|
|
|
@@ -31,18 +41,47 @@ function _getDefinition(definition, name, namespace) {
|
|
|
31
41
|
)
|
|
32
42
|
}
|
|
33
43
|
|
|
44
|
+
function _resolveAliasInParams(params, entity) {
|
|
45
|
+
if (!entity._alias2ref) return
|
|
46
|
+
const paramKeys = Object.keys(params)
|
|
47
|
+
for (const paramKey of paramKeys) {
|
|
48
|
+
if (entity._alias2ref[paramKey]) {
|
|
49
|
+
params[entity._alias2ref[paramKey].join('_')] = params[paramKey]
|
|
50
|
+
params[paramKey] = undefined
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
34
55
|
function _resolveAliasInWhere(where, entity) {
|
|
35
56
|
if (!entity._alias2ref) return
|
|
36
|
-
for (
|
|
37
|
-
if (!
|
|
38
|
-
|
|
57
|
+
for (const w of where) {
|
|
58
|
+
if (!w.ref || w.ref.length > 1 || entity.keys[w.ref[0]]) continue
|
|
59
|
+
w.ref = entity._alias2ref[w.ref[0]] || w.ref
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function _addDefaultParams(ref, view) {
|
|
64
|
+
const params = view.params
|
|
65
|
+
const defaults = params && Object.values(params).filter(p => p.default)
|
|
66
|
+
if (defaults && defaults.length > 0) {
|
|
67
|
+
if (!ref.where) ref.where = []
|
|
68
|
+
for (const def of defaults) {
|
|
69
|
+
if (ref.where.find(e => e.ref && e.ref[0] === def.name)) {
|
|
70
|
+
continue
|
|
71
|
+
}
|
|
72
|
+
if (ref.where.length > 0) ref.where.push('and')
|
|
73
|
+
ref.where.push({ ref: [def.name] }, '=', { val: def.default.val })
|
|
74
|
+
}
|
|
39
75
|
}
|
|
40
76
|
}
|
|
41
77
|
|
|
42
78
|
// case: single key without name, e.g., Foo(1)
|
|
43
79
|
function addRefToWhereIfNecessary(where, entity) {
|
|
44
80
|
if (!where || where.length !== 1) return 0
|
|
45
|
-
|
|
81
|
+
|
|
82
|
+
const isView = !!entity.params
|
|
83
|
+
|
|
84
|
+
const keys = isView ? Object.keys(entity.params) : _keysOf(entity)
|
|
46
85
|
if (keys.length !== 1) return 0
|
|
47
86
|
where.unshift(...[{ ref: [keys[0]] }, '='])
|
|
48
87
|
return 1
|
|
@@ -64,13 +103,14 @@ function _processSegments(cqn, model, namespace) {
|
|
|
64
103
|
|
|
65
104
|
if (incompleteKeys) {
|
|
66
105
|
// > key
|
|
67
|
-
keys = keys || _keysOf(current)
|
|
68
|
-
|
|
106
|
+
keys = keys || _keysOf(current, !cds.env.features.rest_new_adapter && !cds.env.features.rest_new_parser) // if odata skip backlinks as key as they are used from structure
|
|
107
|
+
let key = keys[keyCount++]
|
|
69
108
|
one = true
|
|
70
109
|
const element = current.elements[key]
|
|
71
110
|
let base = ref[i - keyCount]
|
|
72
111
|
if (!base.id) base = { id: base, where: [] }
|
|
73
112
|
if (base.where.length) base.where.push('and')
|
|
113
|
+
|
|
74
114
|
if (ref[i].id) {
|
|
75
115
|
// > fix case key value parsed to collection with filter
|
|
76
116
|
const val = `${ref[i].id}(${Object.keys(params)
|
|
@@ -78,7 +118,7 @@ function _processSegments(cqn, model, namespace) {
|
|
|
78
118
|
.join(',')})`
|
|
79
119
|
base.where.push({ ref: [key] }, '=', { val })
|
|
80
120
|
} else {
|
|
81
|
-
base.where.push({ ref: [key] }, '=', { val: element.
|
|
121
|
+
base.where.push({ ref: [key] }, '=', { val: element._type === 'cds.Integer' ? Number(seg) : seg })
|
|
82
122
|
}
|
|
83
123
|
ref[i] = null
|
|
84
124
|
ref[i - keyCount] = base
|
|
@@ -95,13 +135,40 @@ function _processSegments(cqn, model, namespace) {
|
|
|
95
135
|
// REVISIT: 404 or 400?
|
|
96
136
|
if (!current) cds.error(`Invalid resource path "${path}"`, { code: 404 })
|
|
97
137
|
|
|
98
|
-
if (current.kind === 'entity') {
|
|
138
|
+
if (current.params && current.kind === 'entity') {
|
|
139
|
+
// > View with params
|
|
140
|
+
if (ref[i].where) {
|
|
141
|
+
keyCount += addRefToWhereIfNecessary(ref[i].where, current)
|
|
142
|
+
_resolveAliasInWhere(ref[i].where, current)
|
|
143
|
+
_resolveAliasInParams(params, current)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
_addDefaultParams(ref[i], current)
|
|
147
|
+
if ((!params || !Object.keys(params).length) && ref[i].where) params = where2obj(ref[i].where)
|
|
148
|
+
|
|
149
|
+
_checkAllKeysProvided(params, current)
|
|
150
|
+
|
|
151
|
+
ref[i].args = {}
|
|
152
|
+
for (let j = 0; j < ref[i].where.length; j++) {
|
|
153
|
+
const w = ref[i].where[j]
|
|
154
|
+
if (w === 'and' || !w.ref) continue
|
|
155
|
+
ref[i].args[w.ref[0]] = ref[i].where[j + 2]
|
|
156
|
+
j += 2
|
|
157
|
+
}
|
|
158
|
+
ref[i].where = undefined
|
|
159
|
+
if (ref[i + 1] !== 'Set') {
|
|
160
|
+
// /Set is missing
|
|
161
|
+
throw new Error(`Incorrect call to a view with parameter "${current.name}"`)
|
|
162
|
+
}
|
|
163
|
+
ref[++i] = null
|
|
164
|
+
} else if (current.kind === 'entity') {
|
|
99
165
|
// > entity
|
|
100
166
|
one = !!(ref[i].where || current._isSingleton)
|
|
101
167
|
incompleteKeys = ref[i].where ? false : i === ref.length - 1 || one ? false : true
|
|
102
168
|
if (ref[i].where) {
|
|
103
169
|
keyCount += addRefToWhereIfNecessary(ref[i].where, current)
|
|
104
170
|
_resolveAliasInWhere(ref[i].where, current)
|
|
171
|
+
_resolveAliasInParams(params, current)
|
|
105
172
|
// in case of Foo(1), params will be {} (before addRefToWhereIfNecessary was called)
|
|
106
173
|
if (!Object.keys(params).length) params = where2obj(ref[i].where)
|
|
107
174
|
_checkAllKeysProvided(params, current)
|
|
@@ -114,7 +181,7 @@ function _processSegments(cqn, model, namespace) {
|
|
|
114
181
|
}
|
|
115
182
|
ref[i] = { operation: current.name }
|
|
116
183
|
if (params) ref[i].args = params
|
|
117
|
-
if (current.returns && current.returns.
|
|
184
|
+
if (current.returns && current.returns._type) one = true
|
|
118
185
|
} else if (current.isAssociation) {
|
|
119
186
|
// > navigation
|
|
120
187
|
one = !!(current.is2one || ref[i].where)
|
|
@@ -161,11 +228,38 @@ function _processSegments(cqn, model, namespace) {
|
|
|
161
228
|
const _resolveFrom = from => (from.SELECT ? _resolveFrom(from.SELECT.from) : from)
|
|
162
229
|
|
|
163
230
|
const _checkAllKeysProvided = (params, entity) => {
|
|
164
|
-
|
|
231
|
+
let keysOfEntity
|
|
232
|
+
const isView = !!entity.params
|
|
233
|
+
if (isView) {
|
|
234
|
+
// view with params
|
|
235
|
+
if (params === undefined) {
|
|
236
|
+
throw new Error(`Incorrect call to a view with parameter "${entity.name}"`)
|
|
237
|
+
} else if (Object.keys(params).length === 0) {
|
|
238
|
+
throw new Error('KEY_EXPECTED')
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
keysOfEntity = Object.keys(entity.params)
|
|
242
|
+
} else {
|
|
243
|
+
keysOfEntity = _keysOf(entity)
|
|
244
|
+
}
|
|
245
|
+
|
|
165
246
|
if (!keysOfEntity) return
|
|
166
247
|
for (const keyOfEntity of keysOfEntity) {
|
|
167
|
-
if (!(keyOfEntity in params))
|
|
168
|
-
|
|
248
|
+
if (!(keyOfEntity in params)) {
|
|
249
|
+
if (isView && entity.params[keyOfEntity].default) {
|
|
250
|
+
// will be added later?
|
|
251
|
+
continue
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
throw Object.assign(
|
|
255
|
+
new Error(
|
|
256
|
+
`${isView ? 'Parameter' : 'Key'} "${keyOfEntity}" is missing for ${isView ? 'view' : 'entity'} "${
|
|
257
|
+
entity.name
|
|
258
|
+
}"`
|
|
259
|
+
),
|
|
260
|
+
{ status: 400 }
|
|
261
|
+
)
|
|
262
|
+
}
|
|
169
263
|
}
|
|
170
264
|
}
|
|
171
265
|
|
package/libx/odata/cqn2odata.js
CHANGED
|
@@ -83,14 +83,10 @@ function _args(args) {
|
|
|
83
83
|
|
|
84
84
|
if (hasValidProps(cur, 'func', 'args')) {
|
|
85
85
|
res.push(`${cur.func}(${_args(cur.args)})`)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (hasValidProps(cur, '
|
|
89
|
-
res.push(cur
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (hasValidProps(cur, 'val')) {
|
|
93
|
-
res.push(formatVal(cur.val))
|
|
86
|
+
} else if (hasValidProps(cur, 'ref')) {
|
|
87
|
+
res.push(_format(cur))
|
|
88
|
+
} else if (hasValidProps(cur, 'val')) {
|
|
89
|
+
res.push(_format(cur))
|
|
94
90
|
}
|
|
95
91
|
}
|
|
96
92
|
|
|
@@ -98,13 +94,13 @@ function _args(args) {
|
|
|
98
94
|
}
|
|
99
95
|
|
|
100
96
|
const _in = (column, /* in */ collection, target, kind, isLambda) => {
|
|
101
|
-
const ref =
|
|
97
|
+
const ref = _format(column, null, target, kind, isLambda)
|
|
102
98
|
// { val: [ 1, 2, 3 ] } or { list: [ { val: 1}, { val: 2}, { val: 3} ] }
|
|
103
99
|
const values = collection.val || collection.list
|
|
104
100
|
if (values && values.length) {
|
|
105
101
|
// REVISIT: what about OData `in` operator?
|
|
106
|
-
const expressions = values.map(value => `${ref}
|
|
107
|
-
return expressions.join('
|
|
102
|
+
const expressions = values.map(value => `${ref}%20eq%20${_format(value, ref, target, kind, isLambda)}`)
|
|
103
|
+
return expressions.join('%20or%20')
|
|
108
104
|
}
|
|
109
105
|
}
|
|
110
106
|
|
|
@@ -119,9 +115,10 @@ const _odataV2Func = (func, args) => {
|
|
|
119
115
|
}
|
|
120
116
|
|
|
121
117
|
const _format = (cur, element, target, kind, isLambda) => {
|
|
122
|
-
if (typeof cur !== 'object') return formatVal(cur, element, target, kind)
|
|
123
|
-
if (hasValidProps(cur, 'ref'))
|
|
124
|
-
|
|
118
|
+
if (typeof cur !== 'object') return encodeURIComponent(formatVal(cur, element, target, kind))
|
|
119
|
+
if (hasValidProps(cur, 'ref'))
|
|
120
|
+
return encodeURIComponent(isLambda ? [LAMBDA_VARIABLE, ...cur.ref].join('/') : cur.ref.join('/'))
|
|
121
|
+
if (hasValidProps(cur, 'val')) return encodeURIComponent(formatVal(cur.val, element, target, kind))
|
|
125
122
|
if (hasValidProps(cur, 'xpr')) return `(${_xpr(cur.xpr, target, kind, isLambda)})`
|
|
126
123
|
// REVISIT: How to detect the types for all functions?
|
|
127
124
|
if (hasValidProps(cur, 'func', 'args')) {
|
|
@@ -156,7 +153,7 @@ function _xpr(expr, target, kind, isLambda) {
|
|
|
156
153
|
} else if (isOrIsNotValue) {
|
|
157
154
|
// REVISIT: "is" only used for null values?
|
|
158
155
|
const operator = isOrIsNotValue[1] /* 'is not' */ ? 'ne' : 'eq'
|
|
159
|
-
res.push(...[operator,
|
|
156
|
+
res.push(...[operator, _format({ val: isOrIsNotValue[2] })])
|
|
160
157
|
} else if (cur === 'between') {
|
|
161
158
|
// ref gt low.val and ref lt high.val
|
|
162
159
|
const between = [expr[i - 1], 'gt', expr[i + 1], 'and', expr[i - 1], 'lt', expr[i + 3]]
|
|
@@ -188,7 +185,7 @@ function _xpr(expr, target, kind, isLambda) {
|
|
|
188
185
|
}
|
|
189
186
|
}
|
|
190
187
|
|
|
191
|
-
return res.join('
|
|
188
|
+
return res.join('%20')
|
|
192
189
|
}
|
|
193
190
|
|
|
194
191
|
const _keysOfWhere = (where, kind, target) => {
|
|
@@ -202,11 +199,11 @@ const _keysOfWhere = (where, kind, target) => {
|
|
|
202
199
|
const res = []
|
|
203
200
|
for (const cur of where) {
|
|
204
201
|
if (hasValidProps(cur, 'ref')) {
|
|
205
|
-
res.push(cur
|
|
202
|
+
res.push(_format(cur))
|
|
206
203
|
} else if (hasValidProps(cur, 'val')) {
|
|
207
204
|
// find previous ref
|
|
208
205
|
const element = res[res.length - 2]
|
|
209
|
-
res.push(
|
|
206
|
+
res.push(_format(cur, element, target, kind))
|
|
210
207
|
} else if (cur === 'and') {
|
|
211
208
|
res.push(',')
|
|
212
209
|
} else {
|
|
@@ -267,15 +264,15 @@ const _parseColumnsV2 = (columns, prefix = []) => {
|
|
|
267
264
|
|
|
268
265
|
if (hasValidProps(column, 'expand')) {
|
|
269
266
|
const parsed = _parseColumnsV2(column.expand, [refName])
|
|
270
|
-
expand.push(refName, ...parsed.expand)
|
|
267
|
+
expand.push(encodeURIComponent(refName), ...parsed.expand)
|
|
271
268
|
select.push(...parsed.select)
|
|
272
269
|
} else {
|
|
273
|
-
select.push(refName)
|
|
270
|
+
select.push(encodeURIComponent(refName))
|
|
274
271
|
}
|
|
275
272
|
}
|
|
276
273
|
|
|
277
274
|
if (column === '*') {
|
|
278
|
-
select.push(`${prefix.join('/')}/*`)
|
|
275
|
+
select.push(encodeURIComponent(`${prefix.join('/')}/*`))
|
|
279
276
|
}
|
|
280
277
|
}
|
|
281
278
|
|
|
@@ -288,7 +285,7 @@ const _parseColumns = columns => {
|
|
|
288
285
|
|
|
289
286
|
for (const column of columns) {
|
|
290
287
|
if (hasValidProps(column, 'ref')) {
|
|
291
|
-
let refName = column
|
|
288
|
+
let refName = _format(column)
|
|
292
289
|
if (hasValidProps(column, 'expand')) {
|
|
293
290
|
// REVISIT: incomplete, see test Foo?$expand=invoices($count=true;$expand=item($search="some"))
|
|
294
291
|
if (!columns.some(c => !c.expand)) select.push(refName)
|
|
@@ -350,16 +347,16 @@ function $orderBy(orderBy) {
|
|
|
350
347
|
|
|
351
348
|
for (const cur of orderBy) {
|
|
352
349
|
if (hasValidProps(cur, 'ref', 'sort')) {
|
|
353
|
-
res.push(cur
|
|
350
|
+
res.push(_format(cur) + '%20' + cur.sort)
|
|
354
351
|
continue
|
|
355
352
|
}
|
|
356
353
|
|
|
357
354
|
if (hasValidProps(cur, 'ref')) {
|
|
358
|
-
res.push(cur
|
|
355
|
+
res.push(_format(cur))
|
|
359
356
|
}
|
|
360
357
|
|
|
361
358
|
if (hasValidProps(cur, 'func', 'sort')) {
|
|
362
|
-
res.push(`${cur.func}(${_args(cur.args)})` + '
|
|
359
|
+
res.push(`${cur.func}(${_args(cur.args)})` + '%20' + cur.sort)
|
|
363
360
|
continue
|
|
364
361
|
}
|
|
365
362
|
|
|
@@ -382,7 +379,7 @@ function parseSearch(search) {
|
|
|
382
379
|
|
|
383
380
|
if (hasValidProps(cur, 'val')) {
|
|
384
381
|
// search term must not be formatted
|
|
385
|
-
res.push(`"${cur.val}"`)
|
|
382
|
+
res.push(`"${encodeURIComponent(cur.val)}"`)
|
|
386
383
|
}
|
|
387
384
|
|
|
388
385
|
if (typeof cur === 'string') {
|
|
@@ -398,7 +395,7 @@ function parseSearch(search) {
|
|
|
398
395
|
}
|
|
399
396
|
|
|
400
397
|
function $search(search, kind) {
|
|
401
|
-
const expr = parseSearch(search).join('
|
|
398
|
+
const expr = parseSearch(search).join('%20').replace('(%20', '(').replace('%20)', ')')
|
|
402
399
|
|
|
403
400
|
if (expr) {
|
|
404
401
|
// odata-v2 may support custom query option "search"
|
package/libx/odata/grammar.pegjs
CHANGED
|
@@ -184,7 +184,15 @@
|
|
|
184
184
|
/ rv:$("$ref"/"$value") {return !TECHNICAL_OPTS.includes(rv) && {from: {ref: [rv]}}}
|
|
185
185
|
/ head:(
|
|
186
186
|
(identifier filter:(OPEN CLOSE/OPEN args CLOSE)? !segment) / val:segment{return [val]}
|
|
187
|
-
) tail:(
|
|
187
|
+
)? tail:((s:"/" {return s;}) path?)? {
|
|
188
|
+
tail = tail && tail[1]
|
|
189
|
+
if (!head && !tail) {
|
|
190
|
+
return {from: {ref: ['']}}
|
|
191
|
+
} else if (!head && tail && tail.from) {
|
|
192
|
+
tail.from.ref.unshift('')
|
|
193
|
+
return tail
|
|
194
|
+
}
|
|
195
|
+
|
|
188
196
|
const [id, filter] = head
|
|
189
197
|
// minimal: val also as path segment
|
|
190
198
|
const ref = []
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const cds = require('../../lib')
|
|
2
|
+
|
|
3
|
+
module.exports = (component, service, target, data, odataReq, upsert) => {
|
|
4
|
+
let query = cds.odata.parse(odataReq, { service })
|
|
5
|
+
|
|
6
|
+
const _target = query.SELECT && query.SELECT.from
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
SELECT: { one }
|
|
10
|
+
} = query
|
|
11
|
+
|
|
12
|
+
switch (component) {
|
|
13
|
+
case 'CREATE':
|
|
14
|
+
// create
|
|
15
|
+
// error in cases like `POST Books(1)` i.e. `POST` with navigation to single entity
|
|
16
|
+
if (one && !upsert) cds.error('POST not allowed on entity', { code: 400 })
|
|
17
|
+
return INSERT.into(_target).entries(data)
|
|
18
|
+
case 'DELETE':
|
|
19
|
+
if (!one) cds.error('DELETE not allowed on collection', { code: 400 })
|
|
20
|
+
|
|
21
|
+
// eslint-disable-next-line no-case-declarations
|
|
22
|
+
const last = _target.ref && _target.ref[_target.ref.length - 1]
|
|
23
|
+
if (target.elements[last]) {
|
|
24
|
+
// delete simple property
|
|
25
|
+
const ref = { ref: _target.ref.slice(0, _target.ref.length - 1) }
|
|
26
|
+
return UPDATE(ref).data({ [_target.ref[_target.ref.length - 1]]: null })
|
|
27
|
+
} else {
|
|
28
|
+
return DELETE.from(_target)
|
|
29
|
+
}
|
|
30
|
+
case 'UPDATE':
|
|
31
|
+
// eslint-disable-next-line no-throw-literal
|
|
32
|
+
if (!one) throw { statusCode: 400, code: '400', message: `INVALID_${odataReq.getMethod()}` }
|
|
33
|
+
return UPDATE(_target).data(data)
|
|
34
|
+
case 'READ':
|
|
35
|
+
return query
|
|
36
|
+
default:
|
|
37
|
+
return {}
|
|
38
|
+
}
|
|
39
|
+
}
|