@sap/cds 6.7.2 → 6.8.2
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 +53 -1
- package/README.md +1 -0
- 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-extension/index.js +13 -1
- package/bin/build/provider/mtx-sidecar/index.js +3 -3
- package/bin/build/provider/nodejs/index.js +23 -0
- package/bin/plugins.js +2 -1
- 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/compile/for/lean_drafts.js +0 -1
- 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 +6 -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/metadata.js +2 -0
- 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/cds-services/util/assert.js +28 -5
- package/libx/_runtime/common/aspects/any.js +4 -1
- package/libx/_runtime/common/generic/auth/utils.js +30 -41
- package/libx/_runtime/common/generic/crud.js +1 -1
- package/libx/_runtime/common/i18n/messages.properties +1 -1
- package/libx/_runtime/common/utils/generateOnCond.js +18 -22
- package/libx/_runtime/db/expand/expandCQNToJoin.js +49 -41
- package/libx/_runtime/db/expand/rawToExpanded.js +3 -5
- package/libx/_runtime/db/generic/rewrite.js +3 -0
- 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 +87 -53
- package/libx/_runtime/fiori/utils/handler.js +0 -6
- package/libx/_runtime/hana/customBuilder/CustomFunctionBuilder.js +1 -1
- package/libx/_runtime/hana/customBuilder/CustomReferenceBuilder.js +2 -1
- package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +0 -5
- package/libx/_runtime/hana/execute.js +18 -11
- 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 +83 -48
- package/libx/_runtime/remote/utils/client.js +17 -19
- package/libx/_runtime/sqlite/execute.js +2 -0
- package/libx/rest/middleware/read.js +2 -1
- package/libx/rest/middleware/update.js +1 -1
- package/package.json +1 -1
|
@@ -25,6 +25,30 @@ const DRAFT_ADMIN_ELEMENTS = [
|
|
|
25
25
|
'DraftIsProcessedByMe'
|
|
26
26
|
]
|
|
27
27
|
|
|
28
|
+
const _fillIsActiveEntity = (row, IsActiveEntity, target) => {
|
|
29
|
+
if (target.drafts) row.IsActiveEntity = IsActiveEntity
|
|
30
|
+
for (const key in target.associations) {
|
|
31
|
+
const prop = row[key]
|
|
32
|
+
if (!prop) continue
|
|
33
|
+
const el = target.elements[key]
|
|
34
|
+
const childIsActiveEntity = el._target.isDraft ? IsActiveEntity : true
|
|
35
|
+
if (Array.isArray(prop)) prop.map(r => _fillIsActiveEntity(r, childIsActiveEntity, el._target))
|
|
36
|
+
else if (typeof prop === 'object') _fillIsActiveEntity(prop, childIsActiveEntity, el._target)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// REVISIT: should not be necessary
|
|
41
|
+
const _runWithContext = (srv, req, obj) => {
|
|
42
|
+
const r = new cds.Request(obj)
|
|
43
|
+
r.event // invoke getter
|
|
44
|
+
r._ = Object.assign(r._, req._)
|
|
45
|
+
if (req.getUriInfo) r.getUriInfo = () => req.getUriInfo()
|
|
46
|
+
if (req.getUrlObject) r.getUrlObject = () => req.getUrlObject()
|
|
47
|
+
r._.params = req.params
|
|
48
|
+
r._.query = req.query
|
|
49
|
+
return srv.dispatch(r)
|
|
50
|
+
}
|
|
51
|
+
|
|
28
52
|
/// It's important to wait for the completion of all promises, otherwise a rollback might happen too soon
|
|
29
53
|
const _promiseAll = async array => {
|
|
30
54
|
const results = await Promise.allSettled(array)
|
|
@@ -109,16 +133,15 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
109
133
|
(query.DELETE && 'DELETE') ||
|
|
110
134
|
req.event
|
|
111
135
|
_req.target = query._target
|
|
112
|
-
_req._ = Object.assign({}, req._ || {}) // don't share the same `_` object
|
|
113
|
-
_req._.params = req.params
|
|
114
136
|
_req.params = req.params
|
|
137
|
+
_req._ = Object.assign({}, req._ || {})
|
|
138
|
+
_req._.params = req.params
|
|
115
139
|
_req._.query = query
|
|
140
|
+
const props = ['_isRest', '_isOData', 'isConcurrentResource', 'isConditional', 'validateEtag']
|
|
141
|
+
props.forEach(p => {
|
|
142
|
+
if (req[p]) _req.p = req[p]
|
|
143
|
+
})
|
|
116
144
|
_req._ = req._
|
|
117
|
-
_req._isRest = req._isRest
|
|
118
|
-
_req._isOData = req._isOData
|
|
119
|
-
_req.isConcurrentResource = req.isConcurrentResource
|
|
120
|
-
_req.isConditional = req.isConditional
|
|
121
|
-
_req.validateEtag = req.validateEtag
|
|
122
145
|
const cqnData = _req.query.UPDATE?.data || _req.query.INSERT?.entries?.[0]
|
|
123
146
|
if (cqnData) _req.data = cqnData // must point to the same object
|
|
124
147
|
Object.defineProperty(_req, '_messages', {
|
|
@@ -162,7 +185,8 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
162
185
|
])
|
|
163
186
|
)
|
|
164
187
|
const inProcessByUser = draft?.DraftAdministrativeData?.InProcessByUser
|
|
165
|
-
if (inProcessByUser && inProcessByUser !== cds.context.user.id)
|
|
188
|
+
if (inProcessByUser && inProcessByUser !== cds.context.user.id)
|
|
189
|
+
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER', [inProcessByUser])
|
|
166
190
|
const deletes = [run(DELETE.from({ ref: query.DELETE.from.ref }))]
|
|
167
191
|
if (draft)
|
|
168
192
|
deletes.push(
|
|
@@ -203,7 +227,7 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
203
227
|
)
|
|
204
228
|
if (!res) req.reject(404)
|
|
205
229
|
if (res.DraftAdministrativeData?.InProcessByUser !== cds.context.user.id)
|
|
206
|
-
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER')
|
|
230
|
+
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER', [res.DraftAdministrativeData?.InProcessByUser])
|
|
207
231
|
const DraftAdministrativeData_DraftUUID = res.DraftAdministrativeData_DraftUUID
|
|
208
232
|
delete res.DraftAdministrativeData_DraftUUID
|
|
209
233
|
delete res.DraftAdministrativeData
|
|
@@ -263,7 +287,7 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
263
287
|
)
|
|
264
288
|
if (!res) req.reject(404)
|
|
265
289
|
if (res.DraftAdministrativeData?.InProcessByUser !== cds.context.user.id)
|
|
266
|
-
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER')
|
|
290
|
+
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER', [res.DraftAdministrativeData?.InProcessByUser])
|
|
267
291
|
await UPDATE('DRAFT.DraftAdministrativeData')
|
|
268
292
|
.data({
|
|
269
293
|
InProcessByUser: req.user.id,
|
|
@@ -368,17 +392,17 @@ const Read = {
|
|
|
368
392
|
drafts = []
|
|
369
393
|
}
|
|
370
394
|
}
|
|
371
|
-
Read.merge(query._target, actives, drafts, (row, other) =>
|
|
372
|
-
other
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
)
|
|
395
|
+
Read.merge(query._target, actives, drafts, (row, other) => {
|
|
396
|
+
if (other) Object.assign(row, other, { HasActiveEntity: false, HasDraftEntity: true })
|
|
397
|
+
else
|
|
398
|
+
Object.assign(row, {
|
|
399
|
+
HasActiveEntity: false,
|
|
400
|
+
HasDraftEntity: false,
|
|
401
|
+
DraftAdministrativeData: null,
|
|
402
|
+
DraftAdministrativeData_DraftUUID: null
|
|
403
|
+
})
|
|
404
|
+
_fillIsActiveEntity(row, true, query._target)
|
|
405
|
+
})
|
|
382
406
|
return _requested(actives, query)
|
|
383
407
|
},
|
|
384
408
|
unchanged: async function (run, query) {
|
|
@@ -420,12 +444,12 @@ const Read = {
|
|
|
420
444
|
cds.context.user.id
|
|
421
445
|
)
|
|
422
446
|
const drafts = await run(draftsQuery)
|
|
423
|
-
Read.merge(query._target, drafts, [], row =>
|
|
447
|
+
Read.merge(query._target, drafts, [], row => {
|
|
424
448
|
Object.assign(row, {
|
|
425
|
-
HasDraftEntity: false
|
|
426
|
-
IsActiveEntity: false
|
|
449
|
+
HasDraftEntity: false
|
|
427
450
|
})
|
|
428
|
-
|
|
451
|
+
_fillIsActiveEntity(row, false, query._drafts._target)
|
|
452
|
+
})
|
|
429
453
|
return _requested(drafts, query)
|
|
430
454
|
},
|
|
431
455
|
all: async function (run, query) {
|
|
@@ -467,18 +491,17 @@ const Read = {
|
|
|
467
491
|
const count = isFirstPage ? ownNewDrafts.length + (isCount ? actives[0]?.$count : actives.$count) : actives.$count
|
|
468
492
|
if (isCount) return { $count: count }
|
|
469
493
|
|
|
470
|
-
Read.merge(query._target, ownDrafts, [], row =>
|
|
494
|
+
Read.merge(query._target, ownDrafts, [], row => {
|
|
471
495
|
Object.assign(row, {
|
|
472
|
-
IsActiveEntity: false,
|
|
473
496
|
HasDraftEntity: false
|
|
474
497
|
})
|
|
475
|
-
|
|
498
|
+
_fillIsActiveEntity(row, false, query._drafts._target)
|
|
499
|
+
})
|
|
476
500
|
Read.delete(query._target, actives, ownEditDrafts)
|
|
477
501
|
const otherEditDrafts = await Read.complementaryDrafts(run, query, actives)
|
|
478
502
|
Read.merge(query._target, actives, otherEditDrafts, (row, other) => {
|
|
479
503
|
if (other) {
|
|
480
504
|
Object.assign(row, {
|
|
481
|
-
IsActiveEntity: true,
|
|
482
505
|
HasDraftEntity: true,
|
|
483
506
|
HasActiveEntity: false,
|
|
484
507
|
DraftAdministrativeData_DraftUUID: other.DraftAdministrativeData_DraftUUID,
|
|
@@ -486,13 +509,13 @@ const Read = {
|
|
|
486
509
|
})
|
|
487
510
|
} else {
|
|
488
511
|
Object.assign(row, {
|
|
489
|
-
IsActiveEntity: true,
|
|
490
512
|
HasDraftEntity: false,
|
|
491
513
|
HasActiveEntity: false,
|
|
492
514
|
DraftAdministrativeData_DraftUUID: null,
|
|
493
515
|
DraftAdministrativeData: null
|
|
494
516
|
})
|
|
495
517
|
}
|
|
518
|
+
_fillIsActiveEntity(row, true, query._target)
|
|
496
519
|
})
|
|
497
520
|
const res = isFirstPage ? [...ownNewDrafts, ...ownEditDrafts, ...actives] : actives
|
|
498
521
|
if (query.SELECT.count) res.$count = count
|
|
@@ -518,11 +541,11 @@ const Read = {
|
|
|
518
541
|
const actives = drafts.length
|
|
519
542
|
? await run(query.where(Read.whereIn(query._target, drafts)))
|
|
520
543
|
: Object.assign([], { $count: 0 })
|
|
521
|
-
Read.merge(query._target, actives, drafts, (row, other) =>
|
|
522
|
-
other
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
)
|
|
544
|
+
Read.merge(query._target, actives, drafts, (row, other) => {
|
|
545
|
+
if (other) Object.assign(row, other, { HasDraftEntity: true, HasActiveEntity: false })
|
|
546
|
+
else Object.assign({ HasDraftEntity: false, HasActiveEntity: false })
|
|
547
|
+
_fillIsActiveEntity(row, true, query._target)
|
|
548
|
+
})
|
|
526
549
|
return _requested(actives, query)
|
|
527
550
|
},
|
|
528
551
|
unsavedChangesByAnotherUser: async function (run, query) {
|
|
@@ -833,17 +856,17 @@ async function onNew(req) {
|
|
|
833
856
|
let DraftUUID
|
|
834
857
|
if (isRoot) DraftUUID = cds.utils.uuid()
|
|
835
858
|
else {
|
|
836
|
-
const rootData = await this
|
|
837
|
-
SELECT.one(req.query.INSERT.into.ref[0].id)
|
|
859
|
+
const rootData = await _runWithContext(this, req, {
|
|
860
|
+
query: SELECT.one(req.query.INSERT.into.ref[0].id)
|
|
838
861
|
.columns([
|
|
839
862
|
{ ref: ['DraftAdministrativeData_DraftUUID'] },
|
|
840
863
|
{ ref: ['DraftAdministrativeData'], expand: [{ ref: ['InProcessByUser'] }] }
|
|
841
864
|
])
|
|
842
865
|
.where(req.query.INSERT.into.ref[0].where)
|
|
843
|
-
)
|
|
866
|
+
})
|
|
844
867
|
if (!rootData) req.reject(404)
|
|
845
868
|
if (rootData.DraftAdministrativeData?.InProcessByUser !== req.user.id)
|
|
846
|
-
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER')
|
|
869
|
+
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER', [rootData.DraftAdministrativeData.InProcessByUser])
|
|
847
870
|
DraftUUID = rootData.DraftAdministrativeData_DraftUUID
|
|
848
871
|
}
|
|
849
872
|
const timestamp = cds.context.timestamp.toISOString() // REVISIT: toISOString should be done on db layer
|
|
@@ -888,7 +911,7 @@ async function onNew(req) {
|
|
|
888
911
|
delete draftData.IsActiveEntity
|
|
889
912
|
const draftCQN = INSERT.into(req.target).entries(draftData)
|
|
890
913
|
|
|
891
|
-
await _promiseAll([cds.run(adminDataCQN), this
|
|
914
|
+
await _promiseAll([cds.run(adminDataCQN), _runWithContext(this, req, { query: draftCQN })])
|
|
892
915
|
req._.readAfterWrite = true
|
|
893
916
|
return { ...draftData, IsActiveEntity: false }
|
|
894
917
|
}
|
|
@@ -934,12 +957,12 @@ async function onEdit(req) {
|
|
|
934
957
|
// It's not possible to use `FOR UPDATE` in HANA if the view contains joins/unions. Unfortunately, we can't resolve the table entity
|
|
935
958
|
// because we must trigger the app-service request on the target entity (which could be delegated to a remote service).
|
|
936
959
|
// The best we can do is to catch a potential error
|
|
937
|
-
await this
|
|
960
|
+
await _runWithContext(this, req, { query: activeCheck }).catch(_ => {})
|
|
938
961
|
|
|
939
962
|
const [res, draft] = await _promiseAll([
|
|
940
|
-
this
|
|
963
|
+
_runWithContext(this, req, { query: activeCQN }),
|
|
941
964
|
// no user check must be done here...
|
|
942
|
-
this
|
|
965
|
+
_runWithContext(this, req, { query: existingDraft })
|
|
943
966
|
])
|
|
944
967
|
|
|
945
968
|
if (!res) req.reject(404)
|
|
@@ -951,7 +974,7 @@ async function onEdit(req) {
|
|
|
951
974
|
for (const key in req.target.drafts.keys) keys[key] = res[key]
|
|
952
975
|
await _promiseAll([
|
|
953
976
|
DELETE.from('DRAFT.DraftAdministrativeData').where({ DraftUUID }),
|
|
954
|
-
this
|
|
977
|
+
_runWithContext(this, req, { query: DELETE.from(req.target.drafts).where(keys) })
|
|
955
978
|
])
|
|
956
979
|
}
|
|
957
980
|
|
|
@@ -972,7 +995,7 @@ async function onEdit(req) {
|
|
|
972
995
|
res.DraftAdministrativeData_DraftUUID = DraftUUID
|
|
973
996
|
res.HasActiveEntity = true
|
|
974
997
|
delete res.DraftAdministrativeData
|
|
975
|
-
await this
|
|
998
|
+
await _runWithContext(this, req, { query: INSERT.into(targetDraft).entries(res) })
|
|
976
999
|
|
|
977
1000
|
// REVISIT: we need to use okra API here because it must be set in the batched request
|
|
978
1001
|
// status code must be set in handler to allow overriding for FE V2
|
|
@@ -994,18 +1017,28 @@ async function onCancel(req) {
|
|
|
994
1017
|
])
|
|
995
1018
|
// do not add InProcessByUser restriction
|
|
996
1019
|
Object.defineProperty(draftDelete, '_draftParams', { value: draftParams, enumerable: false })
|
|
997
|
-
const draft = await this
|
|
1020
|
+
const draft = await _runWithContext(this, req, { query: draftDelete })
|
|
998
1021
|
if (draftParams.IsActiveEntity === false && !draft) req.reject(404)
|
|
999
1022
|
if (draft && draft.DraftAdministrativeData?.InProcessByUser !== cds.context.user.id)
|
|
1000
|
-
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER')
|
|
1001
|
-
const
|
|
1023
|
+
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER', [draft.DraftAdministrativeData?.InProcessByUser])
|
|
1024
|
+
const queries = !draft ? [] : [_runWithContext(this, req, { query: DELETE.from({ ref: req.query.DELETE.from.ref }) })]
|
|
1002
1025
|
if (draft && req.target['@Common.DraftRoot.ActivationAction'])
|
|
1003
1026
|
// only for draft root
|
|
1004
|
-
|
|
1027
|
+
queries.push(
|
|
1005
1028
|
DELETE.from('DRAFT.DraftAdministrativeData').where({ DraftUUID: draft.DraftAdministrativeData_DraftUUID })
|
|
1006
1029
|
)
|
|
1007
|
-
|
|
1008
|
-
|
|
1030
|
+
else
|
|
1031
|
+
queries.push(
|
|
1032
|
+
UPDATE('Draft.DraftAdministrativeData')
|
|
1033
|
+
.data({
|
|
1034
|
+
InProcessByUser: cds.context.user.id,
|
|
1035
|
+
LastChangedByUser: cds.context.user.id,
|
|
1036
|
+
LastChangeDateTime: cds.context.timestamp.toISOString()
|
|
1037
|
+
})
|
|
1038
|
+
.where({ DraftUUID: draft.DraftAdministrativeData_DraftUUID })
|
|
1039
|
+
)
|
|
1040
|
+
if (draftParams.IsActiveEntity) queries.push(_runWithContext(this, req, { query: DELETE.from({ ref: activeRef }) }))
|
|
1041
|
+
await _promiseAll(queries)
|
|
1009
1042
|
return req.data
|
|
1010
1043
|
}
|
|
1011
1044
|
|
|
@@ -1025,9 +1058,10 @@ async function onPrepare(req) {
|
|
|
1025
1058
|
.columns(keys)
|
|
1026
1059
|
.where(where)
|
|
1027
1060
|
Object.defineProperty(draftQuery, '_draftParams', { value: draftParams, enumerable: false })
|
|
1028
|
-
const data = await this
|
|
1061
|
+
const data = await _runWithContext(this, req, { query: draftQuery })
|
|
1029
1062
|
if (!data) req.reject(404)
|
|
1030
|
-
if (data.DraftAdministrativeData?.InProcessByUser !== req.user.id)
|
|
1063
|
+
if (data.DraftAdministrativeData?.InProcessByUser !== req.user.id)
|
|
1064
|
+
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER', [data.DraftAdministrativeData?.InProcessByUser])
|
|
1031
1065
|
delete data.DraftAdministrativeData
|
|
1032
1066
|
return { ...data, IsActiveEntity: false }
|
|
1033
1067
|
}
|
|
@@ -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
|
|
@@ -25,7 +25,8 @@ class CustomReferenceBuilder extends ReferenceBuilder {
|
|
|
25
25
|
return
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
const sql = ref.map(el => this._quoteElement(el)).join('.')
|
|
29
|
+
this._outputObj.sql.push(sql)
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
32
|
|
|
@@ -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 })
|
|
@@ -58,6 +58,7 @@ function _getProcedureName(sql) {
|
|
|
58
58
|
function _hdbGetResultForProcedure(rows, args, outParameters) {
|
|
59
59
|
// on hdb, rows already contains results for scalar params
|
|
60
60
|
const result = rows || {}
|
|
61
|
+
|
|
61
62
|
// merge table output params into scalar params
|
|
62
63
|
if (args && args.length && outParameters) {
|
|
63
64
|
const params = outParameters.filter(md => !(md.PARAMETER_NAME in rows))
|
|
@@ -65,6 +66,7 @@ function _hdbGetResultForProcedure(rows, args, outParameters) {
|
|
|
65
66
|
result[params[i].PARAMETER_NAME] = args[i]
|
|
66
67
|
}
|
|
67
68
|
}
|
|
69
|
+
|
|
68
70
|
return result
|
|
69
71
|
}
|
|
70
72
|
|
|
@@ -79,6 +81,7 @@ function _hcGetResultForProcedure(stmt, resultSet, outParameters) {
|
|
|
79
81
|
}
|
|
80
82
|
}
|
|
81
83
|
}
|
|
84
|
+
|
|
82
85
|
// merge table output params into scalar params
|
|
83
86
|
const params = Array.isArray(outParameters) && outParameters.filter(md => !(md.PARAMETER_NAME in result))
|
|
84
87
|
if (params && params.length) {
|
|
@@ -91,6 +94,7 @@ function _hcGetResultForProcedure(stmt, resultSet, outParameters) {
|
|
|
91
94
|
resultSet.nextResult()
|
|
92
95
|
}
|
|
93
96
|
}
|
|
97
|
+
|
|
94
98
|
return result
|
|
95
99
|
}
|
|
96
100
|
|
|
@@ -160,7 +164,6 @@ function _executeAsPreparedStatement(dbc, sql, values, reject, resolve) {
|
|
|
160
164
|
}
|
|
161
165
|
|
|
162
166
|
stmt.drop(() => {})
|
|
163
|
-
|
|
164
167
|
resolve(result)
|
|
165
168
|
})
|
|
166
169
|
})
|
|
@@ -180,6 +183,7 @@ function _executeSimpleSQL(dbc, sql, values) {
|
|
|
180
183
|
if (dbc.name !== 'hdb' && typeof values === 'object') {
|
|
181
184
|
values = Object.values(values)
|
|
182
185
|
}
|
|
186
|
+
|
|
183
187
|
// ensure that stored procedure with parameters is always executed as prepared
|
|
184
188
|
if (_hasValues(values) || !!_getProcedureName(sql)) {
|
|
185
189
|
_executeAsPreparedStatement(dbc, sql, values, reject, resolve)
|
|
@@ -230,12 +234,12 @@ function executeSelectCQN(model, dbc, query, user, locale, txTimestamp) {
|
|
|
230
234
|
) {
|
|
231
235
|
return expandV2(model, dbc, query, user, locale, txTimestamp, executeSelectCQN)
|
|
232
236
|
}
|
|
237
|
+
|
|
233
238
|
return _processExpand(model, dbc, query, user, locale, txTimestamp)
|
|
234
239
|
}
|
|
235
240
|
|
|
236
241
|
const { sql, values = [] } = _cqnToSQL(model, query, user, locale, txTimestamp)
|
|
237
242
|
const postProcessMapper = getPostProcessMapper(HANA_TYPE_CONVERSION_MAP, model, query)
|
|
238
|
-
|
|
239
243
|
return _executeSelectSQL(dbc, sql, values, query.SELECT.one, postProcessMapper)
|
|
240
244
|
}
|
|
241
245
|
|
|
@@ -245,17 +249,17 @@ function _getValuesProxy(values) {
|
|
|
245
249
|
if (prop.length > 1 && prop.startsWith(':')) {
|
|
246
250
|
return Object.getOwnPropertyDescriptor(obj, prop.slice(1))
|
|
247
251
|
}
|
|
252
|
+
|
|
248
253
|
return Object.getOwnPropertyDescriptor(obj, prop)
|
|
249
254
|
},
|
|
250
255
|
get: (obj, prop) => {
|
|
251
256
|
if (prop.length > 1 && prop.startsWith(':')) {
|
|
252
257
|
return obj[prop.slice(1)]
|
|
253
258
|
}
|
|
259
|
+
|
|
254
260
|
return obj[prop]
|
|
255
261
|
},
|
|
256
|
-
ownKeys: target => {
|
|
257
|
-
return Reflect.ownKeys(target).map(key => `:${key}`)
|
|
258
|
-
}
|
|
262
|
+
ownKeys: target => Reflect.ownKeys(target).map(key => `:${key}`)
|
|
259
263
|
})
|
|
260
264
|
}
|
|
261
265
|
|
|
@@ -276,6 +280,7 @@ function executeInsertCQN(model, dbc, query, user, locale, txTimestamp) {
|
|
|
276
280
|
if (dbc.name === 'hdb') {
|
|
277
281
|
return writeStreamWithHdb(dbc, sql, values)
|
|
278
282
|
}
|
|
283
|
+
|
|
279
284
|
return writeStreamWithHanaClient(dbc, sql, values)
|
|
280
285
|
}
|
|
281
286
|
|
|
@@ -284,6 +289,7 @@ function executeInsertCQN(model, dbc, query, user, locale, txTimestamp) {
|
|
|
284
289
|
const affectedRowsCount = Array.isArray(affectedRows)
|
|
285
290
|
? affectedRows.reduce((sum, rows) => sum + rows, 0)
|
|
286
291
|
: affectedRows
|
|
292
|
+
|
|
287
293
|
if (entriesOrRows && entriesOrRows.length !== affectedRowsCount) {
|
|
288
294
|
LOG._warn &&
|
|
289
295
|
LOG.warn(
|
|
@@ -295,13 +301,17 @@ function executeInsertCQN(model, dbc, query, user, locale, txTimestamp) {
|
|
|
295
301
|
query
|
|
296
302
|
}
|
|
297
303
|
)
|
|
304
|
+
|
|
298
305
|
throw new Error('Possible data loss by INSERT into HANA db. Please, update a corresponding HANA driver.')
|
|
299
306
|
}
|
|
307
|
+
|
|
300
308
|
// InsertResult needs an object per row with its values
|
|
301
309
|
// query.INSERT.values -> one row
|
|
302
310
|
if (query.INSERT.values) return [{ affectedRows: 1, values: [values] }]
|
|
311
|
+
|
|
303
312
|
// query.INSERT.entries or .rows -> multiple rows
|
|
304
313
|
if (entriesOrRows) return values.map(v => ({ affectedRows: 1, values: v }))
|
|
314
|
+
|
|
305
315
|
// INSERT into SELECT
|
|
306
316
|
return [{ affectedRows }]
|
|
307
317
|
})
|
|
@@ -331,20 +341,17 @@ function executeGenericCQN(model, dbc, query, user, locale, txTimestamp) {
|
|
|
331
341
|
async function executeSelectStreamCQN(model, dbc, query, user, locale, txTimestamp) {
|
|
332
342
|
const { sql, values = [] } = _cqnToSQL(model, query, user, locale, txTimestamp)
|
|
333
343
|
let result
|
|
344
|
+
|
|
334
345
|
if (dbc.name === 'hdb') {
|
|
335
346
|
result = await readStreamWithHdb(dbc, sql, values)
|
|
336
347
|
} else {
|
|
337
348
|
result = await readStreamWithHanaClient(dbc, sql, values)
|
|
338
349
|
}
|
|
339
350
|
|
|
340
|
-
if (result.length === 0)
|
|
341
|
-
return
|
|
342
|
-
}
|
|
351
|
+
if (result.length === 0) return
|
|
343
352
|
|
|
344
353
|
const val = Object.values(result[0])[0]
|
|
345
|
-
if (val === null)
|
|
346
|
-
return null
|
|
347
|
-
}
|
|
354
|
+
if (val === null) return null
|
|
348
355
|
|
|
349
356
|
return { value: val }
|
|
350
357
|
}
|
|
@@ -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).
|