@sap/cds 5.5.5 → 5.6.3
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 +139 -1
- package/apis/services.d.ts +31 -1
- package/app/index.js +22 -11
- package/bin/build/buildTaskFactory.js +1 -1
- package/bin/build/provider/buildTaskProviderInternal.js +1 -1
- package/bin/build/provider/fiori/index.js +1 -1
- package/bin/build/provider/hana/2migration.js +8 -7
- package/bin/build/provider/java-cf/index.js +1 -1
- package/bin/deploy/to-hana/hana.js +1 -17
- package/common.cds +8 -0
- package/lib/compile/to/sql.js +22 -2
- package/lib/connect/bindings.js +2 -1
- package/lib/core/reflect.js +4 -1
- package/lib/env/index.js +180 -42
- package/lib/env/requires.js +16 -1
- package/lib/i18n/localize.js +33 -5
- package/lib/index.js +3 -3
- package/lib/log/format/kibana.js +6 -2
- package/lib/ql/Query.js +1 -0
- package/lib/ql/SELECT.js +15 -8
- package/lib/ql/Whereable.js +5 -0
- package/lib/req/context.js +13 -5
- package/lib/serve/Service-dispatch.js +8 -1
- package/lib/utils/axios.js +7 -0
- package/lib/utils/data.js +1 -1
- package/lib/utils/tests.js +1 -1
- package/libx/_runtime/audit/Service.js +18 -18
- package/libx/_runtime/audit/generic/personal/access.js +1 -1
- package/libx/_runtime/audit/generic/personal/modification.js +3 -2
- package/libx/_runtime/audit/generic/personal/utils.js +23 -63
- package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +4 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +37 -35
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +3 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +5 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +84 -34
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +12 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +9 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +8 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +13 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +11 -95
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +17 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/SetResponseHeadersCommand.js +1 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +6 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +3 -34
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +64 -31
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +10 -5
- package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +1 -1
- package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +1 -1
- package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +1 -3
- package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +20 -21
- package/libx/_runtime/cds-services/services/utils/columns.js +6 -1
- package/libx/_runtime/cds-services/services/utils/compareJson.js +1 -8
- package/libx/_runtime/cds-services/services/utils/differ.js +7 -26
- package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -4
- package/libx/_runtime/cds-services/util/assert.js +29 -13
- package/libx/_runtime/cds.js +2 -1
- package/libx/_runtime/common/aspects/Association.js +72 -0
- package/libx/_runtime/common/aspects/any.js +8 -45
- package/libx/_runtime/common/aspects/entity.js +0 -1
- package/libx/_runtime/common/aspects/relation.js +40 -0
- package/libx/_runtime/common/aspects/utils.js +73 -1
- package/libx/_runtime/common/auth/strategies/utils/uaa.js +10 -14
- package/libx/_runtime/common/composition/data.js +3 -2
- package/libx/_runtime/common/composition/delete.js +3 -1
- package/libx/_runtime/common/composition/tree.js +23 -18
- package/libx/_runtime/common/composition/update.js +9 -1
- package/libx/_runtime/common/composition/utils.js +34 -8
- package/libx/_runtime/common/error/frontend.js +6 -1
- package/libx/_runtime/common/generic/auth.js +5 -9
- package/libx/_runtime/common/generic/crud.js +2 -2
- package/libx/_runtime/common/generic/etag.js +11 -8
- package/libx/_runtime/common/generic/input.js +3 -3
- package/libx/_runtime/common/generic/paging.js +9 -5
- package/libx/_runtime/common/generic/put.js +3 -2
- package/libx/_runtime/common/generic/sorting.js +3 -3
- package/libx/_runtime/common/generic/temporal.js +3 -3
- package/libx/_runtime/common/utils/cqn.js +20 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +125 -139
- package/libx/_runtime/common/utils/csn.js +50 -52
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +41 -176
- package/libx/_runtime/common/utils/generateOnCond.js +40 -70
- package/libx/_runtime/common/utils/{enrichWithKeysFromWhere.js → keys.js} +29 -28
- package/libx/_runtime/common/utils/postProcessing.js +3 -0
- package/libx/_runtime/common/utils/propagateForeignKeys.js +84 -0
- package/libx/_runtime/common/utils/resolveStructured.js +1 -1
- package/libx/_runtime/common/utils/resolveView.js +7 -5
- package/libx/_runtime/common/utils/rewriteAsterisks.js +94 -0
- package/libx/_runtime/common/utils/search2cqn4sql.js +9 -8
- package/libx/_runtime/common/utils/template.js +54 -46
- package/libx/_runtime/db/Service.js +9 -2
- package/libx/_runtime/db/expand/expandCQNToJoin.js +54 -33
- package/libx/_runtime/db/expand/rawToExpanded.js +2 -1
- package/libx/_runtime/db/generic/arrayed.js +13 -28
- package/libx/_runtime/db/generic/create.js +1 -0
- package/libx/_runtime/db/generic/input.js +7 -11
- package/libx/_runtime/db/generic/integrity.js +2 -2
- package/libx/_runtime/db/generic/rewrite.js +2 -5
- package/libx/_runtime/db/generic/update.js +1 -0
- package/libx/_runtime/db/query/read.js +9 -4
- package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -2
- package/libx/_runtime/db/sql-builder/annotations.js +1 -0
- package/libx/_runtime/db/utils/columns.js +14 -43
- package/libx/_runtime/fiori/generic/activate.js +3 -2
- package/libx/_runtime/fiori/generic/before.js +2 -2
- package/libx/_runtime/fiori/generic/cancel.js +3 -2
- package/libx/_runtime/fiori/generic/delete.js +3 -2
- package/libx/_runtime/fiori/generic/edit.js +3 -3
- package/libx/_runtime/fiori/generic/new.js +2 -2
- package/libx/_runtime/fiori/generic/patch.js +2 -2
- package/libx/_runtime/fiori/generic/prepare.js +2 -2
- package/libx/_runtime/fiori/generic/read.js +45 -63
- package/libx/_runtime/fiori/generic/readOverDraft.js +4 -4
- package/libx/_runtime/fiori/uiflex/extensibility/index.cds +15 -0
- package/libx/_runtime/fiori/uiflex/extensibility/index.js +148 -0
- package/libx/_runtime/fiori/uiflex/handler/transformREAD.js +119 -0
- package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +43 -0
- package/libx/_runtime/fiori/uiflex/handler/transformWRITE.js +62 -0
- package/libx/_runtime/fiori/uiflex/index.js +35 -0
- package/libx/_runtime/fiori/uiflex/utils.js +78 -0
- package/libx/_runtime/fiori/utils/handler.js +3 -13
- package/libx/_runtime/fiori/utils/where.js +6 -1
- package/libx/_runtime/hana/pool.js +12 -11
- package/libx/_runtime/hana/search2cqn4sql.js +34 -43
- package/libx/_runtime/hana/searchToContains.js +3 -3
- package/libx/_runtime/index.js +5 -2
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
- package/libx/_runtime/messaging/common-utils/AMQPClient.js +16 -3
- package/libx/_runtime/messaging/common-utils/connections.js +11 -14
- package/libx/_runtime/messaging/common-utils/naming-conventions.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -1
- package/libx/_runtime/messaging/message-queuing.js +18 -0
- package/libx/_runtime/remote/Service.js +20 -4
- package/libx/_runtime/remote/utils/client-types.d.ts +7 -0
- package/libx/_runtime/remote/utils/client.js +117 -23
- package/libx/_runtime/sqlite/Service.js +2 -2
- package/libx/_runtime/sqlite/convertAssocToOneManaged.js +1 -3
- package/libx/gql/GraphQLAdapter.js +33 -0
- package/libx/gql/constants/adapter.js +69 -0
- package/libx/gql/constants/cds.js +18 -0
- package/libx/gql/constants/graphql.js +33 -0
- package/libx/gql/resolvers/crud/create.js +15 -0
- package/libx/gql/resolvers/crud/delete.js +24 -0
- package/libx/gql/resolvers/crud/index.js +6 -0
- package/libx/gql/resolvers/crud/read.js +25 -0
- package/libx/gql/resolvers/crud/update.js +31 -0
- package/libx/gql/resolvers/crud/utils/index.js +36 -0
- package/libx/gql/resolvers/field.js +5 -0
- package/libx/gql/resolvers/index.js +7 -0
- package/libx/gql/resolvers/mutation.js +23 -0
- package/libx/gql/resolvers/parse/ast/enrich.js +51 -0
- package/libx/gql/resolvers/parse/ast/fragment.js +11 -0
- package/libx/gql/resolvers/parse/ast/fromObject.js +39 -0
- package/libx/gql/resolvers/parse/ast/index.js +3 -0
- package/libx/gql/resolvers/parse/ast/meta.js +4 -0
- package/libx/gql/resolvers/parse/ast/variable.js +7 -0
- package/libx/gql/resolvers/parse/ast2cqn/columns.js +42 -0
- package/libx/gql/resolvers/parse/ast2cqn/entries.js +31 -0
- package/libx/gql/resolvers/parse/ast2cqn/index.js +8 -0
- package/libx/gql/resolvers/parse/ast2cqn/limit.js +6 -0
- package/libx/gql/resolvers/parse/ast2cqn/orderBy.js +24 -0
- package/libx/gql/resolvers/parse/ast2cqn/utils/index.js +3 -0
- package/libx/gql/resolvers/parse/ast2cqn/where.js +70 -0
- package/libx/gql/resolvers/parse/utils/index.js +8 -0
- package/libx/gql/resolvers/query.js +13 -0
- package/libx/gql/resolvers/root.js +34 -0
- package/libx/gql/schema/generate.js +18 -0
- package/libx/gql/schema/index.js +5 -0
- package/libx/gql/schema/mutation.js +76 -0
- package/libx/gql/schema/query.js +108 -0
- package/libx/gql/schema/typeDefMap.js +45 -0
- package/libx/gql/schema/utils/index.js +54 -0
- package/libx/gql/utils/index.js +12 -0
- package/libx/{_runtime/odata/cqn2odata.js → odata/cqn2odata/index.js} +39 -100
- package/libx/odata/index.js +80 -0
- package/libx/odata/odata2cqn/afterburner.js +170 -0
- package/libx/{_runtime/odata/odata2cqn.pegjs → odata/odata2cqn/grammar.pegjs} +102 -123
- package/libx/odata/odata2cqn/index.js +3 -0
- package/libx/odata/odata2cqn/parser.js +1 -0
- package/libx/odata/utils/index.js +64 -0
- package/libx/rest/RestAdapter.js +101 -0
- package/libx/rest/RestRequest.js +30 -0
- package/libx/rest/index.js +3 -0
- package/libx/rest/middleware/auth.js +22 -0
- package/libx/rest/middleware/content.js +15 -0
- package/libx/rest/middleware/create.js +40 -0
- package/libx/rest/middleware/delete.js +20 -0
- package/libx/rest/middleware/error.js +56 -0
- package/libx/rest/middleware/operation.js +39 -0
- package/libx/rest/middleware/parse.js +90 -0
- package/libx/rest/middleware/read.js +29 -0
- package/libx/rest/middleware/update.js +42 -0
- package/libx/rest/utils/data.js +65 -0
- package/package.json +4 -1
- package/server.js +29 -7
- package/libx/_runtime/cds-services/services/utils/diff.js +0 -53
- package/libx/_runtime/cds-services/util/auditlog.js +0 -247
- package/libx/_runtime/cds-services/util/xsenv.js +0 -51
- package/libx/_runtime/common/utils/backlinks.js +0 -83
- package/libx/_runtime/common/utils/rewriteAsterisk.js +0 -72
- package/libx/_runtime/odata/index.js +0 -55
- package/libx/_runtime/odata/odata2cqn.js +0 -1
- package/libx/_runtime/odata/readToCqn.js +0 -129
- package/libx/_runtime/remote/cqn2odata/index.js +0 -2
|
@@ -1,8 +1,23 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { where2obj } = require('./cqn')
|
|
2
|
+
|
|
3
|
+
function _getOnCondElements(onCond, onCondElements = []) {
|
|
4
|
+
const andIndex = onCond.indexOf('and')
|
|
5
|
+
const entityKey = onCond[2].ref && onCond[2].ref.join('.')
|
|
6
|
+
const entityVal = onCond[2].val
|
|
7
|
+
const targetKey = onCond[0].ref && onCond[0].ref.join('.')
|
|
8
|
+
const targetVal = onCond[0].val
|
|
9
|
+
onCondElements.push({ entityKey, targetKey, entityVal, targetVal })
|
|
10
|
+
|
|
11
|
+
if (andIndex !== -1) {
|
|
12
|
+
_getOnCondElements(onCond.slice(andIndex + 1), onCondElements)
|
|
13
|
+
}
|
|
14
|
+
return onCondElements
|
|
15
|
+
}
|
|
2
16
|
|
|
3
17
|
function _modifyWhereWithNavigations(where, newWhere, targetKeyElement, keyName) {
|
|
4
18
|
if (where) {
|
|
5
|
-
|
|
19
|
+
// copy where else query will be modified
|
|
20
|
+
const whereCopy = JSON.parse(JSON.stringify(where))
|
|
6
21
|
if (newWhere.length > 0) newWhere.push('and')
|
|
7
22
|
newWhere.push(...whereCopy)
|
|
8
23
|
}
|
|
@@ -14,7 +29,7 @@ function _modifyWhereWithNavigations(where, newWhere, targetKeyElement, keyName)
|
|
|
14
29
|
})
|
|
15
30
|
}
|
|
16
31
|
|
|
17
|
-
|
|
32
|
+
function _buildWhereForNavigations(ref, newWhere, model, target) {
|
|
18
33
|
const currentRef = ref[0]
|
|
19
34
|
const nextRef = ref[1]
|
|
20
35
|
|
|
@@ -24,7 +39,7 @@ const _buildWhereForNavigations = (ref, newWhere, model, target) => {
|
|
|
24
39
|
|
|
25
40
|
if (!navigationElement || !navigationElement.on) return
|
|
26
41
|
|
|
27
|
-
const nextKeys =
|
|
42
|
+
const nextKeys = _getOnCondElements(navigationElement.on)
|
|
28
43
|
for (const key of nextKeys) {
|
|
29
44
|
const keyName = key.targetKey.replace(navigationElement.name + '.', '')
|
|
30
45
|
const targetKeyElement = navigationElement._target.elements[keyName]
|
|
@@ -55,37 +70,23 @@ function _getWhereFromUpdate(query, target, model) {
|
|
|
55
70
|
return query.UPDATE.where
|
|
56
71
|
}
|
|
57
72
|
|
|
58
|
-
function _addKeysFromWhereToData(where, target, data) {
|
|
59
|
-
const whereLength = where.length
|
|
60
|
-
for (let i = 0; i < whereLength; i++) {
|
|
61
|
-
const whereEl = where[i]
|
|
62
|
-
const colName = whereEl.ref && whereEl.ref[whereEl.ref.length - 1]
|
|
63
|
-
const colEl = colName && target.elements[colName]
|
|
64
|
-
if (colEl && colEl.key) {
|
|
65
|
-
const opWhere = where[i + 1]
|
|
66
|
-
const valWhere = where[i + 2]
|
|
67
|
-
if (opWhere === '=' && valWhere && 'val' in valWhere) {
|
|
68
|
-
data[colName] = valWhere.val
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
73
|
// params: data, req, service/tx
|
|
75
|
-
|
|
74
|
+
function enrichDataWithKeysFromWhere(data, { query, target }, { model }) {
|
|
76
75
|
if (query.INSERT) {
|
|
77
76
|
const where = _getWhereFromInsert(query, target, model)
|
|
78
77
|
if (!where || !where.length) return
|
|
79
|
-
|
|
80
78
|
if (!Array.isArray(data)) data = [data]
|
|
81
|
-
|
|
82
|
-
for (const d of data) {
|
|
83
|
-
_addKeysFromWhereToData(where, target, d)
|
|
84
|
-
}
|
|
79
|
+
for (const d of data) Object.assign(d, where2obj(where, target))
|
|
85
80
|
} else if (query.UPDATE) {
|
|
86
81
|
const where = _getWhereFromUpdate(query, target, model)
|
|
87
82
|
if (!where || !where.length) return
|
|
88
|
-
|
|
89
|
-
|
|
83
|
+
// REVISIT: We should not expect data to be present always!
|
|
84
|
+
if (!data) data = query.UPDATE.data = {}
|
|
85
|
+
Object.assign(data, where2obj(where, target))
|
|
90
86
|
}
|
|
91
87
|
}
|
|
88
|
+
|
|
89
|
+
module.exports = {
|
|
90
|
+
where2obj,
|
|
91
|
+
enrichDataWithKeysFromWhere
|
|
92
|
+
}
|
|
@@ -64,6 +64,9 @@ const handleAliasInResult = (columns, result) => {
|
|
|
64
64
|
// REVISIT: todo renaming for deep operations
|
|
65
65
|
const postProcess = (query, result, service, onlySelectAliases = false) => {
|
|
66
66
|
if (query.SELECT) {
|
|
67
|
+
if (query.SELECT.columns && query.SELECT.columns.find(col => col.func === 'count' && col.as === '$count')) {
|
|
68
|
+
return [{ $count: result }]
|
|
69
|
+
}
|
|
67
70
|
handleAliasInResult(query.SELECT.columns, result)
|
|
68
71
|
|
|
69
72
|
if (!onlySelectAliases) {
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const cds = require('../../cds')
|
|
2
|
+
|
|
3
|
+
const _generateParentField = ({ parentElement }, row) => {
|
|
4
|
+
if (_autoGenerate(parentElement) && !row[parentElement.name]) {
|
|
5
|
+
row[parentElement.name] = cds.utils.uuid()
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const _generateChildField = ({ deep, childElement }, childRow) => {
|
|
10
|
+
if (deep) {
|
|
11
|
+
_generateChildField(deep.propagation, childRow[deep.targetName])
|
|
12
|
+
} else if (_autoGenerate(childElement) && childRow && !childRow[childElement.name]) {
|
|
13
|
+
childRow[childElement.name] = cds.utils.uuid()
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const _autoGenerate = e => e && e.type === 'cds.UUID' && e.key
|
|
18
|
+
|
|
19
|
+
const _getNestedVal = (row, prefix) => {
|
|
20
|
+
let val = row
|
|
21
|
+
const splitted = prefix.split('_')
|
|
22
|
+
let k = ''
|
|
23
|
+
|
|
24
|
+
while (splitted.length > 0) {
|
|
25
|
+
k += splitted.shift()
|
|
26
|
+
if (k in val) {
|
|
27
|
+
val = val[k]
|
|
28
|
+
k = ''
|
|
29
|
+
} else {
|
|
30
|
+
k += '_'
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return val
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const _propagateToChid = ({ parentElement, childElement, prefix, parentFieldValue }, row, childRow) => {
|
|
38
|
+
if (!childElement) return
|
|
39
|
+
if (parentElement) {
|
|
40
|
+
if (prefix) {
|
|
41
|
+
const nested = _getNestedVal(row, prefix)
|
|
42
|
+
childRow[childElement.name] = nested[parentElement.name]
|
|
43
|
+
} else {
|
|
44
|
+
childRow[childElement.name] = row[parentElement.name]
|
|
45
|
+
}
|
|
46
|
+
} else if (parentFieldValue !== undefined) {
|
|
47
|
+
childRow[childElement.name] = parentFieldValue
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const _propagateToParent = ({ parentElement, childElement, deep }, childRow, row) => {
|
|
52
|
+
if (deep) {
|
|
53
|
+
_propagateToParent(deep.propagation, childRow[deep.targetName], childRow)
|
|
54
|
+
}
|
|
55
|
+
if (parentElement && childElement && childRow && Object.prototype.hasOwnProperty.call(childRow, childElement.name)) {
|
|
56
|
+
row[parentElement.name] = childRow[childElement.name]
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const propagateForeignKeys = (tKey, row, foreignKeyPropagations, isCompositionEffective) => {
|
|
61
|
+
const childRows = Array.isArray(row[tKey]) ? row[tKey] : [row[tKey]]
|
|
62
|
+
|
|
63
|
+
for (const childRow of childRows) {
|
|
64
|
+
if (!childRow) return
|
|
65
|
+
|
|
66
|
+
for (const foreignKeyPropagation of foreignKeyPropagations) {
|
|
67
|
+
if (foreignKeyPropagation.fillChild) {
|
|
68
|
+
_generateParentField(foreignKeyPropagation, row)
|
|
69
|
+
if (!isCompositionEffective) {
|
|
70
|
+
delete row[tKey]
|
|
71
|
+
} else {
|
|
72
|
+
_propagateToChid(foreignKeyPropagation, row, childRow)
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
_generateChildField(foreignKeyPropagation, childRow)
|
|
76
|
+
_propagateToParent(foreignKeyPropagation, childRow, row)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = {
|
|
83
|
+
propagateForeignKeys
|
|
84
|
+
}
|
|
@@ -22,7 +22,7 @@ const _flattenProps = (subElement, structName, structProperties, structElement,
|
|
|
22
22
|
|
|
23
23
|
const _resolveStructured = ({ structName, structProperties }, subElements, asRef = true) => {
|
|
24
24
|
if (!subElements) {
|
|
25
|
-
return
|
|
25
|
+
return []
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
// only add from structProperties
|
|
@@ -2,6 +2,7 @@ const cds = require('../../cds')
|
|
|
2
2
|
let LOG = cds.log('app')
|
|
3
3
|
let _event
|
|
4
4
|
const PERSISTENCE_TABLE = '@cds.persistence.table'
|
|
5
|
+
const { rewriteAsterisks } = require('../../common/utils/rewriteAsterisks')
|
|
5
6
|
|
|
6
7
|
const getError = require('../error')
|
|
7
8
|
const { getEntityNameFromDeleteCQN, getEntityNameFromUpdateCQN } = require('../utils/cqn')
|
|
@@ -172,7 +173,7 @@ const _newColumns = (columns = [], transition, service, withAlias = false) => {
|
|
|
172
173
|
}
|
|
173
174
|
|
|
174
175
|
// ensure that renaming of a redirected assoc are also respected
|
|
175
|
-
if (column.expand) {
|
|
176
|
+
if (mapped && column.expand) {
|
|
176
177
|
// column.ref might be structured elements
|
|
177
178
|
let def
|
|
178
179
|
column.ref.forEach((ref, i) => {
|
|
@@ -186,9 +187,7 @@ const _newColumns = (columns = [], transition, service, withAlias = false) => {
|
|
|
186
187
|
// reuse _newColumns with new transition
|
|
187
188
|
const expandTarget = def._target
|
|
188
189
|
const subtransition = getTransition(expandTarget, service)
|
|
189
|
-
|
|
190
|
-
// REVISIT: in edom-retailer case, mapped was undefined which lead to a type error
|
|
191
|
-
if (mapped) mapped.transition = subtransition
|
|
190
|
+
mapped.transition = subtransition
|
|
192
191
|
|
|
193
192
|
newColumn.expand = _newColumns(column.expand, subtransition, service, withAlias)
|
|
194
193
|
}
|
|
@@ -338,8 +337,11 @@ const _newSelect = (query, transitions, service) => {
|
|
|
338
337
|
ref: _rewriteQueryPath(query.SELECT.from, transitions)
|
|
339
338
|
}
|
|
340
339
|
if (!newSelect.columns && targetTransition.mapping.size) newSelect.columns = _initialColumns(targetTransition)
|
|
341
|
-
if (newSelect.columns)
|
|
340
|
+
if (newSelect.columns) {
|
|
341
|
+
const isDB = service instanceof cds.DatabaseService
|
|
342
|
+
rewriteAsterisks({ SELECT: newSelect }, targetTransition.queryTarget, isDB)
|
|
342
343
|
newSelect.columns = _newColumns(newSelect.columns, targetTransition, service, service.kind !== 'app-service')
|
|
344
|
+
}
|
|
343
345
|
if (newSelect.having) newSelect.having = _newColumns(newSelect.having, targetTransition)
|
|
344
346
|
if (newSelect.groupBy) newSelect.groupBy = _newColumns(newSelect.groupBy, targetTransition)
|
|
345
347
|
if (newSelect.orderBy) newSelect.orderBy = _newColumns(newSelect.orderBy, targetTransition)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
const { getNavigationIfStruct } = require('./structured')
|
|
2
|
+
const getColumns = require('../../db/utils/columns')
|
|
3
|
+
const { ensureDraftsSuffix } = require('./draft')
|
|
4
|
+
|
|
5
|
+
const isAsteriskColumn = col => col === '*' || (col.ref && col.ref[0] === '*' && !col.expand)
|
|
6
|
+
|
|
7
|
+
const _isDuplicate = newColumn => column => {
|
|
8
|
+
if (newColumn.as) return column.as && column.as === newColumn.as
|
|
9
|
+
if (!column.ref) return
|
|
10
|
+
if (Array.isArray(newColumn)) newColumn = { ref: newColumn }
|
|
11
|
+
return newColumn.ref ? newColumn.ref.join('_') === column.ref.join('_') : newColumn === column.ref.join('_')
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const _cqlDraftColumns = target => {
|
|
15
|
+
if (target.name.endsWith('DraftAdministrativeData')) return []
|
|
16
|
+
const draftName = ensureDraftsSuffix(target.name)
|
|
17
|
+
const subSelect = SELECT.from(draftName).columns([1])
|
|
18
|
+
for (const key in target.keys) {
|
|
19
|
+
if (key !== 'IsActiveEntity') subSelect.where([{ ref: [target.name, key] }, '=', { ref: [draftName, key] }])
|
|
20
|
+
}
|
|
21
|
+
return [
|
|
22
|
+
{ val: true, as: 'IsActiveEntity', cast: { type: 'cds.Boolean' } },
|
|
23
|
+
{ val: false, as: 'HasActiveEntity', cast: { type: 'cds.Boolean' } },
|
|
24
|
+
{
|
|
25
|
+
xpr: ['case', 'when', 'exists', subSelect, 'then', 'true', 'else', 'false', 'end'],
|
|
26
|
+
as: 'HasDraftEntity',
|
|
27
|
+
cast: { type: 'cds.Boolean' }
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const _expandColumn = (column, target, db) => {
|
|
33
|
+
if (!(column.ref && column.expand)) return
|
|
34
|
+
const nextTarget = getNavigationIfStruct(target, column.ref)
|
|
35
|
+
if (nextTarget && nextTarget._target && nextTarget._target.elements) _rewriteAsterisks(column, nextTarget._target, db)
|
|
36
|
+
return column
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const rewriteExpandAsterisk = (columns, target) => {
|
|
40
|
+
const expandAllColIdx = columns.findIndex(col => {
|
|
41
|
+
if (col.ref || !col.expand) return
|
|
42
|
+
return !Array.isArray(col.expand) ? col.expand === '*' : col.expand.indexOf('*') > -1
|
|
43
|
+
})
|
|
44
|
+
if (expandAllColIdx > -1) {
|
|
45
|
+
const { expand } = columns.splice(expandAllColIdx, 1)[0]
|
|
46
|
+
for (const elName in target.elements) {
|
|
47
|
+
if (target.elements[elName]._target && !columns.find(col => col.expand && col.ref && col.ref[0] === elName)) {
|
|
48
|
+
columns.push({ ref: [elName], expand: [...expand] })
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const _rewriteAsterisk = (columns, target, db, isRoot) => {
|
|
55
|
+
const asteriskColumnIndex = columns.findIndex(col => isAsteriskColumn(col))
|
|
56
|
+
if (asteriskColumnIndex > -1) {
|
|
57
|
+
columns.splice(
|
|
58
|
+
asteriskColumnIndex,
|
|
59
|
+
1,
|
|
60
|
+
...getColumns(target, { db })
|
|
61
|
+
.map(c => ({ ref: [c.name] }))
|
|
62
|
+
.filter(c => !columns.find(_isDuplicate(c)) && (isRoot || c.ref[0] !== 'DraftAdministrativeData_DraftUUID'))
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const _rewriteAsterisks = (cqn, target, db, isRoot) => {
|
|
68
|
+
if (cqn.expand === '*') cqn.expand = ['*']
|
|
69
|
+
const columns = cqn.expand || cqn.columns
|
|
70
|
+
_rewriteAsterisk(columns, target, db, isRoot)
|
|
71
|
+
rewriteExpandAsterisk(columns, target)
|
|
72
|
+
for (const column of columns) {
|
|
73
|
+
_expandColumn(column, target, db)
|
|
74
|
+
}
|
|
75
|
+
return columns
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const rewriteAsterisks = (query, target, db = false, isDraft = false, onlyKeys = false) => {
|
|
79
|
+
if (!target || target.name.endsWith('_drafts')) return
|
|
80
|
+
if (!query.SELECT.columns || (query.SELECT.columns && !query.SELECT.columns.length)) {
|
|
81
|
+
if (isDraft || db) {
|
|
82
|
+
query.SELECT.columns = getColumns(target, { db, onlyKeys }).map(col => ({ ref: [col.name] }))
|
|
83
|
+
if (db && target._isDraftEnabled) query.SELECT.columns.push(..._cqlDraftColumns(target))
|
|
84
|
+
}
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
query.SELECT.columns = _rewriteAsterisks(query.SELECT, target, db, true)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = {
|
|
91
|
+
rewriteAsterisks,
|
|
92
|
+
isAsteriskColumn,
|
|
93
|
+
rewriteExpandAsterisk
|
|
94
|
+
}
|
|
@@ -3,23 +3,24 @@ const searchToLike = require('./searchToLike')
|
|
|
3
3
|
|
|
4
4
|
// convert $search system query option to WHERE/HAVING clause using
|
|
5
5
|
// the operator LIKE or CONTAINS
|
|
6
|
-
const search2cqn4sql = (
|
|
7
|
-
const { search2cqn4sql, targetName =
|
|
6
|
+
const search2cqn4sql = (query, model, options) => {
|
|
7
|
+
const { search2cqn4sql, targetName = query.SELECT.from.ref[0] } = options
|
|
8
8
|
const entity = model.definitions[targetName]
|
|
9
|
-
const columns = computeColumnsToBeSearched(
|
|
9
|
+
const columns = computeColumnsToBeSearched(query, entity)
|
|
10
10
|
|
|
11
|
-
//
|
|
11
|
+
// Call custom (optimized search to cqn for sql implementation) that tries
|
|
12
12
|
// to optimize the search behavior for a specific database service.
|
|
13
|
-
|
|
13
|
+
// Note: $search query option combined with $filter is not currently optimized
|
|
14
|
+
if (typeof search2cqn4sql === 'function' && !query.SELECT.where) {
|
|
14
15
|
const search2cqnOptions = { columns, locale: options.locale }
|
|
15
|
-
return search2cqn4sql(
|
|
16
|
+
return search2cqn4sql(query, entity, search2cqnOptions)
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
const cqnSearchPhrase =
|
|
19
|
+
const cqnSearchPhrase = query.SELECT.search
|
|
19
20
|
const expression = searchToLike(cqnSearchPhrase, columns)
|
|
20
21
|
|
|
21
22
|
// REVISIT: find out here if where or having must be used
|
|
22
|
-
|
|
23
|
+
query._aggregated ? query.having(expression) : query.where(expression)
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
module.exports = search2cqn4sql
|
|
@@ -48,57 +48,60 @@ const _getNextTarget = (model, element, currentPath = []) => {
|
|
|
48
48
|
nextTargetName,
|
|
49
49
|
nextTarget: model.definitions[nextTargetName]
|
|
50
50
|
}
|
|
51
|
-
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (_isInlineStructured(element)) {
|
|
52
54
|
return {
|
|
53
55
|
nextTargetName: [...currentPath, element.name].join(DELIMITER),
|
|
54
56
|
nextTarget: element.items || element
|
|
55
57
|
}
|
|
56
58
|
}
|
|
59
|
+
|
|
57
60
|
return {}
|
|
58
61
|
}
|
|
59
62
|
|
|
60
63
|
/**
|
|
61
64
|
*
|
|
62
|
-
* @param {CSN} model
|
|
63
|
-
* @param {
|
|
64
|
-
* @param {
|
|
65
|
-
* @param {
|
|
66
|
-
* @param {
|
|
67
|
-
* @param {
|
|
68
|
-
* @param {
|
|
69
|
-
* @param
|
|
70
|
-
* @param
|
|
71
|
-
* @param parent
|
|
72
|
-
* @param entityMap
|
|
65
|
+
* @param {import('@sap/cds-compiler/lib/api/main').CSN} model Model
|
|
66
|
+
* @param {Map} cache Internal - do not use
|
|
67
|
+
* @param {object} targetEntity The target entity which needs to be traversed
|
|
68
|
+
* @param {object} callbacks
|
|
69
|
+
* @param {function} callbacks.pick Callback function to pick elements. If it returns a truthy value, the element will be picked. The returned value is part of the template.
|
|
70
|
+
* @param {function} callbacks.ignore Callback function to ignore elements. If it returns a truthy value, the element will be ignored.
|
|
71
|
+
* @param {object} [parent=null] The parent entity
|
|
72
|
+
* @param {Map} [_entityMap] This parameter is an implementation side-effect — don't use it
|
|
73
|
+
* @param {array} [targetPath=[]]
|
|
73
74
|
*/
|
|
74
|
-
function _getTemplate(model, cache,
|
|
75
|
+
function _getTemplate(model, cache, targetEntity, callbacks, parent = null, _entityMap = new Map(), targetPath = []) {
|
|
76
|
+
const { pick, ignore } = callbacks
|
|
75
77
|
const templateElements = new Map()
|
|
76
|
-
const template = { target, elements: templateElements }
|
|
77
|
-
const currentPath = [...targetPath,
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
78
|
+
const template = { target: targetEntity, elements: templateElements }
|
|
79
|
+
const currentPath = [...targetPath, targetEntity.name]
|
|
80
|
+
_entityMap.set(currentPath.join(DELIMITER), { template })
|
|
81
|
+
if (!targetEntity.elements) return template
|
|
82
|
+
|
|
83
|
+
for (const elementName in targetEntity.elements) {
|
|
84
|
+
const element = targetEntity.elements[elementName]
|
|
85
|
+
if (ignore && ignore(element, targetEntity, parent)) continue
|
|
86
|
+
|
|
87
|
+
_pick(pick, element, targetEntity, parent, templateElements, elementName)
|
|
88
|
+
|
|
89
|
+
if (element.items) {
|
|
90
|
+
_pick(pick, element.items, targetEntity, parent, templateElements, ['_itemsOf', elementName].join(DELIMITER))
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const { nextTargetName, nextTarget } = _getNextTarget(model, element, currentPath)
|
|
94
|
+
const nextTargetCached = _entityMap.get(nextTargetName)
|
|
95
|
+
|
|
96
|
+
if (nextTargetCached) {
|
|
97
|
+
_addCacheToTemplateElements(templateElements, elementName, nextTargetCached)
|
|
98
|
+
} else if (nextTarget) {
|
|
99
|
+
// For associations and _typed_ structured elements, there's a (cacheable) target,
|
|
100
|
+
// inline structures must be handled separately.
|
|
101
|
+
const subTemplate = _isInlineStructured(element)
|
|
102
|
+
? _getTemplate(model, cache, nextTarget, { pick, ignore }, targetEntity, _entityMap, currentPath)
|
|
103
|
+
: cache.for(nextTarget, getTemplate(model, { pick, ignore }, targetEntity, _entityMap))
|
|
104
|
+
_addSubTemplate(templateElements, elementName, subTemplate)
|
|
102
105
|
}
|
|
103
106
|
}
|
|
104
107
|
|
|
@@ -112,11 +115,11 @@ const getTemplate =
|
|
|
112
115
|
|
|
113
116
|
const getCache = (anything, cache, newCacheFn) => {
|
|
114
117
|
let _cached = cache.get(anything)
|
|
115
|
-
if (
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
if (_cached) return _cached
|
|
119
|
+
|
|
120
|
+
_cached = (typeof newCacheFn === 'function' && newCacheFn(anything, cache)) || new Map()
|
|
121
|
+
_cached.for = (_usecase, _newCacheFn) => getCache(_usecase, _cached, _newCacheFn)
|
|
122
|
+
cache.set(anything, _cached)
|
|
120
123
|
return _cached
|
|
121
124
|
}
|
|
122
125
|
|
|
@@ -124,11 +127,13 @@ module.exports = (usecase, tx, target, ...args) => {
|
|
|
124
127
|
// get model first as it may be added to tx (cf. "_ensureModel")
|
|
125
128
|
const model = tx.model
|
|
126
129
|
if (!model) return
|
|
130
|
+
|
|
127
131
|
// double-check with get target from model
|
|
128
132
|
// since target might come from anywhere like via cqn etc
|
|
129
133
|
if (!target) return
|
|
130
134
|
const root = (model && model.definitions[target.name]) || (target.elements && target)
|
|
131
135
|
if (!root) return
|
|
136
|
+
|
|
132
137
|
// tx could be the service itself
|
|
133
138
|
// prefer ApplicationService (i.e., tx.context._tx.__proto__)
|
|
134
139
|
// REVISIT: context._tx is not a stable API -> pls do not rely on that
|
|
@@ -136,10 +141,13 @@ module.exports = (usecase, tx, target, ...args) => {
|
|
|
136
141
|
? (tx.context._tx && Object.getPrototypeOf(tx.context._tx)) || Object.getPrototypeOf(tx)
|
|
137
142
|
: tx
|
|
138
143
|
if (!service) return
|
|
144
|
+
|
|
139
145
|
// cache templates at service for garbage collection
|
|
140
|
-
if (!service._templateCache) service._templateCache = new Map()
|
|
146
|
+
if (usecase && !service._templateCache) service._templateCache = new Map()
|
|
141
147
|
// model can be also a subset from tx
|
|
142
|
-
|
|
148
|
+
|
|
149
|
+
// if no usecase, don't save cache on the service object
|
|
150
|
+
return getCache(usecase, usecase ? service._templateCache : new Map())
|
|
143
151
|
.for(model)
|
|
144
152
|
.for(root, getTemplate(model, ...args))
|
|
145
153
|
}
|
|
@@ -109,6 +109,7 @@ class DatabaseService extends cds.Service {
|
|
|
109
109
|
if (query && (!streamQuery.SELECT.columns || streamQuery.SELECT.columns.length !== 0)) {
|
|
110
110
|
streamQuery.columns([query])
|
|
111
111
|
}
|
|
112
|
+
|
|
112
113
|
delete streamQuery.SELECT.one
|
|
113
114
|
streamQuery._streaming = true
|
|
114
115
|
|
|
@@ -119,12 +120,18 @@ class DatabaseService extends cds.Service {
|
|
|
119
120
|
}
|
|
120
121
|
})
|
|
121
122
|
|
|
122
|
-
if (
|
|
123
|
+
if (
|
|
124
|
+
!streamQuery.SELECT.where &&
|
|
125
|
+
!(
|
|
126
|
+
streamQuery.SELECT.from &&
|
|
127
|
+
streamQuery.SELECT.from.ref &&
|
|
128
|
+
streamQuery.SELECT.from.ref[streamQuery.SELECT.from.ref.length - 1].where
|
|
129
|
+
)
|
|
130
|
+
) {
|
|
123
131
|
return {
|
|
124
132
|
where: (...args) => {
|
|
125
133
|
streamQuery.where(...args)
|
|
126
134
|
this._runStream(streamQuery, result)
|
|
127
|
-
|
|
128
135
|
return result
|
|
129
136
|
}
|
|
130
137
|
}
|