@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,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
@@ -286,12 +257,29 @@ const _getOuterMostColumns = (columnsFromRequest, additionalDraftColumns) => {
286
257
  return columns
287
258
  }
288
259
 
260
+ // adds base columns 'InProcessByUser' and 'CreatedByUser' to columns param if needed
261
+ // those are required for calculating 'DraftIsProcessedByMe' and 'DraftIsCreatedByMe'
262
+ const _ensureDraftAdminColumnsForCalculation = columns => {
263
+ columns.forEach((c, i) => {
264
+ if (c.ref && c.ref[0] === 'DraftIsCreatedByMe' && !columns.find(e => e.ref && e.ref[0] === 'CreatedByUser')) {
265
+ columns.push({ ref: ['CreatedByUser'] })
266
+ } else if (
267
+ c.ref &&
268
+ c.ref[0] === 'DraftIsProcessedByMe' &&
269
+ !columns.find(e => e.ref && e.ref[0] === 'InProcessByUser')
270
+ ) {
271
+ columns.push({ ref: ['InProcessByUser'] })
272
+ }
273
+ })
274
+ }
275
+
289
276
  const _draftAdminTable = req => {
290
277
  const { table } = _getTableName(req)
291
278
 
292
279
  let cqn = SELECT.from(table)
293
280
  if (req.query.SELECT.columns) {
294
281
  cqn = cqn.columns(...req.query.SELECT.columns)
282
+ _ensureDraftAdminColumnsForCalculation(cqn.SELECT.columns)
295
283
  }
296
284
 
297
285
  return {
@@ -428,18 +416,6 @@ const _allActive = (req, columns, model) => {
428
416
  }
429
417
  }
430
418
 
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
419
  const _activeWithoutDraft = (req, draftWhere, columns) => {
444
420
  const { table } = _getTableName(req, true)
445
421
  const draftName = table.ref[0]
@@ -805,7 +781,7 @@ const _getUnionCQN = (req, draftName, columns, subSelect, draftWhere) => {
805
781
  _alignAliasForUnion(ensureNoDraftsSuffix(req.target.name), req.query.SELECT.from.as, subSelect)
806
782
  ])
807
783
 
808
- return union.columns({ func: 'sum', args: [{ ref: ['_counted_'] }], as: '_counted_' })
784
+ return union.columns({ func: 'sum', args: [{ ref: ['$count'] }], as: '$count' })
809
785
  }
810
786
 
811
787
  const enrichedColumns = _getOrderByEnrichedColumns(req.query.SELECT.orderBy, columns)
@@ -941,9 +917,6 @@ const _validatedWithSiblingInProcess = (req, draftWhere, draftParameters, column
941
917
  }
942
918
  }
943
919
 
944
- const _validatedActive = (req, draftWhere, draftParameters, columns) =>
945
- _isValidActive(draftParameters.isActiveEntity, req, draftWhere) && _active(req, draftWhere, columns)
946
-
947
920
  const _validatedDraftOfWhichIAmOwner = (req, draftWhere, draftParameters, columns) =>
948
921
  _isValidDraftOfWhichIAmOwner(draftParameters.isActiveEntity) && _draftOfWhichIAmOwner(req, draftWhere, columns)
949
922
 
@@ -989,20 +962,16 @@ const _generateCQN = (reqOriginal, req, columns, model) => {
989
962
  return _allActive(req, columns, model)
990
963
  }
991
964
 
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
- }
965
+ // REVISIT this function does not only read, but modifies where!
996
966
  const draftParameters = _readDraftParameters(req.query.SELECT.where)
997
967
 
998
968
  if (
999
969
  draftParameters.isActiveEntity &&
1000
970
  _isTrue(draftParameters.isActiveEntity.value.val) &&
1001
971
  !draftParameters.siblingIsActive &&
1002
- !draftParameters.hasDraftEntity &&
1003
- !hasKeyInWhere(reqOriginal.query.SELECT.from.ref[0].where, model.definitions[req.query.SELECT.from.ref[0]])
972
+ !draftParameters.hasDraftEntity
1004
973
  ) {
1005
- return _allActive(req, columns)
974
+ return _allActive(req, columns, model)
1006
975
  }
1007
976
 
1008
977
  if (!draftParameters.isActiveEntity) {
@@ -1012,7 +981,7 @@ const _generateCQN = (reqOriginal, req, columns, model) => {
1012
981
  // this is only the case when navigating into tree
1013
982
  return _allInactive(req, columns)
1014
983
  }
1015
- return _allActive(req, columns)
984
+ return _allActive(req, columns, model)
1016
985
  }
1017
986
 
1018
987
  if (draftParameters.hasDraftEntity) {
@@ -1023,9 +992,7 @@ const _generateCQN = (reqOriginal, req, columns, model) => {
1023
992
  return _validatedWithSiblingInProcess(req, req.query.SELECT.where, draftParameters, columns)
1024
993
  }
1025
994
 
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)
995
+ return _validatedDraftOfWhichIAmOwner(req, req.query.SELECT.where, draftParameters, columns)
1029
996
  }
1030
997
 
1031
998
  const _getColumns = ({ query: { SELECT } }, model) => {
@@ -1169,6 +1136,14 @@ const _getLocalizedEntity = (model, target, user) => {
1169
1136
  return localizedEntity || model.definitions[`${prefix}.${target.name}`]
1170
1137
  }
1171
1138
 
1139
+ const _adaptDraftAdminExpand = cqn => {
1140
+ const draftAdminExpand =
1141
+ cqn.SELECT.columns && cqn.SELECT.columns.find(c => c.expand && c.ref[0] === 'DraftAdministrativeData')
1142
+ if (draftAdminExpand) {
1143
+ _ensureDraftAdminColumnsForCalculation(draftAdminExpand.expand)
1144
+ }
1145
+ }
1146
+
1172
1147
  /**
1173
1148
  * Generic Handler for READ requests in the context of draft.
1174
1149
  *
@@ -1183,7 +1158,7 @@ const _handler = async function (req) {
1183
1158
  delete req.query._validationQuery
1184
1159
 
1185
1160
  // REVISIT DRAFT HANDLING: cqn2cqn4sql must not be called here
1186
- const sqlQuery = cqn2cqn4sql(req.query, this.model)
1161
+ const sqlQuery = cqn2cqn4sql(req.query, this.model, { draft: true })
1187
1162
 
1188
1163
  // do not clone with Object.assign as that would skip all non-enumerable properties
1189
1164
  const reqClone = { __proto__: req, query: _copyCQNPartial(sqlQuery) }
@@ -1204,7 +1179,14 @@ const _handler = async function (req) {
1204
1179
  return
1205
1180
  }
1206
1181
 
1207
- const enhacnedWithLastChangeDateTime = enhanceQueryForTimeoutIfNeeded(
1182
+ // ensure base columns for calculation are selected in draft admin expand
1183
+ _adaptDraftAdminExpand(cqnScenario.cqn)
1184
+
1185
+ if (cqnScenario.scenario === SCENARIO.ALL_ACTIVE && cqnScenario.cqn.SELECT.where) {
1186
+ cqnScenario.cqn.SELECT.where = removeIsActiveEntityRecursively(cqnScenario.cqn.SELECT.where)
1187
+ }
1188
+
1189
+ const enhancedWithLastChangeDateTime = enhanceQueryForTimeoutIfNeeded(
1208
1190
  cqnScenario.scenario,
1209
1191
  cqnScenario.cqn.SELECT.columns
1210
1192
  )
@@ -1228,7 +1210,7 @@ const _handler = async function (req) {
1228
1210
  _calculateDraftAdminColumns(resultAsArray[0], req.user.id)
1229
1211
  }
1230
1212
 
1231
- calculateDraftTimeout(cqnScenario.scenario, resultAsArray, enhacnedWithLastChangeDateTime)
1213
+ calculateDraftTimeout(cqnScenario.scenario, resultAsArray, enhancedWithLastChangeDateTime)
1232
1214
 
1233
1215
  if (cqnScenario.scenario === SCENARIO.SIBLING_ENTITY) {
1234
1216
  if (!result || (Array.isArray(result) && !result.length)) return result
@@ -1247,8 +1229,8 @@ const _handler = async function (req) {
1247
1229
  return result
1248
1230
  }
1249
1231
 
1250
- module.exports = function () {
1232
+ module.exports = cds.service.impl(function () {
1251
1233
  for (const entity of Object.values(this.entities).filter(e => e._isDraftEnabled)) {
1252
1234
  this.on('READ', entity, _handler)
1253
1235
  }
1254
- }
1236
+ })
@@ -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
+ }
@@ -0,0 +1,119 @@
1
+ const { EXT_BACK_PACK, getExtendedFields, hasExtendedEntity, isExtendedEntity, getTargetRead } = require('../utils')
2
+
3
+ const _addBackPack = (columns, extFields, alias) => {
4
+ if (!columns) return
5
+
6
+ const hasBackPack = columns.some(
7
+ col => col.ref && col.ref[col.ref.length - 1] === EXT_BACK_PACK && _hasAlias(col.ref, alias)
8
+ )
9
+ if (hasBackPack) return // get out early, avoiding overhead of second check
10
+
11
+ const hasExtFields = columns.some(
12
+ col => col.ref && extFields.includes(col.ref[col.ref.length - 1]) && _hasAlias(col.ref, alias)
13
+ )
14
+
15
+ if (hasExtFields) {
16
+ const col = { ref: [EXT_BACK_PACK] }
17
+ if (alias) col.ref.unshift(alias)
18
+ columns.push(col)
19
+ }
20
+
21
+ /*
22
+ Removing backpack if not needed doesn't work. Probably ref copy problem.
23
+ if (hasBackPack && !hasExtFields) remove backpack.
24
+ */
25
+ }
26
+
27
+ const _hasAlias = (ref, alias) => {
28
+ return (ref.length === 1 && !alias) || (ref.length > 1 && ref[0] === alias)
29
+ }
30
+
31
+ const _removeExtendedFields = (columns, extFields, alias) => {
32
+ if (!columns) return
33
+
34
+ let i = columns.length
35
+ while (i--) {
36
+ const col = columns[i]
37
+ if (col.ref && extFields.includes(col.ref[col.ref.length - 1]) && _hasAlias(col.ref, alias)) {
38
+ columns.splice(i, 1)
39
+ }
40
+ }
41
+ }
42
+
43
+ const _transformUnion = (req, model) => {
44
+ // second element is active entity
45
+ const name = req.target.name.SET ? req.target.name.SET.args[1]._target.name : req.target.name
46
+ const extFields = getExtendedFields(name, model)
47
+ _addBackPack(req.query.SELECT.columns, extFields)
48
+ _removeExtendedFields(req.query.SELECT.columns, extFields)
49
+
50
+ _addBackPack(
51
+ req.query.SELECT.from.SET.args[0].SELECT.columns,
52
+ extFields,
53
+ req.query.SELECT.from.SET.args[0].SELECT.from.args[0].as
54
+ )
55
+ _addBackPack(req.query.SELECT.from.SET.args[1].SELECT.columns, extFields)
56
+ _removeExtendedFields(
57
+ req.query.SELECT.from.SET.args[0].SELECT.columns,
58
+ extFields,
59
+ req.query.SELECT.from.SET.args[0].SELECT.from.args[0].as
60
+ )
61
+ _removeExtendedFields(req.query.SELECT.from.SET.args[1].SELECT.columns, extFields)
62
+ }
63
+
64
+ const _getAliasedEntitiesForJoin = (args, model) => {
65
+ const extEntities = []
66
+
67
+ args.forEach(arg => {
68
+ if (arg.ref && arg.ref[0] !== 'DRAFT.DraftAdministativeData' && isExtendedEntity(arg.ref[0], model)) {
69
+ const extFields = getExtendedFields(arg.ref[0], model)
70
+ extEntities.push({ name: arg.ref[0], as: arg.as, extFields })
71
+ }
72
+
73
+ if (arg.join) {
74
+ extEntities.push(..._getAliasedEntitiesForJoin(arg.args, model))
75
+ }
76
+ })
77
+
78
+ return extEntities
79
+ }
80
+
81
+ const _transformJoin = (req, model) => {
82
+ const extEntities = _getAliasedEntitiesForJoin(req.query.SELECT.from.args, model)
83
+
84
+ extEntities.forEach(ext => {
85
+ _addBackPack(req.query.SELECT.columns, ext.extFields, ext.as)
86
+ _removeExtendedFields(req.query.SELECT.columns, ext.extFields, ext.as)
87
+ })
88
+ }
89
+
90
+ const _transformColumns = (columns, targetName, model) => {
91
+ if (!columns) return
92
+
93
+ const extFields = getExtendedFields(targetName, model)
94
+ if (extFields.length !== 0) {
95
+ _addBackPack(columns, extFields)
96
+ _removeExtendedFields(columns, extFields)
97
+ }
98
+
99
+ columns.forEach(col => {
100
+ if (col.ref && col.expand) {
101
+ const expTargetName = model.definitions[targetName].elements[col.ref[0]].target
102
+ _transformColumns(col.expand, expTargetName, model)
103
+ }
104
+ })
105
+ }
106
+
107
+ function transformExtendedFieldsREAD(req) {
108
+ if (!hasExtendedEntity(req, this.model)) return
109
+
110
+ const target = getTargetRead(req)
111
+ _transformColumns(req.query.SELECT.columns, target.name, this.model)
112
+
113
+ if (req.query.SELECT.from.SET) return _transformUnion(req, this.model) // union
114
+ if (req.query.SELECT.from.join) return _transformJoin(req, this.model) // join
115
+ }
116
+
117
+ module.exports = {
118
+ transformExtendedFieldsREAD
119
+ }
@@ -0,0 +1,43 @@
1
+ const { EXT_BACK_PACK, hasExtendedEntity, getTargetRead } = require('../utils')
2
+
3
+ const getTemplate = require('../../../common/utils/template')
4
+ const templateProcessor = require('../../../common/utils/templateProcessor')
5
+
6
+ const _pick = element => {
7
+ return element['@cds.extension']
8
+ }
9
+
10
+ const _processorFn = ({ row, key }) => {
11
+ if (row[EXT_BACK_PACK]) {
12
+ const extensions = JSON.parse(row[EXT_BACK_PACK])
13
+ Object.keys(extensions).forEach(field => {
14
+ row[field] = extensions[field]
15
+ })
16
+
17
+ delete row[EXT_BACK_PACK]
18
+ }
19
+
20
+ if (row[key] === undefined) {
21
+ row[key] = null
22
+ }
23
+ }
24
+
25
+ function transformExtendedFieldsRESULT(result, req) {
26
+ if (!result || !hasExtendedEntity(req, this.model)) return
27
+
28
+ const template = getTemplate('transform-result', this, getTargetRead(req), {
29
+ pick: _pick
30
+ })
31
+
32
+ if (template.elements.size > 0) {
33
+ const result_ = Array.isArray(result) ? result : [result]
34
+ for (const row of result_) {
35
+ const args = { processFn: _processorFn, row, template }
36
+ templateProcessor(args)
37
+ }
38
+ }
39
+ }
40
+
41
+ module.exports = {
42
+ transformExtendedFieldsRESULT
43
+ }
@@ -0,0 +1,62 @@
1
+ const { EXT_BACK_PACK, getTargetWrite, isExtendedEntity } = require('../utils')
2
+
3
+ const getTemplate = require('../../../common/utils/template')
4
+ const templateProcessor = require('../../../common/utils/templateProcessor')
5
+
6
+ const _pick = element => {
7
+ return element['@cds.extension']
8
+ }
9
+
10
+ const _processorFn = ({ row, key }) => {
11
+ if (row[key] === undefined) return
12
+
13
+ if (!row[EXT_BACK_PACK]) {
14
+ row[EXT_BACK_PACK] = '{}'
15
+ }
16
+
17
+ const json = JSON.parse(row[EXT_BACK_PACK])
18
+ json[key] = row[key]
19
+ row[EXT_BACK_PACK] = JSON.stringify(json)
20
+ delete row[key]
21
+ }
22
+
23
+ function transformExtendedFieldsCREATE(req) {
24
+ if (!req.target) return
25
+
26
+ const target = getTargetWrite(req.target, this.model)
27
+ const template = getTemplate('transform-write', this, target, { pick: _pick })
28
+
29
+ if (template && template.elements.size > 0) {
30
+ for (const row of req.query.INSERT.entries) {
31
+ const args = { processFn: _processorFn, row, template }
32
+ templateProcessor(args)
33
+ }
34
+ }
35
+ }
36
+
37
+ async function transformExtendedFieldsUPDATE(req) {
38
+ if (!req.target || !req.query.UPDATE.where) return
39
+
40
+ const target = getTargetWrite(req.target, this.model)
41
+ const template = getTemplate('transform-write', Object.assign(req, { model: this.model }), target, { pick: _pick })
42
+
43
+ if (template && template.elements.size > 0) {
44
+ // In patch case we first should obtain backpack from db.
45
+ // Patch can be only applied to the root.
46
+ if (isExtendedEntity(target.name, this.model)) {
47
+ const current = await SELECT.from(req.query.UPDATE.entity).columns([EXT_BACK_PACK]).where(req.query.UPDATE.where)
48
+
49
+ if (current[0]) {
50
+ req.data[EXT_BACK_PACK] = JSON.stringify(current[0])
51
+ }
52
+ }
53
+
54
+ const args = { processFn: _processorFn, row: req.data, template }
55
+ templateProcessor(args)
56
+ }
57
+ }
58
+
59
+ module.exports = {
60
+ transformExtendedFieldsCREATE,
61
+ transformExtendedFieldsUPDATE
62
+ }
@@ -0,0 +1,35 @@
1
+ module.exports = async () => {
2
+ const cds = require('../../cds')
3
+ if (!cds.requires.db) return
4
+
5
+ const db = await cds.connect.to({ ...cds.requires.db, model: null, silent: true })
6
+ const rs = await db.read('cds_r.Extensions')
7
+ if (rs.length !== 0) {
8
+ const extensions = []
9
+ rs.forEach(row => extensions.push(...JSON.parse(row.csn).extensions))
10
+ cds.once('loaded', csn => {
11
+ if (cds.model) return // extend cds.model only
12
+ const extended = cds.compile({
13
+ 'base.csn': cds.compile.to.json(csn),
14
+ 'ext.csn': cds.compile.to.json({ extensions })
15
+ })
16
+ csn.definitions = extended.definitions
17
+ })
18
+ }
19
+ await db.disconnect()
20
+
21
+ if (cds.db) return // because of tests
22
+ cds.once('served', () => {
23
+ const { transformExtendedFieldsCREATE, transformExtendedFieldsUPDATE } = require('./handler/transformWRITE')
24
+ const { transformExtendedFieldsREAD } = require('./handler/transformREAD')
25
+ const { transformExtendedFieldsRESULT } = require('./handler/transformRESULT')
26
+ cds.db
27
+ .before('CREATE', transformExtendedFieldsCREATE)
28
+ .before('UPDATE', transformExtendedFieldsUPDATE)
29
+ .before('READ', transformExtendedFieldsREAD)
30
+ .after('READ', transformExtendedFieldsRESULT)
31
+ if ('cds_r.ExtensibilityService' in cds.services) return
32
+ const model = require('path').join(__dirname, 'extensibility')
33
+ return cds.serve(model, { silent: true }).to('odata').in(cds.app)
34
+ })
35
+ }