@sap/cds 5.4.3 → 5.5.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 +239 -2
- package/apis/ql.d.ts +17 -15
- package/app/index.js +1 -1
- package/bin/build/buildTaskEngine.js +26 -42
- package/bin/build/buildTaskFactory.js +6 -10
- package/bin/build/buildTaskHandler.js +2 -4
- package/bin/build/buildTaskProvider.js +3 -1
- package/bin/build/buildTaskProviderFactory.js +9 -15
- package/bin/build/constants.js +15 -3
- package/bin/build/index.js +5 -4
- package/bin/build/mtaUtil.js +8 -11
- package/bin/build/provider/buildTaskHandlerEdmx.js +63 -6
- package/bin/build/provider/buildTaskHandlerInternal.js +2 -34
- package/bin/build/provider/buildTaskProviderInternal.js +16 -42
- package/bin/build/provider/fiori/index.js +13 -24
- package/bin/build/provider/hana/2migration.js +17 -15
- package/bin/build/provider/hana/2tabledata.js +52 -48
- package/bin/build/provider/hana/index.js +27 -25
- package/bin/build/provider/hana/migrationtable.js +91 -67
- package/bin/build/provider/java-cf/index.js +14 -24
- package/bin/build/provider/mtx/index.js +12 -14
- package/bin/build/provider/node-cf/index.js +18 -32
- package/bin/cds.js +5 -5
- package/bin/serve.js +29 -23
- package/bin/version.js +0 -1
- package/lib/compile/etc/_localized.js +4 -9
- package/lib/compile/for/sql.js +5 -2
- package/lib/compile/parse.js +25 -17
- package/lib/compile/to/srvinfo.js +2 -1
- package/lib/connect/bindings.js +2 -1
- package/lib/connect/index.js +48 -49
- package/lib/core/classes.js +1 -1
- package/lib/core/reflect.js +10 -2
- package/lib/deploy.js +26 -23
- package/lib/env/defaults.js +13 -6
- package/lib/env/index.js +73 -78
- package/lib/env/requires.js +38 -19
- package/lib/index.js +9 -10
- package/lib/lazy.js +2 -2
- package/lib/log/index.js +33 -45
- package/lib/log/service/index.js +2 -2
- package/lib/ql/CREATE.js +14 -9
- package/lib/ql/DELETE.js +6 -5
- package/lib/ql/DROP.js +12 -9
- package/lib/ql/INSERT.js +40 -16
- package/lib/ql/Query.js +67 -40
- package/lib/ql/SELECT.js +162 -127
- package/lib/ql/UPDATE.js +74 -42
- package/lib/ql/Whereable.js +77 -87
- package/lib/ql/index.js +36 -24
- package/lib/ql/parse.js +35 -0
- package/lib/req/context.js +44 -8
- package/lib/req/locale.js +7 -7
- package/lib/serve/Service-api.js +21 -14
- package/lib/serve/Service-dispatch.js +28 -12
- package/lib/serve/Transaction.js +22 -10
- package/lib/serve/index.js +16 -11
- package/lib/utils/axios.js +23 -16
- package/lib/utils/data.js +35 -0
- package/lib/utils/tests.js +27 -18
- package/libx/_runtime/audit/generic/personal/access.js +81 -0
- package/libx/_runtime/audit/generic/personal/constants.js +4 -0
- package/libx/_runtime/audit/generic/personal/index.js +50 -0
- package/libx/_runtime/audit/generic/personal/modification.js +138 -0
- package/libx/_runtime/audit/generic/personal/utils.js +186 -0
- package/libx/_runtime/audit/utils/v2.js +10 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +5 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +6 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +5 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +5 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +2 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +4 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +7 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +59 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +11 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +6 -10
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +3 -46
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +2 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/createToCQN.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/deleteToCQN.js +4 -3
- 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/index.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/updateToCQN.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +16 -18
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/EdmEntityType.js +6 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/format/RepresentationKind.js +4 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/core/OdataRequest.js +1 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/SerializerFactory.js +15 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/OperationValidator.js +1 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +8 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +6 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/omitValues.js +12 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +1 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +7 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +14 -18
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +13 -13
- package/libx/_runtime/cds-services/adapter/rest/handlers/create.js +0 -1
- package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +2 -1
- package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +2 -2
- package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +2 -4
- package/libx/_runtime/cds-services/adapter/rest/utils/result.js +4 -2
- package/libx/_runtime/cds-services/services/Service.js +40 -5
- package/libx/_runtime/cds-services/services/utils/columns.js +13 -7
- package/libx/_runtime/cds-services/services/utils/compareJson.js +88 -4
- package/libx/_runtime/cds-services/services/utils/differ.js +24 -6
- package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -2
- package/libx/_runtime/common/composition/data.js +66 -63
- package/libx/_runtime/common/composition/delete.js +97 -71
- package/libx/_runtime/common/composition/index.js +2 -1
- package/libx/_runtime/common/composition/insert.js +34 -11
- package/libx/_runtime/common/composition/tree.js +119 -92
- package/libx/_runtime/common/composition/update.js +12 -1
- package/libx/_runtime/common/composition/utils.js +1 -3
- package/libx/_runtime/common/constants/draft.js +12 -1
- package/libx/_runtime/common/generic/auth.js +53 -31
- package/libx/_runtime/common/generic/crud.js +14 -13
- package/libx/_runtime/common/generic/input.js +23 -26
- package/libx/_runtime/common/generic/put.js +1 -1
- package/libx/_runtime/common/generic/sorting.js +16 -16
- package/libx/_runtime/common/i18n/index.js +1 -1
- package/libx/_runtime/common/i18n/messages.properties +4 -0
- package/libx/_runtime/common/utils/backlinks.js +12 -5
- package/libx/_runtime/common/utils/cqn.js +6 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +123 -108
- package/libx/_runtime/common/utils/csn.js +56 -4
- package/libx/_runtime/common/utils/data.js +0 -37
- package/libx/_runtime/common/utils/enrichWithKeysFromWhere.js +1 -1
- package/libx/_runtime/common/utils/entityFromCqn.js +7 -24
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +39 -7
- package/libx/_runtime/common/utils/generateOnCond.js +11 -12
- package/libx/_runtime/common/utils/onlyKeysRemain.js +10 -0
- package/libx/_runtime/common/utils/path.js +35 -0
- package/libx/_runtime/common/utils/postProcessing.js +86 -0
- package/libx/_runtime/common/utils/quotingStyles.js +37 -26
- package/libx/_runtime/common/utils/resolveView.js +227 -173
- package/libx/_runtime/common/utils/rewriteAsterisk.js +46 -26
- package/libx/_runtime/common/utils/structured.js +13 -13
- package/libx/_runtime/common/utils/template.js +10 -5
- package/libx/_runtime/common/utils/templateDelimiter.js +1 -0
- package/libx/_runtime/common/utils/templateProcessor.js +28 -72
- package/libx/_runtime/common/utils/union.js +31 -0
- package/libx/_runtime/common/utils/unionCqnTemplate.js +184 -0
- package/libx/_runtime/db/Service.js +1 -1
- package/libx/_runtime/db/data-conversion/timestamp.js +2 -9
- package/libx/_runtime/db/expand/expandCQNToJoin.js +204 -297
- package/libx/_runtime/db/expand/index.js +3 -3
- package/libx/_runtime/db/expand/rawToExpanded.js +36 -7
- package/libx/_runtime/db/generic/index.js +1 -1
- package/libx/_runtime/db/generic/input.js +5 -7
- package/libx/_runtime/db/generic/integrity.js +1 -1
- package/libx/_runtime/db/generic/rewrite.js +2 -10
- package/libx/_runtime/db/generic/update.js +13 -5
- package/libx/_runtime/db/generic/virtual.js +22 -58
- package/libx/_runtime/db/query/delete.js +7 -4
- package/libx/_runtime/db/query/insert.js +6 -4
- package/libx/_runtime/db/query/read.js +21 -8
- package/libx/_runtime/db/query/run.js +4 -1
- package/libx/_runtime/db/query/update.js +5 -4
- package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +35 -2
- package/libx/_runtime/db/sql-builder/FunctionBuilder.js +17 -2
- package/libx/_runtime/db/sql-builder/InsertBuilder.js +6 -5
- package/libx/_runtime/db/sql-builder/ReferenceBuilder.js +10 -0
- package/libx/_runtime/db/sql-builder/SelectBuilder.js +35 -24
- package/libx/_runtime/db/sql-builder/UpdateBuilder.js +14 -4
- package/libx/_runtime/db/sql-builder/arrayed.js +4 -0
- package/libx/_runtime/db/utils/deep.js +8 -0
- package/libx/_runtime/db/utils/generateAliases.js +2 -1
- package/libx/_runtime/fiori/generic/activate.js +19 -15
- package/libx/_runtime/fiori/generic/before.js +3 -11
- package/libx/_runtime/fiori/generic/cancel.js +1 -1
- package/libx/_runtime/fiori/generic/delete.js +3 -1
- package/libx/_runtime/fiori/generic/edit.js +12 -2
- package/libx/_runtime/fiori/generic/new.js +5 -5
- package/libx/_runtime/fiori/generic/patch.js +0 -18
- package/libx/_runtime/fiori/generic/read.js +261 -205
- package/libx/_runtime/fiori/utils/delete.js +36 -7
- package/libx/_runtime/fiori/utils/handler.js +43 -44
- package/libx/_runtime/fiori/utils/where.js +30 -15
- package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +4 -6
- package/libx/_runtime/hana/execute.js +3 -3
- package/libx/_runtime/hana/localized.js +4 -4
- package/libx/_runtime/hana/pool.js +29 -14
- package/libx/_runtime/hana/search2cqn4sql.js +2 -1
- package/libx/_runtime/hana/searchToContains.js +18 -14
- package/libx/_runtime/index.js +0 -5
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +13 -5
- package/libx/_runtime/messaging/common-utils/naming-conventions.js +4 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +31 -19
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +1 -2
- package/libx/_runtime/messaging/enterprise-messaging.js +6 -4
- package/libx/_runtime/messaging/service.js +7 -6
- package/libx/_runtime/odata/cqn2odata.js +110 -43
- package/libx/_runtime/odata/index.js +26 -48
- package/libx/_runtime/odata/odata2cqn.js +1 -6154
- package/libx/_runtime/odata/odata2cqn.pegjs +559 -0
- package/libx/_runtime/odata/readToCqn.js +94 -64
- package/libx/_runtime/remote/Service.js +74 -21
- package/libx/_runtime/remote/cqn2odata/index.js +1 -5
- package/libx/_runtime/remote/utils/client.js +24 -101
- package/libx/_runtime/remote/utils/dataConversion.js +27 -12
- package/libx/_runtime/sqlite/Service.js +3 -5
- package/libx/_runtime/sqlite/execute.js +33 -27
- package/libx/_runtime/sqlite/localized.js +12 -7
- package/libx/_runtime/types/api.js +10 -0
- package/package.json +2 -2
- package/server.js +16 -2
- package/lib/ql/grammar.pegjs +0 -208
- package/lib/ql/parser.js +0 -1
- package/lib/ql/rt/DELETE.js +0 -29
- package/lib/ql/rt/INSERT.js +0 -23
- package/lib/ql/rt/Query.js +0 -84
- package/lib/ql/rt/SELECT.js +0 -174
- package/lib/ql/rt/UPDATE.js +0 -119
- package/lib/ql/rt/_helpers.js +0 -91
- package/lib/ql/rt/index.js +0 -32
- package/libx/_runtime/audit/generic/personal.js +0 -260
- package/libx/_runtime/cds-services/statements/BaseStatement.js +0 -72
- package/libx/_runtime/cds-services/statements/Create.js +0 -57
- package/libx/_runtime/cds-services/statements/Delete.js +0 -33
- package/libx/_runtime/cds-services/statements/Drop.js +0 -42
- package/libx/_runtime/cds-services/statements/Insert.js +0 -201
- package/libx/_runtime/cds-services/statements/Select.js +0 -826
- package/libx/_runtime/cds-services/statements/Update.js +0 -181
- package/libx/_runtime/cds-services/statements/Where.js +0 -726
- package/libx/_runtime/cds-services/statements/index.js +0 -25
- package/libx/_runtime/common/generic/resolve-mock.js +0 -9
|
@@ -2,6 +2,11 @@ const cds = require('../../cds')
|
|
|
2
2
|
|
|
3
3
|
const Differ = require('./utils/differ')
|
|
4
4
|
|
|
5
|
+
const { resolveView, restoreLink, findQueryTarget } = require('../../common/utils/resolveView')
|
|
6
|
+
const { postProcess } = require('../../common/utils/postProcessing')
|
|
7
|
+
|
|
8
|
+
const _isSimpleCqnQuery = q => typeof q === 'object' && q !== null && !Array.isArray(q) && Object.keys(q).length > 0
|
|
9
|
+
|
|
5
10
|
/**
|
|
6
11
|
* Generic Service Event Handler.
|
|
7
12
|
*/
|
|
@@ -10,8 +15,7 @@ class ApplicationService extends cds.Service {
|
|
|
10
15
|
// REVISIT: do we still need that -> likely due to legacy test?
|
|
11
16
|
// If not we should remove this legacy constructor
|
|
12
17
|
if (typeof name === 'object') [name, csn, options] = [csn.service, name, csn]
|
|
13
|
-
|
|
14
|
-
super(name, csn, o)
|
|
18
|
+
super(name, csn, options)
|
|
15
19
|
|
|
16
20
|
// REVISIT: umbrella calls srv._calculateDiff
|
|
17
21
|
this._differ = new Differ(this)
|
|
@@ -20,8 +24,7 @@ class ApplicationService extends cds.Service {
|
|
|
20
24
|
|
|
21
25
|
set model(csn) {
|
|
22
26
|
const m = csn && 'definitions' in csn ? cds.linked(cds.compile.for.odata(csn)) : csn
|
|
23
|
-
// with compiler v2 we always need to localized the csn
|
|
24
|
-
cds.alpha_localized(m)
|
|
27
|
+
cds.alpha_localized(m) // with compiler v2 we always need to localized the csn
|
|
25
28
|
super.model = m
|
|
26
29
|
}
|
|
27
30
|
|
|
@@ -36,7 +39,6 @@ class ApplicationService extends cds.Service {
|
|
|
36
39
|
require('../../common/generic/temporal').call(this, this)
|
|
37
40
|
require('../../common/generic/paging').call(this, this) // > paging must be executed before sorting
|
|
38
41
|
require('../../common/generic/sorting').call(this, this)
|
|
39
|
-
require('../../common/generic/resolve-mock').call(this, this)
|
|
40
42
|
|
|
41
43
|
// draft handlers needed?
|
|
42
44
|
// REVISIT: serve 2 fiori
|
|
@@ -111,6 +113,39 @@ class ApplicationService extends cds.Service {
|
|
|
111
113
|
})
|
|
112
114
|
}
|
|
113
115
|
}
|
|
116
|
+
|
|
117
|
+
// Overload .handle in order to resolve projections up to a definition that is known by the remote service instance.
|
|
118
|
+
// Result is post processed according to the inverse projection in order to reflect the correct result of the original query.
|
|
119
|
+
async handle(req) {
|
|
120
|
+
// compat mode
|
|
121
|
+
if (req._resolved || cds.env.features.resolve_views === false) return super.handle(req)
|
|
122
|
+
|
|
123
|
+
if (req.target && req.target.name && this.definition && req.target.name.startsWith(this.definition.name + '.')) {
|
|
124
|
+
return super.handle(req)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// req.query can be:
|
|
128
|
+
// - empty object in case of unbound action/function
|
|
129
|
+
// - undefined/null in case of plain string queries
|
|
130
|
+
if (_isSimpleCqnQuery(req.query) && this.model) {
|
|
131
|
+
const q = resolveView(req.query, this.model, this)
|
|
132
|
+
const t = findQueryTarget(q) || req.target
|
|
133
|
+
|
|
134
|
+
// compat
|
|
135
|
+
restoreLink(req)
|
|
136
|
+
if (req.query.SELECT && req.query.SELECT._4odata) {
|
|
137
|
+
q.SELECT._4odata = req.query.SELECT._4odata
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// REVISIT: We need to provide target explicitly because it's cached already within ensure_target
|
|
141
|
+
const newReq = new cds.Request({ query: q, target: t, _resolved: true })
|
|
142
|
+
const result = await super.dispatch(newReq)
|
|
143
|
+
|
|
144
|
+
return postProcess(q, result)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return super.handle(req)
|
|
148
|
+
}
|
|
114
149
|
}
|
|
115
150
|
|
|
116
151
|
module.exports = ApplicationService
|
|
@@ -99,10 +99,14 @@ const getSearchableColumns = entity => {
|
|
|
99
99
|
const defaultSearchFilteredColumns = searchableColumns.filter(column => column[defaultSearchElementTerm])
|
|
100
100
|
|
|
101
101
|
if (defaultSearchFilteredColumns.length > 0) {
|
|
102
|
-
|
|
103
|
-
LOG.
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
if (!cds._deprecationWarningForDefaultSearchElement) {
|
|
103
|
+
LOG._warn &&
|
|
104
|
+
LOG.warn(
|
|
105
|
+
'Annotation "@Search.defaultSearchElement" is deprecated and will be removed in an upcoming release. Use "@cds.search" instead.'
|
|
106
|
+
)
|
|
107
|
+
cds._deprecationWarningForDefaultSearchElement = true
|
|
108
|
+
}
|
|
109
|
+
|
|
106
110
|
return defaultSearchFilteredColumns.map(column => column.name)
|
|
107
111
|
}
|
|
108
112
|
|
|
@@ -129,9 +133,11 @@ const computeColumnsToBeSearched = (cqn, entity = { _searchableColumns: [] }) =>
|
|
|
129
133
|
}
|
|
130
134
|
|
|
131
135
|
const columnRef = column.ref
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
136
|
+
if (columnRef) {
|
|
137
|
+
const columnName = columnRef[columnRef.length - 1]
|
|
138
|
+
const csnColumn = entity.elements[columnName]
|
|
139
|
+
if (!csnColumn) toBeSearched.push({ ref: [columnName] })
|
|
140
|
+
}
|
|
135
141
|
})
|
|
136
142
|
|
|
137
143
|
return toBeSearched
|
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
const { DRAFT_COLUMNS } = require('../../../common/constants/draft')
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
3
|
+
const _deepEqual = (val1, val2) => {
|
|
4
|
+
if (val1 && typeof val1 === 'object' && val2 && typeof val2 === 'object') {
|
|
5
|
+
for (const key in val1) {
|
|
6
|
+
if (!_deepEqual(val1[key], val2[key])) return false
|
|
7
|
+
}
|
|
8
|
+
return true
|
|
9
|
+
}
|
|
10
|
+
return val1 === val2
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const _getCorrespondingEntryWithSameKeys = (source, entry, keys) => {
|
|
14
|
+
const idx = _getIdxCorrespondingEntryWithSameKeys(source, entry, keys)
|
|
15
|
+
return idx !== -1 ? source[idx] : undefined
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const _getIdxCorrespondingEntryWithSameKeys = (source, entry, keys) =>
|
|
19
|
+
source.findIndex(sourceEntry => keys.every(key => _deepEqual(sourceEntry[key], entry[key])))
|
|
5
20
|
|
|
6
21
|
const _getKeysOfEntity = entity =>
|
|
7
22
|
Object.keys(entity.keys).filter(key => !DRAFT_COLUMNS.includes(key) && !entity.elements[key].isAssociation)
|
|
@@ -63,7 +78,21 @@ const _hasOpDeep = (entry, element) => {
|
|
|
63
78
|
}
|
|
64
79
|
|
|
65
80
|
const _addCompositionsToResult = (result, entity, prop, newValue, oldValue) => {
|
|
66
|
-
|
|
81
|
+
/*
|
|
82
|
+
* REVISIT: the current impl results in {} instead of keeping null for compo to one.
|
|
83
|
+
* unfortunately, many follow-up errors occur (e.g., prop in null checks) if changed.
|
|
84
|
+
*/
|
|
85
|
+
let composition
|
|
86
|
+
if (
|
|
87
|
+
newValue[prop] &&
|
|
88
|
+
typeof newValue[prop] === 'object' &&
|
|
89
|
+
!Array.isArray(newValue[prop]) &&
|
|
90
|
+
Object.keys(newValue[prop]).length === 0
|
|
91
|
+
) {
|
|
92
|
+
composition = compareJsonDeep(entity.elements[prop]._target, undefined, oldValue && oldValue[prop])
|
|
93
|
+
} else {
|
|
94
|
+
composition = compareJsonDeep(entity.elements[prop]._target, newValue[prop], oldValue && oldValue[prop])
|
|
95
|
+
}
|
|
67
96
|
if (composition.some(c => _hasOpDeep(c, entity.elements[prop]))) {
|
|
68
97
|
result[prop] = entity.elements[prop].is2one ? composition[0] : composition
|
|
69
98
|
}
|
|
@@ -246,4 +275,59 @@ const compareJson = (newValue, oldValue, entity) => {
|
|
|
246
275
|
return Array.isArray(newValue) ? result : result[0]
|
|
247
276
|
}
|
|
248
277
|
|
|
249
|
-
|
|
278
|
+
const _isObject = item => item && typeof item === 'object' && !Array.isArray(item)
|
|
279
|
+
|
|
280
|
+
const _mergeArrays = (entity, oldValue, newValue) => {
|
|
281
|
+
const merged = []
|
|
282
|
+
const foundIdxNew = []
|
|
283
|
+
const keys = _getKeysOfEntity(entity)
|
|
284
|
+
for (const entry of oldValue) {
|
|
285
|
+
const idxNew = _getIdxCorrespondingEntryWithSameKeys(newValue, entry, keys)
|
|
286
|
+
if (idxNew === -1) merged.push(entry)
|
|
287
|
+
else {
|
|
288
|
+
foundIdxNew.push(idxNew)
|
|
289
|
+
merged.push(mergeJsonDeep(entity, entry, newValue[idxNew]))
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
for (let i = 0; i < newValue.length; i++) {
|
|
293
|
+
if (!foundIdxNew.includes(i)) merged.push(newValue[i])
|
|
294
|
+
}
|
|
295
|
+
return merged
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const mergeJsonDeep = (entity, oldValue, newValue) => {
|
|
299
|
+
if (_isObject(oldValue) && _isObject(newValue)) {
|
|
300
|
+
Object.keys(newValue).forEach(key => {
|
|
301
|
+
if (_isObject(newValue[key])) {
|
|
302
|
+
if (!(key in oldValue)) Object.assign(oldValue, { [key]: newValue[key] })
|
|
303
|
+
else {
|
|
304
|
+
const target = entity && entity.elements[key] && entity.elements[key]._target
|
|
305
|
+
oldValue[key] = mergeJsonDeep(target, oldValue[key], newValue[key])
|
|
306
|
+
}
|
|
307
|
+
} else if (Array.isArray(newValue[key])) {
|
|
308
|
+
if (!(key in oldValue)) Object.assign(oldValue, { [key]: newValue[key] })
|
|
309
|
+
else {
|
|
310
|
+
const target = entity && entity.elements[key] && entity.elements[key]._target
|
|
311
|
+
if (target) {
|
|
312
|
+
oldValue[key] = _mergeArrays(target, oldValue[key], newValue[key])
|
|
313
|
+
}
|
|
314
|
+
// Can't merge items without target
|
|
315
|
+
}
|
|
316
|
+
} else {
|
|
317
|
+
Object.assign(oldValue, { [key]: newValue[key] })
|
|
318
|
+
}
|
|
319
|
+
})
|
|
320
|
+
}
|
|
321
|
+
return oldValue
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Signature similar to Object.assign(oldValue, newValue)
|
|
325
|
+
const mergeJson = (oldValue, newValue, entity) => {
|
|
326
|
+
const result = mergeJsonDeep(entity, oldValue, newValue)
|
|
327
|
+
return result
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
module.exports = {
|
|
331
|
+
compareJson,
|
|
332
|
+
mergeJson
|
|
333
|
+
}
|
|
@@ -2,7 +2,7 @@ const cds = require('../../../cds')
|
|
|
2
2
|
const LOG = cds.log('app')
|
|
3
3
|
const { SELECT } = cds.ql
|
|
4
4
|
|
|
5
|
-
const compareJson = require('./compareJson')
|
|
5
|
+
const { compareJson } = require('./compareJson')
|
|
6
6
|
const { selectDeepUpdateData } = require('../../../common/composition')
|
|
7
7
|
const { ensureDraftsSuffix } = require('../../../fiori/utils/handler')
|
|
8
8
|
|
|
@@ -43,9 +43,20 @@ module.exports = class {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
_diffDelete(req) {
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
const { DELETE } = (req._ && req._.query) || req.query
|
|
47
|
+
const query = SELECT.from(DELETE.from).columns(this._createSelectColumnsForDelete(req.target))
|
|
48
|
+
if (DELETE.where) query.where(...DELETE.where)
|
|
49
|
+
|
|
50
|
+
// REVISIT: should be done in cqn2cqn4sql
|
|
51
|
+
if (req.target._isDraftEnabled && query.SELECT.from.ref.some(r => r.where)) {
|
|
52
|
+
query.SELECT.from.ref.forEach((r, i) => {
|
|
53
|
+
if (!r.where) return
|
|
54
|
+
const j = r.where.findIndex(w => w.ref && w.ref.some(r => r === 'IsActiveEntity'))
|
|
55
|
+
if (j === -1) return
|
|
56
|
+
r.where.splice(Math.max(j - 1, 0), 4)
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
49
60
|
return cds
|
|
50
61
|
.tx(req)
|
|
51
62
|
.run(query)
|
|
@@ -53,7 +64,14 @@ module.exports = class {
|
|
|
53
64
|
}
|
|
54
65
|
|
|
55
66
|
async _addPartialPersistentState(req) {
|
|
56
|
-
const deepUpdateData = await selectDeepUpdateData(
|
|
67
|
+
const deepUpdateData = await selectDeepUpdateData(
|
|
68
|
+
this._srv.model.definitions,
|
|
69
|
+
req.query,
|
|
70
|
+
req,
|
|
71
|
+
true,
|
|
72
|
+
true,
|
|
73
|
+
this._srv
|
|
74
|
+
)
|
|
57
75
|
req._.partialPersistentState = deepUpdateData
|
|
58
76
|
}
|
|
59
77
|
|
|
@@ -68,7 +86,7 @@ module.exports = class {
|
|
|
68
86
|
const newQuery = cqn2cqn4sql(req.query, this._srv.model)
|
|
69
87
|
const combinedData = providedData || Object.assign({}, req.query.UPDATE.data || {}, req.query.UPDATE.with || {})
|
|
70
88
|
const lastTransition = newQuery.UPDATE._transitions[newQuery.UPDATE._transitions.length - 1]
|
|
71
|
-
const revertedPersistent = revertData(req._.partialPersistentState, lastTransition)
|
|
89
|
+
const revertedPersistent = revertData(req._.partialPersistentState, lastTransition, this._srv)
|
|
72
90
|
return compareJson(combinedData, revertedPersistent, req.target)
|
|
73
91
|
}
|
|
74
92
|
|
|
@@ -20,7 +20,7 @@ const _getWheres = (key, data) => {
|
|
|
20
20
|
|
|
21
21
|
const allKeysAreProvided = req => {
|
|
22
22
|
const data = req.data && (Array.isArray(req.data) ? req.data : [req.data])
|
|
23
|
-
for (const key of Object.values(req.target.keys)) {
|
|
23
|
+
for (const key of Object.values(req.target.keys || {})) {
|
|
24
24
|
if (key._isAssociationStrict || DRAFT_COLUMNS.includes(key.name)) {
|
|
25
25
|
continue
|
|
26
26
|
}
|
|
@@ -55,7 +55,7 @@ const _getSelectCQN = (req, columns) => {
|
|
|
55
55
|
|
|
56
56
|
const data = req.data && (Array.isArray(req.data) ? req.data : [req.data])
|
|
57
57
|
|
|
58
|
-
for (const key of Object.values(req.target.keys)) {
|
|
58
|
+
for (const key of Object.values(req.target.keys || {})) {
|
|
59
59
|
if (key._isAssociationStrict || DRAFT_COLUMNS.includes(key.name)) {
|
|
60
60
|
continue
|
|
61
61
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const { getCompositionTree } = require('./tree')
|
|
2
2
|
const ctUtils = require('./utils')
|
|
3
|
+
const { getEntityNameFromUpdateCQN } = require('../utils/cqn')
|
|
3
4
|
|
|
4
5
|
const { ensureNoDraftsSuffix } = require('../utils/draft')
|
|
5
6
|
const { getDBTable } = require('../utils/resolveView')
|
|
@@ -38,49 +39,50 @@ const _isSameEntity = (cqn, req) => {
|
|
|
38
39
|
return true
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
const _from = query =>
|
|
42
|
-
(query.UPDATE.entity.ref && query.UPDATE.entity.ref[0]) || query.UPDATE.entity.name || query.UPDATE.entity
|
|
43
|
-
|
|
44
42
|
const _getLinksOfCompTree = compositionTree => {
|
|
45
43
|
const links = []
|
|
44
|
+
for (const link of [...compositionTree.backLinks, ...compositionTree.customBackLinks]) {
|
|
45
|
+
links.push(link.entityKey)
|
|
46
|
+
}
|
|
46
47
|
for (const compElement of compositionTree.compositionElements || []) {
|
|
47
|
-
for (const link of compElement.
|
|
48
|
-
links.push(link.
|
|
48
|
+
for (const link of [...compElement.backLinks, ...compElement.customBackLinks]) {
|
|
49
|
+
links.push(link.targetKey)
|
|
49
50
|
}
|
|
50
51
|
}
|
|
51
52
|
return links
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
const _dataElements = entity => {
|
|
55
|
-
// REVISIT: this is expensive
|
|
56
|
-
return Object.keys(entity.elements)
|
|
57
|
-
.map(key => entity.elements[key])
|
|
58
|
-
.filter(e => !e.virtual && !e.isAssociation)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
55
|
const _whereKeys = keys => {
|
|
62
56
|
const where = []
|
|
63
57
|
keys.forEach(key => {
|
|
64
|
-
if (where.length
|
|
65
|
-
where.push('or')
|
|
66
|
-
}
|
|
58
|
+
if (where.length) where.push('or')
|
|
67
59
|
where.push('(', ...ctUtils.whereKey(key), ')')
|
|
68
60
|
})
|
|
69
61
|
return where
|
|
70
62
|
}
|
|
71
63
|
|
|
72
64
|
const _parentKey = (element, key) => {
|
|
73
|
-
|
|
65
|
+
return [...element.customBackLinks, ...element.backLinks].reduce((parentKey, customBackLink) => {
|
|
66
|
+
// TODO: why Object.prototype.hasOwnProperty?
|
|
67
|
+
parentKey[customBackLink.entityKey] = Object.prototype.hasOwnProperty.call(key, customBackLink.targetKey)
|
|
68
|
+
? key[customBackLink.targetKey]
|
|
69
|
+
: customBackLink.targetVal
|
|
74
70
|
|
|
75
|
-
|
|
76
|
-
parentKey[customBackLink.entityKey]
|
|
77
|
-
|
|
78
|
-
|
|
71
|
+
// nested
|
|
72
|
+
if (!parentKey[customBackLink.entityKey]) {
|
|
73
|
+
const splitted = customBackLink.targetKey.split('_')
|
|
74
|
+
let current
|
|
75
|
+
let joined = ''
|
|
76
|
+
while (splitted.length > 1) {
|
|
77
|
+
if (joined) joined += '_'
|
|
78
|
+
joined += splitted.shift()
|
|
79
|
+
if (Object.prototype.hasOwnProperty.call(key, joined)) current = key[joined]
|
|
80
|
+
}
|
|
81
|
+
if (current) parentKey[customBackLink.entityKey] = current[splitted[0]]
|
|
82
|
+
}
|
|
79
83
|
|
|
80
|
-
return element.backLinks.reduce((parentKey, backlink) => {
|
|
81
|
-
parentKey[backlink.entityKey] = key[backlink.targetKey] || backlink.targetVal
|
|
82
84
|
return parentKey
|
|
83
|
-
},
|
|
85
|
+
}, {})
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
const _findWhere = (data, where) => {
|
|
@@ -98,9 +100,7 @@ const _keys = (entity, data) => {
|
|
|
98
100
|
}
|
|
99
101
|
|
|
100
102
|
const _parentKeys = (element, keys) => {
|
|
101
|
-
return keys.map(key =>
|
|
102
|
-
return _parentKey(element, key)
|
|
103
|
-
})
|
|
103
|
+
return keys.map(key => _parentKey(element, key)).filter(ele => Object.keys(ele).length)
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
const _subData = (data, prop) =>
|
|
@@ -113,8 +113,9 @@ const _subData = (data, prop) =>
|
|
|
113
113
|
return result
|
|
114
114
|
}, [])
|
|
115
115
|
|
|
116
|
-
const _subWhere = (result,
|
|
116
|
+
const _subWhere = (result, element) => {
|
|
117
117
|
let where
|
|
118
|
+
const links = [...element.backLinks, ...element.customBackLinks]
|
|
118
119
|
if (links && links.length > 0) {
|
|
119
120
|
where = []
|
|
120
121
|
for (const row of result) {
|
|
@@ -122,10 +123,12 @@ const _subWhere = (result, links) => {
|
|
|
122
123
|
where.push('or')
|
|
123
124
|
}
|
|
124
125
|
const whereObj = links.reduce((res, currentLink) => {
|
|
125
|
-
|
|
126
|
+
if (Object.prototype.hasOwnProperty.call(row, currentLink.targetKey))
|
|
127
|
+
res[currentLink.entityKey] = row[currentLink.targetKey]
|
|
126
128
|
return res
|
|
127
129
|
}, {})
|
|
128
|
-
|
|
130
|
+
const whereCQN = ctUtils.whereKey(whereObj)
|
|
131
|
+
if (whereCQN.length) where.push('(', ...whereCQN, ')')
|
|
129
132
|
}
|
|
130
133
|
}
|
|
131
134
|
return where
|
|
@@ -143,17 +146,7 @@ const _mergeResults = (result, selectData, root, definitions, compositionTree, e
|
|
|
143
146
|
} else if (assoc.is2many) {
|
|
144
147
|
selectEntry[compositionTree.name] = selectEntry[compositionTree.name] || []
|
|
145
148
|
}
|
|
146
|
-
|
|
147
|
-
// adjust pk for nested composition of one
|
|
148
|
-
if (
|
|
149
|
-
(!pk || Object.keys(pk).length === 0) &&
|
|
150
|
-
assoc.isComposition &&
|
|
151
|
-
assoc.is2one &&
|
|
152
|
-
compositionTree.links.length === 1
|
|
153
|
-
) {
|
|
154
|
-
pk = { [compositionTree.links[0].targetKey]: selectEntry[compositionTree.links[0].entityKey] }
|
|
155
|
-
}
|
|
156
|
-
const newData = _findWhere(result, pk)
|
|
149
|
+
const newData = _findWhere(result, _parentKey(compositionTree, selectEntry))
|
|
157
150
|
if (assoc.is2one && newData[0]) {
|
|
158
151
|
selectEntry[compositionTree.name] = Object.assign(selectEntry[compositionTree.name], newData[0])
|
|
159
152
|
} else if (assoc.is2many) {
|
|
@@ -165,23 +158,20 @@ const _mergeResults = (result, selectData, root, definitions, compositionTree, e
|
|
|
165
158
|
}
|
|
166
159
|
|
|
167
160
|
const _columns = (entity, data, compositionTree, selectAll) => {
|
|
168
|
-
const
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
element
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
.map(element => {
|
|
183
|
-
return { ref: [element.name] }
|
|
184
|
-
})
|
|
161
|
+
const backLinkKeys = _getLinksOfCompTree(compositionTree)
|
|
162
|
+
const columns = []
|
|
163
|
+
for (const elementName in entity.elements) {
|
|
164
|
+
const element = entity.elements[elementName]
|
|
165
|
+
if (element.virtual || element.isAssociation) continue
|
|
166
|
+
if (
|
|
167
|
+
selectAll ||
|
|
168
|
+
element.key ||
|
|
169
|
+
backLinkKeys.includes(element.name) ||
|
|
170
|
+
(Array.isArray(data) && data.find(entry => element.name in entry))
|
|
171
|
+
) {
|
|
172
|
+
columns.push({ ref: [element.name] })
|
|
173
|
+
}
|
|
174
|
+
}
|
|
185
175
|
return columns
|
|
186
176
|
}
|
|
187
177
|
|
|
@@ -230,12 +220,16 @@ const _selectDeepUpdateData = async args => {
|
|
|
230
220
|
compositionTree: element,
|
|
231
221
|
entityName: element.source,
|
|
232
222
|
data: _subData(data, element.name),
|
|
233
|
-
where: _subWhere(result, element
|
|
223
|
+
where: _subWhere(result, element),
|
|
234
224
|
selectData: result,
|
|
235
225
|
parentKeys: _parentKeys(element, keys),
|
|
236
226
|
orderBy: false,
|
|
237
227
|
root: false
|
|
238
228
|
}
|
|
229
|
+
|
|
230
|
+
// REVISIT: remove null elements
|
|
231
|
+
subs.data = subs.data.filter(d => d)
|
|
232
|
+
|
|
239
233
|
return _selectDeepUpdateData({ ...args, ...subs })
|
|
240
234
|
})
|
|
241
235
|
)
|
|
@@ -247,7 +241,14 @@ const _selectDeepUpdateData = async args => {
|
|
|
247
241
|
* exports
|
|
248
242
|
*/
|
|
249
243
|
|
|
250
|
-
const selectDeepUpdateData = (
|
|
244
|
+
const selectDeepUpdateData = (
|
|
245
|
+
definitions,
|
|
246
|
+
cqn,
|
|
247
|
+
req,
|
|
248
|
+
includeAllRootColumns = false,
|
|
249
|
+
includeAllColumns = false,
|
|
250
|
+
service
|
|
251
|
+
) => {
|
|
251
252
|
// REVISIT this should be done somewhere before, so it is not done twice for deep updates
|
|
252
253
|
const sqlQuery = cqn2cqn4sql(cqn, { definitions })
|
|
253
254
|
|
|
@@ -255,7 +256,7 @@ const selectDeepUpdateData = (definitions, cqn, req, includeAllRootColumns = fal
|
|
|
255
256
|
return Promise.resolve(req._.partialPersistentState)
|
|
256
257
|
}
|
|
257
258
|
|
|
258
|
-
const from =
|
|
259
|
+
const from = getEntityNameFromUpdateCQN(sqlQuery)
|
|
259
260
|
const alias = sqlQuery.UPDATE.entity.as
|
|
260
261
|
const where = sqlQuery.UPDATE.where || []
|
|
261
262
|
const entityName = ensureNoDraftsSuffix(from)
|
|
@@ -266,7 +267,8 @@ const selectDeepUpdateData = (definitions, cqn, req, includeAllRootColumns = fal
|
|
|
266
267
|
definitions,
|
|
267
268
|
rootEntityName: entityName, // REVISIT: drafts are resolved too eagerly
|
|
268
269
|
checkRoot: false,
|
|
269
|
-
resolveViews: !draft
|
|
270
|
+
resolveViews: !draft,
|
|
271
|
+
service
|
|
270
272
|
})
|
|
271
273
|
|
|
272
274
|
return _selectDeepUpdateData({
|
|
@@ -281,8 +283,9 @@ const selectDeepUpdateData = (definitions, cqn, req, includeAllRootColumns = fal
|
|
|
281
283
|
includeAllRootColumns,
|
|
282
284
|
singleton: req && req.target && req.target._isSingleton,
|
|
283
285
|
alias,
|
|
284
|
-
includeAllColumns: cqn._selectAll,
|
|
285
|
-
root: true
|
|
286
|
+
includeAllColumns: cqn._selectAll || includeAllColumns,
|
|
287
|
+
root: true,
|
|
288
|
+
service
|
|
286
289
|
})
|
|
287
290
|
}
|
|
288
291
|
|