@sap/cds 5.5.5 → 5.6.3

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 (207) hide show
  1. package/CHANGELOG.md +139 -1
  2. package/apis/services.d.ts +31 -1
  3. package/app/index.js +22 -11
  4. package/bin/build/buildTaskFactory.js +1 -1
  5. package/bin/build/provider/buildTaskProviderInternal.js +1 -1
  6. package/bin/build/provider/fiori/index.js +1 -1
  7. package/bin/build/provider/hana/2migration.js +8 -7
  8. package/bin/build/provider/java-cf/index.js +1 -1
  9. package/bin/deploy/to-hana/hana.js +1 -17
  10. package/common.cds +8 -0
  11. package/lib/compile/to/sql.js +22 -2
  12. package/lib/connect/bindings.js +2 -1
  13. package/lib/core/reflect.js +4 -1
  14. package/lib/env/index.js +180 -42
  15. package/lib/env/requires.js +16 -1
  16. package/lib/i18n/localize.js +33 -5
  17. package/lib/index.js +3 -3
  18. package/lib/log/format/kibana.js +6 -2
  19. package/lib/ql/Query.js +1 -0
  20. package/lib/ql/SELECT.js +15 -8
  21. package/lib/ql/Whereable.js +5 -0
  22. package/lib/req/context.js +13 -5
  23. package/lib/serve/Service-dispatch.js +8 -1
  24. package/lib/utils/axios.js +7 -0
  25. package/lib/utils/data.js +1 -1
  26. package/lib/utils/tests.js +1 -1
  27. package/libx/_runtime/audit/Service.js +18 -18
  28. package/libx/_runtime/audit/generic/personal/access.js +1 -1
  29. package/libx/_runtime/audit/generic/personal/modification.js +3 -2
  30. package/libx/_runtime/audit/generic/personal/utils.js +23 -63
  31. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +4 -0
  32. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +37 -35
  33. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +3 -1
  34. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +5 -5
  35. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -7
  36. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +84 -34
  37. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +12 -4
  38. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +9 -3
  39. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +8 -6
  40. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -3
  41. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +13 -11
  42. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +11 -95
  43. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +17 -11
  44. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +2 -1
  45. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/SetResponseHeadersCommand.js +1 -0
  46. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +6 -2
  47. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +3 -34
  48. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -3
  49. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +64 -31
  50. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +10 -5
  51. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +1 -1
  52. package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +1 -1
  53. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +1 -3
  54. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +20 -21
  55. package/libx/_runtime/cds-services/services/utils/columns.js +6 -1
  56. package/libx/_runtime/cds-services/services/utils/compareJson.js +1 -8
  57. package/libx/_runtime/cds-services/services/utils/differ.js +7 -26
  58. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -4
  59. package/libx/_runtime/cds-services/util/assert.js +29 -13
  60. package/libx/_runtime/cds.js +2 -1
  61. package/libx/_runtime/common/aspects/Association.js +72 -0
  62. package/libx/_runtime/common/aspects/any.js +8 -45
  63. package/libx/_runtime/common/aspects/entity.js +0 -1
  64. package/libx/_runtime/common/aspects/relation.js +40 -0
  65. package/libx/_runtime/common/aspects/utils.js +73 -1
  66. package/libx/_runtime/common/auth/strategies/utils/uaa.js +10 -14
  67. package/libx/_runtime/common/composition/data.js +3 -2
  68. package/libx/_runtime/common/composition/delete.js +3 -1
  69. package/libx/_runtime/common/composition/tree.js +23 -18
  70. package/libx/_runtime/common/composition/update.js +9 -1
  71. package/libx/_runtime/common/composition/utils.js +34 -8
  72. package/libx/_runtime/common/error/frontend.js +6 -1
  73. package/libx/_runtime/common/generic/auth.js +5 -9
  74. package/libx/_runtime/common/generic/crud.js +2 -2
  75. package/libx/_runtime/common/generic/etag.js +11 -8
  76. package/libx/_runtime/common/generic/input.js +3 -3
  77. package/libx/_runtime/common/generic/paging.js +9 -5
  78. package/libx/_runtime/common/generic/put.js +3 -2
  79. package/libx/_runtime/common/generic/sorting.js +3 -3
  80. package/libx/_runtime/common/generic/temporal.js +3 -3
  81. package/libx/_runtime/common/utils/cqn.js +20 -1
  82. package/libx/_runtime/common/utils/cqn2cqn4sql.js +125 -139
  83. package/libx/_runtime/common/utils/csn.js +50 -52
  84. package/libx/_runtime/common/utils/foreignKeyPropagations.js +41 -176
  85. package/libx/_runtime/common/utils/generateOnCond.js +40 -70
  86. package/libx/_runtime/common/utils/{enrichWithKeysFromWhere.js → keys.js} +29 -28
  87. package/libx/_runtime/common/utils/postProcessing.js +3 -0
  88. package/libx/_runtime/common/utils/propagateForeignKeys.js +84 -0
  89. package/libx/_runtime/common/utils/resolveStructured.js +1 -1
  90. package/libx/_runtime/common/utils/resolveView.js +7 -5
  91. package/libx/_runtime/common/utils/rewriteAsterisks.js +94 -0
  92. package/libx/_runtime/common/utils/search2cqn4sql.js +9 -8
  93. package/libx/_runtime/common/utils/template.js +54 -46
  94. package/libx/_runtime/db/Service.js +9 -2
  95. package/libx/_runtime/db/expand/expandCQNToJoin.js +54 -33
  96. package/libx/_runtime/db/expand/rawToExpanded.js +2 -1
  97. package/libx/_runtime/db/generic/arrayed.js +13 -28
  98. package/libx/_runtime/db/generic/create.js +1 -0
  99. package/libx/_runtime/db/generic/input.js +7 -11
  100. package/libx/_runtime/db/generic/integrity.js +2 -2
  101. package/libx/_runtime/db/generic/rewrite.js +2 -5
  102. package/libx/_runtime/db/generic/update.js +1 -0
  103. package/libx/_runtime/db/query/read.js +9 -4
  104. package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -2
  105. package/libx/_runtime/db/sql-builder/annotations.js +1 -0
  106. package/libx/_runtime/db/utils/columns.js +14 -43
  107. package/libx/_runtime/fiori/generic/activate.js +3 -2
  108. package/libx/_runtime/fiori/generic/before.js +2 -2
  109. package/libx/_runtime/fiori/generic/cancel.js +3 -2
  110. package/libx/_runtime/fiori/generic/delete.js +3 -2
  111. package/libx/_runtime/fiori/generic/edit.js +3 -3
  112. package/libx/_runtime/fiori/generic/new.js +2 -2
  113. package/libx/_runtime/fiori/generic/patch.js +2 -2
  114. package/libx/_runtime/fiori/generic/prepare.js +2 -2
  115. package/libx/_runtime/fiori/generic/read.js +45 -63
  116. package/libx/_runtime/fiori/generic/readOverDraft.js +4 -4
  117. package/libx/_runtime/fiori/uiflex/extensibility/index.cds +15 -0
  118. package/libx/_runtime/fiori/uiflex/extensibility/index.js +148 -0
  119. package/libx/_runtime/fiori/uiflex/handler/transformREAD.js +119 -0
  120. package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +43 -0
  121. package/libx/_runtime/fiori/uiflex/handler/transformWRITE.js +62 -0
  122. package/libx/_runtime/fiori/uiflex/index.js +35 -0
  123. package/libx/_runtime/fiori/uiflex/utils.js +78 -0
  124. package/libx/_runtime/fiori/utils/handler.js +3 -13
  125. package/libx/_runtime/fiori/utils/where.js +6 -1
  126. package/libx/_runtime/hana/pool.js +12 -11
  127. package/libx/_runtime/hana/search2cqn4sql.js +34 -43
  128. package/libx/_runtime/hana/searchToContains.js +3 -3
  129. package/libx/_runtime/index.js +5 -2
  130. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  131. package/libx/_runtime/messaging/common-utils/AMQPClient.js +16 -3
  132. package/libx/_runtime/messaging/common-utils/connections.js +11 -14
  133. package/libx/_runtime/messaging/common-utils/naming-conventions.js +1 -1
  134. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -1
  135. package/libx/_runtime/messaging/message-queuing.js +18 -0
  136. package/libx/_runtime/remote/Service.js +20 -4
  137. package/libx/_runtime/remote/utils/client-types.d.ts +7 -0
  138. package/libx/_runtime/remote/utils/client.js +117 -23
  139. package/libx/_runtime/sqlite/Service.js +2 -2
  140. package/libx/_runtime/sqlite/convertAssocToOneManaged.js +1 -3
  141. package/libx/gql/GraphQLAdapter.js +33 -0
  142. package/libx/gql/constants/adapter.js +69 -0
  143. package/libx/gql/constants/cds.js +18 -0
  144. package/libx/gql/constants/graphql.js +33 -0
  145. package/libx/gql/resolvers/crud/create.js +15 -0
  146. package/libx/gql/resolvers/crud/delete.js +24 -0
  147. package/libx/gql/resolvers/crud/index.js +6 -0
  148. package/libx/gql/resolvers/crud/read.js +25 -0
  149. package/libx/gql/resolvers/crud/update.js +31 -0
  150. package/libx/gql/resolvers/crud/utils/index.js +36 -0
  151. package/libx/gql/resolvers/field.js +5 -0
  152. package/libx/gql/resolvers/index.js +7 -0
  153. package/libx/gql/resolvers/mutation.js +23 -0
  154. package/libx/gql/resolvers/parse/ast/enrich.js +51 -0
  155. package/libx/gql/resolvers/parse/ast/fragment.js +11 -0
  156. package/libx/gql/resolvers/parse/ast/fromObject.js +39 -0
  157. package/libx/gql/resolvers/parse/ast/index.js +3 -0
  158. package/libx/gql/resolvers/parse/ast/meta.js +4 -0
  159. package/libx/gql/resolvers/parse/ast/variable.js +7 -0
  160. package/libx/gql/resolvers/parse/ast2cqn/columns.js +42 -0
  161. package/libx/gql/resolvers/parse/ast2cqn/entries.js +31 -0
  162. package/libx/gql/resolvers/parse/ast2cqn/index.js +8 -0
  163. package/libx/gql/resolvers/parse/ast2cqn/limit.js +6 -0
  164. package/libx/gql/resolvers/parse/ast2cqn/orderBy.js +24 -0
  165. package/libx/gql/resolvers/parse/ast2cqn/utils/index.js +3 -0
  166. package/libx/gql/resolvers/parse/ast2cqn/where.js +70 -0
  167. package/libx/gql/resolvers/parse/utils/index.js +8 -0
  168. package/libx/gql/resolvers/query.js +13 -0
  169. package/libx/gql/resolvers/root.js +34 -0
  170. package/libx/gql/schema/generate.js +18 -0
  171. package/libx/gql/schema/index.js +5 -0
  172. package/libx/gql/schema/mutation.js +76 -0
  173. package/libx/gql/schema/query.js +108 -0
  174. package/libx/gql/schema/typeDefMap.js +45 -0
  175. package/libx/gql/schema/utils/index.js +54 -0
  176. package/libx/gql/utils/index.js +12 -0
  177. package/libx/{_runtime/odata/cqn2odata.js → odata/cqn2odata/index.js} +39 -100
  178. package/libx/odata/index.js +80 -0
  179. package/libx/odata/odata2cqn/afterburner.js +170 -0
  180. package/libx/{_runtime/odata/odata2cqn.pegjs → odata/odata2cqn/grammar.pegjs} +102 -123
  181. package/libx/odata/odata2cqn/index.js +3 -0
  182. package/libx/odata/odata2cqn/parser.js +1 -0
  183. package/libx/odata/utils/index.js +64 -0
  184. package/libx/rest/RestAdapter.js +101 -0
  185. package/libx/rest/RestRequest.js +30 -0
  186. package/libx/rest/index.js +3 -0
  187. package/libx/rest/middleware/auth.js +22 -0
  188. package/libx/rest/middleware/content.js +15 -0
  189. package/libx/rest/middleware/create.js +40 -0
  190. package/libx/rest/middleware/delete.js +20 -0
  191. package/libx/rest/middleware/error.js +56 -0
  192. package/libx/rest/middleware/operation.js +39 -0
  193. package/libx/rest/middleware/parse.js +90 -0
  194. package/libx/rest/middleware/read.js +29 -0
  195. package/libx/rest/middleware/update.js +42 -0
  196. package/libx/rest/utils/data.js +65 -0
  197. package/package.json +4 -1
  198. package/server.js +29 -7
  199. package/libx/_runtime/cds-services/services/utils/diff.js +0 -53
  200. package/libx/_runtime/cds-services/util/auditlog.js +0 -247
  201. package/libx/_runtime/cds-services/util/xsenv.js +0 -51
  202. package/libx/_runtime/common/utils/backlinks.js +0 -83
  203. package/libx/_runtime/common/utils/rewriteAsterisk.js +0 -72
  204. package/libx/_runtime/odata/index.js +0 -55
  205. package/libx/_runtime/odata/odata2cqn.js +0 -1
  206. package/libx/_runtime/odata/readToCqn.js +0 -129
  207. package/libx/_runtime/remote/cqn2odata/index.js +0 -2
@@ -1,8 +1,23 @@
1
- const { getOnCondElements } = require('./backlinks')
1
+ const { where2obj } = require('./cqn')
2
+
3
+ function _getOnCondElements(onCond, onCondElements = []) {
4
+ const andIndex = onCond.indexOf('and')
5
+ const entityKey = onCond[2].ref && onCond[2].ref.join('.')
6
+ const entityVal = onCond[2].val
7
+ const targetKey = onCond[0].ref && onCond[0].ref.join('.')
8
+ const targetVal = onCond[0].val
9
+ onCondElements.push({ entityKey, targetKey, entityVal, targetVal })
10
+
11
+ if (andIndex !== -1) {
12
+ _getOnCondElements(onCond.slice(andIndex + 1), onCondElements)
13
+ }
14
+ return onCondElements
15
+ }
2
16
 
3
17
  function _modifyWhereWithNavigations(where, newWhere, targetKeyElement, keyName) {
4
18
  if (where) {
5
- const whereCopy = JSON.parse(JSON.stringify(where)) // copy where else query will be modified
19
+ // copy where else query will be modified
20
+ const whereCopy = JSON.parse(JSON.stringify(where))
6
21
  if (newWhere.length > 0) newWhere.push('and')
7
22
  newWhere.push(...whereCopy)
8
23
  }
@@ -14,7 +29,7 @@ function _modifyWhereWithNavigations(where, newWhere, targetKeyElement, keyName)
14
29
  })
15
30
  }
16
31
 
17
- const _buildWhereForNavigations = (ref, newWhere, model, target) => {
32
+ function _buildWhereForNavigations(ref, newWhere, model, target) {
18
33
  const currentRef = ref[0]
19
34
  const nextRef = ref[1]
20
35
 
@@ -24,7 +39,7 @@ const _buildWhereForNavigations = (ref, newWhere, model, target) => {
24
39
 
25
40
  if (!navigationElement || !navigationElement.on) return
26
41
 
27
- const nextKeys = getOnCondElements(navigationElement.on)
42
+ const nextKeys = _getOnCondElements(navigationElement.on)
28
43
  for (const key of nextKeys) {
29
44
  const keyName = key.targetKey.replace(navigationElement.name + '.', '')
30
45
  const targetKeyElement = navigationElement._target.elements[keyName]
@@ -55,37 +70,23 @@ function _getWhereFromUpdate(query, target, model) {
55
70
  return query.UPDATE.where
56
71
  }
57
72
 
58
- function _addKeysFromWhereToData(where, target, data) {
59
- const whereLength = where.length
60
- for (let i = 0; i < whereLength; i++) {
61
- const whereEl = where[i]
62
- const colName = whereEl.ref && whereEl.ref[whereEl.ref.length - 1]
63
- const colEl = colName && target.elements[colName]
64
- if (colEl && colEl.key) {
65
- const opWhere = where[i + 1]
66
- const valWhere = where[i + 2]
67
- if (opWhere === '=' && valWhere && 'val' in valWhere) {
68
- data[colName] = valWhere.val
69
- }
70
- }
71
- }
72
- }
73
-
74
73
  // params: data, req, service/tx
75
- module.exports = (data, { query, target }, { model }) => {
74
+ function enrichDataWithKeysFromWhere(data, { query, target }, { model }) {
76
75
  if (query.INSERT) {
77
76
  const where = _getWhereFromInsert(query, target, model)
78
77
  if (!where || !where.length) return
79
-
80
78
  if (!Array.isArray(data)) data = [data]
81
-
82
- for (const d of data) {
83
- _addKeysFromWhereToData(where, target, d)
84
- }
79
+ for (const d of data) Object.assign(d, where2obj(where, target))
85
80
  } else if (query.UPDATE) {
86
81
  const where = _getWhereFromUpdate(query, target, model)
87
82
  if (!where || !where.length) return
88
- if (!data) data = query.UPDATE.data = {} // REVISIT: We should not expect data to be present always!
89
- _addKeysFromWhereToData(where, target, data)
83
+ // REVISIT: We should not expect data to be present always!
84
+ if (!data) data = query.UPDATE.data = {}
85
+ Object.assign(data, where2obj(where, target))
90
86
  }
91
87
  }
88
+
89
+ module.exports = {
90
+ where2obj,
91
+ enrichDataWithKeysFromWhere
92
+ }
@@ -64,6 +64,9 @@ const handleAliasInResult = (columns, result) => {
64
64
  // REVISIT: todo renaming for deep operations
65
65
  const postProcess = (query, result, service, onlySelectAliases = false) => {
66
66
  if (query.SELECT) {
67
+ if (query.SELECT.columns && query.SELECT.columns.find(col => col.func === 'count' && col.as === '$count')) {
68
+ return [{ $count: result }]
69
+ }
67
70
  handleAliasInResult(query.SELECT.columns, result)
68
71
 
69
72
  if (!onlySelectAliases) {
@@ -0,0 +1,84 @@
1
+ const cds = require('../../cds')
2
+
3
+ const _generateParentField = ({ parentElement }, row) => {
4
+ if (_autoGenerate(parentElement) && !row[parentElement.name]) {
5
+ row[parentElement.name] = cds.utils.uuid()
6
+ }
7
+ }
8
+
9
+ const _generateChildField = ({ deep, childElement }, childRow) => {
10
+ if (deep) {
11
+ _generateChildField(deep.propagation, childRow[deep.targetName])
12
+ } else if (_autoGenerate(childElement) && childRow && !childRow[childElement.name]) {
13
+ childRow[childElement.name] = cds.utils.uuid()
14
+ }
15
+ }
16
+
17
+ const _autoGenerate = e => e && e.type === 'cds.UUID' && e.key
18
+
19
+ const _getNestedVal = (row, prefix) => {
20
+ let val = row
21
+ const splitted = prefix.split('_')
22
+ let k = ''
23
+
24
+ while (splitted.length > 0) {
25
+ k += splitted.shift()
26
+ if (k in val) {
27
+ val = val[k]
28
+ k = ''
29
+ } else {
30
+ k += '_'
31
+ }
32
+ }
33
+
34
+ return val
35
+ }
36
+
37
+ const _propagateToChid = ({ parentElement, childElement, prefix, parentFieldValue }, row, childRow) => {
38
+ if (!childElement) return
39
+ if (parentElement) {
40
+ if (prefix) {
41
+ const nested = _getNestedVal(row, prefix)
42
+ childRow[childElement.name] = nested[parentElement.name]
43
+ } else {
44
+ childRow[childElement.name] = row[parentElement.name]
45
+ }
46
+ } else if (parentFieldValue !== undefined) {
47
+ childRow[childElement.name] = parentFieldValue
48
+ }
49
+ }
50
+
51
+ const _propagateToParent = ({ parentElement, childElement, deep }, childRow, row) => {
52
+ if (deep) {
53
+ _propagateToParent(deep.propagation, childRow[deep.targetName], childRow)
54
+ }
55
+ if (parentElement && childElement && childRow && Object.prototype.hasOwnProperty.call(childRow, childElement.name)) {
56
+ row[parentElement.name] = childRow[childElement.name]
57
+ }
58
+ }
59
+
60
+ const propagateForeignKeys = (tKey, row, foreignKeyPropagations, isCompositionEffective) => {
61
+ const childRows = Array.isArray(row[tKey]) ? row[tKey] : [row[tKey]]
62
+
63
+ for (const childRow of childRows) {
64
+ if (!childRow) return
65
+
66
+ for (const foreignKeyPropagation of foreignKeyPropagations) {
67
+ if (foreignKeyPropagation.fillChild) {
68
+ _generateParentField(foreignKeyPropagation, row)
69
+ if (!isCompositionEffective) {
70
+ delete row[tKey]
71
+ } else {
72
+ _propagateToChid(foreignKeyPropagation, row, childRow)
73
+ }
74
+ } else {
75
+ _generateChildField(foreignKeyPropagation, childRow)
76
+ _propagateToParent(foreignKeyPropagation, childRow, row)
77
+ }
78
+ }
79
+ }
80
+ }
81
+
82
+ module.exports = {
83
+ propagateForeignKeys
84
+ }
@@ -22,7 +22,7 @@ const _flattenProps = (subElement, structName, structProperties, structElement,
22
22
 
23
23
  const _resolveStructured = ({ structName, structProperties }, subElements, asRef = true) => {
24
24
  if (!subElements) {
25
- return
25
+ return []
26
26
  }
27
27
 
28
28
  // only add from structProperties
@@ -2,6 +2,7 @@ const cds = require('../../cds')
2
2
  let LOG = cds.log('app')
3
3
  let _event
4
4
  const PERSISTENCE_TABLE = '@cds.persistence.table'
5
+ const { rewriteAsterisks } = require('../../common/utils/rewriteAsterisks')
5
6
 
6
7
  const getError = require('../error')
7
8
  const { getEntityNameFromDeleteCQN, getEntityNameFromUpdateCQN } = require('../utils/cqn')
@@ -172,7 +173,7 @@ const _newColumns = (columns = [], transition, service, withAlias = false) => {
172
173
  }
173
174
 
174
175
  // ensure that renaming of a redirected assoc are also respected
175
- if (column.expand) {
176
+ if (mapped && column.expand) {
176
177
  // column.ref might be structured elements
177
178
  let def
178
179
  column.ref.forEach((ref, i) => {
@@ -186,9 +187,7 @@ const _newColumns = (columns = [], transition, service, withAlias = false) => {
186
187
  // reuse _newColumns with new transition
187
188
  const expandTarget = def._target
188
189
  const subtransition = getTransition(expandTarget, service)
189
-
190
- // REVISIT: in edom-retailer case, mapped was undefined which lead to a type error
191
- if (mapped) mapped.transition = subtransition
190
+ mapped.transition = subtransition
192
191
 
193
192
  newColumn.expand = _newColumns(column.expand, subtransition, service, withAlias)
194
193
  }
@@ -338,8 +337,11 @@ const _newSelect = (query, transitions, service) => {
338
337
  ref: _rewriteQueryPath(query.SELECT.from, transitions)
339
338
  }
340
339
  if (!newSelect.columns && targetTransition.mapping.size) newSelect.columns = _initialColumns(targetTransition)
341
- if (newSelect.columns)
340
+ if (newSelect.columns) {
341
+ const isDB = service instanceof cds.DatabaseService
342
+ rewriteAsterisks({ SELECT: newSelect }, targetTransition.queryTarget, isDB)
342
343
  newSelect.columns = _newColumns(newSelect.columns, targetTransition, service, service.kind !== 'app-service')
344
+ }
343
345
  if (newSelect.having) newSelect.having = _newColumns(newSelect.having, targetTransition)
344
346
  if (newSelect.groupBy) newSelect.groupBy = _newColumns(newSelect.groupBy, targetTransition)
345
347
  if (newSelect.orderBy) newSelect.orderBy = _newColumns(newSelect.orderBy, targetTransition)
@@ -0,0 +1,94 @@
1
+ const { getNavigationIfStruct } = require('./structured')
2
+ const getColumns = require('../../db/utils/columns')
3
+ const { ensureDraftsSuffix } = require('./draft')
4
+
5
+ const isAsteriskColumn = col => col === '*' || (col.ref && col.ref[0] === '*' && !col.expand)
6
+
7
+ const _isDuplicate = newColumn => column => {
8
+ if (newColumn.as) return column.as && column.as === newColumn.as
9
+ if (!column.ref) return
10
+ if (Array.isArray(newColumn)) newColumn = { ref: newColumn }
11
+ return newColumn.ref ? newColumn.ref.join('_') === column.ref.join('_') : newColumn === column.ref.join('_')
12
+ }
13
+
14
+ const _cqlDraftColumns = target => {
15
+ if (target.name.endsWith('DraftAdministrativeData')) return []
16
+ const draftName = ensureDraftsSuffix(target.name)
17
+ const subSelect = SELECT.from(draftName).columns([1])
18
+ for (const key in target.keys) {
19
+ if (key !== 'IsActiveEntity') subSelect.where([{ ref: [target.name, key] }, '=', { ref: [draftName, key] }])
20
+ }
21
+ return [
22
+ { val: true, as: 'IsActiveEntity', cast: { type: 'cds.Boolean' } },
23
+ { val: false, as: 'HasActiveEntity', cast: { type: 'cds.Boolean' } },
24
+ {
25
+ xpr: ['case', 'when', 'exists', subSelect, 'then', 'true', 'else', 'false', 'end'],
26
+ as: 'HasDraftEntity',
27
+ cast: { type: 'cds.Boolean' }
28
+ }
29
+ ]
30
+ }
31
+
32
+ const _expandColumn = (column, target, db) => {
33
+ if (!(column.ref && column.expand)) return
34
+ const nextTarget = getNavigationIfStruct(target, column.ref)
35
+ if (nextTarget && nextTarget._target && nextTarget._target.elements) _rewriteAsterisks(column, nextTarget._target, db)
36
+ return column
37
+ }
38
+
39
+ const rewriteExpandAsterisk = (columns, target) => {
40
+ const expandAllColIdx = columns.findIndex(col => {
41
+ if (col.ref || !col.expand) return
42
+ return !Array.isArray(col.expand) ? col.expand === '*' : col.expand.indexOf('*') > -1
43
+ })
44
+ if (expandAllColIdx > -1) {
45
+ const { expand } = columns.splice(expandAllColIdx, 1)[0]
46
+ for (const elName in target.elements) {
47
+ if (target.elements[elName]._target && !columns.find(col => col.expand && col.ref && col.ref[0] === elName)) {
48
+ columns.push({ ref: [elName], expand: [...expand] })
49
+ }
50
+ }
51
+ }
52
+ }
53
+
54
+ const _rewriteAsterisk = (columns, target, db, isRoot) => {
55
+ const asteriskColumnIndex = columns.findIndex(col => isAsteriskColumn(col))
56
+ if (asteriskColumnIndex > -1) {
57
+ columns.splice(
58
+ asteriskColumnIndex,
59
+ 1,
60
+ ...getColumns(target, { db })
61
+ .map(c => ({ ref: [c.name] }))
62
+ .filter(c => !columns.find(_isDuplicate(c)) && (isRoot || c.ref[0] !== 'DraftAdministrativeData_DraftUUID'))
63
+ )
64
+ }
65
+ }
66
+
67
+ const _rewriteAsterisks = (cqn, target, db, isRoot) => {
68
+ if (cqn.expand === '*') cqn.expand = ['*']
69
+ const columns = cqn.expand || cqn.columns
70
+ _rewriteAsterisk(columns, target, db, isRoot)
71
+ rewriteExpandAsterisk(columns, target)
72
+ for (const column of columns) {
73
+ _expandColumn(column, target, db)
74
+ }
75
+ return columns
76
+ }
77
+
78
+ const rewriteAsterisks = (query, target, db = false, isDraft = false, onlyKeys = false) => {
79
+ if (!target || target.name.endsWith('_drafts')) return
80
+ if (!query.SELECT.columns || (query.SELECT.columns && !query.SELECT.columns.length)) {
81
+ if (isDraft || db) {
82
+ query.SELECT.columns = getColumns(target, { db, onlyKeys }).map(col => ({ ref: [col.name] }))
83
+ if (db && target._isDraftEnabled) query.SELECT.columns.push(..._cqlDraftColumns(target))
84
+ }
85
+ return
86
+ }
87
+ query.SELECT.columns = _rewriteAsterisks(query.SELECT, target, db, true)
88
+ }
89
+
90
+ module.exports = {
91
+ rewriteAsterisks,
92
+ isAsteriskColumn,
93
+ rewriteExpandAsterisk
94
+ }
@@ -3,23 +3,24 @@ const searchToLike = require('./searchToLike')
3
3
 
4
4
  // convert $search system query option to WHERE/HAVING clause using
5
5
  // the operator LIKE or CONTAINS
6
- const search2cqn4sql = (cqn, model, options) => {
7
- const { search2cqn4sql, targetName = cqn.SELECT.from.ref[0] } = options
6
+ const search2cqn4sql = (query, model, options) => {
7
+ const { search2cqn4sql, targetName = query.SELECT.from.ref[0] } = options
8
8
  const entity = model.definitions[targetName]
9
- const columns = computeColumnsToBeSearched(cqn, entity)
9
+ const columns = computeColumnsToBeSearched(query, entity)
10
10
 
11
- // call custom (optimized search to cqn for sql implementation) that tries
11
+ // Call custom (optimized search to cqn for sql implementation) that tries
12
12
  // to optimize the search behavior for a specific database service.
13
- if (typeof search2cqn4sql === 'function') {
13
+ // Note: $search query option combined with $filter is not currently optimized
14
+ if (typeof search2cqn4sql === 'function' && !query.SELECT.where) {
14
15
  const search2cqnOptions = { columns, locale: options.locale }
15
- return search2cqn4sql(cqn, entity, search2cqnOptions)
16
+ return search2cqn4sql(query, entity, search2cqnOptions)
16
17
  }
17
18
 
18
- const cqnSearchPhrase = cqn.SELECT.search
19
+ const cqnSearchPhrase = query.SELECT.search
19
20
  const expression = searchToLike(cqnSearchPhrase, columns)
20
21
 
21
22
  // REVISIT: find out here if where or having must be used
22
- cqn._aggregated ? cqn.having(expression) : cqn.where(expression)
23
+ query._aggregated ? query.having(expression) : query.where(expression)
23
24
  }
24
25
 
25
26
  module.exports = search2cqn4sql
@@ -48,57 +48,60 @@ const _getNextTarget = (model, element, currentPath = []) => {
48
48
  nextTargetName,
49
49
  nextTarget: model.definitions[nextTargetName]
50
50
  }
51
- } else if (_isInlineStructured(element)) {
51
+ }
52
+
53
+ if (_isInlineStructured(element)) {
52
54
  return {
53
55
  nextTargetName: [...currentPath, element.name].join(DELIMITER),
54
56
  nextTarget: element.items || element
55
57
  }
56
58
  }
59
+
57
60
  return {}
58
61
  }
59
62
 
60
63
  /**
61
64
  *
62
- * @param {CSN} model: Model
63
- * @param {*} cache: Internal - do not use
64
- * @param {CSN} target: target entity which needs to be traversed
65
- * @param {*} param1.pick: Function to pick items. If it returns a truthy value, the item will be picked. The returned value is part of the template.
66
- * @param {*} param1.ignore: Function to ignore items. If it returns a truthy value, the item will be ignored.
67
- * @param {*} parent: The parent entity
68
- * @param {*} entityMap: Internal - do not use
69
- * @param model
70
- * @param targetName
71
- * @param parent
72
- * @param entityMap
65
+ * @param {import('@sap/cds-compiler/lib/api/main').CSN} model Model
66
+ * @param {Map} cache Internal - do not use
67
+ * @param {object} targetEntity The target entity which needs to be traversed
68
+ * @param {object} callbacks
69
+ * @param {function} callbacks.pick Callback function to pick elements. If it returns a truthy value, the element will be picked. The returned value is part of the template.
70
+ * @param {function} callbacks.ignore Callback function to ignore elements. If it returns a truthy value, the element will be ignored.
71
+ * @param {object} [parent=null] The parent entity
72
+ * @param {Map} [_entityMap] This parameter is an implementation side-effect — don't use it
73
+ * @param {array} [targetPath=[]]
73
74
  */
74
- function _getTemplate(model, cache, target, { pick, ignore }, parent = null, entityMap = new Map(), targetPath = []) {
75
+ function _getTemplate(model, cache, targetEntity, callbacks, parent = null, _entityMap = new Map(), targetPath = []) {
76
+ const { pick, ignore } = callbacks
75
77
  const templateElements = new Map()
76
- const template = { target, elements: templateElements }
77
- const currentPath = [...targetPath, target.name]
78
- entityMap.set(currentPath.join(DELIMITER), { template })
79
-
80
- if (target.elements) {
81
- for (const elementName in target.elements) {
82
- const element = target.elements[elementName]
83
- if (ignore && ignore(element, target, parent)) continue
84
-
85
- _pick(pick, element, target, parent, templateElements, elementName)
86
- if (element.items) {
87
- _pick(pick, element.items, target, parent, templateElements, ['_itemsOf', elementName].join(DELIMITER))
88
- }
89
-
90
- const { nextTargetName, nextTarget } = _getNextTarget(model, element, currentPath)
91
- const nextTargetCached = entityMap.get(nextTargetName)
92
- if (nextTargetCached) {
93
- _addCacheToTemplateElements(templateElements, elementName, nextTargetCached)
94
- } else if (nextTarget) {
95
- // For associations and _typed_ structured elements, there's a (cacheable) target,
96
- // inline structures must be handled separately.
97
- const subTemplate = _isInlineStructured(element)
98
- ? _getTemplate(model, cache, nextTarget, { pick, ignore }, target, entityMap, currentPath)
99
- : cache.for(nextTarget, getTemplate(model, { pick, ignore }, target, entityMap))
100
- _addSubTemplate(templateElements, elementName, subTemplate)
101
- }
78
+ const template = { target: targetEntity, elements: templateElements }
79
+ const currentPath = [...targetPath, targetEntity.name]
80
+ _entityMap.set(currentPath.join(DELIMITER), { template })
81
+ if (!targetEntity.elements) return template
82
+
83
+ for (const elementName in targetEntity.elements) {
84
+ const element = targetEntity.elements[elementName]
85
+ if (ignore && ignore(element, targetEntity, parent)) continue
86
+
87
+ _pick(pick, element, targetEntity, parent, templateElements, elementName)
88
+
89
+ if (element.items) {
90
+ _pick(pick, element.items, targetEntity, parent, templateElements, ['_itemsOf', elementName].join(DELIMITER))
91
+ }
92
+
93
+ const { nextTargetName, nextTarget } = _getNextTarget(model, element, currentPath)
94
+ const nextTargetCached = _entityMap.get(nextTargetName)
95
+
96
+ if (nextTargetCached) {
97
+ _addCacheToTemplateElements(templateElements, elementName, nextTargetCached)
98
+ } else if (nextTarget) {
99
+ // For associations and _typed_ structured elements, there's a (cacheable) target,
100
+ // inline structures must be handled separately.
101
+ const subTemplate = _isInlineStructured(element)
102
+ ? _getTemplate(model, cache, nextTarget, { pick, ignore }, targetEntity, _entityMap, currentPath)
103
+ : cache.for(nextTarget, getTemplate(model, { pick, ignore }, targetEntity, _entityMap))
104
+ _addSubTemplate(templateElements, elementName, subTemplate)
102
105
  }
103
106
  }
104
107
 
@@ -112,11 +115,11 @@ const getTemplate =
112
115
 
113
116
  const getCache = (anything, cache, newCacheFn) => {
114
117
  let _cached = cache.get(anything)
115
- if (!_cached) {
116
- _cached = (typeof newCacheFn === 'function' && newCacheFn(anything, cache)) || new Map()
117
- _cached.for = (_usecase, _newCacheFn) => getCache(_usecase, _cached, _newCacheFn)
118
- cache.set(anything, _cached)
119
- }
118
+ if (_cached) return _cached
119
+
120
+ _cached = (typeof newCacheFn === 'function' && newCacheFn(anything, cache)) || new Map()
121
+ _cached.for = (_usecase, _newCacheFn) => getCache(_usecase, _cached, _newCacheFn)
122
+ cache.set(anything, _cached)
120
123
  return _cached
121
124
  }
122
125
 
@@ -124,11 +127,13 @@ module.exports = (usecase, tx, target, ...args) => {
124
127
  // get model first as it may be added to tx (cf. "_ensureModel")
125
128
  const model = tx.model
126
129
  if (!model) return
130
+
127
131
  // double-check with get target from model
128
132
  // since target might come from anywhere like via cqn etc
129
133
  if (!target) return
130
134
  const root = (model && model.definitions[target.name]) || (target.elements && target)
131
135
  if (!root) return
136
+
132
137
  // tx could be the service itself
133
138
  // prefer ApplicationService (i.e., tx.context._tx.__proto__)
134
139
  // REVISIT: context._tx is not a stable API -> pls do not rely on that
@@ -136,10 +141,13 @@ module.exports = (usecase, tx, target, ...args) => {
136
141
  ? (tx.context._tx && Object.getPrototypeOf(tx.context._tx)) || Object.getPrototypeOf(tx)
137
142
  : tx
138
143
  if (!service) return
144
+
139
145
  // cache templates at service for garbage collection
140
- if (!service._templateCache) service._templateCache = new Map()
146
+ if (usecase && !service._templateCache) service._templateCache = new Map()
141
147
  // model can be also a subset from tx
142
- return getCache(usecase, service._templateCache)
148
+
149
+ // if no usecase, don't save cache on the service object
150
+ return getCache(usecase, usecase ? service._templateCache : new Map())
143
151
  .for(model)
144
152
  .for(root, getTemplate(model, ...args))
145
153
  }
@@ -109,6 +109,7 @@ class DatabaseService extends cds.Service {
109
109
  if (query && (!streamQuery.SELECT.columns || streamQuery.SELECT.columns.length !== 0)) {
110
110
  streamQuery.columns([query])
111
111
  }
112
+
112
113
  delete streamQuery.SELECT.one
113
114
  streamQuery._streaming = true
114
115
 
@@ -119,12 +120,18 @@ class DatabaseService extends cds.Service {
119
120
  }
120
121
  })
121
122
 
122
- if (!streamQuery.SELECT.where) {
123
+ if (
124
+ !streamQuery.SELECT.where &&
125
+ !(
126
+ streamQuery.SELECT.from &&
127
+ streamQuery.SELECT.from.ref &&
128
+ streamQuery.SELECT.from.ref[streamQuery.SELECT.from.ref.length - 1].where
129
+ )
130
+ ) {
123
131
  return {
124
132
  where: (...args) => {
125
133
  streamQuery.where(...args)
126
134
  this._runStream(streamQuery, result)
127
-
128
135
  return result
129
136
  }
130
137
  }