@sap/cds 7.5.3 → 7.6.2
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 -21
- package/app/index.js +6 -17
- 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/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/index.js +40 -47
- package/lib/log/cds-error.js +6 -0
- package/lib/log/format/aspects/als.js +1 -0
- package/lib/log/format/json.js +5 -1
- 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/cds-context.js +1 -1
- 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-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/batch/BatchProcessor.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/http/HttpHeaderReader.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/TrustedResourceJsonSerializer.js +6 -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/i18n/messages.properties +3 -0
- 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 -35
- 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 +209 -55
- package/libx/_runtime/hana/Service.js +1 -1
- package/libx/_runtime/hana/execute.js +53 -14
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +2 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +34 -15
- package/libx/_runtime/messaging/file-based.js +4 -3
- package/libx/_runtime/messaging/redis-messaging.js +2 -1
- 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 +74 -89
- 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
|
|
@@ -234,7 +234,7 @@ const _newInsertColumns = (columns = [], transition) => {
|
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
// REVISIT: this hard-coding on ref indexes does not support path expressions
|
|
237
|
-
const _newWhereRef = (newWhereElement, transition, alias, tableName
|
|
237
|
+
const _newWhereRef = (newWhereElement, transition, alias, tableName) => {
|
|
238
238
|
const newRef = Array.isArray(newWhereElement.ref) ? [...newWhereElement.ref] : [newWhereElement.ref]
|
|
239
239
|
|
|
240
240
|
if (newRef.length > 1 && newRef[0] === alias) {
|
|
@@ -244,15 +244,9 @@ const _newWhereRef = (newWhereElement, transition, alias, tableName, isSubSelect
|
|
|
244
244
|
newRef[0] = transition.target.name
|
|
245
245
|
const mapped = transition.mapping.get(newRef[1])
|
|
246
246
|
if (mapped) newRef[1] = mapped.ref.join('_')
|
|
247
|
-
} else {
|
|
247
|
+
} else if (newRef.length === 1) {
|
|
248
248
|
const mapped = transition.mapping.get(newRef[0])
|
|
249
|
-
if (
|
|
250
|
-
// Add a table alias prefix only for not-yet-qualified refs
|
|
251
|
-
newRef.unshift(transition.target.name)
|
|
252
|
-
newRef[1] = mapped.ref[0]
|
|
253
|
-
} else {
|
|
254
|
-
if (mapped) newRef[0] = mapped.ref[0]
|
|
255
|
-
}
|
|
249
|
+
if (mapped) newRef[0] = mapped.ref.join('_')
|
|
256
250
|
}
|
|
257
251
|
|
|
258
252
|
newWhereElement.ref = newRef
|
|
@@ -280,7 +274,7 @@ const _newWhere = (where = [], transition, tableName, alias, isSubselect = false
|
|
|
280
274
|
}
|
|
281
275
|
|
|
282
276
|
if (newWhereElement.ref) {
|
|
283
|
-
_newWhereRef(newWhereElement, transition, alias, tableName
|
|
277
|
+
_newWhereRef(newWhereElement, transition, alias, tableName)
|
|
284
278
|
return newWhereElement
|
|
285
279
|
}
|
|
286
280
|
|
|
@@ -410,28 +404,6 @@ const _newSelect = (query, transitions, service) => {
|
|
|
410
404
|
return newSelect
|
|
411
405
|
}
|
|
412
406
|
|
|
413
|
-
const _newStream = (query, transitions) => {
|
|
414
|
-
const targetTransition = transitions[transitions.length - 1]
|
|
415
|
-
const targetName = targetTransition.target.name
|
|
416
|
-
const newStream = Object.create(query.STREAM)
|
|
417
|
-
if (newStream.into) {
|
|
418
|
-
const refObject = newStream.into.ref ? newStream.into : { ref: [query.STREAM.into] }
|
|
419
|
-
newStream.into = {
|
|
420
|
-
...refObject,
|
|
421
|
-
ref: _rewriteQueryPath(refObject, transitions)
|
|
422
|
-
}
|
|
423
|
-
if (!query.STREAM.into.ref) newStream.into = newStream.into.ref[0] // leave as string
|
|
424
|
-
} else {
|
|
425
|
-
newStream.into = targetName
|
|
426
|
-
}
|
|
427
|
-
if (newStream.column) newStream.column = _resolveColumn(newStream.column, targetTransition)
|
|
428
|
-
Object.defineProperty(newStream, '_transitions', {
|
|
429
|
-
enumerable: false,
|
|
430
|
-
value: transitions
|
|
431
|
-
})
|
|
432
|
-
return newStream
|
|
433
|
-
}
|
|
434
|
-
|
|
435
407
|
const _newInsert = (query, transitions, service) => {
|
|
436
408
|
const targetTransition = transitions[transitions.length - 1]
|
|
437
409
|
const targetName = targetTransition.target.name
|
|
@@ -710,8 +682,7 @@ const _newQuery = (query, event, model, service) => {
|
|
|
710
682
|
INSERT: ['into', _newInsert],
|
|
711
683
|
UPSERT: ['into', _newUpsert],
|
|
712
684
|
UPDATE: ['entity', _newUpdate],
|
|
713
|
-
DELETE: ['from', _newDelete]
|
|
714
|
-
STREAM: ['into', _newStream]
|
|
685
|
+
DELETE: ['from', _newDelete]
|
|
715
686
|
}[event]
|
|
716
687
|
const newQuery = Object.create(query)
|
|
717
688
|
const transitions = _entityTransitionsForTarget(query[event][_prop], model, service)
|
|
@@ -732,7 +703,6 @@ const resolveView = (query, model, service) => {
|
|
|
732
703
|
else if (query.UPSERT) _event = 'UPSERT'
|
|
733
704
|
else if (query.UPDATE) _event = 'UPDATE'
|
|
734
705
|
else if (query.DELETE) _event = 'DELETE'
|
|
735
|
-
else if (query.STREAM) _event = 'STREAM'
|
|
736
706
|
|
|
737
707
|
const newQuery = _newQuery(query, _event, model, service)
|
|
738
708
|
|
|
@@ -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
|
|