@sap/cds 5.7.5 → 5.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +72 -0
- package/app/fiori/routes.js +1 -1
- package/bin/deploy/to-hana/cfUtil.js +251 -138
- package/bin/deploy/to-hana/gitUtil.js +55 -0
- package/bin/deploy/to-hana/hana.js +92 -93
- package/bin/deploy/to-hana/hdiDeployUtil.js +42 -27
- package/bin/deploy/to-hana/index.js +14 -13
- package/bin/mtx/in-cds.js +1 -0
- package/bin/serve.js +1 -1
- package/bin/version.js +1 -0
- package/lib/compile/cdsc.js +0 -6
- package/lib/compile/resolve.js +1 -1
- package/lib/compile/to/srvinfo.js +1 -1
- package/lib/core/classes.js +21 -1
- package/lib/env/index.js +3 -2
- package/lib/env/requires.js +4 -0
- package/lib/i18n/localize.js +5 -8
- package/lib/index.js +1 -0
- package/lib/log/errors.js +1 -1
- package/lib/ql/SELECT.js +2 -2
- package/lib/req/cds-context.js +1 -1
- package/lib/req/context.js +1 -1
- package/lib/serve/Transaction.js +9 -5
- package/lib/serve/index.js +13 -21
- package/lib/utils/tests.js +90 -66
- package/libx/_runtime/audit/generic/personal/modification.js +0 -8
- package/libx/_runtime/auth/index.js +7 -6
- package/libx/_runtime/auth/strategies/dwc.js +43 -0
- package/libx/_runtime/auth/utils.js +24 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +11 -32
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +12 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +7 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +24 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +43 -38
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +11 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/boundToCQN.js +1 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/deleteToCQN.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/orderByToCQN.js +9 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +17 -30
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +12 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/AbstractEdmStructuredType.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriHelper.js +7 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriTokenizer.js +2 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +19 -47
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +4 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +7 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/CommandExecutor.js +0 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/ConditionalRequestControlCommand.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ContextURLFactory.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/TrustedResourceJsonSerializer.js +2 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +6 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +41 -17
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +1 -17
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +60 -18
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +7 -5
- package/libx/_runtime/cds-services/adapter/rest/Rest.js +22 -1
- package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +8 -3
- package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +3 -0
- package/libx/_runtime/cds-services/services/utils/columns.js +5 -1
- package/libx/_runtime/cds-services/services/utils/compareJson.js +15 -16
- package/libx/_runtime/cds-services/services/utils/differ.js +2 -8
- package/libx/_runtime/common/aspects/Association.js +16 -0
- package/libx/_runtime/common/composition/data.js +28 -37
- package/libx/_runtime/common/composition/delete.js +107 -58
- package/libx/_runtime/common/composition/index.js +2 -1
- package/libx/_runtime/common/composition/insert.js +13 -13
- package/libx/_runtime/common/composition/update.js +39 -34
- package/libx/_runtime/common/error/frontend.js +17 -2
- package/libx/_runtime/common/generic/auth.js +20 -85
- package/libx/_runtime/common/generic/crud.js +22 -1
- package/libx/_runtime/common/i18n/messages.properties +2 -1
- package/libx/_runtime/common/utils/cqn.js +2 -6
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +95 -122
- package/libx/_runtime/common/utils/csn.js +14 -3
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +18 -1
- package/libx/_runtime/common/utils/keys.js +2 -1
- package/libx/_runtime/common/utils/path.js +1 -1
- package/libx/_runtime/common/utils/resolveView.js +12 -4
- package/libx/_runtime/common/utils/rewriteAsterisks.js +27 -13
- package/libx/_runtime/common/utils/search2cqn4sql.js +11 -6
- package/libx/_runtime/common/utils/vcap.js +27 -10
- package/libx/_runtime/db/data-conversion/post-processing.js +20 -13
- package/libx/_runtime/db/expand/expand-v2.js +21 -12
- package/libx/_runtime/db/expand/expandCQNToJoin.js +8 -6
- package/libx/_runtime/db/expand/index.js +3 -0
- package/libx/_runtime/db/generic/create.js +0 -10
- package/libx/_runtime/db/generic/index.js +3 -0
- package/libx/_runtime/db/generic/read.js +2 -24
- package/libx/_runtime/db/generic/rewrite.js +1 -3
- package/libx/_runtime/db/generic/update.js +1 -1
- package/libx/_runtime/db/query/delete.js +10 -4
- package/libx/_runtime/db/query/insert.js +3 -3
- package/libx/_runtime/db/query/read.js +4 -1
- package/libx/_runtime/db/query/update.js +5 -5
- package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +9 -2
- package/libx/_runtime/db/sql-builder/FunctionBuilder.js +3 -0
- package/libx/_runtime/db/sql-builder/index.js +3 -0
- package/libx/_runtime/db/utils/columns.js +5 -2
- package/libx/_runtime/db/utils/deep.js +6 -8
- package/libx/_runtime/db/utils/generateAliases.js +56 -6
- package/libx/_runtime/fiori/generic/before.js +73 -49
- package/libx/_runtime/fiori/generic/edit.js +14 -18
- package/libx/_runtime/fiori/generic/patch.js +8 -11
- package/libx/_runtime/fiori/generic/read.js +19 -16
- package/libx/_runtime/fiori/generic/readOverDraft.js +1 -4
- package/libx/_runtime/hana/Service.js +1 -1
- package/libx/_runtime/hana/conversion.js +10 -0
- package/libx/_runtime/hana/execute.js +33 -16
- package/libx/_runtime/hana/search.js +3 -3
- package/libx/_runtime/hana/search2cqn4sql.js +22 -21
- package/libx/_runtime/hana/searchToContains.js +1 -1
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +0 -1
- package/libx/_runtime/messaging/enterprise-messaging.js +1 -0
- package/libx/_runtime/messaging/file-based.js +3 -1
- package/libx/_runtime/messaging/service.js +4 -1
- package/libx/_runtime/remote/utils/client.js +33 -20
- package/libx/_runtime/remote/utils/data.js +52 -11
- package/libx/_runtime/sqlite/Service.js +1 -1
- package/libx/_runtime/sqlite/conversion.js +10 -0
- package/libx/_runtime/types/api.js +2 -2
- package/libx/gql/resolvers/parse/ast/enrich.js +1 -0
- package/libx/odata/afterburner.js +29 -6
- package/libx/odata/cqn2odata.js +9 -0
- package/libx/odata/grammar.pegjs +49 -21
- package/libx/odata/index.js +2 -2
- package/libx/odata/parser.js +1 -1
- package/libx/odata/utils.js +2 -2
- package/libx/rest/RestAdapter.js +29 -1
- package/libx/rest/middleware/auth.js +1 -3
- package/libx/rest/middleware/parse.js +1 -0
- package/package.json +1 -1
- package/server.js +1 -1
- package/bin/deploy/to-hana/logger.js +0 -27
- package/bin/deploy/to-hana/runCommand.js +0 -113
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +0 -37
- package/libx/_runtime/common/utils/auth.js +0 -16
|
@@ -12,9 +12,7 @@ const { DRAFT_COLUMNS_ADMIN } = require('../../common/constants/draft')
|
|
|
12
12
|
|
|
13
13
|
// copied from adapter/odata-v4/utils/context-object
|
|
14
14
|
const _getTargetEntityName = (service, pathSegments) => {
|
|
15
|
-
if (isCustomOperation(pathSegments, false))
|
|
16
|
-
return undefined
|
|
17
|
-
}
|
|
15
|
+
if (isCustomOperation(pathSegments, false)) return
|
|
18
16
|
|
|
19
17
|
let navSegmentName
|
|
20
18
|
let entityName = `${service.name}.${pathSegments[0].getEntitySet().getName()}`
|
|
@@ -36,12 +34,11 @@ const _getTargetEntityName = (service, pathSegments) => {
|
|
|
36
34
|
* @returns {object}
|
|
37
35
|
* @private
|
|
38
36
|
*/
|
|
39
|
-
const _getParent = (
|
|
37
|
+
const _getParent = (req, service) => {
|
|
40
38
|
// REVISIT: get rid of getUriInfo
|
|
41
39
|
if (!req.getUriInfo) return
|
|
42
40
|
|
|
43
41
|
const segments = req.getUriInfo().getPathSegments()
|
|
44
|
-
|
|
45
42
|
if (segments.length === 1) return
|
|
46
43
|
|
|
47
44
|
const parent = {
|
|
@@ -50,6 +47,7 @@ const _getParent = (service, req) => {
|
|
|
50
47
|
|
|
51
48
|
const parentKeyPredicates = segments[segments.length - 2].getKeyPredicates()
|
|
52
49
|
let keyPredicateName, keyPredicateText
|
|
50
|
+
|
|
53
51
|
for (const keyPredicate of parentKeyPredicates) {
|
|
54
52
|
keyPredicateName = keyPredicate.getEdmRef().getName()
|
|
55
53
|
keyPredicateText = keyPredicate.getText()
|
|
@@ -65,36 +63,28 @@ const _getParent = (service, req) => {
|
|
|
65
63
|
return parent
|
|
66
64
|
}
|
|
67
65
|
|
|
68
|
-
const _validateDraft = (draftResult,
|
|
69
|
-
if (!draftResult || draftResult.length === 0)
|
|
70
|
-
req.reject(404)
|
|
71
|
-
}
|
|
72
|
-
|
|
66
|
+
const _validateDraft = (req, draftResult, isBoundAction) => {
|
|
67
|
+
if (!draftResult || draftResult.length === 0) req.reject(404)
|
|
73
68
|
const draftAdminData = draftResult[0]
|
|
74
69
|
|
|
75
|
-
// the same user that locked the entity can always delete it
|
|
76
|
-
if (draftAdminData.InProcessByUser === req.user.id)
|
|
77
|
-
return
|
|
78
|
-
}
|
|
70
|
+
// the same user that locked the entity can always delete/update it
|
|
71
|
+
if (draftAdminData.InProcessByUser === req.user.id) return
|
|
79
72
|
|
|
80
|
-
// proceed with the delete action only if it was initiated by a different
|
|
81
|
-
// than the one who locked the entity and the configured drafts cancellation
|
|
73
|
+
// proceed with the delete/update action only if it was initiated by a different
|
|
74
|
+
// user than the one who locked the entity and the configured drafts cancellation
|
|
82
75
|
// timeout timer has expired
|
|
83
76
|
if (draftIsLocked(draftAdminData.LastChangeDateTime)) {
|
|
84
77
|
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER')
|
|
85
78
|
}
|
|
79
|
+
|
|
80
|
+
// At this point, the request user ID isn't the owner of the draft.
|
|
81
|
+
if (isBoundAction) req.reject(403)
|
|
86
82
|
}
|
|
87
83
|
|
|
88
84
|
const _addDraftDataToContext = (req, result) => {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (req.
|
|
92
|
-
return
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (!req._draftMetadata) {
|
|
96
|
-
req._draftMetadata = {}
|
|
97
|
-
}
|
|
85
|
+
if (!result || result.length === 0) return
|
|
86
|
+
if (req.rejected) return
|
|
87
|
+
if (!req._draftMetadata) req._draftMetadata = {}
|
|
98
88
|
|
|
99
89
|
DRAFT_COLUMNS_ADMIN.forEach(column => {
|
|
100
90
|
if (column in result[0]) req._draftMetadata[column] = result[0][column]
|
|
@@ -103,11 +93,7 @@ const _addDraftDataToContext = (req, result) => {
|
|
|
103
93
|
req.data.DraftAdministrativeData_DraftUUID = result[0].DraftUUID
|
|
104
94
|
}
|
|
105
95
|
|
|
106
|
-
const _prefixDraftColumns = () => {
|
|
107
|
-
return DRAFT_COLUMNS_ADMIN.map(col => {
|
|
108
|
-
return { ref: ['DRAFT_DraftAdministrativeData', col] }
|
|
109
|
-
})
|
|
110
|
-
}
|
|
96
|
+
const _prefixDraftColumns = () => DRAFT_COLUMNS_ADMIN.map(col => ({ ref: ['DRAFT_DraftAdministrativeData', col] }))
|
|
111
97
|
|
|
112
98
|
const _getSelectDraftDataCqn = (entityName, where) => {
|
|
113
99
|
return SELECT.from(ensureDraftsSuffix(entityName), _prefixDraftColumns())
|
|
@@ -116,27 +102,41 @@ const _getSelectDraftDataCqn = (entityName, where) => {
|
|
|
116
102
|
.where(where)
|
|
117
103
|
}
|
|
118
104
|
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
105
|
+
const _getDraftDataFromExistingDraft = async (req, service, parent = _getParent(req, service)) => {
|
|
106
|
+
if (parent) {
|
|
107
|
+
if (parent.IsActiveEntity === 'false') {
|
|
108
|
+
const parentWhere = [{ ref: [parent.keyName] }, '=', { val: parent.keyValue }]
|
|
109
|
+
const query = _getSelectDraftDataCqn(parent.entityName, parentWhere)
|
|
110
|
+
const result = await cds.tx(req).run(query)
|
|
111
|
+
return result
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return []
|
|
128
115
|
}
|
|
129
116
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
117
|
+
const rootWhere = getKeysCondition(req.target, req.data)
|
|
118
|
+
const query = _getSelectDraftDataCqn(ensureNoDraftsSuffix(req.target.name), rootWhere)
|
|
119
|
+
const result = await cds.tx(req).run(query)
|
|
120
|
+
return result
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const _addDraftDataFromExistingDraft = async (req, service) => {
|
|
124
|
+
const parent = _getParent(req, service)
|
|
125
|
+
const result = await _getDraftDataFromExistingDraft(req, service, parent)
|
|
126
|
+
|
|
127
|
+
if (parent) {
|
|
128
|
+
if (parent.IsActiveEntity === 'false') {
|
|
129
|
+
_validateDraft(req, result)
|
|
134
130
|
_addDraftDataToContext(req, result)
|
|
131
|
+
return result
|
|
135
132
|
}
|
|
136
|
-
|
|
133
|
+
|
|
134
|
+
return []
|
|
137
135
|
}
|
|
138
136
|
|
|
139
|
-
|
|
137
|
+
if (result && result.length > 0) _validateDraft(req, result)
|
|
138
|
+
_addDraftDataToContext(req, result)
|
|
139
|
+
return result
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
const _addGeneratedDraftUUID = async req => {
|
|
@@ -156,11 +156,12 @@ const _new = async function (req) {
|
|
|
156
156
|
if (isNavigationToMany(req)) {
|
|
157
157
|
const result = await _addDraftDataFromExistingDraft(req, this)
|
|
158
158
|
|
|
159
|
-
// in order to fix
|
|
159
|
+
// in order to fix corner case where active subitems are created in draft case
|
|
160
160
|
if (result.length === 0) req.reject(404)
|
|
161
|
-
|
|
162
|
-
_addGeneratedDraftUUID(req)
|
|
161
|
+
return
|
|
163
162
|
}
|
|
163
|
+
|
|
164
|
+
_addGeneratedDraftUUID(req)
|
|
164
165
|
}
|
|
165
166
|
|
|
166
167
|
/**
|
|
@@ -173,7 +174,7 @@ const _patchUpdate = async function (req) {
|
|
|
173
174
|
|
|
174
175
|
const result = await _addDraftDataFromExistingDraft(req, this)
|
|
175
176
|
|
|
176
|
-
// means that draft not exists
|
|
177
|
+
// means that the draft does not exists
|
|
177
178
|
if (result.length === 0) req.reject(404)
|
|
178
179
|
}
|
|
179
180
|
|
|
@@ -186,6 +187,28 @@ const _deleteCancel = async function (req) {
|
|
|
186
187
|
await _addDraftDataFromExistingDraft(req, this)
|
|
187
188
|
}
|
|
188
189
|
|
|
190
|
+
const _validateDraftBoundAction = async function (req, srv) {
|
|
191
|
+
const result = await _getDraftDataFromExistingDraft(req, srv)
|
|
192
|
+
const isBoundAction = true
|
|
193
|
+
if (result && result.length > 0) _validateDraft(req, result, isBoundAction)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const _registerBoundActionHandlers = function (entityName, actions) {
|
|
197
|
+
if (!actions) return
|
|
198
|
+
|
|
199
|
+
const boundActions = Object.values(actions).filter(
|
|
200
|
+
action =>
|
|
201
|
+
action.kind === 'action' &&
|
|
202
|
+
action.name !== 'draftPrepare' &&
|
|
203
|
+
action.name !== 'draftEdit' &&
|
|
204
|
+
action.name !== 'draftActivate'
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
for (const action of boundActions) {
|
|
208
|
+
this.before(action.name, entityName, req => _validateDraftBoundAction(req, this))
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
189
212
|
module.exports = cds.service.impl(function () {
|
|
190
213
|
_new._initial = true
|
|
191
214
|
_patchUpdate._initial = true
|
|
@@ -196,5 +219,6 @@ module.exports = cds.service.impl(function () {
|
|
|
196
219
|
this.before('NEW', entity, _new)
|
|
197
220
|
this.before(['PATCH', 'UPDATE'], entity, _patchUpdate)
|
|
198
221
|
this.before(['DELETE', 'CANCEL'], entity, _deleteCancel)
|
|
222
|
+
_registerBoundActionHandlers.call(this, entity.name, entity.actions)
|
|
199
223
|
}
|
|
200
224
|
})
|
|
@@ -56,21 +56,18 @@ const _getLockWhere = (where, columnsMap) => {
|
|
|
56
56
|
return lockWhere
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
const _select = async (
|
|
60
|
-
let allResults
|
|
61
|
-
|
|
59
|
+
const _select = async (lockRecordCQN, draftExistsCQN, selectCQNs, req, dbtx) => {
|
|
62
60
|
try {
|
|
63
|
-
|
|
64
|
-
} catch (
|
|
65
|
-
|
|
66
|
-
if (
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
req.reject(err)
|
|
61
|
+
await dbtx.run(lockRecordCQN)
|
|
62
|
+
} catch (e) {
|
|
63
|
+
const drafts = await dbtx.run(draftExistsCQN)
|
|
64
|
+
if (drafts.length) req.reject(409, 'DRAFT_ALREADY_EXISTS')
|
|
65
|
+
req.reject(409, 'ENTITY_LOCKED')
|
|
71
66
|
}
|
|
72
|
-
|
|
73
|
-
|
|
67
|
+
const promisesResults = await Promise.allSettled([dbtx.run(draftExistsCQN), ...selectCQNs.map(cqn => dbtx.run(cqn))])
|
|
68
|
+
const firstRejected = promisesResults.find(r => r.status === 'rejected')
|
|
69
|
+
if (firstRejected) req.reject(firstRejected.reason)
|
|
70
|
+
return promisesResults.map(r => r.value)
|
|
74
71
|
}
|
|
75
72
|
|
|
76
73
|
/**
|
|
@@ -127,11 +124,9 @@ const _handler = async function (req) {
|
|
|
127
124
|
}
|
|
128
125
|
}
|
|
129
126
|
|
|
130
|
-
const lockAndSelectCQNs = [lockRecordCQN, draftExistsCQN, ...selectCQNs]
|
|
131
|
-
|
|
132
127
|
const dbtx = cds.tx(req)
|
|
133
128
|
// REVISIT: Use service.read with expand **
|
|
134
|
-
const [
|
|
129
|
+
const [draftExists, ...results] = await _select(lockRecordCQN, draftExistsCQN, [...selectCQNs], req, dbtx)
|
|
135
130
|
|
|
136
131
|
if (!results[0].length) {
|
|
137
132
|
req.reject(404)
|
|
@@ -169,12 +164,13 @@ const _handler = async function (req) {
|
|
|
169
164
|
|
|
170
165
|
await Promise.all(insertCQNs.map(CQN => dbtx.run(CQN)))
|
|
171
166
|
setStatusCodeAndHeader(req._.odataRes, rootWhere, req.target.name.replace(`${this.name}.`, ''), false)
|
|
172
|
-
|
|
173
167
|
return Object.assign({}, results[0][0], { HasDraftEntity: false, HasActiveEntity: true, IsActiveEntity: false })
|
|
174
168
|
}
|
|
175
169
|
|
|
176
170
|
module.exports = cds.service.impl(function () {
|
|
177
|
-
|
|
171
|
+
const entities = Object.values(this.entities).filter(e => e._isDraftEnabled)
|
|
172
|
+
|
|
173
|
+
for (const entity of entities) {
|
|
178
174
|
this.on('EDIT', entity, _handler)
|
|
179
175
|
}
|
|
180
176
|
})
|
|
@@ -10,7 +10,6 @@ const {
|
|
|
10
10
|
} = require('../utils/handler')
|
|
11
11
|
const { getKeysCondition } = require('../utils/where')
|
|
12
12
|
const { getColumns } = require('../../cds-services/services/utils/columns')
|
|
13
|
-
|
|
14
13
|
const { DRAFT_COLUMNS_CQN } = require('../../common/constants/draft')
|
|
15
14
|
|
|
16
15
|
const _getSelectCQN = (model, { data, target: { name } }, keysCondition, checkUser = true) => {
|
|
@@ -24,6 +23,7 @@ const _getSelectCQN = (model, { data, target: { name } }, keysCondition, checkUs
|
|
|
24
23
|
),
|
|
25
24
|
...DRAFT_COLUMNS_CQN
|
|
26
25
|
]
|
|
26
|
+
|
|
27
27
|
if (checkUser) {
|
|
28
28
|
columns.push({ ref: ['DRAFT.DraftAdministrativeData', 'inProcessByUser'], as: 'draftAdmin_inProcessByUser' })
|
|
29
29
|
}
|
|
@@ -42,14 +42,16 @@ const _getSelectCQN = (model, { data, target: { name } }, keysCondition, checkUs
|
|
|
42
42
|
|
|
43
43
|
const _getUpdateDraftCQN = ({ query, target: { name } }, keysCondition) => {
|
|
44
44
|
const set = {}
|
|
45
|
+
|
|
45
46
|
for (const entry in query.UPDATE.data) {
|
|
46
47
|
if (entry === 'DraftAdministrativeData_DraftUUID') {
|
|
47
48
|
continue
|
|
48
49
|
}
|
|
50
|
+
|
|
49
51
|
set[entry] = query.UPDATE.data[entry]
|
|
50
52
|
}
|
|
51
|
-
if (set.IsActiveEntity) set.IsActiveEntity = false
|
|
52
53
|
|
|
54
|
+
if (set.IsActiveEntity) set.IsActiveEntity = false
|
|
53
55
|
return UPDATE(ensureDraftsSuffix(name)).data(set).where(keysCondition)
|
|
54
56
|
}
|
|
55
57
|
|
|
@@ -65,9 +67,7 @@ const _handler = async function (req) {
|
|
|
65
67
|
if (req.data.IsActiveEntity === 'true') req.reject(400, 'Patch can only be applied to a draft entity')
|
|
66
68
|
|
|
67
69
|
const keysCondition = getKeysCondition(req.target, req.data)
|
|
68
|
-
|
|
69
70
|
const dbtx = cds.tx(req)
|
|
70
|
-
|
|
71
71
|
let result = await dbtx.run(_getSelectCQN(this.model, req, keysCondition))
|
|
72
72
|
|
|
73
73
|
// Potential timeout scenario supported
|
|
@@ -80,19 +80,16 @@ const _handler = async function (req) {
|
|
|
80
80
|
const updateDraftAdminCQN = getUpdateDraftAdminCQN(req, result[0].DraftAdministrativeData_DraftUUID)
|
|
81
81
|
|
|
82
82
|
await Promise.all([dbtx.run(updateDraftCQN), dbtx.run(updateDraftAdminCQN)])
|
|
83
|
-
|
|
84
83
|
result = await dbtx.run(_getSelectCQN(this.model, req, keysCondition, false))
|
|
85
|
-
if (result.length === 0)
|
|
86
|
-
req.reject(404)
|
|
87
|
-
}
|
|
88
|
-
|
|
84
|
+
if (result.length === 0) req.reject(404)
|
|
89
85
|
removeDraftUUIDIfNecessary(result[0], req)
|
|
90
|
-
|
|
91
86
|
return result[0]
|
|
92
87
|
}
|
|
93
88
|
|
|
94
89
|
module.exports = cds.service.impl(function () {
|
|
95
|
-
|
|
90
|
+
const entities = Object.values(this.entities).filter(e => e._isDraftEnabled)
|
|
91
|
+
|
|
92
|
+
for (const entity of entities) {
|
|
96
93
|
this.on('PATCH', entity, _handler)
|
|
97
94
|
}
|
|
98
95
|
})
|
|
@@ -779,13 +779,16 @@ const _getDraftDoc = (req, draftName, draftWhere) => {
|
|
|
779
779
|
return draftDocs
|
|
780
780
|
}
|
|
781
781
|
|
|
782
|
-
const _getOrderByEnrichedColumns = (orderBy, columns) => {
|
|
782
|
+
const _getOrderByEnrichedColumns = (orderBy, columns, entity) => {
|
|
783
783
|
const enrichedCol = []
|
|
784
784
|
if (orderBy && orderBy.length > 1) {
|
|
785
785
|
const colNames = columns.map(el => el.ref[el.ref.length - 1])
|
|
786
786
|
// REVISIT: GET Books?$select=title&$expand=NotBooks($select=pages)&$orderby=NotBooks/title - what's then?
|
|
787
787
|
for (const el of orderBy) {
|
|
788
|
-
|
|
788
|
+
// For associations we need to 'materialise' the resulting field, otherwise we cannot access it in an outer SELECT.
|
|
789
|
+
if (entity && entity.elements[el.ref[0]] && entity.elements[el.ref[0]].isAssociation) {
|
|
790
|
+
enrichedCol.push({ ref: [...el.ref], as: _poorMansAlias4(el) })
|
|
791
|
+
} else if (!DRAFT_COLUMNS.includes(el.ref[el.ref.length - 1]) && !colNames.includes(el.ref[el.ref.length - 1])) {
|
|
789
792
|
enrichedCol.push({ ref: [...el.ref] })
|
|
790
793
|
}
|
|
791
794
|
}
|
|
@@ -807,11 +810,11 @@ const _replaceDraftAlias = where => {
|
|
|
807
810
|
|
|
808
811
|
const _poorMansAlias4 = xpr => '_' + xpr.ref.join('_') + '_'
|
|
809
812
|
|
|
810
|
-
const _getUnionCQN = (req, draftName, columns, subSelect, draftWhere, model) => {
|
|
813
|
+
const _getUnionCQN = (req, draftName, columns, subSelect, draftWhere, model, entity) => {
|
|
811
814
|
const draftActiveWhere = _getWhereForActive(draftWhere)
|
|
812
815
|
const activeDocs = getEnrichedCQN(SELECT.from(req.target), req.query.SELECT, draftActiveWhere, undefined, false)
|
|
813
816
|
activeDocs.where(_getWhereWithAppendedDraftRestrictions([], req))
|
|
814
|
-
convertWhereExists(activeDocs, model, {})
|
|
817
|
+
convertWhereExists(activeDocs.SELECT, model, {})
|
|
815
818
|
|
|
816
819
|
// @restrict.where not applicable for drafts (I can ALWAYS read mine)
|
|
817
820
|
_replaceDraftAlias(draftWhere)
|
|
@@ -838,7 +841,7 @@ const _getUnionCQN = (req, draftName, columns, subSelect, draftWhere, model) =>
|
|
|
838
841
|
return union.columns({ func: 'sum', args: [{ ref: ['$count'] }], as: '$count' })
|
|
839
842
|
}
|
|
840
843
|
|
|
841
|
-
const enrichedColumns = _getOrderByEnrichedColumns(req.query.SELECT.orderBy, columns)
|
|
844
|
+
const enrichedColumns = _getOrderByEnrichedColumns(req.query.SELECT.orderBy, columns, entity)
|
|
842
845
|
|
|
843
846
|
for (const col of enrichedColumns) {
|
|
844
847
|
// if we have columns for outer order by that may also be needed for joins, we need to duplicate them
|
|
@@ -915,12 +918,14 @@ const _excludeActiveDraftExists = (req, draftWhere, columns, model) => {
|
|
|
915
918
|
])
|
|
916
919
|
.where(_inProcessByUserWhere(req.user.id))
|
|
917
920
|
|
|
921
|
+
const targetName = ensureNoDraftsSuffix(req.target.name)
|
|
918
922
|
for (const key of _getTargetKeys(req)) {
|
|
919
|
-
subSelect.where([{ ref: [
|
|
923
|
+
subSelect.where([{ ref: [targetName, key] }, '=', { ref: [draftName, key] }])
|
|
920
924
|
}
|
|
921
925
|
|
|
926
|
+
const entity = model.definitions[targetName]
|
|
922
927
|
draftWhere = removeIsActiveEntityRecursively(draftWhere)
|
|
923
|
-
const cqn = _getUnionCQN(req, draftName, columns, subSelect, draftWhere, model)
|
|
928
|
+
const cqn = _getUnionCQN(req, draftName, columns, subSelect, draftWhere, model, entity)
|
|
924
929
|
cqn.SELECT.from.as = name
|
|
925
930
|
|
|
926
931
|
if (cqn.SELECT.orderBy) {
|
|
@@ -960,13 +965,16 @@ const _validatedWithSiblingInProcess = (req, draftWhere, draftParameters, column
|
|
|
960
965
|
)
|
|
961
966
|
return _excludeActiveDraftExists(req, draftWhere, columns, model)
|
|
962
967
|
if (
|
|
968
|
+
draftInProcessByUser &&
|
|
963
969
|
draftInProcessByUser.op === '!=' &&
|
|
964
970
|
_isValidWithDraftLocked(isActiveEntity, siblingIsActive, draftInProcessByUser)
|
|
965
971
|
) {
|
|
966
972
|
return _activeWithDraftInProcess(req, draftWhere, columns, req.user.id)
|
|
967
|
-
} else if (_isValidWithDraftTimeout(isActiveEntity, siblingIsActive, draftInProcessByUser)) {
|
|
973
|
+
} else if (draftInProcessByUser && _isValidWithDraftTimeout(isActiveEntity, siblingIsActive, draftInProcessByUser)) {
|
|
968
974
|
return _activeWithDraftInProcess(req, draftWhere, columns, null)
|
|
969
975
|
}
|
|
976
|
+
|
|
977
|
+
//
|
|
970
978
|
}
|
|
971
979
|
|
|
972
980
|
const _validatedDraftOfWhichIAmOwner = (req, draftWhere, draftParameters, columns) =>
|
|
@@ -1215,16 +1223,13 @@ const _handler = async function (req) {
|
|
|
1215
1223
|
// handle localized here as it was previously handled for req.target
|
|
1216
1224
|
req.target = _getLocalizedEntity(this.model, req.target, req.user) || req.target
|
|
1217
1225
|
|
|
1218
|
-
// REVISIT
|
|
1219
|
-
delete req.query._validationQuery
|
|
1220
|
-
|
|
1221
1226
|
const originalFrom = _copyCQNPartial(req.query.SELECT.from)
|
|
1222
1227
|
|
|
1223
1228
|
// REVISIT DRAFT HANDLING: cqn2cqn4sql must not be called here
|
|
1224
|
-
const
|
|
1229
|
+
const query4sql = cqn2cqn4sql(req.query, this.model, { _4fiori: true })
|
|
1225
1230
|
|
|
1226
1231
|
// do not clone with Object.assign as that would skip all non-enumerable properties
|
|
1227
|
-
const reqClone = { __proto__: req, query: _copyCQNPartial(
|
|
1232
|
+
const reqClone = { __proto__: req, query: _copyCQNPartial(query4sql) }
|
|
1228
1233
|
|
|
1229
1234
|
// ensure draft restrictions are copied to new query
|
|
1230
1235
|
reqClone.query._draftRestrictions = req.query._draftRestrictions
|
|
@@ -1234,6 +1239,7 @@ const _handler = async function (req) {
|
|
|
1234
1239
|
reqClone.query._streaming = true
|
|
1235
1240
|
return cds.tx(req).run(reqClone.query)
|
|
1236
1241
|
}
|
|
1242
|
+
|
|
1237
1243
|
let cqnScenario
|
|
1238
1244
|
|
|
1239
1245
|
// to replace scenario CQNs for queries with $apply SELECT chain (new odata2cqn parser)
|
|
@@ -1264,16 +1270,13 @@ const _handler = async function (req) {
|
|
|
1264
1270
|
)
|
|
1265
1271
|
|
|
1266
1272
|
_adaptSubSelects(cqnScenario.cqn, cqnScenario.scenario)
|
|
1267
|
-
|
|
1268
1273
|
_adaptAnnotationAliases(cqnScenario.cqn)
|
|
1269
1274
|
|
|
1270
1275
|
// unlocalize for db and after handlers as it was before
|
|
1271
1276
|
req.target = this.model.definitions[ensureUnlocalized(req.target.name)]
|
|
1272
1277
|
|
|
1273
1278
|
const result = await cds.tx(req).send({ query: cqnScenario.cqn, target: req.target })
|
|
1274
|
-
|
|
1275
1279
|
const resultAsArray = Array.isArray(result) ? result : result ? [result] : []
|
|
1276
|
-
|
|
1277
1280
|
removeDraftUUIDIfNecessary(resultAsArray, req)
|
|
1278
1281
|
|
|
1279
1282
|
if (cqnScenario.scenario === SCENARIO.DRAFT_ADMIN) {
|
|
@@ -45,7 +45,7 @@ const _handler = async function (req) {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
// REVISIT DRAFT HANDLING: cqn2cqn4sql must not be called here
|
|
48
|
-
const sqlQuery = cqn2cqn4sql(req.query, this.model, {
|
|
48
|
+
const sqlQuery = cqn2cqn4sql(req.query, this.model, { _4fiori: true })
|
|
49
49
|
if (req.query._streaming) {
|
|
50
50
|
sqlQuery._streaming = true
|
|
51
51
|
}
|
|
@@ -53,9 +53,6 @@ const _handler = async function (req) {
|
|
|
53
53
|
const hasDraftEntity = hasDraft(this.model.definitions, sqlQuery)
|
|
54
54
|
|
|
55
55
|
if (hasDraftEntity && sqlQuery.SELECT.where && sqlQuery.SELECT.where.length !== 0) {
|
|
56
|
-
// REVISIT
|
|
57
|
-
delete req.query._validationQuery
|
|
58
|
-
|
|
59
56
|
let cqnDraft = SELECT.from({
|
|
60
57
|
ref: [...sqlQuery.SELECT.from.ref],
|
|
61
58
|
as: sqlQuery.SELECT.from.as
|
|
@@ -41,7 +41,7 @@ class HanaDatabase extends DatabaseService {
|
|
|
41
41
|
this._insert = this._queries.insert(execute.insert)
|
|
42
42
|
this._read = this._queries.read(execute.select, execute.stream)
|
|
43
43
|
this._update = this._queries.update(execute.update, execute.select)
|
|
44
|
-
this._delete = this._queries.delete(execute.delete)
|
|
44
|
+
this._delete = this._queries.delete(execute.delete, execute.update)
|
|
45
45
|
this._run = this._queries.run(this._insert, this._read, this._update, this._delete, execute.cqn, execute.sql)
|
|
46
46
|
}
|
|
47
47
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const cds = require('../cds')
|
|
2
|
+
|
|
1
3
|
const convertToBoolean = boolean => {
|
|
2
4
|
if (boolean === null) {
|
|
3
5
|
return null
|
|
@@ -47,6 +49,14 @@ const HANA_TYPE_CONVERSION_MAP = new Map([
|
|
|
47
49
|
['cds.LargeString', convertToString]
|
|
48
50
|
])
|
|
49
51
|
|
|
52
|
+
if (cds.env.features.bigjs) {
|
|
53
|
+
const Big = require('big.js')
|
|
54
|
+
const convertToBig = value => new Big(value)
|
|
55
|
+
|
|
56
|
+
HANA_TYPE_CONVERSION_MAP.set('cds.Integer64', convertToBig)
|
|
57
|
+
HANA_TYPE_CONVERSION_MAP.set('cds.Decimal', convertToBig)
|
|
58
|
+
}
|
|
59
|
+
|
|
50
60
|
module.exports = {
|
|
51
61
|
HANA_TYPE_CONVERSION_MAP
|
|
52
62
|
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
const cds = require('../cds')
|
|
2
|
+
const LOG = cds.log('hana|db|sql')
|
|
3
|
+
|
|
1
4
|
const { HANA_TYPE_CONVERSION_MAP } = require('./conversion')
|
|
2
5
|
const CustomBuilder = require('./customBuilder')
|
|
3
6
|
const { sqlFactory } = require('../db/sql-builder/')
|
|
@@ -30,9 +33,6 @@ function _cqnToSQL(model, query, user, locale, txTimestamp) {
|
|
|
30
33
|
)
|
|
31
34
|
}
|
|
32
35
|
|
|
33
|
-
const cds = require('../cds')
|
|
34
|
-
const LOG = cds.log('hana|db|sql')
|
|
35
|
-
|
|
36
36
|
const SANITIZE_VALUES = process.env.NODE_ENV === 'production' && cds.env.log.sanitize_values !== false
|
|
37
37
|
|
|
38
38
|
function _getOutputParameters(stmt) {
|
|
@@ -48,6 +48,26 @@ function _getOutputParameters(stmt) {
|
|
|
48
48
|
return Object.keys(result).length > 0 ? result : undefined
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
const BINARY_TYPES = {
|
|
52
|
+
12: 'BINARY',
|
|
53
|
+
13: 'VARBINARY',
|
|
54
|
+
25: 'CLOB',
|
|
55
|
+
26: 'NCLOB',
|
|
56
|
+
27: 'BLOB'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function _getBinaries(stmt) {
|
|
60
|
+
// hdb vs. @sap/hana-client
|
|
61
|
+
const parameters = stmt.parameterMetadata || stmt.getParameterInfo()
|
|
62
|
+
const typeKey = stmt.parameterMetadata ? 'dataType' : 'nativeType'
|
|
63
|
+
return parameters.reduce((acc, cur, i) => {
|
|
64
|
+
if (BINARY_TYPES[cur[typeKey]]) acc.push(i)
|
|
65
|
+
return acc
|
|
66
|
+
}, [])
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}={2})$/
|
|
70
|
+
|
|
51
71
|
function _executeAsPreparedStatement(dbc, sql, values, reject, resolve) {
|
|
52
72
|
dbc.prepare(sql, function (err, stmt) {
|
|
53
73
|
if (err) {
|
|
@@ -56,18 +76,15 @@ function _executeAsPreparedStatement(dbc, sql, values, reject, resolve) {
|
|
|
56
76
|
return reject(err)
|
|
57
77
|
}
|
|
58
78
|
|
|
59
|
-
//
|
|
60
|
-
if (
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (stmt.parameterMetadata[i].dataType === 12 || stmt.parameterMetadata[i].dataType === 13) {
|
|
69
|
-
if (row[i] && !Buffer.isBuffer(row[i])) {
|
|
70
|
-
row[i] = Buffer.from(row[i].match(/.{1,2}/g).map(val => parseInt(val, 16)))
|
|
79
|
+
// convert binary strings to buffers ()
|
|
80
|
+
if (cds.env.hana.base64_to_buffer !== false && _hasValues(values)) {
|
|
81
|
+
const binaries = _getBinaries(stmt)
|
|
82
|
+
if (binaries.length) {
|
|
83
|
+
const vals = Array.isArray(values[0]) ? values : [values]
|
|
84
|
+
for (const i of binaries) {
|
|
85
|
+
for (const row of vals) {
|
|
86
|
+
if (row[i] && typeof row[i] === 'string' && row[i].match(BASE64)) {
|
|
87
|
+
row[i] = Buffer.from(row[i], 'base64')
|
|
71
88
|
}
|
|
72
89
|
}
|
|
73
90
|
}
|
|
@@ -108,7 +125,7 @@ function _executeSimpleSQL(dbc, sql, values) {
|
|
|
108
125
|
values = Object.values(values)
|
|
109
126
|
}
|
|
110
127
|
// ensure that stored procedure with parameters is always executed as prepared
|
|
111
|
-
if (_hasValues(
|
|
128
|
+
if (_hasValues(values) || sql.match(/^call.*?\?.*$/i)) {
|
|
112
129
|
_executeAsPreparedStatement(dbc, sql, values, reject, resolve)
|
|
113
130
|
} else {
|
|
114
131
|
dbc.exec(sql, function (err, result, procedureReturn) {
|
|
@@ -11,9 +11,9 @@ function searchHandler(req) {
|
|
|
11
11
|
// REVISIT: remove feature toggle optimized_search after grace period
|
|
12
12
|
// inject the search2cqn4sql module into the rewrite handler only when
|
|
13
13
|
// the optimized search feature toggle is turned on
|
|
14
|
-
if (
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
if (cds.env.features.optimized_search) {
|
|
15
|
+
_setSearchOptions(req.query, { search2cqn4sql, locale: req.locale })
|
|
16
|
+
}
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
// handlers marked with `._initial = true` run in sequence
|