@sap/cds 5.5.3 → 5.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 (227) hide show
  1. package/CHANGELOG.md +134 -1
  2. package/apis/services.d.ts +27 -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/index.js +1 -1
  12. package/lib/compile/to/sql.js +22 -2
  13. package/lib/connect/bindings.js +2 -1
  14. package/lib/connect/index.js +1 -1
  15. package/lib/core/infer.js +1 -1
  16. package/lib/core/reflect.js +3 -1
  17. package/lib/env/index.js +175 -41
  18. package/lib/env/requires.js +24 -3
  19. package/lib/i18n/localize.js +33 -5
  20. package/lib/index.js +7 -6
  21. package/lib/log/format/kibana.js +6 -2
  22. package/lib/ql/DELETE.js +1 -1
  23. package/lib/ql/INSERT.js +1 -1
  24. package/lib/ql/Query.js +13 -10
  25. package/lib/ql/SELECT.js +15 -8
  26. package/lib/ql/UPDATE.js +1 -1
  27. package/lib/ql/Whereable.js +5 -0
  28. package/lib/req/context.js +87 -37
  29. package/lib/req/{impl.js → request.js} +1 -1
  30. package/lib/req/{res.js → response.js} +0 -0
  31. package/lib/serve/Service-api.js +1 -1
  32. package/lib/serve/Service-dispatch.js +12 -2
  33. package/lib/serve/Service-handlers.js +21 -7
  34. package/lib/serve/Service-methods.js +1 -1
  35. package/lib/serve/Transaction.js +7 -6
  36. package/lib/serve/index.js +1 -1
  37. package/lib/utils/axios.js +7 -0
  38. package/lib/utils/data.js +1 -1
  39. package/libx/_runtime/audit/Service.js +18 -18
  40. package/libx/_runtime/audit/generic/personal/access.js +1 -1
  41. package/libx/_runtime/audit/generic/personal/modification.js +3 -2
  42. package/libx/_runtime/audit/generic/personal/utils.js +23 -63
  43. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +6 -0
  44. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +37 -35
  45. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -1
  46. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +3 -1
  47. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +5 -5
  48. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +1 -1
  49. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -7
  50. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +84 -34
  51. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +10 -4
  52. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +9 -3
  53. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +8 -6
  54. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -3
  55. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +13 -11
  56. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +11 -95
  57. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +17 -11
  58. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +2 -1
  59. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +6 -2
  60. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +3 -34
  61. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -3
  62. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +48 -18
  63. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +10 -5
  64. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +1 -1
  65. package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +1 -1
  66. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +1 -3
  67. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +9 -2
  68. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +20 -21
  69. package/libx/_runtime/cds-services/services/utils/columns.js +6 -1
  70. package/libx/_runtime/cds-services/services/utils/compareJson.js +1 -8
  71. package/libx/_runtime/cds-services/services/utils/differ.js +7 -26
  72. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -4
  73. package/libx/_runtime/cds-services/util/assert.js +29 -13
  74. package/libx/_runtime/cds.js +2 -1
  75. package/libx/_runtime/common/aspects/Association.js +72 -0
  76. package/libx/_runtime/common/aspects/any.js +8 -45
  77. package/libx/_runtime/common/aspects/entity.js +0 -1
  78. package/libx/_runtime/common/aspects/relation.js +40 -0
  79. package/libx/_runtime/common/aspects/utils.js +73 -1
  80. package/libx/_runtime/common/auth/strategies/utils/uaa.js +10 -14
  81. package/libx/_runtime/common/composition/data.js +3 -2
  82. package/libx/_runtime/common/composition/delete.js +3 -1
  83. package/libx/_runtime/common/composition/tree.js +23 -18
  84. package/libx/_runtime/common/composition/utils.js +34 -8
  85. package/libx/_runtime/common/error/frontend.js +6 -1
  86. package/libx/_runtime/common/generic/auth.js +15 -13
  87. package/libx/_runtime/common/generic/crud.js +2 -2
  88. package/libx/_runtime/common/generic/etag.js +11 -8
  89. package/libx/_runtime/common/generic/input.js +3 -3
  90. package/libx/_runtime/common/generic/paging.js +9 -5
  91. package/libx/_runtime/common/generic/put.js +3 -2
  92. package/libx/_runtime/common/generic/sorting.js +3 -3
  93. package/libx/_runtime/common/generic/temporal.js +3 -3
  94. package/libx/_runtime/common/toggles/alpha.js +1 -1
  95. package/libx/_runtime/common/utils/cqn.js +20 -1
  96. package/libx/_runtime/common/utils/cqn2cqn4sql.js +125 -139
  97. package/libx/_runtime/common/utils/csn.js +50 -52
  98. package/libx/_runtime/common/utils/foreignKeyPropagations.js +41 -176
  99. package/libx/_runtime/common/utils/generateOnCond.js +40 -70
  100. package/libx/_runtime/common/utils/{enrichWithKeysFromWhere.js → keys.js} +29 -28
  101. package/libx/_runtime/common/utils/postProcessing.js +3 -0
  102. package/libx/_runtime/common/utils/propagateForeignKeys.js +84 -0
  103. package/libx/_runtime/common/utils/resolveStructured.js +1 -1
  104. package/libx/_runtime/common/utils/resolveView.js +19 -9
  105. package/libx/_runtime/common/utils/rewriteAsterisks.js +94 -0
  106. package/libx/_runtime/common/utils/search2cqn4sql.js +9 -8
  107. package/libx/_runtime/common/utils/template.js +54 -46
  108. package/libx/_runtime/db/Service.js +9 -2
  109. package/libx/_runtime/db/expand/expandCQNToJoin.js +10 -24
  110. package/libx/_runtime/db/expand/rawToExpanded.js +2 -1
  111. package/libx/_runtime/db/generic/create.js +1 -0
  112. package/libx/_runtime/db/generic/input.js +7 -11
  113. package/libx/_runtime/db/generic/integrity.js +2 -2
  114. package/libx/_runtime/db/generic/rewrite.js +2 -5
  115. package/libx/_runtime/db/generic/update.js +1 -0
  116. package/libx/_runtime/db/query/read.js +10 -5
  117. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +6 -0
  118. package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -2
  119. package/libx/_runtime/db/sql-builder/annotations.js +1 -0
  120. package/libx/_runtime/db/utils/columns.js +14 -43
  121. package/libx/_runtime/db/utils/deep.js +5 -7
  122. package/libx/_runtime/fiori/generic/activate.js +3 -2
  123. package/libx/_runtime/fiori/generic/before.js +2 -2
  124. package/libx/_runtime/fiori/generic/cancel.js +3 -2
  125. package/libx/_runtime/fiori/generic/delete.js +3 -2
  126. package/libx/_runtime/fiori/generic/edit.js +2 -2
  127. package/libx/_runtime/fiori/generic/new.js +2 -2
  128. package/libx/_runtime/fiori/generic/patch.js +2 -2
  129. package/libx/_runtime/fiori/generic/prepare.js +2 -2
  130. package/libx/_runtime/fiori/generic/read.js +17 -63
  131. package/libx/_runtime/fiori/generic/readOverDraft.js +4 -4
  132. package/libx/_runtime/fiori/uiflex/extensibility/index.cds +15 -0
  133. package/libx/_runtime/fiori/uiflex/extensibility/index.js +148 -0
  134. package/libx/_runtime/fiori/uiflex/handler/transformREAD.js +119 -0
  135. package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +43 -0
  136. package/libx/_runtime/fiori/uiflex/handler/transformWRITE.js +62 -0
  137. package/libx/_runtime/fiori/uiflex/index.js +35 -0
  138. package/libx/_runtime/fiori/uiflex/utils.js +78 -0
  139. package/libx/_runtime/fiori/utils/handler.js +3 -13
  140. package/libx/_runtime/fiori/utils/where.js +6 -1
  141. package/libx/_runtime/hana/Service.js +5 -2
  142. package/libx/_runtime/hana/execute.js +1 -1
  143. package/libx/_runtime/hana/pool.js +12 -11
  144. package/libx/_runtime/hana/search2cqn4sql.js +34 -43
  145. package/libx/_runtime/hana/searchToContains.js +3 -3
  146. package/libx/_runtime/index.js +5 -2
  147. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  148. package/libx/_runtime/messaging/common-utils/AMQPClient.js +16 -3
  149. package/libx/_runtime/messaging/common-utils/connections.js +11 -14
  150. package/libx/_runtime/messaging/common-utils/naming-conventions.js +1 -1
  151. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -1
  152. package/libx/_runtime/messaging/enterprise-messaging.js +1 -1
  153. package/libx/_runtime/messaging/message-queuing.js +18 -0
  154. package/libx/_runtime/remote/Service.js +14 -2
  155. package/libx/_runtime/remote/utils/client-types.d.ts +7 -0
  156. package/libx/_runtime/remote/utils/client.js +117 -23
  157. package/libx/_runtime/sqlite/Service.js +4 -3
  158. package/libx/_runtime/sqlite/convertAssocToOneManaged.js +1 -3
  159. package/libx/_runtime/sqlite/execute.js +1 -1
  160. package/libx/gql/GraphQLAdapter.js +33 -0
  161. package/libx/gql/constants/adapter.js +69 -0
  162. package/libx/gql/constants/cds.js +18 -0
  163. package/libx/gql/constants/graphql.js +33 -0
  164. package/libx/gql/resolvers/crud/create.js +15 -0
  165. package/libx/gql/resolvers/crud/delete.js +24 -0
  166. package/libx/gql/resolvers/crud/index.js +6 -0
  167. package/libx/gql/resolvers/crud/read.js +25 -0
  168. package/libx/gql/resolvers/crud/update.js +31 -0
  169. package/libx/gql/resolvers/crud/utils/index.js +36 -0
  170. package/libx/gql/resolvers/field.js +5 -0
  171. package/libx/gql/resolvers/index.js +7 -0
  172. package/libx/gql/resolvers/mutation.js +23 -0
  173. package/libx/gql/resolvers/parse/ast/enrich.js +51 -0
  174. package/libx/gql/resolvers/parse/ast/fragment.js +11 -0
  175. package/libx/gql/resolvers/parse/ast/fromObject.js +39 -0
  176. package/libx/gql/resolvers/parse/ast/index.js +3 -0
  177. package/libx/gql/resolvers/parse/ast/meta.js +4 -0
  178. package/libx/gql/resolvers/parse/ast/variable.js +7 -0
  179. package/libx/gql/resolvers/parse/ast2cqn/columns.js +42 -0
  180. package/libx/gql/resolvers/parse/ast2cqn/entries.js +31 -0
  181. package/libx/gql/resolvers/parse/ast2cqn/index.js +8 -0
  182. package/libx/gql/resolvers/parse/ast2cqn/limit.js +6 -0
  183. package/libx/gql/resolvers/parse/ast2cqn/orderBy.js +24 -0
  184. package/libx/gql/resolvers/parse/ast2cqn/utils/index.js +3 -0
  185. package/libx/gql/resolvers/parse/ast2cqn/where.js +70 -0
  186. package/libx/gql/resolvers/parse/utils/index.js +8 -0
  187. package/libx/gql/resolvers/query.js +13 -0
  188. package/libx/gql/resolvers/root.js +34 -0
  189. package/libx/gql/schema/generate.js +18 -0
  190. package/libx/gql/schema/index.js +5 -0
  191. package/libx/gql/schema/mutation.js +76 -0
  192. package/libx/gql/schema/query.js +108 -0
  193. package/libx/gql/schema/typeDefMap.js +45 -0
  194. package/libx/gql/schema/utils/index.js +54 -0
  195. package/libx/gql/utils/index.js +12 -0
  196. package/libx/{_runtime/odata/cqn2odata.js → odata/cqn2odata/index.js} +39 -100
  197. package/libx/odata/index.js +80 -0
  198. package/libx/odata/odata2cqn/afterburner.js +170 -0
  199. package/libx/{_runtime/odata/odata2cqn.pegjs → odata/odata2cqn/grammar.pegjs} +102 -123
  200. package/libx/odata/odata2cqn/index.js +3 -0
  201. package/libx/odata/odata2cqn/parser.js +1 -0
  202. package/libx/odata/utils/index.js +64 -0
  203. package/libx/rest/RestAdapter.js +101 -0
  204. package/libx/rest/RestRequest.js +30 -0
  205. package/libx/rest/index.js +3 -0
  206. package/libx/rest/middleware/auth.js +22 -0
  207. package/libx/rest/middleware/content.js +15 -0
  208. package/libx/rest/middleware/create.js +40 -0
  209. package/libx/rest/middleware/delete.js +20 -0
  210. package/libx/rest/middleware/error.js +56 -0
  211. package/libx/rest/middleware/operation.js +39 -0
  212. package/libx/rest/middleware/parse.js +90 -0
  213. package/libx/rest/middleware/read.js +29 -0
  214. package/libx/rest/middleware/update.js +42 -0
  215. package/libx/rest/utils/data.js +65 -0
  216. package/package.json +4 -1
  217. package/server.js +42 -29
  218. package/lib/req/cls.js +0 -39
  219. package/libx/_runtime/cds-services/services/utils/diff.js +0 -53
  220. package/libx/_runtime/cds-services/util/auditlog.js +0 -247
  221. package/libx/_runtime/cds-services/util/xsenv.js +0 -51
  222. package/libx/_runtime/common/utils/backlinks.js +0 -83
  223. package/libx/_runtime/common/utils/rewriteAsterisk.js +0 -72
  224. package/libx/_runtime/odata/index.js +0 -55
  225. package/libx/_runtime/odata/odata2cqn.js +0 -1
  226. package/libx/_runtime/odata/readToCqn.js +0 -129
  227. package/libx/_runtime/remote/cqn2odata/index.js +0 -2
@@ -1,4 +1,5 @@
1
1
  const getColumns = require('../utils/columns')
2
+ // REVISIT: Remove support for @odata.on... with @sap/cds ^6
2
3
  const ANNOTATIONS = ['@cds.on.insert', '@odata.on.insert', '@cds.on.update', '@odata.on.update']
3
4
  const { ensureNoDraftsSuffix } = require('../../common/utils/draft')
4
5
 
@@ -3,17 +3,6 @@ const resolveStructured = require('../../common/utils/resolveStructured')
3
3
 
4
4
  const { DRAFT_COLUMNS } = require('../../common/constants/draft')
5
5
 
6
- const _filterAssociationAndComposition = (entity, columnName) => {
7
- return entity.elements[columnName]
8
- ? entity.elements[columnName].is2one !== true && entity.elements[columnName].is2many !== true
9
- : true
10
- }
11
- const _filterDraft = (entity, columnName) => {
12
- return DRAFT_COLUMNS.includes(columnName) !== true && _filterAssociationAndComposition(entity, columnName)
13
- }
14
-
15
- const _mapNameToValue = (entity, array) => array.map(key => entity.elements[key] || { name: key })
16
-
17
6
  /**
18
7
  * This method gets all columns for an entity.
19
8
  * It includes the generated foreign keys from managed associations, structured elements and complex and custom types.
@@ -22,42 +11,24 @@ const _mapNameToValue = (entity, array) => array.map(key => entity.elements[key]
22
11
  * @param entity - the csn entity
23
12
  * @returns {Array} - array of columns
24
13
  */
25
- const getColumns = entity => {
14
+ const getColumns = (entity, { db, onlyKeys } = { db: true, onlyKeys: false }) => {
26
15
  // REVISIT is this correct or just a problem that occurs because of new structure we do not deal with yet?
27
- if (!entity.elements) return []
28
-
29
- let columnNames
16
+ if (!(entity && entity.elements)) return []
17
+ const columnNames = []
30
18
  // REVISIT!!!
31
- const proto = Object.getPrototypeOf(entity.elements)
32
- if (proto) columnNames = Object.keys(proto)
33
- else columnNames = Object.keys(entity.elements)
34
-
35
- if (cds.env.effective.odata.structs) {
36
- const toBeDeleted = []
37
- for (const column of columnNames) {
38
- const element = entity.elements[column]
39
- if (element && element.elements) {
40
- toBeDeleted.push(column)
41
- columnNames.push(
42
- ...resolveStructured({ structName: element.name, structProperties: [] }, element.elements, false)
43
- )
44
- }
19
+ const elements = Object.getPrototypeOf(entity.elements) || entity.elements
20
+ for (const elementName in elements) {
21
+ const element = elements[elementName]
22
+ if (onlyKeys && !element.key) continue
23
+ if (element.isAssociation) continue
24
+ if (db && entity._isDraftEnabled && DRAFT_COLUMNS.includes(elementName)) continue
25
+ if (cds.env.effective.odata.structs && element.elements) {
26
+ columnNames.push(...resolveStructured({ structName: elementName, structProperties: [] }, element.elements, false))
27
+ continue
45
28
  }
46
-
47
- columnNames = columnNames.filter(col => !toBeDeleted.includes(col))
29
+ columnNames.push(elementName)
48
30
  }
49
-
50
- if (entity && entity._isDraftEnabled) {
51
- return _mapNameToValue(
52
- entity,
53
- columnNames.filter(key => _filterDraft(entity, key))
54
- )
55
- }
56
-
57
- return _mapNameToValue(
58
- entity,
59
- columnNames.filter(key => _filterAssociationAndComposition(entity, key))
60
- )
31
+ return columnNames.map(name => elements[name] || { name })
61
32
  }
62
33
 
63
34
  module.exports = getColumns
@@ -12,14 +12,12 @@ function getFlatArray(arg) {
12
12
 
13
13
  async function _processChunk(processFn, model, dbc, cqns, user, locale, ts, indexes, results) {
14
14
  const promises = []
15
- for (const i of indexes) {
16
- promises.push(processFn(model, dbc, cqns[i], user, locale, ts))
17
- }
18
- const promisesResults = await Promise.all(promises)
15
+ for (const i of indexes) promises.push(processFn(model, dbc, cqns[i], user, locale, ts))
16
+ const promisesResults = await Promise.allSettled(promises)
17
+ const firstRejected = promisesResults.find(r => r.status === 'rejected')
18
+ if (firstRejected) throw firstRejected.reason
19
19
  // put results of queries into correct place of return results
20
- for (let i = 0; i < promisesResults.length; i++) {
21
- results[indexes[i]] = promisesResults[i]
22
- }
20
+ for (let i = 0; i < promisesResults.length; i++) results[indexes[i]] = promisesResults[i].value
23
21
  }
24
22
 
25
23
  async function processCQNs(processFn, cqns, model, dbc, user, locale, ts, chunks) {
@@ -93,6 +93,7 @@ const _draftCompositionTree = async (service, req) => {
93
93
  }
94
94
 
95
95
  if (results[1].length === 1) {
96
+ _removeIsActiveEntityRecursively(results[1])
96
97
  activeData = results[1][0]
97
98
  }
98
99
 
@@ -175,8 +176,8 @@ const _handler = async function (req) {
175
176
  return result
176
177
  }
177
178
 
178
- module.exports = function () {
179
+ module.exports = cds.service.impl(function () {
179
180
  for (const entity of Object.values(this.entities).filter(e => e._isDraftEnabled)) {
180
181
  this.on('draftActivate', entity, _handler)
181
182
  }
182
- }
183
+ })
@@ -186,7 +186,7 @@ const _deleteCancel = async function (req) {
186
186
  await _addDraftDataFromExistingDraft(req, this)
187
187
  }
188
188
 
189
- module.exports = function () {
189
+ module.exports = cds.service.impl(function () {
190
190
  _new._initial = true
191
191
  _patchUpdate._initial = true
192
192
  _deleteCancel._initial = true
@@ -196,4 +196,4 @@ module.exports = function () {
196
196
  this.before(['PATCH', 'UPDATE'], entity, _patchUpdate)
197
197
  this.before(['DELETE', 'CANCEL'], entity, _deleteCancel)
198
198
  }
199
- }
199
+ })
@@ -1,3 +1,4 @@
1
+ const cds = require('../../cds')
1
2
  const { deleteDraft } = require('../utils/delete')
2
3
 
3
4
  /**
@@ -11,8 +12,8 @@ const _handler = function (req) {
11
12
  return deleteDraft(req, this)
12
13
  }
13
14
 
14
- module.exports = function () {
15
+ module.exports = cds.service.impl(function () {
15
16
  for (const entity of Object.values(this.entities).filter(e => e._isDraftEnabled)) {
16
17
  this.on('CANCEL', entity, _handler)
17
18
  }
18
- }
19
+ })
@@ -1,3 +1,4 @@
1
+ const cds = require('../../cds')
1
2
  const { deleteDraft } = require('../utils/delete')
2
3
 
3
4
  /**
@@ -13,8 +14,8 @@ const _handler = function (req) {
13
14
  return deleteDraft(req, this, true)
14
15
  }
15
16
 
16
- module.exports = function () {
17
+ module.exports = cds.service.impl(function () {
17
18
  for (const entity of Object.values(this.entities).filter(e => e._isDraftEnabled)) {
18
19
  this.on('DELETE', entity, _handler)
19
20
  }
20
- }
21
+ })
@@ -172,8 +172,8 @@ const _handler = async function (req) {
172
172
  return Object.assign({}, results[0][0], { HasDraftEntity: false, HasActiveEntity: true, IsActiveEntity: false })
173
173
  }
174
174
 
175
- module.exports = function () {
175
+ module.exports = cds.service.impl(function () {
176
176
  for (const entity of Object.values(this.entities).filter(e => e._isDraftEnabled)) {
177
177
  this.on('EDIT', entity, _handler)
178
178
  }
179
- }
179
+ })
@@ -96,8 +96,8 @@ const _handler = async function (req, next) {
96
96
  return result[0]
97
97
  }
98
98
 
99
- module.exports = function () {
99
+ module.exports = cds.service.impl(function () {
100
100
  for (const entity of Object.values(this.entities).filter(e => e._isDraftEnabled)) {
101
101
  this.on('NEW', entity, _handler)
102
102
  }
103
- }
103
+ })
@@ -91,8 +91,8 @@ const _handler = async function (req) {
91
91
  return result[0]
92
92
  }
93
93
 
94
- module.exports = function () {
94
+ module.exports = cds.service.impl(function () {
95
95
  for (const entity of Object.values(this.entities).filter(e => e._isDraftEnabled)) {
96
96
  this.on('PATCH', entity, _handler)
97
97
  }
98
- }
98
+ })
@@ -50,8 +50,8 @@ const _handler = async function (req) {
50
50
  return result[0]
51
51
  }
52
52
 
53
- module.exports = function () {
53
+ module.exports = cds.service.impl(function () {
54
54
  for (const entity of Object.values(this.entities).filter(e => e._isDraftEnabled)) {
55
55
  this.on('draftPrepare', entity, _handler)
56
56
  }
57
- }
57
+ })
@@ -1,7 +1,7 @@
1
1
  const cds = require('../../cds')
2
2
  const { SELECT } = cds.ql
3
3
 
4
- const cqn2cqn4sql = require('../../common/utils/cqn2cqn4sql')
4
+ const { cqn2cqn4sql } = require('../../common/utils/cqn2cqn4sql')
5
5
  const { getElementDeep } = require('../../common/utils/csn')
6
6
 
7
7
  const { DRAFT_COLUMNS, DRAFT_COLUMNS_MAP, SCENARIO } = require('../../common/constants/draft')
@@ -15,7 +15,6 @@ const {
15
15
  getEnrichedCQN,
16
16
  removeDraftUUIDIfNecessary,
17
17
  replaceRefWithDraft,
18
- hasKeyInWhere,
19
18
  filterKeys
20
19
  } = require('../utils/handler')
21
20
  const { deleteCondition, readAndDeleteKeywords, removeIsActiveEntityRecursively } = require('../utils/where')
@@ -186,34 +185,6 @@ function _copyArray(array) {
186
185
  })
187
186
  }
188
187
 
189
- const _whereContainsKeys = (req, whereDraft) => {
190
- const keys = _getTargetKeys(req)
191
- if (whereDraft.length < keys.length * 4 - 1) {
192
- return false
193
- }
194
-
195
- let i = 0
196
- let keyCount = 0
197
- while (i < whereDraft.length) {
198
- const element = whereDraft[i]
199
- const op = whereDraft[i + 1]
200
- if (element.ref && keys.some(x => x === element.ref[element.ref.length - 1]) && op === '=') {
201
- // op is EQ by keys
202
- i = i + 4
203
- keyCount++
204
- continue
205
- }
206
-
207
- i++
208
- }
209
-
210
- return keyCount === keys.length
211
- }
212
-
213
- const _isValidActive = (isActiveEntity, req, whereDraft) => {
214
- return isActiveEntity.op === '=' && _isTrue(isActiveEntity.value.val) && _whereContainsKeys(req, whereDraft)
215
- }
216
-
217
188
  const _isValidDraftOfWhichIAmOwner = isActiveEntity => {
218
189
  return isActiveEntity.op === '=' && _isFalse(isActiveEntity.value.val)
219
190
  }
@@ -275,7 +246,7 @@ const _filterDraftColumnsBySelected = (draftColumns, columns) => {
275
246
  )
276
247
  }
277
248
 
278
- const _isOnlyCount = columns => columns.length === 1 && columns[0].as === '_counted_'
249
+ const _isOnlyCount = columns => columns.length === 1 && (columns[0].as === '_counted_' || columns[0].as === '$count')
279
250
 
280
251
  const _getOuterMostColumns = (columnsFromRequest, additionalDraftColumns) => {
281
252
  if (_isOnlyCount(columnsFromRequest)) return columnsFromRequest
@@ -428,18 +399,6 @@ const _allActive = (req, columns, model) => {
428
399
  }
429
400
  }
430
401
 
431
- const _active = (req, draftWhere, columns) => {
432
- const { table } = _getTableName(req)
433
-
434
- const outerMostColumns = _getOuterMostColumns(columns, _getDraftPropertiesDetermineDraft(req, draftWhere))
435
-
436
- const cqn = SELECT.from(table).columns(...outerMostColumns)
437
-
438
- draftWhere = _getWhereWithAppendedDraftRestrictions(draftWhere, req)
439
-
440
- return { cqn: getEnrichedCQN(cqn, req.query.SELECT, draftWhere), scenario: SCENARIO.ACTIVE }
441
- }
442
-
443
402
  const _activeWithoutDraft = (req, draftWhere, columns) => {
444
403
  const { table } = _getTableName(req, true)
445
404
  const draftName = table.ref[0]
@@ -805,7 +764,7 @@ const _getUnionCQN = (req, draftName, columns, subSelect, draftWhere) => {
805
764
  _alignAliasForUnion(ensureNoDraftsSuffix(req.target.name), req.query.SELECT.from.as, subSelect)
806
765
  ])
807
766
 
808
- return union.columns({ func: 'sum', args: [{ ref: ['_counted_'] }], as: '_counted_' })
767
+ return union.columns({ func: 'sum', args: [{ ref: ['$count'] }], as: '$count' })
809
768
  }
810
769
 
811
770
  const enrichedColumns = _getOrderByEnrichedColumns(req.query.SELECT.orderBy, columns)
@@ -941,9 +900,6 @@ const _validatedWithSiblingInProcess = (req, draftWhere, draftParameters, column
941
900
  }
942
901
  }
943
902
 
944
- const _validatedActive = (req, draftWhere, draftParameters, columns) =>
945
- _isValidActive(draftParameters.isActiveEntity, req, draftWhere) && _active(req, draftWhere, columns)
946
-
947
903
  const _validatedDraftOfWhichIAmOwner = (req, draftWhere, draftParameters, columns) =>
948
904
  _isValidDraftOfWhichIAmOwner(draftParameters.isActiveEntity) && _draftOfWhichIAmOwner(req, draftWhere, columns)
949
905
 
@@ -989,20 +945,16 @@ const _generateCQN = (reqOriginal, req, columns, model) => {
989
945
  return _allActive(req, columns, model)
990
946
  }
991
947
 
992
- if (req.query.SELECT.where.length === 1 && req.query.SELECT.where[0].xpr) {
993
- // REVISIT ugly workaround until we support .xpr instead of '('...')' in draft choreo
994
- req.query.SELECT.where = req.query.SELECT.where[0].xpr
995
- }
948
+ // REVISIT this function does not only read, but modifies where!
996
949
  const draftParameters = _readDraftParameters(req.query.SELECT.where)
997
950
 
998
951
  if (
999
952
  draftParameters.isActiveEntity &&
1000
953
  _isTrue(draftParameters.isActiveEntity.value.val) &&
1001
954
  !draftParameters.siblingIsActive &&
1002
- !draftParameters.hasDraftEntity &&
1003
- !hasKeyInWhere(reqOriginal.query.SELECT.from.ref[0].where, model.definitions[req.query.SELECT.from.ref[0]])
955
+ !draftParameters.hasDraftEntity
1004
956
  ) {
1005
- return _allActive(req, columns)
957
+ return _allActive(req, columns, model)
1006
958
  }
1007
959
 
1008
960
  if (!draftParameters.isActiveEntity) {
@@ -1012,7 +964,7 @@ const _generateCQN = (reqOriginal, req, columns, model) => {
1012
964
  // this is only the case when navigating into tree
1013
965
  return _allInactive(req, columns)
1014
966
  }
1015
- return _allActive(req, columns)
967
+ return _allActive(req, columns, model)
1016
968
  }
1017
969
 
1018
970
  if (draftParameters.hasDraftEntity) {
@@ -1023,9 +975,7 @@ const _generateCQN = (reqOriginal, req, columns, model) => {
1023
975
  return _validatedWithSiblingInProcess(req, req.query.SELECT.where, draftParameters, columns)
1024
976
  }
1025
977
 
1026
- return _isTrue(draftParameters.isActiveEntity.value.val)
1027
- ? _validatedActive(req, req.query.SELECT.where, draftParameters, columns)
1028
- : _validatedDraftOfWhichIAmOwner(req, req.query.SELECT.where, draftParameters, columns)
978
+ return _validatedDraftOfWhichIAmOwner(req, req.query.SELECT.where, draftParameters, columns)
1029
979
  }
1030
980
 
1031
981
  const _getColumns = ({ query: { SELECT } }, model) => {
@@ -1183,7 +1133,7 @@ const _handler = async function (req) {
1183
1133
  delete req.query._validationQuery
1184
1134
 
1185
1135
  // REVISIT DRAFT HANDLING: cqn2cqn4sql must not be called here
1186
- const sqlQuery = cqn2cqn4sql(req.query, this.model)
1136
+ const sqlQuery = cqn2cqn4sql(req.query, this.model, { draft: true })
1187
1137
 
1188
1138
  // do not clone with Object.assign as that would skip all non-enumerable properties
1189
1139
  const reqClone = { __proto__: req, query: _copyCQNPartial(sqlQuery) }
@@ -1204,7 +1154,11 @@ const _handler = async function (req) {
1204
1154
  return
1205
1155
  }
1206
1156
 
1207
- const enhacnedWithLastChangeDateTime = enhanceQueryForTimeoutIfNeeded(
1157
+ if (cqnScenario.scenario === SCENARIO.ALL_ACTIVE && cqnScenario.cqn.SELECT.where) {
1158
+ cqnScenario.cqn.SELECT.where = removeIsActiveEntityRecursively(cqnScenario.cqn.SELECT.where)
1159
+ }
1160
+
1161
+ const enhancedWithLastChangeDateTime = enhanceQueryForTimeoutIfNeeded(
1208
1162
  cqnScenario.scenario,
1209
1163
  cqnScenario.cqn.SELECT.columns
1210
1164
  )
@@ -1228,7 +1182,7 @@ const _handler = async function (req) {
1228
1182
  _calculateDraftAdminColumns(resultAsArray[0], req.user.id)
1229
1183
  }
1230
1184
 
1231
- calculateDraftTimeout(cqnScenario.scenario, resultAsArray, enhacnedWithLastChangeDateTime)
1185
+ calculateDraftTimeout(cqnScenario.scenario, resultAsArray, enhancedWithLastChangeDateTime)
1232
1186
 
1233
1187
  if (cqnScenario.scenario === SCENARIO.SIBLING_ENTITY) {
1234
1188
  if (!result || (Array.isArray(result) && !result.length)) return result
@@ -1247,8 +1201,8 @@ const _handler = async function (req) {
1247
1201
  return result
1248
1202
  }
1249
1203
 
1250
- module.exports = function () {
1204
+ module.exports = cds.service.impl(function () {
1251
1205
  for (const entity of Object.values(this.entities).filter(e => e._isDraftEnabled)) {
1252
1206
  this.on('READ', entity, _handler)
1253
1207
  }
1254
- }
1208
+ })
@@ -2,7 +2,7 @@ const cds = require('../../cds')
2
2
  const { SELECT } = cds.ql
3
3
  const { getEnrichedCQN, hasDraft, ensureDraftsSuffix } = require('../utils/handler')
4
4
  const { readAndDeleteKeywords } = require('../utils/where')
5
- const cqn2cqn4sql = require('../../common/utils/cqn2cqn4sql')
5
+ const { cqn2cqn4sql } = require('../../common/utils/cqn2cqn4sql')
6
6
 
7
7
  const _modifyCQN = (cqnDraft, where, context) => {
8
8
  const whereDraft = [...where]
@@ -45,7 +45,7 @@ const _handler = async function (req) {
45
45
  }
46
46
 
47
47
  // REVISIT DRAFT HANDLING: cqn2cqn4sql must not be called here
48
- const sqlQuery = cqn2cqn4sql(req.query, this.model)
48
+ const sqlQuery = cqn2cqn4sql(req.query, this.model, { draft: true })
49
49
  if (req.query._streaming) {
50
50
  sqlQuery._streaming = true
51
51
  }
@@ -70,8 +70,8 @@ const _handler = async function (req) {
70
70
  return cds.tx(req).run(sqlQuery)
71
71
  }
72
72
 
73
- module.exports = function () {
73
+ module.exports = cds.service.impl(function () {
74
74
  for (const entity of Object.values(this.entities).filter(e => !e._isDraftEnabled)) {
75
75
  this.on('READ', entity, _handler)
76
76
  }
77
- }
77
+ })
@@ -0,0 +1,15 @@
1
+ namespace cds_r; //> cds system tables
2
+
3
+ service ExtensibilityService @(path:'/-/cds/extensibility') {
4
+ action addExtension(extensions : array of String); // TODO: change to array of CSN extensions
5
+ }
6
+
7
+ entity Extensions {
8
+ key ID : UUID;
9
+ csn : String;
10
+ }
11
+
12
+ // @open type Extension {
13
+ // extend: String;
14
+ // elements:{ /* */ }
15
+ // }
@@ -0,0 +1,148 @@
1
+ const cds = require('../../../cds')
2
+ const { ensureDraftsSuffix } = require('../../../common/utils/draft')
3
+
4
+ const { EXT_BACK_PACK } = require('../utils')
5
+
6
+ const _getDraftTable = (view, cds) => {
7
+ return cds.model.definitions[view]._isDraftEnabled ? ensureDraftsSuffix(view) : undefined
8
+ }
9
+
10
+ const _addAnnotation = extension => {
11
+ Object.values(extension.elements).forEach(el => {
12
+ el['@cds.extension'] = true
13
+ })
14
+ }
15
+
16
+ const _isProjection = target => target && target.query && target.query._target
17
+
18
+ const _resolveViews = (target, views_ = []) => {
19
+ if (_isProjection(target)) {
20
+ views_.push(target)
21
+ return _resolveViews(target.query._target, views_)
22
+ }
23
+
24
+ return target
25
+ }
26
+
27
+ const _getCsn = req => {
28
+ const csn = {
29
+ extensions: req.data.extensions.map(ext => JSON.parse(ext))
30
+ }
31
+
32
+ return csn
33
+ }
34
+
35
+ const _addViews = csn => {
36
+ csn.extensions.forEach(extension => {
37
+ const target = cds.model.definitions[extension.extend]
38
+ const views_ = []
39
+ const view = _resolveViews(target, views_)
40
+ extension.extend = view && view.name
41
+ _addAnnotation(extension)
42
+
43
+ // All projection views leading to the db entity are extended with back pack in case view columns are explicitly listed.
44
+ // The views using projections with '*' obtain the back pack automatically.
45
+ views_.forEach(view => {
46
+ if (!view.projection || (view.projection.columns && !view.projection.columns.some(col => col === '*'))) {
47
+ csn.extensions.push({
48
+ extend: view.name,
49
+ columns: Object.keys(extension.elements).map(key => {
50
+ return { ref: [key] }
51
+ })
52
+ })
53
+ }
54
+ })
55
+ })
56
+ }
57
+
58
+ const _handleDefaults = async (extension, dbEntity, req, cds, draftEntity) => {
59
+ const ext = Object.keys(extension.elements)
60
+ .filter(key => extension.elements[key].default)
61
+ .map(key => {
62
+ const element = extension.elements[key]
63
+ const t = cds.model.definitions[element.type] || cds.builtin.types[element.type]
64
+ const value = t && t instanceof cds.builtin.classes.string ? `"${element.default.val}"` : element.default.val
65
+ return `"${key}":${value}`
66
+ })
67
+
68
+ if (ext.length !== 0) {
69
+ const extStr = ext.join(',')
70
+ const changed = `'{${extStr},' || substr(${EXT_BACK_PACK}, 2, length(${EXT_BACK_PACK})-1)`
71
+ const assign = `${EXT_BACK_PACK} = CASE WHEN ${EXT_BACK_PACK} IS NULL THEN '{${extStr}}' ELSE ${changed} END`
72
+ await UPDATE(dbEntity).with(assign)
73
+ if (draftEntity) await UPDATE(draftEntity).with(assign)
74
+ }
75
+ }
76
+
77
+ const _validateCsn = (csn, req) => {
78
+ csn.extensions.forEach(extension => {
79
+ if (!extension.extend || !cds.model.definitions[extension.extend]) {
80
+ req.reject(400, 'Invalid extension. Parameter "extend" missing or malformed')
81
+ }
82
+
83
+ if (!extension.elements) {
84
+ req.reject(400, 'Invalid extension. Missing parameter "elements"')
85
+ }
86
+ })
87
+ }
88
+
89
+ const _validateExtensionFields = async (csn, req) => {
90
+ csn.extensions.forEach(extension => {
91
+ if (extension.elements) {
92
+ Object.keys(extension.elements).forEach(name => {
93
+ if (!/^[A-Za-z]\w*$/.test(name)) {
94
+ req.reject(400, `Invalid extension. Bad element name "${name}"`)
95
+ }
96
+
97
+ if (Object.keys(cds.model.definitions[extension.extend].elements).includes(name)) {
98
+ req.reject(400, `Invalid extension. Element "${name}" already exists`)
99
+ }
100
+ })
101
+ }
102
+ })
103
+ }
104
+
105
+ const _getCompilerError = messages => {
106
+ const defaultMsg = 'Error while compiling extension'
107
+ if (!messages) return defaultMsg
108
+
109
+ for (const msg of messages) {
110
+ if (msg.severity === 'Error') return msg.message
111
+ }
112
+
113
+ return defaultMsg
114
+ }
115
+
116
+ const _validateExtension = async (csn, req) => {
117
+ try {
118
+ const base = await cds.load('*', cds.options)
119
+ const baseCsn = await cds.compile.to.json(base)
120
+ const extCsn = await cds.compile.to.json(csn)
121
+ await cds.compile.to.csn({ 'base.csn': baseCsn, 'ext.csn': extCsn })
122
+ } catch (err) {
123
+ req.reject(400, _getCompilerError(err.messages))
124
+ }
125
+ }
126
+
127
+ module.exports = function () {
128
+ this.on('addExtension', async req => {
129
+ const csn = _getCsn(req, cds)
130
+ _validateCsn(csn, req)
131
+ await _validateExtensionFields(csn, req)
132
+ _addViews(csn, cds)
133
+ await _validateExtension(csn, req)
134
+
135
+ const ID = cds.utils.uuid()
136
+ await INSERT.into('cds_r.Extensions').entries([{ ID, csn: JSON.stringify(csn) }])
137
+
138
+ for (const ext of req.data.extensions) {
139
+ const extension = JSON.parse(ext)
140
+ const draft = _getDraftTable(extension.extend, cds)
141
+ const target = cds.model.definitions[extension.extend]
142
+ const dbEntity = _resolveViews(target).name
143
+ await _handleDefaults(extension, dbEntity, req, cds, draft)
144
+ }
145
+
146
+ setTimeout(() => process.send('restart'), 1111)
147
+ })
148
+ }