@sap/cds 6.3.2 → 6.4.1
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 +95 -0
- package/apis/cds.d.ts +3 -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 +49 -11
- package/apis/serve.d.ts +8 -1
- package/apis/services.d.ts +311 -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 +17 -12
- 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/auth/index.js +17 -15
- 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 +9 -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/middlewares/cds-context.js +0 -2
- package/lib/srv/middlewares/ctx-auth.js +11 -0
- package/lib/srv/middlewares/ctx-model.js +22 -20
- package/lib/srv/middlewares/index.js +7 -9
- package/lib/srv/protocols/_legacy.js +4 -0
- package/lib/srv/protocols/graphql.js +2 -2
- package/lib/srv/protocols/index.js +7 -3
- package/lib/srv/srv-api.js +1 -0
- package/lib/srv/srv-methods.js +1 -1
- package/lib/utils/cds-utils.js +11 -0
- package/lib/utils/data.js +2 -2
- package/lib/utils/inflect.js +13 -12
- package/lib/utils/tar.js +43 -13
- 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/crud.js +1 -1
- package/libx/_runtime/common/generic/input.js +4 -24
- package/libx/_runtime/common/generic/paging.js +10 -9
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +31 -0
- 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 +27 -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/InsertBuilder.js +5 -1
- package/libx/_runtime/db/sql-builder/UpsertBuilder.js +24 -0
- package/libx/_runtime/db/sql-builder/annotations.js +6 -3
- 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/Service.js +1 -1
- package/libx/_runtime/hana/execute.js +5 -5
- 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 +4 -1
- package/libx/_runtime/sqlite/convertDraftAdminPathExpression.js +56 -0
- package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +41 -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 +2 -19
|
@@ -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
|
|
|
@@ -658,6 +679,8 @@ const findQueryTarget = q => {
|
|
|
658
679
|
? q.INSERT._transitions[q.INSERT._transitions.length - 1].target
|
|
659
680
|
: q.UPDATE
|
|
660
681
|
? q.UPDATE._transitions[q.UPDATE._transitions.length - 1].target
|
|
682
|
+
: q.UPSERT
|
|
683
|
+
? q.UPSERT._transitions[q.UPSERT._transitions.length - 1].target
|
|
661
684
|
: q.DELETE
|
|
662
685
|
? q.DELETE._transitions[q.DELETE._transitions.length - 1].target
|
|
663
686
|
: undefined
|
|
@@ -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' })
|
|
@@ -36,6 +36,10 @@ class InsertBuilder extends BaseBuilder {
|
|
|
36
36
|
this._csn = csn
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
annotatedColumns(entityName, csn) {
|
|
40
|
+
return getAnnotatedColumns(entityName, csn)
|
|
41
|
+
}
|
|
42
|
+
|
|
39
43
|
/**
|
|
40
44
|
* Builds an Object based on the properties of the CQN object.
|
|
41
45
|
*
|
|
@@ -77,7 +81,7 @@ class InsertBuilder extends BaseBuilder {
|
|
|
77
81
|
this._findUuidKeys(entityName)
|
|
78
82
|
|
|
79
83
|
this._columnIndexesToDelete = []
|
|
80
|
-
const annotatedColumns =
|
|
84
|
+
const annotatedColumns = this.annotatedColumns(entityName, this._csn)
|
|
81
85
|
|
|
82
86
|
if (this._obj.INSERT.columns) {
|
|
83
87
|
this._removeAlreadyExistingInsertAnnotatedColumnsFromMap(annotatedColumns)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const InsertBuilder = require('./InsertBuilder')
|
|
2
|
+
const getAnnotatedColumns = require('./annotations')
|
|
3
|
+
|
|
4
|
+
class UpsertBuilder extends InsertBuilder {
|
|
5
|
+
constructor(obj, options, csn) {
|
|
6
|
+
super(obj, options, csn)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
annotatedColumns(entityName, csn) {
|
|
10
|
+
const { updateAnnotatedColumns } = getAnnotatedColumns(entityName, csn)
|
|
11
|
+
return { insertAnnotatedColumns: updateAnnotatedColumns }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// REVISIT: We need to copy over the implementation for annotation handling
|
|
15
|
+
build() {
|
|
16
|
+
this._obj = { INSERT: this._obj.UPSERT }
|
|
17
|
+
super.build()
|
|
18
|
+
this._outputObj.sql = this._outputObj.sql.replace('INSERT INTO', 'UPSERT')
|
|
19
|
+
this._outputObj.sql += ' WITH PRIMARY KEY'
|
|
20
|
+
return this._outputObj
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = UpsertBuilder
|
|
@@ -16,7 +16,10 @@ const _getAnnotationNames = column => {
|
|
|
16
16
|
const getAnnotatedColumns = (entityName, csn) => {
|
|
17
17
|
const entityNameWithoutSuffix = ensureNoDraftsSuffix(entityName)
|
|
18
18
|
if (!csn || !csn.definitions[entityNameWithoutSuffix]) {
|
|
19
|
-
return
|
|
19
|
+
return {
|
|
20
|
+
insertAnnotatedColumns: new Map(),
|
|
21
|
+
updateAnnotatedColumns: new Map()
|
|
22
|
+
}
|
|
20
23
|
}
|
|
21
24
|
const columns = getColumns(csn.definitions[entityNameWithoutSuffix])
|
|
22
25
|
const insertAnnotatedColumns = new Map()
|
|
@@ -39,8 +42,8 @@ const getAnnotatedColumns = (entityName, csn) => {
|
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
return {
|
|
42
|
-
insertAnnotatedColumns
|
|
43
|
-
updateAnnotatedColumns
|
|
45
|
+
insertAnnotatedColumns,
|
|
46
|
+
updateAnnotatedColumns
|
|
44
47
|
}
|
|
45
48
|
}
|
|
46
49
|
|
|
@@ -3,6 +3,7 @@ const DropBuilder = require('./DropBuilder')
|
|
|
3
3
|
const SelectBuilder = require('./SelectBuilder')
|
|
4
4
|
const InsertBuilder = require('./InsertBuilder')
|
|
5
5
|
const UpdateBuilder = require('./UpdateBuilder')
|
|
6
|
+
const UpsertBuilder = require('./UpsertBuilder')
|
|
6
7
|
const DeleteBuilder = require('./DeleteBuilder')
|
|
7
8
|
const ExpressionBuilder = require('./ExpressionBuilder')
|
|
8
9
|
const ReferenceBuilder = require('./ReferenceBuilder')
|
|
@@ -18,6 +19,7 @@ module.exports = {
|
|
|
18
19
|
SelectBuilder,
|
|
19
20
|
InsertBuilder,
|
|
20
21
|
UpdateBuilder,
|
|
22
|
+
UpsertBuilder,
|
|
21
23
|
DeleteBuilder,
|
|
22
24
|
ExpressionBuilder,
|
|
23
25
|
ReferenceBuilder,
|
|
@@ -2,6 +2,7 @@ const DeleteBuilder = require('./DeleteBuilder')
|
|
|
2
2
|
const InsertBuilder = require('./InsertBuilder')
|
|
3
3
|
const SelectBuilder = require('./SelectBuilder')
|
|
4
4
|
const UpdateBuilder = require('./UpdateBuilder')
|
|
5
|
+
const UpsertBuilder = require('./UpsertBuilder')
|
|
5
6
|
const CreateBuilder = require('./CreateBuilder')
|
|
6
7
|
const DropBuilder = require('./DropBuilder')
|
|
7
8
|
|
|
@@ -16,6 +17,10 @@ const _getCustomBuilderIfExists = (options, type) => {
|
|
|
16
17
|
return options.customBuilder.UpdateBuilder
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
case 'UPSERT': {
|
|
21
|
+
return options.customBuilder.UpsertBuilder
|
|
22
|
+
}
|
|
23
|
+
|
|
19
24
|
case 'DELETE': {
|
|
20
25
|
return options.customBuilder.DeleteBuilder
|
|
21
26
|
}
|
|
@@ -77,6 +82,10 @@ const build = (cqn, options, csn) => {
|
|
|
77
82
|
return build(_getCustomBuilderIfExists(options, 'UPDATE') || UpdateBuilder)
|
|
78
83
|
}
|
|
79
84
|
|
|
85
|
+
if (cqn.UPSERT) {
|
|
86
|
+
return build(_getCustomBuilderIfExists(options, 'UPSERT') || UpsertBuilder)
|
|
87
|
+
}
|
|
88
|
+
|
|
80
89
|
if (cqn.DELETE) {
|
|
81
90
|
return build(_getCustomBuilderIfExists(options, 'DELETE') || DeleteBuilder)
|
|
82
91
|
}
|
|
@@ -16,12 +16,14 @@ const getColumns = (entity, { _4db, onlyKeys } = { _4db: true, onlyKeys: false }
|
|
|
16
16
|
if (!(entity && entity.elements)) return []
|
|
17
17
|
const columnNames = []
|
|
18
18
|
// REVISIT!!!
|
|
19
|
-
const elements =
|
|
19
|
+
const elements = cds.env.features.lean_draft
|
|
20
|
+
? entity.elements
|
|
21
|
+
: Object.getPrototypeOf(entity.elements) || entity.elements
|
|
20
22
|
for (const elementName in elements) {
|
|
21
23
|
const element = elements[elementName]
|
|
22
24
|
if (onlyKeys && !element.key) continue
|
|
23
25
|
if (element.isAssociation) continue
|
|
24
|
-
if (_4db && entity._isDraftEnabled && elementName in DRAFT_COLUMNS_MAP) continue
|
|
26
|
+
if (!cds.env.features.lean_draft && _4db && entity._isDraftEnabled && elementName in DRAFT_COLUMNS_MAP) continue
|
|
25
27
|
if ((cds.env.effective.odata.structs || cds.env.features.ucsn_struct_conversion) && element.elements) {
|
|
26
28
|
columnNames.push(...resolveStructured({ element, structProperties: [] }, false))
|
|
27
29
|
continue
|
|
@@ -714,16 +714,6 @@ const _getSiblingScenario = (req, columns, model, siblingIndex, nav) => {
|
|
|
714
714
|
const _getSiblingQueryFromWhere = (query, queryIndex, parentQuery) => {
|
|
715
715
|
if (query.SELECT && query.SELECT.where && queryIndex > 0) {
|
|
716
716
|
for (let i = 0; i < query.SELECT.where.length; i++) {
|
|
717
|
-
if (query.SELECT.where[i].xpr && queryIndex > 0) {
|
|
718
|
-
const sibilingQueryFromWhere = _getSiblingQueryFromWhere(
|
|
719
|
-
{ SELECT: { where: query.SELECT.where[i].xpr } },
|
|
720
|
-
queryIndex - 1,
|
|
721
|
-
query
|
|
722
|
-
)
|
|
723
|
-
|
|
724
|
-
if (sibilingQueryFromWhere) return sibilingQueryFromWhere
|
|
725
|
-
}
|
|
726
|
-
|
|
727
717
|
if (query.SELECT.where[i] === 'exists' && queryIndex > 0) {
|
|
728
718
|
return _getSiblingQueryFromWhere(query.SELECT.where[i + 1], queryIndex - 1, query)
|
|
729
719
|
}
|
|
@@ -757,7 +747,6 @@ const _replaceWhereExists = (query, _siblingIndex, siblingCQN) => {
|
|
|
757
747
|
|
|
758
748
|
const indexExists = query.SELECT.where.indexOf('exists')
|
|
759
749
|
if (indexExists > -1) {
|
|
760
|
-
if (_siblingIndex > 0) return _replaceWhereExists(query.SELECT.where[indexExists + 1], _siblingIndex - 1)
|
|
761
750
|
query.SELECT.where.splice(indexExists + 1, 1, siblingCQN)
|
|
762
751
|
}
|
|
763
752
|
}
|
|
@@ -1019,7 +1008,7 @@ const _generateCQN = (req, columns, from, model) => {
|
|
|
1019
1008
|
let siblingIndex = nav.indexOf('SiblingEntity')
|
|
1020
1009
|
|
|
1021
1010
|
// it can also be a property access (new parser), then we must shift it
|
|
1022
|
-
if (siblingIndex
|
|
1011
|
+
if (siblingIndex > 0 && req.target.elements[nav[0]] && !req.target.elements[nav[0]].isAssociation) {
|
|
1023
1012
|
nav.shift()
|
|
1024
1013
|
siblingIndex = siblingIndex - 1
|
|
1025
1014
|
}
|