@sap/cds 6.3.2 → 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 +76 -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 +2 -0
- 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/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/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 +12 -4
- 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
|
@@ -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
|
|
@@ -138,6 +138,7 @@ class JoinCQNFromExpanded {
|
|
|
138
138
|
this._addImplicitOrderBy(readToOneCQN, entity, tableAlias)
|
|
139
139
|
const givenColumns = readToOneCQN.columns
|
|
140
140
|
readToOneCQN.columns = []
|
|
141
|
+
if (entity['@cds.localized'] === false) defaultLanguage = true
|
|
141
142
|
this._expandedToFlat({ entity, givenColumns, readToOneCQN, tableAlias, toManyTree, defaultLanguage })
|
|
142
143
|
} else {
|
|
143
144
|
const table = unionTable || this._getRef(SELECT).table
|
|
@@ -150,6 +151,7 @@ class JoinCQNFromExpanded {
|
|
|
150
151
|
|
|
151
152
|
const givenColumns = readToOneCQN.columns
|
|
152
153
|
readToOneCQN.columns = []
|
|
154
|
+
if (entity['@cds.localized'] === false) defaultLanguage = true
|
|
153
155
|
this._expandedToFlat({ entity, givenColumns, readToOneCQN, tableAlias, toManyTree, defaultLanguage })
|
|
154
156
|
}
|
|
155
157
|
|
|
@@ -491,6 +493,7 @@ class JoinCQNFromExpanded {
|
|
|
491
493
|
* @param {boolean} arg.defaultLanguage - Use default language for localized fields
|
|
492
494
|
* @private
|
|
493
495
|
*/
|
|
496
|
+
// eslint-disable-next-line complexity
|
|
494
497
|
_expandedToFlat({ entity, givenColumns, readToOneCQN, tableAlias, toManyTree, defaultLanguage }) {
|
|
495
498
|
const toManyColumns = []
|
|
496
499
|
const mappings = this._getMappingObject(toManyTree)
|
|
@@ -518,7 +521,8 @@ class JoinCQNFromExpanded {
|
|
|
518
521
|
entity._isDraftEnabled &&
|
|
519
522
|
navTarget._isAssociationStrict &&
|
|
520
523
|
!navTarget['@odata.draft.enclosed'] &&
|
|
521
|
-
navTarget.name !== 'DraftAdministrativeData'
|
|
524
|
+
navTarget.name !== 'DraftAdministrativeData' &&
|
|
525
|
+
!entity.elements[navProp]._isCompositionBacklink
|
|
522
526
|
) {
|
|
523
527
|
mappings[navProp] = { [TO_ACTIVE]: true }
|
|
524
528
|
}
|
|
@@ -660,7 +664,10 @@ class JoinCQNFromExpanded {
|
|
|
660
664
|
const activeTableRequired =
|
|
661
665
|
readToOneCQN[IS_UNION_DRAFT] || // > REVISIT: blocks expanding comp2one
|
|
662
666
|
readToOneCQN[IS_ACTIVE] ||
|
|
663
|
-
(element &&
|
|
667
|
+
(element &&
|
|
668
|
+
element._isAssociationStrict &&
|
|
669
|
+
!element['@odata.draft.enclosed'] &&
|
|
670
|
+
!element._isCompositionBacklink) ||
|
|
664
671
|
!this._csn.definitions[target]._isDraftEnabled
|
|
665
672
|
|
|
666
673
|
const colTarget = target && ensureUnlocalized(target)
|
|
@@ -688,7 +695,7 @@ class JoinCQNFromExpanded {
|
|
|
688
695
|
|
|
689
696
|
const expandedEntity = this._getEntityForTable(target)
|
|
690
697
|
if (readToOneCQN[IS_UNION_DRAFT] && expandedEntity.drafts) {
|
|
691
|
-
const cols = column.expand.filter(c => !c.expand && !(c.ref[0] in DRAFT_COLUMNS_MAP)).map(c => c.ref[0])
|
|
698
|
+
const cols = column.expand.filter(c => c.ref && !c.expand && !(c.ref[0] in DRAFT_COLUMNS_MAP)).map(c => c.ref[0])
|
|
692
699
|
const ks = Object.keys(expandedEntity.keys).filter(
|
|
693
700
|
c => !expandedEntity.keys[c].isAssociation && !(c in DRAFT_COLUMNS_MAP)
|
|
694
701
|
)
|
|
@@ -968,6 +975,23 @@ class JoinCQNFromExpanded {
|
|
|
968
975
|
return column
|
|
969
976
|
}
|
|
970
977
|
|
|
978
|
+
if (Array.isArray(column.xpr)) {
|
|
979
|
+
return this._buildNewAliasColumn(
|
|
980
|
+
{
|
|
981
|
+
...column,
|
|
982
|
+
xpr: column.xpr.map(x => {
|
|
983
|
+
if (x.ref) {
|
|
984
|
+
const res = this._buildNewAliasColumn(x, entity, tableAlias, mappings)
|
|
985
|
+
delete res.as
|
|
986
|
+
return res
|
|
987
|
+
} else return x
|
|
988
|
+
})
|
|
989
|
+
},
|
|
990
|
+
entity,
|
|
991
|
+
tableAlias,
|
|
992
|
+
mappings
|
|
993
|
+
)
|
|
994
|
+
}
|
|
971
995
|
return this._buildNewAliasColumn(column, entity, tableAlias, mappings)
|
|
972
996
|
}
|
|
973
997
|
|
|
@@ -1146,7 +1170,8 @@ class JoinCQNFromExpanded {
|
|
|
1146
1170
|
readToOneCQN[IS_ACTIVE] ||
|
|
1147
1171
|
(element._isAssociationStrict && !element['@odata.draft.enclosed']) ||
|
|
1148
1172
|
!this._csn.definitions[colTarget]._isDraftEnabled
|
|
1149
|
-
|
|
1173
|
+
|
|
1174
|
+
const ref = this._refFromRefByExpand(column.ref[0], colTarget, defaultLanguageThis, expandActive)
|
|
1150
1175
|
const tableAlias = this._createAlias(toManyTree.concat(colRef).join(':'))
|
|
1151
1176
|
const on = entity._relations[colRef[0]].join(tableAlias, 'filterExpand')
|
|
1152
1177
|
const filterExpand = this._getFilterExpandCQN(readToOneCQN, on, parentAlias, entity.keys)
|
|
@@ -1239,14 +1264,6 @@ class JoinCQNFromExpanded {
|
|
|
1239
1264
|
return cqn
|
|
1240
1265
|
}
|
|
1241
1266
|
|
|
1242
|
-
_getJoinRef(elements, column, isActive, defaultLanguage) {
|
|
1243
|
-
const assoc = elements[column]
|
|
1244
|
-
if (typeof isActive !== 'boolean' || isActive || !assoc.isComposition) {
|
|
1245
|
-
return defaultLanguage ? ensureUnlocalized(assoc.target) : assoc.target
|
|
1246
|
-
}
|
|
1247
|
-
return assoc.target + '_drafts'
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
1267
|
_isPathExpressionToOne(ref, entity) {
|
|
1251
1268
|
const ref0 = ref[0]
|
|
1252
1269
|
const el = entity.elements[ref0]
|
|
@@ -18,8 +18,6 @@ const propagateForeignKeys = require('../../common/utils/propagateForeignKeys')
|
|
|
18
18
|
const getTemplate = require('../../common/utils/template')
|
|
19
19
|
const templateProcessor = require('../../common/utils/templateProcessor')
|
|
20
20
|
|
|
21
|
-
const { checkIfAssocDeep } = require('../../cds-services/util/assert')
|
|
22
|
-
|
|
23
21
|
const { DRAFT_COLUMNS_MAP } = require('../../common/constants/draft')
|
|
24
22
|
|
|
25
23
|
const _isManaged = (category, event) =>
|
|
@@ -85,7 +83,12 @@ const _processCategory = (req, category, { row, key, element }) => {
|
|
|
85
83
|
if (k.isAssociation || k.name === 'IsActiveEntity') continue
|
|
86
84
|
|
|
87
85
|
if (!k.isUUID && !(k.name in val)) {
|
|
88
|
-
req.error(
|
|
86
|
+
req.error({
|
|
87
|
+
code: 'MUST_NOT_BE_NULL',
|
|
88
|
+
message: 'Value is required',
|
|
89
|
+
target: key + '[' + k + ']',
|
|
90
|
+
args: [key + '[' + k + ']']
|
|
91
|
+
})
|
|
89
92
|
return
|
|
90
93
|
}
|
|
91
94
|
}
|
|
@@ -93,7 +96,7 @@ const _processCategory = (req, category, { row, key, element }) => {
|
|
|
93
96
|
|
|
94
97
|
// not null without default (for better error message)
|
|
95
98
|
if (category === '!default' && val == null && req.event === 'CREATE') {
|
|
96
|
-
req.error(
|
|
99
|
+
req.error({ code: 'MUST_NOT_BE_NULL', message: 'Value is required', target: key, args: [key] })
|
|
97
100
|
return
|
|
98
101
|
}
|
|
99
102
|
|
|
@@ -101,11 +104,6 @@ const _processCategory = (req, category, { row, key, element }) => {
|
|
|
101
104
|
if (category === 'uuid' && !val && req.event === 'CREATE') {
|
|
102
105
|
row[key] = cds.utils.uuid()
|
|
103
106
|
}
|
|
104
|
-
|
|
105
|
-
// check for forbidden deep operations for association
|
|
106
|
-
if (category === 'associationEffective' && (req.event === 'CREATE' || req.event === 'UPDATE')) {
|
|
107
|
-
checkIfAssocDeep(element, val, req)
|
|
108
|
-
}
|
|
109
107
|
}
|
|
110
108
|
|
|
111
109
|
const _processorFn = req => elementInfo => {
|
|
@@ -152,10 +150,6 @@ const _pickCRUD = element => {
|
|
|
152
150
|
categories.push({ category: '@cds.on.update', args: element['@cds.on.update'] })
|
|
153
151
|
}
|
|
154
152
|
|
|
155
|
-
if (element._isAssociationStrict && !element._target._hasPersistenceSkip) {
|
|
156
|
-
categories.push('associationEffective')
|
|
157
|
-
}
|
|
158
|
-
|
|
159
153
|
// REVISIT: element._foreignKeys.length seems to be a very broad check
|
|
160
154
|
if (element.isAssociation && element._foreignKeys.length) {
|
|
161
155
|
categories.push({ category: 'propagateForeignKeys' })
|