@sap/cds 5.4.3 → 5.5.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 +239 -2
- package/apis/ql.d.ts +17 -15
- package/app/index.js +1 -1
- package/bin/build/buildTaskEngine.js +26 -42
- package/bin/build/buildTaskFactory.js +6 -10
- package/bin/build/buildTaskHandler.js +2 -4
- package/bin/build/buildTaskProvider.js +3 -1
- package/bin/build/buildTaskProviderFactory.js +9 -15
- package/bin/build/constants.js +15 -3
- package/bin/build/index.js +5 -4
- package/bin/build/mtaUtil.js +8 -11
- package/bin/build/provider/buildTaskHandlerEdmx.js +63 -6
- package/bin/build/provider/buildTaskHandlerInternal.js +2 -34
- package/bin/build/provider/buildTaskProviderInternal.js +16 -42
- package/bin/build/provider/fiori/index.js +13 -24
- package/bin/build/provider/hana/2migration.js +17 -15
- package/bin/build/provider/hana/2tabledata.js +52 -48
- package/bin/build/provider/hana/index.js +27 -25
- package/bin/build/provider/hana/migrationtable.js +91 -67
- package/bin/build/provider/java-cf/index.js +14 -24
- package/bin/build/provider/mtx/index.js +12 -14
- package/bin/build/provider/node-cf/index.js +18 -32
- package/bin/cds.js +5 -5
- package/bin/serve.js +29 -23
- package/bin/version.js +0 -1
- package/lib/compile/etc/_localized.js +4 -9
- package/lib/compile/for/sql.js +5 -2
- package/lib/compile/parse.js +25 -17
- package/lib/compile/to/srvinfo.js +2 -1
- package/lib/connect/bindings.js +2 -1
- package/lib/connect/index.js +48 -49
- package/lib/core/classes.js +1 -1
- package/lib/core/reflect.js +10 -2
- package/lib/deploy.js +26 -23
- package/lib/env/defaults.js +13 -6
- package/lib/env/index.js +73 -78
- package/lib/env/requires.js +38 -19
- package/lib/index.js +9 -10
- package/lib/lazy.js +2 -2
- package/lib/log/index.js +33 -45
- package/lib/log/service/index.js +2 -2
- package/lib/ql/CREATE.js +14 -9
- package/lib/ql/DELETE.js +6 -5
- package/lib/ql/DROP.js +12 -9
- package/lib/ql/INSERT.js +40 -16
- package/lib/ql/Query.js +67 -40
- package/lib/ql/SELECT.js +162 -127
- package/lib/ql/UPDATE.js +74 -42
- package/lib/ql/Whereable.js +77 -87
- package/lib/ql/index.js +36 -24
- package/lib/ql/parse.js +35 -0
- package/lib/req/context.js +44 -8
- package/lib/req/locale.js +7 -7
- package/lib/serve/Service-api.js +21 -14
- package/lib/serve/Service-dispatch.js +28 -12
- package/lib/serve/Transaction.js +22 -10
- package/lib/serve/index.js +16 -11
- package/lib/utils/axios.js +23 -16
- package/lib/utils/data.js +35 -0
- package/lib/utils/tests.js +27 -18
- package/libx/_runtime/audit/generic/personal/access.js +81 -0
- package/libx/_runtime/audit/generic/personal/constants.js +4 -0
- package/libx/_runtime/audit/generic/personal/index.js +50 -0
- package/libx/_runtime/audit/generic/personal/modification.js +138 -0
- package/libx/_runtime/audit/generic/personal/utils.js +186 -0
- package/libx/_runtime/audit/utils/v2.js +10 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +5 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +6 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +5 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +5 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +2 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +4 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +7 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +59 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +11 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +6 -10
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +3 -46
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +2 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/createToCQN.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/deleteToCQN.js +4 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/updateToCQN.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +16 -18
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/EdmEntityType.js +6 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/format/RepresentationKind.js +4 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/core/OdataRequest.js +1 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/SerializerFactory.js +15 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/OperationValidator.js +1 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +8 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +6 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/omitValues.js +12 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +1 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +7 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +14 -18
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +13 -13
- package/libx/_runtime/cds-services/adapter/rest/handlers/create.js +0 -1
- package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +2 -1
- package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +2 -2
- package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +2 -4
- package/libx/_runtime/cds-services/adapter/rest/utils/result.js +4 -2
- package/libx/_runtime/cds-services/services/Service.js +40 -5
- package/libx/_runtime/cds-services/services/utils/columns.js +13 -7
- package/libx/_runtime/cds-services/services/utils/compareJson.js +88 -4
- package/libx/_runtime/cds-services/services/utils/differ.js +24 -6
- package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -2
- package/libx/_runtime/common/composition/data.js +66 -63
- package/libx/_runtime/common/composition/delete.js +97 -71
- package/libx/_runtime/common/composition/index.js +2 -1
- package/libx/_runtime/common/composition/insert.js +34 -11
- package/libx/_runtime/common/composition/tree.js +119 -92
- package/libx/_runtime/common/composition/update.js +12 -1
- package/libx/_runtime/common/composition/utils.js +1 -3
- package/libx/_runtime/common/constants/draft.js +12 -1
- package/libx/_runtime/common/generic/auth.js +53 -31
- package/libx/_runtime/common/generic/crud.js +14 -13
- package/libx/_runtime/common/generic/input.js +23 -26
- package/libx/_runtime/common/generic/put.js +1 -1
- package/libx/_runtime/common/generic/sorting.js +16 -16
- package/libx/_runtime/common/i18n/index.js +1 -1
- package/libx/_runtime/common/i18n/messages.properties +4 -0
- package/libx/_runtime/common/utils/backlinks.js +12 -5
- package/libx/_runtime/common/utils/cqn.js +6 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +123 -108
- package/libx/_runtime/common/utils/csn.js +56 -4
- package/libx/_runtime/common/utils/data.js +0 -37
- package/libx/_runtime/common/utils/enrichWithKeysFromWhere.js +1 -1
- package/libx/_runtime/common/utils/entityFromCqn.js +7 -24
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +39 -7
- package/libx/_runtime/common/utils/generateOnCond.js +11 -12
- package/libx/_runtime/common/utils/onlyKeysRemain.js +10 -0
- package/libx/_runtime/common/utils/path.js +35 -0
- package/libx/_runtime/common/utils/postProcessing.js +86 -0
- package/libx/_runtime/common/utils/quotingStyles.js +37 -26
- package/libx/_runtime/common/utils/resolveView.js +227 -173
- package/libx/_runtime/common/utils/rewriteAsterisk.js +46 -26
- package/libx/_runtime/common/utils/structured.js +13 -13
- package/libx/_runtime/common/utils/template.js +10 -5
- package/libx/_runtime/common/utils/templateDelimiter.js +1 -0
- package/libx/_runtime/common/utils/templateProcessor.js +28 -72
- package/libx/_runtime/common/utils/union.js +31 -0
- package/libx/_runtime/common/utils/unionCqnTemplate.js +184 -0
- package/libx/_runtime/db/Service.js +1 -1
- package/libx/_runtime/db/data-conversion/timestamp.js +2 -9
- package/libx/_runtime/db/expand/expandCQNToJoin.js +204 -297
- package/libx/_runtime/db/expand/index.js +3 -3
- package/libx/_runtime/db/expand/rawToExpanded.js +36 -7
- package/libx/_runtime/db/generic/index.js +1 -1
- package/libx/_runtime/db/generic/input.js +5 -7
- package/libx/_runtime/db/generic/integrity.js +1 -1
- package/libx/_runtime/db/generic/rewrite.js +2 -10
- package/libx/_runtime/db/generic/update.js +13 -5
- package/libx/_runtime/db/generic/virtual.js +22 -58
- package/libx/_runtime/db/query/delete.js +7 -4
- package/libx/_runtime/db/query/insert.js +6 -4
- package/libx/_runtime/db/query/read.js +21 -8
- package/libx/_runtime/db/query/run.js +4 -1
- package/libx/_runtime/db/query/update.js +5 -4
- package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +35 -2
- package/libx/_runtime/db/sql-builder/FunctionBuilder.js +17 -2
- package/libx/_runtime/db/sql-builder/InsertBuilder.js +6 -5
- package/libx/_runtime/db/sql-builder/ReferenceBuilder.js +10 -0
- package/libx/_runtime/db/sql-builder/SelectBuilder.js +35 -24
- package/libx/_runtime/db/sql-builder/UpdateBuilder.js +14 -4
- package/libx/_runtime/db/sql-builder/arrayed.js +4 -0
- package/libx/_runtime/db/utils/deep.js +8 -0
- package/libx/_runtime/db/utils/generateAliases.js +2 -1
- package/libx/_runtime/fiori/generic/activate.js +19 -15
- package/libx/_runtime/fiori/generic/before.js +3 -11
- package/libx/_runtime/fiori/generic/cancel.js +1 -1
- package/libx/_runtime/fiori/generic/delete.js +3 -1
- package/libx/_runtime/fiori/generic/edit.js +12 -2
- package/libx/_runtime/fiori/generic/new.js +5 -5
- package/libx/_runtime/fiori/generic/patch.js +0 -18
- package/libx/_runtime/fiori/generic/read.js +261 -205
- package/libx/_runtime/fiori/utils/delete.js +36 -7
- package/libx/_runtime/fiori/utils/handler.js +43 -44
- package/libx/_runtime/fiori/utils/where.js +30 -15
- package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +4 -6
- package/libx/_runtime/hana/execute.js +3 -3
- package/libx/_runtime/hana/localized.js +4 -4
- package/libx/_runtime/hana/pool.js +29 -14
- package/libx/_runtime/hana/search2cqn4sql.js +2 -1
- package/libx/_runtime/hana/searchToContains.js +18 -14
- package/libx/_runtime/index.js +0 -5
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +13 -5
- package/libx/_runtime/messaging/common-utils/naming-conventions.js +4 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +31 -19
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +1 -2
- package/libx/_runtime/messaging/enterprise-messaging.js +6 -4
- package/libx/_runtime/messaging/service.js +7 -6
- package/libx/_runtime/odata/cqn2odata.js +110 -43
- package/libx/_runtime/odata/index.js +26 -48
- package/libx/_runtime/odata/odata2cqn.js +1 -6154
- package/libx/_runtime/odata/odata2cqn.pegjs +559 -0
- package/libx/_runtime/odata/readToCqn.js +94 -64
- package/libx/_runtime/remote/Service.js +74 -21
- package/libx/_runtime/remote/cqn2odata/index.js +1 -5
- package/libx/_runtime/remote/utils/client.js +24 -101
- package/libx/_runtime/remote/utils/dataConversion.js +27 -12
- package/libx/_runtime/sqlite/Service.js +3 -5
- package/libx/_runtime/sqlite/execute.js +33 -27
- package/libx/_runtime/sqlite/localized.js +12 -7
- package/libx/_runtime/types/api.js +10 -0
- package/package.json +2 -2
- package/server.js +16 -2
- package/lib/ql/grammar.pegjs +0 -208
- package/lib/ql/parser.js +0 -1
- package/lib/ql/rt/DELETE.js +0 -29
- package/lib/ql/rt/INSERT.js +0 -23
- package/lib/ql/rt/Query.js +0 -84
- package/lib/ql/rt/SELECT.js +0 -174
- package/lib/ql/rt/UPDATE.js +0 -119
- package/lib/ql/rt/_helpers.js +0 -91
- package/lib/ql/rt/index.js +0 -32
- package/libx/_runtime/audit/generic/personal.js +0 -260
- package/libx/_runtime/cds-services/statements/BaseStatement.js +0 -72
- package/libx/_runtime/cds-services/statements/Create.js +0 -57
- package/libx/_runtime/cds-services/statements/Delete.js +0 -33
- package/libx/_runtime/cds-services/statements/Drop.js +0 -42
- package/libx/_runtime/cds-services/statements/Insert.js +0 -201
- package/libx/_runtime/cds-services/statements/Select.js +0 -826
- package/libx/_runtime/cds-services/statements/Update.js +0 -181
- package/libx/_runtime/cds-services/statements/Where.js +0 -726
- package/libx/_runtime/cds-services/statements/index.js +0 -25
- package/libx/_runtime/common/generic/resolve-mock.js +0 -9
|
@@ -18,11 +18,11 @@ const BASE_PATH = '/messaging/enterprise-messaging'
|
|
|
18
18
|
const _checkAppURL = appURL => {
|
|
19
19
|
if (!appURL)
|
|
20
20
|
throw new Error(
|
|
21
|
-
'Enterprise Messaging: You need to provide an HTTPS endpoint to your application.\n\nHint: You can set the application URI in environment variable `VCAP_APPLICATION.application_uris[0]`. This is needed because incoming messages are delivered through HTTP via webhooks.\nExample: `{ ..., "VCAP_APPLICATION": { "application_uris": ["my-app.com"] } }`\nIn case you want to use Enterprise Messaging in shared (that means single-tenant) mode, you can use kind `enterprise-messaging-
|
|
21
|
+
'Enterprise Messaging: You need to provide an HTTPS endpoint to your application.\n\nHint: You can set the application URI in environment variable `VCAP_APPLICATION.application_uris[0]`. This is needed because incoming messages are delivered through HTTP via webhooks.\nExample: `{ ..., "VCAP_APPLICATION": { "application_uris": ["my-app.com"] } }`\nIn case you want to use Enterprise Messaging in shared (that means single-tenant) mode, you can use kind `enterprise-messaging-amqp`.'
|
|
22
22
|
)
|
|
23
23
|
if (appURL.startsWith('https://localhost'))
|
|
24
24
|
throw new Error(
|
|
25
|
-
'The endpoint of your application is local and cannot be reached from Enterprise Messaging.\n\nHint: For local development you can set up a tunnel to your local endpoint and enter its public https endpoint in `VCAP_APPLICATION.application_uris[0]`.\nIn case you want to use Enterprise Messaging in shared (that means single-tenant) mode, you can use kind `enterprise-messaging-
|
|
25
|
+
'The endpoint of your application is local and cannot be reached from Enterprise Messaging.\n\nHint: For local development you can set up a tunnel to your local endpoint and enter its public https endpoint in `VCAP_APPLICATION.application_uris[0]`.\nIn case you want to use Enterprise Messaging in shared (that means single-tenant) mode, you can use kind `enterprise-messaging-amqp`.'
|
|
26
26
|
)
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -78,8 +78,10 @@ class EnterpriseMessaging extends AMQPWebhookMessaging {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
startListening() {
|
|
81
|
-
|
|
82
|
-
if (
|
|
81
|
+
const doNotDeploy = cds._mtxEnabled && !this.options.deployForProvider
|
|
82
|
+
if (doNotDeploy) LOG._info && LOG.info('Skipping deployment of messaging artifacts for provider account')
|
|
83
|
+
super.startListening({ doNotDeploy })
|
|
84
|
+
if (!doNotDeploy && this.subscribedTopics.size) {
|
|
83
85
|
const management = this.getManagement()
|
|
84
86
|
// Webhooks will perform an OPTIONS call on creation to check the availability of the app.
|
|
85
87
|
// On systems like Cloud Foundry the app URL will only be advertised once
|
|
@@ -62,7 +62,7 @@ class MessagingService extends cds.Service {
|
|
|
62
62
|
const { emit } = this
|
|
63
63
|
this.emit = function (...args) {
|
|
64
64
|
const context = this.context || cds.context
|
|
65
|
-
if (context)
|
|
65
|
+
if (context && typeof context.on === 'function')
|
|
66
66
|
return context.on('succeeded', () =>
|
|
67
67
|
emit.call(this, ...args).catch(e => {
|
|
68
68
|
LOG._error && LOG.error(e)
|
|
@@ -95,8 +95,8 @@ class MessagingService extends cds.Service {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
prepareTopic(topic, _inbound) {
|
|
98
|
-
// In local
|
|
99
|
-
if (this.options.
|
|
98
|
+
// In local messaging there's a 'short curcuit' so we must not modify the topic
|
|
99
|
+
if (this.options.local) return topic
|
|
100
100
|
let res = topic
|
|
101
101
|
if (!_inbound && this.options.publishPrefix) res = this.options.publishPrefix + res
|
|
102
102
|
if (_inbound && this.options.subscribePrefix) res = this.options.subscribePrefix + res
|
|
@@ -123,10 +123,11 @@ class MessagingService extends cds.Service {
|
|
|
123
123
|
this.prepareHeaders(msg.headers, msg.event)
|
|
124
124
|
msg.event = this.prepareTopic(msg.event, false)
|
|
125
125
|
} else if (this.subscribedTopics) {
|
|
126
|
-
|
|
126
|
+
const subscribedEvent =
|
|
127
127
|
this.subscribedTopics.get(msg.event) ||
|
|
128
|
-
(this.wildcarded && this.subscribedTopics.get(this.wildcarded(msg.event)))
|
|
129
|
-
|
|
128
|
+
(this.wildcarded && this.subscribedTopics.get(this.wildcarded(msg.event)))
|
|
129
|
+
if (!subscribedEvent) throw new Error(`No handler for incoming message with topic '${msg.event}' found.`)
|
|
130
|
+
msg.event = subscribedEvent
|
|
130
131
|
}
|
|
131
132
|
return msg
|
|
132
133
|
}
|
|
@@ -18,7 +18,9 @@ const getSafeNumber = str => {
|
|
|
18
18
|
const needArrayProps = Object.fromEntries(
|
|
19
19
|
['where', 'search', 'xpr', 'columns', 'expand', 'orderBy', 'ref', 'args'].map(propName => [
|
|
20
20
|
propName,
|
|
21
|
-
cur =>
|
|
21
|
+
cur =>
|
|
22
|
+
(Array.isArray(cur) && (cur.length !== 0 || propName === 'expand' || propName === 'ref')) ||
|
|
23
|
+
(propName === 'expand' && cur === '*')
|
|
22
24
|
])
|
|
23
25
|
)
|
|
24
26
|
|
|
@@ -111,6 +113,7 @@ function getProp(obj, propName) {
|
|
|
111
113
|
if (isValid) {
|
|
112
114
|
return obj[propName]
|
|
113
115
|
}
|
|
116
|
+
|
|
114
117
|
throw new Error(`Invalid property '${propName}' provided`)
|
|
115
118
|
}
|
|
116
119
|
|
|
@@ -118,10 +121,12 @@ function hasValidProps(obj, ...names) {
|
|
|
118
121
|
for (const propName of names) {
|
|
119
122
|
const validate = validators[propName]
|
|
120
123
|
const isValid = validate && validate(obj[propName])
|
|
124
|
+
|
|
121
125
|
if (!isValid) {
|
|
122
126
|
return false
|
|
123
127
|
}
|
|
124
128
|
}
|
|
129
|
+
|
|
125
130
|
return true
|
|
126
131
|
}
|
|
127
132
|
|
|
@@ -133,16 +138,20 @@ function _args(args) {
|
|
|
133
138
|
res.push(cur)
|
|
134
139
|
continue
|
|
135
140
|
}
|
|
141
|
+
|
|
136
142
|
if (hasValidProps(cur, 'func', 'args')) {
|
|
137
143
|
res.push(`${cur.func}(${_args(cur.args)})`)
|
|
138
144
|
}
|
|
145
|
+
|
|
139
146
|
if (hasValidProps(cur, 'ref')) {
|
|
140
147
|
res.push(cur.ref.join('/'))
|
|
141
148
|
}
|
|
149
|
+
|
|
142
150
|
if (hasValidProps(cur, 'val')) {
|
|
143
151
|
res.push(formatVal(cur.val))
|
|
144
152
|
}
|
|
145
153
|
}
|
|
154
|
+
|
|
146
155
|
return res.join(',')
|
|
147
156
|
}
|
|
148
157
|
|
|
@@ -169,17 +178,20 @@ const _format = (cur, element, target, kind, isLambda) => {
|
|
|
169
178
|
const _isLambda = (cur, next) => {
|
|
170
179
|
if (cur !== 'exists') return
|
|
171
180
|
const last = Array.isArray(next.ref) && next.ref.slice(-1)[0]
|
|
172
|
-
return last && hasValidProps(last, 'id')
|
|
181
|
+
return last && hasValidProps(last, 'id')
|
|
173
182
|
}
|
|
174
183
|
|
|
175
184
|
function _xpr(expr, target, kind, isLambda) {
|
|
176
185
|
const res = []
|
|
177
186
|
const openBrackets = []
|
|
187
|
+
|
|
178
188
|
for (let i = 0; i < expr.length; i++) {
|
|
179
189
|
const cur = expr[i]
|
|
190
|
+
|
|
180
191
|
if (typeof cur === 'string') {
|
|
181
192
|
// REVISIT: will it be fixed with a new odata2cqn and follow-ups?
|
|
182
193
|
const isOrIsNotValue = cur.match(/^is\s(not)?\s*(.+)$/)
|
|
194
|
+
|
|
183
195
|
if (cur === '(') {
|
|
184
196
|
openBrackets.push(res.length)
|
|
185
197
|
continue
|
|
@@ -221,6 +233,7 @@ function _xpr(expr, target, kind, isLambda) {
|
|
|
221
233
|
if (formatted !== undefined) res.push(formatted)
|
|
222
234
|
}
|
|
223
235
|
}
|
|
236
|
+
|
|
224
237
|
return res.join(' ')
|
|
225
238
|
}
|
|
226
239
|
|
|
@@ -229,7 +242,6 @@ const _keysOfWhere = (where, kind, target) => {
|
|
|
229
242
|
|
|
230
243
|
if (kind === 'rest') {
|
|
231
244
|
const keys = where.length === 1 ? getProp(where[0], 'val') : getProp(where[2], 'val')
|
|
232
|
-
|
|
233
245
|
return `/${keys}`
|
|
234
246
|
}
|
|
235
247
|
|
|
@@ -247,6 +259,7 @@ const _keysOfWhere = (where, kind, target) => {
|
|
|
247
259
|
res.push(cur)
|
|
248
260
|
}
|
|
249
261
|
}
|
|
262
|
+
|
|
250
263
|
return `(${res.join('')})`
|
|
251
264
|
}
|
|
252
265
|
|
|
@@ -274,6 +287,7 @@ function _from(from, kind, model) {
|
|
|
274
287
|
|
|
275
288
|
const path = []
|
|
276
289
|
let queryTarget
|
|
290
|
+
|
|
277
291
|
for (const curRef of ref) {
|
|
278
292
|
if (hasValidProps(curRef, 'where', 'id')) {
|
|
279
293
|
const { where, id } = curRef
|
|
@@ -285,6 +299,7 @@ function _from(from, kind, model) {
|
|
|
285
299
|
path.push(curRef)
|
|
286
300
|
}
|
|
287
301
|
}
|
|
302
|
+
|
|
288
303
|
return { url: _entityUrl(path.join('/')), queryTarget }
|
|
289
304
|
}
|
|
290
305
|
|
|
@@ -292,62 +307,83 @@ const _parseColumnsV2 = (columns, prefix = []) => {
|
|
|
292
307
|
const select = []
|
|
293
308
|
const expand = []
|
|
294
309
|
|
|
295
|
-
for (const
|
|
296
|
-
if (hasValidProps(
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
310
|
+
for (const column of columns) {
|
|
311
|
+
if (hasValidProps(column, 'ref')) {
|
|
312
|
+
const refName = [...prefix, ...column.ref].join('/')
|
|
313
|
+
|
|
314
|
+
if (hasValidProps(column, 'expand')) {
|
|
315
|
+
const parsed = _parseColumnsV2(column.expand, [refName])
|
|
316
|
+
expand.push(refName, ...parsed.expand)
|
|
301
317
|
select.push(...parsed.select)
|
|
302
318
|
} else {
|
|
303
|
-
select.push(
|
|
319
|
+
select.push(refName)
|
|
304
320
|
}
|
|
305
321
|
}
|
|
306
|
-
|
|
322
|
+
|
|
323
|
+
if (column === '*') {
|
|
307
324
|
select.push(`${prefix.join('/')}/*`)
|
|
308
325
|
}
|
|
309
326
|
}
|
|
327
|
+
|
|
310
328
|
return { select, expand }
|
|
311
329
|
}
|
|
312
330
|
|
|
313
|
-
const _parseColumns = columns => {
|
|
331
|
+
const _parseColumns = (columns, options) => {
|
|
332
|
+
const isExpand = options.expand
|
|
314
333
|
const select = []
|
|
315
334
|
const expand = []
|
|
316
335
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
336
|
+
if (columns === '*') {
|
|
337
|
+
return { select, expand }
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const isSelectAll = select =>
|
|
341
|
+
select.length === 1 && (select[0] === '*' || (select[0].ref && select[0].ref[0] === '*'))
|
|
342
|
+
|
|
343
|
+
for (const column of columns) {
|
|
344
|
+
if (hasValidProps(column, 'ref')) {
|
|
345
|
+
const refName = column.ref.join('/')
|
|
346
|
+
let refNameWithOptions = refName
|
|
347
|
+
|
|
348
|
+
if (hasValidProps(column, 'expand')) {
|
|
349
|
+
const curOptions = getOptions(column).join(';')
|
|
350
|
+
refNameWithOptions += curOptions ? `(${curOptions})` : ''
|
|
351
|
+
expand.push(refNameWithOptions)
|
|
352
|
+
if (!isSelectAll(select) && !isExpand) select.push(refName)
|
|
324
353
|
// REVISIT: expand to one & limit in options
|
|
325
|
-
// > const expanded = $expand(
|
|
326
|
-
// > expand.push(expanded ? `${
|
|
354
|
+
// > const expanded = $expand(column.expand)
|
|
355
|
+
// > expand.push(expanded ? `${refNameWithOptions}(${expanded})` : ref)
|
|
327
356
|
// see xtest('READ with expand'... in custom handler test
|
|
328
357
|
} else {
|
|
329
|
-
select.push(
|
|
358
|
+
select.push(refNameWithOptions)
|
|
330
359
|
}
|
|
331
360
|
}
|
|
332
|
-
|
|
333
|
-
|
|
361
|
+
|
|
362
|
+
if (column === '*') {
|
|
363
|
+
select.push(column)
|
|
334
364
|
}
|
|
335
365
|
}
|
|
336
|
-
|
|
337
|
-
|
|
366
|
+
|
|
367
|
+
// omit $select query option if it only contains '*'
|
|
368
|
+
if (isSelectAll(select)) {
|
|
338
369
|
select.pop()
|
|
339
370
|
}
|
|
340
|
-
|
|
371
|
+
|
|
372
|
+
const uniqueSelect = [...new Set(select)]
|
|
373
|
+
return { select: uniqueSelect, expand }
|
|
341
374
|
}
|
|
342
375
|
|
|
343
|
-
function $select(columns, kind, separator = '&') {
|
|
344
|
-
const { select, expand } =
|
|
376
|
+
function $select(columns, kind, separator = '&', isExpand = false) {
|
|
377
|
+
const { select, expand } =
|
|
378
|
+
kind === 'odata-v2' ? _parseColumnsV2(columns) : _parseColumns(columns, { expand: isExpand })
|
|
379
|
+
|
|
345
380
|
const res = []
|
|
346
381
|
if (expand.length) res.unshift('$expand=' + expand.join(','))
|
|
347
382
|
if (select.length) res.unshift('$select=' + select.join(','))
|
|
348
383
|
return res.join(separator)
|
|
349
384
|
}
|
|
350
|
-
|
|
385
|
+
|
|
386
|
+
const $expand = columns => $select(columns, 'odata', ';', true)
|
|
351
387
|
|
|
352
388
|
function $count(count, kind) {
|
|
353
389
|
if (count !== true) return ''
|
|
@@ -357,26 +393,32 @@ function $count(count, kind) {
|
|
|
357
393
|
|
|
358
394
|
function $limit(limit) {
|
|
359
395
|
const res = []
|
|
396
|
+
|
|
360
397
|
if (hasValidProps(limit, 'rows')) {
|
|
361
398
|
res.push('$top=' + getProp(limit.rows, 'val'))
|
|
362
399
|
}
|
|
400
|
+
|
|
363
401
|
if (hasValidProps(limit, 'offset')) {
|
|
364
402
|
res.push('$skip=' + getProp(limit.offset, 'val'))
|
|
365
403
|
}
|
|
404
|
+
|
|
366
405
|
return res
|
|
367
406
|
}
|
|
368
407
|
|
|
369
408
|
function $orderBy(orderBy) {
|
|
370
409
|
const res = []
|
|
410
|
+
|
|
371
411
|
for (const cur of orderBy) {
|
|
372
412
|
if (hasValidProps(cur, 'ref', 'sort')) {
|
|
373
413
|
res.push(cur.ref.join('/') + ' ' + cur.sort)
|
|
374
414
|
continue
|
|
375
415
|
}
|
|
416
|
+
|
|
376
417
|
if (hasValidProps(cur, 'ref')) {
|
|
377
418
|
res.push(cur.ref.join('/'))
|
|
378
419
|
}
|
|
379
420
|
}
|
|
421
|
+
|
|
380
422
|
return '$orderby=' + res.join(',')
|
|
381
423
|
}
|
|
382
424
|
|
|
@@ -388,17 +430,21 @@ function parseSearch(search) {
|
|
|
388
430
|
// search term must not be formatted
|
|
389
431
|
res.push('(', ...parseSearch(cur.xpr), ')')
|
|
390
432
|
}
|
|
433
|
+
|
|
391
434
|
if (hasValidProps(cur, 'val')) {
|
|
392
435
|
// search term must not be formatted
|
|
393
436
|
res.push(`"${cur.val}"`)
|
|
394
437
|
}
|
|
438
|
+
|
|
395
439
|
if (typeof cur === 'string') {
|
|
396
440
|
const upperCur = cur.toUpperCase()
|
|
441
|
+
|
|
397
442
|
if (upperCur === 'OR' || upperCur === 'AND' || upperCur === 'NOT') {
|
|
398
443
|
res.push(upperCur)
|
|
399
444
|
}
|
|
400
445
|
}
|
|
401
446
|
}
|
|
447
|
+
|
|
402
448
|
return res
|
|
403
449
|
}
|
|
404
450
|
|
|
@@ -428,33 +474,48 @@ function $one(one, url, kind) {
|
|
|
428
474
|
const _isOdataUrlWithKeys = (url, kind) => kind !== 'rest' && /^[\w\.]+\(.*\)/.test(url)
|
|
429
475
|
|
|
430
476
|
const parsers = {
|
|
431
|
-
columns: (cqnPart, url, kind) => $select(cqnPart, kind),
|
|
432
|
-
expand: (cqnPart, url, kind) => $expand(cqnPart),
|
|
433
|
-
where: (cqnPart, url, kind, target) => $where(cqnPart, target, kind),
|
|
434
|
-
search: (cqnPart, url, kind) => $search(cqnPart, kind),
|
|
435
|
-
orderBy: cqnPart => $orderBy(cqnPart),
|
|
436
|
-
count: (cqnPart, url, kind) => $count(cqnPart, kind),
|
|
437
|
-
limit: cqnPart => $limit(cqnPart),
|
|
438
|
-
one: (cqnPart, url, kind) => $one(cqnPart, url, kind)
|
|
477
|
+
columns: (cqnPart, url, kind, target, isCount) => !isCount && $select(cqnPart, kind),
|
|
478
|
+
expand: (cqnPart, url, kind, target, isCount) => !isCount && $expand(cqnPart),
|
|
479
|
+
where: (cqnPart, url, kind, target, isCount) => $where(cqnPart, target, kind),
|
|
480
|
+
search: (cqnPart, url, kind, target, isCount) => $search(cqnPart, kind),
|
|
481
|
+
orderBy: (cqnPart, url, kind, target, isCount) => !isCount && $orderBy(cqnPart),
|
|
482
|
+
count: (cqnPart, url, kind, target, isCount) => !isCount && $count(cqnPart, kind),
|
|
483
|
+
limit: (cqnPart, url, kind, target, isCount) => !isCount && $limit(cqnPart),
|
|
484
|
+
one: (cqnPart, url, kind, target, isCount) => !isCount && $one(cqnPart, url, kind)
|
|
439
485
|
}
|
|
440
486
|
|
|
441
|
-
function getOptions(cqnPart, url, kind, target) {
|
|
487
|
+
function getOptions(cqnPart, url, kind, target, isCount) {
|
|
442
488
|
const options = []
|
|
489
|
+
|
|
443
490
|
for (const opt in cqnPart) {
|
|
444
|
-
|
|
491
|
+
const cqnPartOpt = cqnPart[opt]
|
|
492
|
+
if (cqnPartOpt === undefined) continue
|
|
445
493
|
if (!hasValidProps(cqnPart, opt)) throw new Error(`Feature not supported: SELECT statement with .${opt}`)
|
|
446
|
-
const
|
|
494
|
+
const parser = parsers[opt]
|
|
495
|
+
const parsed = parser && parser(cqnPartOpt, url, kind, target, isCount)
|
|
447
496
|
const parsedOpts = (Array.isArray(parsed) && parsed) || (parsed && [parsed]) || []
|
|
448
497
|
options.push(...parsedOpts)
|
|
449
498
|
}
|
|
499
|
+
|
|
450
500
|
return options
|
|
451
501
|
}
|
|
452
502
|
|
|
503
|
+
const _isCount = SELECT => {
|
|
504
|
+
if (SELECT.columns) {
|
|
505
|
+
const columns = getProp(SELECT, 'columns')
|
|
506
|
+
return columns.some(
|
|
507
|
+
c => c.func === 'count' && c.as === '$count' && c.args && c.args.length === 1 && c.args[0] === '*'
|
|
508
|
+
)
|
|
509
|
+
}
|
|
510
|
+
return false
|
|
511
|
+
}
|
|
512
|
+
|
|
453
513
|
const _select = (cqn, kind, model) => {
|
|
454
514
|
const SELECT = getProp(cqn, 'SELECT')
|
|
455
515
|
const { url, queryTarget } = _from(getProp(SELECT, 'from'), kind, model)
|
|
456
|
-
const
|
|
457
|
-
const
|
|
516
|
+
const isCount = _isCount(SELECT)
|
|
517
|
+
const queryOptions = getOptions(SELECT, url, kind, queryTarget, isCount).join('&')
|
|
518
|
+
const path = `${url}${isCount ? '/$count' : ''}${queryOptions ? `?${queryOptions}` : ''}`
|
|
458
519
|
return { method: 'GET', path }
|
|
459
520
|
}
|
|
460
521
|
|
|
@@ -474,6 +535,7 @@ const _copyData = data => {
|
|
|
474
535
|
? data[property].val
|
|
475
536
|
: data[property]
|
|
476
537
|
}
|
|
538
|
+
|
|
477
539
|
return copied
|
|
478
540
|
}
|
|
479
541
|
|
|
@@ -481,12 +543,14 @@ const _update = (cqn, kind, model) => {
|
|
|
481
543
|
const UPDATE = getProp(cqn, 'UPDATE')
|
|
482
544
|
const { url, queryTarget } = _from(getProp(UPDATE, 'entity'), kind, model)
|
|
483
545
|
let keys = ''
|
|
546
|
+
|
|
484
547
|
if (UPDATE.where) {
|
|
485
548
|
if (_isOdataUrlWithKeys(url, kind)) {
|
|
486
549
|
throw new Error('Cannot generate URL for UPDATE CQN. Conflicting .from and .where')
|
|
487
550
|
}
|
|
488
551
|
keys = _keysOfWhere(getProp(UPDATE, 'where'), kind, queryTarget)
|
|
489
552
|
}
|
|
553
|
+
|
|
490
554
|
// TODO: support for .set as well
|
|
491
555
|
const body = _copyData(UPDATE.data)
|
|
492
556
|
return { method: 'PATCH', path: `${url}${keys}`, body }
|
|
@@ -496,12 +560,15 @@ const _delete = (cqn, kind, model) => {
|
|
|
496
560
|
const DELETE = getProp(cqn, 'DELETE')
|
|
497
561
|
const { url, queryTarget } = _from(getProp(DELETE, 'from'), kind, model)
|
|
498
562
|
let keys = ''
|
|
563
|
+
|
|
499
564
|
if (DELETE.where) {
|
|
500
565
|
if (_isOdataUrlWithKeys(url, kind)) {
|
|
501
566
|
throw new Error('Cannot generate URL for DELETE CQN. Conflicting .from and .where')
|
|
502
567
|
}
|
|
568
|
+
|
|
503
569
|
keys = _keysOfWhere(getProp(DELETE, 'where'), kind, queryTarget)
|
|
504
570
|
}
|
|
571
|
+
|
|
505
572
|
return { method: 'DELETE', path: `${url}${keys}` }
|
|
506
573
|
}
|
|
507
574
|
|
|
@@ -1,40 +1,4 @@
|
|
|
1
|
-
const
|
|
2
|
-
const LOG = cds.log('odata')
|
|
3
|
-
|
|
4
|
-
const fs = require('fs')
|
|
5
|
-
const path = require('path')
|
|
6
|
-
const odataPegGrammarPath = path.join(__dirname, '../../../.internal/odata2cqn/odata2cqn.pegjs')
|
|
7
|
-
let odataParser, peg
|
|
8
|
-
|
|
9
|
-
const _pegjsExists = () => {
|
|
10
|
-
try {
|
|
11
|
-
peg = require('pegjs')
|
|
12
|
-
return true
|
|
13
|
-
} catch (e) {
|
|
14
|
-
return false
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// generate parser if grammar and pegjs are available
|
|
19
|
-
if (fs.existsSync(odataPegGrammarPath) && _pegjsExists() && !process.env.TRAVIS_PULL_REQUEST) {
|
|
20
|
-
const odataPegGrammar = fs.readFileSync(odataPegGrammarPath, { encoding: 'utf8' })
|
|
21
|
-
|
|
22
|
-
// generate parser object
|
|
23
|
-
odataParser = peg.generate(odataPegGrammar)
|
|
24
|
-
|
|
25
|
-
// generate parser string and asynchronously write to file
|
|
26
|
-
const parserString = peg.generate(odataPegGrammar, { output: 'source', format: 'commonjs' })
|
|
27
|
-
fs.writeFile(path.join(__dirname, 'odata2cqn.js'), parserString, err => {
|
|
28
|
-
if (err && LOG._warn) {
|
|
29
|
-
err.message = 'Unable to store generated odata2cqn grammar: ' + err.message
|
|
30
|
-
LOG.warn(err)
|
|
31
|
-
}
|
|
32
|
-
})
|
|
33
|
-
} else {
|
|
34
|
-
odataParser = require('./odata2cqn')
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const { cqn2odata, getSafeNumber } = require('./cqn2odata')
|
|
1
|
+
const { cqn2odata, getSafeNumber: safeNumber } = require('./cqn2odata')
|
|
38
2
|
|
|
39
3
|
const strict = {
|
|
40
4
|
functions: {
|
|
@@ -59,19 +23,33 @@ const strict = {
|
|
|
59
23
|
}
|
|
60
24
|
}
|
|
61
25
|
|
|
62
|
-
|
|
26
|
+
let parser = require('./odata2cqn')
|
|
27
|
+
const odata = {
|
|
63
28
|
parse: {
|
|
64
|
-
url
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
29
|
+
url(url, o = {}) {
|
|
30
|
+
o = o === 'strict' ? { strict } : o.strict ? { ...o, strict } : o
|
|
31
|
+
|
|
32
|
+
const uri = decodeURIComponent(url)
|
|
33
|
+
let parsed = parser.parse(uri, { ...o, safeNumber })
|
|
34
|
+
|
|
35
|
+
if (typeof o.afterburner === 'function') parsed = o.afterburner(parsed)
|
|
36
|
+
return parsed
|
|
71
37
|
}
|
|
72
38
|
},
|
|
39
|
+
|
|
40
|
+
urlify: cqn2odata,
|
|
41
|
+
|
|
73
42
|
to: {
|
|
74
|
-
cqn: (url, o) => odata.parse
|
|
75
|
-
url: (cqn, kind, model) => odata.
|
|
43
|
+
cqn: (url, o) => odata.parse(url, o),
|
|
44
|
+
url: (cqn, kind, model) => odata.urlify(cqn, kind, model)
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
genParser() {
|
|
48
|
+
const source = __filename.replace('index.js', 'odata2cqn.pegjs')
|
|
49
|
+
const grammar = require('fs').readFileSync(source, 'utf8')
|
|
50
|
+
parser = require('pegjs').generate(grammar)
|
|
51
|
+
return parser
|
|
76
52
|
}
|
|
77
|
-
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = odata
|