@sap/cds 6.2.2 → 6.3.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 +56 -0
- package/apis/connect.d.ts +1 -1
- package/apis/cqn.d.ts +1 -1
- package/apis/internal/inference.d.ts +14 -0
- package/apis/ql.d.ts +40 -36
- package/apis/services.d.ts +23 -6
- package/bin/build/buildTaskHandler.js +3 -3
- package/bin/build/provider/buildTaskHandlerEdmx.js +1 -1
- package/bin/build/provider/buildTaskHandlerFeatureToggles.js +4 -3
- package/bin/build/provider/buildTaskHandlerInternal.js +2 -2
- package/bin/build/provider/java/index.js +2 -1
- package/bin/build/provider/mtx/index.js +2 -1
- package/bin/build/provider/mtx/resourcesTarBuilder.js +3 -2
- package/bin/build/provider/mtx-extension/index.js +2 -1
- package/bin/build/provider/mtx-sidecar/index.js +3 -1
- package/lib/auth/index.js +2 -1
- package/lib/auth/jwt-auth.js +64 -3
- package/lib/auth/xsuaa-auth.js +2 -3
- package/lib/compile/cdsc.js +1 -0
- package/lib/compile/etc/_localized.js +1 -0
- package/lib/dbs/cds-deploy.js +2 -1
- package/lib/env/cds-env.js +14 -49
- package/lib/env/cds-requires.js +13 -7
- package/lib/env/defaults.js +4 -0
- package/lib/i18n/localize.js +11 -8
- package/lib/index.js +3 -2
- package/lib/log/cds-log.js +2 -2
- package/lib/log/format/cf.js +16 -0
- package/lib/log/format/kibana.js +15 -2
- package/lib/ql/INSERT.js +12 -11
- package/lib/ql/Query.js +14 -7
- package/lib/ql/UPSERT.js +1 -0
- package/lib/ql/Whereable.js +6 -2
- package/lib/ql/cds-ql.js +2 -4
- package/lib/req/request.js +2 -0
- package/lib/srv/middlewares/cds-context.js +1 -1
- package/lib/srv/srv-dispatch.js +1 -0
- package/lib/srv/srv-tx.js +3 -3
- package/lib/utils/cds-utils.js +76 -31
- package/lib/utils/inflect.js +24 -0
- package/libx/_runtime/auth/strategies/ias-auth.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +9 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +23 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +1 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/validator/ValueValidator.js +27 -15
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +1 -1
- package/libx/_runtime/cds-services/services/utils/compareJson.js +11 -10
- package/libx/_runtime/cds-services/services/utils/differ.js +6 -4
- package/libx/_runtime/common/composition/data.js +29 -40
- package/libx/_runtime/common/composition/update.js +6 -19
- package/libx/_runtime/common/generic/paging.js +1 -1
- package/libx/_runtime/common/utils/resolveView.js +7 -13
- package/libx/_runtime/db/utils/generateAliases.js +1 -0
- package/libx/_runtime/fiori/generic/before.js +2 -1
- package/libx/_runtime/fiori/generic/read.js +11 -4
- package/libx/_runtime/hana/execute.js +2 -2
- package/libx/_runtime/hana/search2cqn4sql.js +1 -0
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +5 -2
- package/libx/_runtime/messaging/enterprise-messaging.js +7 -1
- package/libx/_runtime/messaging/file-based.js +1 -1
- package/libx/_runtime/messaging/message-queuing.js +5 -2
- package/libx/_runtime/messaging/outbox/utils.js +1 -1
- package/libx/_runtime/messaging/service.js +13 -8
- package/libx/odata/cqn2odata.js +4 -1
- package/libx/odata/utils.js +8 -7
- package/libx/rest/RestAdapter.js +1 -4
- package/package.json +2 -2
|
@@ -353,7 +353,7 @@ const _newSelect = (query, transitions, service) => {
|
|
|
353
353
|
if (!newSelect.columns && targetTransition.mapping.size) newSelect.columns = _initialColumns(targetTransition)
|
|
354
354
|
if (newSelect.columns) {
|
|
355
355
|
rewriteAsterisks({ SELECT: query.SELECT }, service.model, {
|
|
356
|
-
_4db: service instanceof cds.DatabaseService,
|
|
356
|
+
_4db: service instanceof cds.DatabaseService || service.kind === 'better-sqlite',
|
|
357
357
|
target: targetTransition.queryTarget
|
|
358
358
|
})
|
|
359
359
|
newSelect.columns = _newColumns(newSelect.columns, targetTransition, service, service.kind !== 'app-service')
|
|
@@ -517,26 +517,20 @@ const _getTransitionData = (target, columns, service, skipForbiddenViewCheck) =>
|
|
|
517
517
|
if (!skipForbiddenViewCheck) _checkForForbiddenViews(target)
|
|
518
518
|
const targetStartsWithSrvName = service.namespace && target.name.startsWith(`${service.namespace}.`)
|
|
519
519
|
const persistenceTable = _isPersistenceTable(target)
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
!(service instanceof cds.DatabaseService) && !targetStartsWithSrvName
|
|
525
|
-
)
|
|
526
|
-
if (persistenceTable && service instanceof cds.DatabaseService) {
|
|
520
|
+
const isDatabaseService = service instanceof cds.DatabaseService || service.kind === 'better-sqlite'
|
|
521
|
+
columns = _queryColumns(target, columns, persistenceTable, !isDatabaseService && !targetStartsWithSrvName)
|
|
522
|
+
// REVISIT: Change once we expose database service
|
|
523
|
+
if (persistenceTable && isDatabaseService) {
|
|
527
524
|
return { target, transitionColumns: columns }
|
|
528
525
|
}
|
|
529
526
|
// stop projection resolving if it starts with the service name prefix
|
|
530
|
-
if (!
|
|
527
|
+
if (!isDatabaseService && targetStartsWithSrvName) {
|
|
531
528
|
return { target, transitionColumns: columns }
|
|
532
529
|
}
|
|
533
530
|
// continue projection resolving if the target is a projection
|
|
534
531
|
if (target.query && target.query._target) {
|
|
535
532
|
const newTarget = target.query._target
|
|
536
|
-
if (
|
|
537
|
-
service instanceof cds.DatabaseService ||
|
|
538
|
-
!(service.namespace && newTarget.name.startsWith(`${service.namespace}.`))
|
|
539
|
-
) {
|
|
533
|
+
if (isDatabaseService || !(service.namespace && newTarget.name.startsWith(`${service.namespace}.`))) {
|
|
540
534
|
return _getTransitionData(newTarget, columns, service, skipForbiddenViewCheck)
|
|
541
535
|
}
|
|
542
536
|
return { target: newTarget, transitionColumns: columns }
|
|
@@ -57,6 +57,7 @@ const _generateAliases = (partialCqn, aliasMap = new Map()) => {
|
|
|
57
57
|
_redirectXpr(partialCqn.SELECT.having, selectMap)
|
|
58
58
|
_redirectXpr(partialCqn.SELECT.columns, selectMap)
|
|
59
59
|
_redirectXpr(partialCqn.SELECT.groupBy, selectMap)
|
|
60
|
+
_redirectXpr(partialCqn.SELECT.orderBy, selectMap)
|
|
60
61
|
return
|
|
61
62
|
}
|
|
62
63
|
|
|
@@ -160,7 +160,8 @@ const _registerBoundActionHandlers = function (entityName, actions) {
|
|
|
160
160
|
action.kind === 'action' &&
|
|
161
161
|
action.name !== 'draftPrepare' &&
|
|
162
162
|
action.name !== 'draftEdit' &&
|
|
163
|
-
action.name !== 'draftActivate'
|
|
163
|
+
action.name !== 'draftActivate' &&
|
|
164
|
+
!action['@cds.odata.bindingparameter.collection']
|
|
164
165
|
)
|
|
165
166
|
|
|
166
167
|
for (const action of boundActions) {
|
|
@@ -187,16 +187,23 @@ const _getDraftPropertiesDetermineDraft = (req, where, tableName, calcDraftUUID
|
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
function _copyCQNPartial(partial) {
|
|
190
|
-
if (partial.SELECT
|
|
190
|
+
if (partial.SELECT) {
|
|
191
191
|
const newPartial = Object.assign({}, partial)
|
|
192
192
|
const newSELECT = Object.assign({}, partial.SELECT)
|
|
193
193
|
newSELECT.from = _copyCQNPartial(partial.SELECT.from)
|
|
194
194
|
newPartial.SELECT = newSELECT
|
|
195
|
+
if (partial.SELECT._4odata) newSELECT._4odata = true
|
|
195
196
|
if (partial.SELECT.columns) newPartial.SELECT.columns = _copyArray(partial.SELECT.columns)
|
|
196
197
|
if (partial.SELECT.where) newPartial.SELECT.where = _copyArray(partial.SELECT.where)
|
|
197
198
|
return newPartial
|
|
198
199
|
}
|
|
199
200
|
|
|
201
|
+
if (partial.id) {
|
|
202
|
+
const res = Object.assign({}, partial)
|
|
203
|
+
if (res.where) res.where = _copyArray(res.where)
|
|
204
|
+
return res
|
|
205
|
+
}
|
|
206
|
+
|
|
200
207
|
if (partial.ref) {
|
|
201
208
|
return Object.assign({}, partial, { ref: _copyArray(partial.ref) })
|
|
202
209
|
}
|
|
@@ -1272,12 +1279,12 @@ const fioriGenericRead = async function (req) {
|
|
|
1272
1279
|
req.target = _getLocalizedEntity(this.model, req.target, req.user) || req.target
|
|
1273
1280
|
|
|
1274
1281
|
// REVISIT DRAFT HANDLING: cqn2cqn4sql must not be called here
|
|
1275
|
-
const query4sql = cqn2cqn4sql(req.query, this.model, { _4fiori: true })
|
|
1282
|
+
const query4sql = cqn2cqn4sql(_copyCQNPartial(req.query), this.model, { _4fiori: true })
|
|
1276
1283
|
|
|
1277
1284
|
// Clone the request. Do not clone with Object.assign as that would skip all non-enumerable properties.
|
|
1278
1285
|
// REVISIT: query4sql.clone() doesn't really clone the original query, hence _generateCQN will heavily modify
|
|
1279
1286
|
// it, e.g. IsActiveEntity is stripped. This is a problem for subsequent handlers which rely on this information.
|
|
1280
|
-
const reqClone = { __proto__: req, query: query4sql
|
|
1287
|
+
const reqClone = { __proto__: req, query: query4sql }
|
|
1281
1288
|
// Clone draft restrictions to the cloned query.
|
|
1282
1289
|
reqClone.query._draftRestrictions = query._draftRestrictions
|
|
1283
1290
|
|
|
@@ -1320,7 +1327,7 @@ const fioriGenericRead = async function (req) {
|
|
|
1320
1327
|
_adaptColumns4readAfterWrite(req, cqnScenario, query4sql)
|
|
1321
1328
|
|
|
1322
1329
|
const result = await cds.tx(req).send({ query: cqnScenario.cqn, target: req.target })
|
|
1323
|
-
return _postProcess(result,
|
|
1330
|
+
return _postProcess(result, reqClone, cqnScenario, enhancedWithLastChangeDateTime)
|
|
1324
1331
|
}
|
|
1325
1332
|
|
|
1326
1333
|
module.exports = cds.service.impl(function (srv, entity) {
|
|
@@ -51,8 +51,8 @@ const BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Z
|
|
|
51
51
|
|
|
52
52
|
function _getProcedureName(sql) {
|
|
53
53
|
// name delimited with "" allows any character
|
|
54
|
-
const match = sql.trim().match(/^call \s*(("(?<delimited>.+)")|(?<undelimited>\w+))\s*\(/i)
|
|
55
|
-
return match && (match.groups.undelimited
|
|
54
|
+
const match = sql.trim().match(/^call \s*(("\w+"\.)?("(?<delimited>.+)")|(\w+\.)?(?<undelimited>\w+))\s*\(/i)
|
|
55
|
+
return match && (match.groups.undelimited ?? match.groups.delimited)
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
function _hdbGetResultForProcedure(rows, args, outParameters) {
|
|
@@ -82,6 +82,7 @@ const _addAliasToQuery = (query, entity, columnsToBeSearched) => {
|
|
|
82
82
|
SELECT.columns = addAliasToExpression(SELECT.columns, getEntityName)
|
|
83
83
|
columnsToBeSearched = addAliasToExpression(columnsToBeSearched, getEntityName)
|
|
84
84
|
SELECT.groupBy = addAliasToExpression(SELECT.groupBy, getEntityName)
|
|
85
|
+
SELECT.orderBy = addAliasToExpression(SELECT.orderBy, getEntityName)
|
|
85
86
|
SELECT.where = addAliasToExpression(SELECT.where, getEntityName)
|
|
86
87
|
return columnsToBeSearched
|
|
87
88
|
}
|
|
@@ -38,7 +38,7 @@ class AMQPWebhookMessaging extends MessagingService {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
startListening(opt = {}) {
|
|
41
|
-
if (!this.subscribedTopics.size) return
|
|
41
|
+
if (!this._listenToAll && !this.subscribedTopics.size) return
|
|
42
42
|
if (!opt.doNotDeploy) {
|
|
43
43
|
const management = this.getManagement()
|
|
44
44
|
this.queued(management.createQueueAndSubscriptions.bind(management))()
|
|
@@ -87,12 +87,15 @@ class EMManagement {
|
|
|
87
87
|
this.subdomain ? { queue: queueName, subdomain: this.subdomain } : { queue: queueName }
|
|
88
88
|
)
|
|
89
89
|
try {
|
|
90
|
+
const queueConfig = this.queueConfig && { ...this.queueConfig }
|
|
91
|
+
if (queueConfig?.deadMsgQueue)
|
|
92
|
+
queueConfig.deadMsgQueue = queueConfig.deadMsgQueue.replace(/\$namespace/g, this.namespace)
|
|
90
93
|
const res = await authorizedRequest({
|
|
91
94
|
method: 'PUT',
|
|
92
95
|
uri: this.options.uri,
|
|
93
96
|
path: `/hub/rest/api/v1/management/messaging/queues/${encodeURIComponent(queueName)}`,
|
|
94
97
|
oa2: this.options.oa2,
|
|
95
|
-
dataObj:
|
|
98
|
+
dataObj: queueConfig,
|
|
96
99
|
tokenStore: this
|
|
97
100
|
})
|
|
98
101
|
if (res.statusCode === 201) return true
|
|
@@ -389,7 +392,7 @@ class EMManagement {
|
|
|
389
392
|
this.LOG._info && this.LOG.info('Unchanged subscriptions', unchangedSubs, ' ', this.subdomainInfo)
|
|
390
393
|
await Promise.all([
|
|
391
394
|
...obsoleteSubs.map(s => this.deleteSubscription(s)),
|
|
392
|
-
...additionalSubs.map(async
|
|
395
|
+
...additionalSubs.map(async t => this.createSubscription(t))
|
|
393
396
|
])
|
|
394
397
|
return
|
|
395
398
|
}
|
|
@@ -139,7 +139,7 @@ class EnterpriseMessaging extends AMQPWebhookMessaging {
|
|
|
139
139
|
const doNotDeploy = _multitenancyEnabled() && !this.options.deployForProvider
|
|
140
140
|
if (doNotDeploy) this.LOG._info && this.LOG.info('Skipping deployment of messaging artifacts for provider account')
|
|
141
141
|
super.startListening({ doNotDeploy })
|
|
142
|
-
if (!doNotDeploy && this.subscribedTopics.size) {
|
|
142
|
+
if (!doNotDeploy && (this._listenToAll || this.subscribedTopics.size)) {
|
|
143
143
|
const management = this.getManagement()
|
|
144
144
|
// Webhooks will perform an OPTIONS call on creation to check the availability of the app.
|
|
145
145
|
// On systems like Cloud Foundry the app URL will only be advertised once
|
|
@@ -218,6 +218,11 @@ class EnterpriseMessaging extends AMQPWebhookMessaging {
|
|
|
218
218
|
const topic = msg.event
|
|
219
219
|
const message = { ...(msg.headers || {}), data: msg.data }
|
|
220
220
|
|
|
221
|
+
const contentType =
|
|
222
|
+
msg.headers && ['id', 'source', 'specversion', 'type'].every(el => el in msg.headers)
|
|
223
|
+
? 'application/cloudevents+json'
|
|
224
|
+
: 'application/json'
|
|
225
|
+
|
|
221
226
|
await this.queued(() => {})()
|
|
222
227
|
|
|
223
228
|
try {
|
|
@@ -229,6 +234,7 @@ class EnterpriseMessaging extends AMQPWebhookMessaging {
|
|
|
229
234
|
tenant,
|
|
230
235
|
dataObj: message,
|
|
231
236
|
headers: {
|
|
237
|
+
'Content-Type': contentType,
|
|
232
238
|
'x-qos': 1
|
|
233
239
|
},
|
|
234
240
|
tokenStore: {}
|
|
@@ -35,7 +35,7 @@ class FileBasedMessaging extends MessagingService {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
startWatching() {
|
|
38
|
-
if (!this.subscribedTopics.size) return
|
|
38
|
+
if (!this._listenToAll && !this.subscribedTopics.size) return
|
|
39
39
|
const watcher = async () => {
|
|
40
40
|
if (!(await touched(this.file, this.recent))) return // > not touched since last check
|
|
41
41
|
// REVISIT: Bad if lock file wasn't cleaned up (due to crashes...)
|
|
@@ -61,12 +61,15 @@ class MQManagement {
|
|
|
61
61
|
async createQueue(queueName = this.queueName) {
|
|
62
62
|
this.LOG._info && this.LOG.info('Create queue', { queue: queueName })
|
|
63
63
|
try {
|
|
64
|
+
const queueConfig = this.queueConfig && { ...this.queueConfig }
|
|
65
|
+
if (queueConfig?.deadMessageQueue)
|
|
66
|
+
queueConfig.deadMessageQueue = queueConfig.deadMessageQueue.replace(/\$namespace/g, '')
|
|
64
67
|
const res = await authorizedRequest({
|
|
65
68
|
method: 'PUT',
|
|
66
69
|
uri: this.options.url,
|
|
67
70
|
path: `/v1/management/queues/${encodeURIComponent(queueName)}`,
|
|
68
71
|
oa2: this.options.auth.oauth2,
|
|
69
|
-
dataObj:
|
|
72
|
+
dataObj: queueConfig,
|
|
70
73
|
tokenStore: this
|
|
71
74
|
})
|
|
72
75
|
if (res.statusCode === 201) return true
|
|
@@ -182,7 +185,7 @@ class MQManagement {
|
|
|
182
185
|
.filter(s => !existingSubscriptions.some(e => s === e))
|
|
183
186
|
await Promise.all([
|
|
184
187
|
...obsoleteSubs.map(s => this.deleteSubscription(s)),
|
|
185
|
-
...additionalSubs.map(
|
|
188
|
+
...additionalSubs.map(t => this.createSubscription(t))
|
|
186
189
|
])
|
|
187
190
|
return
|
|
188
191
|
}
|
|
@@ -50,7 +50,7 @@ const _processSingleMessage = async (service, message, succeededMessages) => {
|
|
|
50
50
|
// Promise resolve is necessary because we want to set `cds.context` only
|
|
51
51
|
// inside this call
|
|
52
52
|
return Promise.resolve().then(async () => {
|
|
53
|
-
if (userId) cds.context
|
|
53
|
+
if (userId) cds.context = { user: new cds.User.Privileged(userId) }
|
|
54
54
|
try {
|
|
55
55
|
service._emitImmediate && (await service._emitImmediate(msg))
|
|
56
56
|
succeededMessages.push(message.ID)
|
|
@@ -81,11 +81,14 @@ class MessagingService extends OutboxService {
|
|
|
81
81
|
const _msg = typeof event === 'object' ? event : { event, data, headers }
|
|
82
82
|
if (_msg instanceof cds.Event) return super.emit(_msg)
|
|
83
83
|
if (_msg.inbound && !cds.context) {
|
|
84
|
-
cds.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
84
|
+
return cds._context.run({ tenant: _msg.tenant, user: cds.User.privileged }, async () => {
|
|
85
|
+
if (cds.model) {
|
|
86
|
+
const ctx = cds.context
|
|
87
|
+
ctx.model = await ExtendedModels.model4(ctx.tenant, ctx.features)
|
|
88
|
+
}
|
|
89
|
+
const msg = new cds.Event(this.message4(_msg))
|
|
90
|
+
return super.emit(msg)
|
|
91
|
+
})
|
|
89
92
|
}
|
|
90
93
|
const msg = new cds.Event(this.message4(_msg))
|
|
91
94
|
return super.emit(msg)
|
|
@@ -94,7 +97,8 @@ class MessagingService extends OutboxService {
|
|
|
94
97
|
on(event, cb) {
|
|
95
98
|
const _event = _warnAndStripTopicPrefix(event, this.LOG)
|
|
96
99
|
// save all subscribed topics (not needed for local-messaging)
|
|
97
|
-
this.subscribedTopics.set(this.prepareTopic(_event, true), _event)
|
|
100
|
+
if (event !== '*') this.subscribedTopics.set(this.prepareTopic(_event, true), _event)
|
|
101
|
+
else this._listenToAll = true
|
|
98
102
|
return super.on(_event, cb)
|
|
99
103
|
}
|
|
100
104
|
|
|
@@ -131,8 +135,9 @@ class MessagingService extends OutboxService {
|
|
|
131
135
|
const subscribedEvent =
|
|
132
136
|
this.subscribedTopics.get(_msg.event) ||
|
|
133
137
|
(this.wildcarded && this.subscribedTopics.get(this.wildcarded(_msg.event)))
|
|
134
|
-
if (!subscribedEvent
|
|
135
|
-
|
|
138
|
+
if (!subscribedEvent && !this._listenToAll)
|
|
139
|
+
throw new Error(`No handler for incoming message with topic '${_msg.event}' found.`)
|
|
140
|
+
_msg.event = subscribedEvent || _msg.event
|
|
136
141
|
}
|
|
137
142
|
return _msg
|
|
138
143
|
}
|
package/libx/odata/cqn2odata.js
CHANGED
|
@@ -516,12 +516,15 @@ const _select = (cqn, kind, model) => {
|
|
|
516
516
|
const _insert = (cqn, kind, model) => {
|
|
517
517
|
const INSERT = getProp(cqn, 'INSERT')
|
|
518
518
|
const { url } = _from(getProp(INSERT, 'into'), kind, model)
|
|
519
|
-
const body =
|
|
519
|
+
const body = _copyData(
|
|
520
|
+
Array.isArray(INSERT.entries) && INSERT.entries.length === 1 ? INSERT.entries[0] : INSERT.entries
|
|
521
|
+
)
|
|
520
522
|
return { method: 'POST', path: url, body }
|
|
521
523
|
}
|
|
522
524
|
|
|
523
525
|
const _copyData = data => {
|
|
524
526
|
// only works on flat structures
|
|
527
|
+
if (Array.isArray(data)) return data.map(_copyData)
|
|
525
528
|
const copied = {}
|
|
526
529
|
for (const property in data) {
|
|
527
530
|
copied[property] =
|
package/libx/odata/utils.js
CHANGED
|
@@ -52,10 +52,9 @@ const formatVal = (val, elementName, csnTarget, kind) => {
|
|
|
52
52
|
if (typeof val === 'boolean') return val
|
|
53
53
|
if (typeof val === 'number') return getSafeNumber(val)
|
|
54
54
|
if (!csnTarget && typeof val === 'string' && UUID.test(val)) return kind === 'odata-v2' ? `guid'${val}'` : val
|
|
55
|
-
const
|
|
56
|
-
|
|
55
|
+
const element = _getElement(csnTarget, elementName)
|
|
57
56
|
if (kind === 'odata-v2') {
|
|
58
|
-
switch (type) {
|
|
57
|
+
switch (element.type) {
|
|
59
58
|
case 'cds.Decimal':
|
|
60
59
|
case 'cds.Integer64':
|
|
61
60
|
case 'cds.Int64':
|
|
@@ -64,20 +63,22 @@ const formatVal = (val, elementName, csnTarget, kind) => {
|
|
|
64
63
|
case 'cds.LargeBinary':
|
|
65
64
|
return `binary'${toBase64url(val)}'`
|
|
66
65
|
case 'cds.Date':
|
|
67
|
-
return
|
|
66
|
+
return element['@odata.Type'] === 'Edm.DateTimeOffset'
|
|
67
|
+
? `datetimeoffset'${val}T00:00:00'`
|
|
68
|
+
: `datetime'${val}T00:00:00'`
|
|
68
69
|
case 'cds.DateTime':
|
|
69
|
-
return `datetime'${val}'`
|
|
70
|
+
return element['@odata.Type'] === 'Edm.DateTimeOffset' ? `datetimeoffset'${val}'` : `datetime'${val}'`
|
|
70
71
|
case 'cds.Time':
|
|
71
72
|
return `time'${_PT(val.split(':'))}'`
|
|
72
73
|
case 'cds.Timestamp':
|
|
73
|
-
return `datetimeoffset'${val}'`
|
|
74
|
+
return element['@odata.Type'] === 'Edm.DateTime' ? `datetime'${val}'` : `datetimeoffset'${val}'`
|
|
74
75
|
case 'cds.UUID':
|
|
75
76
|
return `guid'${val}'`
|
|
76
77
|
default:
|
|
77
78
|
return `'${val}'`
|
|
78
79
|
}
|
|
79
80
|
} else {
|
|
80
|
-
switch (type) {
|
|
81
|
+
switch (element.type) {
|
|
81
82
|
case 'cds.Binary':
|
|
82
83
|
case 'cds.LargeBinary':
|
|
83
84
|
return `binary'${toBase64url(val)}'`
|
package/libx/rest/RestAdapter.js
CHANGED
|
@@ -197,10 +197,7 @@ const RestAdapter = function (srv) {
|
|
|
197
197
|
|
|
198
198
|
next(err)
|
|
199
199
|
})
|
|
200
|
-
|
|
201
|
-
if (!cds.env.features.rest_error_handler) {
|
|
202
|
-
router.use(error) // FIXME: nope -> call next()
|
|
203
|
-
}
|
|
200
|
+
router.use(error) // FIXME: nope -> call next()
|
|
204
201
|
|
|
205
202
|
return router
|
|
206
203
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sap/cds",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.0",
|
|
4
4
|
"description": "SAP Cloud Application Programming Model - CDS for Node.js",
|
|
5
5
|
"homepage": "https://cap.cloud.sap/",
|
|
6
6
|
"keywords": [
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"LICENSE"
|
|
28
28
|
],
|
|
29
29
|
"engines": {
|
|
30
|
-
"node": ">=14.
|
|
30
|
+
"node": ">=14.18.0"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@sap/cds-compiler": "^3.2.0",
|