@sap/cds 6.6.2 → 6.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 +72 -2
- package/README.md +1 -1
- package/apis/connect.d.ts +11 -4
- package/apis/core.d.ts +1 -1
- package/apis/csn.d.ts +1 -0
- package/apis/internal/inference.d.ts +15 -2
- package/apis/log.d.ts +10 -0
- package/apis/serve.d.ts +4 -9
- package/apis/services.d.ts +86 -19
- package/bin/build/buildTaskEngine.js +16 -42
- package/bin/build/constants.js +4 -2
- package/bin/build/provider/buildTaskProviderInternal.js +117 -85
- package/bin/build/provider/hana/index.js +6 -1
- package/bin/build/provider/mtx-extension/index.js +74 -34
- package/bin/build/provider/mtx-sidecar/index.js +3 -3
- package/bin/build/provider/nodejs/index.js +2 -2
- package/bin/build/util.js +63 -14
- package/bin/cds-serve.js +6 -0
- package/bin/cds.js +22 -4
- package/bin/deploy/to-hana/cfUtil.js +15 -1
- package/bin/mtx/in-cds.js +2 -9
- package/bin/plugins.js +31 -0
- package/bin/serve.js +12 -12
- package/lib/compile/etc/_localized.js +1 -1
- package/lib/compile/for/lean_drafts.js +23 -6
- package/lib/compile/for/nodejs.js +4 -1
- package/lib/compile/load.js +4 -2
- package/lib/core/index.js +35 -15
- package/lib/dbs/cds-deploy.js +129 -133
- package/lib/env/cds-env.js +25 -17
- package/lib/env/cds-requires.js +10 -40
- package/lib/env/compat.js +12 -0
- package/lib/env/defaults.js +17 -9
- package/lib/env/plugins.js +29 -0
- package/lib/env/schemas/cds-rc.json +14 -0
- package/lib/index.js +3 -0
- package/lib/log/cds-log.js +7 -4
- package/lib/ql/CREATE.js +1 -1
- package/lib/ql/DELETE.js +1 -1
- package/lib/ql/DROP.js +3 -3
- package/lib/ql/INSERT.js +1 -1
- package/lib/ql/Query.js +14 -6
- package/lib/ql/SELECT.js +8 -2
- package/lib/ql/UPDATE.js +1 -1
- package/lib/ql/Whereable.js +1 -1
- package/lib/ql/cds-ql.js +1 -9
- package/lib/req/cds-context.js +1 -4
- package/lib/req/request.js +63 -2
- package/lib/req/response.js +3 -2
- package/lib/srv/bindings.js +69 -71
- package/lib/srv/cds-connect.js +4 -1
- package/lib/srv/cds-serve.js +4 -0
- package/lib/srv/middlewares/index.js +37 -6
- package/lib/srv/protocols/_legacy.js +1 -1
- package/lib/srv/protocols/index.js +1 -1
- package/lib/srv/srv-api.js +4 -6
- package/lib/srv/srv-dispatch.js +4 -3
- package/lib/srv/srv-handlers.js +1 -1
- package/lib/srv/srv-methods.js +8 -2
- package/lib/utils/cds-test.js +4 -1
- package/libx/_runtime/audit/Service.js +8 -9
- package/libx/_runtime/audit/generic/personal/index.js +1 -1
- package/libx/_runtime/audit/generic/personal/utils.js +1 -1
- package/libx/_runtime/audit/utils/v2.js +17 -20
- package/libx/_runtime/auth/strategies/mock.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +2 -0
- 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/delete.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +12 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +1 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +5 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +4 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/oDataConfiguration.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +1 -1
- package/libx/_runtime/cds-services/services/Service.js +28 -1
- package/libx/_runtime/cds-services/util/assert.js +41 -65
- package/libx/_runtime/common/code-ext/WorkerPool.js +90 -0
- package/libx/_runtime/common/code-ext/WorkerReq.js +0 -4
- package/libx/_runtime/common/code-ext/execute.js +28 -18
- package/libx/_runtime/common/code-ext/handlers.js +5 -4
- package/libx/_runtime/common/code-ext/worker.js +45 -3
- package/libx/_runtime/common/code-ext/workerQueryExecutor.js +8 -7
- package/libx/_runtime/common/composition/delete.js +1 -1
- package/libx/_runtime/common/composition/update.js +3 -5
- package/libx/_runtime/common/generic/auth/expand.js +1 -1
- package/libx/_runtime/common/generic/auth/readOnly.js +5 -4
- package/libx/_runtime/common/generic/auth/restrict.js +7 -2
- package/libx/_runtime/common/generic/auth/utils.js +5 -2
- package/libx/_runtime/common/generic/crud.js +12 -1
- package/libx/_runtime/common/generic/etag.js +11 -3
- package/libx/_runtime/common/generic/input.js +8 -6
- package/libx/_runtime/common/generic/paging.js +25 -8
- package/libx/_runtime/common/generic/put.js +1 -1
- package/libx/_runtime/common/generic/sorting.js +0 -1
- package/libx/_runtime/common/i18n/messages.properties +1 -0
- package/libx/_runtime/common/utils/cqn.js +5 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +3 -3
- package/libx/_runtime/common/utils/resolveView.js +14 -10
- package/libx/_runtime/common/utils/rewriteAsterisks.js +2 -3
- package/libx/_runtime/common/utils/templateProcessor.js +15 -17
- package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +18 -6
- package/libx/_runtime/db/Service.js +1 -0
- package/libx/_runtime/db/data-conversion/post-processing.js +0 -18
- package/libx/_runtime/db/expand/expand-v2.js +2 -2
- package/libx/_runtime/db/expand/rawToExpanded.js +6 -6
- package/libx/_runtime/db/generic/integrity.js +1 -1
- package/libx/_runtime/db/utils/columns.js +5 -5
- package/libx/_runtime/fiori/generic/activate.js +3 -3
- package/libx/_runtime/fiori/generic/edit.js +1 -1
- package/libx/_runtime/fiori/generic/new.js +4 -0
- package/libx/_runtime/fiori/lean-draft.js +178 -68
- package/libx/_runtime/hana/execute.js +3 -1
- package/libx/_runtime/hana/pool.js +10 -2
- package/libx/_runtime/hana/search2cqn4sql.js +1 -1
- package/libx/_runtime/messaging/common-utils/AMQPClient.js +6 -1
- package/libx/_runtime/messaging/enterprise-messaging.js +1 -0
- package/libx/_runtime/remote/Service.js +16 -13
- package/libx/_runtime/remote/utils/client.js +6 -1
- package/libx/_runtime/sqlite/Service.js +5 -59
- package/libx/_runtime/sqlite/convertDraftAdminPathExpression.js +1 -0
- package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +2 -2
- package/libx/_runtime/sqlite/execute.js +3 -1
- package/libx/_runtime/types/api.js +12 -3
- package/libx/odata/afterburner.js +38 -2
- package/libx/odata/cqn2odata.js +3 -2
- package/libx/odata/grammar.pegjs +5 -3
- package/libx/odata/parser.js +1 -1
- package/libx/odata/utils.js +1 -1
- package/libx/rest/RestAdapter.js +1 -1
- package/libx/rest/RestRequest.js +1 -0
- package/package.json +5 -2
- package/libx/_runtime/common/code-ext/workerQuery.js +0 -45
- package/libx/_runtime/common/constants/limit.js +0 -12
- package/libx/_runtime/common/utils/page.js +0 -39
|
@@ -1,30 +1,31 @@
|
|
|
1
1
|
const cds = require('../../cds')
|
|
2
2
|
const LOG = cds.log()
|
|
3
3
|
const { parentPort } = require('worker_threads')
|
|
4
|
-
const
|
|
4
|
+
const executionContextMap = new Map()
|
|
5
5
|
|
|
6
6
|
parentPort.on('message', function onWorkerMessageReceived(message) {
|
|
7
7
|
const { id, kind, result } = message
|
|
8
|
+
if (!executionContextMap.has(id)) return
|
|
8
9
|
if (LOG._debug) LOG.debug(`Post message received on worker thread (workerQueryExecutor.js) from main thread`, message)
|
|
9
|
-
if (!executorCallbackMap.has(id)) return
|
|
10
10
|
|
|
11
11
|
switch (kind) {
|
|
12
12
|
case 'responseData':
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
executionContextMap.get(id)(result)
|
|
14
|
+
executionContextMap.delete(id)
|
|
15
15
|
return
|
|
16
16
|
|
|
17
17
|
case 'cleanup':
|
|
18
|
-
|
|
18
|
+
executionContextMap.delete(id)
|
|
19
19
|
return
|
|
20
20
|
}
|
|
21
21
|
})
|
|
22
22
|
|
|
23
|
-
function queryExecutor(resolve) {
|
|
23
|
+
function queryExecutor(contextId, resolve) {
|
|
24
24
|
const id = cds.utils.uuid()
|
|
25
|
-
|
|
25
|
+
executionContextMap.set(id, result => resolve(result))
|
|
26
26
|
parentPort.postMessage({
|
|
27
27
|
id,
|
|
28
|
+
contextId,
|
|
28
29
|
kind: 'run',
|
|
29
30
|
target: 'srv',
|
|
30
31
|
prop: 'run',
|
|
@@ -230,7 +230,7 @@ const getSetNullParentForeignKeyCQNs = async (model, req, dbQuery) => {
|
|
|
230
230
|
const cqns = []
|
|
231
231
|
const query = dbQuery || req.query
|
|
232
232
|
// REVISIT: req._tx should not be used like that!
|
|
233
|
-
const origQuery = (req.tx
|
|
233
|
+
const origQuery = (req.tx.isDatabaseService && req._ && req._.query) || req.query
|
|
234
234
|
if (!dbQuery && origQuery && origQuery.DELETE && origQuery.DELETE.from.ref && origQuery.DELETE.from.ref.length > 1) {
|
|
235
235
|
// delete via 2one navigation => parent is known => no need to SELECT
|
|
236
236
|
const ref = origQuery.DELETE.from.ref
|
|
@@ -9,6 +9,7 @@ const { ensureNoDraftsSuffix } = require('../utils/draft')
|
|
|
9
9
|
const { deepCopyObject } = require('../utils/copy')
|
|
10
10
|
|
|
11
11
|
const getError = require('../../common/error')
|
|
12
|
+
const { getEntityNameFromUpdateCQN } = require('../utils/cqn')
|
|
12
13
|
|
|
13
14
|
const CHUNK_SIZE = cds.env.features.chunk_deep || Number.MAX_VALUE
|
|
14
15
|
|
|
@@ -264,9 +265,7 @@ const _addSubDeepUpdateCQN = async ({ model, compositionTree, data, selectData,
|
|
|
264
265
|
|
|
265
266
|
const hasDeepUpdate = (model, cqn) => {
|
|
266
267
|
if (cqn && cqn.UPDATE && cqn.UPDATE.entity && (cqn.UPDATE.data || cqn.UPDATE.with)) {
|
|
267
|
-
const
|
|
268
|
-
const entityName =
|
|
269
|
-
(updateEntity.ref && (updateEntity.ref[0].id || updateEntity.ref[0])) || updateEntity.name || updateEntity
|
|
268
|
+
const entityName = getEntityNameFromUpdateCQN(cqn)
|
|
270
269
|
const entity = model.definitions[ensureNoDraftsSuffix(entityName)]
|
|
271
270
|
|
|
272
271
|
if (entity) {
|
|
@@ -287,8 +286,7 @@ const getDeepUpdateCQNs = async (model, req, selectData) => {
|
|
|
287
286
|
if (selectData.length > 1) throw getError('Deep update can only be performed on a single instance')
|
|
288
287
|
|
|
289
288
|
const cqns = []
|
|
290
|
-
const from =
|
|
291
|
-
(query.UPDATE.entity.ref && query.UPDATE.entity.ref[0]) || query.UPDATE.entity.name || query.UPDATE.entity
|
|
289
|
+
const from = getEntityNameFromUpdateCQN(query)
|
|
292
290
|
const entityName = ensureNoDraftsSuffix(from)
|
|
293
291
|
const draft = entityName !== from
|
|
294
292
|
const data = query.UPDATE.data ? deepCopyObject(query.UPDATE.data) : {}
|
|
@@ -30,7 +30,7 @@ const _getRestrictedExpand = (columns, target, definitions) => {
|
|
|
30
30
|
if (ref_) return ref_
|
|
31
31
|
}
|
|
32
32
|
// expand: '**' or '*3' is only possible within custom handler, no check needed
|
|
33
|
-
if (typeof col.expand === 'string' && /^\*{1}[\d|*]+/.test(col.expand)) {
|
|
33
|
+
if (typeof col.expand[0] === 'string' && /^\*{1}[\d|*]+/.test(col.expand[0])) {
|
|
34
34
|
continue
|
|
35
35
|
} else {
|
|
36
36
|
const restricted = _getRestrictedExpand(col.expand, _getTarget(col.ref, target, definitions), definitions)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const cds = require('../../../cds')
|
|
1
2
|
const { getAuthRelevantEntity } = require('./utils')
|
|
2
3
|
const { WRITE_EVENTS } = require('./constants')
|
|
3
4
|
|
|
@@ -14,11 +15,11 @@ function handler(req) {
|
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
// @read-only
|
|
17
|
-
|
|
18
|
+
let entity = getAuthRelevantEntity(req, this.model, ['@readonly'])
|
|
19
|
+
if (cds.env.fiori.lean_draft && (req.event === 'NEW' || req.event === 'UPDATE')) entity = entity?.actives
|
|
20
|
+
|
|
18
21
|
if (!entity || !entity['@readonly']) return
|
|
19
|
-
if (entity['@readonly'] && req.event in WRITE_EVENTS)
|
|
20
|
-
req.reject(405, 'ENTITY_IS_READ_ONLY', [entity.name])
|
|
21
|
-
}
|
|
22
|
+
if (entity['@readonly'] && req.event in WRITE_EVENTS) req.reject(405, 'ENTITY_IS_READ_ONLY', [entity.name])
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
handler._initial = true
|
|
@@ -142,13 +142,18 @@ const _getRestrictionForTarget = (resolvedApplicables, target) => {
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
const _addRestrictionsToRead = async (req, model, resolvedApplicables) => {
|
|
145
|
-
if (!cds.env.
|
|
145
|
+
if (!cds.env.fiori.lean_draft && req.target._isDraftEnabled) {
|
|
146
146
|
req.query._draftRestrictions = resolvedApplicables
|
|
147
147
|
return
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
if (typeof req.query.SELECT.from === 'object')
|
|
151
|
-
|
|
151
|
+
// in case of $apply take a ref from sub SELECT//
|
|
152
|
+
req.query.SELECT.from.ref = _addWheresToRef(
|
|
153
|
+
req.query.SELECT.from.ref || req.query.SELECT.from.SELECT?.from?.ref,
|
|
154
|
+
model,
|
|
155
|
+
resolvedApplicables
|
|
156
|
+
)
|
|
152
157
|
|
|
153
158
|
const restrictionForTarget = _getRestrictionForTarget(resolvedApplicables, req.target)
|
|
154
159
|
if (!restrictionForTarget) return
|
|
@@ -141,8 +141,11 @@ const resolveUserAttrs = (restrict, req) => {
|
|
|
141
141
|
attr = parts.shift()
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
if (!skip)
|
|
145
|
-
|
|
144
|
+
if (!skip) {
|
|
145
|
+
const v = val === undefined ? null : typeof val === 'string' && val.match(/^\d*$/) ? `'${val}'` : val
|
|
146
|
+
restrict.where = restrict.where.replace(next[0], v).replace('in null', 'is null')
|
|
147
|
+
}
|
|
148
|
+
|
|
146
149
|
next = _getNext(restrict.where)
|
|
147
150
|
}
|
|
148
151
|
|
|
@@ -54,10 +54,21 @@ exports.impl = cds.service.impl(function () {
|
|
|
54
54
|
|
|
55
55
|
if (req.event in { DELETE: 1, UPDATE: 1 } && req.target && req.target._isSingleton) {
|
|
56
56
|
if (req.event === 'DELETE' && !req.target['@odata.singleton.nullable']) req.reject(400, 'SINGLETON_NOT_NULLABLE')
|
|
57
|
+
const selectSingleton = SELECT.one(req.target)
|
|
57
58
|
const keyColumns = getColumns(req.target, { onlyNames: true, keysOnly: true })
|
|
58
|
-
|
|
59
|
+
|
|
60
|
+
// if no keys available, select all columns so we can delete the singleton with same content
|
|
61
|
+
if (keyColumns.length) selectSingleton.columns(keyColumns)
|
|
62
|
+
|
|
59
63
|
const singleton = await cds.tx(req).run(selectSingleton)
|
|
60
64
|
if (!singleton) req.reject(404)
|
|
65
|
+
|
|
66
|
+
// REVISIT: Workaround for singleton, to get keys into singleton
|
|
67
|
+
for (const keyName in singleton) {
|
|
68
|
+
if (!keyColumns.includes(keyName)) continue
|
|
69
|
+
req.data[keyName] = singleton[keyName]
|
|
70
|
+
}
|
|
71
|
+
|
|
61
72
|
req.query.where(singleton)
|
|
62
73
|
}
|
|
63
74
|
|
|
@@ -6,8 +6,7 @@ const { isActiveEntityRequested } = require('../../fiori/utils/where')
|
|
|
6
6
|
const { ensureDraftsSuffix } = require('../../fiori/utils/handler')
|
|
7
7
|
const { cqn2cqn4sql } = require('../../common/utils/cqn2cqn4sql')
|
|
8
8
|
const { isAsteriskColumn } = require('../../common/utils/rewriteAsterisks')
|
|
9
|
-
const
|
|
10
|
-
const { resolveView, getTransition } = require('../utils/resolveView')
|
|
9
|
+
const { resolveView } = require('../utils/resolveView')
|
|
11
10
|
|
|
12
11
|
const C_U_ = {
|
|
13
12
|
CREATE: 1,
|
|
@@ -61,8 +60,17 @@ const _addEtagColumns = (columns, entity) => {
|
|
|
61
60
|
const _isConcurrentODataReq = req => {
|
|
62
61
|
const isReadAfterDraftAction =
|
|
63
62
|
req.event === 'READ' && req.target._isDraftEnabled && req.context.event in { draftActivate: 1, EDIT: 1 }
|
|
63
|
+
// It's allowed to also delete drafts when actives are deleted
|
|
64
|
+
if (
|
|
65
|
+
cds.env.fiori?.lean_draft &&
|
|
66
|
+
req.event === 'READ' &&
|
|
67
|
+
req.context.event === 'DELETE' &&
|
|
68
|
+
req.target?.name.endsWith('.drafts') &&
|
|
69
|
+
!req.context?.target?.name.endsWith('.drafts')
|
|
70
|
+
)
|
|
71
|
+
return
|
|
64
72
|
const _req = isReadAfterDraftAction ? req.context : req
|
|
65
|
-
return _req
|
|
73
|
+
return _req._isOData && _req.isConcurrentResource
|
|
66
74
|
}
|
|
67
75
|
|
|
68
76
|
/**
|
|
@@ -86,7 +86,7 @@ const _preProcessAssertTarget = (assocInfo, assertMap) => {
|
|
|
86
86
|
if (parentKeys.length === 0) return
|
|
87
87
|
|
|
88
88
|
foreignKeys.forEach(keyMap => {
|
|
89
|
-
const clonedAssocInfo = Object.assign({}, assocInfo, {
|
|
89
|
+
const clonedAssocInfo = Object.assign({}, assocInfo, { pathSegmentsInfo: assocInfo.pathSegmentsInfo.slice(0) })
|
|
90
90
|
const target = {
|
|
91
91
|
key: mapKey,
|
|
92
92
|
entity: assocTarget,
|
|
@@ -126,6 +126,8 @@ const _processCategory = (req, category, value, elementInfo, assertMap) => {
|
|
|
126
126
|
// > preserve computed values if triggered by draftActivate and not managed
|
|
127
127
|
return
|
|
128
128
|
}
|
|
129
|
+
// Always take over the values from active entities
|
|
130
|
+
if (cds.env.fiori?.lean_draft && req.context?.event === 'EDIT') return
|
|
129
131
|
|
|
130
132
|
delete row[key]
|
|
131
133
|
value.val = undefined
|
|
@@ -152,7 +154,7 @@ const _getProcessorFn = (req, errors, assertMap) => {
|
|
|
152
154
|
const event = req.event
|
|
153
155
|
|
|
154
156
|
return elementInfo => {
|
|
155
|
-
const { row, key, element, plain,
|
|
157
|
+
const { row, key, element, plain, pathSegmentsInfo } = elementInfo
|
|
156
158
|
// ugly pointer passing for sonar
|
|
157
159
|
const value = { mandatory: false, val: row && row[key] }
|
|
158
160
|
|
|
@@ -163,7 +165,7 @@ const _getProcessorFn = (req, errors, assertMap) => {
|
|
|
163
165
|
if (_shouldSuppressErrorPropagation(event, value)) return
|
|
164
166
|
|
|
165
167
|
// REVISIT: Convert checkInputConstraints to template mechanism
|
|
166
|
-
checkInputConstraints({ element, value: value.val, errors,
|
|
168
|
+
checkInputConstraints({ element, value: value.val, errors, pathSegmentsInfo, event })
|
|
167
169
|
}
|
|
168
170
|
}
|
|
169
171
|
|
|
@@ -239,7 +241,7 @@ async function commonGenericInput(req) {
|
|
|
239
241
|
const pathOptions = {
|
|
240
242
|
rowUUIDGenerator: getRowUUIDGeneratorFn(req.event),
|
|
241
243
|
includeKeyValues: true,
|
|
242
|
-
|
|
244
|
+
pathSegmentsInfo: []
|
|
243
245
|
}
|
|
244
246
|
|
|
245
247
|
const boundAction = _getBoundAction(req)
|
|
@@ -247,7 +249,7 @@ async function commonGenericInput(req) {
|
|
|
247
249
|
if (boundAction) {
|
|
248
250
|
const pathSegment = _getBoundActionBindingParameter(boundAction)
|
|
249
251
|
const keys = req._ && req._.params && req._.params[0]
|
|
250
|
-
if (pathSegment) pathOptions.
|
|
252
|
+
if (pathSegment) pathOptions.pathSegmentsInfo.push(pathSegment)
|
|
251
253
|
|
|
252
254
|
if (keys && 'IsActiveEntity' in keys) {
|
|
253
255
|
pathOptions.draftKeys = { IsActiveEntity: keys.IsActiveEntity }
|
|
@@ -364,7 +366,7 @@ commonGenericInput._initial = true
|
|
|
364
366
|
_actionFunctionHandler._initial = true
|
|
365
367
|
|
|
366
368
|
module.exports = cds.service.impl(function () {
|
|
367
|
-
if (cds.env.
|
|
369
|
+
if (cds.env.fiori.lean_draft) {
|
|
368
370
|
this.before(['CREATE', 'UPDATE'], '*', commonGenericInput)
|
|
369
371
|
} else {
|
|
370
372
|
this.before(['CREATE', 'UPDATE', 'NEW', 'PATCH'], '*', commonGenericInput)
|
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
const cds = require('../../cds')
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
module.exports = exports = cds.service.impl(function () {
|
|
4
|
+
commonGenericPaging._initial = true
|
|
5
|
+
this.before('READ', '*', commonGenericPaging)
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
const DEFAULT = cds.env.query?.limit?.default || 1000
|
|
9
|
+
const MAX = cds.env.query?.limit?.max || 1000
|
|
10
|
+
const _cached = Symbol('@cds.query.limit')
|
|
11
|
+
|
|
12
|
+
const getPageSize = def => {
|
|
13
|
+
if (_cached in def) return def[_cached]
|
|
14
|
+
let max = def['@cds.query.limit.max'] ?? def._service?.['@cds.query.limit.max'] ?? MAX
|
|
15
|
+
let _default =
|
|
16
|
+
def['@cds.query.limit.default'] ??
|
|
17
|
+
def['@cds.query.limit'] ??
|
|
18
|
+
def._service?.['@cds.query.limit.default'] ??
|
|
19
|
+
def._service?.['@cds.query.limit'] ??
|
|
20
|
+
DEFAULT
|
|
21
|
+
if (!max) max = Number.MAX_SAFE_INTEGER
|
|
22
|
+
if (!_default || _default > max) _default = max
|
|
23
|
+
return (def[_cached] = { default: _default, max })
|
|
24
|
+
}
|
|
3
25
|
|
|
4
26
|
const commonGenericPaging = function (req) {
|
|
5
27
|
// only if http request
|
|
@@ -21,10 +43,5 @@ const _addPaging = function ({ SELECT }, target) {
|
|
|
21
43
|
if (SELECT.from.SELECT?.limit) _addPaging(SELECT.from, target)
|
|
22
44
|
}
|
|
23
45
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
*/
|
|
27
|
-
module.exports = cds.service.impl(function () {
|
|
28
|
-
commonGenericPaging._initial = true
|
|
29
|
-
this.before('READ', '*', commonGenericPaging)
|
|
30
|
-
})
|
|
46
|
+
exports.getPageSize = getPageSize
|
|
47
|
+
exports.commonGenericPaging = commonGenericPaging
|
|
@@ -24,7 +24,7 @@ const _fillStructure = (row, parts, element, category, args) => {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
const _getProcessorFn = req => {
|
|
27
|
-
const REST = req.
|
|
27
|
+
const REST = req._isRest
|
|
28
28
|
|
|
29
29
|
return ({ row, key, element, plain }) => {
|
|
30
30
|
if (!row || row[key] !== undefined) return
|
|
@@ -86,6 +86,7 @@ CRUD_VIA_NAVIGATION_NOT_SUPPORTED=CRUD via navigations is not yet supported
|
|
|
86
86
|
# draft
|
|
87
87
|
DRAFT_ALREADY_EXISTS=A draft for this entity already exists
|
|
88
88
|
DRAFT_LOCKED_BY_ANOTHER_USER=The entity is locked by another user
|
|
89
|
+
DRAFT_MODIFICATION_ONLY_VIA_ROOT=A draft can only be modified via its root entity
|
|
89
90
|
|
|
90
91
|
# singleton
|
|
91
92
|
SINGLETON_NOT_NULLABLE=The singleton entity is not nullable
|
|
@@ -17,7 +17,11 @@ const getEntityNameFromDeleteCQN = cqn => {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
const getEntityNameFromUpdateCQN = cqn => {
|
|
20
|
-
return (
|
|
20
|
+
return (
|
|
21
|
+
(cqn.UPDATE.entity.ref && cqn.UPDATE.entity.ref[0] && (cqn.UPDATE.entity.ref[0].id || cqn.UPDATE.entity.ref[0])) ||
|
|
22
|
+
cqn.UPDATE.entity.name ||
|
|
23
|
+
cqn.UPDATE.entity
|
|
24
|
+
)
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
// scope: simple wheres à la "[{ ref: ['foo'] }, '=', { val: 'bar' }, 'and', ... ]"
|
|
@@ -280,7 +280,7 @@ const _createWindowCQN = (SELECT, model) => {
|
|
|
280
280
|
}
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
-
|
|
283
|
+
SELECT.groupBy = undefined
|
|
284
284
|
}
|
|
285
285
|
|
|
286
286
|
const _unshiftRefsWithNavigation = nav => el => {
|
|
@@ -713,7 +713,7 @@ const _convertToOneEqNullInFilter = (query, target) => {
|
|
|
713
713
|
}
|
|
714
714
|
// eslint-disable-next-line complexity
|
|
715
715
|
const _convertSelect = (query, model, _options) => {
|
|
716
|
-
const _4db = _options.service
|
|
716
|
+
const _4db = _options.service?.isDatabaseService
|
|
717
717
|
const options = Object.assign({ _4db, isStreaming: query._streaming }, _options)
|
|
718
718
|
|
|
719
719
|
// ensure query is ql enabled
|
|
@@ -796,7 +796,7 @@ const _convertSelect = (query, model, _options) => {
|
|
|
796
796
|
if (options._4db && !query.SELECT.columns) {
|
|
797
797
|
let target = query._target
|
|
798
798
|
if (target && target._unresolved && typeof target.name === 'string') {
|
|
799
|
-
target = model.definitions[ensureNoDraftsSuffix(target.name)] || target
|
|
799
|
+
target = model.definitions[cds.env.fiori.lean_draft ? target.name : ensureNoDraftsSuffix(target.name)] || target
|
|
800
800
|
}
|
|
801
801
|
|
|
802
802
|
if (target && !Object.prototype.hasOwnProperty.call(target, '_unresolved')) {
|
|
@@ -5,7 +5,7 @@ const PERSISTENCE_TABLE = '@cds.persistence.table'
|
|
|
5
5
|
const { rewriteAsterisks } = require('../../common/utils/rewriteAsterisks')
|
|
6
6
|
|
|
7
7
|
const getError = require('../error')
|
|
8
|
-
const { getEntityNameFromDeleteCQN
|
|
8
|
+
const { getEntityNameFromDeleteCQN } = require('../utils/cqn')
|
|
9
9
|
|
|
10
10
|
const _setInverseTransition = (mapping, ref, mapped) => {
|
|
11
11
|
const existing = mapping.get(ref)
|
|
@@ -332,7 +332,7 @@ const _newUpdate = (query, transitions, service) => {
|
|
|
332
332
|
newUpdate.where = _newWhere(
|
|
333
333
|
newUpdate.where,
|
|
334
334
|
targetTransition,
|
|
335
|
-
|
|
335
|
+
cds.infer(query, service.model.definitions).name,
|
|
336
336
|
query.UPDATE.entity.as
|
|
337
337
|
)
|
|
338
338
|
}
|
|
@@ -353,7 +353,7 @@ const _newSelect = (query, transitions, service) => {
|
|
|
353
353
|
if (!newSelect.columns && targetTransition.mapping.size) newSelect.columns = _initialColumns(targetTransition)
|
|
354
354
|
if (newSelect.columns) {
|
|
355
355
|
rewriteAsterisks({ SELECT: query.SELECT }, service.model, {
|
|
356
|
-
_4db: service
|
|
356
|
+
_4db: service.isDatabaseService,
|
|
357
357
|
target: targetTransition.queryTarget
|
|
358
358
|
})
|
|
359
359
|
newSelect.columns = _newColumns(newSelect.columns, targetTransition, service, service.kind !== 'app-service')
|
|
@@ -380,12 +380,16 @@ const _newInsert = (query, transitions, service) => {
|
|
|
380
380
|
const targetTransition = transitions[transitions.length - 1]
|
|
381
381
|
const targetName = targetTransition.target.name
|
|
382
382
|
const newInsert = Object.create(query.INSERT)
|
|
383
|
-
newInsert.into
|
|
384
|
-
? {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
383
|
+
if (newInsert.into) {
|
|
384
|
+
const refObject = newInsert.into.ref ? newInsert.into : { ref: [query.INSERT.into] }
|
|
385
|
+
newInsert.into = {
|
|
386
|
+
...refObject,
|
|
387
|
+
ref: _rewriteQueryPath(refObject, transitions)
|
|
388
|
+
}
|
|
389
|
+
if (!query.INSERT.into.ref) newInsert.into = newInsert.into.ref[0] // leave as string
|
|
390
|
+
} else {
|
|
391
|
+
newInsert.into = targetName
|
|
392
|
+
}
|
|
389
393
|
if (newInsert.columns) newInsert.columns = _newInsertColumns(newInsert.columns, targetTransition)
|
|
390
394
|
if (newInsert.entries) newInsert.entries = _newEntries(newInsert.entries, targetTransition, service)
|
|
391
395
|
Object.defineProperty(newInsert, '_transitions', {
|
|
@@ -536,7 +540,7 @@ const _getTransitionData = (target, columns, service, skipForbiddenViewCheck) =>
|
|
|
536
540
|
if (!skipForbiddenViewCheck) _checkForForbiddenViews(target)
|
|
537
541
|
const targetStartsWithSrvName = service.namespace && target.name.startsWith(`${service.namespace}.`)
|
|
538
542
|
const persistenceTable = _isPersistenceTable(target)
|
|
539
|
-
const isDatabaseService = service
|
|
543
|
+
const isDatabaseService = service.isDatabaseService
|
|
540
544
|
columns = _queryColumns(target, columns, persistenceTable, !isDatabaseService && !targetStartsWithSrvName)
|
|
541
545
|
// REVISIT: Change once we expose database service
|
|
542
546
|
if (persistenceTable && isDatabaseService) {
|
|
@@ -29,7 +29,7 @@ const _expandColumn = (column, target, _4db) => {
|
|
|
29
29
|
const rewriteExpandAsterisk = (columns, target) => {
|
|
30
30
|
const expandAllColIdx = columns.findIndex(col => {
|
|
31
31
|
if (col.ref || !col.expand) return
|
|
32
|
-
return
|
|
32
|
+
return col.expand.includes('*')
|
|
33
33
|
})
|
|
34
34
|
if (expandAllColIdx > -1) {
|
|
35
35
|
const { expand } = columns.splice(expandAllColIdx, 1)[0]
|
|
@@ -56,7 +56,6 @@ const _rewriteAsterisk = (columns, target, _4db, isRoot) => {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
const _rewriteAsterisks = (cqn, target, _4db, isRoot) => {
|
|
59
|
-
if (cqn.expand === '*') cqn.expand = ['*']
|
|
60
59
|
const columns = cqn.expand || cqn.columns
|
|
61
60
|
_rewriteAsterisk(columns, target, _4db, isRoot)
|
|
62
61
|
rewriteExpandAsterisk(columns, target)
|
|
@@ -119,7 +118,7 @@ const rewriteAsterisks = (query, model, options) => {
|
|
|
119
118
|
if (!target) return
|
|
120
119
|
|
|
121
120
|
query.SELECT.columns = getColumns(target, { _4db }).map(col => ({ ref: [col.name] }))
|
|
122
|
-
if (_4db && target._isDraftEnabled && !cds.env.
|
|
121
|
+
if (_4db && target._isDraftEnabled && !cds.env.fiori.lean_draft)
|
|
123
122
|
query.SELECT.columns.push(..._cqlDraftColumns(target))
|
|
124
123
|
}
|
|
125
124
|
}
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
const DELIMITER = require('./templateDelimiter')
|
|
2
|
-
const pathSerializer = require('./templateProcessorPathSerializer')
|
|
3
2
|
|
|
4
|
-
const _processElement = (processFn, row, key, target, picked = {}, isRoot,
|
|
3
|
+
const _processElement = (processFn, row, key, target, picked = {}, isRoot, pathSegmentsInfo) => {
|
|
5
4
|
const element = (target.elements || target.params)[key]
|
|
6
5
|
const { plain } = picked
|
|
7
6
|
|
|
8
7
|
if (!plain) return
|
|
9
8
|
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
elementInfo.
|
|
16
|
-
elementInfo.pathSegments.push(...target._flat2struct[key])
|
|
9
|
+
/** @type import('../../types/api').templateElementInfo */
|
|
10
|
+
const elementInfo = { row, key, element, target, plain, isRoot, pathSegmentsInfo }
|
|
11
|
+
|
|
12
|
+
if (!element && target._flat2struct?.[key] && elementInfo.pathSegmentsInfo) {
|
|
13
|
+
elementInfo.pathSegmentsInfo = pathSegmentsInfo.slice(0)
|
|
14
|
+
elementInfo.pathSegmentsInfo.push(...target._flat2struct[key])
|
|
17
15
|
}
|
|
18
16
|
|
|
19
17
|
processFn(elementInfo)
|
|
@@ -23,7 +21,7 @@ const _processRow = (processFn, row, template, tKey, tValue, isRoot, pathOptions
|
|
|
23
21
|
const { template: subTemplate, picked } = tValue
|
|
24
22
|
const key = tKey.split(DELIMITER).pop()
|
|
25
23
|
|
|
26
|
-
_processElement(processFn, row, key, template.target, picked, isRoot, pathOptions.
|
|
24
|
+
_processElement(processFn, row, key, template.target, picked, isRoot, pathOptions.pathSegmentsInfo)
|
|
27
25
|
|
|
28
26
|
// process deep
|
|
29
27
|
if (subTemplate && typeof row === 'object' && row) {
|
|
@@ -48,20 +46,20 @@ const _processComplex = (processFn, row, template, key, pathOptions) => {
|
|
|
48
46
|
if (rows.length === 0) return
|
|
49
47
|
const keyNames = pathOptions.includeKeyValues && _getTargetKeyNames(template.target)
|
|
50
48
|
|
|
51
|
-
for (
|
|
52
|
-
const row = rows[idx]
|
|
49
|
+
for (const row of rows) {
|
|
53
50
|
if (row == null) continue
|
|
54
51
|
const args = { processFn, row, template, isRoot: false, pathOptions }
|
|
55
52
|
|
|
56
|
-
let
|
|
53
|
+
let pathSegmentInfo
|
|
57
54
|
if (pathOptions.includeKeyValues) {
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
pathOptions.rowUUIDGenerator?.(keyNames, row, template)
|
|
56
|
+
/** @type import('../../types/api').pathSegmentInfo */
|
|
57
|
+
pathSegmentInfo = { key, keyNames, row, elements: template.target.elements, draftKeys: pathOptions.draftKeys }
|
|
60
58
|
}
|
|
61
59
|
|
|
62
|
-
if (pathOptions.
|
|
60
|
+
if (pathOptions.pathSegmentsInfo) pathOptions.pathSegmentsInfo.push(pathSegmentInfo || key)
|
|
63
61
|
templateProcessor(args)
|
|
64
|
-
if (pathOptions.
|
|
62
|
+
if (pathOptions.pathSegmentsInfo) pathOptions.pathSegmentsInfo.pop()
|
|
65
63
|
}
|
|
66
64
|
}
|
|
67
65
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
const cds = require('../../cds')
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
const segmentSerializer = pathSegmentInfo => {
|
|
4
|
+
const { key: tKey, row, elements, draftKeys } = pathSegmentInfo
|
|
5
|
+
let keyNames = pathSegmentInfo.keyNames
|
|
6
|
+
|
|
3
7
|
const keyValuePairs = keyNames
|
|
4
|
-
.filter(key => {
|
|
5
|
-
if (cds.env.features.lean_draft && key === 'IsActiveEntity') return false
|
|
6
|
-
return true
|
|
7
|
-
})
|
|
8
8
|
.map(key => {
|
|
9
9
|
let quote
|
|
10
10
|
|
|
@@ -19,11 +19,23 @@ const templatePathSerializer = (tKey, keyNames, row, elements, draftKeys) => {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
const keyValue = row[key] ?? draftKeys?.[key]
|
|
22
|
+
if (keyValue == null) return
|
|
22
23
|
return `${key}=${quote}${keyValue}${quote}`
|
|
23
24
|
})
|
|
25
|
+
.filter(c => c)
|
|
24
26
|
|
|
25
27
|
const keyValuePairsSerialized = keyValuePairs.join(',')
|
|
26
|
-
|
|
28
|
+
const pathSegment = `${tKey}(${keyValuePairsSerialized})`
|
|
29
|
+
return pathSegment
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const templatePathSerializer = (elementName, pathSegmentsInfo) => {
|
|
33
|
+
const pathSegments = pathSegmentsInfo.map(pathSegmentInfo => {
|
|
34
|
+
if (typeof pathSegmentInfo === 'string') return pathSegmentInfo
|
|
35
|
+
return segmentSerializer(pathSegmentInfo)
|
|
36
|
+
})
|
|
37
|
+
const path = `${pathSegments.join('/')}${pathSegments.length ? '/' : ''}${elementName}`
|
|
38
|
+
return path
|
|
27
39
|
}
|
|
28
40
|
|
|
29
41
|
module.exports = templatePathSerializer
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
const { getEntityNameFromCQN, traverseFroms } = require('../../common/utils/entityFromCqn')
|
|
2
2
|
const { ensureNoDraftsSuffix } = require('../../common/utils/draft')
|
|
3
|
-
const { proxifyIfFlattened } = require('../../../common/utils/ucsn')
|
|
4
|
-
const cds = require('../../../../lib')
|
|
5
3
|
|
|
6
4
|
const _getCastFunction = ({ type }) => {
|
|
7
5
|
switch (type) {
|
|
@@ -34,13 +32,6 @@ const _getNestedElement = (entity, key) => {
|
|
|
34
32
|
return _structElement(entity, structPath)
|
|
35
33
|
}
|
|
36
34
|
|
|
37
|
-
const _structure = csnEntity => (value, key, row, unaliasedKey) => {
|
|
38
|
-
proxifyIfFlattened(csnEntity, row)
|
|
39
|
-
const effectiveKey = unaliasedKey || key
|
|
40
|
-
delete row[effectiveKey]
|
|
41
|
-
row[effectiveKey] = value
|
|
42
|
-
}
|
|
43
|
-
|
|
44
35
|
const _addConverter = (mapper, name, converter) => {
|
|
45
36
|
if (mapper.has(name)) {
|
|
46
37
|
const oldConverter = mapper.get(name)
|
|
@@ -126,15 +117,6 @@ const _getMapperForListedElements = (conversionMap, csn, cqn) => {
|
|
|
126
117
|
row[effectiveKey] = JSON.parse(val)
|
|
127
118
|
}) // > arrayed elements
|
|
128
119
|
}
|
|
129
|
-
|
|
130
|
-
if (
|
|
131
|
-
cds.env.features.ucsn_struct_conversion &&
|
|
132
|
-
element.parent &&
|
|
133
|
-
element.parent._isStructured &&
|
|
134
|
-
!element._isStructured
|
|
135
|
-
) {
|
|
136
|
-
_addConverter(mapper, col.as ? col.as : name, _structure(entity))
|
|
137
|
-
}
|
|
138
120
|
}
|
|
139
121
|
}
|
|
140
122
|
|
|
@@ -119,7 +119,7 @@ const _addForeignKeys = (columns, entity, options) => {
|
|
|
119
119
|
* @returns object
|
|
120
120
|
*/
|
|
121
121
|
const expandV2 = async (model, dbc, query, user, locale, txTimestamp, executeSelectCQN) => {
|
|
122
|
-
const expandColumn = query.SELECT.columns.find(c => c.expand && typeof c.expand === 'string')
|
|
122
|
+
const expandColumn = query.SELECT.columns.find(c => c.expand && typeof c.expand[0] === 'string')
|
|
123
123
|
const options = Object.assign({ onlyKeys: false, onlyCompositions: false }, expandColumn._options)
|
|
124
124
|
// remove expand columns from query without modifying
|
|
125
125
|
const topLevelSelect = query.clone().columns(query.SELECT.columns.filter(c => !c.expand))
|
|
@@ -137,7 +137,7 @@ const expandV2 = async (model, dbc, query, user, locale, txTimestamp, executeSel
|
|
|
137
137
|
|
|
138
138
|
// _associations contains compositions and associations
|
|
139
139
|
if (entity._associations) {
|
|
140
|
-
const depth = expandColumn.expand === '**' ? -1 : Number(expandColumn.expand.replace('*', ''))
|
|
140
|
+
const depth = expandColumn.expand[0] === '**' ? -1 : Number(expandColumn.expand[0].replace('*', ''))
|
|
141
141
|
await _autoExpandNavsAndAttachToResult(entity, Array.isArray(result) ? result : [result], depth, options)
|
|
142
142
|
}
|
|
143
143
|
|