@sap/cds 5.7.5 → 5.8.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 +72 -0
- package/app/fiori/routes.js +1 -1
- package/bin/deploy/to-hana/cfUtil.js +251 -138
- package/bin/deploy/to-hana/gitUtil.js +55 -0
- package/bin/deploy/to-hana/hana.js +92 -93
- package/bin/deploy/to-hana/hdiDeployUtil.js +42 -27
- package/bin/deploy/to-hana/index.js +14 -13
- package/bin/mtx/in-cds.js +1 -0
- package/bin/serve.js +1 -1
- package/bin/version.js +1 -0
- package/lib/compile/cdsc.js +0 -6
- package/lib/compile/resolve.js +1 -1
- package/lib/compile/to/srvinfo.js +1 -1
- package/lib/core/classes.js +21 -1
- package/lib/env/index.js +3 -2
- package/lib/env/requires.js +4 -0
- package/lib/i18n/localize.js +5 -8
- package/lib/index.js +1 -0
- package/lib/log/errors.js +1 -1
- package/lib/ql/SELECT.js +2 -2
- package/lib/req/cds-context.js +1 -1
- package/lib/req/context.js +1 -1
- package/lib/serve/Transaction.js +9 -5
- package/lib/serve/index.js +13 -21
- package/lib/utils/tests.js +90 -66
- package/libx/_runtime/audit/generic/personal/modification.js +0 -8
- package/libx/_runtime/auth/index.js +7 -6
- package/libx/_runtime/auth/strategies/dwc.js +43 -0
- package/libx/_runtime/auth/utils.js +24 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +11 -32
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +12 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +7 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +24 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +43 -38
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +11 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/boundToCQN.js +1 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/deleteToCQN.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/orderByToCQN.js +9 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +17 -30
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +12 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/AbstractEdmStructuredType.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriHelper.js +7 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriTokenizer.js +2 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +19 -47
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +4 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +7 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/CommandExecutor.js +0 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/ConditionalRequestControlCommand.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ContextURLFactory.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/TrustedResourceJsonSerializer.js +2 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +6 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +41 -17
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +1 -17
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +60 -18
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +7 -5
- package/libx/_runtime/cds-services/adapter/rest/Rest.js +22 -1
- package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +8 -3
- package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +3 -0
- package/libx/_runtime/cds-services/services/utils/columns.js +5 -1
- package/libx/_runtime/cds-services/services/utils/compareJson.js +15 -16
- package/libx/_runtime/cds-services/services/utils/differ.js +2 -8
- package/libx/_runtime/common/aspects/Association.js +16 -0
- package/libx/_runtime/common/composition/data.js +28 -37
- package/libx/_runtime/common/composition/delete.js +107 -58
- package/libx/_runtime/common/composition/index.js +2 -1
- package/libx/_runtime/common/composition/insert.js +13 -13
- package/libx/_runtime/common/composition/update.js +39 -34
- package/libx/_runtime/common/error/frontend.js +17 -2
- package/libx/_runtime/common/generic/auth.js +20 -85
- package/libx/_runtime/common/generic/crud.js +22 -1
- package/libx/_runtime/common/i18n/messages.properties +2 -1
- package/libx/_runtime/common/utils/cqn.js +2 -6
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +95 -122
- package/libx/_runtime/common/utils/csn.js +14 -3
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +18 -1
- package/libx/_runtime/common/utils/keys.js +2 -1
- package/libx/_runtime/common/utils/path.js +1 -1
- package/libx/_runtime/common/utils/resolveView.js +12 -4
- package/libx/_runtime/common/utils/rewriteAsterisks.js +27 -13
- package/libx/_runtime/common/utils/search2cqn4sql.js +11 -6
- package/libx/_runtime/common/utils/vcap.js +27 -10
- package/libx/_runtime/db/data-conversion/post-processing.js +20 -13
- package/libx/_runtime/db/expand/expand-v2.js +21 -12
- package/libx/_runtime/db/expand/expandCQNToJoin.js +8 -6
- package/libx/_runtime/db/expand/index.js +3 -0
- package/libx/_runtime/db/generic/create.js +0 -10
- package/libx/_runtime/db/generic/index.js +3 -0
- package/libx/_runtime/db/generic/read.js +2 -24
- package/libx/_runtime/db/generic/rewrite.js +1 -3
- package/libx/_runtime/db/generic/update.js +1 -1
- package/libx/_runtime/db/query/delete.js +10 -4
- package/libx/_runtime/db/query/insert.js +3 -3
- package/libx/_runtime/db/query/read.js +4 -1
- package/libx/_runtime/db/query/update.js +5 -5
- package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +9 -2
- package/libx/_runtime/db/sql-builder/FunctionBuilder.js +3 -0
- package/libx/_runtime/db/sql-builder/index.js +3 -0
- package/libx/_runtime/db/utils/columns.js +5 -2
- package/libx/_runtime/db/utils/deep.js +6 -8
- package/libx/_runtime/db/utils/generateAliases.js +56 -6
- package/libx/_runtime/fiori/generic/before.js +73 -49
- package/libx/_runtime/fiori/generic/edit.js +14 -18
- package/libx/_runtime/fiori/generic/patch.js +8 -11
- package/libx/_runtime/fiori/generic/read.js +19 -16
- package/libx/_runtime/fiori/generic/readOverDraft.js +1 -4
- package/libx/_runtime/hana/Service.js +1 -1
- package/libx/_runtime/hana/conversion.js +10 -0
- package/libx/_runtime/hana/execute.js +33 -16
- package/libx/_runtime/hana/search.js +3 -3
- package/libx/_runtime/hana/search2cqn4sql.js +22 -21
- package/libx/_runtime/hana/searchToContains.js +1 -1
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +0 -1
- package/libx/_runtime/messaging/enterprise-messaging.js +1 -0
- package/libx/_runtime/messaging/file-based.js +3 -1
- package/libx/_runtime/messaging/service.js +4 -1
- package/libx/_runtime/remote/utils/client.js +33 -20
- package/libx/_runtime/remote/utils/data.js +52 -11
- package/libx/_runtime/sqlite/Service.js +1 -1
- package/libx/_runtime/sqlite/conversion.js +10 -0
- package/libx/_runtime/types/api.js +2 -2
- package/libx/gql/resolvers/parse/ast/enrich.js +1 -0
- package/libx/odata/afterburner.js +29 -6
- package/libx/odata/cqn2odata.js +9 -0
- package/libx/odata/grammar.pegjs +49 -21
- package/libx/odata/index.js +2 -2
- package/libx/odata/parser.js +1 -1
- package/libx/odata/utils.js +2 -2
- package/libx/rest/RestAdapter.js +29 -1
- package/libx/rest/middleware/auth.js +1 -3
- package/libx/rest/middleware/parse.js +1 -0
- package/package.json +1 -1
- package/server.js +1 -1
- package/bin/deploy/to-hana/logger.js +0 -27
- package/bin/deploy/to-hana/runCommand.js +0 -113
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +0 -37
- package/libx/_runtime/common/utils/auth.js +0 -16
|
@@ -90,7 +90,8 @@ const _extractRefs = (from, as) => {
|
|
|
90
90
|
return [ref]
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
|
|
93
|
+
// REVISIT: check why we need includeAlias for some draft cases (check AFC tests)
|
|
94
|
+
const _addMapperFunction = (elements, toService, key, type, alias, includeAlias) => {
|
|
94
95
|
if (!toService.has(type)) {
|
|
95
96
|
return
|
|
96
97
|
}
|
|
@@ -99,9 +100,8 @@ const _addMapperFunction = (elements, toService, key, type, from, includeAlias)
|
|
|
99
100
|
|
|
100
101
|
// ambiguous cases will lead to SQL syntax errors anyway, so no need for a check
|
|
101
102
|
elements.set(key, convertFunction)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
elements.set(`${from.as}_${key}`, convertFunction)
|
|
103
|
+
if (includeAlias && alias) {
|
|
104
|
+
elements.set(`${alias}_${key}`, convertFunction)
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
|
|
@@ -109,13 +109,13 @@ const _filterUnique = (value, index, arr) => {
|
|
|
109
109
|
return arr.indexOf(value) === index
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
const _addMapperFunction4struct = (elements, toService, parent, struct,
|
|
112
|
+
const _addMapperFunction4struct = (elements, toService, parent, struct, alias, includeAlias) => {
|
|
113
113
|
for (const k in struct.elements) {
|
|
114
114
|
const current = struct.elements[k]
|
|
115
115
|
if (current._isStructured) {
|
|
116
|
-
_addMapperFunction4struct(elements, toService, `${parent}_${k}`, current,
|
|
116
|
+
_addMapperFunction4struct(elements, toService, `${parent}_${k}`, current, alias, includeAlias)
|
|
117
117
|
} else {
|
|
118
|
-
_addMapperFunction(elements, toService, `${parent}_${k}`, current.type,
|
|
118
|
+
_addMapperFunction(elements, toService, `${parent}_${k}`, current.type, alias, includeAlias)
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
}
|
|
@@ -126,7 +126,6 @@ const _addMapperFunction4struct = (elements, toService, parent, struct, from, in
|
|
|
126
126
|
* @param {Map} toService - Mapping instructions for data conversions based on CDS data types
|
|
127
127
|
* @param {object} csn - Reflected CSN
|
|
128
128
|
* @param {object} cqn - CQN that is used to query the DB.
|
|
129
|
-
* @param {boolean} [includeAlias] - Include mapping for aliases. Defaults to false.
|
|
130
129
|
* @returns {Map<any, any>}
|
|
131
130
|
* @private
|
|
132
131
|
*/
|
|
@@ -142,13 +141,16 @@ const _getElementCombinations = (toService, csn, cqn, includeAlias = false) => {
|
|
|
142
141
|
}
|
|
143
142
|
|
|
144
143
|
const entity = csn.definitions[ensureUnlocalized(entityName)]
|
|
145
|
-
|
|
146
144
|
for (const key in entity.elements) {
|
|
147
145
|
const element = entity.elements[key]
|
|
148
146
|
if (element._isStructured) {
|
|
149
|
-
_addMapperFunction4struct(elements, toService, key, element, from, includeAlias)
|
|
147
|
+
_addMapperFunction4struct(elements, toService, key, element, from.as, includeAlias)
|
|
150
148
|
} else {
|
|
151
|
-
|
|
149
|
+
if ('SET' in cqn.SELECT.from) {
|
|
150
|
+
_addMapperFunction(elements, toService, key, element.type, cqn.SELECT.from.as, includeAlias)
|
|
151
|
+
} else {
|
|
152
|
+
_addMapperFunction(elements, toService, key, element.type, from.as, includeAlias)
|
|
153
|
+
}
|
|
152
154
|
}
|
|
153
155
|
}
|
|
154
156
|
}
|
|
@@ -182,15 +184,17 @@ const _getMapperForListedElements = (toService, csn, cqn) => {
|
|
|
182
184
|
|
|
183
185
|
for (const element of cqn.SELECT.columns) {
|
|
184
186
|
if (element.ref) {
|
|
185
|
-
const identifier = element.ref
|
|
187
|
+
const identifier = element.ref.length === 1 ? element.ref[0] : undefined
|
|
186
188
|
const name = element.as ? element.as : identifier
|
|
187
189
|
|
|
188
190
|
if (element.cast) {
|
|
189
191
|
mapper.set(name, _getCastFunction(element.cast))
|
|
190
192
|
} else if (elements.has(name)) {
|
|
191
193
|
mapper.set(name, elements.get(name))
|
|
192
|
-
} else if (elements.has(identifier)
|
|
194
|
+
} else if (elements.has(identifier)) {
|
|
193
195
|
mapper.set(name, elements.get(identifier))
|
|
196
|
+
} else if (elements.has(element.ref.join('_'))) {
|
|
197
|
+
mapper.set(name || element.ref[element.ref.length - 1], elements.get(element.ref.join('_')))
|
|
194
198
|
}
|
|
195
199
|
} else if (element.as && element.cast) {
|
|
196
200
|
mapper.set(element.as, _getCastFunction(element.cast))
|
|
@@ -514,6 +518,9 @@ const getPropertyMapper = (csn, cqn) => {
|
|
|
514
518
|
return new Map()
|
|
515
519
|
}
|
|
516
520
|
|
|
521
|
+
/*
|
|
522
|
+
* this module is required by cds-pg. -> in case of incompatible changes, we should let them know.
|
|
523
|
+
*/
|
|
517
524
|
module.exports = {
|
|
518
525
|
getPropertyMapper,
|
|
519
526
|
getPostProcessMapper,
|
|
@@ -11,9 +11,10 @@ const _removeParentKeysFromRow = (row, prefix, keys) => {
|
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
const _autoExpandNavsAndAttachToResult = async (entity, previousResult, depth) => {
|
|
14
|
+
const _autoExpandNavsAndAttachToResult = async (entity, previousResult, depth, options) => {
|
|
15
15
|
for (const nav in entity._associations) {
|
|
16
16
|
const navigation = entity._associations[nav]
|
|
17
|
+
if (options.onlyCompositions && navigation._isAssociationEffective) continue
|
|
17
18
|
|
|
18
19
|
// do not expand backlinks
|
|
19
20
|
if (navigation._isBacklink) continue
|
|
@@ -26,7 +27,9 @@ const _autoExpandNavsAndAttachToResult = async (entity, previousResult, depth) =
|
|
|
26
27
|
.on(entity._relations[navigation.name].join(childAlias, parentAlias))
|
|
27
28
|
|
|
28
29
|
// set alias for expanded columns already
|
|
29
|
-
const childColumns = getColumns(navigation._target
|
|
30
|
+
const childColumns = getColumns(navigation._target, { _4db: true, onlyKeys: options.onlyKeys }).map(c => ({
|
|
31
|
+
ref: [childAlias, c.name]
|
|
32
|
+
}))
|
|
30
33
|
const parentKeys = Object.keys(entity.keys).filter(k => !entity.keys[k].isAssociation)
|
|
31
34
|
// mark parent key with prefix in alias
|
|
32
35
|
const parentKeysWithAlias = parentKeys.map(pk => ({ ref: [parentAlias, pk], as: `$$pk_${pk}` }))
|
|
@@ -69,16 +72,17 @@ const _autoExpandNavsAndAttachToResult = async (entity, previousResult, depth) =
|
|
|
69
72
|
|
|
70
73
|
// expand next level if needed
|
|
71
74
|
if (depth - 1 !== 0 && result.length && navigation._target._associations) {
|
|
72
|
-
await _autoExpandNavsAndAttachToResult(navigation._target, result, depth - 1)
|
|
75
|
+
await _autoExpandNavsAndAttachToResult(navigation._target, result, depth - 1, options)
|
|
73
76
|
}
|
|
74
77
|
}
|
|
75
78
|
|
|
76
79
|
return previousResult
|
|
77
80
|
}
|
|
78
81
|
|
|
79
|
-
const _foreignKeysOfTopLevelNavs = entity => {
|
|
82
|
+
const _foreignKeysOfTopLevelNavs = (entity, options) => {
|
|
80
83
|
const requiredFks = new Set()
|
|
81
84
|
for (const nav in entity._associations) {
|
|
85
|
+
if (options.onlyCompositions && entity._associations[nav]._isAssociationEffective) continue
|
|
82
86
|
const onCond = entity._relations[nav].join('child', 'parent')
|
|
83
87
|
for (const ele of onCond) {
|
|
84
88
|
if (ele.ref && ele.ref[0] === 'parent') {
|
|
@@ -89,6 +93,15 @@ const _foreignKeysOfTopLevelNavs = entity => {
|
|
|
89
93
|
return [...requiredFks]
|
|
90
94
|
}
|
|
91
95
|
|
|
96
|
+
const _addForeignKeys = (columns, entity, options) => {
|
|
97
|
+
const fks = _foreignKeysOfTopLevelNavs(entity, options)
|
|
98
|
+
fks.forEach(fk => {
|
|
99
|
+
if (!columns.some(c => c.ref[0] === fk)) {
|
|
100
|
+
columns.push({ ref: [fk] })
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
|
|
92
105
|
/**
|
|
93
106
|
* 1. Creates flattened SQL statements for each expand layer
|
|
94
107
|
* 2. Mixes in foreign keys if needed
|
|
@@ -98,18 +111,15 @@ const _foreignKeysOfTopLevelNavs = entity => {
|
|
|
98
111
|
* @returns object
|
|
99
112
|
*/
|
|
100
113
|
const expandV2 = async (model, dbc, query, user, locale, txTimestamp, executeSelectCQN) => {
|
|
114
|
+
const expandColumn = query.SELECT.columns.find(c => c.expand && typeof c.expand === 'string')
|
|
115
|
+
const options = Object.assign({ onlyKeys: false, onlyCompositions: false }, expandColumn._options)
|
|
101
116
|
// remove expand columns from query without modifying
|
|
102
117
|
const topLevelSelect = query.clone().columns(query.SELECT.columns.filter(c => !c.expand))
|
|
103
118
|
|
|
104
119
|
const entity = model.definitions[topLevelSelect.SELECT.from.ref[0]]
|
|
105
120
|
|
|
106
121
|
// ensure foreign keys are selected if needed
|
|
107
|
-
|
|
108
|
-
fks.forEach(fk => {
|
|
109
|
-
if (!topLevelSelect.SELECT.columns.some(c => c.ref[0] === fk)) {
|
|
110
|
-
topLevelSelect.SELECT.columns.push({ ref: [fk] })
|
|
111
|
-
}
|
|
112
|
-
})
|
|
122
|
+
_addForeignKeys(topLevelSelect.SELECT.columns, entity, options)
|
|
113
123
|
|
|
114
124
|
const result = await executeSelectCQN(model, dbc, topLevelSelect, user, locale, txTimestamp)
|
|
115
125
|
|
|
@@ -119,9 +129,8 @@ const expandV2 = async (model, dbc, query, user, locale, txTimestamp, executeSel
|
|
|
119
129
|
|
|
120
130
|
// _associations contains compositions and associations
|
|
121
131
|
if (entity._associations) {
|
|
122
|
-
const expandColumn = query.SELECT.columns.find(c => c.expand && typeof c.expand === 'string')
|
|
123
132
|
const depth = expandColumn.expand === '**' ? -1 : Number(expandColumn.expand.replace('*', ''))
|
|
124
|
-
await _autoExpandNavsAndAttachToResult(entity, Array.isArray(result) ? result : [result], depth)
|
|
133
|
+
await _autoExpandNavsAndAttachToResult(entity, Array.isArray(result) ? result : [result], depth, options)
|
|
125
134
|
}
|
|
126
135
|
|
|
127
136
|
return result
|
|
@@ -2,10 +2,15 @@ const cds = require('../../cds')
|
|
|
2
2
|
|
|
3
3
|
const { getAllKeys } = require('../../cds-services/adapter/odata-v4/odata-to-cqn/utils')
|
|
4
4
|
|
|
5
|
+
const { deepCopyObject } = require('../../common/utils/copy')
|
|
5
6
|
const { getNavigationIfStruct } = require('../../common/utils/structured')
|
|
6
7
|
const { ensureNoDraftsSuffix, ensureDraftsSuffix, ensureUnlocalized } = require('../../common/utils/draft')
|
|
7
|
-
const { filterKeys } = require('../../fiori/utils/handler')
|
|
8
8
|
const { isAsteriskColumn } = require('../../common/utils/rewriteAsterisks')
|
|
9
|
+
const { getCQNUnionFrom } = require('../../common/utils/union')
|
|
10
|
+
|
|
11
|
+
const { DRAFT_COLUMNS } = require('../../common/constants/draft')
|
|
12
|
+
|
|
13
|
+
const { filterKeys } = require('../../fiori/utils/handler')
|
|
9
14
|
|
|
10
15
|
// Symbols are used to add extra information in response structure
|
|
11
16
|
const GET_KEY_VALUE = Symbol.for('sap.cds.getKeyValue')
|
|
@@ -16,12 +21,9 @@ const IDENTIFIER = Symbol.for('sap.cds.identifier')
|
|
|
16
21
|
const IS_ACTIVE = Symbol.for('sap.cds.isActive')
|
|
17
22
|
const IS_UNION_DRAFT = Symbol.for('sap.cds.isUnionDraft')
|
|
18
23
|
|
|
19
|
-
const { DRAFT_COLUMNS } = require('../../common/constants/draft')
|
|
20
|
-
|
|
21
|
-
const { getCQNUnionFrom } = require('../../common/utils/union')
|
|
22
|
-
|
|
23
24
|
function getCqnCopy(readToOneCQN) {
|
|
24
|
-
|
|
25
|
+
// REVISIT: Use query.clone() instead
|
|
26
|
+
const readToOneCQNCopy = deepCopyObject(readToOneCQN)
|
|
25
27
|
if (readToOneCQN[GET_KEY_VALUE] !== undefined) readToOneCQNCopy[GET_KEY_VALUE] = readToOneCQN[GET_KEY_VALUE]
|
|
26
28
|
if (readToOneCQN[TO_MANY] !== undefined) readToOneCQNCopy[TO_MANY] = readToOneCQN[TO_MANY]
|
|
27
29
|
if (readToOneCQN[TO_ACTIVE] !== undefined) readToOneCQNCopy[TO_ACTIVE] = readToOneCQN[TO_ACTIVE]
|
|
@@ -2,6 +2,9 @@ const { hasExpand, createJoinCQNFromExpanded } = require('./expandCQNToJoin')
|
|
|
2
2
|
const rawToExpanded = require('./rawToExpanded')
|
|
3
3
|
const expandV2 = require('./expand-v2')
|
|
4
4
|
|
|
5
|
+
/*
|
|
6
|
+
* this module is required by cds-pg. -> in case of incompatible changes, we should let them know.
|
|
7
|
+
*/
|
|
5
8
|
module.exports = {
|
|
6
9
|
hasExpand,
|
|
7
10
|
createJoinCQNFromExpanded,
|
|
@@ -15,16 +15,6 @@ module.exports = async function (req) {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
try {
|
|
18
|
-
// REVISIT: should be handled in protocol adapter
|
|
19
|
-
// execute validation query first to fail early
|
|
20
|
-
if (req.query._validationQuery) {
|
|
21
|
-
const validationResult = await this._read(this.model, this.dbc, req.query._validationQuery, req)
|
|
22
|
-
|
|
23
|
-
if (validationResult.length === 0) {
|
|
24
|
-
// > validation target (e.g., root of navigation) doesn't exist
|
|
25
|
-
req.reject(404)
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
18
|
const results = await this._insert(this.model, this.dbc, req.query, req)
|
|
29
19
|
return new InsertResult(req, results)
|
|
30
20
|
} catch (err) {
|
|
@@ -12,6 +12,9 @@ const DELETE = require('./delete')
|
|
|
12
12
|
const structured = require('./structured')
|
|
13
13
|
const arrayed = require('./arrayed')
|
|
14
14
|
|
|
15
|
+
/*
|
|
16
|
+
* this module is required by cds-pg. -> in case of incompatible changes, we should let them know.
|
|
17
|
+
*/
|
|
15
18
|
module.exports = {
|
|
16
19
|
rewrite,
|
|
17
20
|
virtual,
|
|
@@ -7,32 +7,10 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @param req - cds.Request
|
|
9
9
|
*/
|
|
10
|
-
module.exports =
|
|
10
|
+
module.exports = function (req) {
|
|
11
11
|
if (typeof req.query === 'string') {
|
|
12
12
|
return this._execute.sql(this.dbc, req.query, req.data)
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
// execute validation query first to fail early
|
|
17
|
-
if (req.query._validationQuery) {
|
|
18
|
-
const validationResult = await this._read(this.model, this.dbc, req.query._validationQuery, req)
|
|
19
|
-
|
|
20
|
-
if (validationResult.length === 0) {
|
|
21
|
-
// > validation target (e.g., root of navigation) doesn't exist
|
|
22
|
-
req.reject(404)
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const result = await this._read(this.model, this.dbc, req.query, req)
|
|
27
|
-
|
|
28
|
-
if (
|
|
29
|
-
req.query._validationQuery &&
|
|
30
|
-
req.query._validationQuery.__navToManyWithKeys &&
|
|
31
|
-
(!result || result.length === 0)
|
|
32
|
-
) {
|
|
33
|
-
// > navigation to collection with key specified without result -> 404
|
|
34
|
-
req.reject(404)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return result
|
|
15
|
+
return this._read(this.model, this.dbc, req.query, req)
|
|
38
16
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const { cqn2cqn4sql } = require('../../common/utils/cqn2cqn4sql')
|
|
2
|
-
const generateAliases = require('../utils/generateAliases')
|
|
2
|
+
const { generateAliases } = require('../utils/generateAliases')
|
|
3
3
|
const { restoreLink } = require('../../common/utils/resolveView')
|
|
4
4
|
|
|
5
5
|
const _isLinked = req => {
|
|
@@ -18,7 +18,6 @@ function handler(req) {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
const streaming = req.query._streaming
|
|
21
|
-
const validationQuery = req.query._validationQuery
|
|
22
21
|
|
|
23
22
|
// for restore link to req.data
|
|
24
23
|
const linked = _isLinked(req)
|
|
@@ -31,7 +30,6 @@ function handler(req) {
|
|
|
31
30
|
if (linked) restoreLink(req)
|
|
32
31
|
|
|
33
32
|
if (streaming) req.query._streaming = streaming
|
|
34
|
-
if (validationQuery) req.query._validationQuery = validationQuery
|
|
35
33
|
|
|
36
34
|
generateAliases(req.query)
|
|
37
35
|
}
|
|
@@ -70,7 +70,7 @@ module.exports = async function (req) {
|
|
|
70
70
|
if (onlyKeysRemain(req)) return
|
|
71
71
|
|
|
72
72
|
try {
|
|
73
|
-
const result = await this._update(this.model, this.dbc, req
|
|
73
|
+
const result = await this._update(this.model, this.dbc, req)
|
|
74
74
|
return result
|
|
75
75
|
} catch (err) {
|
|
76
76
|
// REVISIT: db specifics
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
const { getFlatArray, processCQNs } = require('../utils/deep')
|
|
2
2
|
const { timestampToISO } = require('../data-conversion/timestamp')
|
|
3
|
-
const { hasDeepDelete, getDeepDeleteCQNs } = require('../../common/composition')
|
|
3
|
+
const { hasDeepDelete, getDeepDeleteCQNs, getSetNullParentForeignKeyCQNs } = require('../../common/composition')
|
|
4
4
|
|
|
5
|
-
const deleteFn = executeDeleteCQN => async (model, dbc, query, req) => {
|
|
5
|
+
const deleteFn = (executeDeleteCQN, executeUpdateCQN) => async (model, dbc, query, req) => {
|
|
6
6
|
const { user, locale, timestamp } = req
|
|
7
7
|
const isoTs = timestampToISO(timestamp)
|
|
8
8
|
|
|
9
9
|
let result
|
|
10
|
-
if (hasDeepDelete(model
|
|
11
|
-
let cqns = getDeepDeleteCQNs(model
|
|
10
|
+
if (hasDeepDelete(model, query)) {
|
|
11
|
+
let cqns = await getDeepDeleteCQNs(model, req)
|
|
12
12
|
|
|
13
13
|
// the delete chunks, i.e., how many deletes can be processed in parallel
|
|
14
14
|
const chunks = []
|
|
@@ -25,6 +25,12 @@ const deleteFn = executeDeleteCQN => async (model, dbc, query, req) => {
|
|
|
25
25
|
result = results[results.length - 1]
|
|
26
26
|
} else {
|
|
27
27
|
result = await executeDeleteCQN(model, dbc, query, user, locale, isoTs)
|
|
28
|
+
if (result) {
|
|
29
|
+
const updateCQNs = await getSetNullParentForeignKeyCQNs(model, req)
|
|
30
|
+
for (const cqn of updateCQNs) {
|
|
31
|
+
await executeUpdateCQN(model, dbc, cqn, user, locale, isoTs)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
28
34
|
}
|
|
29
35
|
|
|
30
36
|
return result
|
|
@@ -6,8 +6,8 @@ const insert = executeInsertCQN => async (model, dbc, query, req) => {
|
|
|
6
6
|
const { user, locale, timestamp } = req
|
|
7
7
|
const isoTs = timestampToISO(timestamp)
|
|
8
8
|
|
|
9
|
-
if (hasDeepInsert(model
|
|
10
|
-
const cqns = getFlatArray(getDeepInsertCQNs(model
|
|
9
|
+
if (hasDeepInsert(model, query)) {
|
|
10
|
+
const cqns = getFlatArray(getDeepInsertCQNs(model, query))
|
|
11
11
|
|
|
12
12
|
// return array of individual results
|
|
13
13
|
if (cqns.length === 0) return []
|
|
@@ -15,7 +15,7 @@ const insert = executeInsertCQN => async (model, dbc, query, req) => {
|
|
|
15
15
|
return getFlatArray(results)
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
cleanEmptyCompositionsOfMany(model
|
|
18
|
+
cleanEmptyCompositionsOfMany(model, query)
|
|
19
19
|
return executeInsertCQN(model, dbc, query, user, locale, isoTs)
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const { timestampToISO } = require('../data-conversion/timestamp')
|
|
2
2
|
|
|
3
|
+
const { deepCopyObject } = require('../../common/utils/copy')
|
|
4
|
+
|
|
3
5
|
function _arrayWithCount(a, count) {
|
|
4
6
|
const _map = a.map
|
|
5
7
|
const map = (..._) => _arrayWithCount(_map.call(a, ..._), count)
|
|
@@ -10,7 +12,8 @@ function _arrayWithCount(a, count) {
|
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
function _createCountQuery(query) {
|
|
13
|
-
|
|
15
|
+
// REVISIT: Use query.clone() instead
|
|
16
|
+
let _query = { SELECT: deepCopyObject(query.SELECT) }
|
|
14
17
|
delete _query.SELECT.orderBy // not necessary to keep that
|
|
15
18
|
delete _query.SELECT.limit
|
|
16
19
|
// Also change columns in sub queries
|
|
@@ -50,11 +50,11 @@ const _getFilteredCqns = (cqns, model) => {
|
|
|
50
50
|
return cqns
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
const update =
|
|
54
|
-
const { user, locale, timestamp } = req
|
|
53
|
+
const update = executeUpdateCQN => async (model, dbc, req) => {
|
|
54
|
+
const { query, user, locale, timestamp } = req
|
|
55
55
|
const isoTs = timestampToISO(timestamp)
|
|
56
56
|
|
|
57
|
-
if (hasDeepUpdate(model
|
|
57
|
+
if (hasDeepUpdate(model, query)) {
|
|
58
58
|
// REVISIT: _activeData gets set in case of draftActivate for performance, but this is a layer violation
|
|
59
59
|
let selectData = req._ && req._.query && req._.query._activeData
|
|
60
60
|
|
|
@@ -62,10 +62,10 @@ const update = (executeUpdateCQN, executeSelectCQN) => async (model, dbc, query,
|
|
|
62
62
|
selectData = [selectData]
|
|
63
63
|
} else {
|
|
64
64
|
// REVISIT: avoid additional read
|
|
65
|
-
selectData = await selectDeepUpdateData(
|
|
65
|
+
selectData = await selectDeepUpdateData(cds.db, model, req)
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
let cqns = getDeepUpdateCQNs(model
|
|
68
|
+
let cqns = await getDeepUpdateCQNs(model, req, selectData)
|
|
69
69
|
|
|
70
70
|
// the delete chunks, i.e., how many deletes can be processed in parallel
|
|
71
71
|
const chunks = []
|
|
@@ -11,6 +11,10 @@ function _fillAfterDot(val) {
|
|
|
11
11
|
return `${beforeDot}.${afterDot.padEnd(3, '0')}`
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
function _valButNoBuffer(arg) {
|
|
15
|
+
return arg && arg.val && typeof arg.val === 'object' && !(arg.val instanceof Buffer)
|
|
16
|
+
}
|
|
17
|
+
|
|
14
18
|
/**
|
|
15
19
|
* ExpressionBuilder is used to take a part of a CQN object as an input and to build an object representing an expression
|
|
16
20
|
* with SQL string and values to be used with a prepared statement.
|
|
@@ -87,9 +91,9 @@ class ExpressionBuilder extends BaseBuilder {
|
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
_isStructured(op1, comp, op2) {
|
|
90
|
-
if (op1.ref && comp === '=' &&
|
|
94
|
+
if (op1.ref && comp === '=' && _valButNoBuffer(op2)) return true
|
|
91
95
|
// also check reverse
|
|
92
|
-
if (op1
|
|
96
|
+
if (_valButNoBuffer(op1) && comp === '=' && op2.ref) return true
|
|
93
97
|
}
|
|
94
98
|
|
|
95
99
|
_expressionObjectsToSQL(objects) {
|
|
@@ -379,4 +383,7 @@ class ExpressionBuilder extends BaseBuilder {
|
|
|
379
383
|
}
|
|
380
384
|
}
|
|
381
385
|
|
|
386
|
+
/*
|
|
387
|
+
* this module is required by cds-pg. -> in case of incompatible changes, we should let them know.
|
|
388
|
+
*/
|
|
382
389
|
module.exports = ExpressionBuilder
|
|
@@ -9,6 +9,9 @@ const ReferenceBuilder = require('./ReferenceBuilder')
|
|
|
9
9
|
const FunctionBuilder = require('./FunctionBuilder')
|
|
10
10
|
const sqlFactory = require('./sqlFactory')
|
|
11
11
|
|
|
12
|
+
/*
|
|
13
|
+
* this module is required by cds-pg. -> in case of incompatible changes, we should let them know.
|
|
14
|
+
*/
|
|
12
15
|
module.exports = {
|
|
13
16
|
CreateBuilder,
|
|
14
17
|
DropBuilder,
|
|
@@ -11,7 +11,7 @@ const { DRAFT_COLUMNS } = require('../../common/constants/draft')
|
|
|
11
11
|
* @param entity - the csn entity
|
|
12
12
|
* @returns {Array} - array of columns
|
|
13
13
|
*/
|
|
14
|
-
const getColumns = (entity, {
|
|
14
|
+
const getColumns = (entity, { _4db, onlyKeys } = { _4db: true, onlyKeys: false }) => {
|
|
15
15
|
// REVISIT is this correct or just a problem that occurs because of new structure we do not deal with yet?
|
|
16
16
|
if (!(entity && entity.elements)) return []
|
|
17
17
|
const columnNames = []
|
|
@@ -21,7 +21,7 @@ const getColumns = (entity, { db, onlyKeys } = { db: true, onlyKeys: false }) =>
|
|
|
21
21
|
const element = elements[elementName]
|
|
22
22
|
if (onlyKeys && !element.key) continue
|
|
23
23
|
if (element.isAssociation) continue
|
|
24
|
-
if (
|
|
24
|
+
if (_4db && entity._isDraftEnabled && DRAFT_COLUMNS.includes(elementName)) continue
|
|
25
25
|
if (cds.env.effective.odata.structs && element.elements) {
|
|
26
26
|
columnNames.push(...resolveStructured({ structName: elementName, structProperties: [] }, element.elements, false))
|
|
27
27
|
continue
|
|
@@ -31,4 +31,7 @@ const getColumns = (entity, { db, onlyKeys } = { db: true, onlyKeys: false }) =>
|
|
|
31
31
|
return columnNames.map(name => elements[name] || { name })
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/*
|
|
35
|
+
* this module is required by cds-pg. -> in case of incompatible changes, we should let them know.
|
|
36
|
+
*/
|
|
34
37
|
module.exports = getColumns
|
|
@@ -28,20 +28,14 @@ async function processCQNs(processFn, cqns, model, dbc, user, locale, ts, chunks
|
|
|
28
28
|
const results = new Array(cqns.length)
|
|
29
29
|
|
|
30
30
|
const deletes = []
|
|
31
|
-
const
|
|
31
|
+
const updatesForDeletes = []
|
|
32
32
|
const others = []
|
|
33
33
|
for (let i = 0; i < cqns.length; i++) {
|
|
34
34
|
if (cqns[i].DELETE) deletes.push(i)
|
|
35
|
-
else if (cqns[i].
|
|
35
|
+
else if (cqns[i].__4delete) updatesForDeletes.push(i)
|
|
36
36
|
else others.push(i)
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
// UPDATEs to SET null parent's foreign keys of one compositions
|
|
40
|
-
// which are otherwise violate foreign key constraints
|
|
41
|
-
if (updatesBeforeDelete.length) {
|
|
42
|
-
await _processChunk(processFn, model, dbc, cqns, user, locale, ts, updatesBeforeDelete, results)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
39
|
if (deletes.length > 0) {
|
|
46
40
|
if (chunks) {
|
|
47
41
|
let offset = 0
|
|
@@ -55,6 +49,10 @@ async function processCQNs(processFn, cqns, model, dbc, user, locale, ts, chunks
|
|
|
55
49
|
}
|
|
56
50
|
}
|
|
57
51
|
|
|
52
|
+
if (updatesForDeletes.length > 0) {
|
|
53
|
+
await _processChunk(processFn, model, dbc, cqns, user, locale, ts, updatesForDeletes, results)
|
|
54
|
+
}
|
|
55
|
+
|
|
58
56
|
if (others.length > 0) {
|
|
59
57
|
await _processChunk(processFn, model, dbc, cqns, user, locale, ts, others, results)
|
|
60
58
|
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
const { ensureUnlocalized } = require('../../common/utils/draft')
|
|
2
|
-
|
|
3
2
|
const ALIAS_PREFIX = 'ALIAS_'
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
const PARENT_ALIAS = '_parent_'
|
|
4
|
+
const FOREIGN_ALIAS = '_foreign_'
|
|
5
|
+
const PARENT_ALIAS_REGEX = new RegExp('^' + PARENT_ALIAS + '\\d*$')
|
|
6
|
+
const cleanUpName = name => ensureUnlocalized(name).replace(/\./g, '_')
|
|
8
7
|
|
|
9
8
|
const _redirectXpr = (xpr, aliasMap) => {
|
|
10
9
|
if (!xpr) return
|
|
@@ -97,4 +96,55 @@ const generateAliases = query => {
|
|
|
97
96
|
_generateAliases(query)
|
|
98
97
|
}
|
|
99
98
|
|
|
100
|
-
|
|
99
|
+
const _addParentAlias = (where, alias) => {
|
|
100
|
+
where.forEach(e => {
|
|
101
|
+
if (e.ref && e.ref[0].match(PARENT_ALIAS_REGEX)) {
|
|
102
|
+
e.ref[0] = alias
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const _addAliasToElement = (expr, alias) => {
|
|
108
|
+
if (expr.ref) {
|
|
109
|
+
if (typeof alias === 'function') {
|
|
110
|
+
alias = alias(expr.ref)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return { ref: [alias, ...expr.ref] }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (expr.list) {
|
|
117
|
+
return { list: expr.list.map(arg => _addAliasToElement(arg, alias)) }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (expr.func) {
|
|
121
|
+
const args = expr.args.map(arg => _addAliasToElement(arg, alias))
|
|
122
|
+
return { ...expr, args }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (expr.SELECT && expr.SELECT.where) {
|
|
126
|
+
// special case in lambda functions
|
|
127
|
+
_addParentAlias(expr.SELECT.where, alias)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (expr.xpr) {
|
|
131
|
+
return { xpr: expr.xpr.map(xpr => _addAliasToElement(xpr, alias)) }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return expr
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const addAliasToExpression = (expression, alias) => {
|
|
138
|
+
if (expression && alias) {
|
|
139
|
+
return expression.map(expr => _addAliasToElement(expr, alias))
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return expression
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
module.exports = {
|
|
146
|
+
generateAliases,
|
|
147
|
+
addAliasToExpression,
|
|
148
|
+
PARENT_ALIAS,
|
|
149
|
+
FOREIGN_ALIAS
|
|
150
|
+
}
|