@sap/cds 6.3.1 → 6.4.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 +87 -0
- package/apis/cds.d.ts +1 -1
- package/apis/core.d.ts +118 -90
- package/apis/cqn.d.ts +11 -2
- package/apis/internal/inference.d.ts +7 -2
- package/apis/ql.d.ts +45 -11
- package/apis/serve.d.ts +8 -1
- package/apis/services.d.ts +303 -305
- package/bin/build/buildTaskEngine.js +28 -36
- package/bin/build/buildTaskFactory.js +32 -81
- package/bin/build/buildTaskHandler.js +3 -2
- package/bin/build/buildTaskProvider.js +2 -2
- package/bin/build/buildTaskProviderFactory.js +5 -14
- package/bin/build/constants.js +0 -1
- package/bin/build/provider/buildTaskHandlerEdmx.js +7 -6
- package/bin/build/provider/buildTaskHandlerFeatureToggles.js +6 -5
- package/bin/build/provider/buildTaskHandlerInternal.js +9 -30
- package/bin/build/provider/buildTaskProviderInternal.js +70 -58
- package/bin/build/provider/fiori/index.js +6 -5
- package/bin/build/provider/hana/2migration.js +20 -3
- package/bin/build/provider/hana/2tabledata.js +1 -0
- package/bin/build/provider/hana/index.js +40 -17
- package/bin/build/provider/java/index.js +10 -10
- package/bin/build/provider/mtx/index.js +25 -16
- package/bin/build/provider/mtx/resourcesTarBuilder.js +22 -27
- package/bin/build/provider/mtx-extension/index.js +3 -2
- package/bin/build/provider/mtx-sidecar/index.js +16 -15
- package/bin/build/provider/nodejs/index.js +14 -56
- package/bin/build/util.js +56 -16
- package/bin/deploy/to-hana/cfUtil.js +4 -1
- package/bin/deploy/to-hana/gitUtil.js +1 -1
- package/bin/deploy/to-hana/hana.js +45 -38
- package/bin/deploy/to-hana/hdiDeployUtil.js +8 -9
- package/bin/deploy/to-hana/mtaUtil.js +13 -14
- package/bin/mtx/in-cds.js +3 -1
- package/bin/serve.js +1 -1
- package/bin/version.js +2 -1
- package/lib/compile/cds-compile.js +1 -0
- package/lib/compile/cdsc.js +1 -0
- package/lib/compile/etc/_localized.js +2 -2
- package/lib/compile/for/lean_drafts.js +83 -0
- package/lib/compile/for/nodejs.js +1 -0
- package/lib/compile/minify.js +2 -1
- package/lib/compile/parse.js +2 -1
- package/lib/compile/to/gql.js +1 -1
- package/lib/compile/to/sql.js +11 -1
- package/lib/core/entities.js +1 -1
- package/lib/core/index.js +8 -9
- package/lib/core/infer.js +1 -0
- package/lib/dbs/cds-deploy.js +97 -41
- package/lib/env/cds-env.js +9 -10
- package/lib/env/cds-requires.js +8 -2
- package/lib/env/defaults.js +0 -4
- package/lib/env/schemas/cds-rc.json +38 -0
- package/lib/ql/SELECT.js +10 -4
- package/lib/srv/bindings.js +1 -1
- package/lib/srv/factory.js +1 -1
- package/lib/srv/protocols/index.js +3 -1
- package/lib/srv/srv-methods.js +1 -1
- package/lib/utils/cds-utils.js +11 -0
- package/lib/utils/inflect.js +13 -12
- package/lib/utils/tar.js +53 -10
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +1 -15
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/errors/UriSyntaxError.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +6 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/BufferedWriter.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +0 -12
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/oDataConfiguration.js +1 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +4 -0
- package/libx/_runtime/cds-services/services/Service.js +23 -1
- package/libx/_runtime/cds-services/util/assert.js +0 -41
- package/libx/_runtime/common/composition/data.js +5 -1
- package/libx/_runtime/common/generic/auth/utils.js +3 -3
- package/libx/_runtime/common/generic/input.js +4 -24
- package/libx/_runtime/common/generic/paging.js +3 -3
- package/libx/_runtime/common/utils/csn.js +21 -15
- package/libx/_runtime/common/utils/draft.js +2 -1
- package/libx/_runtime/common/utils/resolveView.js +25 -4
- package/libx/_runtime/common/utils/rewriteAsterisks.js +3 -1
- package/libx/_runtime/common/utils/rowUUIDGenerator.js +21 -0
- package/libx/_runtime/common/utils/templateProcessor.js +12 -15
- package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +23 -0
- package/libx/_runtime/db/expand/expandCQNToJoin.js +29 -12
- package/libx/_runtime/db/generic/input.js +7 -13
- package/libx/_runtime/db/sql-builder/UpsertBuilder.js +47 -0
- package/libx/_runtime/db/sql-builder/index.js +2 -0
- package/libx/_runtime/db/sql-builder/sqlFactory.js +9 -0
- package/libx/_runtime/db/utils/columns.js +4 -2
- package/libx/_runtime/fiori/generic/read.js +1 -12
- package/libx/_runtime/fiori/lean-draft.js +657 -0
- package/libx/_runtime/fiori/utils/handler.js +1 -1
- package/libx/_runtime/hana/pool.js +16 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +2 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging.js +2 -3
- package/libx/_runtime/messaging/outbox/utils.js +109 -70
- package/libx/_runtime/messaging/service.js +16 -7
- package/libx/_runtime/remote/Service.js +15 -2
- package/libx/_runtime/remote/utils/client.js +41 -11
- package/libx/_runtime/sqlite/Service.js +3 -0
- package/libx/_runtime/sqlite/convertDraftAdminPathExpression.js +56 -0
- package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +59 -0
- package/libx/_runtime/sqlite/customBuilder/index.js +5 -0
- package/libx/_runtime/sqlite/execute.js +1 -1
- package/libx/_runtime/types/api.js +2 -2
- package/libx/rest/RestAdapter.js +15 -13
- package/package.json +1 -1
- package/server.js +1 -0
|
@@ -179,7 +179,7 @@ const update = service => {
|
|
|
179
179
|
await tx.rollback(e).catch(() => {})
|
|
180
180
|
}
|
|
181
181
|
} finally {
|
|
182
|
-
req.messages && odataRes.setHeader('sap-messages', getSapMessages(req.messages, req.
|
|
182
|
+
req.messages && odataRes.setHeader('sap-messages', getSapMessages(req.messages, req.http.req))
|
|
183
183
|
|
|
184
184
|
if (err) next(err)
|
|
185
185
|
else if (primitive && result) {
|
package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/errors/UriSyntaxError.js
CHANGED
|
@@ -37,7 +37,7 @@ UriSyntaxError.Message = {
|
|
|
37
37
|
KEY_VALUE_NOT_FOUND: "No '%s' value found for key '%s'",
|
|
38
38
|
PREVIOUS_TYPE_HAS_NO_MEDIA: "Previous segment type '%s' does not have a media resource",
|
|
39
39
|
MUST_BE_COUNT_OR_BOUND_OPERATION: "Expected current segment '%s' to be '$count' or a bound operation",
|
|
40
|
-
MUST_BE_COUNT_OR_REF_OR_BOUND_OPERATION: "Expected current segment '%s' to be '$count', '$ref', or a
|
|
40
|
+
MUST_BE_COUNT_OR_REF_OR_BOUND_OPERATION: "Expected current segment '%s' to be '$count', '$ref', a bound operation or a key value with a proper type",
|
|
41
41
|
|
|
42
42
|
ALIAS_NOT_FOUND: "Parameter alias '%s' not found",
|
|
43
43
|
WRONG_ALIAS_VALUE: "Wrong value for parameter alias '%s'",
|
|
@@ -143,7 +143,12 @@ class UriParser {
|
|
|
143
143
|
* @returns {UriInfo} the result of parsing
|
|
144
144
|
*/
|
|
145
145
|
parseRelativeUri (uri, queryOptions) {
|
|
146
|
-
let uriPathSegments
|
|
146
|
+
let uriPathSegments
|
|
147
|
+
try {
|
|
148
|
+
uriPathSegments = uri.split('/').map(decodeURIComponent)
|
|
149
|
+
} catch (error) {
|
|
150
|
+
throw new UriSyntaxError('wrong percent encoding in uri: ' + uri)
|
|
151
|
+
}
|
|
147
152
|
|
|
148
153
|
let uriInfo = new UriInfo()
|
|
149
154
|
|
|
@@ -24,18 +24,6 @@ class ConditionalRequestValidator {
|
|
|
24
24
|
if (method !== HttpMethods.GET && !ifMatch && !ifNoneMatch) throw new PreconditionRequiredError()
|
|
25
25
|
return
|
|
26
26
|
}
|
|
27
|
-
|
|
28
|
-
if (ifMatch || ifNoneMatch) {
|
|
29
|
-
// Careless clients send this also for DELETE and POST, other careless clients send the star in double-quotes.
|
|
30
|
-
if ([HttpMethods.POST].includes(method)) {
|
|
31
|
-
if (
|
|
32
|
-
(ifMatch && ifMatch.trim() !== '*' && ifMatch.trim() !== '"*"') ||
|
|
33
|
-
(ifNoneMatch && ifNoneMatch.trim() !== '*' && ifNoneMatch.trim() !== '"*"')
|
|
34
|
-
) {
|
|
35
|
-
throw new PreconditionFailedError()
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
27
|
}
|
|
40
28
|
|
|
41
29
|
/**
|
|
@@ -18,12 +18,6 @@ const _getEntitySets = (edm, namespace) => {
|
|
|
18
18
|
return entities
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
const _getConcurrent = (namespace, element, csn) => {
|
|
22
|
-
// autoexposed entities now used . in csn and _ in edm
|
|
23
|
-
const e = findCsnTargetFor(element, csn, namespace)
|
|
24
|
-
return !!e._etag
|
|
25
|
-
}
|
|
26
|
-
|
|
27
21
|
const oDataConfiguration = (edm, csn) => {
|
|
28
22
|
let namespace
|
|
29
23
|
for (const prop in edm) {
|
|
@@ -44,7 +38,7 @@ const oDataConfiguration = (edm, csn) => {
|
|
|
44
38
|
|
|
45
39
|
configuration[entitySet] = {
|
|
46
40
|
maxPageSize: getMaxPageSize(e),
|
|
47
|
-
isConcurrent:
|
|
41
|
+
isConcurrent: !!e._etag
|
|
48
42
|
}
|
|
49
43
|
|
|
50
44
|
// custom aggregates
|
|
@@ -208,6 +208,9 @@ const _processCategory = (req, category, elementInfo, options, previousResult) =
|
|
|
208
208
|
localizeAfterDraftActivate(row, key, req.locale)
|
|
209
209
|
break
|
|
210
210
|
|
|
211
|
+
case '@cds.Boolean':
|
|
212
|
+
if (row[key] != null) row[key] = !!row[key]
|
|
213
|
+
|
|
211
214
|
// no default
|
|
212
215
|
}
|
|
213
216
|
}
|
|
@@ -270,6 +273,7 @@ const _pick = options => (element, target) => {
|
|
|
270
273
|
|
|
271
274
|
if (element['@odata.etag']) categories.push('@odata.etag')
|
|
272
275
|
if (element._type === 'cds.Decimal') categories.push('@cds.Decimal')
|
|
276
|
+
if (cds.db?.kind === 'better-sqlite' && element._type === 'cds.Boolean') categories.push('@cds.Boolean')
|
|
273
277
|
|
|
274
278
|
categories.push(..._assocs(element, target))
|
|
275
279
|
|
|
@@ -50,7 +50,29 @@ class ApplicationService extends cds.Service {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
registerFioriHandlers() {
|
|
53
|
-
|
|
53
|
+
if (cds.env.features.lean_draft) {
|
|
54
|
+
const {
|
|
55
|
+
onNewDraft,
|
|
56
|
+
onDraftPrepare,
|
|
57
|
+
onDraftActivate,
|
|
58
|
+
onPatch,
|
|
59
|
+
onDraftEdit,
|
|
60
|
+
onDelete
|
|
61
|
+
} = require('../../fiori/lean-draft')
|
|
62
|
+
const LOG = cds.log('fiori|drafts')
|
|
63
|
+
|
|
64
|
+
for (let each of this.entities)
|
|
65
|
+
if (each.drafts) {
|
|
66
|
+
LOG.debug('serving drafts for', { entity: each.name })
|
|
67
|
+
this.on('NEW', each, onNewDraft)
|
|
68
|
+
this.on('PATCH', each, onPatch)
|
|
69
|
+
this.on('EDIT', each, onDraftEdit)
|
|
70
|
+
this.on('draftPrepare', each, onDraftPrepare)
|
|
71
|
+
this.on('draftActivate', each, onDraftActivate)
|
|
72
|
+
this.on('draftActivate', each, onDraftActivate)
|
|
73
|
+
this.on(['CANCEL', 'DELETE'], each, onDelete)
|
|
74
|
+
}
|
|
75
|
+
} else return require('../../fiori/generic').impl.call(this)
|
|
54
76
|
}
|
|
55
77
|
|
|
56
78
|
registerCrudHandlers() {
|
|
@@ -259,46 +259,6 @@ const _checkFormatElement = (element, value, errors, key, pathSegments) => {
|
|
|
259
259
|
}
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
-
// check for forbidden deep operations for association
|
|
263
|
-
const checkIfAssocDeep = (element, value, req) => {
|
|
264
|
-
if (!value) return
|
|
265
|
-
|
|
266
|
-
if (element.on) {
|
|
267
|
-
req.error(
|
|
268
|
-
assertError(
|
|
269
|
-
element.is2one
|
|
270
|
-
? { code: ASSERT_DEEP_ASSOCIATION, args: ['unmanaged to-one', element.name] }
|
|
271
|
-
: { code: ASSERT_DEEP_ASSOCIATION, args: ['to-many', element.name] },
|
|
272
|
-
element,
|
|
273
|
-
value
|
|
274
|
-
)
|
|
275
|
-
)
|
|
276
|
-
|
|
277
|
-
return
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
if (element.is2one) {
|
|
281
|
-
// managed to one
|
|
282
|
-
Object.keys(value).forEach(prop => {
|
|
283
|
-
if (typeof value[prop] !== 'object') {
|
|
284
|
-
const foreignKey = element._foreignKeys.find(fk => fk.childElement.name === prop)
|
|
285
|
-
if (foreignKey) return
|
|
286
|
-
|
|
287
|
-
const key = element.keys.find(element => element.ref[0] === prop)
|
|
288
|
-
if (key) return
|
|
289
|
-
|
|
290
|
-
const err = assertError(
|
|
291
|
-
{ code: ASSERT_DEEP_ASSOCIATION, args: ['managed to-one', element.name] },
|
|
292
|
-
element,
|
|
293
|
-
value
|
|
294
|
-
)
|
|
295
|
-
err.target += `.${prop}`
|
|
296
|
-
req.error(err)
|
|
297
|
-
}
|
|
298
|
-
})
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
262
|
/**
|
|
303
263
|
* @param {import('../../types/api').InputConstraints} constraints
|
|
304
264
|
*/
|
|
@@ -407,7 +367,6 @@ module.exports = {
|
|
|
407
367
|
checkInputConstraints,
|
|
408
368
|
checkKeys,
|
|
409
369
|
assertError,
|
|
410
|
-
checkIfAssocDeep,
|
|
411
370
|
checkStaticElementByKey,
|
|
412
371
|
assertNotNullError,
|
|
413
372
|
assertTargets
|
|
@@ -283,7 +283,11 @@ const _selectDeepUpdateData = async args => {
|
|
|
283
283
|
|
|
284
284
|
// if a view has an orderBy with renamed field, we need to resolve it
|
|
285
285
|
const _resolveOrderBy = (orderBy, transitions) => {
|
|
286
|
-
|
|
286
|
+
// no resolved entity found
|
|
287
|
+
if (!transitions?.length) return
|
|
288
|
+
// if there are no renamed fields, no need to resolve
|
|
289
|
+
if (!transitions[0].mapping.size) return
|
|
290
|
+
if (orderBy) orderBy.map(el => (el.ref[0] = transitions[0].mapping.get(el.ref[0]).ref[0]))
|
|
287
291
|
}
|
|
288
292
|
|
|
289
293
|
/*
|
|
@@ -9,9 +9,9 @@ const reject = (req, reason = null) => {
|
|
|
9
9
|
// unauthorized or forbidden?
|
|
10
10
|
if (req.user._is_anonymous) {
|
|
11
11
|
// REVISIT: challenges handling should be done in protocol adapter (i.e., express error middleware)
|
|
12
|
-
// REVISIT: improve `req.
|
|
13
|
-
if (req.
|
|
14
|
-
req.
|
|
12
|
+
// REVISIT: improve `req.http.req` check if this is an HTTP request
|
|
13
|
+
if (req.http?.req && req.user._challenges && req.user._challenges.length > 0) {
|
|
14
|
+
req.http.res.set('WWW-Authenticate', req.user._challenges.join(';'))
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
// REVISIT: security log in else case?
|
|
@@ -16,6 +16,7 @@ const { checkInputConstraints, assertTargets } = require('../../cds-services/uti
|
|
|
16
16
|
const getTemplate = require('../utils/template')
|
|
17
17
|
const templateProcessor = require('../utils/templateProcessor')
|
|
18
18
|
const { getDataFromCQN, setDataFromCQN } = require('../utils/data')
|
|
19
|
+
const getRowUUIDGeneratorFn = require('../utils/rowUUIDGenerator')
|
|
19
20
|
|
|
20
21
|
const _shouldSuppressErrorPropagation = (event, value) => {
|
|
21
22
|
return (
|
|
@@ -34,24 +35,6 @@ const _getSimpleCategory = category => {
|
|
|
34
35
|
return category
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
const _rowKeysGenerator = eventName => {
|
|
38
|
-
if (eventName === 'UPDATE') return
|
|
39
|
-
return (keyNames, row, template) => {
|
|
40
|
-
for (const keyName of keyNames) {
|
|
41
|
-
if (Object.prototype.hasOwnProperty.call(row, keyName)) {
|
|
42
|
-
continue
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const elementInfo = template.elements.get(keyName)
|
|
46
|
-
const plain = elementInfo && elementInfo.picked && elementInfo.picked.plain
|
|
47
|
-
if (!plain || !plain.categories) continue
|
|
48
|
-
if (plain.categories.includes('uuid')) {
|
|
49
|
-
row[keyName] = cds.utils.uuid()
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
38
|
const _isDraftCoreComputed = (req, element, event) =>
|
|
56
39
|
cds.env.features.preserve_computed !== false &&
|
|
57
40
|
req._ &&
|
|
@@ -65,10 +48,7 @@ const _isStreamingProperty = (elements, row, property) =>
|
|
|
65
48
|
)
|
|
66
49
|
|
|
67
50
|
const _getMediaTypeValue = req =>
|
|
68
|
-
req.
|
|
69
|
-
req._.req.headers['content-type'] &&
|
|
70
|
-
!req._.req.headers['content-type'].match(/json|multipart/i) &&
|
|
71
|
-
req._.req.headers['content-type']
|
|
51
|
+
!req.http?.req?.headers?.['content-type'].match(/json|multipart/i) && req.http?.req?.headers?.['content-type']
|
|
72
52
|
|
|
73
53
|
const _preProcessAssertTarget = (assocInfo, assertMap) => {
|
|
74
54
|
const { element: assoc, row } = assocInfo
|
|
@@ -265,7 +245,7 @@ async function commonGenericInput(req) {
|
|
|
265
245
|
}
|
|
266
246
|
|
|
267
247
|
const pathOptions = {
|
|
268
|
-
|
|
248
|
+
rowUUIDGenerator: getRowUUIDGeneratorFn(req.event),
|
|
269
249
|
includeKeyValues: true,
|
|
270
250
|
pathSegments: []
|
|
271
251
|
}
|
|
@@ -276,7 +256,7 @@ async function commonGenericInput(req) {
|
|
|
276
256
|
if (pathSegment) pathOptions.pathSegments.push(pathSegment)
|
|
277
257
|
|
|
278
258
|
if (keys && 'IsActiveEntity' in keys) {
|
|
279
|
-
pathOptions.
|
|
259
|
+
pathOptions.draftKeys = { IsActiveEntity: keys.IsActiveEntity }
|
|
280
260
|
}
|
|
281
261
|
}
|
|
282
262
|
|
|
@@ -3,10 +3,10 @@ const { getDefaultPageSize, getMaxPageSize } = require('../utils/page')
|
|
|
3
3
|
|
|
4
4
|
const commonGenericPaging = function (req) {
|
|
5
5
|
// only if http request
|
|
6
|
-
if (!
|
|
6
|
+
if (!req.http?.req) return
|
|
7
7
|
|
|
8
8
|
// target === null if view with parameters
|
|
9
|
-
if (!req.target || !req.query
|
|
9
|
+
if (!req.target || !req.query?.SELECT || req.query.SELECT.one) return
|
|
10
10
|
|
|
11
11
|
_addPaging(req.query, req.target)
|
|
12
12
|
}
|
|
@@ -17,7 +17,7 @@ const _addPaging = function (query, target) {
|
|
|
17
17
|
offset = offset && 'val' in offset ? offset.val : 0
|
|
18
18
|
query.limit(...[Math.min(rows, getMaxPageSize(target)), offset])
|
|
19
19
|
//Handle nested limits
|
|
20
|
-
if (query.SELECT.from.SELECT) _addPaging(query.SELECT.from, target)
|
|
20
|
+
if (query.SELECT.from.SELECT?.limit) _addPaging(query.SELECT.from, target)
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
/**
|
|
@@ -129,21 +129,22 @@ const _findCsnTarget = (edmName, model, namespace) => {
|
|
|
129
129
|
return target
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
const _initializeCache = (model, namespace) => {
|
|
133
|
+
const cache = {}
|
|
134
|
+
for (const name in model.definitions) {
|
|
135
|
+
// do no cache entities within different namespace
|
|
136
|
+
if (!name.startsWith(`${namespace}.`)) continue
|
|
137
|
+
// cut off namespace and underscoreify entity name (OData does not allow dots)
|
|
138
|
+
cache[name.replace(new RegExp(`^${namespace}\\.`), '').replace(/\./g, '_')] = model.definitions[name]
|
|
139
|
+
}
|
|
140
|
+
return cache
|
|
141
|
+
}
|
|
142
|
+
|
|
132
143
|
const findCsnTargetFor = (edmName, model, namespace) => {
|
|
133
|
-
const cache =
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
Object.defineProperty(cache, namespace, {
|
|
138
|
-
get() {
|
|
139
|
-
const _ = {}
|
|
140
|
-
for (const name in model.definitions) {
|
|
141
|
-
if (!name.startsWith(`${namespace}.`)) continue
|
|
142
|
-
_[name.replace(new RegExp(`^${namespace}\\.`), '').replace(/\./g, '_')] = model.definitions[name]
|
|
143
|
-
}
|
|
144
|
-
return _
|
|
145
|
-
}
|
|
146
|
-
})[namespace]
|
|
144
|
+
const cache = model._edmToCSNNameMap || (model._edmToCSNNameMap = {})
|
|
145
|
+
const edm2csnMap = cache[namespace] || (cache[namespace] = _initializeCache(model, namespace))
|
|
146
|
+
|
|
147
|
+
if (edm2csnMap[edmName]) return edm2csnMap[edmName]
|
|
147
148
|
|
|
148
149
|
const target = _findCsnTarget(edmName, model, namespace)
|
|
149
150
|
|
|
@@ -226,7 +227,12 @@ function getDraftTreeRoot(entity, model) {
|
|
|
226
227
|
for (const k in model.definitions) {
|
|
227
228
|
const e = model.definitions[k]
|
|
228
229
|
if (e.kind !== 'entity' || !e.compositions) continue
|
|
229
|
-
for (const c in e.compositions)
|
|
230
|
+
for (const c in e.compositions)
|
|
231
|
+
if (
|
|
232
|
+
e.compositions[c].target === current.name ||
|
|
233
|
+
e.compositions[c].target === current.name.replace(/\.drafts/, '')
|
|
234
|
+
)
|
|
235
|
+
parents.push(e)
|
|
230
236
|
}
|
|
231
237
|
if (parents.length > 1 && parents.some(p => p !== parents[0])) {
|
|
232
238
|
// > unable to determine single parent
|
|
@@ -19,7 +19,8 @@ const ensureUnlocalized = table => {
|
|
|
19
19
|
return _table
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
const ensureDraftsSuffix = name =>
|
|
22
|
+
const ensureDraftsSuffix = name =>
|
|
23
|
+
name.endsWith('_drafts') || name.endsWith('.drafts') ? name : `${ensureUnlocalized(name)}_drafts`
|
|
23
24
|
|
|
24
25
|
const ensureNoDraftsSuffix = name => name.replace(/_drafts$/g, '')
|
|
25
26
|
|
|
@@ -319,7 +319,7 @@ const _rewriteQueryPath = (path, transitions) => {
|
|
|
319
319
|
const _newUpdate = (query, transitions, service) => {
|
|
320
320
|
const targetTransition = transitions[transitions.length - 1]
|
|
321
321
|
const targetName = targetTransition.target.name
|
|
322
|
-
const newUpdate =
|
|
322
|
+
const newUpdate = Object.create(query.UPDATE)
|
|
323
323
|
newUpdate.entity = newUpdate.entity.ref
|
|
324
324
|
? {
|
|
325
325
|
...newUpdate.entity,
|
|
@@ -345,7 +345,7 @@ const _newUpdate = (query, transitions, service) => {
|
|
|
345
345
|
|
|
346
346
|
const _newSelect = (query, transitions, service) => {
|
|
347
347
|
const targetTransition = transitions[transitions.length - 1]
|
|
348
|
-
const newSelect =
|
|
348
|
+
const newSelect = Object.create(query.SELECT)
|
|
349
349
|
newSelect.from = {
|
|
350
350
|
...newSelect.from,
|
|
351
351
|
ref: _rewriteQueryPath(query.SELECT.from, transitions)
|
|
@@ -379,7 +379,7 @@ const _newSelect = (query, transitions, service) => {
|
|
|
379
379
|
const _newInsert = (query, transitions, service) => {
|
|
380
380
|
const targetTransition = transitions[transitions.length - 1]
|
|
381
381
|
const targetName = targetTransition.target.name
|
|
382
|
-
const newInsert =
|
|
382
|
+
const newInsert = Object.create(query.INSERT)
|
|
383
383
|
newInsert.into = newInsert.into.ref
|
|
384
384
|
? {
|
|
385
385
|
...newInsert.into,
|
|
@@ -395,10 +395,29 @@ const _newInsert = (query, transitions, service) => {
|
|
|
395
395
|
return newInsert
|
|
396
396
|
}
|
|
397
397
|
|
|
398
|
+
const _newUpsert = (query, transitions, service) => {
|
|
399
|
+
const targetTransition = transitions[transitions.length - 1]
|
|
400
|
+
const targetName = targetTransition.target.name
|
|
401
|
+
const newUpsert = Object.create(query.UPSERT)
|
|
402
|
+
newUpsert.into = newUpsert.into.ref
|
|
403
|
+
? {
|
|
404
|
+
...newUpsert.into,
|
|
405
|
+
ref: _rewriteQueryPath(query.UPSERT.into, transitions)
|
|
406
|
+
}
|
|
407
|
+
: targetName
|
|
408
|
+
if (newUpsert.columns) newUpsert.columns = _newInsertColumns(newUpsert.columns, targetTransition)
|
|
409
|
+
if (newUpsert.entries) newUpsert.entries = _newEntries(newUpsert.entries, targetTransition, service)
|
|
410
|
+
Object.defineProperty(newUpsert, '_transitions', {
|
|
411
|
+
enumerable: false,
|
|
412
|
+
value: transitions
|
|
413
|
+
})
|
|
414
|
+
return newUpsert
|
|
415
|
+
}
|
|
416
|
+
|
|
398
417
|
const _newDelete = (query, transitions) => {
|
|
399
418
|
const targetTransition = transitions[transitions.length - 1]
|
|
400
419
|
const targetName = targetTransition.target.name
|
|
401
|
-
const newDelete =
|
|
420
|
+
const newDelete = Object.create(query.DELETE)
|
|
402
421
|
newDelete.from = newDelete.from.ref
|
|
403
422
|
? {
|
|
404
423
|
...newDelete.from,
|
|
@@ -600,6 +619,7 @@ const _newQuery = (query, event, model, service) => {
|
|
|
600
619
|
const [_prop, _func] = {
|
|
601
620
|
SELECT: ['from', _newSelect],
|
|
602
621
|
INSERT: ['into', _newInsert],
|
|
622
|
+
UPSERT: ['into', _newUpsert],
|
|
603
623
|
UPDATE: ['entity', _newUpdate],
|
|
604
624
|
DELETE: ['from', _newDelete]
|
|
605
625
|
}[event]
|
|
@@ -619,6 +639,7 @@ const resolveView = (query, model, service) => {
|
|
|
619
639
|
if (query.cmd) _event = query.cmd
|
|
620
640
|
else if (query.SELECT) _event = 'SELECT'
|
|
621
641
|
else if (query.INSERT) _event = 'INSERT'
|
|
642
|
+
else if (query.UPSERT) _event = 'UPSERT'
|
|
622
643
|
else if (query.UPDATE) _event = 'UPDATE'
|
|
623
644
|
else if (query.DELETE) _event = 'DELETE'
|
|
624
645
|
|
|
@@ -2,6 +2,7 @@ const { getNavigationIfStruct } = require('./structured')
|
|
|
2
2
|
const getColumns = require('../../db/utils/columns')
|
|
3
3
|
const { ensureNoDraftsSuffix, getDraftColumnsCQNForActive } = require('./draft')
|
|
4
4
|
const { getEntityNameFromCQN } = require('./entityFromCqn')
|
|
5
|
+
const cds = require('../../cds')
|
|
5
6
|
|
|
6
7
|
const isAsteriskColumn = col => col === '*' || (col.ref && col.ref[0] === '*' && !col.expand)
|
|
7
8
|
|
|
@@ -118,7 +119,8 @@ const rewriteAsterisks = (query, model, options) => {
|
|
|
118
119
|
if (!target) return
|
|
119
120
|
|
|
120
121
|
query.SELECT.columns = getColumns(target, { _4db }).map(col => ({ ref: [col.name] }))
|
|
121
|
-
if (_4db && target._isDraftEnabled
|
|
122
|
+
if (_4db && target._isDraftEnabled && !cds.env.features.lean_draft)
|
|
123
|
+
query.SELECT.columns.push(..._cqlDraftColumns(target))
|
|
122
124
|
}
|
|
123
125
|
}
|
|
124
126
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const cds = require('../../cds')
|
|
2
|
+
|
|
3
|
+
const getRowUUIDGeneratorFn = eventName => {
|
|
4
|
+
if (eventName === 'UPDATE') return
|
|
5
|
+
return (keyNames, row, template) => {
|
|
6
|
+
for (const keyName of keyNames) {
|
|
7
|
+
if (Object.prototype.hasOwnProperty.call(row, keyName)) {
|
|
8
|
+
continue
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const elementInfo = template.elements.get(keyName)
|
|
12
|
+
const plain = elementInfo && elementInfo.picked && elementInfo.picked.plain
|
|
13
|
+
if (!plain || !plain.categories) continue
|
|
14
|
+
if (plain.categories.includes('uuid')) {
|
|
15
|
+
row[keyName] = cds.utils.uuid()
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = getRowUUIDGeneratorFn
|
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
const DELIMITER = require('./templateDelimiter')
|
|
2
|
-
|
|
3
|
-
const _formatRowContext = (tKey, keyNames, row) => {
|
|
4
|
-
const keyValuePairs = keyNames.map(key => `${key}=${row[key]}`)
|
|
5
|
-
const keyValuePairsSerialized = keyValuePairs.join(',')
|
|
6
|
-
return `${tKey}(${keyValuePairsSerialized})`
|
|
7
|
-
}
|
|
2
|
+
const pathSerializer = require('./templateProcessorPathSerializer')
|
|
8
3
|
|
|
9
4
|
const _processElement = (processFn, row, key, target, picked = {}, isRoot, pathSegments) => {
|
|
10
5
|
const element = (target.elements || target.params)[key]
|
|
11
6
|
const { plain } = picked
|
|
12
7
|
|
|
13
8
|
if (!plain) return
|
|
9
|
+
|
|
14
10
|
/**
|
|
15
11
|
* @type import('../../types/api').templateElementInfo
|
|
16
12
|
*/
|
|
@@ -19,6 +15,7 @@ const _processElement = (processFn, row, key, target, picked = {}, isRoot, pathS
|
|
|
19
15
|
elementInfo.pathSegments = pathSegments.slice(0)
|
|
20
16
|
elementInfo.pathSegments.push(...target._flat2struct[key])
|
|
21
17
|
}
|
|
18
|
+
|
|
22
19
|
processFn(elementInfo)
|
|
23
20
|
}
|
|
24
21
|
|
|
@@ -36,34 +33,34 @@ const _processRow = (processFn, row, template, tKey, tValue, isRoot, pathOptions
|
|
|
36
33
|
|
|
37
34
|
const _getTargetKeyNames = target => {
|
|
38
35
|
const keyNames = []
|
|
36
|
+
|
|
39
37
|
for (const keyName in target.keys) {
|
|
40
38
|
if (target.keys[keyName].__isAssociationStrict) continue
|
|
41
39
|
keyNames.push(keyName)
|
|
42
40
|
}
|
|
41
|
+
|
|
43
42
|
return keyNames
|
|
44
43
|
}
|
|
45
44
|
|
|
46
45
|
const _processComplex = (processFn, row, template, key, pathOptions) => {
|
|
47
|
-
const
|
|
48
|
-
const rows = Array.isArray(
|
|
46
|
+
const subRow = row?.[key]
|
|
47
|
+
const rows = Array.isArray(subRow) ? subRow : [subRow]
|
|
49
48
|
if (rows.length === 0) return
|
|
50
|
-
const keyNames = _getTargetKeyNames(template.target)
|
|
49
|
+
const keyNames = pathOptions.includeKeyValues && _getTargetKeyNames(template.target)
|
|
51
50
|
|
|
52
51
|
for (let idx = 0; idx < rows.length; idx++) {
|
|
53
52
|
const row = rows[idx]
|
|
54
53
|
if (row == null) continue
|
|
55
54
|
const args = { processFn, row, template, isRoot: false, pathOptions }
|
|
56
55
|
|
|
57
|
-
let
|
|
56
|
+
let pathSegment
|
|
58
57
|
if (pathOptions.includeKeyValues) {
|
|
59
|
-
if (pathOptions.
|
|
60
|
-
|
|
58
|
+
if (pathOptions.rowUUIDGenerator) pathOptions.rowUUIDGenerator(keyNames, row, template)
|
|
59
|
+
pathSegment = pathSerializer(key, keyNames, row, template.target.elements, pathOptions.draftKeys)
|
|
61
60
|
}
|
|
62
61
|
|
|
63
|
-
if (pathOptions.pathSegments) pathOptions.pathSegments.push(
|
|
64
|
-
|
|
62
|
+
if (pathOptions.pathSegments) pathOptions.pathSegments.push(pathSegment || key)
|
|
65
63
|
templateProcessor(args)
|
|
66
|
-
|
|
67
64
|
if (pathOptions.pathSegments) pathOptions.pathSegments.pop()
|
|
68
65
|
}
|
|
69
66
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const templatePathSerializer = (tKey, keyNames, row, elements, draftKeys) => {
|
|
2
|
+
const keyValuePairs = keyNames.map(key => {
|
|
3
|
+
let quote
|
|
4
|
+
|
|
5
|
+
switch (elements[key].type) {
|
|
6
|
+
case 'cds.String':
|
|
7
|
+
quote = "'"
|
|
8
|
+
break
|
|
9
|
+
|
|
10
|
+
default:
|
|
11
|
+
quote = ''
|
|
12
|
+
break
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const keyValue = row[key] ?? draftKeys?.[key]
|
|
16
|
+
return `${key}=${quote}${keyValue}${quote}`
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const keyValuePairsSerialized = keyValuePairs.join(',')
|
|
20
|
+
return `${tKey}(${keyValuePairsSerialized})`
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = templatePathSerializer
|