@sap/cds 6.7.2 → 6.8.1
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 +41 -1
- package/_i18n/i18n.properties +9 -6
- package/_i18n/i18n_ar.properties +6 -6
- package/_i18n/i18n_cs.properties +6 -6
- package/_i18n/i18n_da.properties +6 -6
- package/_i18n/i18n_de.properties +6 -6
- package/_i18n/i18n_en.properties +6 -6
- package/_i18n/i18n_es.properties +6 -6
- package/_i18n/i18n_fi.properties +6 -6
- package/_i18n/i18n_fr.properties +6 -6
- package/_i18n/i18n_hu.properties +6 -6
- package/_i18n/i18n_it.properties +6 -6
- package/_i18n/i18n_ja.properties +6 -6
- package/_i18n/i18n_ko.properties +6 -6
- package/_i18n/i18n_ms.properties +6 -6
- package/_i18n/i18n_nl.properties +6 -6
- package/_i18n/i18n_no.properties +6 -6
- package/_i18n/i18n_pl.properties +6 -6
- package/_i18n/i18n_pt.properties +6 -6
- package/_i18n/i18n_ro.properties +6 -6
- package/_i18n/i18n_ru.properties +6 -6
- package/_i18n/i18n_sv.properties +6 -6
- package/_i18n/i18n_th.properties +6 -6
- package/_i18n/i18n_tr.properties +8 -8
- package/_i18n/i18n_zh_CN.properties +3 -3
- package/_i18n/i18n_zh_TW.properties +6 -6
- package/apis/core.d.ts +30 -31
- package/apis/csn.d.ts +1 -1
- package/apis/ql.d.ts +69 -39
- package/apis/serve.d.ts +4 -3
- package/apis/services.d.ts +20 -7
- package/bin/build/buildTaskEngine.js +1 -1
- package/bin/build/index.js +1 -1
- package/bin/build/provider/buildTaskProviderInternal.js +9 -6
- package/bin/build/provider/hana/index.js +11 -4
- package/bin/build/provider/mtx-sidecar/index.js +3 -3
- package/bin/build/provider/nodejs/index.js +23 -0
- package/bin/version.js +3 -2
- package/common.cds +3 -2
- package/lib/auth/index.js +3 -0
- package/lib/auth/mocked-users.js +13 -0
- package/lib/compile/etc/_localized.js +3 -0
- package/lib/core/entities.js +7 -3
- package/lib/dbs/cds-deploy.js +36 -12
- package/lib/env/cds-env.js +47 -14
- package/lib/env/cds-requires.js +16 -7
- package/lib/env/defaults.js +2 -2
- package/lib/env/schemas/cds-rc.json +1 -8
- package/lib/index.js +1 -1
- package/lib/ql/STREAM.js +89 -0
- package/lib/ql/cds-ql.js +2 -1
- package/lib/req/request.js +5 -2
- package/lib/req/user.js +1 -1
- package/lib/srv/middlewares/index.js +9 -7
- package/lib/srv/middlewares/trace.js +6 -5
- package/lib/srv/srv-api.js +1 -0
- package/lib/utils/cds-utils.js +1 -1
- package/lib/utils/tar.js +30 -31
- package/libx/_runtime/audit/Service.js +96 -37
- package/libx/_runtime/audit/generic/personal/utils.js +26 -13
- package/libx/_runtime/audit/utils/v2.js +21 -22
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +2 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/batch/BatchProcessor.js +2 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +10 -3
- package/libx/_runtime/cds-services/services/Service.js +2 -7
- package/libx/_runtime/cds-services/services/utils/differ.js +1 -1
- package/libx/_runtime/common/aspects/any.js +1 -1
- package/libx/_runtime/common/generic/auth/utils.js +30 -41
- package/libx/_runtime/common/i18n/messages.properties +1 -1
- package/libx/_runtime/db/expand/expandCQNToJoin.js +19 -17
- package/libx/_runtime/db/expand/rawToExpanded.js +3 -5
- package/libx/_runtime/db/utils/generateAliases.js +1 -1
- package/libx/_runtime/fiori/generic/activate.js +1 -1
- package/libx/_runtime/fiori/generic/before.js +18 -19
- package/libx/_runtime/fiori/generic/prepare.js +1 -1
- package/libx/_runtime/fiori/generic/read.js +1 -1
- package/libx/_runtime/fiori/lean-draft.js +8 -6
- package/libx/_runtime/fiori/utils/handler.js +0 -6
- package/libx/_runtime/hana/customBuilder/CustomFunctionBuilder.js +1 -1
- package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +0 -5
- package/libx/_runtime/hana/pool.js +26 -18
- package/libx/_runtime/hana/search2Contains.js +1 -1
- package/libx/_runtime/hana/search2cqn4sql.js +26 -18
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +23 -16
- package/libx/_runtime/messaging/outbox/utils.js +6 -1
- package/libx/_runtime/remote/Service.js +64 -38
- package/libx/_runtime/remote/utils/client.js +13 -9
- package/libx/rest/middleware/read.js +2 -1
- package/package.json +1 -1
|
@@ -40,54 +40,51 @@ const _getCurrentSubClause = (next, restrict) => {
|
|
|
40
40
|
const escaped = next[0].replace(/\$/g, '\\$').replace(/\./g, '\\.')
|
|
41
41
|
const re1 = new RegExp(`([\\w\\.']*)\\s*=\\s*(${escaped})|(${escaped})\\s*=\\s*([\\w\\.']*)`)
|
|
42
42
|
const re2 = new RegExp(`([\\w\\.']*)\\s*in\\s*(${escaped})|(${escaped})\\s*in\\s*([\\w\\.']*)`)
|
|
43
|
-
const
|
|
43
|
+
const re3 = new RegExp(`(${escaped})\\s*is\\s*null`)
|
|
44
|
+
const re4 = new RegExp(`(${escaped})\\s*is\\s*not\\s*null`)
|
|
45
|
+
const clause =
|
|
46
|
+
restrict.where.match(re3) || restrict.where.match(re4) || restrict.where.match(re1) || restrict.where.match(re2)
|
|
44
47
|
|
|
45
48
|
if (clause) return clause
|
|
46
49
|
|
|
47
50
|
// NOTE: arrayed attr with "=" as operator is some kind of legacy case
|
|
48
|
-
throw new Error('user attribute array must be used with operator "=" or "
|
|
51
|
+
throw new Error('user attribute array must be used with operator "=", "in", "is null", or "is not null"')
|
|
49
52
|
}
|
|
50
53
|
|
|
51
|
-
const
|
|
54
|
+
const _isNull = (userAttrs, attr) =>
|
|
55
|
+
userAttrs[attr] == null || (Array.isArray(userAttrs[attr]) && userAttrs[attr].length === 0)
|
|
56
|
+
const _isNotNull = (userAttrs, attr) =>
|
|
57
|
+
userAttrs[attr] != null && Array.isArray(userAttrs[attr]) && userAttrs[attr].length > 0
|
|
58
|
+
|
|
59
|
+
const _processUserAttr = (next, restrict, userAttrs, attr) => {
|
|
52
60
|
const clause = _getCurrentSubClause(next, restrict)
|
|
53
61
|
const valOrRef = clause[1] || clause[4]
|
|
54
62
|
|
|
55
|
-
if (clause[0].match(/
|
|
56
|
-
|
|
63
|
+
if (clause[0].match(/ is\s*null/)) {
|
|
64
|
+
restrict.where = restrict.where.replace(clause[0], _isNull(userAttrs, attr) ? '1 = 1' : '1 = 2')
|
|
65
|
+
} else if (clause[0].match(/ is\s*not\s*null/)) {
|
|
66
|
+
restrict.where = restrict.where.replace(clause[0], _isNotNull(userAttrs, attr) ? '1 = 1' : '1 = 2')
|
|
67
|
+
} else {
|
|
68
|
+
if (_isNull(userAttrs, attr)) {
|
|
57
69
|
restrict.where = restrict.where.replace(clause[0], '1 = 2')
|
|
58
|
-
} else if (
|
|
59
|
-
|
|
70
|
+
} else if (clause[0].match(/ in /)) {
|
|
71
|
+
if (userAttrs[attr].length === 1) {
|
|
72
|
+
restrict.where = restrict.where.replace(clause[0], `${valOrRef} = '${userAttrs[attr][0]}'`)
|
|
73
|
+
} else {
|
|
74
|
+
restrict.where = restrict.where.replace(
|
|
75
|
+
clause[0],
|
|
76
|
+
`${valOrRef} in (${userAttrs[attr].map(ele => `'${ele}'`).join(', ')})`
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
} else if (valOrRef.startsWith("'") && userAttrs[attr].includes(valOrRef.split("'")[1])) {
|
|
80
|
+
restrict.where = restrict.where.replace(clause[0], `${valOrRef} = ${valOrRef}`)
|
|
60
81
|
} else {
|
|
61
82
|
restrict.where = restrict.where.replace(
|
|
62
83
|
clause[0],
|
|
63
|
-
|
|
84
|
+
`(${userAttrs[attr].map(ele => `${valOrRef} = '${ele}'`).join(' or ')})`
|
|
64
85
|
)
|
|
65
86
|
}
|
|
66
|
-
} else if (valOrRef.startsWith("'") && user[attr].includes(valOrRef.split("'")[1])) {
|
|
67
|
-
restrict.where = restrict.where.replace(clause[0], `${valOrRef} = ${valOrRef}`)
|
|
68
|
-
} else {
|
|
69
|
-
restrict.where = restrict.where.replace(
|
|
70
|
-
clause[0],
|
|
71
|
-
`(${user[attr].map(ele => `${valOrRef} = '${ele}'`).join(' or ')})`
|
|
72
|
-
)
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const _getShortcut = (attrs, attr) => {
|
|
77
|
-
// undefined
|
|
78
|
-
if (attrs[attr] === undefined) {
|
|
79
|
-
return '1 = 2'
|
|
80
87
|
}
|
|
81
|
-
|
|
82
|
-
// $UNRESTRICTED
|
|
83
|
-
if (
|
|
84
|
-
(typeof attrs[attr] === 'string' && attrs[attr].match(/\$UNRESTRICTED/i)) ||
|
|
85
|
-
(Array.isArray(attrs[attr]) && attrs[attr].some(a => a.match(/\$UNRESTRICTED/i)))
|
|
86
|
-
) {
|
|
87
|
-
return '1 = 1'
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return null
|
|
91
88
|
}
|
|
92
89
|
|
|
93
90
|
/*
|
|
@@ -120,15 +117,7 @@ const resolveUserAttrs = (restrict, req) => {
|
|
|
120
117
|
let attr = parts.shift()
|
|
121
118
|
|
|
122
119
|
while (attr) {
|
|
123
|
-
|
|
124
|
-
if (shortcut) {
|
|
125
|
-
const clause = _getCurrentSubClause(next, restrict)
|
|
126
|
-
restrict.where = restrict.where.replace(clause[0], shortcut)
|
|
127
|
-
skip = true
|
|
128
|
-
break
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (Array.isArray(attrs[attr])) {
|
|
120
|
+
if (attrs[attr] === undefined || Array.isArray(attrs[attr])) {
|
|
132
121
|
_processUserAttr(next, restrict, attrs, attr)
|
|
133
122
|
skip = true
|
|
134
123
|
break
|
|
@@ -85,7 +85,7 @@ CRUD_VIA_NAVIGATION_NOT_SUPPORTED=CRUD via navigations is not yet supported
|
|
|
85
85
|
|
|
86
86
|
# draft
|
|
87
87
|
DRAFT_ALREADY_EXISTS=A draft for this entity already exists
|
|
88
|
-
DRAFT_LOCKED_BY_ANOTHER_USER=The entity is locked by
|
|
88
|
+
DRAFT_LOCKED_BY_ANOTHER_USER=The entity is locked by user "{0}"
|
|
89
89
|
DRAFT_MODIFICATION_ONLY_VIA_ROOT=A draft can only be modified via its root entity
|
|
90
90
|
|
|
91
91
|
# singleton
|
|
@@ -66,7 +66,6 @@ class JoinCQNFromExpanded {
|
|
|
66
66
|
|
|
67
67
|
// Get first level of expanding regarding to many and all to one if not part of a nested to many expand.
|
|
68
68
|
this._createJoinCQNFromExpanded(this._SELECT, [])
|
|
69
|
-
|
|
70
69
|
return this
|
|
71
70
|
}
|
|
72
71
|
|
|
@@ -117,10 +116,10 @@ class JoinCQNFromExpanded {
|
|
|
117
116
|
*/
|
|
118
117
|
_createJoinCQNFromExpanded(SELECT, toManyTree, defaultLanguage) {
|
|
119
118
|
const joinArgs = SELECT.from.args
|
|
120
|
-
const isJoinOfTwoSelects = joinArgs
|
|
119
|
+
const isJoinOfTwoSelects = joinArgs?.every(a => a.SELECT)
|
|
121
120
|
|
|
122
121
|
const unionTableRef = this._getUnionTable(SELECT)
|
|
123
|
-
const unionTable = unionTableRef
|
|
122
|
+
const unionTable = unionTableRef?.table
|
|
124
123
|
const tableAlias = this._getTableAlias(SELECT, toManyTree)
|
|
125
124
|
|
|
126
125
|
const readToOneCQN = this._getReadToOneCQN(SELECT, isJoinOfTwoSelects ? 'filterExpand' : tableAlias)
|
|
@@ -178,7 +177,8 @@ class JoinCQNFromExpanded {
|
|
|
178
177
|
* @private
|
|
179
178
|
*/
|
|
180
179
|
_getTableAlias(SELECT, toManyTree) {
|
|
181
|
-
|
|
180
|
+
const ref = this._getRef(SELECT)
|
|
181
|
+
return this._createAlias(toManyTree.length === 0 ? ref.table : toManyTree.join(':'), ref.as)
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
_getRef(SELECT) {
|
|
@@ -212,15 +212,21 @@ class JoinCQNFromExpanded {
|
|
|
212
212
|
* Create an alias from value.
|
|
213
213
|
*
|
|
214
214
|
* @param {string} value
|
|
215
|
+
* @param {string} [alias]
|
|
215
216
|
* @returns {string}
|
|
216
217
|
* @private
|
|
217
218
|
*/
|
|
218
|
-
_createAlias(value) {
|
|
219
|
+
_createAlias(value, alias) {
|
|
219
220
|
if (!this._aliases) {
|
|
220
221
|
this._aliases = {}
|
|
221
222
|
}
|
|
222
223
|
|
|
223
224
|
if (!this._aliases[value]) {
|
|
225
|
+
if (alias) {
|
|
226
|
+
this._aliases[value] = alias
|
|
227
|
+
return alias
|
|
228
|
+
}
|
|
229
|
+
|
|
224
230
|
const aliasNum = Object.keys(this._aliases).length
|
|
225
231
|
|
|
226
232
|
if (aliasNum < 26) {
|
|
@@ -319,6 +325,7 @@ class JoinCQNFromExpanded {
|
|
|
319
325
|
list: element.list.map(element => this._checkOrderByWhereElementRecursive(cqn, element, tableAlias))
|
|
320
326
|
})
|
|
321
327
|
}
|
|
328
|
+
|
|
322
329
|
return this._checkOrderByWhereElementRecursive(cqn, element, tableAlias)
|
|
323
330
|
}
|
|
324
331
|
|
|
@@ -333,9 +340,7 @@ class JoinCQNFromExpanded {
|
|
|
333
340
|
*/
|
|
334
341
|
_adaptWhereOrderBy(cqn, tableAlias) {
|
|
335
342
|
if (cqn.where) {
|
|
336
|
-
cqn.where = cqn.where.map(element =>
|
|
337
|
-
return this._adaptWhereElement(element, cqn, tableAlias)
|
|
338
|
-
})
|
|
343
|
+
cqn.where = cqn.where.map(element => this._adaptWhereElement(element, cqn, tableAlias))
|
|
339
344
|
}
|
|
340
345
|
|
|
341
346
|
if (cqn.having) {
|
|
@@ -343,15 +348,11 @@ class JoinCQNFromExpanded {
|
|
|
343
348
|
}
|
|
344
349
|
|
|
345
350
|
if (cqn.orderBy) {
|
|
346
|
-
cqn.orderBy = cqn.orderBy.map(element =>
|
|
347
|
-
return this._checkOrderByWhereElementRecursive(cqn, element, tableAlias)
|
|
348
|
-
})
|
|
351
|
+
cqn.orderBy = cqn.orderBy.map(element => this._checkOrderByWhereElementRecursive(cqn, element, tableAlias))
|
|
349
352
|
}
|
|
350
353
|
|
|
351
354
|
if (cqn.groupBy) {
|
|
352
|
-
cqn.groupBy = cqn.groupBy.map(element =>
|
|
353
|
-
return this._checkOrderByWhereElementRecursive(cqn, element, tableAlias)
|
|
354
|
-
})
|
|
355
|
+
cqn.groupBy = cqn.groupBy.map(element => this._checkOrderByWhereElementRecursive(cqn, element, tableAlias))
|
|
355
356
|
}
|
|
356
357
|
|
|
357
358
|
return cqn
|
|
@@ -393,7 +394,7 @@ class JoinCQNFromExpanded {
|
|
|
393
394
|
element.xpr = element.xpr.map(nestedElement => {
|
|
394
395
|
return this._checkOrderByWhereElementRecursive(cqn, nestedElement, tableAlias)
|
|
395
396
|
})
|
|
396
|
-
} else if (element.SELECT
|
|
397
|
+
} else if (element.SELECT?.where) {
|
|
397
398
|
element = {
|
|
398
399
|
SELECT: Object.assign({}, element.SELECT, {
|
|
399
400
|
where: this._adaptWhereSELECT(this._getRef(cqn), element.SELECT.where, tableAlias)
|
|
@@ -416,6 +417,7 @@ class JoinCQNFromExpanded {
|
|
|
416
417
|
if (element.xpr) {
|
|
417
418
|
return { xpr: this._adaptWhereSELECT(aliasedTable, element.xpr, tableAlias) }
|
|
418
419
|
}
|
|
420
|
+
|
|
419
421
|
return this._elementAliasNeedsReplacement(element, aliasedTable)
|
|
420
422
|
? Object.assign({}, element, { ref: [tableAlias, element.ref[1]] })
|
|
421
423
|
: element
|
|
@@ -847,7 +849,7 @@ class JoinCQNFromExpanded {
|
|
|
847
849
|
continue
|
|
848
850
|
}
|
|
849
851
|
|
|
850
|
-
if (arg.SELECT
|
|
852
|
+
if (arg.SELECT?.columns.some(column => column[IDENTIFIER])) {
|
|
851
853
|
return arg.SELECT.columns
|
|
852
854
|
}
|
|
853
855
|
|
|
@@ -1434,7 +1436,7 @@ class JoinCQNFromExpanded {
|
|
|
1434
1436
|
}
|
|
1435
1437
|
|
|
1436
1438
|
_getValueFromEntry(entry, parentAlias, key, struct) {
|
|
1437
|
-
let value = entry[key]
|
|
1439
|
+
let value = entry[key] ?? entry[key.toUpperCase()]
|
|
1438
1440
|
if (value === undefined) {
|
|
1439
1441
|
value = entry[`${parentAlias}_${key}`] || entry[`${parentAlias}_${key}`.toUpperCase()]
|
|
1440
1442
|
}
|
|
@@ -158,9 +158,7 @@ class RawToExpanded {
|
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
// No property holds any value. A to null must have failed.
|
|
161
|
-
if (isEntityNull)
|
|
162
|
-
return
|
|
163
|
-
}
|
|
161
|
+
if (isEntityNull) return
|
|
164
162
|
|
|
165
163
|
return row
|
|
166
164
|
}
|
|
@@ -175,10 +173,10 @@ class RawToExpanded {
|
|
|
175
173
|
*/
|
|
176
174
|
_isNull(isEntityNull, value, key) {
|
|
177
175
|
if (isEntityNull === undefined) {
|
|
178
|
-
return value
|
|
176
|
+
return value == null || key === 'IsActiveEntity'
|
|
179
177
|
}
|
|
180
178
|
|
|
181
|
-
return isEntityNull === true && (value
|
|
179
|
+
return isEntityNull === true && (value == null || key === 'IsActiveEntity')
|
|
182
180
|
}
|
|
183
181
|
|
|
184
182
|
/**
|
|
@@ -122,7 +122,7 @@ const fioriGenericActivate = async function (req) {
|
|
|
122
122
|
if (!draftData) req.reject(404)
|
|
123
123
|
if (adminData.InProcessByUser !== req.user.id) {
|
|
124
124
|
// REVISIT: security log?
|
|
125
|
-
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER')
|
|
125
|
+
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER', [adminData.InProcessByUser])
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
/*
|
|
@@ -4,7 +4,7 @@ const { SELECT } = cds.ql
|
|
|
4
4
|
|
|
5
5
|
const { isNavigationToMany } = require('../utils/req')
|
|
6
6
|
const { getKeysCondition, removeIsActiveEntityRecursively } = require('../utils/where')
|
|
7
|
-
const {
|
|
7
|
+
const { ensureNoDraftsSuffix, ensureDraftsSuffix, draftIsLocked } = require('../utils/handler')
|
|
8
8
|
|
|
9
9
|
const { DRAFT_COLUMNS_ADMIN_MAP } = require('../../common/constants/draft')
|
|
10
10
|
const { deepCopyArray } = require('../../common/utils/copy')
|
|
@@ -32,7 +32,7 @@ const _validateDraft = (req, draftResult, isBoundAction) => {
|
|
|
32
32
|
// user than the one who locked the entity and the configured drafts cancellation
|
|
33
33
|
// timeout timer has expired
|
|
34
34
|
if (draftIsLocked(draftAdminData.LastChangeDateTime)) {
|
|
35
|
-
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER')
|
|
35
|
+
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER', [draftAdminData.CreatedByUser])
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
// At this point, the request user ID isn't the owner of the draft.
|
|
@@ -60,16 +60,19 @@ const _getSelectDraftDataCqn = (entityName, where) => {
|
|
|
60
60
|
|
|
61
61
|
const _getRoot = req => {
|
|
62
62
|
if (!req.query) return
|
|
63
|
+
|
|
63
64
|
const refObj = req.query.SELECT?.from || req.query.UPDATE?.entity || req.query.INSERT?.into || req.query.DELETE?.from
|
|
65
|
+
const ref0 = refObj.ref[0]
|
|
66
|
+
|
|
64
67
|
const root = {
|
|
65
|
-
entityName: ensureDraftsSuffix(
|
|
66
|
-
where: removeIsActiveEntityRecursively(deepCopyArray(
|
|
68
|
+
entityName: ensureDraftsSuffix(ref0.id),
|
|
69
|
+
where: removeIsActiveEntityRecursively(deepCopyArray(ref0.where))
|
|
67
70
|
}
|
|
68
71
|
|
|
69
|
-
for (const item of
|
|
72
|
+
for (const item of ref0.where) {
|
|
70
73
|
if (item.ref && item.ref[item.ref.length - 1] === 'IsActiveEntity') {
|
|
71
|
-
const index =
|
|
72
|
-
root.IsActiveEntity =
|
|
74
|
+
const index = ref0.where.indexOf(item)
|
|
75
|
+
root.IsActiveEntity = ref0.where[index + 2].val
|
|
73
76
|
break
|
|
74
77
|
}
|
|
75
78
|
}
|
|
@@ -115,14 +118,12 @@ const _addDraftDataFromExistingDraft = async req => {
|
|
|
115
118
|
* Generic Handler for before NEW requests.
|
|
116
119
|
*/
|
|
117
120
|
const _new = async function (req) {
|
|
118
|
-
if (isDraftActivateAction(req)) return // REVISIT: How can NEW be draftActivate???
|
|
119
|
-
|
|
120
121
|
if (isNavigationToMany(req)) {
|
|
121
|
-
// REVISIT: How can NEW be a navigation to many?
|
|
122
122
|
const result = await _addDraftDataFromExistingDraft(req)
|
|
123
123
|
|
|
124
124
|
// in order to fix corner case where active subitems are created in draft case
|
|
125
125
|
if (result.length === 0) req.reject(404)
|
|
126
|
+
|
|
126
127
|
return
|
|
127
128
|
}
|
|
128
129
|
|
|
@@ -134,19 +135,17 @@ const _new = async function (req) {
|
|
|
134
135
|
/**
|
|
135
136
|
* Generic Handler for before PATCH and UPDATE requests.
|
|
136
137
|
*/
|
|
137
|
-
const
|
|
138
|
-
if (isDraftActivateAction(req)) return
|
|
139
|
-
|
|
138
|
+
const _patch = async function (req) {
|
|
140
139
|
const result = await _addDraftDataFromExistingDraft(req)
|
|
141
140
|
|
|
142
|
-
// means that the draft does not
|
|
141
|
+
// no result means that the draft does not exist
|
|
143
142
|
if (result.length === 0) req.reject(404)
|
|
144
143
|
}
|
|
145
144
|
|
|
146
145
|
/**
|
|
147
146
|
* Generic Handler for before DELETE and CANCEL requests.
|
|
148
147
|
*/
|
|
149
|
-
const
|
|
148
|
+
const _cancel = async function (req) {
|
|
150
149
|
await _addDraftDataFromExistingDraft(req)
|
|
151
150
|
}
|
|
152
151
|
|
|
@@ -181,12 +180,12 @@ const _registerBoundActionHandlers = function (entityName, actions) {
|
|
|
181
180
|
}
|
|
182
181
|
|
|
183
182
|
_new._initial = true
|
|
184
|
-
|
|
185
|
-
|
|
183
|
+
_patch._initial = true
|
|
184
|
+
_cancel._initial = true
|
|
186
185
|
|
|
187
186
|
module.exports = cds.service.impl((srv, entity) => {
|
|
188
187
|
srv.before('NEW', entity, _new)
|
|
189
|
-
srv.before(
|
|
190
|
-
srv.before(
|
|
188
|
+
srv.before('PATCH', entity, _patch)
|
|
189
|
+
srv.before('CANCEL', entity, _cancel)
|
|
191
190
|
_registerBoundActionHandlers.call(srv, entity.name, entity.actions)
|
|
192
191
|
})
|
|
@@ -39,7 +39,7 @@ const fioriGenericPrepare = async function (req) {
|
|
|
39
39
|
if (!result) req.reject(404)
|
|
40
40
|
if (result.draftAdmin_inProcessByUser !== req.user.id) {
|
|
41
41
|
// REVISIT: security log?
|
|
42
|
-
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER')
|
|
42
|
+
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER', [result.draftAdmin_inProcessByUser])
|
|
43
43
|
}
|
|
44
44
|
delete result.draftAdmin_inProcessByUser
|
|
45
45
|
return result
|
|
@@ -787,7 +787,7 @@ const _getOrderByEnrichedColumns = (orderBy, columns, entity) => {
|
|
|
787
787
|
const enrichedCol = []
|
|
788
788
|
|
|
789
789
|
if (orderBy && orderBy.length > 1) {
|
|
790
|
-
const colNames = columns.map(el => el.ref[el.ref.length - 1])
|
|
790
|
+
const colNames = columns.filter(el => el.ref).map(el => el.ref[el.ref.length - 1])
|
|
791
791
|
|
|
792
792
|
// REVISIT: GET Books?$select=title&$expand=NotBooks($select=pages)&$orderby=NotBooks/title - what's then?
|
|
793
793
|
for (const el of orderBy) {
|
|
@@ -162,7 +162,8 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
162
162
|
])
|
|
163
163
|
)
|
|
164
164
|
const inProcessByUser = draft?.DraftAdministrativeData?.InProcessByUser
|
|
165
|
-
if (inProcessByUser && inProcessByUser !== cds.context.user.id)
|
|
165
|
+
if (inProcessByUser && inProcessByUser !== cds.context.user.id)
|
|
166
|
+
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER', [inProcessByUser])
|
|
166
167
|
const deletes = [run(DELETE.from({ ref: query.DELETE.from.ref }))]
|
|
167
168
|
if (draft)
|
|
168
169
|
deletes.push(
|
|
@@ -203,7 +204,7 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
203
204
|
)
|
|
204
205
|
if (!res) req.reject(404)
|
|
205
206
|
if (res.DraftAdministrativeData?.InProcessByUser !== cds.context.user.id)
|
|
206
|
-
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER')
|
|
207
|
+
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER', [res.DraftAdministrativeData?.InProcessByUser])
|
|
207
208
|
const DraftAdministrativeData_DraftUUID = res.DraftAdministrativeData_DraftUUID
|
|
208
209
|
delete res.DraftAdministrativeData_DraftUUID
|
|
209
210
|
delete res.DraftAdministrativeData
|
|
@@ -263,7 +264,7 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
263
264
|
)
|
|
264
265
|
if (!res) req.reject(404)
|
|
265
266
|
if (res.DraftAdministrativeData?.InProcessByUser !== cds.context.user.id)
|
|
266
|
-
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER')
|
|
267
|
+
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER', [res.DraftAdministrativeData?.InProcessByUser])
|
|
267
268
|
await UPDATE('DRAFT.DraftAdministrativeData')
|
|
268
269
|
.data({
|
|
269
270
|
InProcessByUser: req.user.id,
|
|
@@ -843,7 +844,7 @@ async function onNew(req) {
|
|
|
843
844
|
)
|
|
844
845
|
if (!rootData) req.reject(404)
|
|
845
846
|
if (rootData.DraftAdministrativeData?.InProcessByUser !== req.user.id)
|
|
846
|
-
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER')
|
|
847
|
+
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER', [rootData.DraftAdministrativeData.InProcessByUser])
|
|
847
848
|
DraftUUID = rootData.DraftAdministrativeData_DraftUUID
|
|
848
849
|
}
|
|
849
850
|
const timestamp = cds.context.timestamp.toISOString() // REVISIT: toISOString should be done on db layer
|
|
@@ -997,7 +998,7 @@ async function onCancel(req) {
|
|
|
997
998
|
const draft = await this.run(draftDelete)
|
|
998
999
|
if (draftParams.IsActiveEntity === false && !draft) req.reject(404)
|
|
999
1000
|
if (draft && draft.DraftAdministrativeData?.InProcessByUser !== cds.context.user.id)
|
|
1000
|
-
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER')
|
|
1001
|
+
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER', [draft.DraftAdministrativeData?.InProcessByUser])
|
|
1001
1002
|
const deletes = !draft ? [] : [this.run(DELETE.from({ ref: req.query.DELETE.from.ref }))]
|
|
1002
1003
|
if (draft && req.target['@Common.DraftRoot.ActivationAction'])
|
|
1003
1004
|
// only for draft root
|
|
@@ -1027,7 +1028,8 @@ async function onPrepare(req) {
|
|
|
1027
1028
|
Object.defineProperty(draftQuery, '_draftParams', { value: draftParams, enumerable: false })
|
|
1028
1029
|
const data = await this.run(draftQuery)
|
|
1029
1030
|
if (!data) req.reject(404)
|
|
1030
|
-
if (data.DraftAdministrativeData?.InProcessByUser !== req.user.id)
|
|
1031
|
+
if (data.DraftAdministrativeData?.InProcessByUser !== req.user.id)
|
|
1032
|
+
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER', [data.DraftAdministrativeData?.InProcessByUser])
|
|
1031
1033
|
delete data.DraftAdministrativeData
|
|
1032
1034
|
return { ...data, IsActiveEntity: false }
|
|
1033
1035
|
}
|
|
@@ -188,11 +188,6 @@ const removeDraftUUIDIfNecessary = req =>
|
|
|
188
188
|
? () => {}
|
|
189
189
|
: result => delete result.DraftAdministrativeData_DraftUUID
|
|
190
190
|
|
|
191
|
-
const isDraftActivateAction = req => {
|
|
192
|
-
// REVISIT: get rid of getUrlObject
|
|
193
|
-
if (req.getUrlObject) return req.getUrlObject().pathname.endsWith('draftActivate')
|
|
194
|
-
}
|
|
195
|
-
|
|
196
191
|
const addColumnAlias = (columns, alias) => {
|
|
197
192
|
if (!alias) {
|
|
198
193
|
return columns
|
|
@@ -258,7 +253,6 @@ module.exports = {
|
|
|
258
253
|
getUpdateDraftAdminCQN,
|
|
259
254
|
getEnrichedCQN,
|
|
260
255
|
removeDraftUUIDIfNecessary,
|
|
261
|
-
isDraftActivateAction,
|
|
262
256
|
ensureDraftsSuffix,
|
|
263
257
|
ensureNoDraftsSuffix,
|
|
264
258
|
ensureUnlocalized,
|
|
@@ -21,7 +21,7 @@ class CustomFunctionBuilder extends FunctionBuilder {
|
|
|
21
21
|
|
|
22
22
|
_handleContains(args) {
|
|
23
23
|
// fuzzy search has three arguments, must not be converted to like expressions
|
|
24
|
-
if (args.length > 2 || this.
|
|
24
|
+
if (args.length > 2 || this._obj.searchUsingContains) {
|
|
25
25
|
this._outputObj.sql.push('CONTAINS')
|
|
26
26
|
this._addFunctionArgs(args, true)
|
|
27
27
|
return
|
|
@@ -4,11 +4,6 @@ const LOG = cds.log('hana|db|sql')
|
|
|
4
4
|
const SelectBuilder = require('../../db/sql-builder').SelectBuilder
|
|
5
5
|
|
|
6
6
|
class CustomSelectBuilder extends SelectBuilder {
|
|
7
|
-
constructor(obj, options, csn) {
|
|
8
|
-
super(obj, options, csn)
|
|
9
|
-
// $searchUsingContains property is set in the sub SELECT of the query, optimized search case
|
|
10
|
-
if (this._obj?._$searchUsingContains) this._options.$searchUsingContains = this._obj._$searchUsingContains
|
|
11
|
-
}
|
|
12
7
|
get FunctionBuilder() {
|
|
13
8
|
const FunctionBuilder = require('./CustomFunctionBuilder')
|
|
14
9
|
Object.defineProperty(this, 'FunctionBuilder', { value: FunctionBuilder })
|
|
@@ -17,6 +17,7 @@ function multiTenantServiceManager() {
|
|
|
17
17
|
if (e.code === 'MODULE_NOT_FOUND') return null
|
|
18
18
|
else throw e
|
|
19
19
|
}
|
|
20
|
+
|
|
20
21
|
const oldIm =
|
|
21
22
|
cds.requires.multitenancy?.['old-instance-manager'] ??
|
|
22
23
|
cds.env.requires?.['cds.xt.DeploymentService']?.['old-instance-manager']
|
|
@@ -93,8 +94,10 @@ async function credentials4(tenant, db) {
|
|
|
93
94
|
return new Promise((resolve, reject) => {
|
|
94
95
|
db._instance_manager.get(tenant, (err, res) => {
|
|
95
96
|
if (err) return reject(err)
|
|
96
|
-
if (!res)
|
|
97
|
+
if (!res) {
|
|
97
98
|
return reject(Object.assign(new Error(`There is no instance for tenant "${tenant}"`), { statusCode: 404 }))
|
|
99
|
+
}
|
|
100
|
+
|
|
98
101
|
resolve(res.credentials)
|
|
99
102
|
})
|
|
100
103
|
})
|
|
@@ -102,15 +105,9 @@ async function credentials4(tenant, db) {
|
|
|
102
105
|
|
|
103
106
|
function factory4(creds, tenant) {
|
|
104
107
|
return {
|
|
105
|
-
create:
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
destroy: function (client) {
|
|
109
|
-
return hana.__disconnect(client)
|
|
110
|
-
},
|
|
111
|
-
validate: function (client) {
|
|
112
|
-
return hana.__isConnected(client)
|
|
113
|
-
}
|
|
108
|
+
create: () => hana.__connect(creds, tenant),
|
|
109
|
+
destroy: client => hana.__disconnect(client),
|
|
110
|
+
validate: client => hana.__isConnected(client)
|
|
114
111
|
}
|
|
115
112
|
}
|
|
116
113
|
|
|
@@ -121,7 +118,6 @@ const defaultConfig = { min: 0, max: 100, testOnBorrow: true, fifo: false }
|
|
|
121
118
|
|
|
122
119
|
const _getPoolConfig = function () {
|
|
123
120
|
const { pool: poolConfig } = cds.env.requires.db
|
|
124
|
-
|
|
125
121
|
const mergedConfig = Object.assign({}, defaultConfig, poolConfig)
|
|
126
122
|
|
|
127
123
|
// defaults
|
|
@@ -154,15 +150,19 @@ const _getMassagedCreds = function (creds) {
|
|
|
154
150
|
if (!('ca' in creds) && creds.certificate) {
|
|
155
151
|
creds.ca = creds.certificate
|
|
156
152
|
}
|
|
153
|
+
|
|
157
154
|
if ('encrypt' in creds && !('useTLS' in creds)) {
|
|
158
155
|
creds.useTLS = creds.encrypt
|
|
159
156
|
}
|
|
157
|
+
|
|
160
158
|
if ('hostname_in_certificate' in creds && !('sslHostNameInCertificate' in creds)) {
|
|
161
159
|
creds.sslHostNameInCertificate = creds.hostname_in_certificate
|
|
162
160
|
}
|
|
161
|
+
|
|
163
162
|
if ('validate_certificate' in creds && !('sslValidateCertificate' in creds)) {
|
|
164
163
|
creds.sslValidateCertificate = creds.validate_certificate
|
|
165
164
|
}
|
|
165
|
+
|
|
166
166
|
return creds
|
|
167
167
|
}
|
|
168
168
|
|
|
@@ -185,9 +185,11 @@ async function pool4(tenant, db) {
|
|
|
185
185
|
)
|
|
186
186
|
|
|
187
187
|
/*
|
|
188
|
-
* The error listener for "factoryCreateError" is registered
|
|
189
|
-
* If it fails due to invalid credentials, we delete the current pool from the pools map and overwrite the
|
|
190
|
-
*
|
|
188
|
+
* The error listener for "factoryCreateError" is registered to find out failed connection attempts.
|
|
189
|
+
* If it fails due to invalid credentials, we delete the current pool from the pools map and overwrite the
|
|
190
|
+
* pool factory create function.
|
|
191
|
+
* Background is that generic-pool will continue to try to open a connection by calling the factory create
|
|
192
|
+
* function until the "acquireTimeoutMillis" is reached.
|
|
191
193
|
* This ends up in many connection attempts for one request even though the credentials are invalid.
|
|
192
194
|
* Because of the deletion in the map, subsequent requests will fetch the credentials again.
|
|
193
195
|
*/
|
|
@@ -218,6 +220,7 @@ async function pool4(tenant, db) {
|
|
|
218
220
|
})
|
|
219
221
|
)
|
|
220
222
|
}
|
|
223
|
+
|
|
221
224
|
if ('then' in pools.get(tenant)) {
|
|
222
225
|
pools.set(tenant, await pools.get(tenant))
|
|
223
226
|
}
|
|
@@ -240,12 +243,17 @@ async function resilientAcquire(pool, attempts = 1) {
|
|
|
240
243
|
attempt++
|
|
241
244
|
}
|
|
242
245
|
}
|
|
246
|
+
|
|
243
247
|
if (client) return client
|
|
248
|
+
|
|
244
249
|
const { borrowed, pending, size, available, max } = pool
|
|
250
|
+
const message =
|
|
251
|
+
'Acquiring client from pool timed out. Please review your system setup, transaction handling, and pool configuration. ' +
|
|
252
|
+
`Pool State: borrowed: ${borrowed}, pending: ${pending}, size: ${size}, available: ${available}, max: ${max}`
|
|
245
253
|
err = getError(
|
|
246
254
|
Object.assign(err, {
|
|
247
255
|
statusCode: 503,
|
|
248
|
-
message
|
|
256
|
+
message
|
|
249
257
|
})
|
|
250
258
|
)
|
|
251
259
|
err._attempts = attempt
|
|
@@ -262,9 +270,9 @@ module.exports = {
|
|
|
262
270
|
client._pool = pool
|
|
263
271
|
return client
|
|
264
272
|
},
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
273
|
+
|
|
274
|
+
release: client => client._pool.release(client),
|
|
275
|
+
|
|
268
276
|
drain: async tenant => {
|
|
269
277
|
const pool = pools.get(tenant)
|
|
270
278
|
if (!pool) return
|
|
@@ -62,7 +62,7 @@ const search2Contains = (cqnSearchPhrase, columns) => {
|
|
|
62
62
|
const isContainsPredicateSupported = (query, entity, columns2Search) => {
|
|
63
63
|
const cqnSearchPhrase = query.SELECT.search
|
|
64
64
|
|
|
65
|
-
if (cqnSearchPhrase
|
|
65
|
+
if (cqnSearchPhrase?.[0]?.val === ' ') return false
|
|
66
66
|
|
|
67
67
|
// REVISIT: In the future, to further optimize search queries, you might
|
|
68
68
|
// want to remove the following condition(s).
|