@sap/cds 8.8.2 → 8.9.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 +46 -4
- package/_i18n/i18n_en_US_saptrc.properties +3 -0
- package/bin/colors.js +2 -0
- package/bin/test.js +103 -75
- package/eslint.config.mjs +16 -4
- package/lib/compile/for/lean_drafts.js +4 -0
- package/lib/compile/parse.js +26 -6
- package/lib/env/cds-env.js +3 -1
- package/lib/env/cds-requires.js +0 -3
- package/lib/env/schemas/cds-rc.js +11 -0
- package/lib/log/format/aspects/cls.js +2 -1
- package/lib/log/format/json.js +1 -1
- package/lib/plugins.js +2 -3
- package/lib/ql/SELECT.js +2 -1
- package/lib/ql/cds-ql.js +2 -0
- package/lib/ql/cds.ql-predicates.js +6 -4
- package/lib/ql/resolve.js +46 -0
- package/lib/req/validate.js +1 -0
- package/lib/srv/bindings.js +64 -43
- package/lib/srv/cds-connect.js +1 -1
- package/lib/srv/cds-serve.js +2 -2
- package/lib/srv/middlewares/auth/ias-auth.js +2 -0
- package/lib/srv/protocols/http.js +2 -2
- package/lib/srv/protocols/index.js +1 -1
- package/lib/srv/protocols/odata-v4.js +0 -1
- package/lib/srv/srv-tx.js +1 -1
- package/lib/test/cds-test.js +3 -4
- package/lib/utils/cds-utils.js +19 -19
- package/lib/utils/colors.js +46 -45
- package/lib/utils/csv-reader.js +5 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +1 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -1
- package/libx/_runtime/common/Service.js +4 -2
- package/libx/_runtime/common/composition/data.js +1 -2
- package/libx/_runtime/common/composition/tree.js +6 -4
- package/libx/_runtime/common/generic/sorting.js +6 -2
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +6 -7
- package/libx/_runtime/common/utils/differ.js +1 -1
- package/libx/_runtime/common/utils/draft.js +1 -1
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +6 -2
- package/libx/_runtime/common/utils/keys.js +13 -84
- package/libx/_runtime/common/utils/propagateForeignKeys.js +4 -3
- package/libx/_runtime/common/utils/resolveView.js +96 -102
- package/libx/_runtime/common/utils/rewriteAsterisks.js +1 -1
- package/libx/_runtime/common/utils/stream.js +2 -3
- package/libx/_runtime/db/utils/columns.js +1 -1
- package/libx/_runtime/fiori/lean-draft.js +11 -7
- package/libx/_runtime/messaging/common-utils/connections.js +6 -2
- package/libx/_runtime/messaging/kafka.js +3 -4
- package/libx/_runtime/remote/Service.js +13 -5
- package/libx/_runtime/remote/utils/client.js +1 -0
- package/libx/_runtime/ucl/Service.js +135 -126
- package/libx/common/utils/path.js +34 -22
- package/libx/odata/middleware/create.js +2 -0
- package/libx/odata/middleware/operation.js +8 -2
- package/libx/odata/middleware/parse.js +1 -1
- package/libx/odata/middleware/stream.js +1 -2
- package/libx/odata/middleware/update.js +2 -0
- package/libx/odata/parse/afterburner.js +17 -9
- package/libx/odata/parse/cqn2odata.js +3 -1
- package/libx/odata/parse/grammar.peggy +21 -19
- package/libx/odata/parse/parser.js +1 -1
- package/libx/odata/utils/metadata.js +8 -2
- package/libx/odata/utils/odataBind.js +36 -0
- package/libx/outbox/index.js +1 -0
- package/libx/rest/middleware/operation.js +9 -8
- package/libx/rest/middleware/parse.js +1 -0
- package/package.json +3 -3
- package/lib/i18n/resources.js +0 -150
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
const cds = require('
|
|
1
|
+
const cds = require('../../../../lib')
|
|
2
2
|
let LOG = cds.log('app')
|
|
3
|
-
|
|
4
|
-
const PERSISTENCE_TABLE = '@cds.persistence.table'
|
|
3
|
+
|
|
5
4
|
const { rewriteAsterisks } = require('../../common/utils/rewriteAsterisks')
|
|
6
5
|
|
|
7
6
|
const getError = require('../error')
|
|
8
|
-
const { getEntityNameFromDeleteCQN } = require('../utils/cqn')
|
|
9
7
|
|
|
10
8
|
const _setInverseTransition = (mapping, ref, mapped) => {
|
|
11
9
|
const existing = mapping.get(ref)
|
|
@@ -34,7 +32,7 @@ const _inverseTransition = transition => {
|
|
|
34
32
|
const ref0 = value.ref[0]
|
|
35
33
|
if (value.ref.length > 1) {
|
|
36
34
|
// ignore flattened columns like author.name
|
|
37
|
-
if (transition.target.elements[ref0]
|
|
35
|
+
if (transition.target.elements[ref0].isAssociation) continue
|
|
38
36
|
|
|
39
37
|
const nested = inverseTransition.mapping.get(ref0) || {}
|
|
40
38
|
if (!nested.transition) nested.transition = { mapping: new Map() }
|
|
@@ -57,15 +55,15 @@ const _inverseTransition = transition => {
|
|
|
57
55
|
return inverseTransition
|
|
58
56
|
}
|
|
59
57
|
|
|
60
|
-
const revertData = (data, transition, service) => {
|
|
58
|
+
const revertData = (data, transition, service, options) => {
|
|
61
59
|
if (!transition || !transition.mapping.size) return data
|
|
62
60
|
const inverseTransition = _inverseTransition(transition)
|
|
63
61
|
return Array.isArray(data)
|
|
64
|
-
? data.map(entry => _newData(entry, inverseTransition, true, service))
|
|
65
|
-
: _newData(data, inverseTransition, true, service)
|
|
62
|
+
? data.map(entry => _newData(entry, inverseTransition, true, service, options))
|
|
63
|
+
: _newData(data, inverseTransition, true, service, options)
|
|
66
64
|
}
|
|
67
65
|
|
|
68
|
-
const _newSubData = (val, key, transition, el, inverse, service) => {
|
|
66
|
+
const _newSubData = (val, key, transition, el, inverse, service, options) => {
|
|
69
67
|
if ((!Array.isArray(val) && typeof val === 'object') || (Array.isArray(val) && val.length !== 0)) {
|
|
70
68
|
let mapped = transition.mapping.get(key)
|
|
71
69
|
if (!mapped) {
|
|
@@ -74,14 +72,14 @@ const _newSubData = (val, key, transition, el, inverse, service) => {
|
|
|
74
72
|
}
|
|
75
73
|
|
|
76
74
|
if (!mapped.transition) {
|
|
77
|
-
const subTransition = getTransition(el._target, service)
|
|
75
|
+
const subTransition = getTransition(el._target, service, undefined, options?.event, { abort: options?.abort })
|
|
78
76
|
mapped.transition = inverse ? _inverseTransition(subTransition) : subTransition
|
|
79
77
|
}
|
|
80
78
|
|
|
81
79
|
if (Array.isArray(val)) {
|
|
82
|
-
return val.map(singleVal => _newData(singleVal, mapped.transition, inverse, service))
|
|
80
|
+
return val.map(singleVal => _newData(singleVal, mapped.transition, inverse, service, options))
|
|
83
81
|
} else {
|
|
84
|
-
return _newData(val, mapped.transition, inverse, service)
|
|
82
|
+
return _newData(val, mapped.transition, inverse, service, options)
|
|
85
83
|
}
|
|
86
84
|
}
|
|
87
85
|
return val //Case of empty array
|
|
@@ -105,7 +103,7 @@ const _newNestedData = (queryTarget, newData, ref, value) => {
|
|
|
105
103
|
}
|
|
106
104
|
}
|
|
107
105
|
|
|
108
|
-
const _newData = (data, transition, inverse, service) => {
|
|
106
|
+
const _newData = (data, transition, inverse, service, options) => {
|
|
109
107
|
if (data === null) return null
|
|
110
108
|
|
|
111
109
|
// no transition -> nothing to do
|
|
@@ -131,12 +129,12 @@ const _newData = (data, transition, inverse, service) => {
|
|
|
131
129
|
let value = data[key]
|
|
132
130
|
if (isAssoc) {
|
|
133
131
|
if (value || (value === null && service.name === 'db')) {
|
|
134
|
-
value = _newSubData(value, key, transition, el, inverse, service)
|
|
132
|
+
value = _newSubData(value, key, transition, el, inverse, service, options)
|
|
135
133
|
}
|
|
136
134
|
}
|
|
137
135
|
|
|
138
136
|
if (!isAssoc && mapped.transition) {
|
|
139
|
-
value = _newSubData(value, key, transition, el, inverse)
|
|
137
|
+
value = _newSubData(value, key, transition, el, inverse, undefined, options)
|
|
140
138
|
Object.assign(newData, value)
|
|
141
139
|
}
|
|
142
140
|
|
|
@@ -154,14 +152,14 @@ const _newData = (data, transition, inverse, service) => {
|
|
|
154
152
|
return newData
|
|
155
153
|
}
|
|
156
154
|
|
|
157
|
-
const _newColumns = (columns = [], transition, service, withAlias = false) => {
|
|
155
|
+
const _newColumns = (columns = [], transition, service, withAlias = false, options) => {
|
|
158
156
|
const newColumns = []
|
|
159
157
|
|
|
160
158
|
columns.forEach(column => {
|
|
161
159
|
let newColumn
|
|
162
160
|
if (column.func) {
|
|
163
161
|
newColumn = { ...column }
|
|
164
|
-
newColumn.args = _newColumns(column.args, transition, service, withAlias)
|
|
162
|
+
newColumn.args = _newColumns(column.args, transition, service, withAlias, options)
|
|
165
163
|
newColumns.push(newColumn)
|
|
166
164
|
return newColumns
|
|
167
165
|
}
|
|
@@ -200,9 +198,9 @@ const _newColumns = (columns = [], transition, service, withAlias = false) => {
|
|
|
200
198
|
|
|
201
199
|
// reuse _newColumns with new transition
|
|
202
200
|
const expandTarget = def._target
|
|
203
|
-
const subtransition = getTransition(expandTarget, service)
|
|
201
|
+
const subtransition = getTransition(expandTarget, service, undefined, options.event, { abort: options.abort })
|
|
204
202
|
mapped.transition = subtransition
|
|
205
|
-
newColumn.expand = _newColumns(column.expand, subtransition, service, withAlias)
|
|
203
|
+
newColumn.expand = _newColumns(column.expand, subtransition, service, withAlias, options)
|
|
206
204
|
}
|
|
207
205
|
|
|
208
206
|
newColumns.push(newColumn)
|
|
@@ -269,8 +267,8 @@ const _newWhereRef = (newWhereElement, transition, alias, tableName) => {
|
|
|
269
267
|
newWhereElement.ref = newRef
|
|
270
268
|
}
|
|
271
269
|
|
|
272
|
-
const _newEntries = (entries = [], transition, service) =>
|
|
273
|
-
entries.map(entry => _newData(entry, transition, false, service))
|
|
270
|
+
const _newEntries = (entries = [], transition, service, options) =>
|
|
271
|
+
entries.map(entry => _newData(entry, transition, false, service, options))
|
|
274
272
|
|
|
275
273
|
const _newWhere = (where = [], transition, tableName, alias, isSubselect = false) => {
|
|
276
274
|
const newWhere = where.map(whereElement => {
|
|
@@ -355,7 +353,7 @@ const _rewriteQueryPath = (path, transitions) => {
|
|
|
355
353
|
})
|
|
356
354
|
}
|
|
357
355
|
|
|
358
|
-
const _newUpdate = (query, transitions, service) => {
|
|
356
|
+
const _newUpdate = (query, transitions, service, options) => {
|
|
359
357
|
const targetTransition = transitions.at(-1)
|
|
360
358
|
const targetName = targetTransition.target.name
|
|
361
359
|
const newUpdate = Object.create(query.UPDATE)
|
|
@@ -366,8 +364,8 @@ const _newUpdate = (query, transitions, service) => {
|
|
|
366
364
|
ref: _rewriteQueryPath(query.UPDATE.entity, transitions)
|
|
367
365
|
}
|
|
368
366
|
: targetName
|
|
369
|
-
if (newUpdate.data) newUpdate.data = _newData(newUpdate.data, targetTransition, false, service)
|
|
370
|
-
if (newUpdate.with) newUpdate.with = _newData(newUpdate.with, targetTransition, false, service)
|
|
367
|
+
if (newUpdate.data) newUpdate.data = _newData(newUpdate.data, targetTransition, false, service, options)
|
|
368
|
+
if (newUpdate.with) newUpdate.with = _newData(newUpdate.with, targetTransition, false, service, options)
|
|
371
369
|
if (newUpdate.where) {
|
|
372
370
|
newUpdate.where = _newWhere(
|
|
373
371
|
newUpdate.where,
|
|
@@ -384,7 +382,7 @@ const _newUpdate = (query, transitions, service) => {
|
|
|
384
382
|
return newUpdate
|
|
385
383
|
}
|
|
386
384
|
|
|
387
|
-
const _newSelect = (query, transitions, service) => {
|
|
385
|
+
const _newSelect = (query, transitions, service, options) => {
|
|
388
386
|
const targetTransition = transitions.at(-1)
|
|
389
387
|
const newSelect = Object.create(query.SELECT)
|
|
390
388
|
newSelect.from = {
|
|
@@ -398,12 +396,21 @@ const _newSelect = (query, transitions, service) => {
|
|
|
398
396
|
_4db: service.isDatabaseService,
|
|
399
397
|
target: targetTransition.queryTarget
|
|
400
398
|
})
|
|
401
|
-
newSelect.columns = _newColumns(
|
|
399
|
+
newSelect.columns = _newColumns(
|
|
400
|
+
newSelect.columns,
|
|
401
|
+
targetTransition,
|
|
402
|
+
service,
|
|
403
|
+
service.kind !== 'app-service',
|
|
404
|
+
options
|
|
405
|
+
)
|
|
402
406
|
}
|
|
403
407
|
|
|
404
|
-
if (newSelect.having)
|
|
405
|
-
|
|
406
|
-
if (newSelect.
|
|
408
|
+
if (newSelect.having)
|
|
409
|
+
newSelect.having = _newColumns(newSelect.having, targetTransition, undefined, undefined, options)
|
|
410
|
+
if (newSelect.groupBy)
|
|
411
|
+
newSelect.groupBy = _newColumns(newSelect.groupBy, targetTransition, undefined, undefined, options)
|
|
412
|
+
if (newSelect.orderBy)
|
|
413
|
+
newSelect.orderBy = _newColumns(newSelect.orderBy, targetTransition, undefined, undefined, options)
|
|
407
414
|
if (newSelect.where) {
|
|
408
415
|
newSelect.where = _newWhere(
|
|
409
416
|
newSelect.where,
|
|
@@ -421,7 +428,7 @@ const _newSelect = (query, transitions, service) => {
|
|
|
421
428
|
return newSelect
|
|
422
429
|
}
|
|
423
430
|
|
|
424
|
-
const _newInsert = (query, transitions, service) => {
|
|
431
|
+
const _newInsert = (query, transitions, service, options) => {
|
|
425
432
|
const targetTransition = transitions[transitions.length - 1]
|
|
426
433
|
const targetName = targetTransition.target.name
|
|
427
434
|
const newInsert = Object.create(query.INSERT)
|
|
@@ -438,7 +445,7 @@ const _newInsert = (query, transitions, service) => {
|
|
|
438
445
|
}
|
|
439
446
|
|
|
440
447
|
if (newInsert.columns) newInsert.columns = _newInsertColumns(newInsert.columns, targetTransition)
|
|
441
|
-
if (newInsert.entries) newInsert.entries = _newEntries(newInsert.entries, targetTransition, service)
|
|
448
|
+
if (newInsert.entries) newInsert.entries = _newEntries(newInsert.entries, targetTransition, service, options)
|
|
442
449
|
Object.defineProperty(newInsert, '_transitions', {
|
|
443
450
|
enumerable: false,
|
|
444
451
|
value: transitions
|
|
@@ -447,7 +454,7 @@ const _newInsert = (query, transitions, service) => {
|
|
|
447
454
|
return newInsert
|
|
448
455
|
}
|
|
449
456
|
|
|
450
|
-
const _newUpsert = (query, transitions, service) => {
|
|
457
|
+
const _newUpsert = (query, transitions, service, options) => {
|
|
451
458
|
const targetTransition = transitions[transitions.length - 1]
|
|
452
459
|
const targetName = targetTransition.target.name
|
|
453
460
|
const newUpsert = Object.create(query.UPSERT)
|
|
@@ -459,7 +466,7 @@ const _newUpsert = (query, transitions, service) => {
|
|
|
459
466
|
}
|
|
460
467
|
: targetName
|
|
461
468
|
if (newUpsert.columns) newUpsert.columns = _newInsertColumns(newUpsert.columns, targetTransition)
|
|
462
|
-
if (newUpsert.entries) newUpsert.entries = _newEntries(newUpsert.entries, targetTransition, service)
|
|
469
|
+
if (newUpsert.entries) newUpsert.entries = _newEntries(newUpsert.entries, targetTransition, service, options)
|
|
463
470
|
Object.defineProperty(newUpsert, '_transitions', {
|
|
464
471
|
enumerable: false,
|
|
465
472
|
value: transitions
|
|
@@ -481,12 +488,8 @@ const _newDelete = (query, transitions) => {
|
|
|
481
488
|
: targetName
|
|
482
489
|
|
|
483
490
|
if (newDelete.where) {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
targetTransition,
|
|
487
|
-
getEntityNameFromDeleteCQN(query),
|
|
488
|
-
query.DELETE.from.as
|
|
489
|
-
)
|
|
491
|
+
const from = typeof query.DELETE.from === 'string' ? query.DELETE.from : query.DELETE.from.ref[0]
|
|
492
|
+
newDelete.where = _newWhere(newDelete.where, targetTransition, from, query.DELETE.from.as)
|
|
490
493
|
}
|
|
491
494
|
|
|
492
495
|
Object.defineProperty(newDelete, '_transitions', {
|
|
@@ -497,9 +500,6 @@ const _newDelete = (query, transitions) => {
|
|
|
497
500
|
return newDelete
|
|
498
501
|
}
|
|
499
502
|
|
|
500
|
-
const _isPersistenceTable = target =>
|
|
501
|
-
Object.prototype.hasOwnProperty.call(target, PERSISTENCE_TABLE) && target[PERSISTENCE_TABLE]
|
|
502
|
-
|
|
503
503
|
const _findRenamed = (cqnColumns, column) =>
|
|
504
504
|
cqnColumns.find(
|
|
505
505
|
cqnColumn =>
|
|
@@ -508,7 +508,7 @@ const _findRenamed = (cqnColumns, column) =>
|
|
|
508
508
|
(column.as === cqnColumn.as && Object.prototype.hasOwnProperty.call(cqnColumn, 'val')))
|
|
509
509
|
)
|
|
510
510
|
|
|
511
|
-
const _queryColumns = (target, columns = [],
|
|
511
|
+
const _queryColumns = (target, columns = [], isAborted) => {
|
|
512
512
|
if (!(target && target.query && target.query.SELECT)) return columns
|
|
513
513
|
|
|
514
514
|
const cqnColumns = target.query.SELECT.columns || []
|
|
@@ -527,11 +527,7 @@ const _queryColumns = (target, columns = [], persistenceTable = false, force = f
|
|
|
527
527
|
if (!renamed.ref || renamed.ref.some(e => typeof e !== 'string') || renamed.xpr) return res
|
|
528
528
|
if (isTargetAliased && renamed.ref[0] === from.as) renamed.ref.shift()
|
|
529
529
|
|
|
530
|
-
|
|
531
|
-
// and elements aliases exist, the aliases must be used as column references.
|
|
532
|
-
// The reason is that in this scenario, the cds compiler generate a table
|
|
533
|
-
// instead of a view. If forced, skip this.
|
|
534
|
-
column.ref = !force && persistenceTable ? [renamed.as] : [...renamed.ref]
|
|
530
|
+
column.ref = isAborted ? [renamed.as] : [...renamed.ref]
|
|
535
531
|
}
|
|
536
532
|
|
|
537
533
|
res.push(column)
|
|
@@ -552,12 +548,7 @@ const _mappedValue = (col, alias) => {
|
|
|
552
548
|
return [key, { val: col.val }]
|
|
553
549
|
}
|
|
554
550
|
|
|
555
|
-
const getDBTable = target =>
|
|
556
|
-
if (target.query && target.query._target && !_isPersistenceTable(target)) {
|
|
557
|
-
return getDBTable(target.query._target)
|
|
558
|
-
}
|
|
559
|
-
return target
|
|
560
|
-
}
|
|
551
|
+
const getDBTable = target => cds.ql.resolve.table(target)
|
|
561
552
|
|
|
562
553
|
const _appendForeignKeys = (newColumns, target, columns, { as, ref = [] }) => {
|
|
563
554
|
const el = target.elements[as] || target.query._target.elements[ref.at(-1)]
|
|
@@ -584,55 +575,46 @@ const _appendForeignKeys = (newColumns, target, columns, { as, ref = [] }) => {
|
|
|
584
575
|
return newColumns
|
|
585
576
|
}
|
|
586
577
|
|
|
587
|
-
const _checkForForbiddenViews = queryTarget => {
|
|
578
|
+
const _checkForForbiddenViews = (queryTarget, event) => {
|
|
588
579
|
const select = queryTarget && queryTarget.query && queryTarget.query.SELECT
|
|
589
580
|
|
|
590
581
|
if (select) {
|
|
591
582
|
if (!select.from || select.from.join || select.from.length > 1) {
|
|
592
583
|
throw getError({
|
|
593
584
|
code: 501,
|
|
594
|
-
message: `${
|
|
585
|
+
message: `${event || 'INSERT|UPDATE|DELETE'} on views with join and/or union is not supported`,
|
|
595
586
|
target: queryTarget.name
|
|
596
587
|
})
|
|
597
588
|
}
|
|
598
589
|
|
|
599
590
|
if (select.where) {
|
|
600
591
|
LOG._debug &&
|
|
601
|
-
LOG.debug(`Ignoring where clause during ${
|
|
592
|
+
LOG.debug(`Ignoring where clause during ${event || 'INSERT|UPDATE|DELETE'} on view "${queryTarget.name}".`)
|
|
602
593
|
}
|
|
603
594
|
}
|
|
604
595
|
}
|
|
605
596
|
|
|
606
|
-
const _getTransitionData = (target, columns, service,
|
|
597
|
+
const _getTransitionData = (target, columns, service, options) => {
|
|
598
|
+
let { abort, skipForbiddenViewCheck, event } = options
|
|
599
|
+
// REVISIT revert after cds-dbs pr
|
|
600
|
+
if (!abort) abort = cds.ql.resolve.abortDB
|
|
607
601
|
// REVISIT: Find less param polluting way to skip forbidden view check for reads
|
|
608
|
-
if (!skipForbiddenViewCheck) _checkForForbiddenViews(target)
|
|
609
|
-
const
|
|
610
|
-
|
|
611
|
-
const isDatabaseService = service.isDatabaseService
|
|
612
|
-
columns = _queryColumns(target, columns, persistenceTable, !isDatabaseService && !targetStartsWithSrvName)
|
|
613
|
-
|
|
614
|
-
// REVISIT: Change once we expose database service
|
|
615
|
-
if (persistenceTable && isDatabaseService) {
|
|
616
|
-
return { target, transitionColumns: columns }
|
|
617
|
-
}
|
|
602
|
+
if (!skipForbiddenViewCheck) _checkForForbiddenViews(target, event)
|
|
603
|
+
const isAborted = abort(target)
|
|
604
|
+
columns = _queryColumns(target, columns, isAborted)
|
|
618
605
|
|
|
619
|
-
|
|
620
|
-
if (!isDatabaseService && targetStartsWithSrvName) {
|
|
621
|
-
return { target, transitionColumns: columns }
|
|
622
|
-
}
|
|
606
|
+
if (isAborted) return { target, transitionColumns: columns }
|
|
623
607
|
|
|
624
|
-
|
|
625
|
-
|
|
608
|
+
if (!target.query?._target) {
|
|
609
|
+
// for cross service in x4 and DRAFT.DraftAdministrativeData we cannot abort properly
|
|
610
|
+
// therefore return last resolved target
|
|
611
|
+
if (cds.env.features.restrict_service_scope === false) return { target, transitionColumns: columns }
|
|
612
|
+
return undefined
|
|
613
|
+
} else {
|
|
626
614
|
const newTarget = target.query._target
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
return _getTransitionData(newTarget, columns, service, skipForbiddenViewCheck)
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
return { target: newTarget, transitionColumns: columns }
|
|
615
|
+
// continue projection resolving for projections
|
|
616
|
+
return _getTransitionData(newTarget, columns, service, options)
|
|
633
617
|
}
|
|
634
|
-
|
|
635
|
-
return { target, transitionColumns: columns }
|
|
636
618
|
}
|
|
637
619
|
|
|
638
620
|
/**
|
|
@@ -642,14 +624,19 @@ const _getTransitionData = (target, columns, service, skipForbiddenViewCheck) =>
|
|
|
642
624
|
* @param service
|
|
643
625
|
* @param skipForbiddenViewCheck
|
|
644
626
|
*/
|
|
645
|
-
const getTransition = (queryTarget, service, skipForbiddenViewCheck, event) => {
|
|
646
|
-
if (event) _event = event
|
|
627
|
+
const getTransition = (queryTarget, service, skipForbiddenViewCheck, event, options) => {
|
|
647
628
|
// Never resolve unknown targets (e.g. for drafts)
|
|
648
629
|
if (!queryTarget) {
|
|
649
630
|
return { target: queryTarget, queryTarget, mapping: new Map() }
|
|
650
631
|
}
|
|
651
632
|
|
|
652
|
-
const
|
|
633
|
+
const transitionData = _getTransitionData(queryTarget, [], service, {
|
|
634
|
+
skipForbiddenViewCheck,
|
|
635
|
+
event,
|
|
636
|
+
abort: options?.abort
|
|
637
|
+
})
|
|
638
|
+
if (!transitionData) return undefined
|
|
639
|
+
const { target: _target, transitionColumns } = transitionData
|
|
653
640
|
const query = queryTarget.query
|
|
654
641
|
const alias = query && query.SELECT && query.SELECT.from && query.SELECT.from.as
|
|
655
642
|
const mappedColumns = transitionColumns.map(column => _mappedValue(column, alias))
|
|
@@ -657,11 +644,15 @@ const getTransition = (queryTarget, service, skipForbiddenViewCheck, event) => {
|
|
|
657
644
|
return { target: _target, queryTarget, mapping }
|
|
658
645
|
}
|
|
659
646
|
|
|
660
|
-
const _entityTransitionsForTarget = (from, model, service) => {
|
|
647
|
+
const _entityTransitionsForTarget = (from, model, service, options) => {
|
|
661
648
|
let previousEntity
|
|
662
649
|
|
|
663
650
|
if (typeof from === 'string') {
|
|
664
|
-
return
|
|
651
|
+
return (
|
|
652
|
+
model.definitions[from] && [
|
|
653
|
+
getTransition(model.definitions[from], service, undefined, options.event, { abort: options.abort })
|
|
654
|
+
]
|
|
655
|
+
)
|
|
665
656
|
}
|
|
666
657
|
|
|
667
658
|
return from.ref.map((f, i) => {
|
|
@@ -671,7 +662,7 @@ const _entityTransitionsForTarget = (from, model, service) => {
|
|
|
671
662
|
const entity = model.definitions[element]
|
|
672
663
|
if (entity) {
|
|
673
664
|
previousEntity = entity
|
|
674
|
-
return getTransition(entity, service)
|
|
665
|
+
return getTransition(entity, service, undefined, options.event, { abort: options.abort })
|
|
675
666
|
}
|
|
676
667
|
}
|
|
677
668
|
|
|
@@ -680,7 +671,7 @@ const _entityTransitionsForTarget = (from, model, service) => {
|
|
|
680
671
|
if (entity) {
|
|
681
672
|
// > assoc
|
|
682
673
|
previousEntity = entity
|
|
683
|
-
return getTransition(entity, service)
|
|
674
|
+
return getTransition(entity, service, undefined, options.event, { abort: options.abort })
|
|
684
675
|
}
|
|
685
676
|
|
|
686
677
|
// > struct
|
|
@@ -694,7 +685,8 @@ const _entityTransitionsForTarget = (from, model, service) => {
|
|
|
694
685
|
})
|
|
695
686
|
}
|
|
696
687
|
|
|
697
|
-
const _newQuery = (query,
|
|
688
|
+
const _newQuery = (query, model, service, options) => {
|
|
689
|
+
const { event } = options
|
|
698
690
|
const [_prop, _func] = {
|
|
699
691
|
SELECT: ['from', _newSelect],
|
|
700
692
|
INSERT: ['into', _newInsert],
|
|
@@ -703,30 +695,32 @@ const _newQuery = (query, event, model, service) => {
|
|
|
703
695
|
DELETE: ['from', _newDelete]
|
|
704
696
|
}[event]
|
|
705
697
|
const newQuery = Object.create(query)
|
|
706
|
-
const transitions = _entityTransitionsForTarget(query[event][_prop], model, service)
|
|
707
|
-
|
|
698
|
+
const transitions = _entityTransitionsForTarget(query[event][_prop], model, service, options)
|
|
699
|
+
if (!service.isDatabaseService && cds.env.features.restrict_service_scope !== false && transitions.some(t => !t))
|
|
700
|
+
return undefined
|
|
701
|
+
newQuery[event] = (transitions?.[0] && _func(newQuery, transitions, service, options)) || { ...query[event] }
|
|
708
702
|
return newQuery
|
|
709
703
|
}
|
|
710
704
|
|
|
711
|
-
const resolveView = (query, model, service) => {
|
|
705
|
+
const resolveView = (query, model, service, abort) => {
|
|
712
706
|
// swap logger
|
|
713
707
|
const _LOG = LOG
|
|
714
708
|
LOG = cds.log(service.kind) // REVISIT: Avoid obtaining loggers per request!
|
|
715
709
|
|
|
716
710
|
// If the query is a projection, one must follow it
|
|
717
711
|
// to let the underlying service know its true entity.
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
else if (query.
|
|
721
|
-
else if (query.
|
|
722
|
-
else if (query.
|
|
723
|
-
else if (query.
|
|
712
|
+
let event
|
|
713
|
+
if (query.kind) event = query.kind
|
|
714
|
+
else if (query.SELECT) event = 'SELECT'
|
|
715
|
+
else if (query.INSERT) event = 'INSERT'
|
|
716
|
+
else if (query.UPSERT) event = 'UPSERT'
|
|
717
|
+
else if (query.UPDATE) event = 'UPDATE'
|
|
718
|
+
else if (query.DELETE) event = 'DELETE'
|
|
724
719
|
|
|
725
|
-
const newQuery = _newQuery(query,
|
|
720
|
+
const newQuery = _newQuery(query, model, service, { abort, event: event })
|
|
726
721
|
|
|
727
|
-
// restore logger
|
|
722
|
+
// restore logger
|
|
728
723
|
LOG = _LOG
|
|
729
|
-
_event = undefined
|
|
730
724
|
|
|
731
725
|
return newQuery
|
|
732
726
|
}
|
|
@@ -2,7 +2,7 @@ const { getNavigationIfStruct } = require('./structured')
|
|
|
2
2
|
const getColumns = require('../../db/utils/columns')
|
|
3
3
|
const { ensureNoDraftsSuffix } = require('./draft')
|
|
4
4
|
const { getEntityNameFromCQN } = require('./entityFromCqn')
|
|
5
|
-
const cds = require('
|
|
5
|
+
const cds = require('../../../../lib')
|
|
6
6
|
|
|
7
7
|
const isAsteriskColumn = col => col === '*' || (col.ref && col.ref[0] === '*' && !col.expand)
|
|
8
8
|
|
|
@@ -2,7 +2,6 @@ const cds = require('../../cds')
|
|
|
2
2
|
const LOG = cds.log('odata')
|
|
3
3
|
const { SELECT } = cds.ql
|
|
4
4
|
const { deepCopy } = require('./copy')
|
|
5
|
-
const { getTransition } = require('./resolveView')
|
|
6
5
|
|
|
7
6
|
const _getStreamProperties = (req, query) => {
|
|
8
7
|
// new odata parser sets streaming property in SELECT.from
|
|
@@ -29,7 +28,7 @@ const _getStreamProperties = (req, query) => {
|
|
|
29
28
|
LOG.warn(
|
|
30
29
|
`@Core.MediaType in entity "${req.target.name}" points to property "${contentTypeProperty}" which was renamed or is not part of the projection. You must update the annotation value.`
|
|
31
30
|
)
|
|
32
|
-
const mapping =
|
|
31
|
+
const mapping = cds.ql.resolve.transitions(req.query, cds.db).mapping
|
|
33
32
|
const key = [...mapping.entries()].find(({ 1: val }) => val.ref[0] === contentTypeProperty)
|
|
34
33
|
contentTypeProperty = key && key.length && key[0]
|
|
35
34
|
}
|
|
@@ -52,7 +51,7 @@ const _getStreamProperties = (req, query) => {
|
|
|
52
51
|
LOG.warn(
|
|
53
52
|
`@Core.ContentDisposition.Filename in entity "${req.target.name}" points to property "${contentDispositionProperty}" which was renamed or is not part of the projection. You must update the annotation value.`
|
|
54
53
|
)
|
|
55
|
-
const mapping =
|
|
54
|
+
const mapping = cds.ql.resolve.transitions(req.query, cds.db).mapping
|
|
56
55
|
const key = [...mapping.entries()].find(({ 1: val }) => val.ref[0] === contentDispositionProperty)
|
|
57
56
|
contentDispositionProperty = key && key.length && key[0]
|
|
58
57
|
}
|
|
@@ -2,8 +2,6 @@ const { Readable, PassThrough } = require('stream')
|
|
|
2
2
|
const cds = require('../cds'),
|
|
3
3
|
{ Object_keys } = cds.utils
|
|
4
4
|
|
|
5
|
-
const { getTransition } = require('../common/utils/resolveView')
|
|
6
|
-
|
|
7
5
|
const { getPageSize, commonGenericPaging } = require('../common/generic/paging')
|
|
8
6
|
const { handler: commonGenericSorting } = require('../common/generic/sorting')
|
|
9
7
|
const { addEtagColumns } = require('../common/utils/etag')
|
|
@@ -639,6 +637,9 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
639
637
|
'location',
|
|
640
638
|
'../' + calculateLocationHeader(req.target, this, read_result || { ...res, IsActiveEntity: true })
|
|
641
639
|
)
|
|
640
|
+
|
|
641
|
+
if (read_result == null) req.res.status(204)
|
|
642
|
+
|
|
642
643
|
return read_result
|
|
643
644
|
}
|
|
644
645
|
|
|
@@ -804,7 +805,7 @@ const Read = {
|
|
|
804
805
|
// DraftAdministrativeData is only accessible via drafts
|
|
805
806
|
if (_isCount(query)) return run(query)
|
|
806
807
|
if (query._target.name.endsWith('.DraftAdministrativeData')) {
|
|
807
|
-
if (query.SELECT.from.ref?.length === 1) throw
|
|
808
|
+
if (query.SELECT.from.ref?.length === 1) throw cds.error('INVALID_DRAFT_REQUEST', { statusCode: 400 }) // only via drafts
|
|
808
809
|
return run(query._drafts)
|
|
809
810
|
}
|
|
810
811
|
if (!query._target._isDraftEnabled) return run(query)
|
|
@@ -852,7 +853,7 @@ const Read = {
|
|
|
852
853
|
LOG.debug('List Editing Status: Unchanged')
|
|
853
854
|
|
|
854
855
|
const draftsQuery = query._drafts
|
|
855
|
-
if (!draftsQuery) throw
|
|
856
|
+
if (!draftsQuery) throw cds.error('INVALID_DRAFT_REQUEST', { statusCode: 400 }) // only via drafts
|
|
856
857
|
draftsQuery.SELECT.count = undefined
|
|
857
858
|
draftsQuery.SELECT.orderBy = undefined
|
|
858
859
|
draftsQuery.SELECT.limit = null
|
|
@@ -868,7 +869,7 @@ const Read = {
|
|
|
868
869
|
ownDrafts: async function (run, query) {
|
|
869
870
|
LOG.debug('List Editing Status: Own Draft')
|
|
870
871
|
|
|
871
|
-
if (!query._drafts) throw
|
|
872
|
+
if (!query._drafts) throw cds.error('INVALID_DRAFT_REQUEST', { statusCode: 400 }) // only via drafts
|
|
872
873
|
|
|
873
874
|
// read active from draft
|
|
874
875
|
if (!query._drafts._target?.name.endsWith('.drafts')) {
|
|
@@ -1069,7 +1070,7 @@ const Read = {
|
|
|
1069
1070
|
|
|
1070
1071
|
activesFromDrafts: async function (run, query, { isLocked = true }) {
|
|
1071
1072
|
const draftsQuery = query._drafts
|
|
1072
|
-
if (!draftsQuery) throw
|
|
1073
|
+
if (!draftsQuery) throw cds.error('INVALID_DRAFT_REQUEST', { statusCode: 400 }) // only via drafts
|
|
1073
1074
|
|
|
1074
1075
|
const additionalCols = draftsQuery.SELECT.columns
|
|
1075
1076
|
? draftsQuery.SELECT.columns.filter(
|
|
@@ -1668,7 +1669,7 @@ async function onEdit(req) {
|
|
|
1668
1669
|
res[key] = keyData[key]
|
|
1669
1670
|
return res
|
|
1670
1671
|
}, {})
|
|
1671
|
-
const transition =
|
|
1672
|
+
const transition = cds.ql.resolve.transitions(req.query, cds.db)
|
|
1672
1673
|
|
|
1673
1674
|
// gets the underlying target entity, as record locking can't be
|
|
1674
1675
|
// applied to localized views
|
|
@@ -1781,6 +1782,9 @@ async function onEdit(req) {
|
|
|
1781
1782
|
'location',
|
|
1782
1783
|
'../' + calculateLocationHeader(req.target, this, read_result || { ...res, IsActiveEntity: false })
|
|
1783
1784
|
)
|
|
1785
|
+
|
|
1786
|
+
if (read_result == null) req.res.status(204)
|
|
1787
|
+
|
|
1784
1788
|
return read_result
|
|
1785
1789
|
} else {
|
|
1786
1790
|
return { ...res, IsActiveEntity: false } // REVISIT: Flatten?
|
|
@@ -23,12 +23,16 @@ const connect = (client, LOG, keepAlive) => {
|
|
|
23
23
|
.once('connected', function () {
|
|
24
24
|
client.removeAllListeners('error')
|
|
25
25
|
|
|
26
|
-
client.
|
|
26
|
+
client.once('error', err => {
|
|
27
27
|
if (LOG._error) {
|
|
28
28
|
err.message = 'Client error: ' + err.message
|
|
29
29
|
LOG.error(err)
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
if (keepAlive) {
|
|
32
|
+
client.removeAllListeners('error')
|
|
33
|
+
client.removeAllListeners('connected')
|
|
34
|
+
_connectUntilConnected(client, LOG, 0)
|
|
35
|
+
}
|
|
32
36
|
})
|
|
33
37
|
|
|
34
38
|
if (keepAlive) {
|
|
@@ -13,8 +13,7 @@ class KafkaService extends cds.MessagingService {
|
|
|
13
13
|
|
|
14
14
|
this._cachedKeyFns = new Map()
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
this.appId = appId()
|
|
16
|
+
this.consumerGroup = this.options.consumerGroup || appId() + '/' + this.options.topic
|
|
18
17
|
|
|
19
18
|
if (!this.options.local && !this.options.credentials) {
|
|
20
19
|
throw new Error(
|
|
@@ -95,7 +94,7 @@ class KafkaService extends cds.MessagingService {
|
|
|
95
94
|
}
|
|
96
95
|
|
|
97
96
|
async startListening() {
|
|
98
|
-
const consumer = this.client.consumer({ groupId: this.
|
|
97
|
+
const consumer = this.client.consumer({ groupId: this.consumerGroup })
|
|
99
98
|
await consumer.connect()
|
|
100
99
|
|
|
101
100
|
// In the future, we might allow to support the annotation @kafka.topic
|
|
@@ -216,7 +215,7 @@ async function _getConfig(srv) {
|
|
|
216
215
|
const brokers = allBrokers.split(',')
|
|
217
216
|
|
|
218
217
|
return {
|
|
219
|
-
clientId: srv.
|
|
218
|
+
clientId: srv.consumerGroup,
|
|
220
219
|
// logLevel: 4,
|
|
221
220
|
connectionTimeout: 15000,
|
|
222
221
|
authenticationTimeout: 15000,
|
|
@@ -3,12 +3,13 @@ const cds = require('../cds')
|
|
|
3
3
|
const { run, getReqOptions } = require('./utils/client')
|
|
4
4
|
const { getCloudSdk, getCloudSdkConnectivity, getCloudSdkResilience } = require('./utils/cloudSdkProvider')
|
|
5
5
|
const { hasAliasedColumns } = require('./utils/data')
|
|
6
|
-
const {
|
|
6
|
+
const { findQueryTarget } = require('../common/utils/resolveView')
|
|
7
7
|
const postProcess = require('../common/utils/postProcess')
|
|
8
8
|
const { formatVal } = require('../../odata/utils')
|
|
9
9
|
|
|
10
|
-
const
|
|
10
|
+
const _getHeaders = (defaultHeaders, req) => {
|
|
11
11
|
return Object.assign(
|
|
12
|
+
{},
|
|
12
13
|
defaultHeaders,
|
|
13
14
|
Object.keys(req.headers).reduce((acc, cur) => {
|
|
14
15
|
acc[cur.toLowerCase()] = req.headers[cur]
|
|
@@ -254,10 +255,16 @@ class RemoteService extends cds.Service {
|
|
|
254
255
|
throw new Error(`"url" or "destination" property must be configured in "credentials" of "${this.name}".`)
|
|
255
256
|
|
|
256
257
|
const reqOptions = getReqOptions(req, query, this)
|
|
257
|
-
reqOptions.headers =
|
|
258
|
+
reqOptions.headers = _getHeaders(reqOptions.headers, req)
|
|
259
|
+
|
|
260
|
+
// ensure request correlation (even with systems that use x-correlationid)
|
|
261
|
+
const correlationId = reqOptions.headers['x-correlation-id'] || cds.context?.id //> prefer custom header over context id
|
|
262
|
+
reqOptions.headers['x-correlation-id'] = correlationId
|
|
263
|
+
reqOptions.headers['x-correlationid'] = correlationId
|
|
258
264
|
|
|
259
265
|
const { kind, destination, destinationOptions } = this
|
|
260
|
-
const resolvedTarget =
|
|
266
|
+
const resolvedTarget =
|
|
267
|
+
resolvedTargetOfQuery(query) || cds.ql.resolve.transitions(query, this)?.target || req.target
|
|
261
268
|
const returnType = req._returnType
|
|
262
269
|
const additionalOptions = { destination, kind, resolvedTarget, returnType, destinationOptions }
|
|
263
270
|
|
|
@@ -289,7 +296,8 @@ class RemoteService extends cds.Service {
|
|
|
289
296
|
// we need to post process if alias was explicitly set in query
|
|
290
297
|
if (_isSelectWithAliasedColumns(req.query)) result = postProcess(req.query, result, this, true)
|
|
291
298
|
} else {
|
|
292
|
-
const query =
|
|
299
|
+
const query = cds.ql.resolve(req.query, this)
|
|
300
|
+
if (!query) throw new Error(`Target ${req.target.name} cannot be resolved for service ${this.name}`)
|
|
293
301
|
const target = findQueryTarget(query) || req.target
|
|
294
302
|
// we need to provide target explicitly because it's cached within ensure_target
|
|
295
303
|
const _req = new cds.Request({ query, target, _resolved: true, headers: req.headers, method: req.method })
|