@sap/cds 7.9.3 → 8.0.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 (278) hide show
  1. package/CHANGELOG.md +126 -3655
  2. package/_i18n/i18n_en_US_saptrc.properties +113 -0
  3. package/_i18n/i18n_zh_CN.properties +7 -4
  4. package/app/index.css +129 -0
  5. package/app/index.html +16 -64
  6. package/app/index.js +14 -9
  7. package/bin/args.js +34 -0
  8. package/bin/serve.js +18 -24
  9. package/bin/test.js +97 -0
  10. package/common.cds +5 -12
  11. package/eslint.config.mjs +133 -0
  12. package/lib/auth/basic-auth.js +16 -20
  13. package/lib/auth/dummy-auth.js +1 -1
  14. package/lib/auth/ias-auth.js +9 -41
  15. package/lib/auth/index.js +1 -14
  16. package/lib/auth/jwt-auth.js +10 -40
  17. package/lib/compile/cds-compile.js +1 -2
  18. package/lib/compile/cdsc.js +21 -26
  19. package/lib/compile/etc/_localized.js +1 -6
  20. package/lib/compile/etc/csv.js +1 -1
  21. package/lib/compile/etc/properties.js +1 -1
  22. package/lib/compile/for/java.js +1 -1
  23. package/lib/compile/for/lean_drafts.js +4 -6
  24. package/lib/compile/for/nodejs.js +1 -1
  25. package/lib/compile/parse.js +4 -0
  26. package/lib/compile/resolve.js +4 -4
  27. package/lib/compile/to/edm-files.js +16 -23
  28. package/lib/compile/to/hana.js +27 -0
  29. package/lib/compile/to/json.js +1 -1
  30. package/lib/compile/to/sql.js +5 -1
  31. package/lib/compile/to/yaml.js +3 -3
  32. package/lib/dbs/cds-deploy.js +4 -2
  33. package/lib/env/cds-env.js +10 -14
  34. package/lib/env/cds-requires.js +29 -13
  35. package/lib/env/defaults.js +46 -16
  36. package/lib/env/plugins.js +1 -1
  37. package/lib/env/schemas/cds-rc.js +8 -4
  38. package/lib/env/schemas/index.js +7 -7
  39. package/lib/env/serviceBindings.js +1 -1
  40. package/lib/index.js +12 -10
  41. package/lib/lazy.js +1 -1
  42. package/lib/linked/classes.js +36 -8
  43. package/lib/linked/entities.js +2 -10
  44. package/lib/linked/models.js +2 -1
  45. package/lib/linked/validate.js +292 -0
  46. package/lib/log/cds-error.js +0 -6
  47. package/lib/log/cds-log.js +3 -3
  48. package/lib/log/format/json.js +1 -1
  49. package/lib/log/service/index.js +0 -1
  50. package/lib/plugins.js +2 -2
  51. package/lib/ql/Query.js +2 -10
  52. package/lib/ql/SELECT.js +1 -1
  53. package/lib/ql/Whereable.js +3 -2
  54. package/lib/req/cds-context.js +14 -25
  55. package/lib/req/context.js +23 -25
  56. package/lib/req/request.js +1 -34
  57. package/lib/req/user.js +47 -35
  58. package/lib/srv/bindings.js +1 -1
  59. package/lib/srv/cds-connect.js +4 -4
  60. package/lib/srv/cds-serve.js +2 -2
  61. package/lib/srv/factory.js +1 -1
  62. package/lib/srv/middlewares/cds-context.js +11 -22
  63. package/lib/srv/middlewares/ctx-model.js +2 -3
  64. package/lib/srv/middlewares/errors.js +41 -8
  65. package/lib/srv/middlewares/index.js +3 -3
  66. package/lib/srv/middlewares/trace.js +0 -2
  67. package/lib/srv/protocols/hcql.js +15 -10
  68. package/lib/srv/protocols/http.js +44 -49
  69. package/lib/srv/protocols/index.js +1 -23
  70. package/lib/srv/protocols/odata-v4.js +12 -74
  71. package/lib/srv/protocols/rest.js +1 -13
  72. package/lib/srv/srv-api.js +0 -20
  73. package/lib/srv/srv-dispatch.js +3 -2
  74. package/lib/srv/srv-handlers.js +22 -11
  75. package/lib/srv/srv-methods.js +2 -2
  76. package/lib/srv/srv-models.js +3 -36
  77. package/lib/test/expect.js +343 -0
  78. package/lib/test/index.js +2 -0
  79. package/lib/test/reporter.js +176 -0
  80. package/lib/utils/axios.js +10 -9
  81. package/lib/utils/cds-test.js +85 -36
  82. package/lib/utils/cds-utils.js +54 -7
  83. package/lib/utils/check-version.js +0 -4
  84. package/lib/utils/colors.js +49 -0
  85. package/lib/utils/data.js +5 -4
  86. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +2 -7
  87. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +3 -30
  88. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +6 -12
  89. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +1 -3
  90. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +0 -1
  91. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +4 -7
  92. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +12 -6
  93. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +2 -4
  94. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +1 -0
  95. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -1
  96. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +0 -1
  97. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +1 -3
  98. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +1 -1
  99. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/AbstractEdmStructuredType.js +1 -2
  100. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +5 -0
  101. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ContextURLFactory.js +1 -1
  102. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +9 -43
  103. package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +0 -1
  104. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +8 -3
  105. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +4 -2
  106. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +1 -3
  107. package/libx/_runtime/cds-services/util/assert.js +1 -1
  108. package/libx/_runtime/cds.js +10 -3
  109. package/libx/_runtime/common/Service.js +12 -32
  110. package/libx/_runtime/common/aspects/any.js +1 -0
  111. package/libx/_runtime/common/code-ext/execute.js +1 -1
  112. package/libx/_runtime/common/code-ext/worker.js +0 -1
  113. package/libx/_runtime/common/composition/data.js +0 -1
  114. package/libx/_runtime/common/composition/delete.js +0 -1
  115. package/libx/_runtime/common/composition/insert.js +2 -2
  116. package/libx/_runtime/common/composition/tree.js +0 -1
  117. package/libx/_runtime/common/composition/update.js +3 -3
  118. package/libx/_runtime/common/error/frontend.js +21 -12
  119. package/libx/_runtime/common/error/log.js +36 -0
  120. package/libx/_runtime/common/error/utils.js +2 -5
  121. package/libx/_runtime/common/generic/auth/autoexpose.js +18 -17
  122. package/libx/_runtime/common/generic/auth/expand.js +1 -1
  123. package/libx/_runtime/common/generic/auth/readOnly.js +1 -2
  124. package/libx/_runtime/common/generic/auth/restrict.js +23 -42
  125. package/libx/_runtime/common/generic/auth/restrictions.js +2 -7
  126. package/libx/_runtime/common/generic/auth/utils.js +91 -88
  127. package/libx/_runtime/common/generic/crud.js +6 -5
  128. package/libx/_runtime/common/generic/etag.js +7 -12
  129. package/libx/_runtime/common/generic/input.js +70 -68
  130. package/libx/_runtime/common/generic/paging.js +1 -0
  131. package/libx/_runtime/common/generic/sorting.js +1 -0
  132. package/libx/_runtime/common/generic/temporal.js +8 -2
  133. package/libx/_runtime/common/i18n/index.js +1 -1
  134. package/libx/_runtime/common/i18n/messages.properties +3 -1
  135. package/libx/_runtime/common/utils/binary.js +8 -2
  136. package/libx/_runtime/common/utils/compareJson.js +5 -1
  137. package/libx/_runtime/common/utils/copy.js +6 -11
  138. package/libx/_runtime/common/utils/cqn2cqn4sql.js +16 -14
  139. package/libx/_runtime/common/utils/differ.js +3 -6
  140. package/libx/_runtime/common/utils/keys.js +77 -18
  141. package/libx/_runtime/common/utils/postProcess.js +12 -15
  142. package/libx/_runtime/common/utils/propagateForeignKeys.js +0 -1
  143. package/libx/_runtime/common/utils/resolveView.js +2 -3
  144. package/libx/_runtime/common/utils/restrictions.js +45 -17
  145. package/libx/_runtime/common/utils/rewriteAsterisks.js +1 -8
  146. package/libx/_runtime/common/utils/stream.js +3 -16
  147. package/libx/_runtime/common/utils/streamProp.js +8 -18
  148. package/libx/_runtime/common/utils/structured.js +1 -1
  149. package/libx/_runtime/common/utils/ucsn.js +0 -2
  150. package/libx/_runtime/db/Service.js +0 -72
  151. package/libx/_runtime/db/data-conversion/post-processing.js +0 -1
  152. package/libx/_runtime/db/expand/expandCQNToJoin.js +9 -9
  153. package/libx/_runtime/db/expand/rawToExpanded.js +0 -8
  154. package/libx/_runtime/db/generic/input.js +3 -8
  155. package/libx/_runtime/db/generic/rewrite.js +1 -0
  156. package/libx/_runtime/db/query/read.js +2 -2
  157. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +0 -1
  158. package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -1
  159. package/libx/_runtime/db/utils/columns.js +2 -6
  160. package/libx/_runtime/fiori/lean-draft.js +138 -56
  161. package/libx/_runtime/hana/Service.js +0 -1
  162. package/libx/_runtime/hana/driver.js +1 -1
  163. package/libx/_runtime/hana/dynatrace.js +1 -2
  164. package/libx/_runtime/hana/pool.js +11 -21
  165. package/libx/_runtime/hana/streaming.js +0 -1
  166. package/libx/_runtime/messaging/common-utils/AMQPClient.js +0 -1
  167. package/libx/_runtime/messaging/common-utils/authorizedRequest.js +1 -1
  168. package/libx/_runtime/messaging/common-utils/normalizeIncomingMessage.js +1 -1
  169. package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +1 -1
  170. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +19 -33
  171. package/libx/_runtime/messaging/event-broker.js +0 -12
  172. package/libx/_runtime/messaging/file-based.js +3 -3
  173. package/libx/_runtime/messaging/http-utils/token.js +1 -1
  174. package/libx/_runtime/messaging/kafka.js +2 -2
  175. package/libx/_runtime/messaging/redis-messaging.js +0 -1
  176. package/libx/_runtime/remote/Service.js +25 -25
  177. package/libx/_runtime/remote/utils/client.js +4 -5
  178. package/libx/_runtime/remote/utils/cloudSdkProvider.js +0 -3
  179. package/libx/_runtime/remote/utils/data.js +0 -1
  180. package/libx/_runtime/sqlite/Service.js +1 -2
  181. package/libx/_runtime/ucl/Service.js +37 -78
  182. package/libx/common/assert/index.js +22 -21
  183. package/libx/common/assert/type-relaxed.js +39 -0
  184. package/libx/common/assert/utils.js +3 -2
  185. package/libx/common/assert/validation.js +3 -8
  186. package/libx/common/utils/index.js +5 -0
  187. package/libx/common/utils/path.js +51 -0
  188. package/libx/odata/ODataAdapter.js +126 -0
  189. package/libx/odata/index.js +15 -2
  190. package/libx/odata/middleware/batch.js +261 -72
  191. package/libx/odata/middleware/body-parser.js +33 -0
  192. package/libx/odata/middleware/create.js +44 -59
  193. package/libx/odata/middleware/delete.js +23 -12
  194. package/libx/odata/middleware/error.js +30 -6
  195. package/libx/odata/middleware/metadata.js +38 -26
  196. package/libx/odata/middleware/operation.js +93 -69
  197. package/libx/odata/middleware/parse.js +6 -8
  198. package/libx/odata/middleware/read.js +117 -93
  199. package/libx/odata/middleware/service-document.js +22 -19
  200. package/libx/odata/middleware/stream.js +54 -56
  201. package/libx/odata/middleware/update.js +79 -87
  202. package/libx/odata/parse/afterburner.js +191 -175
  203. package/libx/odata/parse/cqn2odata.js +8 -8
  204. package/libx/odata/parse/grammar.peggy +27 -20
  205. package/libx/odata/parse/multipartToJson.js +17 -9
  206. package/libx/odata/parse/parser.js +1 -1
  207. package/libx/odata/utils/etag.js +14 -6
  208. package/libx/odata/utils/index.js +84 -12
  209. package/libx/odata/utils/metadata.js +161 -0
  210. package/libx/odata/utils/postProcess.js +89 -0
  211. package/libx/odata/utils/readAfterWrite.js +134 -17
  212. package/libx/odata/utils/result.js +36 -142
  213. package/libx/outbox/index.js +4 -3
  214. package/libx/rest/RestAdapter.js +115 -182
  215. package/libx/rest/middleware/create.js +28 -24
  216. package/libx/rest/middleware/delete.js +7 -10
  217. package/libx/rest/middleware/error.js +19 -16
  218. package/libx/rest/middleware/operation.js +48 -41
  219. package/libx/rest/middleware/parse.js +128 -126
  220. package/libx/rest/middleware/read.js +20 -27
  221. package/libx/rest/middleware/update.js +26 -31
  222. package/package.json +17 -8
  223. package/server.js +4 -2
  224. package/tasks/enterprise-messaging-deploy.js +1 -1
  225. package/apis/cds.d.ts +0 -3
  226. package/apis/core.d.ts +0 -21
  227. package/apis/cqn.d.ts +0 -18
  228. package/apis/csn.d.ts +0 -21
  229. package/apis/events.d.ts +0 -18
  230. package/apis/internal/inference.d.ts +0 -18
  231. package/apis/linked.d.ts +0 -18
  232. package/apis/log.d.ts +0 -20
  233. package/apis/models.d.ts +0 -18
  234. package/apis/ql.d.ts +0 -18
  235. package/apis/reflect.d.ts +0 -32
  236. package/apis/server.d.ts +0 -18
  237. package/apis/services.d.ts +0 -22
  238. package/bin/cds-serve.js +0 -56
  239. package/lib/compile/to/gql.js +0 -15
  240. package/lib/srv/protocols/_legacy.js +0 -44
  241. package/lib/utils/jest.js +0 -43
  242. package/libx/_runtime/auth/index.js +0 -193
  243. package/libx/_runtime/auth/strategies/JWT.js +0 -37
  244. package/libx/_runtime/auth/strategies/basic.js +0 -20
  245. package/libx/_runtime/auth/strategies/dummy.js +0 -14
  246. package/libx/_runtime/auth/strategies/ias-auth.js +0 -1
  247. package/libx/_runtime/auth/strategies/mock.js +0 -77
  248. package/libx/_runtime/auth/strategies/xssecUtils.js +0 -93
  249. package/libx/_runtime/auth/strategies/xsuaa.js +0 -38
  250. package/libx/_runtime/common/perf/index.js +0 -19
  251. package/libx/_runtime/common/utils/ensureIEEE754.js +0 -29
  252. package/libx/_runtime/fiori/draft.js +0 -2
  253. package/libx/_runtime/fiori/generic/activate.js +0 -190
  254. package/libx/_runtime/fiori/generic/before.js +0 -201
  255. package/libx/_runtime/fiori/generic/cancel.js +0 -19
  256. package/libx/_runtime/fiori/generic/delete.js +0 -21
  257. package/libx/_runtime/fiori/generic/edit.js +0 -157
  258. package/libx/_runtime/fiori/generic/index.js +0 -25
  259. package/libx/_runtime/fiori/generic/new.js +0 -82
  260. package/libx/_runtime/fiori/generic/patch.js +0 -101
  261. package/libx/_runtime/fiori/generic/prepare.js +0 -57
  262. package/libx/_runtime/fiori/generic/read.js +0 -1340
  263. package/libx/_runtime/fiori/generic/readOverDraft.js +0 -146
  264. package/libx/_runtime/fiori/utils/csn.js +0 -13
  265. package/libx/_runtime/fiori/utils/delete.js +0 -114
  266. package/libx/_runtime/fiori/utils/handler.js +0 -264
  267. package/libx/_runtime/fiori/utils/lockInfo.js +0 -27
  268. package/libx/_runtime/fiori/utils/req.js +0 -23
  269. package/libx/_runtime/fiori/utils/stream.js +0 -36
  270. package/libx/_runtime/fiori/utils/where.js +0 -254
  271. package/libx/_runtime/index.js +0 -22
  272. package/libx/odata/utils/handler.js +0 -120
  273. package/libx/odata/utils/metaInfo.js +0 -410
  274. package/libx/odata/utils/path.js +0 -75
  275. package/libx/rest/RestRequest.js +0 -32
  276. package/libx/rest/index.js +0 -3
  277. package/libx/rest/readme.md +0 -1
  278. /package/libx/common/assert/{type.js → type-strict.js} +0 -0
@@ -1,146 +0,0 @@
1
- const cds = require('../../cds')
2
- const { SELECT } = cds.ql
3
- const { getEnrichedCQN, hasDraft, ensureDraftsSuffix } = require('../utils/handler')
4
- const { readAndDeleteKeywords } = require('../utils/where')
5
- const { cqn2cqn4sql } = require('../../common/utils/cqn2cqn4sql')
6
- const { isActiveEntityRequested } = require('../../../_runtime/fiori/utils/where')
7
-
8
- const _modifyWhere = (where, context) => {
9
- for (let i = 0; i < where.length; i++) {
10
- const element = where[i]
11
-
12
- if (element.xpr) {
13
- _modifyWhere(element.xpr, context)
14
- continue
15
- }
16
-
17
- if (element.SELECT) {
18
- const subCqnDraft = SELECT.from(
19
- {
20
- ref: [...element.SELECT.from.ref],
21
- as: element.SELECT.from.as
22
- },
23
- [1]
24
- )
25
-
26
- where[i] = subCqnDraft
27
- _modifyCQN(subCqnDraft, element.SELECT.where, context)
28
- }
29
- }
30
- }
31
-
32
- const _modifyCQN = (cqnDraft, where, context) => {
33
- const whereDraft = [...where]
34
- const result = readAndDeleteKeywords(['IsActiveEntity'], whereDraft)
35
- cqnDraft.where(whereDraft)
36
-
37
- if (result && result.value.val === false) {
38
- const fromRef = cqnDraft.SELECT.from.ref
39
- cqnDraft.SELECT.from.ref[fromRef.length - 1] = ensureDraftsSuffix(fromRef[fromRef.length - 1])
40
- }
41
- if (cqnDraft.SELECT.columns) {
42
- cqnDraft.SELECT.columns = cqnDraft.SELECT.columns.map(e =>
43
- e.ref && e.ref.includes('IsActiveEntity') ? { val: true, as: 'IsActiveEntity', cast: { type: 'cds.Boolean' } } : e
44
- )
45
- }
46
-
47
- _modifyWhere(cqnDraft.SELECT.where, context)
48
- }
49
-
50
- const _hasNavToNonDraftEnclosedAssoc = (pathSegments, definitions, excludeAssoc) => {
51
- if (pathSegments.length < 2) return false
52
- const entity = definitions[pathSegments[0]]
53
- const nav = entity.elements[pathSegments[1]]
54
-
55
- if (nav._isAssociationStrict) {
56
- if (nav['@odata.draft.enclosed']) return false
57
- if (excludeAssoc == null) return true
58
- if (!excludeAssoc(nav)) return true
59
- }
60
-
61
- // At this point we know that nav is a composition, so we have to recursively
62
- // follow the navigation until we reach the end or find an association.
63
- pathSegments.shift()
64
- pathSegments[0] = nav.target
65
- return _hasNavToNonDraftEnclosedAssoc(pathSegments, definitions, excludeAssoc)
66
- }
67
-
68
- const _shouldReadOverDraft = (req, definitions) => {
69
- const SELECT = req.query.SELECT
70
- const fromRef = SELECT.from.ref
71
-
72
- if (!fromRef) return false
73
-
74
- // for $expand requests, read over the draft if the navigation target is non-draft-enabled
75
- // REVISIT: this is an interim workaround to be removed
76
- if (!req.target._isDraftEnabled && SELECT.columns?.some(column => column.expand)) return true
77
-
78
- // read over the draft only for navigation scenarios
79
- if (fromRef.length === 1) return false
80
-
81
- const firstFromRef = fromRef[0]
82
- const rootEntityName = typeof firstFromRef === 'string' ? firstFromRef : firstFromRef.id
83
- const rootEntity = definitions[rootEntityName]
84
-
85
- // read over the draft only if the root entity is draft-enabled
86
- if (!rootEntity._isDraftEnabled) return false
87
-
88
- // read over the draft only if the navigation starts from a draft entity, e.g.,
89
- // /Books(ID=1, IsActiveEntity=false)
90
- if (isActiveEntityRequested(firstFromRef.where)) return false
91
-
92
- const pathSegments = fromRef.map(path => (typeof path === 'string' ? path : path.id))
93
- const excludeAssoc = assoc => assoc.name === 'DraftAdministrativeData' || assoc.name === 'SiblingEntity'
94
-
95
- // Read over the draft only if:
96
- // - the navigation target is an association and
97
- // - isn't annotated with the @odata.draft.enclosed annotation
98
- return _hasNavToNonDraftEnclosedAssoc(pathSegments, definitions, excludeAssoc)
99
- }
100
-
101
- /**
102
- * Generic Handler for READ requests.
103
- *
104
- * @param {import('../../cds-services/adapter/odata-v4/ODataRequest')} req
105
- * @param next
106
- * @returns {Promise<Array>}
107
- */
108
- const _readOverDraftHandler = async function (req, next) {
109
- if (!cds.db) req.reject('NO_DATABASE_CONNECTION')
110
-
111
- const definitions = this.model.definitions
112
-
113
- // determine whether the request is handled here (read over draft handler),
114
- // or whether it is passed to the next registered handler/route
115
- if (!_shouldReadOverDraft(req, definitions)) return next()
116
-
117
- const rows = req.query.SELECT.limit && req.query.SELECT.limit.rows
118
- if (rows && rows.val === 0) return Promise.resolve([])
119
-
120
- // REVISIT DRAFT HANDLING: cqn2cqn4sql must not be called here
121
- const sqlQuery = cqn2cqn4sql(req.query, this.model, { _4db: req.target._isDraftEnabled, _4fiori: true })
122
-
123
- if (cds.env.features.stream_compat && req.query._streaming) {
124
- sqlQuery._streaming = true
125
- }
126
-
127
- const hasDraftEntity = hasDraft(definitions, sqlQuery)
128
-
129
- if (hasDraftEntity && sqlQuery.SELECT.where && sqlQuery.SELECT.where.length) {
130
- let cqnDraft = SELECT.from({
131
- ref: [...sqlQuery.SELECT.from.ref],
132
- as: sqlQuery.SELECT.from.as
133
- })
134
-
135
- cqnDraft.SELECT.columns = sqlQuery.SELECT.columns
136
- _modifyCQN(cqnDraft, sqlQuery.SELECT.where, req)
137
- cqnDraft = getEnrichedCQN(cqnDraft, sqlQuery.SELECT, [])
138
- return cds.tx(req).run(cqnDraft)
139
- }
140
-
141
- return cds.tx(req).run(sqlQuery)
142
- }
143
-
144
- module.exports = cds.service.impl(function readOverDraft(srv) {
145
- srv.on('READ', '*', _readOverDraftHandler)
146
- })
@@ -1,13 +0,0 @@
1
- /**
2
- * Returns true/false if entity is root of a document in a draft enabled service.
3
- *
4
- * @param {object} definitions Definitions of the reflected model
5
- * @param {string} entityName Name of the entity
6
- */
7
- const isDraftRootEntity = (definitions, entityName) => {
8
- return definitions[entityName] && definitions[entityName]['@Common.DraftRoot.PreparationAction']
9
- }
10
-
11
- module.exports = {
12
- isDraftRootEntity
13
- }
@@ -1,114 +0,0 @@
1
- const cds = require('../../cds')
2
- const getError = require('../../common/error')
3
- const { SELECT, DELETE } = cds.ql
4
-
5
- const { isDraftRootEntity } = require('./csn')
6
- const {
7
- getUpdateDraftAdminCQN,
8
- ensureDraftsSuffix,
9
- ensureNoDraftsSuffix,
10
- getDeleteDraftAdminCqn,
11
- getCompositionTargets
12
- } = require('./handler')
13
- const { extractKeyConditions } = require('./where')
14
-
15
- const _getSelectCQN = (req, keys) => {
16
- return SELECT.from(ensureNoDraftsSuffix(req.target.name), [1]).where(keys.keyList)
17
- }
18
-
19
- const _getDraftSelectCQN = (req, keys) => {
20
- const draftEntityName = ensureDraftsSuffix(req.target.name)
21
-
22
- return SELECT.from(draftEntityName, ['DraftUUID'])
23
- .join('DRAFT.DraftAdministrativeData')
24
- .on('DraftAdministrativeData_DraftUUID =', { ref: ['DRAFT.DraftAdministrativeData', 'DraftUUID'] })
25
- .where(keys.keyList)
26
- }
27
-
28
- const _validate = (activeResult, draftResult, req, IsActiveEntity) => {
29
- if (
30
- (IsActiveEntity === true && activeResult.length === 0) ||
31
- (IsActiveEntity === false && draftResult.length === 0)
32
- ) {
33
- req.reject(req._etagValidationClause ? 412 : 404)
34
- }
35
- }
36
-
37
- const deleteDraft = async (req, srv, includingActive = false) => {
38
- if (!cds.db) req.reject('NO_DATABASE_CONNECTION')
39
-
40
- const dbtx = cds.tx(req)
41
- const definitions = srv.model.definitions
42
-
43
- const where = req.query.DELETE.from.ref?.[req.query.DELETE.from.ref.length - 1].where
44
- if (!where) {
45
- req.reject(getError(500, 'Invalid delete draft request'))
46
- }
47
-
48
- // REVISIT: how to handle delete of to 1 assoc
49
- const keys = extractKeyConditions(where)
50
-
51
- // IsActiveEntity is deleted from where clause in auth.js, hence keys.IsActiveEntity is undefined here.
52
- // Intentional?
53
- const deleteActive = keys.IsActiveEntity !== false
54
-
55
- const selectActive = _getSelectCQN(req, keys)
56
- const selectDraft = _getDraftSelectCQN(req, keys)
57
-
58
- if (req._etagValidationClause) {
59
- if (keys.IsActiveEntity) selectActive.where(req._etagValidationClause)
60
- else selectDraft.where(req._etagValidationClause)
61
- }
62
-
63
- const [activeResult, draftResult] = await Promise.all([dbtx.run(selectActive), dbtx.run(selectDraft)])
64
-
65
- _validate(activeResult, draftResult, req, deleteActive)
66
-
67
- if (isDraftRootEntity(definitions, ensureNoDraftsSuffix(req.target.name)) && !deleteActive) {
68
- const draftUUID = draftResult[0].DraftUUID
69
-
70
- const draftTablesToDeleteFrom = [req.target.name + '_drafts']
71
- for (const [entity] of getCompositionTargets(req.target, srv).entries()) {
72
- if (!draftTablesToDeleteFrom.includes(entity + '_drafts')) draftTablesToDeleteFrom.push(entity + '_drafts')
73
- }
74
-
75
- const deleteDraftAdminCqn = getDeleteDraftAdminCqn(draftUUID)
76
-
77
- return dbtx.run([
78
- deleteDraftAdminCqn,
79
- ...draftTablesToDeleteFrom.map(dt => {
80
- const d = DELETE.from(dt).where({ DraftAdministrativeData_DraftUUID: draftUUID })
81
- d._suppressDeepDelete = true // hidden flag to tell db layer that no deep delete is required
82
- return d
83
- })
84
- ])
85
- }
86
-
87
- const source = definitions[ensureNoDraftsSuffix(req.target.name)]
88
- const delCQNs = []
89
-
90
- if (includingActive) {
91
- const r = new cds.Request({ query: DELETE.from(ensureNoDraftsSuffix(req.target.name)).where(keys.keyList) })
92
-
93
- // REVISIT: should not be necessary
94
- r._ = Object.assign(r._, req._)
95
- r._.query = req.query
96
-
97
- await dbtx.dispatch(r)
98
- }
99
-
100
- if (draftResult.length !== 0) {
101
- delCQNs.push(DELETE.from(ensureDraftsSuffix(source.name)).where(keys.keyList))
102
-
103
- const draftUUID = draftResult[0].DraftUUID
104
- if (isDraftRootEntity(definitions, ensureNoDraftsSuffix(req.target.name))) {
105
- delCQNs.push(DELETE.from('DRAFT.DraftAdministrativeData').where({ draftUUID }))
106
- } else {
107
- delCQNs.push(getUpdateDraftAdminCQN(req, draftUUID))
108
- }
109
- }
110
-
111
- return Promise.all(delCQNs.map(cqn => dbtx.run(cqn)))
112
- }
113
-
114
- module.exports = { deleteDraft }
@@ -1,264 +0,0 @@
1
- const cds = require('../../cds')
2
- const { Object_keys } = cds.utils
3
- const { UPDATE, SELECT } = cds.ql
4
- const { getColumns } = require('../../common/utils/columns')
5
- const { ensureNoDraftsSuffix, ensureDraftsSuffix, ensureUnlocalized } = require('../../common/utils/draft')
6
- const getTemplate = require('../../common/utils/template')
7
-
8
- const { DRAFT_COLUMNS_MAP } = require('../../common/constants/draft')
9
-
10
- // unofficial config!
11
- const MAX_RECURSION_DEPTH = (cds.env.features.recursion_depth && Number(cds.env.features.recursion_depth)) || 2
12
-
13
- const _getParentCQNWithKeyColumn = (parentCQN, parentKeyName) => {
14
- const parentCQNWithKeyColumn = Object.assign({}, parentCQN)
15
- parentCQNWithKeyColumn.SELECT = Object.assign({}, parentCQN.SELECT)
16
- parentCQNWithKeyColumn.SELECT.columns = [{ ref: [parentKeyName] }]
17
- return parentCQNWithKeyColumn
18
- }
19
-
20
- const _getSubSelectFromCQN = (element, columns, selectFromDraft) => {
21
- return SELECT.from(
22
- selectFromDraft ? ensureDraftsSuffix(element.source) : element.source,
23
- selectFromDraft ? [...columns, 'DraftAdministrativeData_DraftUUID'] : columns
24
- )
25
- }
26
-
27
- const getSubCQNs = ({ definitions, rootCQN, compositionTree, selectFromDraft = false }) => {
28
- const subCQNs = []
29
- // only one backLink
30
- const _generateSubCQNs = (parentCQN, compositionElements, elementMap = new Map()) => {
31
- for (const element of compositionElements) {
32
- const backLink = element.backLinks[0] || element.customBackLinks[0]
33
- if (backLink) {
34
- const fqn = element.source + ':' + element.name
35
- const seen = elementMap.get(fqn)
36
- if (seen && seen >= MAX_RECURSION_DEPTH) {
37
- // recursion -> abort
38
- continue
39
- }
40
-
41
- const columns = getColumns(definitions[element.source], { onlyNames: true, filterVirtual: true })
42
- const subCQN = _getSubSelectFromCQN(element, columns, selectFromDraft)
43
- subCQN.where([{ ref: [backLink.entityKey] }, 'in', _getParentCQNWithKeyColumn(parentCQN, backLink.targetKey)])
44
- subCQNs.push({ cqn: subCQN })
45
- const newElementMap = new Map(elementMap)
46
- newElementMap.set(fqn, (seen && seen + 1) || 1)
47
- _generateSubCQNs(subCQN, element.compositionElements, newElementMap)
48
- }
49
- }
50
- }
51
-
52
- _generateSubCQNs(rootCQN, compositionTree.compositionElements)
53
-
54
- return subCQNs
55
- }
56
-
57
- const proxifyToNoDraftsName = target => {
58
- const entityProxyHandler = {
59
- get: (obj, prop) => (prop === 'name' ? ensureNoDraftsSuffix(target.name) : obj[prop])
60
- }
61
- return new Proxy(target, entityProxyHandler)
62
- }
63
-
64
- const hasDraft = (definitions, cqn) => {
65
- if (
66
- cqn.SELECT.from.ref &&
67
- definitions[cqn.SELECT.from.ref[cqn.SELECT.from.ref.length - 1]] &&
68
- definitions[cqn.SELECT.from.ref[cqn.SELECT.from.ref.length - 1]]._isDraftEnabled
69
- ) {
70
- return true
71
- }
72
-
73
- if (cqn.SELECT.where) {
74
- for (const element of cqn.SELECT.where) {
75
- if (element.SELECT && hasDraft(definitions, element)) {
76
- return true
77
- }
78
- }
79
- }
80
-
81
- return false
82
- }
83
-
84
- const getUpdateDraftAdminCQN = ({ user }, draftUUID) => {
85
- const set = {
86
- InProcessByUser: user.id,
87
- LastChangedByUser: user.id,
88
- LastChangeDateTime: new Date()
89
- }
90
-
91
- return UPDATE('DRAFT.DraftAdministrativeData').data(set).where({ DraftUUID: draftUUID })
92
- }
93
-
94
- const _addAlias = (where, tableName) => {
95
- // copy where
96
- return where.map(element => {
97
- if (element.xpr) {
98
- return { xpr: _addAlias(element.xpr, tableName) }
99
- }
100
- if (element.ref && element.ref.length === 1) {
101
- // and copy ref
102
- return { ref: [tableName, element.ref[0]] }
103
- }
104
- return element
105
- })
106
- }
107
-
108
- const _getSelectedColumns = (columns, selectedColumns) => {
109
- return columns.filter(col => {
110
- if (
111
- col.ref &&
112
- selectedColumns.some(sel => sel.ref && sel.ref[sel.ref.length - 1] === col.ref[col.ref.length - 1])
113
- ) {
114
- return true
115
- } else if (col.as && selectedColumns.some(sel => sel.as && sel.as === col.as)) {
116
- return true
117
- }
118
-
119
- return false
120
- })
121
- }
122
-
123
- const getEnrichedCQN = (cqn, select, draftWhere, scenarioAlias, addLimitOrder = true) => {
124
- const tableName =
125
- (cqn.SELECT.from.ref && cqn.SELECT.from.ref[0]) || (cqn.SELECT.from.args && cqn.SELECT.from.args[0].ref[0])
126
-
127
- if (draftWhere && draftWhere.length !== 0) {
128
- cqn.where(_addAlias(draftWhere, tableName))
129
- }
130
-
131
- if (select.distinct) {
132
- cqn.SELECT.distinct = true
133
- }
134
-
135
- const alias = (select.from && select.from.as) || scenarioAlias
136
-
137
- if (select.count) cqn.SELECT.count = true
138
- if (select.one) cqn.SELECT.one = true
139
-
140
- if (select.having) {
141
- cqn.having(_aliased(select.having, select.columns, alias))
142
- }
143
-
144
- // groupBy, orderBy and limit do not support partial CQNs
145
- if (select.groupBy) {
146
- cqn.SELECT.groupBy = _aliased(select.groupBy, select.columns, alias)
147
- cqn.SELECT.columns = _getSelectedColumns(cqn.SELECT.columns, select.columns)
148
- }
149
-
150
- if (select.orderBy && addLimitOrder) {
151
- cqn.SELECT.orderBy = _aliased(select.orderBy, select.columns, alias)
152
- }
153
-
154
- if (select.limit && addLimitOrder) {
155
- cqn.SELECT.limit = select.limit
156
- }
157
-
158
- return cqn
159
- }
160
-
161
- const _aliasRef = (ref, alias) => {
162
- const newRef = [...ref]
163
- // we skip draft columns because they are mostly calculated later on
164
- if (alias && !(ref[ref.length - 1] in DRAFT_COLUMNS_MAP)) {
165
- newRef.unshift(alias)
166
- }
167
- return newRef
168
- }
169
-
170
- const getDeleteDraftAdminCqn = draftUUID =>
171
- DELETE.from('DRAFT.DraftAdministrativeData').where([{ ref: ['DraftUUID'] }, '=', { val: draftUUID }])
172
-
173
- const _aliased = (arr, columns, alias) =>
174
- arr.map(item => {
175
- if (item.ref && columns.some(c => c.as === item.ref[item.ref.length - 1])) {
176
- // remove table alias if present for a calculated field
177
- if (item.ref[0] === alias) {
178
- return item.ref.splice(0, 1)
179
- }
180
- } else if (alias && item.ref && item.ref[0] !== alias) {
181
- return Object.assign({}, item, { ref: _aliasRef(item.ref, alias) })
182
- }
183
- return item
184
- })
185
-
186
- // Only works for root entity, otherwise the relative position needs to be adapted
187
- const removeDraftUUIDIfNecessary = req =>
188
- req.http?.req?.headers?.['x-cds-odata-version'] === 'v2'
189
- ? () => {}
190
- : result => delete result.DraftAdministrativeData_DraftUUID
191
-
192
- const addColumnAlias = (columns, alias) => {
193
- if (!alias) {
194
- return columns
195
- }
196
-
197
- return columns.map(col => {
198
- if (typeof col === 'string') {
199
- return { ref: [alias, col] }
200
- }
201
-
202
- if (col.ref && !col.expand) {
203
- const obj = Object.assign({}, col)
204
- obj.ref = [alias, ...col.ref.slice(0)]
205
- return obj
206
- }
207
-
208
- if (col.func && col.args) {
209
- const obj = Object.assign({}, col)
210
- obj.args = addColumnAlias(col.args, alias)
211
- return obj
212
- }
213
-
214
- return col
215
- })
216
- }
217
-
218
- const getCompositionTargets = (entity, srv) => {
219
- if (!entity.own('_deepCompositionTargets')) {
220
- const _deepCompositionTargets = []
221
- getTemplate(undefined, srv, entity, {
222
- pick: element => {
223
- if (element.isAssociation && !element._isAssociationStrict && srv.model.definitions[element.target].drafts)
224
- _deepCompositionTargets.push(element.target)
225
- },
226
- ignore: element => !element.isAssociation || element._isAssociationStrict
227
- })
228
- entity.set('_deepCompositionTargets', new Set(_deepCompositionTargets))
229
- }
230
-
231
- return entity.own('_deepCompositionTargets')
232
- }
233
-
234
- const replaceRefWithDraft = ref => {
235
- if (!ref || !ref[0]) return
236
- ref[0] = ensureDraftsSuffix(ref[0])
237
- }
238
-
239
- const draftIsLocked = lastChangedAt => {
240
- // default timeout timer is 15 minutes
241
- const DRAFT_CANCEL_TIMEOUT_IN_MS = ((cds.env.drafts && cds.env.drafts.cancellationTimeout) || 15) * 60 * 1000
242
- return DRAFT_CANCEL_TIMEOUT_IN_MS > Date.now() - Date.parse(lastChangedAt)
243
- }
244
-
245
- const entity_keys = entity =>
246
- Object_keys(entity.keys).filter(key => key !== 'IsActiveEntity' && !entity.keys[key]._isAssociationStrict)
247
-
248
- module.exports = {
249
- getSubCQNs,
250
- draftIsLocked,
251
- getUpdateDraftAdminCQN,
252
- getEnrichedCQN,
253
- removeDraftUUIDIfNecessary,
254
- ensureDraftsSuffix,
255
- ensureNoDraftsSuffix,
256
- ensureUnlocalized,
257
- hasDraft,
258
- proxifyToNoDraftsName,
259
- addColumnAlias,
260
- replaceRefWithDraft,
261
- entity_keys,
262
- getDeleteDraftAdminCqn,
263
- getCompositionTargets
264
- }
@@ -1,27 +0,0 @@
1
- const cds = require('../../cds')
2
- const { getTransition } = require('../../common/utils/resolveView')
3
- const { getKeyData, getLockWhere } = require('../utils/where')
4
- const { entity_keys } = require('../utils/handler')
5
-
6
- const getLockInfo = (req, dataSource) => {
7
- const keys = entity_keys(req.target)
8
- const keyData = getKeyData(keys, req.query.SELECT.from.ref[0].where)
9
- const rootWhere = keys.reduce((res, key) => {
10
- res[key] = keyData[key]
11
- return res
12
- }, {})
13
-
14
- if (dataSource === undefined || dataSource === cds.db) {
15
- const transition = getTransition(req.target, cds.db)
16
-
17
- // gets the underlying target entity, as record locking can't be
18
- // applied to localized views
19
- const target = transition.target
20
- const where = getLockWhere(rootWhere, transition.mapping)
21
- return { target, where, rootWhere }
22
- }
23
-
24
- return { target: req.target, where: req.query.SELECT.from.ref[0].where, rootWhere }
25
- }
26
-
27
- module.exports = getLockInfo
@@ -1,23 +0,0 @@
1
- const _ref2name = ref => (ref.id || ref).replace(/_drafts$/, '')
2
-
3
- const isNavigationToMany = (req, model) => {
4
- // only one segment -> false
5
- if (req.subject.ref.length < 2) return
6
-
7
- // last segment has it's own where -> false
8
- if (req.subject.ref[req.subject.ref.length - 1].where) return
9
-
10
- // determine the csn element of the last navigation and return it's is2many property
11
- let cur
12
- for (let i = 0; i < req.subject.ref.length - 1; i++) {
13
- cur = cur
14
- ? model.definitions[cur.elements[_ref2name(req.subject.ref[i])].target]
15
- : model.definitions[_ref2name(req.subject.ref[i])]
16
- }
17
- const last = cur.elements[_ref2name(req.subject.ref[req.subject.ref.length - 1])]
18
- return last.is2many
19
- }
20
-
21
- module.exports = {
22
- isNavigationToMany
23
- }
@@ -1,36 +0,0 @@
1
- const { ensureDraftsSuffix } = require('./handler')
2
- const { removeIsActiveEntityRecursively, isActiveEntityRequested } = require('./where')
3
-
4
- const _adaptSubSelectsDraft = select => {
5
- if (select.SELECT.from.ref) {
6
- const index = select.SELECT.from.ref.length - 1
7
- select.SELECT.from.ref[index] = ensureDraftsSuffix(select.SELECT.from.ref[index])
8
- }
9
-
10
- if (select.SELECT.where) {
11
- for (let i = 0; i < select.SELECT.where.length; i++) {
12
- const element = select.SELECT.where[i]
13
- if (element.SELECT) {
14
- _adaptSubSelectsDraft(element)
15
- } else if (element.xpr) {
16
- _adaptSubSelectsDraft({ SELECT: { from: {}, where: element.xpr } })
17
- }
18
- }
19
- }
20
- }
21
-
22
- const adaptStreamCQN = (cqn, isDraft = false) => {
23
- let draft = isDraft
24
- if (!draft) {
25
- draft = !isActiveEntityRequested(cqn.SELECT.where)
26
- const ref = cqn.SELECT.from?.ref
27
- if (!draft && ref) draft = !isActiveEntityRequested(ref[ref.length - 1].where)
28
- }
29
-
30
- if (draft) _adaptSubSelectsDraft(cqn)
31
- cqn.SELECT.where = removeIsActiveEntityRecursively(cqn.SELECT.where)
32
- }
33
-
34
- module.exports = {
35
- adaptStreamCQN
36
- }