@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.
Files changed (95) hide show
  1. package/CHANGELOG.md +79 -22
  2. package/app/index.js +1 -1
  3. package/lib/auth/index.js +3 -0
  4. package/lib/compile/extend.js +9 -4
  5. package/lib/compile/for/lean_drafts.js +3 -4
  6. package/lib/compile/load.js +11 -15
  7. package/lib/compile/minify.js +2 -4
  8. package/lib/compile/to/sql.js +6 -4
  9. package/lib/compile/to/srvinfo.js +25 -3
  10. package/lib/compile/to/yaml.js +1 -1
  11. package/lib/dbs/cds-deploy.js +7 -13
  12. package/lib/env/defaults.js +1 -10
  13. package/lib/env/schemas/cds-package.js +27 -0
  14. package/lib/env/schemas/cds-rc.js +693 -0
  15. package/lib/env/schemas/index.js +6 -4
  16. package/lib/i18n/localize.js +15 -1
  17. package/lib/index.js +40 -47
  18. package/lib/log/cds-error.js +6 -0
  19. package/lib/ql/Query.js +2 -1
  20. package/lib/ql/cds-ql.js +1 -2
  21. package/lib/ql/infer.js +0 -2
  22. package/lib/req/request.js +3 -6
  23. package/lib/srv/middlewares/trace.js +2 -2
  24. package/lib/srv/protocols/hcql.js +44 -30
  25. package/lib/srv/protocols/http.js +60 -0
  26. package/lib/srv/protocols/index.js +0 -7
  27. package/lib/srv/protocols/odata-v4.js +8 -2
  28. package/lib/srv/srv-api.js +129 -62
  29. package/lib/srv/srv-handlers.js +0 -1
  30. package/lib/srv/srv-models.js +1 -0
  31. package/lib/utils/cds-test.js +1 -1
  32. package/lib/utils/cds-utils.js +26 -0
  33. package/lib/utils/check-version.js +10 -13
  34. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +22 -6
  35. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +3 -4
  36. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +89 -21
  37. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/boundToCQN.js +4 -2
  38. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +1 -24
  39. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/updateToCQN.js +1 -7
  40. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ApplyParser.js +3 -3
  41. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/TrustedResourceJsonSerializer.js +7 -0
  42. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +0 -5
  43. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +2 -0
  44. package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +17 -1
  45. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +22 -2
  46. package/libx/_runtime/cds-services/services/utils/columns.js +1 -2
  47. package/libx/_runtime/common/aspects/Association.js +17 -9
  48. package/libx/_runtime/common/generic/crud.js +13 -22
  49. package/libx/_runtime/common/generic/etag.js +1 -1
  50. package/libx/_runtime/common/generic/input.js +9 -1
  51. package/libx/_runtime/common/generic/paging.js +3 -3
  52. package/libx/_runtime/common/generic/sorting.js +25 -15
  53. package/libx/_runtime/common/generic/stream.js +2 -16
  54. package/libx/_runtime/common/utils/copy.js +5 -0
  55. package/libx/_runtime/common/utils/cqn.js +1 -1
  56. package/libx/_runtime/common/utils/cqn2cqn4sql.js +4 -3
  57. package/libx/_runtime/common/utils/csn.js +0 -49
  58. package/libx/_runtime/common/utils/foreignKeyPropagations.js +5 -5
  59. package/libx/_runtime/common/utils/generateOnCond.js +50 -25
  60. package/libx/_runtime/common/utils/resolveView.js +5 -44
  61. package/libx/_runtime/common/utils/rewriteAsterisks.js +17 -4
  62. package/libx/_runtime/common/utils/stream.js +16 -15
  63. package/libx/_runtime/common/utils/streamProp.js +25 -22
  64. package/libx/_runtime/db/Service.js +27 -8
  65. package/libx/_runtime/db/generic/input.js +6 -1
  66. package/libx/_runtime/db/generic/rewrite.js +3 -2
  67. package/libx/_runtime/db/query/read.js +15 -5
  68. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +0 -11
  69. package/libx/_runtime/db/utils/columns.js +1 -0
  70. package/libx/_runtime/db/utils/stream.js +41 -0
  71. package/libx/_runtime/fiori/generic/read.js +2 -1
  72. package/libx/_runtime/fiori/generic/readOverDraft.js +1 -1
  73. package/libx/_runtime/fiori/lean-draft.js +216 -59
  74. package/libx/_runtime/hana/Service.js +1 -1
  75. package/libx/_runtime/hana/execute.js +53 -14
  76. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +34 -15
  77. package/libx/_runtime/remote/Service.js +2 -1
  78. package/libx/_runtime/remote/utils/client.js +1 -1
  79. package/libx/_runtime/sqlite/Service.js +1 -1
  80. package/libx/_runtime/sqlite/execute.js +17 -5
  81. package/libx/odata/afterburner.js +58 -19
  82. package/libx/odata/cqn2odata.js +6 -8
  83. package/libx/odata/create.js +44 -0
  84. package/libx/odata/delete.js +25 -0
  85. package/libx/odata/error.js +8 -3
  86. package/libx/odata/metadata.js +6 -8
  87. package/libx/odata/service-document.js +1 -1
  88. package/libx/odata/update.js +110 -0
  89. package/libx/odata/utils.js +9 -6
  90. package/libx/outbox/index.js +48 -78
  91. package/libx/rest/RestAdapter.js +0 -3
  92. package/package.json +1 -1
  93. package/lib/env/schemas/cds-package.json +0 -17
  94. package/lib/env/schemas/cds-rc.json +0 -740
  95. 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 _args = (csnElement, path, aliases) => {
33
- const onCond = csnElement.on
34
- if (!onCond || onCond.length === 0) return []
35
- if (onCond.length < 3 && !onCond[0]?.xpr) return onCond
36
- if (!csnElement._isSelfManaged) return _adaptRefs(onCond, path, aliases)
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
- // revert join and select aliases because of backlink
39
- const mutOnCond = _newOnConditions(csnElement._backlink, [csnElement._backlink.name], {
40
- select: aliases.join,
41
- join: aliases.select
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
- if (onCond.some(e => e === 'and')) {
45
- // managed with ON-conditions must contain `$self`, which we replace with `mutOnCond`
46
- const onCondWithoutSelf = _adaptRefs(_onCondWithout$self(onCond), path, aliases)
47
- mutOnCond.push('and', ...onCondWithoutSelf)
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
- return mutOnCond
74
+ element.ref = _toRef(aliases.join, element.ref.slice(0)).ref
75
+ }
76
+ }
51
77
  }
52
78
 
53
- const _isSelfRef = e => e && e.ref && e.ref[0] === '$self'
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 _onCondWithout$self = onCond => {
56
- const onCondWithoutSelf = [...onCond]
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
- onCondWithoutSelf.splice(selfIndex, 4)
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
- const tableAlias =
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[0]
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(c => !columns.find(isDuplicate(c)) && (isRoot || c.ref[0] !== 'DraftAdministrativeData_DraftUUID'))
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 }).map(col => ({ ref: [col.name] }))
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
- const ref = query.SELECT ? query.SELECT.from.ref : query.STREAM.from.ref
76
- const as = query.SELECT ? query.SELECT.from.as : query.STREAM.from.as
77
- let select = SELECT.one.from({ ref: deepCopyArray(ref), as }).columns(columns)
78
- const where = query.SELECT ? query.SELECT.where : query.STREAM.where
79
- if (where?.length) select.SELECT.where = where
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
- const isNewStream = () => cds.env.features.new_stream && (!cds.db || !!cds.db.stream('foo').STREAM)
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: [type['=']],
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 _changeStreamProperties = (target, columns, model) => {
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['@Core.MediaType']
36
+ const type = element && element.type
37
+ const mediaType = element && element['@Core.MediaType']
24
38
 
25
39
  if (col === '*') {
26
- for (const k in target.elements) {
27
- const el = target.elements[k]
28
- if (el['@Core.MediaType']) {
29
- const type = el['@Core.MediaType']
30
- _addColumn(el.name, type, columns)
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 && _changeStreamProperties(model.definitions[ensureUnlocalized(ensureNoDraftsSuffix(tgt))], col.expand, model)
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 (!stream) {
77
- result.push(null)
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
- stream.value.pipe(result)
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
- const res = await this.run(Object.assign(query, { _streaming: true }))
91
- resolve((res && res.value) || res)
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
- delete streamQuery.SELECT.one
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 (category === 'uuid' && !val && req.event === 'CREATE') {
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 || (query.SELECT && !query.SELECT.columns)) {
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