@sap/cds 5.8.2 → 5.9.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 +214 -78
- package/app/fiori/preview.js +16 -11
- package/app/fiori/routes.js +3 -0
- 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 +20 -17
- 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 +12 -5
- package/bin/utils/modules.js +7 -0
- package/bin/version.js +56 -3
- package/lib/compile/cdsc.js +26 -3
- 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/for/odata.js +1 -1
- 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 +13 -0
- 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-commons/uri/UriHelper.js +1 -1
- 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/deserializer/ResourceJsonDeserializer.js +5 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/DispatcherCommand.js +2 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/UriHelper.js +4 -1
- 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 +50 -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/index.js +2 -31
- 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 +9 -6
- 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 -417
- 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/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 +2 -2
- package/libx/_runtime/hana/driver.js +2 -2
- package/libx/_runtime/hana/execute.js +29 -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 +25 -13
- 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/create.js +6 -1
- package/libx/gql/resolvers/crud/delete.js +6 -1
- package/libx/gql/resolvers/crud/read.js +6 -1
- package/libx/gql/resolvers/crud/update.js +9 -1
- 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/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 +1 -1
- 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
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
const cds = require('../cds.js')
|
|
2
1
|
const AMQPWebhookMessaging = require('./AMQPWebhookMessaging')
|
|
3
2
|
const AMQPClient = require('./common-utils/AMQPClient.js')
|
|
4
3
|
|
|
5
4
|
const optionsMessaging = require('./message-queuing-utils/options-messaging.js')
|
|
6
5
|
const optionsManagement = require('./message-queuing-utils/options-management.js')
|
|
7
6
|
const authorizedRequest = require('./common-utils/authorizedRequest')
|
|
8
|
-
const LOG = cds.log('messaging')
|
|
9
7
|
|
|
10
8
|
class MQManagement {
|
|
11
|
-
constructor({ options, queueConfig, queueName, subscribedTopics }) {
|
|
9
|
+
constructor({ options, queueConfig, queueName, subscribedTopics, LOG }) {
|
|
12
10
|
this.options = options
|
|
13
11
|
this.queueConfig = queueConfig
|
|
14
12
|
this.queueName = queueName
|
|
15
13
|
this.subscribedTopics = subscribedTopics
|
|
14
|
+
this.LOG = LOG
|
|
16
15
|
}
|
|
17
16
|
|
|
18
17
|
async getQueue(queueName = this.queueName) {
|
|
@@ -21,7 +20,7 @@ class MQManagement {
|
|
|
21
20
|
uri: this.options.url,
|
|
22
21
|
path: `/v1/management/queues/${encodeURIComponent(queueName)}`,
|
|
23
22
|
oa2: this.options.auth.oauth2,
|
|
24
|
-
attemptInfo: () => LOG._info && LOG.info('Get queue', { queue: queueName }),
|
|
23
|
+
attemptInfo: () => this.LOG._info && this.LOG.info('Get queue', { queue: queueName }),
|
|
25
24
|
errMsg: `Queue "${queueName}" could not be retrieved`,
|
|
26
25
|
target: { kind: 'QUEUE', queue: queueName },
|
|
27
26
|
tokenStore: this
|
|
@@ -35,7 +34,7 @@ class MQManagement {
|
|
|
35
34
|
uri: this.options.url,
|
|
36
35
|
path: `/v1/management/queues`,
|
|
37
36
|
oa2: this.options.auth.oauth2,
|
|
38
|
-
attemptInfo: () => LOG._info && LOG.info('Get queues'),
|
|
37
|
+
attemptInfo: () => this.LOG._info && this.LOG.info('Get queues'),
|
|
39
38
|
errMsg: `Queues could not be retrieved`,
|
|
40
39
|
target: { kind: 'QUEUE' },
|
|
41
40
|
tokenStore: this
|
|
@@ -50,7 +49,7 @@ class MQManagement {
|
|
|
50
49
|
path: `/v1/management/queues/${encodeURIComponent(queueName)}`,
|
|
51
50
|
oa2: this.options.auth.oauth2,
|
|
52
51
|
dataObj: this.queueConfig,
|
|
53
|
-
attemptInfo: () => LOG._info && LOG.info('Create queue', { queue: queueName }),
|
|
52
|
+
attemptInfo: () => this.LOG._info && this.LOG.info('Create queue', { queue: queueName }),
|
|
54
53
|
errMsg: `Queue "${queueName}" could not be created`,
|
|
55
54
|
target: { kind: 'QUEUE', queue: queueName },
|
|
56
55
|
tokenStore: this
|
|
@@ -63,7 +62,7 @@ class MQManagement {
|
|
|
63
62
|
uri: this.options.url,
|
|
64
63
|
path: `/v1/management/queues/${encodeURIComponent(queueName)}`,
|
|
65
64
|
oa2: this.options.auth.oauth2,
|
|
66
|
-
attemptInfo: () => LOG._info && LOG.info('Delete queue', { queue: queueName }),
|
|
65
|
+
attemptInfo: () => this.LOG._info && this.LOG.info('Delete queue', { queue: queueName }),
|
|
67
66
|
errMsg: `Queue "${queueName}" could not be deleted`,
|
|
68
67
|
target: { kind: 'QUEUE', queue: queueName },
|
|
69
68
|
tokenStore: this
|
|
@@ -76,7 +75,7 @@ class MQManagement {
|
|
|
76
75
|
uri: this.options.url,
|
|
77
76
|
path: `/v1/management/queues/${encodeURIComponent(queueName)}/subscriptions/topics`,
|
|
78
77
|
oa2: this.options.auth.oauth2,
|
|
79
|
-
attemptInfo: () => LOG._info && LOG.info('Get subscriptions', { queue: queueName }),
|
|
78
|
+
attemptInfo: () => this.LOG._info && this.LOG.info('Get subscriptions', { queue: queueName }),
|
|
80
79
|
errMsg: `Subscriptions for "${queueName}" could not be retrieved`,
|
|
81
80
|
target: { kind: 'SUBSCRIPTION', queue: queueName },
|
|
82
81
|
tokenStore: this
|
|
@@ -92,7 +91,8 @@ class MQManagement {
|
|
|
92
91
|
topicPattern
|
|
93
92
|
)}`,
|
|
94
93
|
oa2: this.options.auth.oauth2,
|
|
95
|
-
attemptInfo: () =>
|
|
94
|
+
attemptInfo: () =>
|
|
95
|
+
this.LOG._info && this.LOG.info('Create subscription', { topic: topicPattern, queue: queueName }),
|
|
96
96
|
errMsg: `Subscription "${topicPattern}" could not be added to queue "${queueName}"`,
|
|
97
97
|
target: { kind: 'SUBSCRIPTION', queue: queueName, topic: topicPattern },
|
|
98
98
|
tokenStore: this
|
|
@@ -107,7 +107,8 @@ class MQManagement {
|
|
|
107
107
|
topicPattern
|
|
108
108
|
)}`,
|
|
109
109
|
oa2: this.options.auth.oauth2,
|
|
110
|
-
attemptInfo: () =>
|
|
110
|
+
attemptInfo: () =>
|
|
111
|
+
this.LOG._info && this.LOG.info('Delete subscription', { topic: topicPattern, queue: queueName }),
|
|
111
112
|
errMsg: `Subscription "${topicPattern}" could not be deleted from queue "${queueName}"`,
|
|
112
113
|
target: { kind: 'SUBSCRIPTION', queue: queueName, topic: topicPattern },
|
|
113
114
|
tokenStore: this
|
|
@@ -115,7 +116,7 @@ class MQManagement {
|
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
async createQueueAndSubscriptions() {
|
|
118
|
-
LOG._info && LOG.info(`Create messaging artifacts`)
|
|
119
|
+
this.LOG._info && this.LOG.info(`Create messaging artifacts`)
|
|
119
120
|
const created = await this.createQueue()
|
|
120
121
|
if (created && created.statusCode === 200) {
|
|
121
122
|
// We need to make sure to only keep our own subscriptions
|
|
@@ -171,7 +172,8 @@ class MessageQueuing extends AMQPWebhookMessaging {
|
|
|
171
172
|
options: _optionsManagement,
|
|
172
173
|
queueConfig,
|
|
173
174
|
queueName,
|
|
174
|
-
subscribedTopics: this.subscribedTopics
|
|
175
|
+
subscribedTopics: this.subscribedTopics,
|
|
176
|
+
LOG: this.LOG
|
|
175
177
|
})
|
|
176
178
|
return this.management
|
|
177
179
|
}
|
|
@@ -21,9 +21,10 @@ const _getMessagesEntity = () => {
|
|
|
21
21
|
return messagesEntity
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
// REVISIT: Is
|
|
24
|
+
// REVISIT: Is this always a reliable way to identify the provider tenant?
|
|
25
|
+
// Are there scenarios where the credentials have a different format?
|
|
25
26
|
const _isProviderTenant = tenant =>
|
|
26
|
-
cds.requires.
|
|
27
|
+
cds.requires.auth && cds.requires.auth.credentials && cds.requires.auth.credentials.identityzoneid === tenant
|
|
27
28
|
|
|
28
29
|
const hasPersistentOutbox = (srv, tenant) => {
|
|
29
30
|
if (!cds.requires.outbox || cds.requires.outbox.kind !== 'persistent-outbox') return false
|
|
@@ -86,16 +87,15 @@ const processMessages = async (service, tenant, _opts = {}) => {
|
|
|
86
87
|
} catch (e) {
|
|
87
88
|
// could potentially be a timeout
|
|
88
89
|
const _waitingTime = waitingTime(opts.attempt)
|
|
89
|
-
LOG.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
)
|
|
90
|
+
LOG.error(
|
|
91
|
+
'Outbox SELECT FOR UPDATE failed',
|
|
92
|
+
opts.attempt > 0
|
|
93
|
+
? ''
|
|
94
|
+
: {
|
|
95
|
+
cause: e
|
|
96
|
+
},
|
|
97
|
+
`Retrying in ${Math.round(_waitingTime / 1000)} s`
|
|
98
|
+
)
|
|
99
99
|
outboxRunner.schedule(
|
|
100
100
|
{
|
|
101
101
|
name,
|
|
@@ -127,7 +127,7 @@ const processMessages = async (service, tenant, _opts = {}) => {
|
|
|
127
127
|
const _waitingTime = waitingTime(error.failedMessage.attempts)
|
|
128
128
|
const info = { service: name, event: error.failedMessage.event }
|
|
129
129
|
if (error.failedMessage.attempts > 0) info.cause = error.failedMessage.error
|
|
130
|
-
LOG.
|
|
130
|
+
LOG.error('Emit failed', info, `Retrying in ${Math.round(_waitingTime / 1000)} s`)
|
|
131
131
|
await UPDATE(messagesEntity)
|
|
132
132
|
.where({ ID: error.failedMessage.ID })
|
|
133
133
|
.set({ attempts: { '+=': 1 } })
|
|
@@ -140,12 +140,11 @@ const processMessages = async (service, tenant, _opts = {}) => {
|
|
|
140
140
|
() => processMessages(service, tenant, opts)
|
|
141
141
|
)
|
|
142
142
|
} else {
|
|
143
|
-
LOG.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
)
|
|
143
|
+
LOG.error(
|
|
144
|
+
'Emit failed',
|
|
145
|
+
{ service: name, event: error.failedMessage.event, cause: error.failedMessage.error },
|
|
146
|
+
'Unrecoverable, outbox entry deleted'
|
|
147
|
+
)
|
|
149
148
|
}
|
|
150
149
|
} else {
|
|
151
150
|
outboxRunner.success({ name, tenant })
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/* eslint-disable prettier/prettier */
|
|
2
|
+
const redis = require('redis')
|
|
3
|
+
const cds = require('../../../lib')
|
|
4
|
+
const waitingTime = require('./common-utils/waitingTime')
|
|
5
|
+
const normalizeIncomingMessage = require('./common-utils/normalizeIncomingMessage')
|
|
6
|
+
const { hasPersistentOutbox } = require('./outbox/utils')
|
|
7
|
+
|
|
8
|
+
const _handleReconnects = (client, LOG) => {
|
|
9
|
+
client.on('reconnecting', () => {
|
|
10
|
+
LOG.warn('Reconnecting')
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
client.on('error', error => {
|
|
14
|
+
LOG.warn('Failed to connect to Redis: ', error)
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
class RedisMessaging extends cds.MessagingService {
|
|
19
|
+
async init() {
|
|
20
|
+
await super.init()
|
|
21
|
+
const credentials = this.options && this.options.credentials
|
|
22
|
+
const config = {
|
|
23
|
+
socket: {
|
|
24
|
+
reconnectStrategy: attempts => {
|
|
25
|
+
const _waitingTime = waitingTime(attempts)
|
|
26
|
+
this.LOG.warn(`Connection to Redis lost: Reconnecting in ${Math.round(_waitingTime / 1000)} s`)
|
|
27
|
+
return _waitingTime
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// rediss://someUserName:somePassword@hostName:port -> rediss://:somePassword@hostName:port
|
|
32
|
+
const url = credentials && credentials.uri && credentials.uri.replace(/\/\/.*?:/, '//:')
|
|
33
|
+
if (!url) this.LOG._warn && this.LOG.warn('No Redis credentials found, using default credentials')
|
|
34
|
+
else config.url = url
|
|
35
|
+
this.client = redis.createClient(config)
|
|
36
|
+
_handleReconnects(this.client, this.LOG)
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
await this.client.connect()
|
|
40
|
+
} catch (e) {
|
|
41
|
+
throw new Error('Connection to Redis could not be established: ' + e)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
this._ready = true
|
|
45
|
+
this.client.on('end', () => {
|
|
46
|
+
this._ready = false
|
|
47
|
+
})
|
|
48
|
+
this.client.on('error', () => {
|
|
49
|
+
this._ready = false
|
|
50
|
+
})
|
|
51
|
+
this.client.on('ready', () => {
|
|
52
|
+
this._ready = true
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
cds.once('listening', () => {
|
|
56
|
+
this.startListening()
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async startListening() {
|
|
61
|
+
let subscriber
|
|
62
|
+
for (const topic of [...this.subscribedTopics].map(kv => kv[0])) {
|
|
63
|
+
if (!subscriber) {
|
|
64
|
+
// For subscriptions we need to duplicate the connection
|
|
65
|
+
subscriber = this.client.duplicate()
|
|
66
|
+
_handleReconnects(subscriber)
|
|
67
|
+
await subscriber.connect()
|
|
68
|
+
}
|
|
69
|
+
this.LOG._info && this.LOG('Create subscription', { topic })
|
|
70
|
+
await subscriber.subscribe(topic, async message => {
|
|
71
|
+
const msg = normalizeIncomingMessage(message)
|
|
72
|
+
msg.event = topic
|
|
73
|
+
try {
|
|
74
|
+
await super.emit(msg)
|
|
75
|
+
} catch (e) {
|
|
76
|
+
this.LOG.error('ERROR occured in asynchronous event processing:', e)
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async emit(msg) {
|
|
83
|
+
const _msg = this.message4(msg)
|
|
84
|
+
this.LOG._info && this.LOG.info('Emit', { topic: _msg.event })
|
|
85
|
+
if (!this._ready && hasPersistentOutbox(this, cds.context && cds.context.tenant))
|
|
86
|
+
throw new Error('Redis connection not ready')
|
|
87
|
+
await this.client.publish(_msg.event, JSON.stringify({ data: _msg.data, ...(_msg.headers || {}) }))
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = RedisMessaging
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
const cds = require('../cds')
|
|
2
|
-
const LOG = cds.log('messaging')
|
|
3
2
|
const queued = require('./common-utils/queued')
|
|
4
3
|
const OutboxService = require('./Outbox')
|
|
5
4
|
|
|
6
5
|
const _topic = declared => declared['@topic'] || declared.name
|
|
7
6
|
|
|
8
7
|
let usedTopicOnce = false
|
|
9
|
-
const _warnAndStripTopicPrefix = event => {
|
|
8
|
+
const _warnAndStripTopicPrefix = (event, LOG) => {
|
|
10
9
|
if (event.startsWith('topic:')) {
|
|
11
10
|
// backwards compatibility
|
|
12
11
|
event = event.replace(/topic:/, '')
|
|
@@ -24,6 +23,7 @@ class MessagingService extends OutboxService {
|
|
|
24
23
|
// enables queued async operations (without awaiting)
|
|
25
24
|
this.queued = queued()
|
|
26
25
|
this.subscribedTopics = new Map()
|
|
26
|
+
this.LOG = cds.log(this.kind ? `${this.kind}|messaging` : 'messaging')
|
|
27
27
|
// Only for one central `messaging` service, otherwise all technical services would register themselves
|
|
28
28
|
if (this.name === 'messaging') {
|
|
29
29
|
this._registeredServices = new Map()
|
|
@@ -81,7 +81,7 @@ class MessagingService extends OutboxService {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
on(event, cb) {
|
|
84
|
-
const _event = _warnAndStripTopicPrefix(event)
|
|
84
|
+
const _event = _warnAndStripTopicPrefix(event, this.LOG)
|
|
85
85
|
// save all subscribed topics (not needed for local-messaging)
|
|
86
86
|
this.subscribedTopics.set(this.prepareTopic(_event, true), _event)
|
|
87
87
|
return super.on(_event, cb)
|
|
@@ -109,9 +109,11 @@ class MessagingService extends OutboxService {
|
|
|
109
109
|
|
|
110
110
|
message4(msg) {
|
|
111
111
|
const _msg = { ...msg }
|
|
112
|
-
if (msg.inbound && !cds.context)
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
if (msg.inbound && !cds.context) {
|
|
113
|
+
// REVISIT: why are all inbound messages executed with privileged user?
|
|
114
|
+
cds.context = { tenant: msg.tenant, user: new cds.User.Privileged() }
|
|
115
|
+
}
|
|
116
|
+
_msg.event = _warnAndStripTopicPrefix(_msg.event, this.LOG)
|
|
115
117
|
if (!_msg.headers) _msg.headers = {}
|
|
116
118
|
if (!_msg.inbound) {
|
|
117
119
|
_msg.headers = { ..._msg.headers } // don't change the original object
|
|
@@ -40,12 +40,13 @@ const _setCorrectValue = (el, data, params, kind) => {
|
|
|
40
40
|
const _buildPartialUrlFunctions = (url, data, params, kind = 'odata-v4') => {
|
|
41
41
|
const funcParams = []
|
|
42
42
|
const queryOptions = []
|
|
43
|
-
|
|
43
|
+
// REVISIT: take params from params after importer fix (the keys should not be part of params)
|
|
44
|
+
for (const param in data) {
|
|
44
45
|
if (kind === 'odata-v2') {
|
|
45
|
-
funcParams.push(`${
|
|
46
|
+
funcParams.push(`${param}=${_setCorrectValue(param, data, params, kind)}`)
|
|
46
47
|
} else {
|
|
47
|
-
funcParams.push(`${
|
|
48
|
-
queryOptions.push(`@${
|
|
48
|
+
funcParams.push(`${param}=@${param}`)
|
|
49
|
+
queryOptions.push(`@${param}=${_setCorrectValue(param, data, params, kind)}`)
|
|
49
50
|
}
|
|
50
51
|
}
|
|
51
52
|
return kind === 'odata-v2'
|
|
@@ -62,8 +63,17 @@ const _extractParamsFromData = (data, params) => {
|
|
|
62
63
|
|
|
63
64
|
const _buildKeys = (req, kind) => {
|
|
64
65
|
const keys = []
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
if (req.params && req.params.length > 0) {
|
|
67
|
+
const p1 = req.params[0]
|
|
68
|
+
if (typeof p1 !== 'object') return [p1]
|
|
69
|
+
for (const key in req.target.keys) {
|
|
70
|
+
keys.push(`${key}=${formatVal(p1[key], key, req.target, kind)}`)
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
// REVISIT: shall we keep that or remove it?
|
|
74
|
+
for (const key in req.target.keys) {
|
|
75
|
+
keys.push(`${key}=${formatVal(req.data[key], key, req.target, kind)}`)
|
|
76
|
+
}
|
|
67
77
|
}
|
|
68
78
|
return keys
|
|
69
79
|
}
|
|
@@ -83,7 +93,14 @@ const _handleBoundActionFunction = (srv, def, req, url) => {
|
|
|
83
93
|
|
|
84
94
|
const _handleUnboundActionFunction = (srv, def, req, event) => {
|
|
85
95
|
if (def.kind === 'action') {
|
|
86
|
-
return
|
|
96
|
+
// REVISIT: only for "rest" unbound actions/functions, we enforce axios to return a buffer
|
|
97
|
+
// required by cds-mt
|
|
98
|
+
const isBinary =
|
|
99
|
+
srv.kind === 'rest' &&
|
|
100
|
+
def &&
|
|
101
|
+
def.returns &&
|
|
102
|
+
(def.returns.type === 'cds.LargeBinary' || def.returns.type === 'cds.Binary')
|
|
103
|
+
return srv.send({ method: 'POST', path: `/${event}`, data: req.data, _binary: isBinary })
|
|
87
104
|
}
|
|
88
105
|
|
|
89
106
|
const url =
|
|
@@ -97,11 +114,30 @@ const _handleV2ActionFunction = (srv, def, req, event, kind) => {
|
|
|
97
114
|
return def.kind === 'function' ? srv.get(url) : srv.post(url, {})
|
|
98
115
|
}
|
|
99
116
|
|
|
117
|
+
const _handleV2BoundActionFunction = (srv, def, req, event, kind) => {
|
|
118
|
+
const params = []
|
|
119
|
+
const data = req.data
|
|
120
|
+
// REVISIT: take params from def.params, after importer fix (the keys should not be part of params)
|
|
121
|
+
for (const param in req.data) {
|
|
122
|
+
params.push(`${param}=${formatVal(data[param], param, { elements: def.params }, kind)}`)
|
|
123
|
+
}
|
|
124
|
+
const keys = _buildKeys(req, this.kind)
|
|
125
|
+
if (keys.length === 1 && typeof req.params[0] !== 'object') {
|
|
126
|
+
params.push(`${Object.keys(req.target.keys)[0]}=${keys[0]}`)
|
|
127
|
+
} else {
|
|
128
|
+
params.push(...keys)
|
|
129
|
+
}
|
|
130
|
+
const url = `${`/${event}`}?${params.join('&')}`
|
|
131
|
+
return def.kind === 'function' ? srv.get(url) : srv.post(url, {})
|
|
132
|
+
}
|
|
133
|
+
|
|
100
134
|
const _addHandlerActionFunction = (srv, def, target) => {
|
|
101
135
|
const event = def.name.match(/\w*$/)[0]
|
|
102
136
|
if (target) {
|
|
103
137
|
srv.on(event, target, async function (req) {
|
|
104
138
|
const shortEntityName = req.target.name.replace(`${this.namespace}.`, '')
|
|
139
|
+
if (this.kind === 'odata-v2')
|
|
140
|
+
return _handleV2BoundActionFunction(srv, def, req, `${shortEntityName}_${event}`, this.kind)
|
|
105
141
|
const url = `/${shortEntityName}(${_buildKeys(req, this.kind).join(',')})/${this.namespace}.${event}`
|
|
106
142
|
return _handleBoundActionFunction(srv, def, req, url)
|
|
107
143
|
})
|
|
@@ -208,7 +244,7 @@ class RemoteService extends cds.Service {
|
|
|
208
244
|
|
|
209
245
|
if (req.target && req.target.name && this.definition && req.target.name.startsWith(this.definition.name + '.')) {
|
|
210
246
|
const result = await super.handle(req)
|
|
211
|
-
// only post process if alias was
|
|
247
|
+
// only post process if alias was explicitly set in query
|
|
212
248
|
if (_selectOnlyWithAlias(req.query)) {
|
|
213
249
|
return postProcess(req.query, result, this, true)
|
|
214
250
|
}
|
|
@@ -3,8 +3,6 @@ const LOG = cds.log('remote')
|
|
|
3
3
|
|
|
4
4
|
const SANITIZE_VALUES = process.env.NODE_ENV === 'production' && cds.env.log.sanitize_values !== false
|
|
5
5
|
|
|
6
|
-
const cdsLocale = require('../../../../lib/req/locale')
|
|
7
|
-
|
|
8
6
|
const { convertV2ResponseData, deepSanitize, convertV2PayloadData } = require('./data')
|
|
9
7
|
|
|
10
8
|
let _cloudSdkCore
|
|
@@ -67,6 +65,11 @@ const getDestination = (name, credentials) => {
|
|
|
67
65
|
throw new Error(`"url" or "destination" property must be configured in "credentials" of "${name}".`)
|
|
68
66
|
}
|
|
69
67
|
|
|
68
|
+
// Cloud SDK wants property "queryParameters" but we have documented "queries"
|
|
69
|
+
if (credentials.queries && !credentials.queryParameters) {
|
|
70
|
+
credentials.queryParameters = credentials.queries
|
|
71
|
+
}
|
|
72
|
+
|
|
70
73
|
return { name, ...credentials }
|
|
71
74
|
}
|
|
72
75
|
|
|
@@ -244,10 +247,11 @@ const run = async (
|
|
|
244
247
|
|
|
245
248
|
const sanitizedError = _getSanitizedError(e, requestConfig, suppressRemoteResponseBody)
|
|
246
249
|
|
|
247
|
-
LOG._warn && LOG.warn(sanitizedError)
|
|
248
|
-
|
|
249
250
|
// REVISIT: switch from innererror to reason in cds^6
|
|
250
|
-
|
|
251
|
+
const err = Object.assign(new Error(e.message), { statusCode: 502, innererror: sanitizedError })
|
|
252
|
+
LOG._warn && LOG.warn(err)
|
|
253
|
+
|
|
254
|
+
throw err
|
|
251
255
|
}
|
|
252
256
|
|
|
253
257
|
// text/html indicates a redirect -> reject
|
|
@@ -266,13 +270,15 @@ const run = async (
|
|
|
266
270
|
|
|
267
271
|
const sanitizedError = _getSanitizedError(e, requestConfig, suppressRemoteResponseBody)
|
|
268
272
|
|
|
269
|
-
LOG._warn && LOG.warn(sanitizedError)
|
|
270
|
-
|
|
271
273
|
// REVISIT: switch from innererror to reason in cds^6
|
|
272
|
-
|
|
274
|
+
const err = Object.assign(new Error(`Error during request to remote service: ${e.message}`), {
|
|
273
275
|
statusCode: 502,
|
|
274
276
|
innererror: sanitizedError
|
|
275
277
|
})
|
|
278
|
+
|
|
279
|
+
LOG._warn && LOG.warn(err)
|
|
280
|
+
|
|
281
|
+
throw err
|
|
276
282
|
}
|
|
277
283
|
|
|
278
284
|
// get result of $batch
|
|
@@ -295,10 +301,13 @@ const run = async (
|
|
|
295
301
|
? 'Error during request to remote service: ' + contentJSON.message
|
|
296
302
|
: 'Request to remote service failed.'
|
|
297
303
|
const sanitizedError = _getSanitizedError(contentJSON, requestConfig)
|
|
298
|
-
|
|
304
|
+
|
|
305
|
+
const err = Object.assign(new Error(contentJSON.message), { statusCode: 502, innererror: sanitizedError })
|
|
306
|
+
|
|
307
|
+
LOG._warn && LOG.warn(err)
|
|
299
308
|
|
|
300
309
|
// REVISIT: switch from innererror to reason in cds^6
|
|
301
|
-
throw
|
|
310
|
+
throw err
|
|
302
311
|
}
|
|
303
312
|
}
|
|
304
313
|
|
|
@@ -394,9 +403,7 @@ const getReqOptions = (req, query, service) => {
|
|
|
394
403
|
if (!_hasHeader(req.headers, 'accept-language')) {
|
|
395
404
|
// Forward the locale properties from the original request (including region variants or weight factors),
|
|
396
405
|
// if not given, it's taken from the user's locale (normalized and simplified)
|
|
397
|
-
const locale =
|
|
398
|
-
(req.context && req.context._ && req.context._.req && cdsLocale.from_req(req.context._.req)) ||
|
|
399
|
-
(req.user && req.user.locale)
|
|
406
|
+
const locale = req._locale
|
|
400
407
|
if (locale) reqOptions.headers['accept-language'] = locale
|
|
401
408
|
}
|
|
402
409
|
|
|
@@ -437,6 +444,11 @@ const getReqOptions = (req, query, service) => {
|
|
|
437
444
|
|
|
438
445
|
if (service.path) reqOptions.url = `${encodeURI(service.path)}${reqOptions.url}`
|
|
439
446
|
|
|
447
|
+
// set axios responseType to 'arraybuffer' if returning binary in rest
|
|
448
|
+
if (req._binary) {
|
|
449
|
+
reqOptions.responseType = 'arraybuffer'
|
|
450
|
+
}
|
|
451
|
+
|
|
440
452
|
return reqOptions
|
|
441
453
|
}
|
|
442
454
|
|
|
@@ -60,15 +60,15 @@ const _convertValue = (ieee754Compatible, exponentialDecimals) => (value, elemen
|
|
|
60
60
|
|
|
61
61
|
const type = _elementType(element)
|
|
62
62
|
|
|
63
|
-
if (
|
|
63
|
+
if (type === 'cds.Boolean') {
|
|
64
64
|
if (value === 'true') {
|
|
65
65
|
value = true
|
|
66
66
|
} else if (value === 'false') {
|
|
67
67
|
value = false
|
|
68
68
|
}
|
|
69
|
-
} else if (
|
|
69
|
+
} else if (type === 'cds.Integer') {
|
|
70
70
|
value = parseInt(value, 10)
|
|
71
|
-
} else if (
|
|
71
|
+
} else if (type === 'cds.Decimal' || type === 'cds.DecimalFloat' || type === 'cds.Integer64') {
|
|
72
72
|
const bigValue = big(value)
|
|
73
73
|
if (ieee754Compatible) {
|
|
74
74
|
// TODO test with arrayed => element.items.scale?
|
|
@@ -77,15 +77,15 @@ const _convertValue = (ieee754Compatible, exponentialDecimals) => (value, elemen
|
|
|
77
77
|
// OData V2 does not even mention ieee754Compatible, but V4 requires JSON number if ieee754Compatible=false
|
|
78
78
|
value = bigValue.toNumber()
|
|
79
79
|
}
|
|
80
|
-
} else if (
|
|
80
|
+
} else if (type === 'cds.Double') {
|
|
81
81
|
value = parseFloat(value)
|
|
82
|
-
} else if (
|
|
82
|
+
} else if (type === 'cds.Time') {
|
|
83
83
|
const match = value.match(DurationRegex)
|
|
84
84
|
|
|
85
85
|
if (match) {
|
|
86
86
|
value = `${match[4] || '00'}:${match[5] || '00'}:${match[6] || '00'}`
|
|
87
87
|
}
|
|
88
|
-
} else if (
|
|
88
|
+
} else if (type === 'cds.Timestamp' || type === 'cds.DateTime' || type === 'cds.Date') {
|
|
89
89
|
const match = value.match(/\/Date\((.*)\)\//)
|
|
90
90
|
const ticksAndOffset = match && match.pop()
|
|
91
91
|
|
|
@@ -93,9 +93,9 @@ const _convertValue = (ieee754Compatible, exponentialDecimals) => (value, elemen
|
|
|
93
93
|
value = new Date(_calculateTicksOffsetSum(ticksAndOffset)).toISOString() // always UTC
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
if (
|
|
96
|
+
if (type === 'cds.DateTime') {
|
|
97
97
|
value = value.slice(0, 19) + 'Z' // Cut millis
|
|
98
|
-
} else if (
|
|
98
|
+
} else if (type === 'cds.Date') {
|
|
99
99
|
value = value.slice(0, 10) // Cut time
|
|
100
100
|
}
|
|
101
101
|
}
|
|
@@ -136,15 +136,15 @@ const _elementType = element => {
|
|
|
136
136
|
let type
|
|
137
137
|
|
|
138
138
|
if (element) {
|
|
139
|
-
type = element.
|
|
139
|
+
type = element._type
|
|
140
140
|
|
|
141
141
|
if (element['@odata.Type']) {
|
|
142
142
|
const odataType = element['@odata.Type'].match(/\w+$/)
|
|
143
143
|
type = (odataType && DataTypeOData[odataType[0]]) || type
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
if (!type && element.items && element.items.
|
|
147
|
-
type = element.items.
|
|
146
|
+
if (!type && element.items && element.items._type) {
|
|
147
|
+
type = element.items._type
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
150
|
|
|
@@ -91,17 +91,12 @@ module.exports = class SQLiteDatabase extends DatabaseService {
|
|
|
91
91
|
|
|
92
92
|
_registerAfterHandlers() {
|
|
93
93
|
// REVISIT: after phase runs in parallel -> side effects possible!
|
|
94
|
-
const { effective } = cds.env
|
|
94
|
+
const { effective, features } = cds.env
|
|
95
95
|
|
|
96
|
-
if (effective.odata.structs) {
|
|
96
|
+
if (effective.odata.structs && !features.ucsn_struct_conversion) {
|
|
97
97
|
// REVISIT: only register for entities that contain structured or navigation to it
|
|
98
98
|
this.after(['READ'], '*', this._structured)
|
|
99
99
|
}
|
|
100
|
-
|
|
101
|
-
if (effective.odata.version !== 'v2') {
|
|
102
|
-
// REVISIT: only register for entities that contain arrayed or navigation to it
|
|
103
|
-
this.after(['READ'], '*', this._arrayed)
|
|
104
|
-
}
|
|
105
100
|
}
|
|
106
101
|
|
|
107
102
|
/*
|
|
@@ -110,7 +105,9 @@ module.exports = class SQLiteDatabase extends DatabaseService {
|
|
|
110
105
|
async acquire(arg) {
|
|
111
106
|
// in non-multi-tenant scenarios, the default db should be returned regardless of arg
|
|
112
107
|
let tenant = 'anonymous'
|
|
113
|
-
|
|
108
|
+
const isMultitenant = 'multiTenant' in this.options ? this.options.multiTenant : cds.env.requires.multitenancy
|
|
109
|
+
// REVISIT: there should already be compat for the multitenancy flag, why is it not working here?
|
|
110
|
+
if (isMultitenant && arg) {
|
|
114
111
|
if (typeof arg === 'string') tenant = arg
|
|
115
112
|
else tenant = arg.tenant || (arg.user && arg.user.tenant) || tenant // > REVISIT: remove fallback arg.user.tenant with cds^6
|
|
116
113
|
}
|
|
@@ -120,7 +117,7 @@ module.exports = class SQLiteDatabase extends DatabaseService {
|
|
|
120
117
|
const credentials = this.options.credentials || this.options || {}
|
|
121
118
|
let dbUrl = credentials.database || credentials.url || credentials.host || ':memory:'
|
|
122
119
|
|
|
123
|
-
if (
|
|
120
|
+
if (isMultitenant && dbUrl.endsWith('.db')) {
|
|
124
121
|
dbUrl = dbUrl.split('.db')[0] + '_' + tenant + '.db'
|
|
125
122
|
}
|
|
126
123
|
|
|
@@ -8,7 +8,10 @@ const dateTimeFunctions = new Map([
|
|
|
8
8
|
['hour', "'%H'"],
|
|
9
9
|
['minute', "'%M'"]
|
|
10
10
|
])
|
|
11
|
-
const
|
|
11
|
+
const STANDAD_FUNCTIONS_MAP = ['locate', 'substring', 'to_date', 'to_time'].reduce((acc, cur) => {
|
|
12
|
+
acc[cur] = 1
|
|
13
|
+
return acc
|
|
14
|
+
}, {})
|
|
12
15
|
|
|
13
16
|
class CustomFunctionBuilder extends FunctionBuilder {
|
|
14
17
|
get ExpressionBuilder() {
|
|
@@ -35,7 +38,7 @@ class CustomFunctionBuilder extends FunctionBuilder {
|
|
|
35
38
|
|
|
36
39
|
if (dateTimeFunctions.has(functionName)) {
|
|
37
40
|
this._timeFunction(functionName, args)
|
|
38
|
-
} else if (
|
|
41
|
+
} else if (functionName in STANDAD_FUNCTIONS_MAP) {
|
|
39
42
|
this._standardFunction(functionName, args)
|
|
40
43
|
} else if (functionName === 'seconds_between') {
|
|
41
44
|
this._secondsBetweenFunction(args)
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
* @typedef {object} TemplateProcessorPathOptions
|
|
50
50
|
* @property {object} [extraKeys]
|
|
51
51
|
* @property {function} [rowKeysGenerator]
|
|
52
|
-
* @property {
|
|
52
|
+
* @property {pathSegment[]} [path=[]] - Path segments to relate the error message.
|
|
53
53
|
* @property {boolean} [includeKeyValues=false] Indicates whether the key values are included in the path segments
|
|
54
54
|
* The path segments are used to build the error target (a relative resource path)
|
|
55
55
|
*/
|
|
@@ -70,7 +70,14 @@
|
|
|
70
70
|
* @property {object} element
|
|
71
71
|
* @property {boolean} plain
|
|
72
72
|
* @property {boolean} isRoot
|
|
73
|
-
* @property {Array<
|
|
73
|
+
* @property {Array<pathSegment>} [path]
|
|
74
|
+
*/
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @typedef {object} pathSegment
|
|
78
|
+
* @property {string} key
|
|
79
|
+
* @property {number} idx
|
|
80
|
+
* @property {string} url
|
|
74
81
|
*/
|
|
75
82
|
|
|
76
83
|
// Search
|
|
@@ -102,6 +109,7 @@
|
|
|
102
109
|
* @typedef {object} cqn2cqn4sqlOptions
|
|
103
110
|
* @property {boolean} [suppressSearch=false] Indicates whether the search handler is called.
|
|
104
111
|
* @property {boolean} [_4fiori]
|
|
112
|
+
* @property {boolean} [_4db]
|
|
105
113
|
* @property {import('../db/Service')} [service]
|
|
106
114
|
*/
|
|
107
115
|
|