@sap/cds 7.5.2 → 7.6.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 +79 -22
- package/app/index.js +1 -1
- package/lib/auth/index.js +3 -0
- package/lib/compile/extend.js +9 -4
- package/lib/compile/for/lean_drafts.js +3 -4
- package/lib/compile/load.js +11 -15
- package/lib/compile/minify.js +2 -4
- package/lib/compile/to/sql.js +6 -4
- package/lib/compile/to/srvinfo.js +25 -3
- package/lib/compile/to/yaml.js +1 -1
- package/lib/dbs/cds-deploy.js +7 -13
- package/lib/env/defaults.js +1 -10
- package/lib/env/schemas/cds-package.js +27 -0
- package/lib/env/schemas/cds-rc.js +693 -0
- package/lib/env/schemas/index.js +6 -4
- package/lib/i18n/localize.js +15 -1
- package/lib/index.js +40 -47
- package/lib/log/cds-error.js +6 -0
- package/lib/ql/Query.js +2 -1
- package/lib/ql/cds-ql.js +1 -2
- package/lib/ql/infer.js +0 -2
- package/lib/req/request.js +3 -6
- package/lib/srv/middlewares/trace.js +2 -2
- package/lib/srv/protocols/hcql.js +44 -30
- package/lib/srv/protocols/http.js +60 -0
- package/lib/srv/protocols/index.js +0 -7
- package/lib/srv/protocols/odata-v4.js +8 -2
- package/lib/srv/srv-api.js +129 -62
- package/lib/srv/srv-handlers.js +0 -1
- package/lib/srv/srv-models.js +1 -0
- package/lib/utils/cds-test.js +1 -1
- package/lib/utils/cds-utils.js +26 -0
- package/lib/utils/check-version.js +10 -13
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +22 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +3 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +89 -21
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/boundToCQN.js +4 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +1 -24
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/updateToCQN.js +1 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ApplyParser.js +3 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/TrustedResourceJsonSerializer.js +7 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +0 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +2 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +17 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +22 -2
- package/libx/_runtime/cds-services/services/utils/columns.js +1 -2
- package/libx/_runtime/common/aspects/Association.js +17 -9
- package/libx/_runtime/common/generic/crud.js +13 -22
- package/libx/_runtime/common/generic/etag.js +1 -1
- package/libx/_runtime/common/generic/input.js +9 -1
- package/libx/_runtime/common/generic/paging.js +3 -3
- package/libx/_runtime/common/generic/sorting.js +25 -15
- package/libx/_runtime/common/generic/stream.js +2 -16
- package/libx/_runtime/common/utils/copy.js +5 -0
- package/libx/_runtime/common/utils/cqn.js +1 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +4 -3
- package/libx/_runtime/common/utils/csn.js +0 -49
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +5 -5
- package/libx/_runtime/common/utils/generateOnCond.js +50 -25
- package/libx/_runtime/common/utils/resolveView.js +5 -44
- package/libx/_runtime/common/utils/rewriteAsterisks.js +17 -4
- package/libx/_runtime/common/utils/stream.js +16 -15
- package/libx/_runtime/common/utils/streamProp.js +25 -22
- package/libx/_runtime/db/Service.js +27 -8
- package/libx/_runtime/db/generic/input.js +6 -1
- package/libx/_runtime/db/generic/rewrite.js +3 -2
- package/libx/_runtime/db/query/read.js +15 -5
- package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +0 -11
- package/libx/_runtime/db/utils/columns.js +1 -0
- package/libx/_runtime/db/utils/stream.js +41 -0
- package/libx/_runtime/fiori/generic/read.js +2 -1
- package/libx/_runtime/fiori/generic/readOverDraft.js +1 -1
- package/libx/_runtime/fiori/lean-draft.js +216 -59
- package/libx/_runtime/hana/Service.js +1 -1
- package/libx/_runtime/hana/execute.js +53 -14
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +34 -15
- package/libx/_runtime/remote/Service.js +2 -1
- package/libx/_runtime/remote/utils/client.js +1 -1
- package/libx/_runtime/sqlite/Service.js +1 -1
- package/libx/_runtime/sqlite/execute.js +17 -5
- package/libx/odata/afterburner.js +58 -19
- package/libx/odata/cqn2odata.js +6 -8
- package/libx/odata/create.js +44 -0
- package/libx/odata/delete.js +25 -0
- package/libx/odata/error.js +8 -3
- package/libx/odata/metadata.js +6 -8
- package/libx/odata/service-document.js +1 -1
- package/libx/odata/update.js +110 -0
- package/libx/odata/utils.js +9 -6
- package/libx/outbox/index.js +48 -78
- package/libx/rest/RestAdapter.js +0 -3
- package/package.json +1 -1
- package/lib/env/schemas/cds-package.json +0 -17
- package/lib/env/schemas/cds-rc.json +0 -740
- package/lib/ql/STREAM.js +0 -90
|
@@ -29,38 +29,63 @@ const _adaptRefs = (onCond, path, { select, join }) => {
|
|
|
29
29
|
return onCond.map(_adaptEl)
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
const replace$selfAndAliasOnCOnd = (xpr, csnElement, aliases, path) => {
|
|
33
|
+
const selfIndex = xpr.findIndex(({ ref }) => ref?.[0] === '$self')
|
|
34
|
+
if (selfIndex != -1) {
|
|
35
|
+
let backLinkIndex
|
|
36
|
+
if (xpr[selfIndex + 1] && xpr[selfIndex + 1] === '=') backLinkIndex = selfIndex + 2
|
|
37
|
+
if (xpr[selfIndex - 1] && xpr[selfIndex - 1] === '=') backLinkIndex = selfIndex - 2
|
|
38
|
+
if (backLinkIndex != null) {
|
|
39
|
+
const ref = xpr[backLinkIndex].ref
|
|
40
|
+
const backlinkName = ref[ref.length - 1]
|
|
41
|
+
const mutOnCond = _newOnConditions(csnElement._backlink, [backlinkName], {
|
|
42
|
+
select: aliases.join,
|
|
43
|
+
join: aliases.select
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
xpr.splice(Math.min(backLinkIndex, selfIndex), 3, ...mutOnCond)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
37
49
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
50
|
+
for (let i = 0; i < xpr.length; i++) {
|
|
51
|
+
const element = xpr[i]
|
|
52
|
+
if (element.xpr) {
|
|
53
|
+
replace$selfAndAliasOnCOnd(element.xpr, csnElement, aliases, path)
|
|
54
|
+
continue
|
|
55
|
+
}
|
|
43
56
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
57
|
+
if (element.ref) {
|
|
58
|
+
if (element.ref[0] === path.join('_') && element.ref[1]) {
|
|
59
|
+
element.ref = _toRef(aliases.select, element.ref.slice(1)).ref
|
|
60
|
+
continue
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// no alias for special $user of canonical localized association
|
|
64
|
+
if (element.ref[0] === '$user' && path[0] === 'localized') {
|
|
65
|
+
element.ref = _toRef(undefined, element.ref.slice(0)).ref
|
|
66
|
+
continue
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (element.ref[0] === aliases.join || element.ref[0] === aliases.select) {
|
|
70
|
+
// nothing todo here, as already right alias
|
|
71
|
+
continue
|
|
72
|
+
}
|
|
49
73
|
|
|
50
|
-
|
|
74
|
+
element.ref = _toRef(aliases.join, element.ref.slice(0)).ref
|
|
75
|
+
}
|
|
76
|
+
}
|
|
51
77
|
}
|
|
52
78
|
|
|
53
|
-
const
|
|
79
|
+
const _args = (csnElement, path, aliases) => {
|
|
80
|
+
const onCond = csnElement.on
|
|
81
|
+
if (!onCond || onCond.length === 0) return []
|
|
82
|
+
if (onCond.length < 3 && !onCond[0]?.xpr) return onCond
|
|
83
|
+
if (!csnElement._isSelfManaged) return _adaptRefs(onCond, path, aliases)
|
|
54
84
|
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
const selfIndex = onCondWithoutSelf.findIndex((e, i, on) => {
|
|
58
|
-
if (e === 'and') return _isSelfRef(on[i + 1]) || _isSelfRef(on[i + 3])
|
|
59
|
-
return on[i + 1] === '=' && (_isSelfRef(e) || _isSelfRef(on[i + 2]))
|
|
60
|
-
})
|
|
85
|
+
const onCondCopy = JSON.parse(JSON.stringify(onCond))
|
|
86
|
+
replace$selfAndAliasOnCOnd(onCondCopy, csnElement, aliases, path)
|
|
61
87
|
|
|
62
|
-
|
|
63
|
-
return onCondWithoutSelf
|
|
88
|
+
return onCondCopy
|
|
64
89
|
}
|
|
65
90
|
|
|
66
91
|
// this is only for 2one managed w/o on-conditions, i.e. no static values are possible
|
|
@@ -237,28 +237,13 @@ const _newInsertColumns = (columns = [], transition) => {
|
|
|
237
237
|
const _newWhereRef = (newWhereElement, transition, alias, tableName, isSubSelect) => {
|
|
238
238
|
const newRef = Array.isArray(newWhereElement.ref) ? [...newWhereElement.ref] : [newWhereElement.ref]
|
|
239
239
|
|
|
240
|
-
if (newRef[0] === alias) {
|
|
240
|
+
if (newRef.length > 1 && newRef[0] === alias) {
|
|
241
241
|
const mapped = transition.mapping.get(newRef[1])
|
|
242
|
-
if (mapped)
|
|
243
|
-
|
|
244
|
-
transition.queryTarget.query?.SELECT?.from.as ??
|
|
245
|
-
transition.queryTarget.query?.SELECT?.from.ref.at(-1).split('.').pop()
|
|
246
|
-
const newMapped = []
|
|
247
|
-
|
|
248
|
-
if (tableAlias && mapped.ref[0] === tableAlias) {
|
|
249
|
-
// remove table alias from mapped array
|
|
250
|
-
newMapped.push(...mapped.ref.slice(1))
|
|
251
|
-
} else {
|
|
252
|
-
newMapped.push(...mapped.ref)
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// we assume it's a foreign key or single element
|
|
256
|
-
newRef[1] = newMapped.join('_')
|
|
257
|
-
}
|
|
258
|
-
} else if (newRef[0] === tableName) {
|
|
242
|
+
if (mapped) newRef[1] = mapped.ref.join('_')
|
|
243
|
+
} else if (newRef.length > 1 && newRef[0] === tableName) {
|
|
259
244
|
newRef[0] = transition.target.name
|
|
260
245
|
const mapped = transition.mapping.get(newRef[1])
|
|
261
|
-
if (mapped) newRef[1] = mapped.ref
|
|
246
|
+
if (mapped) newRef[1] = mapped.ref.join('_')
|
|
262
247
|
} else {
|
|
263
248
|
const mapped = transition.mapping.get(newRef[0])
|
|
264
249
|
if (isSubSelect && mapped && newRef.length === 1) {
|
|
@@ -425,28 +410,6 @@ const _newSelect = (query, transitions, service) => {
|
|
|
425
410
|
return newSelect
|
|
426
411
|
}
|
|
427
412
|
|
|
428
|
-
const _newStream = (query, transitions) => {
|
|
429
|
-
const targetTransition = transitions[transitions.length - 1]
|
|
430
|
-
const targetName = targetTransition.target.name
|
|
431
|
-
const newStream = Object.create(query.STREAM)
|
|
432
|
-
if (newStream.into) {
|
|
433
|
-
const refObject = newStream.into.ref ? newStream.into : { ref: [query.STREAM.into] }
|
|
434
|
-
newStream.into = {
|
|
435
|
-
...refObject,
|
|
436
|
-
ref: _rewriteQueryPath(refObject, transitions)
|
|
437
|
-
}
|
|
438
|
-
if (!query.STREAM.into.ref) newStream.into = newStream.into.ref[0] // leave as string
|
|
439
|
-
} else {
|
|
440
|
-
newStream.into = targetName
|
|
441
|
-
}
|
|
442
|
-
if (newStream.column) newStream.column = _resolveColumn(newStream.column, targetTransition)
|
|
443
|
-
Object.defineProperty(newStream, '_transitions', {
|
|
444
|
-
enumerable: false,
|
|
445
|
-
value: transitions
|
|
446
|
-
})
|
|
447
|
-
return newStream
|
|
448
|
-
}
|
|
449
|
-
|
|
450
413
|
const _newInsert = (query, transitions, service) => {
|
|
451
414
|
const targetTransition = transitions[transitions.length - 1]
|
|
452
415
|
const targetName = targetTransition.target.name
|
|
@@ -725,8 +688,7 @@ const _newQuery = (query, event, model, service) => {
|
|
|
725
688
|
INSERT: ['into', _newInsert],
|
|
726
689
|
UPSERT: ['into', _newUpsert],
|
|
727
690
|
UPDATE: ['entity', _newUpdate],
|
|
728
|
-
DELETE: ['from', _newDelete]
|
|
729
|
-
STREAM: ['into', _newStream]
|
|
691
|
+
DELETE: ['from', _newDelete]
|
|
730
692
|
}[event]
|
|
731
693
|
const newQuery = Object.create(query)
|
|
732
694
|
const transitions = _entityTransitionsForTarget(query[event][_prop], model, service)
|
|
@@ -747,7 +709,6 @@ const resolveView = (query, model, service) => {
|
|
|
747
709
|
else if (query.UPSERT) _event = 'UPSERT'
|
|
748
710
|
else if (query.UPDATE) _event = 'UPDATE'
|
|
749
711
|
else if (query.DELETE) _event = 'DELETE'
|
|
750
|
-
else if (query.STREAM) _event = 'STREAM'
|
|
751
712
|
|
|
752
713
|
const newQuery = _newQuery(query, _event, model, service)
|
|
753
714
|
|
|
@@ -36,9 +36,13 @@ const _resolveTarget = (ref, target) => {
|
|
|
36
36
|
// in case there is an alias, try with the next entry
|
|
37
37
|
return _resolveTarget(ref.slice(1), target)
|
|
38
38
|
}
|
|
39
|
-
} else {
|
|
40
|
-
return target.elements[ref[0].id || ref[0]]._target
|
|
41
39
|
}
|
|
40
|
+
|
|
41
|
+
const _ref = ref[0].id || ref[0]
|
|
42
|
+
const element = target.elements[_ref]
|
|
43
|
+
if (element) return element._target
|
|
44
|
+
|
|
45
|
+
throw cds.error(`Navigation property '${_ref}' is not defined in type '${target.name}'`, { code: 400 })
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
const rewriteExpandAsterisk = (columns, target) => {
|
|
@@ -64,6 +68,8 @@ const rewriteExpandAsterisk = (columns, target) => {
|
|
|
64
68
|
}
|
|
65
69
|
}
|
|
66
70
|
|
|
71
|
+
const _isLargeBinary = (col, target) => target.elements[col.ref[col.ref.length - 1]]?.type === 'cds.LargeBinary'
|
|
72
|
+
|
|
67
73
|
const _rewriteAsterisk = (columns, target, _4db, isRoot) => {
|
|
68
74
|
const asteriskColumnIndex = columns.findIndex(col => isAsteriskColumn(col))
|
|
69
75
|
if (asteriskColumnIndex > -1) {
|
|
@@ -72,7 +78,12 @@ const _rewriteAsterisk = (columns, target, _4db, isRoot) => {
|
|
|
72
78
|
1,
|
|
73
79
|
...getColumns(target, { _4db })
|
|
74
80
|
.map(c => ({ ref: [c.name] }))
|
|
75
|
-
.filter(
|
|
81
|
+
.filter(
|
|
82
|
+
c =>
|
|
83
|
+
!columns.find(isDuplicate(c)) &&
|
|
84
|
+
(!_4db || cds.env.features.stream_compat || !_isLargeBinary(c, target)) &&
|
|
85
|
+
(isRoot || c.ref[0] !== 'DraftAdministrativeData_DraftUUID')
|
|
86
|
+
)
|
|
76
87
|
)
|
|
77
88
|
}
|
|
78
89
|
}
|
|
@@ -139,7 +150,9 @@ const rewriteAsterisks = (query, model, options) => {
|
|
|
139
150
|
const target = _targetOfQueryIfNotDraft(query, model)
|
|
140
151
|
if (!target) return
|
|
141
152
|
|
|
142
|
-
query.SELECT.columns = getColumns(target, { _4db })
|
|
153
|
+
query.SELECT.columns = getColumns(target, { _4db })
|
|
154
|
+
.map(col => ({ ref: [col.name] }))
|
|
155
|
+
.filter(col => !_4db || cds.env.features.stream_compat || !_isLargeBinary(col, target))
|
|
143
156
|
if (_4db && target._isDraftEnabled && !cds.env.fiori.lean_draft)
|
|
144
157
|
query.SELECT.columns.push(..._cqlDraftColumns(target))
|
|
145
158
|
}
|
|
@@ -12,9 +12,7 @@ const { isPathToDraft } = require('./cqn')
|
|
|
12
12
|
// eslint-disable-next-line complexity
|
|
13
13
|
const _getStreamProperties = (req, query, model) => {
|
|
14
14
|
// new odata parser sets streaming property in SELECT.from
|
|
15
|
-
const ref = query.SELECT
|
|
16
|
-
? (query.SELECT.columns && query.SELECT.columns[0].ref) || query.SELECT.from.ref
|
|
17
|
-
: [query.STREAM.column]
|
|
15
|
+
const ref = (query.SELECT.columns && query.SELECT.columns[0].ref) || query.SELECT.from.ref
|
|
18
16
|
const propertyName = ref[ref.length - 1]
|
|
19
17
|
let mediaTypeProperty
|
|
20
18
|
for (let key in req.target.elements) {
|
|
@@ -25,10 +23,13 @@ const _getStreamProperties = (req, query, model) => {
|
|
|
25
23
|
}
|
|
26
24
|
}
|
|
27
25
|
|
|
26
|
+
// REVISIT: workaround for read after write
|
|
27
|
+
// if (!mediaTypeProperty) return {}
|
|
28
|
+
|
|
28
29
|
let contentType, contentDispositionFilename
|
|
29
30
|
const columns = []
|
|
30
31
|
if (typeof mediaTypeProperty['@Core.MediaType'] === 'object') {
|
|
31
|
-
let contentTypeProperty = mediaTypeProperty['@Core.MediaType']['=']
|
|
32
|
+
let contentTypeProperty = mediaTypeProperty['@Core.MediaType']['='].replaceAll(/\./g, '_')
|
|
32
33
|
if (!req.target.elements[contentTypeProperty]) {
|
|
33
34
|
LOG._warn &&
|
|
34
35
|
LOG.warn(
|
|
@@ -48,7 +49,10 @@ const _getStreamProperties = (req, query, model) => {
|
|
|
48
49
|
}
|
|
49
50
|
if (mediaTypeProperty['@Core.ContentDisposition.Filename']) {
|
|
50
51
|
if (typeof mediaTypeProperty['@Core.ContentDisposition.Filename'] === 'object') {
|
|
51
|
-
let contentDispositionProperty = mediaTypeProperty['@Core.ContentDisposition.Filename']['=']
|
|
52
|
+
let contentDispositionProperty = mediaTypeProperty['@Core.ContentDisposition.Filename']['='].replaceAll(
|
|
53
|
+
/\./g,
|
|
54
|
+
'_'
|
|
55
|
+
)
|
|
52
56
|
if (!req.target.elements[contentDispositionProperty]) {
|
|
53
57
|
LOG._warn &&
|
|
54
58
|
LOG.warn(
|
|
@@ -72,12 +76,11 @@ const _getStreamProperties = (req, query, model) => {
|
|
|
72
76
|
|
|
73
77
|
if (columns.length && cds.db && !req.target._hasPersistenceSkip) {
|
|
74
78
|
// used cloned path
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (
|
|
80
|
-
if (!(isNewStream() || cds.env.fiori.lean_draft)) {
|
|
79
|
+
let select = SELECT.one
|
|
80
|
+
.from({ ref: deepCopyArray(query.SELECT.from.ref), as: query.SELECT.from.as })
|
|
81
|
+
.columns(columns)
|
|
82
|
+
if (query.SELECT.where?.length) select.SELECT.where = query.SELECT.where
|
|
83
|
+
if (!cds.env.fiori.lean_draft) {
|
|
81
84
|
const draft = req.target._isDraftEnabled && isPathToDraft(select.SELECT.from.ref, model)
|
|
82
85
|
if (draft) {
|
|
83
86
|
select = cqn2cqn4sql(select, model)
|
|
@@ -99,7 +102,6 @@ const _getStreamProperties = (req, query, model) => {
|
|
|
99
102
|
}
|
|
100
103
|
|
|
101
104
|
const enhanceStreamResult = async (req, query, result, model) => {
|
|
102
|
-
if (!result) return
|
|
103
105
|
if (result.$mediaContentType || result.$mediaContentDispositionFilename || result.$mediaContentDispositionType) return
|
|
104
106
|
|
|
105
107
|
const { contentType, contentDispositionFilename, contentDispositionType } = await _getStreamProperties(
|
|
@@ -124,6 +126,7 @@ const processFn = ({ row, key }) => {
|
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
function transformRedirectProperties(req, service, result) {
|
|
129
|
+
if (!result) return
|
|
127
130
|
if (!Array.isArray(result)) result = [result]
|
|
128
131
|
if (result.length === 0) return
|
|
129
132
|
|
|
@@ -135,6 +138,4 @@ function transformRedirectProperties(req, service, result) {
|
|
|
135
138
|
}
|
|
136
139
|
}
|
|
137
140
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
module.exports = { isNewStream, enhanceStreamResult, transformRedirectProperties }
|
|
141
|
+
module.exports = { enhanceStreamResult, transformRedirectProperties }
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
const cds = require('../../cds')
|
|
1
2
|
const { ensureNoDraftsSuffix, ensureUnlocalized } = require('../../fiori/utils/handler')
|
|
2
3
|
const { isDuplicate } = require('./rewriteAsterisks')
|
|
3
4
|
|
|
4
5
|
const _addColumn = (name, type, columns) => {
|
|
5
6
|
if (typeof type === 'object') {
|
|
7
|
+
let mType = type['='].replaceAll(/\./g, '_')
|
|
6
8
|
const ref = {
|
|
7
|
-
ref: [
|
|
9
|
+
ref: [mType],
|
|
8
10
|
as: `${name}@odata.mediaContentType`
|
|
9
11
|
}
|
|
10
12
|
if (!columns.find(isDuplicate(ref))) columns.push(ref)
|
|
@@ -14,39 +16,40 @@ const _addColumn = (name, type, columns) => {
|
|
|
14
16
|
}
|
|
15
17
|
}
|
|
16
18
|
|
|
17
|
-
const
|
|
19
|
+
const _addColumns = (target, columns) => {
|
|
20
|
+
for (const k in target.elements) {
|
|
21
|
+
const el = target.elements[k]
|
|
22
|
+
if (el['@Core.MediaType']) {
|
|
23
|
+
_addColumn(el.name, el['@Core.MediaType'], columns)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const handleStreamProperties = (target, columns, model) => {
|
|
29
|
+
if (!target || !model || !columns) return
|
|
30
|
+
|
|
18
31
|
let index = columns.length
|
|
19
32
|
while (index--) {
|
|
20
33
|
const col = columns[index]
|
|
21
34
|
const name = col.ref && col.ref[col.ref.length - 1]
|
|
22
35
|
const element = name && target.elements[name]
|
|
23
|
-
const type = element && element
|
|
36
|
+
const type = element && element.type
|
|
37
|
+
const mediaType = element && element['@Core.MediaType']
|
|
24
38
|
|
|
25
39
|
if (col === '*') {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
40
|
+
_addColumns(target, columns)
|
|
41
|
+
} else if (col.ref && type === 'cds.LargeBinary') {
|
|
42
|
+
if (mediaType) {
|
|
43
|
+
_addColumn(name, mediaType, columns)
|
|
44
|
+
}
|
|
45
|
+
if ((mediaType || !cds.env.features.stream_compat) && !element['@Core.IsURL']) {
|
|
46
|
+
columns.splice(index, 1)
|
|
32
47
|
}
|
|
33
|
-
} else if (col.ref && type) {
|
|
34
|
-
_addColumn(name, type, columns)
|
|
35
|
-
if (!element['@Core.IsURL']) columns.splice(index, 1)
|
|
36
48
|
} else if (col.expand && col.ref) {
|
|
37
49
|
const tgt = target.elements[col.ref] && target.elements[col.ref].target
|
|
38
|
-
tgt &&
|
|
50
|
+
tgt && handleStreamProperties(model.definitions[ensureUnlocalized(ensureNoDraftsSuffix(tgt))], col.expand, model)
|
|
39
51
|
}
|
|
40
52
|
}
|
|
41
53
|
}
|
|
42
54
|
|
|
43
|
-
const handleStreamProperties = (target, select, model, _4odata) => {
|
|
44
|
-
const columns = select.SELECT?.columns
|
|
45
|
-
if (!columns || !target || !model) return
|
|
46
|
-
if (!_4odata && !select.SELECT._4odata) return
|
|
47
|
-
if (select._streaming) return
|
|
48
|
-
|
|
49
|
-
_changeStreamProperties(target, columns, model)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
55
|
module.exports = { handleStreamProperties }
|
|
@@ -73,22 +73,42 @@ class DatabaseService extends cds.Service {
|
|
|
73
73
|
*/
|
|
74
74
|
_runStream(streamQuery, result) {
|
|
75
75
|
this.run(streamQuery).then(stream => {
|
|
76
|
-
if (
|
|
77
|
-
|
|
76
|
+
if (cds.env.features.stream_compat) {
|
|
77
|
+
if (!stream || !stream.value) {
|
|
78
|
+
result.push(null)
|
|
79
|
+
} else {
|
|
80
|
+
stream.value.pipe(result)
|
|
81
|
+
}
|
|
78
82
|
} else {
|
|
79
|
-
|
|
83
|
+
const col = streamQuery.SELECT.columns[0].ref[0]
|
|
84
|
+
if (!stream || !stream[col]) {
|
|
85
|
+
result.push(null)
|
|
86
|
+
} else {
|
|
87
|
+
stream[col].pipe(result)
|
|
88
|
+
}
|
|
80
89
|
}
|
|
81
90
|
})
|
|
82
91
|
}
|
|
83
92
|
|
|
84
93
|
stream(query) {
|
|
94
|
+
cds._logDeprecation('stream method is deprecated and will be removed in upcoming releases!')
|
|
85
95
|
// aynchronous API: cds.stream(query)
|
|
86
96
|
if (typeof query === 'object') {
|
|
87
97
|
// eslint-disable-next-line no-async-promise-executor
|
|
88
98
|
return new Promise(async (resolve, reject) => {
|
|
89
99
|
try {
|
|
90
|
-
|
|
91
|
-
|
|
100
|
+
if (cds.env.features.stream_compat) {
|
|
101
|
+
const res = await this.run(Object.assign(query, { _streaming: true }))
|
|
102
|
+
resolve(res && res.value)
|
|
103
|
+
} else {
|
|
104
|
+
const res = await this.run(query)
|
|
105
|
+
// rely on query returning correct column (as documented for srv.stream(query))
|
|
106
|
+
if (Array.isArray(res)) {
|
|
107
|
+
resolve(res.length ? Object.values(res[0])[0] : undefined)
|
|
108
|
+
} else {
|
|
109
|
+
resolve(res && Object.values(res)[0])
|
|
110
|
+
}
|
|
111
|
+
}
|
|
92
112
|
} catch (e) {
|
|
93
113
|
reject(e)
|
|
94
114
|
}
|
|
@@ -98,13 +118,12 @@ class DatabaseService extends cds.Service {
|
|
|
98
118
|
// synchronous API: cds.stream('column').from(entity).where(...)
|
|
99
119
|
return {
|
|
100
120
|
from: (...args) => {
|
|
101
|
-
const streamQuery = SELECT.from(...args)
|
|
121
|
+
const streamQuery = SELECT.one.from(...args)
|
|
102
122
|
if (query && (!streamQuery.SELECT.columns || streamQuery.SELECT.columns.length !== 0)) {
|
|
103
123
|
streamQuery.columns([query])
|
|
104
124
|
}
|
|
105
125
|
|
|
106
|
-
|
|
107
|
-
streamQuery._streaming = true
|
|
126
|
+
if (cds.env.features.stream_compat) streamQuery._streaming = true
|
|
108
127
|
|
|
109
128
|
const result = new Transform({
|
|
110
129
|
transform(chunk, encoding, callback) {
|
|
@@ -102,7 +102,12 @@ const _processCategory = (req, category, { row, key, element }) => {
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
// generate UUIDs
|
|
105
|
-
if (
|
|
105
|
+
if (
|
|
106
|
+
category === 'uuid' &&
|
|
107
|
+
!val &&
|
|
108
|
+
req.event === 'CREATE' &&
|
|
109
|
+
!element.parent.elements[element._foreignKey4]?._isAssociationStrict
|
|
110
|
+
) {
|
|
106
111
|
row[key] = cds.utils.uuid()
|
|
107
112
|
}
|
|
108
113
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const cds = require('../../cds')
|
|
1
2
|
const { cqn2cqn4sql } = require('../../common/utils/cqn2cqn4sql')
|
|
2
3
|
const { generateAliases } = require('../utils/generateAliases')
|
|
3
4
|
const { restoreLink } = require('../../common/utils/resolveView')
|
|
@@ -25,7 +26,7 @@ function handler(req) {
|
|
|
25
26
|
return
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
const streaming = req.query._streaming
|
|
29
|
+
const streaming = cds.env.features.stream_compat && req.query._streaming
|
|
29
30
|
|
|
30
31
|
// for restore link to req.data
|
|
31
32
|
const linked = _isLinked(req)
|
|
@@ -37,7 +38,7 @@ function handler(req) {
|
|
|
37
38
|
// restore link to req.data
|
|
38
39
|
if (linked) restoreLink(req)
|
|
39
40
|
|
|
40
|
-
if (streaming) req.query._streaming = streaming
|
|
41
|
+
if (cds.env.features.stream_compat && streaming) req.query._streaming = streaming
|
|
41
42
|
generateAliases(req.query)
|
|
42
43
|
}
|
|
43
44
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const cds = require('../../cds')
|
|
1
2
|
const normalizeTimestamp = require('../../common/utils/normalizeTimestamp')
|
|
2
3
|
const { deepCopyObject } = require('../../common/utils/copy')
|
|
3
4
|
const getError = require('../../common/error')
|
|
@@ -37,16 +38,21 @@ const countValue = countResults => {
|
|
|
37
38
|
if (countResult.$count != null) return countResult.$count
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
const read = (executeSelectCQN, executeStreamCQN) => (model, dbc, query, req) => {
|
|
41
|
+
const read = (executeSelectCQN, executeStreamCQN, convertStreams) => (model, dbc, query, req) => {
|
|
41
42
|
const { user, locale, timestamp } = req
|
|
42
43
|
const isoTs = normalizeTimestamp(timestamp)
|
|
43
44
|
|
|
44
|
-
if (query._streaming) {
|
|
45
|
-
if (!query.SELECT ||
|
|
45
|
+
if (cds.env.features.stream_compat && query._streaming) {
|
|
46
|
+
if (!query.SELECT || !query.SELECT.columns) {
|
|
46
47
|
throw getError(500, 'Invalid SELECT statement for streaming')
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
return executeStreamCQN(model, dbc, query, user, locale, isoTs)
|
|
50
|
+
return executeStreamCQN({ model, dbc, query, user, locale, isoTs })
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// REVISIT: streaming from OData - to be generalized to all streaming properties
|
|
54
|
+
if (query.SELECT?.columns?.find(col => col.as === '$mediaContentType')) {
|
|
55
|
+
return executeStreamCQN({ model, dbc, query, user, locale, isoTs })
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
// needed in case of expand
|
|
@@ -72,7 +78,11 @@ const read = (executeSelectCQN, executeStreamCQN) => (model, dbc, query, req) =>
|
|
|
72
78
|
)
|
|
73
79
|
}
|
|
74
80
|
|
|
75
|
-
return executeSelectCQN(model, dbc, query, user, locale, isoTs)
|
|
81
|
+
return executeSelectCQN(model, dbc, query, user, locale, isoTs).then(result => {
|
|
82
|
+
if (!cds.env.features.stream_compat) convertStreams(query.SELECT.columns, query.target, result, query.SELECT.one)
|
|
83
|
+
|
|
84
|
+
return result
|
|
85
|
+
})
|
|
76
86
|
}
|
|
77
87
|
|
|
78
88
|
module.exports = read
|
|
@@ -1,17 +1,6 @@
|
|
|
1
|
-
const cds = require('../../cds')
|
|
2
|
-
|
|
3
1
|
const BaseBuilder = require('./BaseBuilder')
|
|
4
2
|
const { flattenStructuredWhereHaving } = require('../../common/utils/structured')
|
|
5
3
|
|
|
6
|
-
const SQLITE_DATETIME_FUNCTIONS = new Set(['year', 'month', 'day', 'second', 'hour', 'minute'])
|
|
7
|
-
const HANA_DATETIME_FUNCTIONS = new Set(['year', 'month', 'dayofmonth', 'second', 'hour', 'minute'])
|
|
8
|
-
const OPERATORS = new Set(['=', '!=', '<>', '<', '>', '<=', '>='])
|
|
9
|
-
|
|
10
|
-
function _fillAfterDot(val) {
|
|
11
|
-
const [beforeDot, afterDot = ''] = val.split('.')
|
|
12
|
-
return `${beforeDot}.${afterDot.padEnd(3, '0')}`
|
|
13
|
-
}
|
|
14
|
-
|
|
15
4
|
function _valButNoBuffer(arg) {
|
|
16
5
|
return arg && arg.val && typeof arg.val === 'object' && !(arg.val instanceof Buffer)
|
|
17
6
|
}
|
|
@@ -21,6 +21,7 @@ const getColumns = (entity, { _4db, onlyKeys } = { _4db: true, onlyKeys: false }
|
|
|
21
21
|
const elements = lean_draft ? entity.elements : Object.getPrototypeOf(entity.elements) || entity.elements
|
|
22
22
|
for (const elementName in elements) {
|
|
23
23
|
const element = elements[elementName]
|
|
24
|
+
if (element['@cds.api.ignore']) continue
|
|
24
25
|
if (onlyKeys && !element.key) continue
|
|
25
26
|
if (element.isAssociation) continue
|
|
26
27
|
if (!lean_draft && _4db && entity._isDraftEnabled && elementName in DRAFT_COLUMNS_MAP) continue
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const { Readable } = require('stream')
|
|
2
|
+
|
|
3
|
+
const _stream = val => {
|
|
4
|
+
if (val === null) return null
|
|
5
|
+
return new Readable({
|
|
6
|
+
read(size) {
|
|
7
|
+
if (val.length === 0) return this.push(null)
|
|
8
|
+
const chunk = val.slice(0, size)
|
|
9
|
+
val = val.slice(size)
|
|
10
|
+
this.push(chunk)
|
|
11
|
+
}
|
|
12
|
+
})
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const convertStream = (columns, target, result, one) => {
|
|
16
|
+
if (!result || !columns || !target) return
|
|
17
|
+
if (!Array.isArray(result)) result = [result]
|
|
18
|
+
if (!result.length) return
|
|
19
|
+
|
|
20
|
+
for (let col of columns) {
|
|
21
|
+
let name = col.ref?.[col.ref.length - 1] || (typeof col === 'string' && col)
|
|
22
|
+
const element = target.elements?.[name]
|
|
23
|
+
if (!element) continue
|
|
24
|
+
name = col.as || name
|
|
25
|
+
if (element.isAssociation) {
|
|
26
|
+
if (one) convertStream(col.expand, element._target, result[0][name], false)
|
|
27
|
+
else
|
|
28
|
+
result.forEach(row => {
|
|
29
|
+
convertStream(col.expand, element._target, row[name], false)
|
|
30
|
+
})
|
|
31
|
+
} else if (element.type === 'cds.LargeBinary') {
|
|
32
|
+
if (one) result[0][name] = _stream(result[0][name])
|
|
33
|
+
else
|
|
34
|
+
result.forEach(row => {
|
|
35
|
+
row[name] = _stream(row[name])
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = { convertStream }
|
|
@@ -1187,6 +1187,7 @@ const _getOriginalColumns = req => {
|
|
|
1187
1187
|
return originalColumns
|
|
1188
1188
|
}
|
|
1189
1189
|
|
|
1190
|
+
// REVISIT: remove after stream_compat is removed
|
|
1190
1191
|
const _handlerStreaming = async (req, query) => {
|
|
1191
1192
|
adaptStreamCQN(query)
|
|
1192
1193
|
query._streaming = true
|
|
@@ -1289,7 +1290,7 @@ const fioriGenericRead = async function (req, next) {
|
|
|
1289
1290
|
// Clone draft restrictions to the cloned query.
|
|
1290
1291
|
reqClone.query._draftRestrictions = query._draftRestrictions
|
|
1291
1292
|
|
|
1292
|
-
if (query._streaming) return _handlerStreaming(req, reqClone.query)
|
|
1293
|
+
if (cds.env.features.stream_compat && query._streaming) return _handlerStreaming(req, reqClone.query)
|
|
1293
1294
|
|
|
1294
1295
|
let cqnScenario
|
|
1295
1296
|
|
|
@@ -120,7 +120,7 @@ const _readOverDraftHandler = async function (req, next) {
|
|
|
120
120
|
// REVISIT DRAFT HANDLING: cqn2cqn4sql must not be called here
|
|
121
121
|
const sqlQuery = cqn2cqn4sql(req.query, this.model, { _4db: req.target._isDraftEnabled, _4fiori: true })
|
|
122
122
|
|
|
123
|
-
if (req.query._streaming) {
|
|
123
|
+
if (cds.env.features.stream_compat && req.query._streaming) {
|
|
124
124
|
sqlQuery._streaming = true
|
|
125
125
|
}
|
|
126
126
|
|