@sap/cds 6.6.2 → 6.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +59 -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 +20 -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 +22 -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/cds-services/adapter/odata-v4/ODataRequest.js +2 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +11 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +3 -3
- 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 +1 -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/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 +2 -2
- 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 +138 -46
- package/libx/_runtime/hana/execute.js +3 -1
- package/libx/_runtime/hana/pool.js +10 -2
- 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 +36 -0
- package/libx/odata/cqn2odata.js +1 -1
- 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,6 +1,7 @@
|
|
|
1
1
|
const cds = require('../cds'),
|
|
2
2
|
{ Object_keys } = cds.utils
|
|
3
3
|
const LOG = cds.log('fiori|drafts')
|
|
4
|
+
const original = Symbol('original')
|
|
4
5
|
|
|
5
6
|
const DRAFT_ELEMENTS = new Set([
|
|
6
7
|
'IsActiveEntity',
|
|
@@ -69,12 +70,14 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
69
70
|
|
|
70
71
|
if (
|
|
71
72
|
!req.query ||
|
|
73
|
+
req.query.UPSERT || // skip UPSERTs (might have an additional INSERT)
|
|
72
74
|
(!req.query.SELECT && !req.query.INSERT && !req.query.UPDATE && !req.query.DELETE) ||
|
|
73
75
|
req.query._draftParams
|
|
74
76
|
)
|
|
75
77
|
return handle(req)
|
|
76
78
|
const query = _cleansed(req.query, this.model)
|
|
77
|
-
_cleanseParams(req.params)
|
|
79
|
+
_cleanseParams(req.params, req.target)
|
|
80
|
+
if (req.data) _cleanseParams(req.data, req.target)
|
|
78
81
|
const draftParams = query._draftParams
|
|
79
82
|
|
|
80
83
|
const _newReq = (req, query, draftParams, event) => {
|
|
@@ -82,8 +85,11 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
82
85
|
query._target = undefined
|
|
83
86
|
query._draftParams = draftParams
|
|
84
87
|
cds.infer(query, this.model.definitions)
|
|
88
|
+
|
|
89
|
+
// REVISIT: This is extremely bad. We should be able to just create a copy without such hacks.
|
|
85
90
|
const _req = cds.Request.for(req._) // REVISIT: this causes req._.data of WRITE reqs copied to READ reqs
|
|
86
|
-
|
|
91
|
+
// If we create a `READ` event based on a modifying request, we delete data
|
|
92
|
+
if (event === 'READ' && req.event !== 'READ') delete _req.data // which we fix here -> but this is an ugly workaround
|
|
87
93
|
_req.query = query
|
|
88
94
|
_req.event =
|
|
89
95
|
event ||
|
|
@@ -93,19 +99,28 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
93
99
|
(query.DELETE && 'DELETE') ||
|
|
94
100
|
req.event
|
|
95
101
|
_req.target = query._target
|
|
102
|
+
_req._ = Object.assign({}, req._ || {}) // don't share the same `_` object
|
|
96
103
|
_req._.params = req.params
|
|
97
104
|
_req.params = req.params
|
|
98
105
|
_req._.query = query
|
|
99
106
|
_req._ = req._
|
|
100
|
-
_req.
|
|
107
|
+
_req._isRest = req._isRest
|
|
108
|
+
_req._isOData = req._isOData
|
|
109
|
+
_req.isConcurrentResource = req.isConcurrentResource
|
|
110
|
+
_req.isConditional = req.isConditional
|
|
111
|
+
_req.validateEtag = req.validateEtag
|
|
112
|
+
const cqnData = _req.query.UPDATE?.data || _req.query.INSERT?.entries?.[0]
|
|
113
|
+
if (cqnData) _req.data = cqnData // must point to the same object
|
|
101
114
|
|
|
102
115
|
// Dirty hack: delegate messages to original request by binding the getter _messages to req
|
|
103
116
|
let proto = req
|
|
104
117
|
let _messagesDescr
|
|
105
118
|
while (proto && !(_messagesDescr = Object.getOwnPropertyDescriptor(proto, '_messages')))
|
|
106
119
|
proto = Object.getPrototypeOf(proto)
|
|
107
|
-
|
|
120
|
+
if (_messagesDescr)
|
|
121
|
+
Object.defineProperty(_req, '_messages', { ..._messagesDescr, get: _messagesDescr.get.bind(req) })
|
|
108
122
|
|
|
123
|
+
if (req.tx) _req.tx = req.tx
|
|
109
124
|
return _req
|
|
110
125
|
}
|
|
111
126
|
|
|
@@ -196,7 +211,7 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
196
211
|
const result = await run(
|
|
197
212
|
HasActiveEntity ? UPDATE(req.target).data(res).where(targetWhere) : INSERT.into(req.target).entries(res)
|
|
198
213
|
)
|
|
199
|
-
req?._?.odataRes
|
|
214
|
+
req?._?.odataRes?.setStatusCode(201)
|
|
200
215
|
|
|
201
216
|
return Object.assign(result, { IsActiveEntity: true })
|
|
202
217
|
|
|
@@ -252,11 +267,20 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
252
267
|
const updateData = { ...req.data }
|
|
253
268
|
delete updateData.IsActiveEntity
|
|
254
269
|
await run(UPDATE({ ref: draftsRef }).data(updateData))
|
|
255
|
-
|
|
270
|
+
req.data.IsActiveEntity = false
|
|
271
|
+
return req.data
|
|
256
272
|
}
|
|
257
273
|
}
|
|
258
274
|
|
|
259
275
|
if (req.event === 'READ') {
|
|
276
|
+
if (
|
|
277
|
+
!Object.keys(draftParams).length &&
|
|
278
|
+
!req.query._target.name?.endsWith('DraftAdministrativeData') &&
|
|
279
|
+
!req.query._target.drafts
|
|
280
|
+
) {
|
|
281
|
+
req.query = query
|
|
282
|
+
return handle(req)
|
|
283
|
+
}
|
|
260
284
|
const read = req.query._target.name.endsWith('.drafts')
|
|
261
285
|
? Read.ownDrafts
|
|
262
286
|
: draftParams.IsActiveEntity === false && draftParams.SiblingEntity_IsActiveEntity === null
|
|
@@ -281,8 +305,34 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
281
305
|
return result
|
|
282
306
|
}
|
|
283
307
|
|
|
284
|
-
|
|
285
|
-
const result = await handle(
|
|
308
|
+
req.query = query
|
|
309
|
+
const result = await handle(req)
|
|
310
|
+
return result
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// REVISIT: It's not optimal to first calculate the whole result array and only later
|
|
314
|
+
// delete unrequested properties. However, as a first step, we do it that way,
|
|
315
|
+
// especially since the current db driver always adds those fields.
|
|
316
|
+
// Once we switch to the new driver, we'll adapt it.
|
|
317
|
+
const _requested = (result, query) => {
|
|
318
|
+
const originalQuery = query[original]
|
|
319
|
+
if (!result || !originalQuery) return result
|
|
320
|
+
const all = ['HasActiveEntity', 'HasDraftEntity']
|
|
321
|
+
|
|
322
|
+
const ignoredCols = new Set(all.concat('DraftAdministrativeData'))
|
|
323
|
+
const _isODataV2 = cds.context?.http?.req?.headers?.['x-cds-odata-version'] === 'v2'
|
|
324
|
+
if (!_isODataV2) ignoredCols.add('DraftAdministrativeData_DraftUUID')
|
|
325
|
+
for (const col of originalQuery.SELECT.columns || ['*']) {
|
|
326
|
+
const name = col.as || col.ref?.[0] || col
|
|
327
|
+
if (all.includes(name) || name === 'DraftAdministrativeData' || name === 'DraftAdministrativeData_DraftUUID')
|
|
328
|
+
ignoredCols.delete(name)
|
|
329
|
+
if (name === '*') all.forEach(c => ignoredCols.delete(c))
|
|
330
|
+
}
|
|
331
|
+
if (!ignoredCols.size) return result
|
|
332
|
+
const resArray = Array.isArray(result) ? result : [result]
|
|
333
|
+
for (const row of resArray) {
|
|
334
|
+
for (const ignoredCol of ignoredCols) delete row[ignoredCol]
|
|
335
|
+
}
|
|
286
336
|
return result
|
|
287
337
|
}
|
|
288
338
|
|
|
@@ -291,6 +341,13 @@ const Read = {
|
|
|
291
341
|
LOG.debug('List Editing Status: Only Active')
|
|
292
342
|
// DraftAdministrativeData is only accessible via drafts
|
|
293
343
|
if (query._target.name.endsWith('.DraftAdministrativeData')) return run(query._drafts)
|
|
344
|
+
if (!query._target._isDraftEnabled) return run(query)
|
|
345
|
+
if (query.SELECT.columns && !query.SELECT.columns.some(c => c === '*')) {
|
|
346
|
+
const keys = Object_keys(query._target.keys).filter(k => k !== 'IsActiveEntity')
|
|
347
|
+
for (const key of keys) {
|
|
348
|
+
if (!query.SELECT.columns.some(c => c.ref?.[0] === key)) query.SELECT.columns.push({ ref: [key] })
|
|
349
|
+
}
|
|
350
|
+
}
|
|
294
351
|
const actives = await run(query)
|
|
295
352
|
if (!actives || (Array.isArray(actives) && !actives.length) || !query._target.drafts) return actives
|
|
296
353
|
let drafts
|
|
@@ -313,7 +370,7 @@ const Read = {
|
|
|
313
370
|
DraftAdministrativeData_DraftUUID: null
|
|
314
371
|
})
|
|
315
372
|
)
|
|
316
|
-
return actives
|
|
373
|
+
return _requested(actives, query)
|
|
317
374
|
},
|
|
318
375
|
unchanged: async function (run, query) {
|
|
319
376
|
LOG.debug('List Editing Status: Unchanged')
|
|
@@ -328,7 +385,7 @@ const Read = {
|
|
|
328
385
|
const res = Read.onlyActives(run, query.where(Read.whereNotIn(query._target, drafts)), {
|
|
329
386
|
ignoreDrafts: true
|
|
330
387
|
})
|
|
331
|
-
return res
|
|
388
|
+
return _requested(res, query)
|
|
332
389
|
},
|
|
333
390
|
ownDrafts: async function (run, query) {
|
|
334
391
|
LOG.debug('List Editing Status: Own Draft')
|
|
@@ -356,11 +413,11 @@ const Read = {
|
|
|
356
413
|
const drafts = await run(draftsQuery)
|
|
357
414
|
Read.merge(query._target, drafts, [], row =>
|
|
358
415
|
Object.assign(row, {
|
|
359
|
-
|
|
360
|
-
|
|
416
|
+
HasDraftEntity: false,
|
|
417
|
+
IsActiveEntity: false
|
|
361
418
|
})
|
|
362
419
|
)
|
|
363
|
-
return drafts
|
|
420
|
+
return _requested(drafts, query)
|
|
364
421
|
},
|
|
365
422
|
all: async function (run, query) {
|
|
366
423
|
LOG.debug('List Editing Status: All')
|
|
@@ -391,6 +448,13 @@ const Read = {
|
|
|
391
448
|
else ownNewDrafts.push(draft)
|
|
392
449
|
}
|
|
393
450
|
|
|
451
|
+
// We can't properly calculate `count`:
|
|
452
|
+
// - Not all actives are retrieved (e.g. top = 0), hence there could be more deletes if more actives are requested,
|
|
453
|
+
// hence we cannot count deletions based on data.
|
|
454
|
+
// - We can't rely on the fact that `HasActiveEntity` always has an active counterpart because the filter
|
|
455
|
+
// is applied on draft and active data respectively (you could fetch a draft but not an active instance).
|
|
456
|
+
// However, there's not much we can do, so we use use this as a best guess.
|
|
457
|
+
|
|
394
458
|
const count = isFirstPage ? ownNewDrafts.length + (isCount ? actives[0]?.$count : actives.$count) : actives.$count
|
|
395
459
|
if (isCount) return { $count: count }
|
|
396
460
|
|
|
@@ -423,7 +487,7 @@ const Read = {
|
|
|
423
487
|
})
|
|
424
488
|
const res = isFirstPage ? [...ownNewDrafts, ...ownEditDrafts, ...actives] : actives
|
|
425
489
|
if (query.SELECT.count) res.$count = count
|
|
426
|
-
return res
|
|
490
|
+
return _requested(res, query)
|
|
427
491
|
},
|
|
428
492
|
activesFromDrafts: async function (run, query, { isLocked = true }) {
|
|
429
493
|
const draftsQuery = query._drafts
|
|
@@ -450,7 +514,7 @@ const Read = {
|
|
|
450
514
|
? Object.assign(row, other, { IsActiveEntity: true, HasDraftEntity: true, HasActiveEntity: false })
|
|
451
515
|
: Object.assign({ IsActiveEntity: true, HasDraftEntity: false, HasActiveEntity: false })
|
|
452
516
|
)
|
|
453
|
-
return actives
|
|
517
|
+
return _requested(actives, query)
|
|
454
518
|
},
|
|
455
519
|
unsavedChangesByAnotherUser: async function (run, query) {
|
|
456
520
|
LOG.debug('List Editing Status: Unsaved Changes by Another User')
|
|
@@ -529,29 +593,33 @@ const Read = {
|
|
|
529
593
|
}
|
|
530
594
|
}
|
|
531
595
|
|
|
532
|
-
function _cleanseParams(params) {
|
|
596
|
+
function _cleanseParams(params, target) {
|
|
597
|
+
if (!target?.drafts) return
|
|
533
598
|
if (Array.isArray(params)) {
|
|
534
|
-
for (const param of params) _cleanseParams(param)
|
|
599
|
+
for (const param of params) _cleanseParams(param, target)
|
|
535
600
|
return
|
|
536
601
|
}
|
|
537
602
|
if (typeof params === 'object') {
|
|
538
603
|
for (const key in params) {
|
|
539
|
-
if (key === 'IsActiveEntity')
|
|
604
|
+
if (key === 'IsActiveEntity') {
|
|
605
|
+
const value = params[key]
|
|
606
|
+
delete params[key]
|
|
607
|
+
if (cds.env.fiori?.draft_compat) Object.defineProperty(params, key, { value, enumerable: false })
|
|
608
|
+
}
|
|
540
609
|
}
|
|
541
610
|
}
|
|
542
611
|
}
|
|
543
612
|
|
|
544
|
-
function _cleanseCols(columns, elements) {
|
|
545
|
-
|
|
546
|
-
return
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
)
|
|
613
|
+
function _cleanseCols(columns, elements, target) {
|
|
614
|
+
// TODO: sometimes target is undefined
|
|
615
|
+
if (!target || typeof columns?.filter !== 'function') return columns
|
|
616
|
+
const filtered = target?.drafts ? columns.filter(c => !elements.has(c.ref?.[0])) : columns
|
|
617
|
+
return filtered.map(c => {
|
|
618
|
+
if (c.expand && c.ref) {
|
|
619
|
+
return { ...c, expand: _cleanseCols(c.expand, elements, target.elements[c.ref[0]]?._target) }
|
|
620
|
+
}
|
|
621
|
+
return c
|
|
622
|
+
})
|
|
555
623
|
}
|
|
556
624
|
|
|
557
625
|
/**
|
|
@@ -559,10 +627,10 @@ function _cleanseCols(columns, elements) {
|
|
|
559
627
|
*/
|
|
560
628
|
function _cleansed(query, model) {
|
|
561
629
|
const draftParams = {} //> used to collect draft filter criteria
|
|
562
|
-
const q = _cleanseQuery(query, draftParams)
|
|
630
|
+
const q = _cleanseQuery(query, draftParams, model)
|
|
563
631
|
if (query.SELECT) {
|
|
564
632
|
const getDrafts = () => {
|
|
565
|
-
const draftsQuery = _cleanseQuery(query, {}) // could just clone `q` but the latter is ruined by database layer
|
|
633
|
+
const draftsQuery = _cleanseQuery(query, {}, model) // could just clone `q` but the latter is ruined by database layer
|
|
566
634
|
draftsQuery._target = undefined
|
|
567
635
|
const [root, ...tail] = draftsQuery.SELECT.from.ref
|
|
568
636
|
const draft = model.definitions[root.id || root].drafts
|
|
@@ -572,7 +640,7 @@ function _cleansed(query, model) {
|
|
|
572
640
|
cds.infer(draftsQuery, model.definitions)
|
|
573
641
|
// draftsQuery._target = draftsQuery._target?.drafts || draftsQuery._target
|
|
574
642
|
if (query.SELECT.columns && query._target.drafts)
|
|
575
|
-
draftsQuery.SELECT.columns = _cleanseCols(query.SELECT.columns, REDUCED_DRAFT_ELEMENTS)
|
|
643
|
+
draftsQuery.SELECT.columns = _cleanseCols(query.SELECT.columns, REDUCED_DRAFT_ELEMENTS, draft)
|
|
576
644
|
|
|
577
645
|
if (draftsQuery._target.name.endsWith('.DraftAdministrativeData')) {
|
|
578
646
|
draftsQuery.SELECT.columns = _tweakAdminCols(draftsQuery.SELECT.columns)
|
|
@@ -592,21 +660,30 @@ function _cleansed(query, model) {
|
|
|
592
660
|
}
|
|
593
661
|
|
|
594
662
|
Object.defineProperty(q, '_draftParams', { value: draftParams, enumerable: false })
|
|
663
|
+
q[original] = query
|
|
595
664
|
return q
|
|
596
665
|
|
|
597
|
-
function _cleanseQuery(query, draftParams) {
|
|
666
|
+
function _cleanseQuery(query, draftParams, model) {
|
|
667
|
+
const target = query._target
|
|
598
668
|
const q = cds.ql.clone(query)
|
|
599
669
|
|
|
600
670
|
const ref = q.SELECT?.from.ref || q.UPDATE?.entity.ref || q.INSERT?.into.ref || q.DELETE?.from.ref
|
|
601
671
|
const cqn = q.SELECT || q.UPDATE || q.INSERT || q.DELETE
|
|
602
672
|
|
|
603
673
|
if (ref) {
|
|
604
|
-
|
|
674
|
+
let entity
|
|
675
|
+
const cleansedRef = ref.map(r => {
|
|
676
|
+
entity = (entity && entity.elements[r.id || r]._target) || model.definitions[r.id || r]
|
|
677
|
+
if (!entity?.drafts) return r
|
|
678
|
+
return r.where ? { ...r, where: _cleanseWhere(r.where, draftParams) } : r
|
|
679
|
+
})
|
|
605
680
|
if (q.SELECT) q.SELECT.from = { ...q.SELECT.from, ref: cleansedRef }
|
|
606
681
|
else if (q.DELETE) q.DELETE.from = { ...q.DELETE.from, ref: cleansedRef }
|
|
607
682
|
else if (q.UPDATE) q.UPDATE.entity = { ...q.UPDATE.entity, ref: cleansedRef }
|
|
608
683
|
else if (q.INSERT) q.INSERT.into = { ...q.INSERT.into, ref: cleansedRef }
|
|
609
684
|
|
|
685
|
+
// This only works for simple cases of `SiblingEntity`, e.g. `root(ID=1,IsActiveEntity=false)/SiblingEntity`
|
|
686
|
+
// , check if there are more complicated use cases
|
|
610
687
|
const siblingIdx = cleansedRef.findIndex(r => r === 'SiblingEntity')
|
|
611
688
|
if (siblingIdx !== -1) {
|
|
612
689
|
cleansedRef.splice(siblingIdx, 1)
|
|
@@ -614,10 +691,9 @@ function _cleansed(query, model) {
|
|
|
614
691
|
}
|
|
615
692
|
}
|
|
616
693
|
|
|
617
|
-
if (cqn.where) cqn.where = _cleanseWhere(cqn.where, draftParams)
|
|
618
|
-
if (cqn.
|
|
619
|
-
if (cqn.
|
|
620
|
-
|
|
694
|
+
if (target.drafts && cqn.where) cqn.where = _cleanseWhere(cqn.where, draftParams)
|
|
695
|
+
if (target.drafts && cqn.orderBy) cqn.orderBy = _cleanseWhere(cqn.orderBy, {})
|
|
696
|
+
if (cqn.columns) cqn.columns = _cleanseCols(cqn.columns, DRAFT_ELEMENTS, target)
|
|
621
697
|
return q
|
|
622
698
|
}
|
|
623
699
|
|
|
@@ -740,7 +816,10 @@ function expandStarStar(target, recursion = new Map()) {
|
|
|
740
816
|
|
|
741
817
|
async function onNew(req) {
|
|
742
818
|
LOG.debug('new draft')
|
|
743
|
-
const isRoot = typeof req.query.INSERT.into === 'string'
|
|
819
|
+
const isRoot = typeof req.query.INSERT.into === 'string' || req.query.INSERT.into.ref?.length === 1
|
|
820
|
+
// Only allowed for pseudo draft roots (entities with this action)
|
|
821
|
+
if (isRoot && !req.target.actives['@Common.DraftRoot.ActivationAction'])
|
|
822
|
+
req.reject(403, 'DRAFT_MODIFICATION_ONLY_VIA_ROOT')
|
|
744
823
|
let DraftUUID
|
|
745
824
|
if (isRoot) DraftUUID = cds.utils.uuid()
|
|
746
825
|
else {
|
|
@@ -777,10 +856,21 @@ async function onNew(req) {
|
|
|
777
856
|
})
|
|
778
857
|
.where({ DraftUUID })
|
|
779
858
|
|
|
780
|
-
const
|
|
781
|
-
{ DraftAdministrativeData_DraftUUID: DraftUUID, HasActiveEntity: false },
|
|
782
|
-
|
|
783
|
-
|
|
859
|
+
const _assignDraftData = (obj, target) => {
|
|
860
|
+
const newObj = Object.assign({ DraftAdministrativeData_DraftUUID: DraftUUID, HasActiveEntity: false }, obj)
|
|
861
|
+
if (!target) return newObj
|
|
862
|
+
|
|
863
|
+
// Also support deep insertions
|
|
864
|
+
for (const key in newObj) {
|
|
865
|
+
if (typeof newObj[key] === 'object' && target.elements[key]?.isComposition) {
|
|
866
|
+
newObj[key] = _assignDraftData(newObj[key], target.elements[key]._target)
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
return newObj
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
const draftData = _assignDraftData(req.query.INSERT.entries[0], req.target)
|
|
784
874
|
|
|
785
875
|
delete draftData.IsActiveEntity
|
|
786
876
|
const draftCQN = INSERT.into(req.target).entries(draftData)
|
|
@@ -794,7 +884,7 @@ async function onEdit(req) {
|
|
|
794
884
|
LOG.debug('edit active')
|
|
795
885
|
const draftParams = req.query._draftParams
|
|
796
886
|
if (req.query.SELECT.from.ref.length > 1 || draftParams.IsActiveEntity !== true) {
|
|
797
|
-
req.reject(400, 'Action "draftEdit" can only be called on the root entity')
|
|
887
|
+
req.reject(400, 'Action "draftEdit" can only be called on the root active entity')
|
|
798
888
|
}
|
|
799
889
|
const targetWhere = req.query.SELECT.from.ref[0].where
|
|
800
890
|
|
|
@@ -823,8 +913,10 @@ async function onEdit(req) {
|
|
|
823
913
|
// prevent service to check for own user
|
|
824
914
|
Object.defineProperty(draftsCheck, '_draftParams', { value: draftParams, enumerable: false })
|
|
825
915
|
|
|
916
|
+
const activeCQN = SELECT.one.from(req.target).columns(cols).where(targetWhere).forUpdate({ wait: 0 })
|
|
917
|
+
activeCQN._suppressLocalization = true // in the future we should be able to just set activeCQN.SELECT.localized = false
|
|
826
918
|
const [res, draft] = await Promise.all([
|
|
827
|
-
this.run(
|
|
919
|
+
this.run(activeCQN),
|
|
828
920
|
// no user check must be done here...
|
|
829
921
|
this.run(draftsCheck)
|
|
830
922
|
])
|
|
@@ -862,7 +954,7 @@ async function onEdit(req) {
|
|
|
862
954
|
|
|
863
955
|
// REVISIT: we need to use okra API here because it must be set in the batched request
|
|
864
956
|
// status code must be set in handler to allow overriding for FE V2
|
|
865
|
-
req?._?.odataRes
|
|
957
|
+
req?._?.odataRes?.setStatusCode(201)
|
|
866
958
|
|
|
867
959
|
return { ...res, IsActiveEntity: false } // REVISIT: Flatten?
|
|
868
960
|
}
|
|
@@ -225,7 +225,9 @@ function _processExpand(model, dbc, cqn, user, locale, txTimestamp) {
|
|
|
225
225
|
function executeSelectCQN(model, dbc, query, user, locale, txTimestamp) {
|
|
226
226
|
if (hasExpand(query)) {
|
|
227
227
|
// expand: '**' or '*3' is handled by new impl
|
|
228
|
-
if (
|
|
228
|
+
if (
|
|
229
|
+
query.SELECT.columns.some(c => c.expand && typeof c.expand[0] === 'string' && /^\*{1}[\d|*]+/.test(c.expand[0]))
|
|
230
|
+
) {
|
|
229
231
|
return expandV2(model, dbc, query, user, locale, txTimestamp, executeSelectCQN)
|
|
230
232
|
}
|
|
231
233
|
return _processExpand(model, dbc, query, user, locale, txTimestamp)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// REVISIT: Remove @sap/instance-manager compat with CDS 7
|
|
2
|
+
|
|
1
3
|
const cds = require('../cds')
|
|
2
4
|
const LOG = cds.log('pool|db')
|
|
3
5
|
|
|
@@ -15,7 +17,10 @@ function multiTenantServiceManager() {
|
|
|
15
17
|
if (e.code === 'MODULE_NOT_FOUND') return null
|
|
16
18
|
else throw e
|
|
17
19
|
}
|
|
18
|
-
|
|
20
|
+
const oldIm =
|
|
21
|
+
cds.requires.multitenancy?.['old-instance-manager'] ??
|
|
22
|
+
cds.env.requires?.['cds.xt.DeploymentService']?.['old-instance-manager']
|
|
23
|
+
return oldIm ? null : cds.xt?.serviceManager
|
|
19
24
|
}
|
|
20
25
|
|
|
21
26
|
function multiTenantInstanceManager(config = cds.env.requires.db) {
|
|
@@ -78,7 +83,10 @@ async function credentials4(tenant, db) {
|
|
|
78
83
|
: singleTenantInstanceManager(opts)
|
|
79
84
|
}
|
|
80
85
|
|
|
81
|
-
|
|
86
|
+
const oldIm =
|
|
87
|
+
cds.requires.multitenancy?.['old-instance-manager'] ??
|
|
88
|
+
cds.env.requires?.['cds.xt.DeploymentService']?.['old-instance-manager']
|
|
89
|
+
if (cds.xt?.serviceManager && !oldIm) {
|
|
82
90
|
return (await db._instance_manager.get(tenant, { disableCache: true })).credentials
|
|
83
91
|
}
|
|
84
92
|
|
|
@@ -31,7 +31,12 @@ const emit = ({ data, event: topic, headers = {} }, stream, prefix, LOG) =>
|
|
|
31
31
|
new Promise((resolve, reject) => {
|
|
32
32
|
LOG._info && LOG.info('Emit', { topic })
|
|
33
33
|
const message = { ...headers, data }
|
|
34
|
-
const payload = {
|
|
34
|
+
const payload = {
|
|
35
|
+
chunks: [Buffer.from(JSON.stringify(message))],
|
|
36
|
+
type: ['id', 'source', 'specversion', 'type'].every(el => el in headers)
|
|
37
|
+
? 'application/cloudevents+json'
|
|
38
|
+
: 'application/json'
|
|
39
|
+
}
|
|
35
40
|
const msg = {
|
|
36
41
|
done: resolve,
|
|
37
42
|
failed: e => {
|
|
@@ -82,6 +82,7 @@ class EnterpriseMessaging extends AMQPWebhookMessaging {
|
|
|
82
82
|
|
|
83
83
|
// New mtx based on @sap/cds-mtxs
|
|
84
84
|
async addMTXSHandlers() {
|
|
85
|
+
// REVISIT: Is that tested with MTX services in sidecar?
|
|
85
86
|
const deploymentSrv = await cds.connect.to('cds.xt.DeploymentService')
|
|
86
87
|
const provisioningSrv = await cds.connect.to('cds.xt.SaasProvisioningService')
|
|
87
88
|
deploymentSrv.impl(() => {
|
|
@@ -1,19 +1,6 @@
|
|
|
1
1
|
const cds = require('../cds')
|
|
2
2
|
const LOG = cds.log('remote')
|
|
3
3
|
|
|
4
|
-
// REVISIT: use cds.log's logger in cloud sdk
|
|
5
|
-
|
|
6
|
-
// disable sdk logger if not in debug mode
|
|
7
|
-
if (!LOG._debug) {
|
|
8
|
-
try {
|
|
9
|
-
// eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
|
|
10
|
-
const sdkUtils = require('@sap-cloud-sdk/util')
|
|
11
|
-
sdkUtils.setGlobalLogLevel('error')
|
|
12
|
-
} catch (err) {
|
|
13
|
-
/* might fail in cds repl due to winston's exception handler, see cap/issues#10134 */
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
4
|
const { resolveView, getTransition, restoreLink, findQueryTarget } = require('../common/utils/resolveView')
|
|
18
5
|
const { postProcess } = require('../common/utils/postProcessing')
|
|
19
6
|
const { getKind, run, getDestination, getAdditionalOptions, getReqOptions } = require('./utils/client')
|
|
@@ -165,6 +152,7 @@ const resolvedTargetOfQuery = q => {
|
|
|
165
152
|
return transitions.length && [transitions.length - 1].target
|
|
166
153
|
}
|
|
167
154
|
let logged
|
|
155
|
+
let sdkLoggerDisabled
|
|
168
156
|
class RemoteService extends cds.Service {
|
|
169
157
|
init() {
|
|
170
158
|
if (!this.options.credentials) {
|
|
@@ -186,6 +174,21 @@ class RemoteService extends cds.Service {
|
|
|
186
174
|
'Configuration option "cds.env.features.fetch_csrf" is deprecated.\n Please use "csrf"/"csrfInBatch" as described in https://cap.cloud.sap/docs/node.js/remote-services'
|
|
187
175
|
)
|
|
188
176
|
}
|
|
177
|
+
|
|
178
|
+
// REVISIT: use cds.log's logger in cloud sdk
|
|
179
|
+
|
|
180
|
+
// disable sdk logger if not in debug mode
|
|
181
|
+
if (!LOG._debug && !sdkLoggerDisabled) {
|
|
182
|
+
try {
|
|
183
|
+
// eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
|
|
184
|
+
const sdkUtils = require('@sap-cloud-sdk/util')
|
|
185
|
+
sdkUtils.setGlobalLogLevel('error')
|
|
186
|
+
// disable sdk logger once
|
|
187
|
+
sdkLoggerDisabled = true
|
|
188
|
+
} catch (err) {
|
|
189
|
+
/* might fail in cds repl due to winston's exception handler, see cap/issues#10134 */
|
|
190
|
+
}
|
|
191
|
+
}
|
|
189
192
|
// REVISIT: remove cds.env.features.fetch_csrf in next major ^7
|
|
190
193
|
this.csrf = cds.env.features.fetch_csrf || this.options.csrf
|
|
191
194
|
this.csrfInBatch = this.options.csrfInBatch
|
|
@@ -483,7 +483,12 @@ const getReqOptions = (req, query, service) => {
|
|
|
483
483
|
for (const k in originalHeaders) if (k.match(/^dwc-/)) reqOptions.headers[k] = originalHeaders[k]
|
|
484
484
|
}
|
|
485
485
|
|
|
486
|
-
if (
|
|
486
|
+
if (
|
|
487
|
+
reqOptions.data &&
|
|
488
|
+
reqOptions.method !== 'GET' &&
|
|
489
|
+
reqOptions.method !== 'HEAD' &&
|
|
490
|
+
!(reqOptions.data instanceof require('stream').Readable)
|
|
491
|
+
) {
|
|
487
492
|
if (typeof reqOptions.data === 'object' && !Buffer.isBuffer(reqOptions.data)) {
|
|
488
493
|
reqOptions.headers['content-type'] = 'application/json'
|
|
489
494
|
reqOptions.headers['content-length'] = Buffer.byteLength(JSON.stringify(reqOptions.data))
|
|
@@ -75,8 +75,7 @@ module.exports = class SQLiteDatabase extends DatabaseService {
|
|
|
75
75
|
this.before(['CREATE', 'UPDATE'], '*', this._input) // > has to run before rewrite
|
|
76
76
|
this.before(['CREATE', 'READ', 'UPDATE', 'DELETE', 'UPSERT'], '*', this._rewrite)
|
|
77
77
|
|
|
78
|
-
if (cds.env.
|
|
79
|
-
this.before('READ', '*', convertDraftAdminPathExpression)
|
|
78
|
+
if (cds.env.fiori.lean_draft && !cds.db?.cqn2sql) this.before('READ', '*', convertDraftAdminPathExpression)
|
|
80
79
|
this.before('READ', '*', convertAssocToOneManaged)
|
|
81
80
|
this.before('READ', '*', localized) // > has to run after rewrite
|
|
82
81
|
this.before('READ', '*', this._virtual)
|
|
@@ -103,6 +102,9 @@ module.exports = class SQLiteDatabase extends DatabaseService {
|
|
|
103
102
|
}
|
|
104
103
|
|
|
105
104
|
getDbUrl(tenant) {
|
|
105
|
+
return this.url4(tenant)
|
|
106
|
+
}
|
|
107
|
+
url4(tenant) {
|
|
106
108
|
const credentials = this.options.credentials || this.options || {}
|
|
107
109
|
let dbUrl = credentials.database || credentials.url || credentials.host || ':memory:'
|
|
108
110
|
|
|
@@ -123,7 +125,7 @@ module.exports = class SQLiteDatabase extends DatabaseService {
|
|
|
123
125
|
const tenant = isMultitenant && arg && (typeof arg === 'string' ? arg : arg.tenant || (arg.user && arg.user.tenant))
|
|
124
126
|
let dbc = this.dbcs.get(tenant)
|
|
125
127
|
if (!dbc) {
|
|
126
|
-
const dbUrl = this.
|
|
128
|
+
const dbUrl = this.url4(tenant)
|
|
127
129
|
|
|
128
130
|
dbc = await _new(dbUrl)
|
|
129
131
|
|
|
@@ -152,62 +154,6 @@ module.exports = class SQLiteDatabase extends DatabaseService {
|
|
|
152
154
|
else dbc._busy = false
|
|
153
155
|
}
|
|
154
156
|
|
|
155
|
-
/*
|
|
156
|
-
* deploy
|
|
157
|
-
*/
|
|
158
|
-
// REVISIT: make tenant aware
|
|
159
|
-
async deploy(model, options = {}) {
|
|
160
|
-
let createEntities = cds.compile.to.sql(model, options)
|
|
161
|
-
if (createEntities.length === 0) return // > nothing to deploy
|
|
162
|
-
|
|
163
|
-
let dropViews = []
|
|
164
|
-
let dropTables = []
|
|
165
|
-
for (const each of createEntities) {
|
|
166
|
-
const [, table, entity] = each.match(/^CREATE (?:(TABLE)|VIEW)\s+"?([^\s"(]+)"?/im) || []
|
|
167
|
-
if (table) dropTables.push({ DROP: { entity } })
|
|
168
|
-
else dropViews.push({ DROP: { view: entity } })
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// H2 is picky on the order
|
|
172
|
-
dropTables.reverse()
|
|
173
|
-
dropViews.reverse()
|
|
174
|
-
|
|
175
|
-
if (options.dry) {
|
|
176
|
-
// do not use cds.log() here!
|
|
177
|
-
const log = console.log // eslint-disable-line no-console
|
|
178
|
-
for (const {
|
|
179
|
-
DROP: { view }
|
|
180
|
-
} of dropViews) {
|
|
181
|
-
log('DROP VIEW IF EXISTS ' + view + ';')
|
|
182
|
-
}
|
|
183
|
-
log()
|
|
184
|
-
for (const {
|
|
185
|
-
DROP: { entity }
|
|
186
|
-
} of dropTables) {
|
|
187
|
-
log('DROP TABLE IF EXISTS ' + entity + ';')
|
|
188
|
-
}
|
|
189
|
-
log()
|
|
190
|
-
for (const each of createEntities) log(each + '\n')
|
|
191
|
-
return
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
await this.run(async tx => {
|
|
195
|
-
// This starts a new transaction if called from CLI, while joining
|
|
196
|
-
// existing root tx, e.g. when called from DeploymenrService
|
|
197
|
-
const [ext] = await tx.run(`SELECT 1 from sqlite_master where name='cds_xt_Extensions'`)
|
|
198
|
-
if (ext) {
|
|
199
|
-
// Poor man's schema evolution for MTX upgrade operations
|
|
200
|
-
createEntities = createEntities.filter(ct => !ct.match(/^CREATE TABLE cds_xt_Extensions/im))
|
|
201
|
-
dropTables = dropTables.filter(dt => dt.DROP.entity !== 'cds_xt_Extensions')
|
|
202
|
-
}
|
|
203
|
-
await tx.run(dropViews)
|
|
204
|
-
await tx.run(dropTables)
|
|
205
|
-
await tx.run(createEntities)
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
return true
|
|
209
|
-
}
|
|
210
|
-
|
|
211
157
|
async disconnect(tenant) {
|
|
212
158
|
this.dbcs.delete(tenant)
|
|
213
159
|
}
|
|
@@ -31,8 +31,8 @@ class CustomUpsertBuilder extends InsertBuilder {
|
|
|
31
31
|
if (!keys.includes(col_)) updates.push(`${sqlColumn}=excluded.${sqlColumn}`)
|
|
32
32
|
})
|
|
33
33
|
const conflict = updates.length
|
|
34
|
-
? ` ON CONFLICT(${keys}) DO UPDATE SET ` + updates.join(', ')
|
|
35
|
-
: ` ON CONFLICT(${keys}) DO NOTHING`
|
|
34
|
+
? ` ON CONFLICT (${keys}) DO UPDATE SET ` + updates.join(', ')
|
|
35
|
+
: ` ON CONFLICT (${keys}) DO NOTHING`
|
|
36
36
|
|
|
37
37
|
this._outputObj.sql = this._outputObj.sql + conflict
|
|
38
38
|
return this._outputObj
|
|
@@ -109,7 +109,9 @@ function _processExpand(model, dbc, cqn, user, locale, txTimestamp) {
|
|
|
109
109
|
function executeSelectCQN(model, dbc, query, user, locale, txTimestamp) {
|
|
110
110
|
if (hasExpand(query)) {
|
|
111
111
|
// expand: '**' or '*3' is handled by new impl
|
|
112
|
-
if (
|
|
112
|
+
if (
|
|
113
|
+
query.SELECT.columns.some(c => c.expand && typeof c.expand[0] === 'string' && /^\*{1}[\d|*]+/.test(c.expand[0]))
|
|
114
|
+
) {
|
|
113
115
|
return expandV2(model, dbc, query, user, locale, txTimestamp, executeSelectCQN)
|
|
114
116
|
}
|
|
115
117
|
return _processExpand(model, dbc, query, user, locale, txTimestamp)
|