@sap/cds 5.7.2 → 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 +108 -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/minify.js +1 -1
- 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 -38
- 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 -42
- 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/ExpressionToCQN.js +18 -8
- 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 +7 -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 +21 -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 +5 -8
- 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 +18 -5
- 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 +80 -21
- 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/Service.js +1 -1
- 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 +3 -0
- package/libx/_runtime/common/utils/cqn.js +2 -6
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +97 -123
- 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/structured.js +1 -1
- package/libx/_runtime/common/utils/vcap.js +27 -10
- package/libx/_runtime/db/data-conversion/post-processing.js +42 -35
- package/libx/_runtime/db/expand/expand-v2.js +21 -12
- package/libx/_runtime/db/expand/expandCQNToJoin.js +27 -29
- 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 +15 -8
- 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 +22 -17
- 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/localized.js +1 -1
- 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 +4 -2
- 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/message-queuing-utils/options-messaging.js +1 -0
- package/libx/_runtime/messaging/service.js +16 -7
- package/libx/_runtime/remote/utils/client.js +33 -20
- package/libx/_runtime/remote/utils/data.js +53 -12
- package/libx/_runtime/sqlite/Service.js +1 -1
- package/libx/_runtime/sqlite/conversion.js +10 -0
- package/libx/_runtime/sqlite/localized.js +1 -1
- 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 +101 -45
- package/libx/odata/index.js +7 -1
- 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
|
@@ -152,9 +152,15 @@ const _newColumns = (columns = [], transition, service, withAlias = false) => {
|
|
|
152
152
|
const newColumns = []
|
|
153
153
|
|
|
154
154
|
columns.forEach(column => {
|
|
155
|
+
let newColumn
|
|
156
|
+
if (column.func) {
|
|
157
|
+
newColumn = { ...column }
|
|
158
|
+
newColumn.args = _newColumns(column.args, transition, service, withAlias)
|
|
159
|
+
newColumns.push(newColumn)
|
|
160
|
+
return newColumns
|
|
161
|
+
}
|
|
155
162
|
const mapped = column.ref && transition.mapping.get(column.ref[0])
|
|
156
163
|
|
|
157
|
-
let newColumn
|
|
158
164
|
if (mapped && mapped.ref) {
|
|
159
165
|
newColumn = { ...column }
|
|
160
166
|
|
|
@@ -238,7 +244,7 @@ const _newEntries = (entries = [], transition, service) =>
|
|
|
238
244
|
const _newWhere = (where = [], transition, tableName, alias, isSubselect = false) => {
|
|
239
245
|
const newWhere = where.map(whereElement => {
|
|
240
246
|
const newWhereElement = { ...whereElement }
|
|
241
|
-
if (!whereElement.ref && !whereElement.SELECT) return whereElement
|
|
247
|
+
if (!whereElement.ref && !whereElement.SELECT && !whereElement.func) return whereElement
|
|
242
248
|
if (whereElement.SELECT && whereElement.SELECT.where) {
|
|
243
249
|
newWhereElement.SELECT.where = _newWhere(whereElement.SELECT.where, transition, tableName, alias, true)
|
|
244
250
|
return newWhereElement
|
|
@@ -246,6 +252,9 @@ const _newWhere = (where = [], transition, tableName, alias, isSubselect = false
|
|
|
246
252
|
if (newWhereElement.ref) {
|
|
247
253
|
_newWhereRef(newWhereElement, transition, alias, tableName, isSubselect)
|
|
248
254
|
return newWhereElement
|
|
255
|
+
} else if (newWhereElement.func) {
|
|
256
|
+
newWhereElement.args = _newWhere(newWhereElement.args, transition, tableName, alias)
|
|
257
|
+
return newWhereElement
|
|
249
258
|
} else {
|
|
250
259
|
return whereElement
|
|
251
260
|
}
|
|
@@ -337,8 +346,7 @@ const _newSelect = (query, transitions, service) => {
|
|
|
337
346
|
}
|
|
338
347
|
if (!newSelect.columns && targetTransition.mapping.size) newSelect.columns = _initialColumns(targetTransition)
|
|
339
348
|
if (newSelect.columns) {
|
|
340
|
-
|
|
341
|
-
rewriteAsterisks({ SELECT: query.SELECT }, service.model, isDB)
|
|
349
|
+
rewriteAsterisks({ SELECT: query.SELECT }, service.model, { _4db: service instanceof cds.DatabaseService })
|
|
342
350
|
newSelect.columns = _newColumns(newSelect.columns, targetTransition, service, service.kind !== 'app-service')
|
|
343
351
|
}
|
|
344
352
|
if (newSelect.having) newSelect.having = _newColumns(newSelect.having, targetTransition)
|
|
@@ -30,10 +30,11 @@ const _cqlDraftColumns = target => {
|
|
|
30
30
|
]
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
const _expandColumn = (column, target,
|
|
33
|
+
const _expandColumn = (column, target, _4db) => {
|
|
34
34
|
if (!(column.ref && column.expand)) return
|
|
35
35
|
const nextTarget = getNavigationIfStruct(target, column.ref)
|
|
36
|
-
if (nextTarget && nextTarget._target && nextTarget._target.elements)
|
|
36
|
+
if (nextTarget && nextTarget._target && nextTarget._target.elements)
|
|
37
|
+
_rewriteAsterisks(column, nextTarget._target, _4db)
|
|
37
38
|
return column
|
|
38
39
|
}
|
|
39
40
|
|
|
@@ -46,32 +47,33 @@ const rewriteExpandAsterisk = (columns, target) => {
|
|
|
46
47
|
const { expand } = columns.splice(expandAllColIdx, 1)[0]
|
|
47
48
|
for (const elName in target.elements) {
|
|
48
49
|
if (target.elements[elName]._target && !columns.find(col => col.expand && col.ref && col.ref[0] === elName)) {
|
|
50
|
+
if (elName === 'SiblingEntity') continue
|
|
49
51
|
columns.push({ ref: [elName], expand: [...expand] })
|
|
50
52
|
}
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
55
|
}
|
|
54
56
|
|
|
55
|
-
const _rewriteAsterisk = (columns, target,
|
|
57
|
+
const _rewriteAsterisk = (columns, target, _4db, isRoot) => {
|
|
56
58
|
const asteriskColumnIndex = columns.findIndex(col => isAsteriskColumn(col))
|
|
57
59
|
if (asteriskColumnIndex > -1) {
|
|
58
60
|
columns.splice(
|
|
59
61
|
asteriskColumnIndex,
|
|
60
62
|
1,
|
|
61
|
-
...getColumns(target, {
|
|
63
|
+
...getColumns(target, { _4db })
|
|
62
64
|
.map(c => ({ ref: [c.name] }))
|
|
63
65
|
.filter(c => !columns.find(_isDuplicate(c)) && (isRoot || c.ref[0] !== 'DraftAdministrativeData_DraftUUID'))
|
|
64
66
|
)
|
|
65
67
|
}
|
|
66
68
|
}
|
|
67
69
|
|
|
68
|
-
const _rewriteAsterisks = (cqn, target,
|
|
70
|
+
const _rewriteAsterisks = (cqn, target, _4db, isRoot) => {
|
|
69
71
|
if (cqn.expand === '*') cqn.expand = ['*']
|
|
70
72
|
const columns = cqn.expand || cqn.columns
|
|
71
|
-
_rewriteAsterisk(columns, target,
|
|
73
|
+
_rewriteAsterisk(columns, target, _4db, isRoot)
|
|
72
74
|
rewriteExpandAsterisk(columns, target)
|
|
73
75
|
for (const column of columns) {
|
|
74
|
-
_expandColumn(column, target,
|
|
76
|
+
_expandColumn(column, target, _4db)
|
|
75
77
|
}
|
|
76
78
|
return columns
|
|
77
79
|
}
|
|
@@ -83,9 +85,17 @@ const _targetOfQueryIfNotDraft = (query, model) => {
|
|
|
83
85
|
return target
|
|
84
86
|
}
|
|
85
87
|
|
|
86
|
-
const rewriteAsterisks = (query, model,
|
|
88
|
+
const rewriteAsterisks = (query, model, options) => {
|
|
89
|
+
/*
|
|
90
|
+
* REVISIT:
|
|
91
|
+
* - _4db: called on db level
|
|
92
|
+
* - _4fiori: cqn2cqn4sql called in a fiori handler
|
|
93
|
+
* this is extremely obfuscated!
|
|
94
|
+
*/
|
|
95
|
+
const { _4db, _4fiori } = options
|
|
96
|
+
|
|
87
97
|
if (!query.SELECT.columns || !query.SELECT.columns.length) {
|
|
88
|
-
if (
|
|
98
|
+
if (_4db || _4fiori) {
|
|
89
99
|
if (
|
|
90
100
|
query.SELECT.from.SET &&
|
|
91
101
|
query.SELECT.from.SET.args[0] &&
|
|
@@ -101,7 +111,7 @@ const rewriteAsterisks = (query, model, db = false, isDraft = false, onlyKeys =
|
|
|
101
111
|
for (const arg of query.SELECT.from.args) {
|
|
102
112
|
const _targetName = arg.ref[0].id || arg.ref[0]
|
|
103
113
|
const _target = model.definitions[ensureNoDraftsSuffix(_targetName)]
|
|
104
|
-
const columns = getColumns(_target, {
|
|
114
|
+
const columns = getColumns(_target, { _4db })
|
|
105
115
|
.filter(
|
|
106
116
|
c =>
|
|
107
117
|
!query.SELECT.columns.some(
|
|
@@ -116,16 +126,20 @@ const rewriteAsterisks = (query, model, db = false, isDraft = false, onlyKeys =
|
|
|
116
126
|
} else {
|
|
117
127
|
const target = _targetOfQueryIfNotDraft(query, model)
|
|
118
128
|
if (!target) return
|
|
119
|
-
|
|
120
|
-
|
|
129
|
+
|
|
130
|
+
query.SELECT.columns = getColumns(target, { _4db }).map(col => ({ ref: [col.name] }))
|
|
131
|
+
if (_4db && target._isDraftEnabled) query.SELECT.columns.push(..._cqlDraftColumns(target))
|
|
121
132
|
}
|
|
122
133
|
}
|
|
134
|
+
|
|
123
135
|
return
|
|
124
136
|
}
|
|
137
|
+
|
|
125
138
|
const target = _targetOfQueryIfNotDraft(query, model)
|
|
126
139
|
if (!target) return
|
|
140
|
+
|
|
127
141
|
// REVISIT: Also support JOINs/SETs here
|
|
128
|
-
query.SELECT.columns = _rewriteAsterisks(query.SELECT, target,
|
|
142
|
+
query.SELECT.columns = _rewriteAsterisks(query.SELECT, target, _4db, true)
|
|
129
143
|
}
|
|
130
144
|
|
|
131
145
|
module.exports = {
|
|
@@ -18,8 +18,8 @@ const _search2cqn4sql = (query, model, options = {}) => {
|
|
|
18
18
|
|
|
19
19
|
// Call custom (optimized search to cqn for sql implementation) that tries
|
|
20
20
|
// to optimize the search behavior for a specific database service.
|
|
21
|
-
//
|
|
22
|
-
if (typeof search2cqn4sql === 'function' && !query.SELECT.
|
|
21
|
+
// REVISIT: $search query option combined with $count is not currently optimized
|
|
22
|
+
if (typeof search2cqn4sql === 'function' && !query.SELECT.count) {
|
|
23
23
|
const search2cqnOptions = { columns, locale: options.locale }
|
|
24
24
|
return search2cqn4sql(query, entity, search2cqnOptions)
|
|
25
25
|
}
|
|
@@ -30,9 +30,14 @@ const _search2cqn4sql = (query, model, options = {}) => {
|
|
|
30
30
|
query._aggregated || /* if new parser */ query.SELECT.groupBy ? query.having(expression) : query.where(expression)
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
const search2cqn4sql = (query, model, options) => {
|
|
34
|
+
if (query.SELECT.from.SET) {
|
|
35
|
+
return query.SELECT.from.SET.args.forEach(arg => _search2cqn4sql(arg, model, options))
|
|
36
|
+
}
|
|
37
|
+
|
|
37
38
|
return _search2cqn4sql(query, model, options)
|
|
38
39
|
}
|
|
40
|
+
|
|
41
|
+
// convert $search system query option to WHERE/HAVING clause using
|
|
42
|
+
// the operator LIKE or CONTAINS
|
|
43
|
+
module.exports = search2cqn4sql
|
|
@@ -284,7 +284,7 @@ const flattenStructuredSelect = ({ SELECT }, model) => {
|
|
|
284
284
|
_flattenColumns(SELECT, flattenedElements, toBeDeleted, entity)
|
|
285
285
|
SELECT.columns = SELECT.columns.filter(column => {
|
|
286
286
|
const columnName = column.ref ? column.ref[0] : column.as
|
|
287
|
-
return (columnName && !toBeDeleted.includes(columnName)) || column.func || column.expand
|
|
287
|
+
return (columnName && !toBeDeleted.includes(columnName)) || column.func || column.expand || 'val' in column
|
|
288
288
|
})
|
|
289
289
|
if (flattenedElements.length) SELECT.columns.push(...flattenedElements)
|
|
290
290
|
}
|
|
@@ -1,11 +1,28 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
const cds = require('../../../../libx/_runtime/cds')
|
|
2
|
+
|
|
3
|
+
const getAppMetadata = () => {
|
|
4
|
+
const appMetadata = cds.env.app
|
|
5
|
+
|
|
6
|
+
if (appMetadata) {
|
|
7
|
+
return {
|
|
8
|
+
appID: appMetadata.id,
|
|
9
|
+
appName: appMetadata.name,
|
|
10
|
+
appURL: appMetadata.url
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// fallback: if the app metadata is undefined, then extract the metadata from the underlying environment (CF/Kyma/...)
|
|
15
|
+
const vcapApplication = process.env.VCAP_APPLICATION && JSON.parse(process.env.VCAP_APPLICATION)
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
appID: vcapApplication && vcapApplication.application_id,
|
|
19
|
+
appName: vcapApplication && vcapApplication.application_name,
|
|
20
|
+
appURL:
|
|
21
|
+
vcapApplication &&
|
|
22
|
+
vcapApplication.application_uris &&
|
|
23
|
+
vcapApplication.application_uris[0] &&
|
|
24
|
+
`https://${vcapApplication.application_uris[0].replace(/^https?:\/\//, '')}`
|
|
25
|
+
}
|
|
11
26
|
}
|
|
27
|
+
|
|
28
|
+
module.exports = getAppMetadata()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const cds = require('../../cds')
|
|
2
2
|
|
|
3
|
-
const { ensureUnlocalized } = require('../../common/utils/draft')
|
|
3
|
+
const { ensureUnlocalized, ensureNoDraftsSuffix } = require('../../common/utils/draft')
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Check if the value is a function or reference to private function.
|
|
@@ -48,49 +48,50 @@ const _getEntityName = (csn, from) => {
|
|
|
48
48
|
|
|
49
49
|
const _refs = (refs, as) => {
|
|
50
50
|
const arr = []
|
|
51
|
-
const hasOwnProperty = Object.prototype.hasOwnProperty
|
|
52
|
-
|
|
53
51
|
for (const element of refs) {
|
|
54
52
|
// multiple join are nested, so we need to find all the table names in there as well
|
|
55
|
-
if (hasOwnProperty.call(element, 'join')) {
|
|
53
|
+
if (Object.prototype.hasOwnProperty.call(element, 'join')) {
|
|
56
54
|
arr.push(..._extractRefs(element))
|
|
57
55
|
// Likely a union
|
|
58
|
-
} else if (hasOwnProperty.call(element, 'SELECT')) {
|
|
59
|
-
arr.push(..._extractRefs(element.SELECT.from, as))
|
|
56
|
+
} else if (Object.prototype.hasOwnProperty.call(element, 'SELECT')) {
|
|
57
|
+
arr.push(..._extractRefs(element.SELECT.from, element.as))
|
|
60
58
|
} else {
|
|
61
|
-
arr.push(element)
|
|
59
|
+
arr.push(..._extractRefs(element, as))
|
|
62
60
|
}
|
|
63
61
|
}
|
|
64
62
|
|
|
65
63
|
return arr
|
|
66
64
|
}
|
|
67
65
|
|
|
66
|
+
const _getActiveFromUnion = refs => {
|
|
67
|
+
if (refs.length !== 2) return
|
|
68
|
+
const [maybeDraft, maybeActive] = refs
|
|
69
|
+
if (ensureNoDraftsSuffix(maybeDraft.ref[0]) === maybeActive.ref[0]) return maybeActive
|
|
70
|
+
if (ensureNoDraftsSuffix(maybeActive.ref[0]) === maybeDraft.ref[0]) return maybeDraft
|
|
71
|
+
}
|
|
72
|
+
|
|
68
73
|
const _extractRefs = (from, as) => {
|
|
69
74
|
if (from.SELECT) {
|
|
70
|
-
return _extractRefs(from.SELECT.from, from.SELECT.as)
|
|
75
|
+
return _extractRefs(from.SELECT.from, as || from.SELECT.as)
|
|
71
76
|
}
|
|
72
|
-
|
|
73
|
-
const hasOwnProperty = Object.prototype.hasOwnProperty
|
|
74
|
-
|
|
75
|
-
if (hasOwnProperty.call(from, 'join')) {
|
|
77
|
+
if (Object.prototype.hasOwnProperty.call(from, 'join')) {
|
|
76
78
|
// cqn with join in from
|
|
77
79
|
return _refs(from.args)
|
|
78
80
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
if (Object.prototype.hasOwnProperty.call(from, 'SET')) {
|
|
82
|
+
let refs = _refs(from.SET.args).filter(a => !a.as || a.as !== 'filterAdmin')
|
|
83
|
+
refs = _getActiveFromUnion(refs) ? [_getActiveFromUnion(refs)] : refs
|
|
84
|
+
if (as) return refs.map(({ ref }) => ({ as, ref }))
|
|
85
|
+
return refs
|
|
82
86
|
}
|
|
83
|
-
|
|
84
|
-
const ref = { ref: from.ref
|
|
85
|
-
|
|
86
|
-
if (as) {
|
|
87
|
-
ref.as = as
|
|
88
|
-
}
|
|
89
|
-
|
|
87
|
+
if (!from.ref) return []
|
|
88
|
+
const ref = { ref: [...from.ref] }
|
|
89
|
+
if (as || from.as) ref.as = as || 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]
|
|
@@ -626,7 +628,7 @@ class JoinCQNFromExpanded {
|
|
|
626
628
|
// if union always only expand with active, otherwise evaluate flag
|
|
627
629
|
// if flag shows false, we check entity for associations to non draft
|
|
628
630
|
const activeTableRequired =
|
|
629
|
-
readToOneCQN[IS_UNION_DRAFT] ||
|
|
631
|
+
readToOneCQN[IS_UNION_DRAFT] || // > REVISIT: blocks expanding comp2one
|
|
630
632
|
readToOneCQN[IS_ACTIVE] ||
|
|
631
633
|
(element && element.type === 'cds.Association' && !element['@odata.draft.enclosed']) ||
|
|
632
634
|
!this._csn.definitions[target]._isDraftEnabled
|
|
@@ -665,9 +667,9 @@ class JoinCQNFromExpanded {
|
|
|
665
667
|
readToOneCQN.from.args[1] = {
|
|
666
668
|
SELECT: {
|
|
667
669
|
columns: cols,
|
|
668
|
-
from: unionFrom
|
|
669
|
-
|
|
670
|
-
|
|
670
|
+
from: unionFrom
|
|
671
|
+
},
|
|
672
|
+
as: tableAlias
|
|
671
673
|
}
|
|
672
674
|
}
|
|
673
675
|
|
|
@@ -755,9 +757,9 @@ class JoinCQNFromExpanded {
|
|
|
755
757
|
return {
|
|
756
758
|
SELECT: {
|
|
757
759
|
columns: Array.from(readToOneCQN.columns),
|
|
758
|
-
from: readToOneCQN.from
|
|
759
|
-
|
|
760
|
-
|
|
760
|
+
from: readToOneCQN.from
|
|
761
|
+
},
|
|
762
|
+
as: readToOneCQN.from.as
|
|
761
763
|
}
|
|
762
764
|
}
|
|
763
765
|
|
|
@@ -858,18 +860,18 @@ class JoinCQNFromExpanded {
|
|
|
858
860
|
|
|
859
861
|
if (arg.args) {
|
|
860
862
|
this._addJoinKeyColumnsToUnion(arg.args, on, parentAlias)
|
|
861
|
-
} else if (arg.SELECT.from.SET && arg.SELECT.as === parentAlias) {
|
|
862
|
-
|
|
863
|
+
} else if (arg.SELECT.from.SET && (arg.as === parentAlias || arg.SELECT.from.as === parentAlias)) {
|
|
864
|
+
for (const _arg of arg.SELECT.from.SET.args) {
|
|
865
|
+
this._addColumns(_arg.SELECT.columns, on, parentAlias)
|
|
866
|
+
}
|
|
867
|
+
if (arg.SELECT.columns) {
|
|
868
|
+
this._addColumns(arg.SELECT.columns, on, parentAlias, true)
|
|
869
|
+
}
|
|
863
870
|
}
|
|
864
871
|
}
|
|
865
872
|
}
|
|
866
873
|
|
|
867
|
-
_addColumns(
|
|
868
|
-
const [
|
|
869
|
-
{
|
|
870
|
-
SELECT: { columns }
|
|
871
|
-
}
|
|
872
|
-
] = args
|
|
874
|
+
_addColumns(columns, on, parentAlias, withAlias = false) {
|
|
873
875
|
const keyColumns = on
|
|
874
876
|
.filter(entry => {
|
|
875
877
|
return (
|
|
@@ -878,15 +880,11 @@ class JoinCQNFromExpanded {
|
|
|
878
880
|
!columns.some(column => column.ref && column.ref[column.ref.length - 1] === entry.ref[1])
|
|
879
881
|
)
|
|
880
882
|
})
|
|
881
|
-
.map(entry =>
|
|
882
|
-
|
|
883
|
+
.map(entry =>
|
|
884
|
+
withAlias ? { ref: [parentAlias, entry.ref[1]], as: `${parentAlias}_${entry.ref[1]}` } : { ref: [entry.ref[1]] }
|
|
885
|
+
)
|
|
883
886
|
if (keyColumns.length === 0) return
|
|
884
|
-
|
|
885
|
-
for (const {
|
|
886
|
-
SELECT: { columns }
|
|
887
|
-
} of args) {
|
|
888
|
-
columns.push(...keyColumns)
|
|
889
|
-
}
|
|
887
|
+
columns.push(...keyColumns)
|
|
890
888
|
}
|
|
891
889
|
|
|
892
890
|
/**
|
|
@@ -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,
|