@sap/cds 5.6.4 → 5.7.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 +102 -0
- package/_i18n/i18n_fr.properties +4 -4
- package/apis/cds.d.ts +7 -10
- package/apis/connect.d.ts +3 -3
- package/apis/core.d.ts +2 -4
- package/apis/models.d.ts +2 -3
- package/apis/ql.d.ts +0 -1
- package/apis/services.d.ts +3 -3
- package/bin/build/buildTaskFactory.js +16 -10
- package/bin/build/buildTaskProviderFactory.js +3 -3
- package/bin/build/constants.js +2 -1
- package/bin/build/provider/buildTaskProviderInternal.js +14 -14
- package/bin/build/provider/hana/2migration.js +2 -3
- package/bin/build/provider/hana/index.js +34 -0
- package/bin/build/provider/hana/migrationtable.js +90 -22
- package/bin/build/provider/hana/template/undeploy.json +5 -0
- package/bin/build/provider/node-cf/index.js +9 -2
- package/bin/serve.js +16 -18
- package/lib/compile/cdsc.js +15 -5
- package/lib/compile/etc/_localized.js +4 -4
- package/lib/compile/extend.js +8 -0
- package/lib/compile/index.js +3 -1
- package/lib/compile/minify.js +61 -0
- package/lib/compile/resolve.js +4 -1
- package/lib/compile/to/gql.js +9 -0
- package/lib/compile/to/sql.js +26 -30
- package/lib/connect/index.js +1 -1
- package/lib/core/entities.js +0 -3
- package/lib/core/infer.js +1 -0
- package/lib/core/reflect.js +0 -34
- package/lib/deploy.js +25 -17
- package/lib/env/defaults.js +3 -1
- package/lib/env/index.js +8 -3
- package/lib/env/presets.js +38 -0
- package/lib/env/requires.js +16 -11
- package/lib/index.js +13 -11
- package/lib/log/format/kibana.js +3 -1
- package/lib/log/index.js +2 -2
- package/lib/req/cds-context.js +79 -0
- package/lib/req/context.js +5 -77
- package/lib/req/request.js +1 -1
- package/lib/serve/Service-api.js +8 -4
- package/lib/serve/Service-dispatch.js +0 -7
- package/lib/serve/Service-methods.js +6 -8
- package/lib/serve/Transaction.js +35 -30
- package/lib/serve/adapters.js +1 -4
- package/lib/utils/axios.js +1 -1
- package/libx/_runtime/audit/Service.js +44 -20
- package/libx/_runtime/audit/generic/personal/access.js +16 -11
- package/libx/_runtime/audit/generic/personal/modification.js +5 -5
- package/libx/_runtime/audit/generic/personal/utils.js +46 -37
- package/libx/_runtime/{common/auth → auth}/index.js +21 -7
- package/libx/_runtime/{common/auth → auth}/strategies/JWT.js +2 -2
- package/libx/_runtime/{common/auth → auth}/strategies/basic.js +2 -2
- package/libx/_runtime/{common/auth → auth}/strategies/dummy.js +1 -1
- package/libx/_runtime/{common/auth → auth}/strategies/mock.js +2 -2
- package/libx/_runtime/{common/auth → auth}/strategies/utils/uaa.js +1 -1
- package/libx/_runtime/{common/auth → auth}/strategies/utils/xssec.js +0 -0
- package/libx/_runtime/{common/auth → auth}/strategies/xsuaa.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +7 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +0 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +0 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +3 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +6 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +3 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +2 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +16 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +26 -65
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +0 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +3 -66
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +24 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +13 -10
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/ConditionalRequestControlCommand.js +0 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +0 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +54 -76
- package/libx/_runtime/cds-services/adapter/rest/RestRequest.js +0 -7
- package/libx/_runtime/cds-services/adapter/rest/handlers/create.js +3 -6
- package/libx/_runtime/cds-services/adapter/rest/handlers/delete.js +3 -6
- package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +3 -6
- package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +3 -6
- package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +3 -6
- package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +8 -4
- package/libx/_runtime/cds-services/services/Service.js +0 -6
- package/libx/_runtime/cds-services/services/utils/columns.js +10 -3
- package/libx/_runtime/cds-services/services/utils/compareJson.js +3 -6
- package/libx/_runtime/cds-services/services/utils/differ.js +4 -1
- package/libx/_runtime/cds-services/services/utils/handlerUtils.js +1 -41
- package/libx/_runtime/cds-services/util/assert.js +1 -262
- package/libx/_runtime/cds.js +6 -9
- package/libx/_runtime/common/aspects/entity.js +1 -1
- package/libx/_runtime/common/composition/delete.js +4 -2
- package/libx/_runtime/common/composition/update.js +22 -38
- package/libx/_runtime/common/composition/utils.js +3 -7
- package/libx/_runtime/common/error/standardError.js +11 -0
- package/libx/_runtime/common/generic/auth.js +61 -30
- package/libx/_runtime/common/generic/crud.js +11 -23
- package/libx/_runtime/common/generic/input.js +20 -0
- package/libx/_runtime/common/generic/put.js +4 -10
- package/libx/_runtime/common/generic/sorting.js +12 -30
- package/libx/_runtime/common/perf/index.js +24 -0
- package/libx/_runtime/common/utils/cqn.js +58 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +289 -114
- package/libx/_runtime/common/utils/csn.js +38 -56
- package/libx/_runtime/common/utils/entityFromCqn.js +6 -6
- package/libx/_runtime/common/utils/resolveView.js +4 -5
- package/libx/_runtime/common/utils/rewriteAsterisks.js +46 -5
- package/libx/_runtime/common/utils/search2cqn4sql.js +21 -9
- package/libx/_runtime/common/utils/structured.js +35 -25
- package/libx/_runtime/db/Service.js +0 -6
- package/libx/_runtime/db/expand/expand-v2.js +130 -0
- package/libx/_runtime/db/expand/expandCQNToJoin.js +38 -52
- package/libx/_runtime/db/expand/index.js +3 -1
- package/libx/_runtime/db/generic/input.js +52 -10
- package/libx/_runtime/db/generic/integrity.js +367 -26
- package/libx/_runtime/db/generic/virtual.js +51 -13
- package/libx/_runtime/db/query/update.js +9 -3
- package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +8 -9
- package/libx/_runtime/{common → db}/utils/propagateForeignKeys.js +11 -14
- package/libx/_runtime/fiori/generic/activate.js +1 -0
- package/libx/_runtime/fiori/generic/before.js +2 -1
- package/libx/_runtime/fiori/generic/edit.js +1 -0
- package/libx/_runtime/fiori/generic/patch.js +1 -1
- package/libx/_runtime/fiori/generic/read.js +123 -57
- package/libx/_runtime/fiori/uiflex/index.js +1 -1
- package/libx/_runtime/fiori/uiflex/{extensibility/index.js → service.js} +3 -3
- package/libx/_runtime/fiori/utils/delete.js +7 -1
- package/libx/_runtime/hana/Service.js +1 -8
- package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -14
- package/libx/_runtime/hana/execute.js +10 -4
- package/libx/_runtime/hana/pool.js +55 -45
- package/libx/_runtime/hana/search.js +7 -6
- package/libx/_runtime/hana/search2cqn4sql.js +8 -5
- package/libx/_runtime/hana/searchToContains.js +3 -1
- package/libx/_runtime/index.js +5 -5
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +3 -3
- package/libx/_runtime/messaging/Outbox.js +53 -0
- package/libx/_runtime/messaging/common-utils/AMQPClient.js +17 -10
- package/libx/_runtime/messaging/common-utils/connections.js +14 -9
- package/libx/_runtime/messaging/common-utils/waitingTime.js +2 -0
- package/libx/_runtime/messaging/enterprise-messaging-shared.js +2 -3
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -2
- package/libx/_runtime/messaging/enterprise-messaging.js +21 -15
- package/libx/_runtime/messaging/file-based.js +5 -5
- package/libx/_runtime/messaging/message-queuing.js +2 -3
- package/libx/_runtime/messaging/outbox/OutboxRunner.js +75 -0
- package/libx/_runtime/messaging/outbox/utils.js +192 -0
- package/libx/_runtime/messaging/service.js +16 -30
- package/libx/_runtime/remote/Service.js +15 -0
- package/libx/_runtime/remote/utils/client.js +15 -3
- package/libx/_runtime/remote/utils/{dataConversion.js → data.js} +12 -2
- package/libx/_runtime/sqlite/Service.js +7 -10
- package/libx/_runtime/sqlite/customBuilder/CustomExpressionBuilder.js +19 -0
- package/libx/_runtime/sqlite/execute.js +18 -12
- package/libx/_runtime/types/api.js +2 -1
- package/libx/odata/{odata2cqn/afterburner.js → afterburner.js} +19 -15
- package/libx/odata/{cqn2odata/index.js → cqn2odata.js} +1 -1
- package/libx/odata/{odata2cqn/grammar.pegjs → grammar.pegjs} +171 -130
- package/libx/odata/index.js +16 -14
- package/libx/odata/parser.js +1 -0
- package/libx/odata/utils.js +57 -0
- package/libx/rest/RestAdapter.js +2 -6
- package/libx/rest/utils/data.js +1 -6
- package/package.json +4 -3
- package/server.js +4 -5
- package/srv/audit-log.cds +87 -0
- package/{libx/_runtime/fiori/uiflex/extensibility/index.cds → srv/flex.cds} +0 -0
- package/srv/flex.js +1 -0
- package/srv/outbox.cds +11 -0
- package/srv/outbox.js +0 -0
- package/libx/_runtime/cds-services/adapter/perf/performance.js +0 -104
- package/libx/_runtime/cds-services/adapter/perf/performanceMeasurement.js +0 -33
- package/libx/odata/odata2cqn/index.js +0 -3
- package/libx/odata/odata2cqn/parser.js +0 -1
- package/libx/odata/readme.md +0 -1
- package/libx/odata/utils/index.js +0 -64
|
@@ -147,6 +147,7 @@ const _handler = async function (req) {
|
|
|
147
147
|
r.getUriInfo = () => req.getUriInfo()
|
|
148
148
|
r.getUrlObject = () => req.getUrlObject()
|
|
149
149
|
r._.params = req.params
|
|
150
|
+
r._.query = req.query
|
|
150
151
|
|
|
151
152
|
// use finally to preserve r.messages in success or error case
|
|
152
153
|
let result
|
|
@@ -190,8 +190,9 @@ module.exports = cds.service.impl(function () {
|
|
|
190
190
|
_new._initial = true
|
|
191
191
|
_patchUpdate._initial = true
|
|
192
192
|
_deleteCancel._initial = true
|
|
193
|
+
const entities = Object.values(this.entities).filter(e => e._isDraftEnabled)
|
|
193
194
|
|
|
194
|
-
for (const entity of
|
|
195
|
+
for (const entity of entities) {
|
|
195
196
|
this.before('NEW', entity, _new)
|
|
196
197
|
this.before(['PATCH', 'UPDATE'], entity, _patchUpdate)
|
|
197
198
|
this.before(['DELETE', 'CANCEL'], entity, _deleteCancel)
|
|
@@ -130,6 +130,7 @@ const _handler = async function (req) {
|
|
|
130
130
|
const lockAndSelectCQNs = [lockRecordCQN, draftExistsCQN, ...selectCQNs]
|
|
131
131
|
|
|
132
132
|
const dbtx = cds.tx(req)
|
|
133
|
+
// REVISIT: Use service.read with expand **
|
|
133
134
|
const [, draftExists, ...results] = await _select(lockAndSelectCQNs, req, dbtx)
|
|
134
135
|
|
|
135
136
|
if (!results[0].length) {
|
|
@@ -62,7 +62,7 @@ const _getUpdateDraftCQN = ({ query, target: { name } }, keysCondition) => {
|
|
|
62
62
|
* @param req
|
|
63
63
|
*/
|
|
64
64
|
const _handler = async function (req) {
|
|
65
|
-
if (req.data.IsActiveEntity === 'true') req.reject(400)
|
|
65
|
+
if (req.data.IsActiveEntity === 'true') req.reject(400, 'Patch can only be applied to a draft entity')
|
|
66
66
|
|
|
67
67
|
const keysCondition = getKeysCondition(req.target, req.data)
|
|
68
68
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const cds = require('../../cds')
|
|
2
2
|
const { SELECT } = cds.ql
|
|
3
3
|
|
|
4
|
-
const { cqn2cqn4sql } = require('../../common/utils/cqn2cqn4sql')
|
|
4
|
+
const { cqn2cqn4sql, convertWhereExists } = require('../../common/utils/cqn2cqn4sql')
|
|
5
5
|
const { getElementDeep } = require('../../common/utils/csn')
|
|
6
6
|
|
|
7
7
|
const { DRAFT_COLUMNS, DRAFT_COLUMNS_MAP, SCENARIO } = require('../../common/constants/draft')
|
|
@@ -21,26 +21,51 @@ const { deleteCondition, readAndDeleteKeywords, removeIsActiveEntityRecursively
|
|
|
21
21
|
|
|
22
22
|
const { getColumns } = require('../../cds-services/services/utils/columns')
|
|
23
23
|
|
|
24
|
+
const _findRootSubSelectFor = query => {
|
|
25
|
+
if (query.SELECT.where) {
|
|
26
|
+
const subSelect = query.SELECT.where.find((e, i) => e.SELECT && query.SELECT.where[i - 1] === 'exists')
|
|
27
|
+
return subSelect ? _findRootSubSelectFor(subSelect) : query
|
|
28
|
+
}
|
|
29
|
+
return query
|
|
30
|
+
}
|
|
31
|
+
|
|
24
32
|
// append where with clauses from @restrict
|
|
25
33
|
const _getWhereWithAppendedDraftRestrictions = (where = [], req, scenarioAlias, model) => {
|
|
26
34
|
if (req.query._draftRestrictions) {
|
|
27
35
|
for (const each of req.query._draftRestrictions) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
const xpr = each._xpr
|
|
37
|
+
if (each.target.name === ensureUnlocalized(req.target.name)) {
|
|
38
|
+
// > restriction directly on child
|
|
39
|
+
// REVISIT: remove support for selects in restriction with cds^6
|
|
40
|
+
// adjust alias of @restrict where "exists (select ...)"
|
|
41
|
+
if (scenarioAlias && model)
|
|
42
|
+
xpr
|
|
43
|
+
.filter(e => e.SELECT && e.SELECT.from && e.SELECT.where)
|
|
44
|
+
.forEach(e => {
|
|
45
|
+
const entity = model.definitions[e.SELECT.from.ref[0]]
|
|
46
|
+
e.SELECT.where = e.SELECT.where.map(w => {
|
|
47
|
+
if (w.ref && w.ref.length === 1 && !entity.elements[w.ref[0]]) w.ref.unshift(scenarioAlias)
|
|
48
|
+
return w
|
|
49
|
+
})
|
|
40
50
|
})
|
|
41
|
-
})
|
|
42
51
|
|
|
43
|
-
|
|
52
|
+
if (where.length) where.push('and')
|
|
53
|
+
where.push(...xpr)
|
|
54
|
+
} else {
|
|
55
|
+
// > restriction inherited from parent via autoexposure
|
|
56
|
+
// find inner most sub select if available and append restriction to where clause
|
|
57
|
+
const rootSubSelect = _findRootSubSelectFor({ SELECT: { where } })
|
|
58
|
+
if (rootSubSelect && rootSubSelect.SELECT.from) {
|
|
59
|
+
if (rootSubSelect.SELECT.where && rootSubSelect.SELECT.where.length) rootSubSelect.SELECT.where.push('and')
|
|
60
|
+
const tableAlias = rootSubSelect.SELECT.from.as
|
|
61
|
+
rootSubSelect.SELECT.where.push(
|
|
62
|
+
...xpr.map(e => {
|
|
63
|
+
if (e.ref) return tableAlias ? { ref: [tableAlias, ...e.ref] } : { ref: [...e.ref] }
|
|
64
|
+
return e
|
|
65
|
+
})
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
44
69
|
}
|
|
45
70
|
}
|
|
46
71
|
return where
|
|
@@ -299,21 +324,24 @@ const _allInactive = (req, columns) => {
|
|
|
299
324
|
_getDefaultDraftProperties({ hasDraft: false, isActive: false, withDraftUUID: false })
|
|
300
325
|
)
|
|
301
326
|
|
|
302
|
-
const ids = filterKeys(req.target.keys)
|
|
303
327
|
const isCount = columns.some(element => element.func === 'count')
|
|
304
328
|
|
|
329
|
+
// ensure only own drafts are read
|
|
305
330
|
const cqn = SELECT.from(table)
|
|
331
|
+
.join('DRAFT.DraftAdministrativeData', 'filterAdmin')
|
|
332
|
+
.on([
|
|
333
|
+
{ ref: [table.as, 'DraftAdministrativeData_DraftUUID'] },
|
|
334
|
+
'=',
|
|
335
|
+
{
|
|
336
|
+
ref: ['filterAdmin', 'DraftUUID']
|
|
337
|
+
}
|
|
338
|
+
])
|
|
339
|
+
.where(_inProcessByUserWhere(req.user.id))
|
|
306
340
|
|
|
307
341
|
if (isCount) {
|
|
308
342
|
cqn.columns(...outerMostColumns)
|
|
309
343
|
} else {
|
|
310
344
|
cqn.columns(...outerMostColumns.filter(o => o.as !== 'HasActiveEntity'), { ref: ['HasActiveEntity'] })
|
|
311
|
-
cqn.leftJoin(ensureNoDraftsSuffix(table.ref[0]) + ' as active').on(`${table.as}.${ids[0]} = active.${ids[0]}`)
|
|
312
|
-
|
|
313
|
-
for (let i = 1; i < ids.length; i++) {
|
|
314
|
-
// REVISIT: this is extremely expensive as it repeatedly invokes the compiler's cds.parse.expr -> better extend plain CQN yourself here
|
|
315
|
-
cqn.and(`${table.as}.${ids[i]} = active.${ids[i]}`)
|
|
316
|
-
}
|
|
317
345
|
}
|
|
318
346
|
|
|
319
347
|
cqn.where(req.query.SELECT.where)
|
|
@@ -507,11 +535,11 @@ const _activeWithDraftInProcess = (req, draftWhere, columns, isLocked) => {
|
|
|
507
535
|
subSelect
|
|
508
536
|
)
|
|
509
537
|
|
|
510
|
-
subSelect.SELECT.where = _getWhereWithAppendedDraftRestrictions(subSelect.SELECT.where, req)
|
|
511
|
-
|
|
512
538
|
const outerMostColumns = _getOuterMostColumns(columns, draftColumns)
|
|
513
539
|
|
|
514
|
-
const cqn = SELECT.from(active.table).columns(outerMostColumns)
|
|
540
|
+
const cqn = SELECT.from(active.table).columns(outerMostColumns)
|
|
541
|
+
cqn.where(_getWhereWithAppendedDraftRestrictions([], req))
|
|
542
|
+
cqn.where(['exists', subSelect])
|
|
515
543
|
|
|
516
544
|
return { cqn: getEnrichedCQN(cqn, req.query.SELECT, draftWhere), scenario: SCENARIO.DRAFT_IN_PROCESS }
|
|
517
545
|
}
|
|
@@ -598,13 +626,21 @@ const _getWhereForActive = where => {
|
|
|
598
626
|
return activeWhere
|
|
599
627
|
}
|
|
600
628
|
|
|
601
|
-
const _siblingEntity = (
|
|
629
|
+
const _siblingEntity = (
|
|
630
|
+
{ query, target, nav, params },
|
|
631
|
+
columns,
|
|
632
|
+
model,
|
|
633
|
+
draftAdminAlias,
|
|
634
|
+
parentQuery,
|
|
635
|
+
siblingIndex,
|
|
636
|
+
req
|
|
637
|
+
) => {
|
|
602
638
|
const parentLinks = parentQuery ? _findJoinInQuery(query, parentQuery.SELECT.from.as) : []
|
|
603
639
|
const keys = (nav[siblingIndex + 1].where && (params[siblingIndex] || params[0])) || {}
|
|
604
640
|
const siblingQuery = query.SELECT.where[query.SELECT.where.indexOf('exists') + 1]
|
|
605
641
|
const onCond = _findJoinInQuery(siblingQuery, target.as)
|
|
606
642
|
const siblingAlias = siblingQuery.SELECT.from.as
|
|
607
|
-
const subScenario = _siblingSubScenario(nav, siblingIndex, siblingQuery, target, params, model, onCond)
|
|
643
|
+
const subScenario = _siblingSubScenario(nav, siblingIndex, siblingQuery, target, params, model, onCond, req)
|
|
608
644
|
const isSiblingDraft = subScenario
|
|
609
645
|
? subScenario.isSiblingActive || subScenario.scenario === 'ACTIVE' || subScenario.scenario === 'ALL_ACTIVE'
|
|
610
646
|
: keys.IsActiveEntity && keys.IsActiveEntity !== 'false'
|
|
@@ -652,12 +688,17 @@ const _siblingEntity = ({ query, target, nav, params }, columns, model, draftAdm
|
|
|
652
688
|
return { cqn, scenario: SCENARIO.SIBLING_ENTITY, isSiblingActive: !isSiblingDraft }
|
|
653
689
|
}
|
|
654
690
|
|
|
655
|
-
function _siblingSubScenario(nav, siblingIndex, siblingQuery, target, params, model, onCond) {
|
|
691
|
+
function _siblingSubScenario(nav, siblingIndex, siblingQuery, target, params, model, onCond, req) {
|
|
656
692
|
if (nav[siblingIndex + 1].where) return
|
|
657
693
|
let subScenario
|
|
658
694
|
const subNav = nav.slice(siblingIndex + 1)
|
|
659
695
|
const subSiblingIndex = subNav.indexOf('SiblingEntity')
|
|
660
|
-
const subReq = {
|
|
696
|
+
const subReq = {
|
|
697
|
+
query: siblingQuery,
|
|
698
|
+
target: model.definitions[target.name],
|
|
699
|
+
params: [...params].reverse(),
|
|
700
|
+
user: req.user
|
|
701
|
+
}
|
|
661
702
|
if (subSiblingIndex > -1) {
|
|
662
703
|
subScenario = _getSiblingScenario(subReq, [{ val: 1 }], model, subSiblingIndex, subNav, params)
|
|
663
704
|
if (subSiblingIndex > 0) {
|
|
@@ -670,8 +711,8 @@ function _siblingSubScenario(nav, siblingIndex, siblingQuery, target, params, mo
|
|
|
670
711
|
subReq.query = SELECT.from(siblingQuery.SELECT.from).columns([{ val: 1 }])
|
|
671
712
|
const existsIdx = siblingQuery.SELECT.where.indexOf('exists')
|
|
672
713
|
if (existsIdx > -1) subReq.query.where(siblingQuery.SELECT.where.slice(existsIdx, existsIdx + 2))
|
|
673
|
-
const
|
|
674
|
-
subScenario = _generateCQN(
|
|
714
|
+
const subOrigFrom = { ref: [...subNav].reverse() }
|
|
715
|
+
subScenario = _generateCQN(subOrigFrom, subReq, [{ val: 1 }], model)
|
|
675
716
|
subScenario.cqn.where(onCond)
|
|
676
717
|
}
|
|
677
718
|
return subScenario
|
|
@@ -688,7 +729,15 @@ const _getSiblingScenario = (req, columns, model, siblingIndex, nav) => {
|
|
|
688
729
|
}
|
|
689
730
|
}
|
|
690
731
|
const target = { name: query.SELECT.from.ref[0].id || query.SELECT.from.ref[0], as: query.SELECT.from.as }
|
|
691
|
-
return _siblingEntity(
|
|
732
|
+
return _siblingEntity(
|
|
733
|
+
{ query, target, params, nav },
|
|
734
|
+
columns,
|
|
735
|
+
model,
|
|
736
|
+
draftAdminAlias,
|
|
737
|
+
parentQuery,
|
|
738
|
+
siblingIndex,
|
|
739
|
+
req
|
|
740
|
+
)
|
|
692
741
|
}
|
|
693
742
|
return _getSiblingQueryFromWhere(req.query, siblingIndex)
|
|
694
743
|
}
|
|
@@ -731,7 +780,7 @@ const _getDraftDoc = (req, draftName, draftWhere) => {
|
|
|
731
780
|
|
|
732
781
|
const _getOrderByEnrichedColumns = (orderBy, columns) => {
|
|
733
782
|
const enrichedCol = []
|
|
734
|
-
if (orderBy.length > 1) {
|
|
783
|
+
if (orderBy && orderBy.length > 1) {
|
|
735
784
|
const colNames = columns.map(el => el.ref[el.ref.length - 1])
|
|
736
785
|
// REVISIT: GET Books?$select=title&$expand=NotBooks($select=pages)&$orderby=NotBooks/title - what's then?
|
|
737
786
|
for (const el of orderBy) {
|
|
@@ -757,10 +806,13 @@ const _replaceDraftAlias = where => {
|
|
|
757
806
|
|
|
758
807
|
const _poorMansAlias4 = xpr => '_' + xpr.ref.join('_') + '_'
|
|
759
808
|
|
|
760
|
-
const _getUnionCQN = (req, draftName, columns, subSelect, draftWhere) => {
|
|
809
|
+
const _getUnionCQN = (req, draftName, columns, subSelect, draftWhere, model) => {
|
|
761
810
|
const draftActiveWhere = _getWhereForActive(draftWhere)
|
|
762
811
|
const activeDocs = getEnrichedCQN(SELECT.from(req.target), req.query.SELECT, draftActiveWhere, undefined, false)
|
|
812
|
+
activeDocs.where(_getWhereWithAppendedDraftRestrictions([], req))
|
|
813
|
+
convertWhereExists(activeDocs, model, {})
|
|
763
814
|
|
|
815
|
+
// @restrict.where not applicable for drafts (I can ALWAYS read mine)
|
|
764
816
|
_replaceDraftAlias(draftWhere)
|
|
765
817
|
const draftDocs = _getDraftDoc(req, draftName, draftWhere)
|
|
766
818
|
|
|
@@ -846,7 +898,7 @@ const _getUnionCQN = (req, draftName, columns, subSelect, draftWhere) => {
|
|
|
846
898
|
.columns(..._filterDraftColumnsBySelected(DRAFT_COLUMNS_CASTED, req.query.SELECT.columns))
|
|
847
899
|
}
|
|
848
900
|
|
|
849
|
-
const _excludeActiveDraftExists = (req, draftWhere, columns) => {
|
|
901
|
+
const _excludeActiveDraftExists = (req, draftWhere, columns, model) => {
|
|
850
902
|
const { table, name } = _getTableName(req, true)
|
|
851
903
|
const draftName = table.ref[0]
|
|
852
904
|
|
|
@@ -865,10 +917,8 @@ const _excludeActiveDraftExists = (req, draftWhere, columns) => {
|
|
|
865
917
|
subSelect.where([{ ref: [ensureNoDraftsSuffix(req.target.name), key] }, '=', { ref: [draftName, key] }])
|
|
866
918
|
}
|
|
867
919
|
|
|
868
|
-
draftWhere = _getWhereWithAppendedDraftRestrictions(draftWhere, req)
|
|
869
|
-
|
|
870
920
|
draftWhere = removeIsActiveEntityRecursively(draftWhere)
|
|
871
|
-
const cqn = _getUnionCQN(req, draftName, columns, subSelect, draftWhere)
|
|
921
|
+
const cqn = _getUnionCQN(req, draftName, columns, subSelect, draftWhere, model)
|
|
872
922
|
cqn.SELECT.from.as = name
|
|
873
923
|
|
|
874
924
|
if (cqn.SELECT.orderBy) {
|
|
@@ -900,13 +950,13 @@ const _validatedActiveWithoutDraft = (req, draftWhere, draftParameters, columns)
|
|
|
900
950
|
_isValidActiveWithoutDraft(draftParameters.isActiveEntity, draftParameters.hasDraftEntity) &&
|
|
901
951
|
_activeWithoutDraft(req, draftWhere, columns)
|
|
902
952
|
|
|
903
|
-
const _validatedWithSiblingInProcess = (req, draftWhere, draftParameters, columns) => {
|
|
953
|
+
const _validatedWithSiblingInProcess = (req, draftWhere, draftParameters, columns, model) => {
|
|
904
954
|
const { isActiveEntity, siblingIsActive, draftInProcessByUser } = draftParameters
|
|
905
955
|
if (
|
|
906
956
|
!draftInProcessByUser &&
|
|
907
957
|
_isValidExcludeActiveDraftExists(draftParameters.isActiveEntity, draftParameters.siblingIsActive)
|
|
908
958
|
)
|
|
909
|
-
return _excludeActiveDraftExists(req, draftWhere, columns)
|
|
959
|
+
return _excludeActiveDraftExists(req, draftWhere, columns, model)
|
|
910
960
|
if (
|
|
911
961
|
draftInProcessByUser.op === '!=' &&
|
|
912
962
|
_isValidWithDraftLocked(isActiveEntity, siblingIsActive, draftInProcessByUser)
|
|
@@ -925,9 +975,7 @@ const _draftInSubSelect = (where, req) => {
|
|
|
925
975
|
if (SELECT && SELECT.where) {
|
|
926
976
|
const isActiveEntity = readAndDeleteKeywords(['IsActiveEntity'], SELECT.where, false)
|
|
927
977
|
if (isActiveEntity) {
|
|
928
|
-
|
|
929
|
-
if (isFalse) SELECT.where = _getWhereWithAppendedDraftRestrictions(SELECT.where, req)
|
|
930
|
-
return isFalse
|
|
978
|
+
return _isFalse(isActiveEntity.value.val)
|
|
931
979
|
}
|
|
932
980
|
|
|
933
981
|
return _draftInSubSelect(SELECT.where, req)
|
|
@@ -940,10 +988,16 @@ const _draftInSubSelect = (where, req) => {
|
|
|
940
988
|
const _isDraftAdminScenario = req =>
|
|
941
989
|
req.target.query && req.target.query._target && req.target.query._target.name === 'DRAFT.DraftAdministrativeData'
|
|
942
990
|
|
|
943
|
-
|
|
944
|
-
const
|
|
945
|
-
|
|
946
|
-
|
|
991
|
+
const _generateCQN = (originalFrom, req, columns, model) => {
|
|
992
|
+
const nav = [...originalFrom.ref].reverse() || []
|
|
993
|
+
let siblingIndex = nav.indexOf('SiblingEntity')
|
|
994
|
+
|
|
995
|
+
// it can also be a property access (new parser), then we must shift it
|
|
996
|
+
if (siblingIndex === 1 && req.target.elements[nav[0]]) {
|
|
997
|
+
nav.shift()
|
|
998
|
+
siblingIndex = siblingIndex - 1
|
|
999
|
+
}
|
|
1000
|
+
|
|
947
1001
|
let siblingScenario
|
|
948
1002
|
if (siblingIndex > -1) {
|
|
949
1003
|
siblingScenario = _getSiblingScenario(req, columns, model, siblingIndex, nav)
|
|
@@ -975,8 +1029,6 @@ const _generateCQN = (reqOriginal, req, columns, model) => {
|
|
|
975
1029
|
}
|
|
976
1030
|
|
|
977
1031
|
if (!draftParameters.isActiveEntity) {
|
|
978
|
-
// _draftInSubSelect adds draft restrictions in case check is truthy
|
|
979
|
-
// -> not nice but works for now and we don't need to go in recursively again
|
|
980
1032
|
if (_draftInSubSelect(req.query.SELECT.where, req) || (siblingScenario && !siblingScenario.isSiblingActive)) {
|
|
981
1033
|
// this is only the case when navigating into tree
|
|
982
1034
|
return _allInactive(req, columns)
|
|
@@ -989,23 +1041,20 @@ const _generateCQN = (reqOriginal, req, columns, model) => {
|
|
|
989
1041
|
}
|
|
990
1042
|
|
|
991
1043
|
if (draftParameters.siblingIsActive) {
|
|
992
|
-
return _validatedWithSiblingInProcess(req, req.query.SELECT.where, draftParameters, columns)
|
|
1044
|
+
return _validatedWithSiblingInProcess(req, req.query.SELECT.where, draftParameters, columns, model)
|
|
993
1045
|
}
|
|
994
1046
|
|
|
995
1047
|
return _validatedDraftOfWhichIAmOwner(req, req.query.SELECT.where, draftParameters, columns)
|
|
996
1048
|
}
|
|
997
1049
|
|
|
998
|
-
const _getColumns = ({ query: { SELECT } }, model) => {
|
|
1050
|
+
const _getColumns = ({ query: { SELECT }, target }, model) => {
|
|
999
1051
|
return SELECT.columns
|
|
1000
1052
|
? SELECT.columns.filter(
|
|
1001
1053
|
col =>
|
|
1002
1054
|
(col.ref && !DRAFT_COLUMNS.includes(col.ref[col.ref.length - 1])) ||
|
|
1003
1055
|
(!col.ref && !DRAFT_COLUMNS.includes(col))
|
|
1004
1056
|
)
|
|
1005
|
-
: getColumns(
|
|
1006
|
-
onlyNames: true,
|
|
1007
|
-
removeIgnore: true
|
|
1008
|
-
})
|
|
1057
|
+
: getColumns(target, { onlyNames: true, removeIgnore: true })
|
|
1009
1058
|
}
|
|
1010
1059
|
|
|
1011
1060
|
const _isIsActiveEntity = element => element.ref && element.ref[element.ref.length - 1] === 'IsActiveEntity'
|
|
@@ -1136,6 +1185,13 @@ const _getLocalizedEntity = (model, target, user) => {
|
|
|
1136
1185
|
return localizedEntity || model.definitions[`${prefix}.${target.name}`]
|
|
1137
1186
|
}
|
|
1138
1187
|
|
|
1188
|
+
const _getLastSubQuery = query => (query.SELECT.from.SELECT ? _getLastSubQuery(query.SELECT.from) : query)
|
|
1189
|
+
const _setLastSubQuery = (query, last, prev = query) => {
|
|
1190
|
+
if (query.SELECT.from.SELECT) return _setLastSubQuery(query.SELECT.from, last, query)
|
|
1191
|
+
else prev.SELECT.from = _copyCQNPartial(last)
|
|
1192
|
+
return prev
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1139
1195
|
const _adaptDraftAdminExpand = cqn => {
|
|
1140
1196
|
const draftAdminExpand =
|
|
1141
1197
|
cqn.SELECT.columns && cqn.SELECT.columns.find(c => c.expand && c.ref[0] === 'DraftAdministrativeData')
|
|
@@ -1149,7 +1205,6 @@ const _adaptDraftAdminExpand = cqn => {
|
|
|
1149
1205
|
*
|
|
1150
1206
|
* @param req
|
|
1151
1207
|
*/
|
|
1152
|
-
// eslint-disable-next-line complexity
|
|
1153
1208
|
const _handler = async function (req) {
|
|
1154
1209
|
// handle localized here as it was previously handled for req.target
|
|
1155
1210
|
req.target = _getLocalizedEntity(this.model, req.target, req.user) || req.target
|
|
@@ -1157,6 +1212,8 @@ const _handler = async function (req) {
|
|
|
1157
1212
|
// REVISIT
|
|
1158
1213
|
delete req.query._validationQuery
|
|
1159
1214
|
|
|
1215
|
+
const originalFrom = _copyCQNPartial(req.query.SELECT.from)
|
|
1216
|
+
|
|
1160
1217
|
// REVISIT DRAFT HANDLING: cqn2cqn4sql must not be called here
|
|
1161
1218
|
const sqlQuery = cqn2cqn4sql(req.query, this.model, { draft: true })
|
|
1162
1219
|
|
|
@@ -1171,8 +1228,17 @@ const _handler = async function (req) {
|
|
|
1171
1228
|
reqClone.query._streaming = true
|
|
1172
1229
|
return cds.tx(req).run(reqClone.query)
|
|
1173
1230
|
}
|
|
1231
|
+
let cqnScenario
|
|
1174
1232
|
|
|
1175
|
-
|
|
1233
|
+
// to replace scenario CQNs for queries with $apply SELECT chain (new odata2cqn parser)
|
|
1234
|
+
// just to make existing tests working with new parser. not really tested, not needed to be supported
|
|
1235
|
+
if (reqClone.query.SELECT.from.SELECT) {
|
|
1236
|
+
const subQueryReq = { __proto__: req, query: _copyCQNPartial(_getLastSubQuery(reqClone.query)) }
|
|
1237
|
+
cqnScenario = _generateCQN(originalFrom.SELECT.from, subQueryReq, _getColumns(subQueryReq, this.model), this.model)
|
|
1238
|
+
cqnScenario.cqn = _setLastSubQuery(reqClone.query, cqnScenario.cqn)
|
|
1239
|
+
} else {
|
|
1240
|
+
cqnScenario = _generateCQN(originalFrom, reqClone, _getColumns(reqClone, this.model), this.model)
|
|
1241
|
+
}
|
|
1176
1242
|
|
|
1177
1243
|
if (!cqnScenario) {
|
|
1178
1244
|
req.reject(400)
|
|
@@ -29,7 +29,7 @@ module.exports = async () => {
|
|
|
29
29
|
.before('READ', transformExtendedFieldsREAD)
|
|
30
30
|
.after('READ', transformExtendedFieldsRESULT)
|
|
31
31
|
if ('cds_r.ExtensibilityService' in cds.services) return
|
|
32
|
-
const model = require('path').join(__dirname, '
|
|
32
|
+
const model = require('path').join(__dirname, '../../../..', 'srv/flex.cds')
|
|
33
33
|
return cds.serve(model, { silent: true }).to('odata').in(cds.app)
|
|
34
34
|
})
|
|
35
35
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
const cds = require('
|
|
2
|
-
const { ensureDraftsSuffix } = require('
|
|
1
|
+
const cds = require('../../cds')
|
|
2
|
+
const { ensureDraftsSuffix } = require('../../common/utils/draft')
|
|
3
3
|
|
|
4
|
-
const { EXT_BACK_PACK } = require('
|
|
4
|
+
const { EXT_BACK_PACK } = require('./utils')
|
|
5
5
|
|
|
6
6
|
const _getDraftTable = (view, cds) => {
|
|
7
7
|
return cds.model.definitions[view]._isDraftEnabled ? ensureDraftsSuffix(view) : undefined
|
|
@@ -75,7 +75,13 @@ const deleteDraft = async (req, srv, includingActive = false) => {
|
|
|
75
75
|
const delCQNs = []
|
|
76
76
|
|
|
77
77
|
if (includingActive) {
|
|
78
|
-
|
|
78
|
+
const r = new cds.Request({ query: DELETE.from(ensureNoDraftsSuffix(req.target.name)).where(keys.keyList) })
|
|
79
|
+
|
|
80
|
+
// REVISIT: should not be necessary
|
|
81
|
+
r._ = Object.assign(r._, req._)
|
|
82
|
+
r._.query = req.query
|
|
83
|
+
|
|
84
|
+
await dbtx.dispatch(r)
|
|
79
85
|
}
|
|
80
86
|
|
|
81
87
|
if (draftResult.length !== 0) {
|
|
@@ -45,13 +45,6 @@ class HanaDatabase extends DatabaseService {
|
|
|
45
45
|
this._run = this._queries.run(this._insert, this._read, this._update, this._delete, execute.cqn, execute.sql)
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
set model(csn) {
|
|
49
|
-
const m = csn && 'definitions' in csn ? cds.linked(cds.compile.for.odata(csn)) : csn
|
|
50
|
-
// with compiler v2 we always need to localized the csn
|
|
51
|
-
cds.alpha_localized(m)
|
|
52
|
-
super.model = m
|
|
53
|
-
}
|
|
54
|
-
|
|
55
48
|
init() {
|
|
56
49
|
this._registerBeforeHandlers()
|
|
57
50
|
this._registerOnHandlers()
|
|
@@ -134,7 +127,7 @@ class HanaDatabase extends DatabaseService {
|
|
|
134
127
|
async acquire(arg) {
|
|
135
128
|
// REVISIT: remove fallback arg.user.tenant with cds^6
|
|
136
129
|
const tenant = (typeof arg === 'string' ? arg : arg.tenant || (arg.user && arg.user.tenant)) || 'anonymous'
|
|
137
|
-
const dbc = await pool.acquire(tenant, this
|
|
130
|
+
const dbc = await pool.acquire(tenant, this)
|
|
138
131
|
|
|
139
132
|
if (typeof arg !== 'string') {
|
|
140
133
|
_setSessionContext(dbc, 'APPLICATIONUSER', arg.user.id || 'ANONYMOUS')
|
|
@@ -34,18 +34,9 @@ class CustomSelectBuilder extends SelectBuilder {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
getParameters() {
|
|
37
|
-
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
(cds.env.runtime && cds.env.runtime.skipWithParameters)
|
|
41
|
-
) {
|
|
42
|
-
return ''
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// REVISIT: remove feature flag skip_with_parameters after grace period of at least two months (> June release)
|
|
46
|
-
if (cds.env.features && cds.env.features.skip_with_parameters === false) {
|
|
47
|
-
return this._options.locale ? `with parameters ('LOCALE' = '${this._options.locale}')` : ''
|
|
48
|
-
}
|
|
37
|
+
const { with_parameters: withParameters } = cds.env.features
|
|
38
|
+
if (withParameters === false || !this._options.locale) return ''
|
|
39
|
+
if (withParameters === true) return `with parameters ('LOCALE' = '${this._options.locale}')`
|
|
49
40
|
|
|
50
41
|
// skip with parameters if all orderby columns are not strings
|
|
51
42
|
let skip
|
|
@@ -78,9 +69,9 @@ class CustomSelectBuilder extends SelectBuilder {
|
|
|
78
69
|
}
|
|
79
70
|
}
|
|
80
71
|
}
|
|
81
|
-
if (skip) return ''
|
|
82
72
|
|
|
83
|
-
|
|
73
|
+
if (skip) return ''
|
|
74
|
+
return `with parameters ('LOCALE' = '${this._options.locale}')`
|
|
84
75
|
}
|
|
85
76
|
}
|
|
86
77
|
|
|
@@ -7,7 +7,7 @@ const {
|
|
|
7
7
|
getStructMapper,
|
|
8
8
|
postProcess
|
|
9
9
|
} = require('../db/data-conversion/post-processing')
|
|
10
|
-
const { createJoinCQNFromExpanded, hasExpand, rawToExpanded } = require('../db/expand')
|
|
10
|
+
const { createJoinCQNFromExpanded, hasExpand, rawToExpanded, expandV2 } = require('../db/expand')
|
|
11
11
|
const {
|
|
12
12
|
hasStreamInsert,
|
|
13
13
|
hasStreamUpdate,
|
|
@@ -33,6 +33,8 @@ function _cqnToSQL(model, query, user, locale, txTimestamp) {
|
|
|
33
33
|
const cds = require('../cds')
|
|
34
34
|
const LOG = cds.log('hana|db|sql')
|
|
35
35
|
|
|
36
|
+
const SANITIZE_VALUES = process.env.NODE_ENV === 'production' && cds.env.log.sanitize_values !== false
|
|
37
|
+
|
|
36
38
|
function _getOutputParameters(stmt) {
|
|
37
39
|
const result = {}
|
|
38
40
|
const info = stmt.getParameterInfo()
|
|
@@ -50,7 +52,7 @@ function _executeAsPreparedStatement(dbc, sql, values, reject, resolve) {
|
|
|
50
52
|
dbc.prepare(sql, function (err, stmt) {
|
|
51
53
|
if (err) {
|
|
52
54
|
err.query = sql
|
|
53
|
-
if (values) err.values = values
|
|
55
|
+
if (values) err.values = SANITIZE_VALUES ? ['***'] : values
|
|
54
56
|
return reject(err)
|
|
55
57
|
}
|
|
56
58
|
|
|
@@ -76,7 +78,7 @@ function _executeAsPreparedStatement(dbc, sql, values, reject, resolve) {
|
|
|
76
78
|
if (err) {
|
|
77
79
|
stmt.drop(() => {})
|
|
78
80
|
err.query = sql
|
|
79
|
-
if (values) err.values = values
|
|
81
|
+
if (values) err.values = SANITIZE_VALUES ? ['***'] : values
|
|
80
82
|
return reject(err)
|
|
81
83
|
}
|
|
82
84
|
|
|
@@ -99,7 +101,7 @@ function _executeSimpleSQL(dbc, sql, values) {
|
|
|
99
101
|
const res = sql.match(regex)
|
|
100
102
|
if (res) sql = sql.replace(regex, '') + ' ' + res[0]
|
|
101
103
|
|
|
102
|
-
LOG._debug && LOG.debug(
|
|
104
|
+
LOG._debug && LOG.debug(sql, SANITIZE_VALUES ? ['***'] : values)
|
|
103
105
|
return new Promise((resolve, reject) => {
|
|
104
106
|
// hana-client only accepts arrays
|
|
105
107
|
if (dbc.name !== 'hdb' && typeof values === 'object') {
|
|
@@ -149,6 +151,10 @@ function _processExpand(model, dbc, cqn, user, locale, txTimestamp) {
|
|
|
149
151
|
|
|
150
152
|
function executeSelectCQN(model, dbc, query, user, locale, txTimestamp) {
|
|
151
153
|
if (hasExpand(query)) {
|
|
154
|
+
// expand: '**' or '*3' is handled by new impl
|
|
155
|
+
if (query.SELECT.columns.some(c => c.expand && typeof c.expand === 'string' && /^\*{1}[\d|*]+/.test(c.expand))) {
|
|
156
|
+
return expandV2(model, dbc, query, user, locale, txTimestamp, executeSelectCQN)
|
|
157
|
+
}
|
|
152
158
|
return _processExpand(model, dbc, query, user, locale, txTimestamp)
|
|
153
159
|
}
|
|
154
160
|
|