@sap/cds 5.4.6 → 5.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/CHANGELOG.md +208 -2
  2. package/apis/ql.d.ts +17 -15
  3. package/app/index.js +1 -1
  4. package/bin/build/buildTaskEngine.js +26 -42
  5. package/bin/build/buildTaskFactory.js +6 -10
  6. package/bin/build/buildTaskHandler.js +2 -4
  7. package/bin/build/buildTaskProvider.js +3 -1
  8. package/bin/build/buildTaskProviderFactory.js +9 -15
  9. package/bin/build/constants.js +15 -3
  10. package/bin/build/index.js +5 -4
  11. package/bin/build/mtaUtil.js +8 -11
  12. package/bin/build/provider/buildTaskHandlerEdmx.js +63 -6
  13. package/bin/build/provider/buildTaskHandlerInternal.js +2 -34
  14. package/bin/build/provider/buildTaskProviderInternal.js +16 -42
  15. package/bin/build/provider/fiori/index.js +13 -24
  16. package/bin/build/provider/hana/2migration.js +17 -15
  17. package/bin/build/provider/hana/2tabledata.js +52 -48
  18. package/bin/build/provider/hana/index.js +27 -25
  19. package/bin/build/provider/hana/migrationtable.js +91 -67
  20. package/bin/build/provider/java-cf/index.js +14 -24
  21. package/bin/build/provider/mtx/index.js +12 -14
  22. package/bin/build/provider/node-cf/index.js +18 -32
  23. package/bin/cds.js +5 -5
  24. package/bin/serve.js +29 -23
  25. package/bin/version.js +0 -1
  26. package/lib/compile/etc/_localized.js +4 -9
  27. package/lib/compile/for/sql.js +5 -2
  28. package/lib/compile/parse.js +25 -17
  29. package/lib/compile/to/srvinfo.js +2 -1
  30. package/lib/connect/bindings.js +2 -1
  31. package/lib/connect/index.js +48 -49
  32. package/lib/core/classes.js +1 -1
  33. package/lib/core/reflect.js +10 -2
  34. package/lib/deploy.js +26 -23
  35. package/lib/env/defaults.js +13 -6
  36. package/lib/env/index.js +73 -78
  37. package/lib/env/requires.js +38 -19
  38. package/lib/index.js +9 -10
  39. package/lib/lazy.js +2 -2
  40. package/lib/log/index.js +33 -45
  41. package/lib/log/service/index.js +2 -2
  42. package/lib/ql/CREATE.js +14 -9
  43. package/lib/ql/DELETE.js +6 -5
  44. package/lib/ql/DROP.js +12 -9
  45. package/lib/ql/INSERT.js +40 -16
  46. package/lib/ql/Query.js +67 -40
  47. package/lib/ql/SELECT.js +162 -127
  48. package/lib/ql/UPDATE.js +74 -42
  49. package/lib/ql/Whereable.js +77 -87
  50. package/lib/ql/index.js +36 -24
  51. package/lib/ql/parse.js +35 -0
  52. package/lib/req/context.js +44 -8
  53. package/lib/req/locale.js +7 -7
  54. package/lib/serve/Service-api.js +21 -14
  55. package/lib/serve/Service-dispatch.js +28 -12
  56. package/lib/serve/Transaction.js +22 -10
  57. package/lib/serve/index.js +16 -11
  58. package/lib/utils/axios.js +23 -16
  59. package/lib/utils/data.js +35 -0
  60. package/lib/utils/tests.js +27 -18
  61. package/libx/_runtime/audit/generic/personal/access.js +81 -0
  62. package/libx/_runtime/audit/generic/personal/constants.js +4 -0
  63. package/libx/_runtime/audit/generic/personal/index.js +50 -0
  64. package/libx/_runtime/audit/generic/personal/modification.js +138 -0
  65. package/libx/_runtime/audit/generic/personal/utils.js +186 -0
  66. package/libx/_runtime/audit/utils/v2.js +10 -4
  67. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +5 -5
  68. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +6 -7
  69. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +5 -7
  70. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +5 -7
  71. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +2 -3
  72. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +4 -0
  73. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +7 -4
  74. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +59 -8
  75. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +11 -1
  76. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +6 -10
  77. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +3 -46
  78. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +2 -5
  79. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/createToCQN.js +2 -2
  80. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/deleteToCQN.js +4 -3
  81. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -2
  82. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +0 -1
  83. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +1 -1
  84. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/updateToCQN.js +2 -2
  85. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +16 -18
  86. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/EdmEntityType.js +6 -3
  87. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/format/RepresentationKind.js +4 -1
  88. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/core/OdataRequest.js +1 -0
  89. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/SerializerFactory.js +15 -2
  90. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/OperationValidator.js +1 -0
  91. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
  92. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +8 -1
  93. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +6 -1
  94. package/libx/_runtime/cds-services/adapter/odata-v4/utils/omitValues.js +12 -5
  95. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +1 -7
  96. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +7 -7
  97. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +14 -18
  98. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +13 -13
  99. package/libx/_runtime/cds-services/adapter/rest/handlers/create.js +0 -1
  100. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +2 -1
  101. package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +2 -2
  102. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +2 -4
  103. package/libx/_runtime/cds-services/adapter/rest/utils/result.js +4 -2
  104. package/libx/_runtime/cds-services/services/Service.js +40 -5
  105. package/libx/_runtime/cds-services/services/utils/columns.js +13 -7
  106. package/libx/_runtime/cds-services/services/utils/compareJson.js +88 -4
  107. package/libx/_runtime/cds-services/services/utils/differ.js +24 -6
  108. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -2
  109. package/libx/_runtime/common/composition/data.js +44 -55
  110. package/libx/_runtime/common/composition/delete.js +97 -71
  111. package/libx/_runtime/common/composition/index.js +2 -1
  112. package/libx/_runtime/common/composition/insert.js +34 -11
  113. package/libx/_runtime/common/composition/tree.js +119 -92
  114. package/libx/_runtime/common/composition/update.js +4 -1
  115. package/libx/_runtime/common/composition/utils.js +1 -3
  116. package/libx/_runtime/common/constants/draft.js +12 -1
  117. package/libx/_runtime/common/generic/auth.js +6 -22
  118. package/libx/_runtime/common/generic/crud.js +14 -13
  119. package/libx/_runtime/common/generic/input.js +23 -26
  120. package/libx/_runtime/common/generic/put.js +1 -1
  121. package/libx/_runtime/common/generic/sorting.js +16 -16
  122. package/libx/_runtime/common/i18n/index.js +1 -1
  123. package/libx/_runtime/common/i18n/messages.properties +4 -0
  124. package/libx/_runtime/common/utils/backlinks.js +12 -5
  125. package/libx/_runtime/common/utils/cqn.js +6 -1
  126. package/libx/_runtime/common/utils/cqn2cqn4sql.js +102 -101
  127. package/libx/_runtime/common/utils/csn.js +47 -4
  128. package/libx/_runtime/common/utils/data.js +0 -37
  129. package/libx/_runtime/common/utils/enrichWithKeysFromWhere.js +1 -1
  130. package/libx/_runtime/common/utils/entityFromCqn.js +7 -24
  131. package/libx/_runtime/common/utils/foreignKeyPropagations.js +39 -7
  132. package/libx/_runtime/common/utils/generateOnCond.js +11 -12
  133. package/libx/_runtime/common/utils/onlyKeysRemain.js +10 -0
  134. package/libx/_runtime/common/utils/path.js +35 -0
  135. package/libx/_runtime/common/utils/postProcessing.js +86 -0
  136. package/libx/_runtime/common/utils/quotingStyles.js +37 -26
  137. package/libx/_runtime/common/utils/resolveView.js +223 -171
  138. package/libx/_runtime/common/utils/rewriteAsterisk.js +46 -26
  139. package/libx/_runtime/common/utils/structured.js +6 -12
  140. package/libx/_runtime/common/utils/template.js +10 -5
  141. package/libx/_runtime/common/utils/templateDelimiter.js +1 -0
  142. package/libx/_runtime/common/utils/templateProcessor.js +22 -30
  143. package/libx/_runtime/common/utils/union.js +31 -0
  144. package/libx/_runtime/common/utils/unionCqnTemplate.js +184 -0
  145. package/libx/_runtime/db/Service.js +1 -1
  146. package/libx/_runtime/db/data-conversion/timestamp.js +2 -9
  147. package/libx/_runtime/db/expand/expandCQNToJoin.js +204 -297
  148. package/libx/_runtime/db/expand/index.js +3 -3
  149. package/libx/_runtime/db/expand/rawToExpanded.js +36 -7
  150. package/libx/_runtime/db/generic/index.js +1 -1
  151. package/libx/_runtime/db/generic/input.js +5 -7
  152. package/libx/_runtime/db/generic/integrity.js +1 -1
  153. package/libx/_runtime/db/generic/rewrite.js +2 -10
  154. package/libx/_runtime/db/generic/update.js +13 -5
  155. package/libx/_runtime/db/generic/virtual.js +22 -58
  156. package/libx/_runtime/db/query/delete.js +7 -4
  157. package/libx/_runtime/db/query/insert.js +6 -4
  158. package/libx/_runtime/db/query/read.js +13 -20
  159. package/libx/_runtime/db/query/run.js +4 -1
  160. package/libx/_runtime/db/query/update.js +5 -4
  161. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +35 -2
  162. package/libx/_runtime/db/sql-builder/FunctionBuilder.js +17 -2
  163. package/libx/_runtime/db/sql-builder/InsertBuilder.js +6 -5
  164. package/libx/_runtime/db/sql-builder/ReferenceBuilder.js +10 -0
  165. package/libx/_runtime/db/sql-builder/SelectBuilder.js +35 -24
  166. package/libx/_runtime/db/sql-builder/UpdateBuilder.js +14 -4
  167. package/libx/_runtime/db/sql-builder/arrayed.js +4 -0
  168. package/libx/_runtime/db/utils/deep.js +8 -0
  169. package/libx/_runtime/db/utils/generateAliases.js +2 -1
  170. package/libx/_runtime/fiori/generic/activate.js +19 -15
  171. package/libx/_runtime/fiori/generic/before.js +3 -11
  172. package/libx/_runtime/fiori/generic/cancel.js +1 -1
  173. package/libx/_runtime/fiori/generic/delete.js +3 -1
  174. package/libx/_runtime/fiori/generic/edit.js +12 -2
  175. package/libx/_runtime/fiori/generic/new.js +5 -5
  176. package/libx/_runtime/fiori/generic/patch.js +0 -18
  177. package/libx/_runtime/fiori/generic/read.js +241 -189
  178. package/libx/_runtime/fiori/utils/delete.js +36 -7
  179. package/libx/_runtime/fiori/utils/handler.js +43 -44
  180. package/libx/_runtime/fiori/utils/where.js +30 -15
  181. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +4 -6
  182. package/libx/_runtime/hana/execute.js +2 -2
  183. package/libx/_runtime/hana/localized.js +4 -4
  184. package/libx/_runtime/hana/pool.js +29 -14
  185. package/libx/_runtime/hana/search2cqn4sql.js +2 -1
  186. package/libx/_runtime/hana/searchToContains.js +18 -14
  187. package/libx/_runtime/index.js +0 -5
  188. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +13 -5
  189. package/libx/_runtime/messaging/common-utils/naming-conventions.js +4 -1
  190. package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +31 -19
  191. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +1 -2
  192. package/libx/_runtime/messaging/enterprise-messaging.js +6 -4
  193. package/libx/_runtime/messaging/service.js +7 -6
  194. package/libx/_runtime/odata/cqn2odata.js +110 -43
  195. package/libx/_runtime/odata/index.js +26 -48
  196. package/libx/_runtime/odata/odata2cqn.js +1 -6154
  197. package/libx/_runtime/odata/odata2cqn.pegjs +559 -0
  198. package/libx/_runtime/odata/readToCqn.js +94 -64
  199. package/libx/_runtime/remote/Service.js +74 -21
  200. package/libx/_runtime/remote/cqn2odata/index.js +1 -5
  201. package/libx/_runtime/remote/utils/client.js +24 -101
  202. package/libx/_runtime/remote/utils/dataConversion.js +27 -12
  203. package/libx/_runtime/sqlite/Service.js +3 -5
  204. package/libx/_runtime/sqlite/execute.js +23 -24
  205. package/libx/_runtime/sqlite/localized.js +12 -7
  206. package/libx/_runtime/types/api.js +10 -0
  207. package/package.json +1 -1
  208. package/server.js +16 -2
  209. package/lib/ql/grammar.pegjs +0 -208
  210. package/lib/ql/parser.js +0 -1
  211. package/lib/ql/rt/DELETE.js +0 -29
  212. package/lib/ql/rt/INSERT.js +0 -23
  213. package/lib/ql/rt/Query.js +0 -84
  214. package/lib/ql/rt/SELECT.js +0 -174
  215. package/lib/ql/rt/UPDATE.js +0 -119
  216. package/lib/ql/rt/_helpers.js +0 -91
  217. package/lib/ql/rt/index.js +0 -32
  218. package/libx/_runtime/audit/generic/personal.js +0 -260
  219. package/libx/_runtime/cds-services/statements/BaseStatement.js +0 -72
  220. package/libx/_runtime/cds-services/statements/Create.js +0 -57
  221. package/libx/_runtime/cds-services/statements/Delete.js +0 -33
  222. package/libx/_runtime/cds-services/statements/Drop.js +0 -42
  223. package/libx/_runtime/cds-services/statements/Insert.js +0 -201
  224. package/libx/_runtime/cds-services/statements/Select.js +0 -826
  225. package/libx/_runtime/cds-services/statements/Update.js +0 -181
  226. package/libx/_runtime/cds-services/statements/Where.js +0 -726
  227. package/libx/_runtime/cds-services/statements/index.js +0 -25
  228. package/libx/_runtime/common/generic/resolve-mock.js +0 -9
@@ -1,8 +1,8 @@
1
- const expandCqnToJoin = require('./expandCQNToJoin')
1
+ const { hasExpand, createJoinCQNFromExpanded } = require('./expandCQNToJoin')
2
2
  const rawToExpanded = require('./rawToExpanded')
3
3
 
4
4
  module.exports = {
5
- hasExpand: expandCqnToJoin.hasExpand,
6
- createJoinCQNFromExpanded: expandCqnToJoin.createJoinCQNFromExpanded,
5
+ hasExpand,
6
+ createJoinCQNFromExpanded,
7
7
  rawToExpanded
8
8
  }
@@ -1,15 +1,17 @@
1
1
  const EXPAND = Symbol.for('sap.cds.expand')
2
2
  const GET_KEY_VALUE = Symbol.for('sap.cds.getKeyValue')
3
3
  const TO_MANY = Symbol.for('sap.cds.toMany')
4
+ const TO_ACTIVE = Symbol.for('sap.cds.toActive')
4
5
  const { DRAFT_COLUMNS } = require('../../common/constants/draft')
5
6
 
6
7
  class RawToExpanded {
7
- constructor(configs, queries, one) {
8
+ constructor(configs, queries, one, rootEntity) {
8
9
  this._one = one
9
10
  this._toManyResults = { expand: {} }
10
11
  this._result = []
11
12
  this._configs = configs
12
13
  this._queries = queries
14
+ this._rootEntity = rootEntity
13
15
  }
14
16
 
15
17
  /**
@@ -66,20 +68,40 @@ class RawToExpanded {
66
68
  * @returns {object}
67
69
  * @private
68
70
  */
71
+ // eslint-disable-next-line complexity
69
72
  _parseRaw({ mappings, toManyTree, conversionMapper, entry }) {
70
73
  let isEntityNull
71
74
 
72
75
  const row = {}
73
76
 
77
+ const rootIsActiveEntity = this._rootEntity._isDraftEnabled
78
+ ? mappings.IsActiveEntity
79
+ ? entry[mappings.IsActiveEntity] === null
80
+ ? null
81
+ : !!entry[mappings.IsActiveEntity]
82
+ : 'IsActiveEntity' in entry
83
+ ? entry.IsActiveEntity === null
84
+ ? null
85
+ : !!entry.IsActiveEntity
86
+ : null
87
+ : null
88
+
74
89
  // A raw row contains more elements than the config. Iterating over config is faster.
75
90
  for (const key in mappings) {
76
91
  // To many entries have been already processed and cached
77
92
  const mapping = mappings[key]
78
93
  if (mapping[TO_MANY]) {
79
- row[key] = this._getResultCache(toManyTree.concat(key))[mapping[GET_KEY_VALUE](false, entry)] || []
94
+ let expandedItems = this._getResultCache(toManyTree.concat(key))[mapping[GET_KEY_VALUE](false, entry)] || []
95
+
96
+ // the expanded items may include the actives of the deleted drafts -> filter out
97
+ if (rootIsActiveEntity !== null) {
98
+ if (mapping[TO_ACTIVE]) expandedItems = expandedItems.filter(ele => ele.IsActiveEntity === true)
99
+ else expandedItems = expandedItems.filter(ele => ele.IsActiveEntity === rootIsActiveEntity)
100
+ }
80
101
 
81
- // Will be true in case of 1:1 expands
102
+ row[key] = expandedItems
82
103
  } else if (typeof mapping === 'object') {
104
+ // > Will be true in case of 1:1 expands
83
105
  // check if the expanded entry doesn't exists
84
106
  if (this._isExpandEmpty(mapping, entry)) {
85
107
  row[key] = null
@@ -92,9 +114,15 @@ class RawToExpanded {
92
114
  entry: entry
93
115
  })
94
116
 
95
- row[key] = parsed || null
96
- // No expand convert the result directly.
117
+ // the expanded items may include the actives of the deleted drafts -> filter out
118
+ if (rootIsActiveEntity === null || key === 'DraftAdministrativeData') {
119
+ row[key] = parsed || null
120
+ } else if (rootIsActiveEntity !== null) {
121
+ if (mapping[TO_ACTIVE]) row[key] = parsed && parsed.IsActiveEntity !== false ? parsed : null
122
+ else row[key] = parsed && parsed.IsActiveEntity === rootIsActiveEntity ? parsed : null
123
+ }
97
124
  } else {
125
+ // > No expand convert the result directly.
98
126
  const rawValue = entry[mapping]
99
127
  // Assume a DB will not return undefined, but always null
100
128
  row[key] = this._convertValue(rawValue, conversionMapper.get(mapping))
@@ -195,10 +223,11 @@ class RawToExpanded {
195
223
  * @param {Array} configs - Array of instructions how to combine and expand the 1 to N results
196
224
  * @param {Array} queries - Same amount of queries as configs. Contains one query for each to many expand.
197
225
  * @param {boolean} one - SELECT.one has been used
226
+ * @param {object} rootEntity - the root entity
198
227
  * @returns {Promise<Array>} The complete expanded result set.
199
228
  */
200
- const rawToExpanded = (configs, queries, one) => {
201
- return new RawToExpanded(configs, queries, one).toExpanded().catch(err => {
229
+ const rawToExpanded = (configs, queries, one, rootEntity) => {
230
+ return new RawToExpanded(configs, queries, one, rootEntity).toExpanded().catch(err => {
202
231
  Promise.all(queries).catch(() => {})
203
232
  throw err
204
233
  })
@@ -2,7 +2,7 @@
2
2
  const rewrite = require('./rewrite')
3
3
  const input = require('./input')
4
4
  const integrity = require('./integrity')
5
- const { removeVirtuals: virtual } = require('./virtual')
5
+ const { convertVirtuals: virtual } = require('./virtual')
6
6
  // on
7
7
  const CREATE = require('./create')
8
8
  const READ = require('./read')
@@ -31,7 +31,7 @@ const _processComplexCategory = ({ row, key, val, category, req }) => {
31
31
 
32
32
  // propagate keys
33
33
  if (category === 'propagateForeignKeys') {
34
- propagateForeignKeys(key, row, categoryArgs.foreignKeyPropagations)
34
+ propagateForeignKeys(key, row, categoryArgs.foreignKeyPropagations, { onlyWriteCompositionEffective: true })
35
35
  return
36
36
  }
37
37
 
@@ -60,10 +60,10 @@ const _processComplexCategory = ({ row, key, val, category, req }) => {
60
60
  }
61
61
  }
62
62
 
63
- const _processCategory = ({ row, key, val, category, req, element, suppressErrorPropagation }) => {
63
+ const _processCategory = ({ category, row, key, element, val, req }) => {
64
64
  // use args only inside this if (sonar type error warning)
65
65
  if (typeof category === 'object') {
66
- _processComplexCategory({ row, key, val, category, req })
66
+ _processComplexCategory({ category, row, key, val, req })
67
67
  return
68
68
  }
69
69
 
@@ -75,7 +75,6 @@ const _processCategory = ({ row, key, val, category, req, element, suppressError
75
75
 
76
76
  // not null without default (for better error message)
77
77
  if (category === '!default' && val == null && req.event === 'CREATE') {
78
- if (suppressErrorPropagation) return
79
78
  req.error(400, 'ASSERT_NOT_NULL', key, [key])
80
79
  return
81
80
  }
@@ -87,19 +86,18 @@ const _processCategory = ({ row, key, val, category, req, element, suppressError
87
86
 
88
87
  // check for forbidden deep operations for association
89
88
  if (category === 'associationEffective' && (req.event === 'CREATE' || req.event === 'UPDATE')) {
90
- if (suppressErrorPropagation) return
91
89
  checkIfAssocDeep(element, val, req)
92
90
  }
93
91
  }
94
92
 
95
93
  const processorFn =
96
94
  req =>
97
- (row, key, element, plain, isRoot, pathSegments, suppressErrorPropagation = false) => {
95
+ ({ row, key, element, plain }) => {
98
96
  const categories = plain.categories
99
97
  const val = row[key]
100
98
 
101
99
  for (const category of categories) {
102
- _processCategory({ row, key, val, category, req, element, suppressErrorPropagation })
100
+ _processCategory({ category, row, key, element, val, req })
103
101
  }
104
102
  }
105
103
 
@@ -48,7 +48,7 @@ async function beforeDelete(req) {
48
48
  const dependents = getDependents(target, this.model)
49
49
  if (!dependents) return
50
50
 
51
- const keys = Object.keys(target.keys).filter(k => _isPrimitiveKey(target.elements[k]))
51
+ const keys = Object.keys(target.keys).filter(k => _isPrimitiveKey(target.elements[k]) && k !== 'IsActiveEntity')
52
52
  let select = SELECT(keys).from(req.target.name)
53
53
  if (req.query.DELETE.where) {
54
54
  select = select.where(req.query.DELETE.where)
@@ -1,6 +1,7 @@
1
1
  const cqn2cqn4sql = require('../../common/utils/cqn2cqn4sql')
2
2
  const generateAliases = require('../utils/generateAliases')
3
3
  const rewriteAsterisk = require('../../common/utils/rewriteAsterisk')
4
+ const { restoreLink } = require('../../common/utils/resolveView')
4
5
 
5
6
  const _isLinked = req => {
6
7
  if (req.query.INSERT && req.query.INSERT.entries) {
@@ -11,15 +12,6 @@ const _isLinked = req => {
11
12
  }
12
13
  }
13
14
 
14
- const _restoreLink = req => {
15
- if (req.query.INSERT && req.query.INSERT.entries) {
16
- if (Array.isArray(req.query.INSERT.entries)) req.data = req.query.INSERT.entries[0]
17
- else req.data = req.query.INSERT.entries
18
- } else if (req.query.UPDATE && req.query.UPDATE.data) {
19
- req.data = req.query.UPDATE.data
20
- }
21
- }
22
-
23
15
  function handler(req) {
24
16
  // REVISIT: req.target._unresolved for join queries
25
17
  if (!this.model || typeof req.query === 'string' /* || !req.target || req.target._unresolved */) {
@@ -39,7 +31,7 @@ function handler(req) {
39
31
 
40
32
  // REVISIT: should not be necessary
41
33
  // restore link to req.data
42
- if (linked) _restoreLink(req)
34
+ if (linked) restoreLink(req)
43
35
 
44
36
  if (streaming) req.query._streaming = streaming
45
37
  if (validationQuery) req.query._validationQuery = validationQuery
@@ -2,6 +2,7 @@
2
2
  // const UpdateResult = require('../result/UpdateResult')
3
3
 
4
4
  const { allKeysAreProvided } = require('../../cds-services/services/utils/handlerUtils')
5
+ const onlyKeysRemain = require('../../common/utils/onlyKeysRemain')
5
6
 
6
7
  const _enrichKeysFromOldData = (req, oldData) => {
7
8
  const data = req.data && (Array.isArray(req.data) ? req.data : [req.data])
@@ -27,7 +28,7 @@ const _addKeysToQuery = req => {
27
28
  }
28
29
 
29
30
  const _targetKeys = target => {
30
- return Object.values(target.keys)
31
+ return Object.values(target.keys || {})
31
32
  .filter(k => !(k.is2one || k.is2many))
32
33
  .map(k => ({ ref: [k.name] }))
33
34
  }
@@ -47,20 +48,27 @@ module.exports = async function (req) {
47
48
 
48
49
  if (req.target && !req.target._unresolved && req.target._isSingleton) {
49
50
  if (!allKeysAreProvided(req)) {
50
- const readKeysCQN = {
51
- SELECT: Object.assign({ columns: _targetKeys(req.target), one: true }, req.target.query.SELECT)
52
- }
51
+ // REVISIT: There can also be renaming... we better use resolveView here.
52
+ const targetKeys = _targetKeys(req.target)
53
+ const readKeysCQN =
54
+ req.target.query && req.target.query.SELECT
55
+ ? {
56
+ SELECT: Object.assign({ columns: targetKeys, one: true }, req.target.query.SELECT)
57
+ }
58
+ : SELECT.from(req.target).columns(targetKeys)
53
59
 
54
60
  // REVISIT: avoid additional read
55
61
  const current = await this._read(this.model, this.dbc, readKeysCQN, req)
56
62
  _enrichKeysFromOldData(req, current)
57
63
  }
58
64
 
59
- if (!req.query.UPDATE.where) {
65
+ if (!req.query.UPDATE.where && req.target.keys) {
60
66
  _addKeysToQuery(req)
61
67
  }
62
68
  }
63
69
 
70
+ if (onlyKeysRemain(req)) return
71
+
64
72
  try {
65
73
  const result = await this._update(this.model, this.dbc, req.query, req)
66
74
  return result
@@ -1,80 +1,44 @@
1
1
  /*
2
- * handler for filtering virtual fields by READ
2
+ * handler for converting virtual fields to { val: null, as: 'myVirtualField' } by READ
3
3
  */
4
4
 
5
- const _filter = (columns, target, model, remove = true, virtuals = []) => {
6
- if (!target) return virtuals
7
- let i = 0
8
- while (i < columns.length) {
9
- const col = columns[i]
5
+ const { ensureNoDraftsSuffix } = require('../../common/utils/draft')
6
+
7
+ const _convert = (columns, target, model) => {
8
+ if (!target) return
9
+ for (const col of columns) {
10
10
  const element = col.ref && target.elements[col.ref[col.ref.length - 1]]
11
11
  if (element) {
12
12
  if (element.virtual) {
13
- if (remove) {
14
- columns.splice(i, 1)
15
- i--
16
- }
17
- virtuals.push(element.name)
13
+ col.as = col.as || col.ref[col.ref.length - 1]
14
+ delete col.ref
15
+ col.val = (element.default && element.default.val) || null
18
16
  }
19
17
  if (col.expand && element.isAssociation) {
20
- const _virtuals = _filter(col.expand, model.definitions[element.target], model, remove)
21
- if (_virtuals.length) virtuals.push({ name: element.name, virtuals: _virtuals })
18
+ _convert(col.expand, model.definitions[element.target], model)
22
19
  }
23
20
  }
24
- i++
25
21
  }
26
- return virtuals
27
22
  }
28
23
 
29
- const _find = (columns, target, model) => _filter(columns, target, model, false)
30
-
31
- const _handleVirtuals = (req, model, remove) => {
24
+ const convertVirtuals = function (req, _model) {
25
+ const model = this.model || _model
32
26
  // target.name ensures it is not a union or join
33
- if (typeof req.query === 'string' || !req.target || typeof req.target.name !== 'string' || !model) return []
34
- const target = (!req.target._unresolved && req.target) || model.definitions[req.target.name.replace(/_drafts$/, '')]
27
+ if (typeof req.query === 'string' || !req.target || typeof req.target.name !== 'string' || !model) return
28
+ const target = (!req.target._unresolved && req.target) || model.definitions[ensureNoDraftsSuffix(req.target.name)]
35
29
  const columns = (req.query && req.query.SELECT && req.query.SELECT.columns) || []
36
- if (!target) return []
37
- if (!remove && !columns.length && target && target.elements) {
38
- for (const el in target.elements) {
39
- columns.push({ ref: [el] })
40
- }
41
- }
42
- const _handlerFn = (!remove && _find) || _filter
43
- return _handlerFn(columns, target, model)
44
- }
45
-
46
- const getVirtuals = (req, model) => _handleVirtuals(req, model)
47
-
48
- const removeVirtuals = function (req, model) {
49
- return _handleVirtuals(req, this.model || model, true)
50
- }
51
-
52
- const postProcessVirtuals = (virtuals, result) => {
53
- if (!result) return
54
- result = result.value && Object.keys(result).filter(k => !k.match(/^\W/)).length === 1 ? result.value : result
55
- const rows = Array.isArray(result) ? result : [result]
56
- for (const virtual of virtuals) {
57
- for (const row of rows) {
58
- if (typeof row !== 'object') continue
59
- if (virtual.name && row[virtual.name]) postProcessVirtuals(virtual.virtuals, row[virtual.name])
60
- else if (!(virtual in row)) row[virtual] = null
30
+ _convert(columns, target, model)
31
+ if (req.query.SELECT.from && req.query.SELECT.from.SET) {
32
+ for (const arg of req.query.SELECT.from.SET.args) {
33
+ const target = model.definitions[ensureNoDraftsSuffix(arg._target.name)]
34
+ const columns = (arg.SELECT && arg.SELECT.columns) || []
35
+ _convert(columns, target, model, true)
61
36
  }
62
37
  }
63
38
  }
64
39
 
65
- const getVirtualFromTarget = ({ target }) => {
66
- const virtuals = []
67
- for (const k in target.elements) {
68
- if (target.elements[k].virtual) virtuals.push(k)
69
- }
70
- return virtuals
71
- }
72
-
73
- removeVirtuals._initial = true
40
+ convertVirtuals._initial = true
74
41
 
75
42
  module.exports = {
76
- removeVirtuals,
77
- getVirtualFromTarget,
78
- postProcessVirtuals,
79
- getVirtuals
43
+ convertVirtuals
80
44
  }
@@ -3,7 +3,8 @@ const { timestampToISO } = require('../data-conversion/timestamp')
3
3
  const { hasDeepDelete, getDeepDeleteCQNs } = require('../../common/composition')
4
4
 
5
5
  const deleteFn = executeDeleteCQN => async (model, dbc, query, req) => {
6
- const ts = timestampToISO(req.timestamp)
6
+ const { user, locale, timestamp } = req
7
+ const isoTs = timestampToISO(timestamp)
7
8
 
8
9
  let result
9
10
  if (hasDeepDelete(model && model.definitions, query)) {
@@ -11,17 +12,19 @@ const deleteFn = executeDeleteCQN => async (model, dbc, query, req) => {
11
12
 
12
13
  // the delete chunks, i.e., how many deletes can be processed in parallel
13
14
  const chunks = []
14
- for (const each of cqns) chunks.push(each.length)
15
+ for (const each of cqns) {
16
+ if (each.length) chunks.push(each.length)
17
+ }
15
18
 
16
19
  cqns = getFlatArray(cqns)
17
20
 
18
21
  if (cqns.length === 0) return 0
19
22
 
20
- const results = await processCQNs(executeDeleteCQN, cqns, model, dbc, req.user, req.user.locale, ts, chunks)
23
+ const results = await processCQNs(executeDeleteCQN, cqns, model, dbc, user, locale, isoTs, chunks)
21
24
  // return number of affected rows of "root cqn"
22
25
  result = results[results.length - 1]
23
26
  } else {
24
- result = await executeDeleteCQN(model, dbc, query, req.user, req.user.locale, ts)
27
+ result = await executeDeleteCQN(model, dbc, query, user, locale, isoTs)
25
28
  }
26
29
 
27
30
  return result
@@ -1,20 +1,22 @@
1
- const { hasDeepInsert, getDeepInsertCQNs } = require('../../common/composition')
1
+ const { hasDeepInsert, getDeepInsertCQNs, cleanEmptyCompositionsOfMany } = require('../../common/composition')
2
2
  const { getFlatArray, processCQNs } = require('../utils/deep')
3
3
  const { timestampToISO } = require('../data-conversion/timestamp')
4
4
 
5
5
  const insert = executeInsertCQN => async (model, dbc, query, req) => {
6
- const ts = timestampToISO(req.timestamp)
6
+ const { user, locale, timestamp } = req
7
+ const isoTs = timestampToISO(timestamp)
7
8
 
8
9
  if (hasDeepInsert(model && model.definitions, query)) {
9
10
  const cqns = getFlatArray(getDeepInsertCQNs(model && model.definitions, query))
10
11
 
11
12
  // return array of individual results
12
13
  if (cqns.length === 0) return []
13
- const results = await processCQNs(executeInsertCQN, cqns, model, dbc, req.user, req.user.locale, ts)
14
+ const results = await processCQNs(executeInsertCQN, cqns, model, dbc, user, locale, isoTs)
14
15
  return getFlatArray(results)
15
16
  }
16
17
 
17
- return executeInsertCQN(model, dbc, query, req.user, req.user.locale, ts)
18
+ cleanEmptyCompositionsOfMany(model && model.definitions, query)
19
+ return executeInsertCQN(model, dbc, query, user, locale, isoTs)
18
20
  }
19
21
 
20
22
  module.exports = insert
@@ -21,52 +21,45 @@ function _createCountQuery(query) {
21
21
  subCountQuery.SELECT.columns = [{ val: 1 }]
22
22
  })
23
23
  }
24
+ if (query.SELECT._4odata) _query.SELECT._4odata = true
24
25
  return _query
25
26
  }
26
27
 
27
28
  const read = (executeSelectCQN, executeStreamCQN) => (model, dbc, query, req) => {
29
+ const { user, locale, timestamp } = req
30
+ const isoTs = timestampToISO(timestamp)
31
+
28
32
  if (query._streaming) {
29
33
  if (!query.SELECT || (query.SELECT && (!query.SELECT.columns || query.SELECT.columns.length !== 1))) {
30
34
  req.reject(400)
31
35
  }
32
- return executeStreamCQN(model, dbc, query, req.user, req.user.locale, timestampToISO(req.timestamp))
36
+ return executeStreamCQN(model, dbc, query, user, locale, isoTs)
33
37
  }
34
38
 
39
+ // needed in case of expand
40
+ query._rootEntity = req.target
41
+
35
42
  if (query.SELECT.count) {
36
43
  if (query.SELECT.limit) {
37
44
  const countQuery = _createCountQuery(query)
38
- const countResultPromise = executeSelectCQN(
39
- model,
40
- dbc,
41
- countQuery,
42
- req.user,
43
- req.user.locale,
44
- timestampToISO(req.timestamp)
45
- )
45
+ const countResultPromise = executeSelectCQN(model, dbc, countQuery, user, locale, isoTs)
46
46
  if (query.SELECT.limit.rows && query.SELECT.limit.rows.val === 0) {
47
47
  // We don't need to perform our result query
48
48
  return countResultPromise.then(countResult => _arrayWithCount([], countResult[0]._counted_))
49
49
  } else {
50
- const resultPromise = executeSelectCQN(
51
- model,
52
- dbc,
53
- query,
54
- req.user,
55
- req.user.locale,
56
- timestampToISO(req.timestamp)
57
- )
50
+ const resultPromise = executeSelectCQN(model, dbc, query, user, locale, isoTs)
58
51
  return Promise.all([countResultPromise, resultPromise]).then(([countResult, result]) =>
59
52
  _arrayWithCount(result, countResult[0]._counted_)
60
53
  )
61
54
  }
62
55
  } else {
63
- return executeSelectCQN(model, dbc, query, req.user, req.user.locale, timestampToISO(req.timestamp)).then(
64
- result => _arrayWithCount(result, result.length)
56
+ return executeSelectCQN(model, dbc, query, user, locale, isoTs).then(result =>
57
+ _arrayWithCount(result, result.length)
65
58
  )
66
59
  }
67
60
  }
68
61
 
69
- return executeSelectCQN(model, dbc, query, req.user, req.user.locale, timestampToISO(req.timestamp))
62
+ return executeSelectCQN(model, dbc, query, user, locale, isoTs)
70
63
  }
71
64
 
72
65
  module.exports = read
@@ -21,7 +21,10 @@ const run = (insert, read, update, deleet, cqn, sql) => (model, dbc, query, req,
21
21
  return update(model, dbc, query, req)
22
22
  }
23
23
 
24
- return cqn(model, dbc, query, req.user, req.user.locale, timestampToISO(req.timestamp))
24
+ const { user, locale, timestamp } = req
25
+ const isoTs = timestampToISO(timestamp)
26
+
27
+ return cqn(model, dbc, query, user, locale, isoTs)
25
28
  }
26
29
 
27
30
  module.exports = run
@@ -48,14 +48,15 @@ const _getFilteredCqns = (cqns, model) => {
48
48
  }
49
49
 
50
50
  const update = (executeUpdateCQN, executeSelectCQN) => async (model, dbc, query, req) => {
51
- const ts = timestampToISO(req.timestamp)
51
+ const { user, locale, timestamp } = req
52
+ const isoTs = timestampToISO(timestamp)
52
53
 
53
54
  if (hasDeepUpdate(model && model.definitions, query)) {
54
55
  // REVISIT: _activeData gets set in case of draftActivate for performance, but this is a layer violation
55
56
  let selectData = req._ && req._.query && req._.query._activeData
56
57
  if (!selectData) {
57
58
  // REVISIT: avoid additional read
58
- selectData = await selectDeepUpdateData(model && model.definitions, query, req)
59
+ selectData = await selectDeepUpdateData(model && model.definitions, query, req, false, false, cds.db)
59
60
  } else {
60
61
  selectData = [selectData]
61
62
  }
@@ -70,14 +71,14 @@ const update = (executeUpdateCQN, executeSelectCQN) => async (model, dbc, query,
70
71
  cqns = _getFilteredCqns(getFlatArray(cqns), model)
71
72
 
72
73
  if (cqns.length === 0) return 0
73
- const results = await processCQNs(executeUpdateCQN, cqns, model, dbc, req.user, req.user.locale, ts, chunks)
74
+ const results = await processCQNs(executeUpdateCQN, cqns, model, dbc, user, locale, isoTs, chunks)
74
75
  // return number of affected rows of "root cqn", if an update, 1 otherwise (as not update of root but its children)
75
76
  if (cqns[0].UPDATE) return results[0]
76
77
  return 1
77
78
  }
78
79
 
79
80
  // REVISIT: don't invoke setters if not needed
80
- return executeUpdateCQN(model, dbc, query, req.user, req.user.locale, ts)
81
+ return executeUpdateCQN(model, dbc, query, user, locale, isoTs)
81
82
  }
82
83
 
83
84
  module.exports = update
@@ -1,6 +1,16 @@
1
+ const cds = require('../../cds')
2
+
1
3
  const BaseBuilder = require('./BaseBuilder')
2
4
  const { flattenStructuredWhereHaving } = require('../../common/utils/structured')
3
5
 
6
+ const SQLITE_DATETIME_FUNCTIONS = new Set(['year', 'month', 'day', 'second', 'hour', 'minute'])
7
+ const OPERATORS = new Set(['=', '!=', '<>', '<', '>', '<=', '>='])
8
+
9
+ function _fillAfterDot(val) {
10
+ const [beforeDot, afterDot = ''] = val.split('.')
11
+ return `${beforeDot}.${afterDot.padEnd(3, '0')}`
12
+ }
13
+
4
14
  /**
5
15
  * ExpressionBuilder is used to take a part of a CQN object as an input and to build an object representing an expression
6
16
  * with SQL string and values to be used with a prepared statement.
@@ -127,7 +137,30 @@ class ExpressionBuilder extends BaseBuilder {
127
137
  * @returns {number}
128
138
  * @private
129
139
  */
140
+ // eslint-disable-next-line complexity
130
141
  _reseverdKeyWords(objects, i) {
142
+ if (objects[i] === 'not' && objects[i + 1].func) {
143
+ objects[i + 1].func = `not ${objects[i + 1].func}`
144
+ return 1
145
+ }
146
+ if (objects[i].func || (objects[i + 2] && objects[i + 2].func)) {
147
+ // sqlite requires leading 0 for numbers in datetime functions
148
+ const f = objects[i].func ? i : OPERATORS.has(objects[i + 1]) ? i + 2 : i - 2
149
+ const v = objects[i].val ? i : OPERATORS.has(objects[i + 1]) ? i + 2 : i - 2
150
+ if (objects[f] && SQLITE_DATETIME_FUNCTIONS.has(objects[f].func) && cds.db && cds.db.kind === 'sqlite') {
151
+ if (objects[v] && objects[v].val !== undefined && typeof objects[v].val === 'number') {
152
+ objects[v] = { val: `${objects[v].val < 10 ? 0 : ''}${objects[v].val}` }
153
+ if (objects[f].func === 'second') objects[v].val = _fillAfterDot(objects[v].val)
154
+ }
155
+ }
156
+ // odata indexof function returns the zero-based character position of the first occurrence
157
+ if (this._options._4odata && objects[i].func && objects[i].func === 'indexof') {
158
+ if (objects[i + 2] && objects[i + 2].val !== undefined) objects[i + 2].val++
159
+ else if (objects[i - 2] && objects[i - 2].val !== undefined) this._outputObj.sql[i - 2]++
160
+ }
161
+ return 0
162
+ }
163
+
131
164
  if ((objects[i + 1] === '=' || objects[i + 1] === '!=') && objects[i + 2] && objects[i + 2].val === null) {
132
165
  this._addNullOrNotNull(objects[i], objects[i + 1])
133
166
  return 3
@@ -304,8 +337,8 @@ class ExpressionBuilder extends BaseBuilder {
304
337
 
305
338
  _xprOutputFromElement(element) {
306
339
  this._options.objectKey = 'xpr'
307
-
308
- this._addToOutputObj(new ExpressionBuilder(element, this._options, this._csn).build(), true)
340
+ // new instance of subclass builder
341
+ this._addToOutputObj(new this.constructor(element, this._options, this._csn).build(), true)
309
342
  }
310
343
 
311
344
  /**
@@ -6,7 +6,8 @@ const cqn2sqlFunc = {
6
6
  indexof: 'locate',
7
7
  day: 'dayofmonth',
8
8
  date: 'to_date',
9
- time: 'to_time'
9
+ time: 'to_time',
10
+ average: 'avg'
10
11
  }
11
12
 
12
13
  /**
@@ -66,6 +67,7 @@ class FunctionBuilder extends BaseBuilder {
66
67
  _handleFunction() {
67
68
  const functionName = this._functionName()
68
69
  const args = this._functionArgs()
70
+
69
71
  if (!args) {
70
72
  // > arg-less func such as current_date
71
73
  this._outputObj.sql.push(functionName)
@@ -89,12 +91,24 @@ class FunctionBuilder extends BaseBuilder {
89
91
  return
90
92
  }
91
93
 
94
+ if (functionName === 'countdistinct') {
95
+ this._handleCountdistinct(args)
96
+ return
97
+ }
98
+
92
99
  this._outputObj.sql.push(functionName, '(')
93
100
  if (typeof args === 'string') this._outputObj.sql.push(args)
94
101
  else this._addFunctionArgs(args)
95
102
  this._outputObj.sql.push(')')
96
103
  }
97
104
 
105
+ _handleCountdistinct(args) {
106
+ this._outputObj.sql.push('count', '(', 'DISTINCT')
107
+ if (typeof args === 'string') this._outputObj.sql.push(args)
108
+ else this._addFunctionArgs(args)
109
+ this._outputObj.sql.push(')')
110
+ }
111
+
98
112
  _handleContains(args) {
99
113
  this._handleLikewiseFunc(args)
100
114
  }
@@ -165,7 +179,8 @@ class FunctionBuilder extends BaseBuilder {
165
179
  res.push(sql)
166
180
  this._outputObj.values.push(...values)
167
181
  } else if (arg.func) {
168
- const { sql, values } = new FunctionBuilder(arg, this._options, this._csn).build()
182
+ // new instance of subclass builder
183
+ const { sql, values } = new this.constructor(arg, this._options, this._csn).build()
169
184
  res.push(sql)
170
185
  this._outputObj.values.push(...values)
171
186
  } else if (arg.xpr) {