@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
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
const cds = require('../../../cds')
|
|
2
|
+
|
|
3
|
+
const { getDataSubject } = require('../../../common/utils/csn')
|
|
4
|
+
const { getBackLinks, isSelfManaged } = require('../../../common/utils/backlinks')
|
|
5
|
+
|
|
6
|
+
const WRITE = { CREATE: 1, UPDATE: 1, DELETE: 1 }
|
|
7
|
+
const ASPECTS = { CREATE: '_auditCreate', READ: '_auditRead', UPDATE: '_auditUpdate', DELETE: '_auditDelete' }
|
|
8
|
+
|
|
9
|
+
const getMapKeyForCurrentRequest = req => {
|
|
10
|
+
// running in srv or db layer? -> srv's req.query used as key of diff and logs maps at req.context
|
|
11
|
+
return req._tx.constructor.name.match(/Database$/i) ? req._.query : req.query
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const getRootEntity = element => {
|
|
15
|
+
let entity = element.parent
|
|
16
|
+
while (entity.kind !== 'entity') entity = entity.parent
|
|
17
|
+
return entity
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const getPick = event => {
|
|
21
|
+
return (element, target) => {
|
|
22
|
+
if (!target[ASPECTS[event]]) return
|
|
23
|
+
const categories = []
|
|
24
|
+
if (!element.isAssociation && element.key) categories.push('ObjectID')
|
|
25
|
+
if (
|
|
26
|
+
element.parent['@PersonalData.EntitySemantics'] === 'DataSubject' &&
|
|
27
|
+
element['@PersonalData.FieldSemantics'] === 'DataSubjectID'
|
|
28
|
+
)
|
|
29
|
+
categories.push('DataSubjectID')
|
|
30
|
+
if (event in WRITE && element['@PersonalData.IsPotentiallyPersonal']) categories.push('IsPotentiallyPersonal')
|
|
31
|
+
if (element['@PersonalData.IsPotentiallySensitive']) categories.push('IsPotentiallySensitive')
|
|
32
|
+
if (categories.length) return { categories }
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const _getHash = (entity, row) => {
|
|
37
|
+
return `${entity.name}(${Object.keys(entity.keys)
|
|
38
|
+
.map(k => `${k}=${row[k]}`)
|
|
39
|
+
.join(',')})`
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const createLogEntry = (logs, entity, row) => {
|
|
43
|
+
const hash = _getHash(entity, row)
|
|
44
|
+
let log = logs[hash]
|
|
45
|
+
if (!log) {
|
|
46
|
+
logs[hash] = {
|
|
47
|
+
dataObject: { type: entity.name, id: [] },
|
|
48
|
+
dataSubject: { id: [], role: entity['@PersonalData.DataSubjectRole'] },
|
|
49
|
+
attributes: [],
|
|
50
|
+
attachments: []
|
|
51
|
+
}
|
|
52
|
+
log = logs[hash]
|
|
53
|
+
}
|
|
54
|
+
return log
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const addObjectID = (log, row, key) => {
|
|
58
|
+
if (!log.dataObject.id.find(ele => ele.keyName === key)) log.dataObject.id.push({ keyName: key, value: row[key] })
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const addDataSubject = (log, row, key, entity) => {
|
|
62
|
+
if (!log.dataSubject.type) log.dataSubject.type = entity.name
|
|
63
|
+
if (!log.dataSubject.id.find(ele => ele.key === key)) {
|
|
64
|
+
const value = row[key] || (row._old && row._old[key])
|
|
65
|
+
log.dataSubject.id.push({ keyName: key, value })
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const _addKeysToWhere = (child, row) => {
|
|
70
|
+
const keysWithValue = []
|
|
71
|
+
Object.keys(child.keys).forEach(el => {
|
|
72
|
+
if (keysWithValue.length > 0) keysWithValue.push('and')
|
|
73
|
+
keysWithValue.push({ ref: [child.name, el] }, '=', {
|
|
74
|
+
val: row[el]
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
return keysWithValue
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const _addForeignKeysToWhere = (parent, child, assoc) => {
|
|
81
|
+
const foreignKeys = []
|
|
82
|
+
const backlinks = getBackLinks(assoc)
|
|
83
|
+
let parentName, childName
|
|
84
|
+
if (assoc.isComposition && assoc.is2one && !assoc.on) {
|
|
85
|
+
// look for foreign keys in parent
|
|
86
|
+
parentName = parent.name
|
|
87
|
+
childName = child.name
|
|
88
|
+
} else if (assoc.is2many && isSelfManaged(assoc)) {
|
|
89
|
+
// look for foreign keys in child
|
|
90
|
+
parentName = child.name
|
|
91
|
+
childName = parent.name
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
backlinks.forEach(el => {
|
|
95
|
+
if (foreignKeys.length > 0) foreignKeys.push('and')
|
|
96
|
+
foreignKeys.push({ ref: [parentName, el.entityKey] }, '=', {
|
|
97
|
+
ref: [childName, el.targetKey]
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
return foreignKeys
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const _buildWhere = (child, dataSubjectInfo, row) => {
|
|
104
|
+
const where = []
|
|
105
|
+
where.push(
|
|
106
|
+
..._addForeignKeysToWhere(dataSubjectInfo.entity, child, dataSubjectInfo.assoc),
|
|
107
|
+
'and',
|
|
108
|
+
..._addKeysToWhere(child, row)
|
|
109
|
+
)
|
|
110
|
+
return where
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const _buildSubSelect = (child, dataSubjectInfo, row) => {
|
|
114
|
+
let previousCqn
|
|
115
|
+
const childCqn = SELECT.from(child.name)
|
|
116
|
+
.columns(Object.keys(child.keys))
|
|
117
|
+
.where(_buildWhere(child, dataSubjectInfo.previous[0] ? dataSubjectInfo.previous[0] : dataSubjectInfo, row))
|
|
118
|
+
if (dataSubjectInfo.previous.length > 0) {
|
|
119
|
+
let currentCQN
|
|
120
|
+
|
|
121
|
+
dataSubjectInfo.previous.forEach((el, i) => {
|
|
122
|
+
const nextEl = dataSubjectInfo.previous[i + 1]
|
|
123
|
+
const args = nextEl
|
|
124
|
+
? [nextEl.entity, el.entity, nextEl.assoc]
|
|
125
|
+
: [dataSubjectInfo.entity, el.entity, dataSubjectInfo.assoc]
|
|
126
|
+
|
|
127
|
+
currentCQN = SELECT.from(el.entity.name)
|
|
128
|
+
.columns(Object.keys(el.entity.keys))
|
|
129
|
+
.where([..._addForeignKeysToWhere(...args), 'and', 'exists', previousCqn || childCqn])
|
|
130
|
+
|
|
131
|
+
previousCqn = currentCQN
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
return previousCqn || childCqn
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const _getDataSubjectIdPromise = (child, dataSubjectInfo, row, req) => {
|
|
138
|
+
const root = dataSubjectInfo.entity
|
|
139
|
+
const cqn = SELECT.from(root.name)
|
|
140
|
+
.columns(Object.keys(root.keys))
|
|
141
|
+
.where(['exists', _buildSubSelect(child, dataSubjectInfo, row)])
|
|
142
|
+
return cds
|
|
143
|
+
.tx(req)
|
|
144
|
+
.run(cqn)
|
|
145
|
+
.then(res => {
|
|
146
|
+
const id = []
|
|
147
|
+
for (const k in res[0]) id.push({ keyName: k, value: res[0][k] })
|
|
148
|
+
return id
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const addDataSubjectForDetailsEntity = (row, log, req, entity, model) => {
|
|
153
|
+
const role = entity['@PersonalData.DataSubjectRole']
|
|
154
|
+
|
|
155
|
+
const dataSubjectInfo = getDataSubject(entity, model, role)
|
|
156
|
+
|
|
157
|
+
log.dataSubject.type = dataSubjectInfo.entity.name
|
|
158
|
+
|
|
159
|
+
/*
|
|
160
|
+
* for each req (cf. $batch with atomicity) and data subject role (e.g., customer vs supplier),
|
|
161
|
+
* store (in audit data structure at context) and reuse a single promise to look up the respective data subject
|
|
162
|
+
*/
|
|
163
|
+
const mapKey = getMapKeyForCurrentRequest(req)
|
|
164
|
+
if (!req.context._audit.dataSubjects) req.context._audit.dataSubjects = new Map()
|
|
165
|
+
if (!req.context._audit.dataSubjects.has(mapKey)) req.context._audit.dataSubjects.set(mapKey, new Map())
|
|
166
|
+
const map = req.context._audit.dataSubjects.get(mapKey)
|
|
167
|
+
if (map.has(role)) log.dataSubject.id = map.get(role)
|
|
168
|
+
else map.set(role, _getDataSubjectIdPromise(entity, dataSubjectInfo, row, req))
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const resolveDataSubjectPromises = async logs => {
|
|
172
|
+
const dataSubjPromise = logs.filter(el => el.dataSubject.id instanceof Promise)
|
|
173
|
+
const res = await Promise.all(dataSubjPromise.map(el => el.dataSubject.id))
|
|
174
|
+
dataSubjPromise.forEach((el, i) => (el.dataSubject.id = res[i]))
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
module.exports = {
|
|
178
|
+
getMapKeyForCurrentRequest,
|
|
179
|
+
getRootEntity,
|
|
180
|
+
getPick,
|
|
181
|
+
createLogEntry,
|
|
182
|
+
addObjectID,
|
|
183
|
+
addDataSubject,
|
|
184
|
+
addDataSubjectForDetailsEntity,
|
|
185
|
+
resolveDataSubjectPromises
|
|
186
|
+
}
|
|
@@ -5,16 +5,22 @@ const { getObjectAndDataSubject, getAttributeToLog } = require('./log')
|
|
|
5
5
|
|
|
6
6
|
function connect(credentials) {
|
|
7
7
|
return new Promise((resolve, reject) => {
|
|
8
|
+
let auditLogging
|
|
9
|
+
try {
|
|
10
|
+
auditLogging = require('@sap/audit-logging')
|
|
11
|
+
} catch (e) {
|
|
12
|
+
LOG._warn &&
|
|
13
|
+
LOG.warn('Unable to require module @sap/audit-logging. Make sure it is installed if audit logging is required.')
|
|
14
|
+
return resolve()
|
|
15
|
+
}
|
|
8
16
|
try {
|
|
9
|
-
const auditLogging = require('@sap/audit-logging')
|
|
10
17
|
auditLogging.v2(credentials, function (err, auditLog) {
|
|
11
18
|
if (err) return reject(err)
|
|
12
19
|
resolve(auditLog)
|
|
13
20
|
})
|
|
14
21
|
} catch (e) {
|
|
15
|
-
LOG._warn &&
|
|
16
|
-
|
|
17
|
-
resolve()
|
|
22
|
+
LOG._warn && LOG.warn('Unable to initialize audit-logging client with error:', e)
|
|
23
|
+
return resolve()
|
|
18
24
|
}
|
|
19
25
|
})
|
|
20
26
|
}
|
|
@@ -3,16 +3,16 @@ const LOG = cds.log('odata')
|
|
|
3
3
|
|
|
4
4
|
const OData = require('./OData')
|
|
5
5
|
|
|
6
|
-
function _createNewService(csn, defaultOptions) {
|
|
6
|
+
function _createNewService(name, csn, defaultOptions) {
|
|
7
7
|
const reflectedModel = cds.linked(cds.compile.for.odata(csn))
|
|
8
8
|
const options = Object.assign({}, defaultOptions, { reflectedModel })
|
|
9
9
|
|
|
10
|
-
const service = new cds.ApplicationService(csn, options)
|
|
10
|
+
const service = new cds.ApplicationService(name, csn, options)
|
|
11
11
|
service.init()
|
|
12
12
|
if (options.impl) service.impl(options.impl)
|
|
13
13
|
service._isExtended = true
|
|
14
14
|
|
|
15
|
-
const edm = cds.compile.to.edm(csn, { service:
|
|
15
|
+
const edm = cds.compile.to.edm(csn, { service: name })
|
|
16
16
|
const odataService = new OData(edm, csn, options)
|
|
17
17
|
odataService.addCDSServiceToChannel(service)
|
|
18
18
|
|
|
@@ -43,7 +43,7 @@ class Dispatcher {
|
|
|
43
43
|
|
|
44
44
|
const csn = await cds.mtx.getCsn(tenant)
|
|
45
45
|
|
|
46
|
-
resolve(_createNewService(csn, this._odata._options))
|
|
46
|
+
resolve(_createNewService(this._odata._cdsService.definition.name, csn, this._odata._options))
|
|
47
47
|
} catch (e) {
|
|
48
48
|
reject(e)
|
|
49
49
|
}
|
|
@@ -68,7 +68,7 @@ class Dispatcher {
|
|
|
68
68
|
const toggles = (req.features && Object.keys(req.features)) || []
|
|
69
69
|
const csn = await this._mps.csn(tenant, 'dummy', toggles)
|
|
70
70
|
|
|
71
|
-
resolve(_createNewService(csn, this._odata._options))
|
|
71
|
+
resolve(_createNewService(this._odata._cdsService.definition.name, csn, this._odata._options))
|
|
72
72
|
} catch (e) {
|
|
73
73
|
reject(e)
|
|
74
74
|
}
|
|
@@ -102,7 +102,6 @@ class OData {
|
|
|
102
102
|
* @param {object} [options] - optional object with options.
|
|
103
103
|
* @param {object} [options.logger] - optional logger object to be used in the odata library.
|
|
104
104
|
* @param {string} [options.logLevel] - optional log level to be used according to winston/npm specification.
|
|
105
|
-
* @param {string} [options.service] - Service name as specified in CSN.
|
|
106
105
|
* @param {boolean} [options.crashOnError] - Application should crash on error. Defaults to true.
|
|
107
106
|
*
|
|
108
107
|
* @throws Error in case no or an invalid csn model is provided.
|
|
@@ -214,14 +213,14 @@ class OData {
|
|
|
214
213
|
})
|
|
215
214
|
|
|
216
215
|
this._odataService.use(LOCALE_NEGOTIATOR, _language)
|
|
217
|
-
this._odataService.use(METADATA_HANDLER, _metadata(cdsService
|
|
216
|
+
this._odataService.use(METADATA_HANDLER, _metadata(cdsService))
|
|
218
217
|
|
|
219
|
-
this._odataService.use(DATA_CREATE_HANDLER, _create(cdsService
|
|
220
|
-
this._odataService.use(DATA_READ_HANDLER, _read(cdsService
|
|
221
|
-
this._odataService.use(DATA_UPDATE_HANDLER, _update(cdsService
|
|
222
|
-
this._odataService.use(DATA_DELETE_HANDLER, _delete(cdsService
|
|
218
|
+
this._odataService.use(DATA_CREATE_HANDLER, _create(cdsService))
|
|
219
|
+
this._odataService.use(DATA_READ_HANDLER, _read(cdsService))
|
|
220
|
+
this._odataService.use(DATA_UPDATE_HANDLER, _update(cdsService))
|
|
221
|
+
this._odataService.use(DATA_DELETE_HANDLER, _delete(cdsService))
|
|
223
222
|
|
|
224
|
-
this._odataService.use(ACTION_EXECUTE_HANDLER, _action(cdsService
|
|
223
|
+
this._odataService.use(ACTION_EXECUTE_HANDLER, _action(cdsService))
|
|
225
224
|
}
|
|
226
225
|
|
|
227
226
|
// _startPerfMeasurementOData (req) {
|
|
@@ -11,7 +11,8 @@ const { actionAndFunctionQueries, getActionOrFunctionReturnType } = require('../
|
|
|
11
11
|
const { validateResourcePath } = require('../utils/request')
|
|
12
12
|
const readAfterWrite = require('../utils/readAfterWrite')
|
|
13
13
|
const { setStatusCodeAndHeader, getKeyProperty } = require('../../../../fiori/utils/handler')
|
|
14
|
-
const { toODataResult,
|
|
14
|
+
const { toODataResult, postProcess } = require('../utils/result')
|
|
15
|
+
const { mergeJson } = require('../../../services/utils/compareJson')
|
|
15
16
|
|
|
16
17
|
/*
|
|
17
18
|
* Get the returns object for the (un)bound action from CSN.
|
|
@@ -42,9 +43,7 @@ const _postProcessDraftActivate = async (req, result, service) => {
|
|
|
42
43
|
// update req.data (keys needed in readAfterWrite)
|
|
43
44
|
req.data = result
|
|
44
45
|
const dataInDb = await readAfterWrite(req, service)
|
|
45
|
-
|
|
46
|
-
const virtuals = getVirtualsFromResult(req.target, result)
|
|
47
|
-
result = Object.assign(dataInDb[0] || result, virtuals)
|
|
46
|
+
if (dataInDb.length) result = mergeJson(dataInDb[0], result, req.target)
|
|
48
47
|
|
|
49
48
|
// add static draft columns
|
|
50
49
|
result.IsActiveEntity = true
|
|
@@ -74,14 +73,13 @@ const _postProcess = async (req, odataReq, odataRes, tx, result) => {
|
|
|
74
73
|
* The handler that will be registered with odata-v4.
|
|
75
74
|
*
|
|
76
75
|
* @param {import('../../../services/Service')} service
|
|
77
|
-
* @param {object} options
|
|
78
76
|
* @returns {function}
|
|
79
77
|
*/
|
|
80
|
-
const action =
|
|
78
|
+
const action = service => {
|
|
81
79
|
return async (odataReq, odataRes, next) => {
|
|
82
80
|
let req
|
|
83
81
|
try {
|
|
84
|
-
validateResourcePath(odataReq,
|
|
82
|
+
validateResourcePath(odataReq, service)
|
|
85
83
|
req = new ODataRequest(ACTION_EXECUTE_HANDLER, service, odataReq, odataRes)
|
|
86
84
|
} catch (e) {
|
|
87
85
|
return next(e)
|
|
@@ -9,20 +9,20 @@ const { getSapMessages } = require('../../../../common/error/frontend')
|
|
|
9
9
|
const { validateResourcePath } = require('../utils/request')
|
|
10
10
|
const { isReturnMinimal } = require('../utils/handlerUtils')
|
|
11
11
|
const readAfterWrite = require('../utils/readAfterWrite')
|
|
12
|
-
const { toODataResult,
|
|
12
|
+
const { toODataResult, postProcess } = require('../utils/result')
|
|
13
|
+
const { mergeJson } = require('../../../services/utils/compareJson')
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* The handler that will be registered with odata-v4.
|
|
16
17
|
*
|
|
17
18
|
* @param {import('../../../services/Service')} service
|
|
18
|
-
* @param {object} options
|
|
19
19
|
* @returns {function}
|
|
20
20
|
*/
|
|
21
|
-
const create =
|
|
21
|
+
const create = service => {
|
|
22
22
|
return async (odataReq, odataRes, next) => {
|
|
23
23
|
let req
|
|
24
24
|
try {
|
|
25
|
-
validateResourcePath(odataReq,
|
|
25
|
+
validateResourcePath(odataReq, service)
|
|
26
26
|
req = new ODataRequest(DATA_CREATE_HANDLER, service, odataReq, odataRes)
|
|
27
27
|
} catch (e) {
|
|
28
28
|
return next(e)
|
|
@@ -40,9 +40,7 @@ const create = (service, options) => {
|
|
|
40
40
|
// REVISIT: find better solution
|
|
41
41
|
if (req._.readAfterWrite) {
|
|
42
42
|
const dataInDb = await readAfterWrite(req, service)
|
|
43
|
-
|
|
44
|
-
const virtuals = getVirtualsFromResult(req.target, result)
|
|
45
|
-
result = Object.assign(dataInDb[0] || result, virtuals)
|
|
43
|
+
if (dataInDb.length) result = mergeJson(dataInDb[0], result, req.target)
|
|
46
44
|
}
|
|
47
45
|
|
|
48
46
|
postProcess(req, odataRes, service, result)
|
|
@@ -12,14 +12,13 @@ const { validateResourcePath } = require('../utils/request')
|
|
|
12
12
|
* The handler that will be registered with odata-v4.
|
|
13
13
|
*
|
|
14
14
|
* @param {import('../../../services/Service')} service
|
|
15
|
-
* @param {object} options
|
|
16
15
|
* @returns {function}
|
|
17
16
|
*/
|
|
18
|
-
const del =
|
|
17
|
+
const del = service => {
|
|
19
18
|
return async (odataReq, odataRes, next) => {
|
|
20
19
|
let req
|
|
21
20
|
try {
|
|
22
|
-
validateResourcePath(odataReq,
|
|
21
|
+
validateResourcePath(odataReq, service)
|
|
23
22
|
req = new ODataRequest(DATA_DELETE_HANDLER, service, odataReq, odataRes)
|
|
24
23
|
} catch (e) {
|
|
25
24
|
return next(e)
|
|
@@ -112,6 +112,10 @@ const getErrorHandler = (crashOnError = true, srv) => {
|
|
|
112
112
|
err = _betterOkraError(err)
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
// add content id if not generated by okra ("~...")
|
|
116
|
+
const contentId = odataReq.getOdataRequestId()
|
|
117
|
+
if (contentId && !contentId.match(/^~/)) err['@Core.ContentID'] = contentId
|
|
118
|
+
|
|
115
119
|
const { error, statusCode } = normalizeError(err, req)
|
|
116
120
|
|
|
117
121
|
next(null, Object.assign(error, { statusCode }))
|
|
@@ -29,10 +29,9 @@ const _get4Toggles = async (tenant, locale, service, req) => {
|
|
|
29
29
|
* Provide localized metadata handler.
|
|
30
30
|
*
|
|
31
31
|
* @param {object} service
|
|
32
|
-
* @param {object} options
|
|
33
32
|
* @returns {Function}
|
|
34
33
|
*/
|
|
35
|
-
const metadata =
|
|
34
|
+
const metadata = service => {
|
|
36
35
|
return async (odataReq, odataRes, next) => {
|
|
37
36
|
try {
|
|
38
37
|
const req = odataReq.getIncomingRequest()
|
|
@@ -51,8 +50,12 @@ const metadata = (service, options) => {
|
|
|
51
50
|
}
|
|
52
51
|
|
|
53
52
|
if (!edmx) {
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
edmx = cds.localize(
|
|
54
|
+
service.model,
|
|
55
|
+
locale,
|
|
56
|
+
// REVISIT: we could cache this in a weak map
|
|
57
|
+
cds.compile.to.edmx(service.model, { service: service.definition.name })
|
|
58
|
+
)
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
return next(null, toODataResult(edmx))
|
|
@@ -20,7 +20,7 @@ const { validateResourcePath } = require('../utils/request')
|
|
|
20
20
|
const { toODataResult, postProcess } = require('../utils/result')
|
|
21
21
|
const { isStreaming, getStreamProperties } = require('../utils/stream')
|
|
22
22
|
const { resolveStructuredName } = require('../utils/handlerUtils')
|
|
23
|
-
const {
|
|
23
|
+
const { ensureNoDraftsSuffix } = require('../../../../common/utils/draft')
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Checks whether a bound function or function import is invoked.
|
|
@@ -225,7 +225,7 @@ const _readEntityOrProperty = async (tx, req, segments) => {
|
|
|
225
225
|
const name = resolveStructuredName(segments, segments.length - 2)
|
|
226
226
|
const res = _getResult(name, result[0])
|
|
227
227
|
|
|
228
|
-
const odataResult = toODataResult(res[propertyElement.getName()])
|
|
228
|
+
const odataResult = toODataResult(typeof res === 'object' ? res[propertyElement.getName()] : res)
|
|
229
229
|
if (req.target._etag) odataResult['*@odata.etag'] = res[req.target._etag]
|
|
230
230
|
|
|
231
231
|
// property is read via a to one association and last segment is not $value
|
|
@@ -373,6 +373,17 @@ const _readAndTransform = (tx, req, odataReq) => {
|
|
|
373
373
|
return _readCollection(tx, req, odataReq)
|
|
374
374
|
}
|
|
375
375
|
|
|
376
|
+
// REVISIT: move to afterburner
|
|
377
|
+
if (segments[segments.length - 1]._isStreamByDollarValue) {
|
|
378
|
+
for (const k in req.target.elements) {
|
|
379
|
+
if (req.target.elements[k]['@Core.MediaType']) {
|
|
380
|
+
req.query.SELECT.columns = [{ ref: [k] }]
|
|
381
|
+
break
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return _readStream(tx, req, segments)
|
|
385
|
+
}
|
|
386
|
+
|
|
376
387
|
if (isStreaming(segments)) {
|
|
377
388
|
return _readStream(tx, req, segments)
|
|
378
389
|
}
|
|
@@ -404,6 +415,41 @@ const _removeKeysForParams = result => {
|
|
|
404
415
|
return options
|
|
405
416
|
}
|
|
406
417
|
|
|
418
|
+
const _getTarget = (ref, target, definitions) => {
|
|
419
|
+
if (cds.env.effective.odata.proxies) {
|
|
420
|
+
const target_ = target.elements[ref[0]]
|
|
421
|
+
|
|
422
|
+
if (ref.length === 1) {
|
|
423
|
+
return definitions[ensureNoDraftsSuffix(target_.target)]
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return _getTarget(ref.slice(1), target_, definitions)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const target_ = target.elements[ref.join('_')]
|
|
430
|
+
return definitions[ensureNoDraftsSuffix(target_.target)]
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const _getRestrictedExpand = (columns, target, definitions) => {
|
|
434
|
+
if (!columns || !target) return
|
|
435
|
+
|
|
436
|
+
const annotation = target['@Capabilities.ExpandRestrictions.NonExpandableProperties']
|
|
437
|
+
const restrictions = annotation && annotation.map(element => element['='])
|
|
438
|
+
|
|
439
|
+
for (const col of columns) {
|
|
440
|
+
if (col.expand) {
|
|
441
|
+
if (restrictions && restrictions.length !== 0) {
|
|
442
|
+
const ref = col.ref.join('_')
|
|
443
|
+
const ref_ = restrictions.find(element => element.replace(/\./g, '_') === ref)
|
|
444
|
+
if (ref_) return ref_
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const restricted = _getRestrictedExpand(col.expand, _getTarget(col.ref, target, definitions), definitions)
|
|
448
|
+
if (restricted) return restricted
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
407
453
|
/**
|
|
408
454
|
* The handler that will be registered with odata-v4.
|
|
409
455
|
*
|
|
@@ -415,25 +461,31 @@ const _removeKeysForParams = result => {
|
|
|
415
461
|
* In all other failure cases it calls next with error to return a 500.
|
|
416
462
|
*
|
|
417
463
|
* @param {import('../../../services/Service')} service
|
|
418
|
-
* @param {object} options
|
|
419
464
|
* @returns {function}
|
|
420
465
|
*/
|
|
421
|
-
const read =
|
|
466
|
+
const read = service => {
|
|
422
467
|
return async (odataReq, odataRes, next) => {
|
|
423
468
|
let req
|
|
424
469
|
try {
|
|
425
|
-
validateResourcePath(odataReq,
|
|
470
|
+
validateResourcePath(odataReq, service)
|
|
426
471
|
req = new ODataRequest(DATA_READ_HANDLER, service, odataReq, odataRes)
|
|
427
472
|
} catch (e) {
|
|
428
473
|
return next(e)
|
|
429
474
|
}
|
|
430
475
|
|
|
476
|
+
const restricted = _getRestrictedExpand(
|
|
477
|
+
req.query.SELECT && req.query.SELECT.columns,
|
|
478
|
+
req.target,
|
|
479
|
+
service.model.definitions
|
|
480
|
+
)
|
|
481
|
+
if (restricted) {
|
|
482
|
+
return next(getError(400, 'EXPAND_IS_RESTRICTED', [restricted]))
|
|
483
|
+
}
|
|
484
|
+
|
|
431
485
|
const changeset = odataReq.getAtomicityGroupId()
|
|
432
486
|
const tx = changeset ? odataReq.getBatchApplicationData().txs[changeset] : service.tx(req)
|
|
433
487
|
cds.context = tx
|
|
434
488
|
|
|
435
|
-
const virtuals = getVirtuals(req, service.model)
|
|
436
|
-
|
|
437
489
|
let result, err, commit
|
|
438
490
|
let additional = {}
|
|
439
491
|
try {
|
|
@@ -444,7 +496,6 @@ const read = (service, options) => {
|
|
|
444
496
|
result = { value: null }
|
|
445
497
|
} else {
|
|
446
498
|
_postProcess(odataReq, req, odataRes, service, result)
|
|
447
|
-
postProcessVirtuals(virtuals, result)
|
|
448
499
|
additional = _removeKeysForParams(result)
|
|
449
500
|
}
|
|
450
501
|
|
|
@@ -11,7 +11,7 @@ module.exports = srv => {
|
|
|
11
11
|
const req = odataReq.getBatchApplicationData()
|
|
12
12
|
? odataReq.getBatchApplicationData().req
|
|
13
13
|
: odataReq.getIncomingRequest()
|
|
14
|
-
const { res, user, path } = req
|
|
14
|
+
const { res, user, path, headers } = req
|
|
15
15
|
|
|
16
16
|
const { protectMetadata } = cds.env.odata
|
|
17
17
|
if (protectMetadata === false && (path === '/' || path.endsWith('/$metadata'))) {
|
|
@@ -42,6 +42,16 @@ module.exports = srv => {
|
|
|
42
42
|
* that can be used in ATOMICITY_GROUP_START and ATOMICITY_GROUP_END
|
|
43
43
|
*/
|
|
44
44
|
if (path.endsWith('/$batch')) {
|
|
45
|
+
// ensure content type
|
|
46
|
+
const ct = headers['content-type'] || ''
|
|
47
|
+
if (!ct.match(/multipart\/mixed/) && !ct.match(/application\/json/)) {
|
|
48
|
+
return next({
|
|
49
|
+
statusCode: 400,
|
|
50
|
+
code: '400',
|
|
51
|
+
message: 'Batch requests must have content type multipart/mixed or application/json'
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
45
55
|
odataReq.setApplicationData({ req })
|
|
46
56
|
}
|
|
47
57
|
|
|
@@ -11,8 +11,9 @@ const { validateResourcePath } = require('../utils/request')
|
|
|
11
11
|
const { isReturnMinimal } = require('../utils/handlerUtils')
|
|
12
12
|
const { foreignKeyPropagations } = require('../../../../common/utils/foreignKeyPropagations')
|
|
13
13
|
const readAfterWrite = require('../utils/readAfterWrite')
|
|
14
|
-
const { toODataResult,
|
|
14
|
+
const { toODataResult, postProcess } = require('../utils/result')
|
|
15
15
|
const { hasOmitValuesPreference } = require('../utils/omitValues')
|
|
16
|
+
const { mergeJson } = require('../../../services/utils/compareJson')
|
|
16
17
|
|
|
17
18
|
const _isUpsertAllowed = target => {
|
|
18
19
|
return !(cds.env.runtime && cds.env.runtime.allow_upsert === false) && !(target && target._isDraftEnabled)
|
|
@@ -21,7 +22,7 @@ const _isUpsertAllowed = target => {
|
|
|
21
22
|
const _infoForeignKeyInParent = (req, odataReq, odataRes, tx) => {
|
|
22
23
|
const info = {}
|
|
23
24
|
// keys not in data
|
|
24
|
-
if (Object.keys(req.target.keys).some(key => Object.keys(req.data).includes(key))) {
|
|
25
|
+
if (req.target.keys && Object.keys(req.target.keys).some(key => Object.keys(req.data).includes(key))) {
|
|
25
26
|
return info
|
|
26
27
|
}
|
|
27
28
|
|
|
@@ -119,11 +120,7 @@ const _updateThenCreate = async (req, odataReq, odataRes, tx) => {
|
|
|
119
120
|
|
|
120
121
|
const _readAfterWriteAndVirtuals = async (req, service, result) => {
|
|
121
122
|
const dataInDb = await readAfterWrite(req, service)
|
|
122
|
-
|
|
123
|
-
// REVISIT: may be a problem on direct update of a property (which we don't support yet)
|
|
124
|
-
// augment data in db so values of virtual properties are kept
|
|
125
|
-
const virtuals = getVirtualsFromResult(req.target, result)
|
|
126
|
-
if (dataInDb.length) result = Object.assign(dataInDb[0] || result, virtuals)
|
|
123
|
+
if (dataInDb.length) result = mergeJson(dataInDb[0], result, req.target)
|
|
127
124
|
return result
|
|
128
125
|
}
|
|
129
126
|
|
|
@@ -137,14 +134,13 @@ const _shouldReadPreviousResult = req =>
|
|
|
137
134
|
* In case of error it calls next with error.
|
|
138
135
|
*
|
|
139
136
|
* @param {import('../../../services/Service')} service
|
|
140
|
-
* @param {object} options
|
|
141
137
|
* @returns {function}
|
|
142
138
|
*/
|
|
143
|
-
const update =
|
|
139
|
+
const update = service => {
|
|
144
140
|
return async (odataReq, odataRes, next) => {
|
|
145
141
|
let req
|
|
146
142
|
try {
|
|
147
|
-
validateResourcePath(odataReq,
|
|
143
|
+
validateResourcePath(odataReq, service)
|
|
148
144
|
req = new ODataRequest(DATA_UPDATE_HANDLER, service, odataReq, odataRes)
|
|
149
145
|
} catch (e) {
|
|
150
146
|
return next(e)
|
|
@@ -16,8 +16,6 @@ const _binaryOperatorToCQN = new Map([
|
|
|
16
16
|
[BinaryOperatorKind.LT, '<']
|
|
17
17
|
])
|
|
18
18
|
|
|
19
|
-
const toStrMethods = ['year', 'month', 'day', 'second', 'hour', 'minute']
|
|
20
|
-
|
|
21
19
|
class ExpressionToCQN {
|
|
22
20
|
constructor(entity, model, columns = []) {
|
|
23
21
|
this._model = model
|
|
@@ -196,55 +194,14 @@ class ExpressionToCQN {
|
|
|
196
194
|
}
|
|
197
195
|
/* eslint-enable complexity */
|
|
198
196
|
|
|
199
|
-
_fillAfterDot(val) {
|
|
200
|
-
let [beforeDot, afterDot = ''] = val.split('.')
|
|
201
|
-
|
|
202
|
-
while (afterDot.length < 3) {
|
|
203
|
-
afterDot = afterDot.concat(0)
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return `${beforeDot}.${afterDot}`
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
_convertValToString(valueObj) {
|
|
210
|
-
return `${valueObj.val < 10 ? 0 : ''}${valueObj.val}`
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
_convertForIndexOf(left, right) {
|
|
214
|
-
if (left.func === 'indexof') right.val++
|
|
215
|
-
else if (right.func === 'indexof') left.val++
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
_convertDateFunctions(arg1, arg2) {
|
|
219
|
-
if (arg1.func && toStrMethods.includes(arg1.func) && arg1.args) {
|
|
220
|
-
arg2.val = this._convertValToString(arg2)
|
|
221
|
-
if (arg1.func === 'second') {
|
|
222
|
-
arg2.val = this._fillAfterDot(arg2.val)
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
_convertNumbersToStringForDateFunctions(left, right) {
|
|
228
|
-
this._convertDateFunctions(left, right)
|
|
229
|
-
this._convertDateFunctions(right, left)
|
|
230
|
-
}
|
|
231
|
-
|
|
232
197
|
_ensureArr(something) {
|
|
233
198
|
return Array.isArray(something) ? something : [something]
|
|
234
199
|
}
|
|
235
200
|
|
|
236
201
|
_compare(operator, left, right, unary) {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
this._convertForIndexOf(left, right)
|
|
241
|
-
|
|
242
|
-
// sqlite requires leading 0 for numbers, this works on hana as well so we generally add it
|
|
243
|
-
this._convertNumbersToStringForDateFunctions(left, right)
|
|
244
|
-
if (unary === 'not') {
|
|
245
|
-
return [unary, left, _binaryOperatorToCQN.get(operator), right]
|
|
246
|
-
}
|
|
247
|
-
return [left, _binaryOperatorToCQN.get(operator), right]
|
|
202
|
+
return unary === 'not'
|
|
203
|
+
? [unary, '(', left, _binaryOperatorToCQN.get(operator), right, ')']
|
|
204
|
+
: [left, _binaryOperatorToCQN.get(operator), right]
|
|
248
205
|
}
|
|
249
206
|
|
|
250
207
|
_binary(expression, unary) {
|