@sap/cds 7.9.4 → 8.0.4

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 (276) hide show
  1. package/CHANGELOG.md +128 -3659
  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 +30 -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/tree.js +0 -1
  116. package/libx/_runtime/common/composition/update.js +3 -3
  117. package/libx/_runtime/common/error/frontend.js +21 -12
  118. package/libx/_runtime/common/error/log.js +36 -0
  119. package/libx/_runtime/common/error/utils.js +2 -5
  120. package/libx/_runtime/common/generic/auth/autoexpose.js +18 -17
  121. package/libx/_runtime/common/generic/auth/expand.js +1 -1
  122. package/libx/_runtime/common/generic/auth/readOnly.js +1 -2
  123. package/libx/_runtime/common/generic/auth/restrict.js +23 -42
  124. package/libx/_runtime/common/generic/auth/restrictions.js +2 -7
  125. package/libx/_runtime/common/generic/auth/utils.js +91 -88
  126. package/libx/_runtime/common/generic/crud.js +6 -5
  127. package/libx/_runtime/common/generic/etag.js +7 -12
  128. package/libx/_runtime/common/generic/input.js +70 -68
  129. package/libx/_runtime/common/generic/paging.js +1 -0
  130. package/libx/_runtime/common/generic/sorting.js +1 -0
  131. package/libx/_runtime/common/generic/temporal.js +8 -2
  132. package/libx/_runtime/common/i18n/index.js +1 -1
  133. package/libx/_runtime/common/i18n/messages.properties +3 -1
  134. package/libx/_runtime/common/utils/binary.js +8 -2
  135. package/libx/_runtime/common/utils/compareJson.js +5 -1
  136. package/libx/_runtime/common/utils/copy.js +6 -11
  137. package/libx/_runtime/common/utils/cqn2cqn4sql.js +16 -14
  138. package/libx/_runtime/common/utils/differ.js +3 -6
  139. package/libx/_runtime/common/utils/keys.js +77 -18
  140. package/libx/_runtime/common/utils/postProcess.js +12 -15
  141. package/libx/_runtime/common/utils/propagateForeignKeys.js +0 -1
  142. package/libx/_runtime/common/utils/resolveView.js +2 -3
  143. package/libx/_runtime/common/utils/restrictions.js +45 -17
  144. package/libx/_runtime/common/utils/rewriteAsterisks.js +1 -8
  145. package/libx/_runtime/common/utils/stream.js +3 -16
  146. package/libx/_runtime/common/utils/streamProp.js +8 -18
  147. package/libx/_runtime/common/utils/structured.js +1 -1
  148. package/libx/_runtime/common/utils/ucsn.js +0 -2
  149. package/libx/_runtime/db/Service.js +0 -72
  150. package/libx/_runtime/db/data-conversion/post-processing.js +0 -1
  151. package/libx/_runtime/db/expand/expandCQNToJoin.js +9 -9
  152. package/libx/_runtime/db/expand/rawToExpanded.js +0 -8
  153. package/libx/_runtime/db/generic/input.js +3 -8
  154. package/libx/_runtime/db/generic/rewrite.js +1 -0
  155. package/libx/_runtime/db/query/read.js +2 -2
  156. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +0 -1
  157. package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -1
  158. package/libx/_runtime/db/utils/columns.js +2 -6
  159. package/libx/_runtime/fiori/lean-draft.js +138 -56
  160. package/libx/_runtime/hana/Service.js +0 -1
  161. package/libx/_runtime/hana/driver.js +1 -1
  162. package/libx/_runtime/hana/dynatrace.js +1 -2
  163. package/libx/_runtime/hana/pool.js +11 -21
  164. package/libx/_runtime/hana/streaming.js +0 -1
  165. package/libx/_runtime/messaging/common-utils/AMQPClient.js +0 -1
  166. package/libx/_runtime/messaging/common-utils/authorizedRequest.js +1 -1
  167. package/libx/_runtime/messaging/common-utils/normalizeIncomingMessage.js +1 -1
  168. package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +1 -1
  169. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +19 -33
  170. package/libx/_runtime/messaging/event-broker.js +54 -27
  171. package/libx/_runtime/messaging/file-based.js +3 -3
  172. package/libx/_runtime/messaging/http-utils/token.js +1 -1
  173. package/libx/_runtime/messaging/kafka.js +2 -2
  174. package/libx/_runtime/messaging/redis-messaging.js +0 -1
  175. package/libx/_runtime/remote/Service.js +25 -25
  176. package/libx/_runtime/remote/utils/client.js +4 -5
  177. package/libx/_runtime/remote/utils/cloudSdkProvider.js +0 -3
  178. package/libx/_runtime/remote/utils/data.js +0 -1
  179. package/libx/_runtime/sqlite/Service.js +1 -2
  180. package/libx/_runtime/ucl/Service.js +37 -78
  181. package/libx/common/assert/index.js +22 -21
  182. package/libx/common/assert/type-relaxed.js +39 -0
  183. package/libx/common/assert/utils.js +3 -2
  184. package/libx/common/assert/validation.js +3 -8
  185. package/libx/common/utils/index.js +5 -0
  186. package/libx/common/utils/path.js +51 -0
  187. package/libx/odata/ODataAdapter.js +126 -0
  188. package/libx/odata/index.js +15 -2
  189. package/libx/odata/middleware/batch.js +320 -84
  190. package/libx/odata/middleware/body-parser.js +33 -0
  191. package/libx/odata/middleware/create.js +44 -59
  192. package/libx/odata/middleware/delete.js +23 -12
  193. package/libx/odata/middleware/error.js +30 -6
  194. package/libx/odata/middleware/metadata.js +38 -26
  195. package/libx/odata/middleware/operation.js +93 -69
  196. package/libx/odata/middleware/parse.js +6 -8
  197. package/libx/odata/middleware/read.js +117 -93
  198. package/libx/odata/middleware/service-document.js +22 -19
  199. package/libx/odata/middleware/stream.js +54 -56
  200. package/libx/odata/middleware/update.js +79 -87
  201. package/libx/odata/parse/afterburner.js +191 -175
  202. package/libx/odata/parse/cqn2odata.js +5 -5
  203. package/libx/odata/parse/grammar.peggy +27 -20
  204. package/libx/odata/parse/multipartToJson.js +17 -9
  205. package/libx/odata/parse/parser.js +1 -1
  206. package/libx/odata/utils/etag.js +14 -6
  207. package/libx/odata/utils/index.js +84 -12
  208. package/libx/odata/utils/metadata.js +161 -0
  209. package/libx/odata/utils/postProcess.js +89 -0
  210. package/libx/odata/utils/readAfterWrite.js +134 -17
  211. package/libx/odata/utils/result.js +36 -142
  212. package/libx/outbox/index.js +4 -3
  213. package/libx/rest/RestAdapter.js +115 -182
  214. package/libx/rest/middleware/create.js +28 -24
  215. package/libx/rest/middleware/delete.js +7 -10
  216. package/libx/rest/middleware/error.js +26 -16
  217. package/libx/rest/middleware/operation.js +48 -41
  218. package/libx/rest/middleware/parse.js +128 -126
  219. package/libx/rest/middleware/read.js +20 -27
  220. package/libx/rest/middleware/update.js +26 -31
  221. package/package.json +17 -8
  222. package/server.js +4 -2
  223. package/apis/cds.d.ts +0 -3
  224. package/apis/core.d.ts +0 -21
  225. package/apis/cqn.d.ts +0 -18
  226. package/apis/csn.d.ts +0 -21
  227. package/apis/events.d.ts +0 -18
  228. package/apis/internal/inference.d.ts +0 -18
  229. package/apis/linked.d.ts +0 -18
  230. package/apis/log.d.ts +0 -20
  231. package/apis/models.d.ts +0 -18
  232. package/apis/ql.d.ts +0 -18
  233. package/apis/reflect.d.ts +0 -32
  234. package/apis/server.d.ts +0 -18
  235. package/apis/services.d.ts +0 -22
  236. package/bin/cds-serve.js +0 -56
  237. package/lib/compile/to/gql.js +0 -15
  238. package/lib/srv/protocols/_legacy.js +0 -44
  239. package/lib/utils/jest.js +0 -43
  240. package/libx/_runtime/auth/index.js +0 -193
  241. package/libx/_runtime/auth/strategies/JWT.js +0 -37
  242. package/libx/_runtime/auth/strategies/basic.js +0 -20
  243. package/libx/_runtime/auth/strategies/dummy.js +0 -14
  244. package/libx/_runtime/auth/strategies/ias-auth.js +0 -1
  245. package/libx/_runtime/auth/strategies/mock.js +0 -77
  246. package/libx/_runtime/auth/strategies/xssecUtils.js +0 -93
  247. package/libx/_runtime/auth/strategies/xsuaa.js +0 -38
  248. package/libx/_runtime/common/perf/index.js +0 -19
  249. package/libx/_runtime/common/utils/ensureIEEE754.js +0 -29
  250. package/libx/_runtime/fiori/draft.js +0 -2
  251. package/libx/_runtime/fiori/generic/activate.js +0 -190
  252. package/libx/_runtime/fiori/generic/before.js +0 -201
  253. package/libx/_runtime/fiori/generic/cancel.js +0 -19
  254. package/libx/_runtime/fiori/generic/delete.js +0 -21
  255. package/libx/_runtime/fiori/generic/edit.js +0 -157
  256. package/libx/_runtime/fiori/generic/index.js +0 -25
  257. package/libx/_runtime/fiori/generic/new.js +0 -82
  258. package/libx/_runtime/fiori/generic/patch.js +0 -101
  259. package/libx/_runtime/fiori/generic/prepare.js +0 -57
  260. package/libx/_runtime/fiori/generic/read.js +0 -1340
  261. package/libx/_runtime/fiori/generic/readOverDraft.js +0 -146
  262. package/libx/_runtime/fiori/utils/csn.js +0 -13
  263. package/libx/_runtime/fiori/utils/delete.js +0 -114
  264. package/libx/_runtime/fiori/utils/handler.js +0 -264
  265. package/libx/_runtime/fiori/utils/lockInfo.js +0 -27
  266. package/libx/_runtime/fiori/utils/req.js +0 -23
  267. package/libx/_runtime/fiori/utils/stream.js +0 -36
  268. package/libx/_runtime/fiori/utils/where.js +0 -254
  269. package/libx/_runtime/index.js +0 -22
  270. package/libx/odata/utils/handler.js +0 -120
  271. package/libx/odata/utils/metaInfo.js +0 -410
  272. package/libx/odata/utils/path.js +0 -75
  273. package/libx/rest/RestRequest.js +0 -32
  274. package/libx/rest/index.js +0 -3
  275. package/libx/rest/readme.md +0 -1
  276. /package/libx/common/assert/{type.js → type-strict.js} +0 -0
@@ -1,1340 +0,0 @@
1
- const cds = require('../../cds')
2
- const { SELECT } = cds.ql
3
-
4
- const { cqn2cqn4sql, convertWhereExists } = require('../../common/utils/cqn2cqn4sql')
5
- const { getElementDeep } = require('../../common/utils/csn')
6
- const { DRAFT_COLUMNS_MAP, SCENARIO } = require('../../common/constants/draft')
7
- const { filterNonDraftColumns } = require('../../common/utils/draft')
8
- const {
9
- addColumnAlias,
10
- draftIsLocked,
11
- ensureDraftsSuffix,
12
- ensureNoDraftsSuffix,
13
- ensureUnlocalized,
14
- getEnrichedCQN,
15
- removeDraftUUIDIfNecessary,
16
- replaceRefWithDraft,
17
- entity_keys
18
- } = require('../utils/handler')
19
- const { deleteCondition, readAndDeleteKeywords, removeIsActiveEntityRecursively } = require('../utils/where')
20
- const { adaptStreamCQN } = require('../utils/stream')
21
- const getError = require('../../common/error')
22
-
23
- const _findSubselect = where => {
24
- return where.find((e, i) => {
25
- if (e.xpr) return _findSubselect(e.xpr)
26
- return e.SELECT && where[i - 1] === 'exists'
27
- })
28
- }
29
-
30
- const _findRootSubSelectFor = query => {
31
- if (query.SELECT.where) {
32
- const subSelect = _findSubselect(query.SELECT.where)
33
- return subSelect ? _findRootSubSelectFor(subSelect) : query
34
- }
35
-
36
- return query
37
- }
38
-
39
- // append where with clauses from @restrict
40
- const _getWhereWithAppendedDraftRestrictions = (where = [], req) => {
41
- const restrictions = []
42
- if (req.query._draftRestrictions) {
43
- for (const each of req.query._draftRestrictions) {
44
- const xpr = each._xpr
45
- if (each.target.name === ensureUnlocalized(req.target.name)) {
46
- // restriction might contain or clause -> use xpr for grouping
47
- if (restrictions.length) restrictions.push('or')
48
- xpr.includes('or') ? restrictions.push({ xpr }) : restrictions.push(...xpr)
49
- } else {
50
- // restriction inherited from parent via autoexposure
51
- // find inner most sub select if available and append restriction to where clause
52
- const rootSubSelect = _findRootSubSelectFor({ SELECT: { where } })
53
- if (rootSubSelect && rootSubSelect.SELECT.from) {
54
- if (rootSubSelect.SELECT.where && rootSubSelect.SELECT.where.length) rootSubSelect.SELECT.where.push('and')
55
- const tableAlias = rootSubSelect.SELECT.from.as
56
- rootSubSelect.SELECT.where.push(
57
- ...xpr.map(e => {
58
- if (e.ref) return tableAlias ? { ref: [tableAlias, ...e.ref] } : { ref: [...e.ref] }
59
- return e
60
- })
61
- )
62
- }
63
- }
64
- }
65
- }
66
-
67
- if (restrictions.length) {
68
- if (where.length) where.push('and', { xpr: restrictions })
69
- else where.push(...restrictions)
70
- }
71
- return where
72
- }
73
-
74
- const _isTrue = val => val === true || val === 'true'
75
- const _isFalse = val => val === false || val === 'false'
76
- const _inProcessByUserWhere = userId => [{ ref: ['filterAdmin', 'InProcessByUser'] }, '=', { val: userId }]
77
-
78
- const _getTableName = (
79
- {
80
- target: { name },
81
- query: {
82
- SELECT: { from }
83
- }
84
- },
85
- isDraft = false
86
- ) => {
87
- const table = isDraft ? ensureDraftsSuffix(name) : ensureNoDraftsSuffix(name)
88
- const as = from.args ? from.args[0].as : from.as
89
- if (as) {
90
- return {
91
- table: {
92
- ref: [table],
93
- as: as
94
- },
95
- name: as
96
- }
97
- }
98
-
99
- return {
100
- table: {
101
- ref: [table]
102
- },
103
- name: table
104
- }
105
- }
106
-
107
- const _getTargetKeys = ({ target }) => entity_keys(target)
108
-
109
- const DRAFT_COLUMNS_CASTED = [
110
- {
111
- ref: ['IsActiveEntity'],
112
- cast: { type: 'cds.Boolean' }
113
- },
114
- {
115
- ref: ['HasActiveEntity'],
116
- cast: { type: 'cds.Boolean' }
117
- },
118
- {
119
- ref: ['HasDraftEntity'],
120
- cast: { type: 'cds.Boolean' }
121
- }
122
- ]
123
-
124
- const DRAFT_COLUMNS_CASTED_WITH_DRAFTADMIN_UUID = [
125
- ...DRAFT_COLUMNS_CASTED,
126
- { ref: ['DraftAdministrativeData_DraftUUID'] }
127
- ]
128
-
129
- // default draft values for active entities
130
- const _getDefaultDraftProperties = ({ hasDraft, isActive = true, withDraftUUID = true }) => {
131
- const columns = [
132
- { val: isActive, as: 'IsActiveEntity', cast: { type: 'cds.Boolean' } },
133
- { val: false, as: 'HasActiveEntity', cast: { type: 'cds.Boolean' } }
134
- ]
135
-
136
- if (hasDraft !== null) {
137
- columns.push({
138
- val: Boolean(hasDraft),
139
- as: 'HasDraftEntity',
140
- cast: { type: 'cds.Boolean' }
141
- })
142
- }
143
-
144
- if (withDraftUUID) {
145
- columns.push(
146
- isActive
147
- ? { val: null, as: 'DraftAdministrativeData_DraftUUID' }
148
- : { ref: ['DraftAdministrativeData_DraftUUID'], as: 'DraftAdministrativeData_DraftUUID' }
149
- )
150
- }
151
-
152
- return columns
153
- }
154
-
155
- // draft values for active entities with calculated hasDraft property
156
- const _getDraftPropertiesDetermineDraft = (req, where, tableName, calcDraftUUID = false) => {
157
- const { table } = _getTableName(req, true)
158
- tableName = tableName || table
159
-
160
- const hasDraftQuery = SELECT.from(tableName, [{ val: 1 }])
161
- if (where && where.length > 0) {
162
- // clone where to protect from later modification
163
- hasDraftQuery.where([...where])
164
- }
165
-
166
- let draftUUIDColumn
167
- if (calcDraftUUID) {
168
- draftUUIDColumn = SELECT.from(tableName, ['DraftAdministrativeData_DraftUUID'])
169
- if (where && where.length > 0) {
170
- draftUUIDColumn.where(where)
171
- }
172
- } else {
173
- draftUUIDColumn = { val: null, as: 'DraftAdministrativeData_DraftUUID' }
174
- }
175
-
176
- const xpr = {
177
- xpr: ['case', 'when', hasDraftQuery, 'IS NOT NULL', 'then', 'true', 'else', 'false', 'end'],
178
- as: 'HasDraftEntity',
179
- cast: { type: 'cds.Boolean' }
180
- }
181
-
182
- hasDraftQuery.as = 'HasDraftEntity'
183
- hasDraftQuery.cast = { type: 'cds.Boolean' }
184
-
185
- return [
186
- { val: true, as: 'IsActiveEntity', cast: { type: 'cds.Boolean' } },
187
- { val: false, as: 'HasActiveEntity', cast: { type: 'cds.Boolean' } },
188
- xpr,
189
- draftUUIDColumn
190
- ]
191
- }
192
-
193
- function _copyCQNPartial(partial) {
194
- if (partial.SELECT) {
195
- const newPartial = Object.assign({}, partial)
196
- const newSELECT = Object.assign({}, partial.SELECT)
197
- newSELECT.from = _copyCQNPartial(partial.SELECT.from)
198
- newPartial.SELECT = newSELECT
199
- if (partial.SELECT._4odata) newSELECT._4odata = true
200
- if (partial.SELECT.columns) newPartial.SELECT.columns = _copyArray(partial.SELECT.columns)
201
- if (partial.SELECT.where) newPartial.SELECT.where = _copyArray(partial.SELECT.where)
202
- return newPartial
203
- }
204
-
205
- if (partial.id) {
206
- const res = Object.assign({}, partial)
207
- if (res.where) res.where = _copyArray(res.where)
208
- return res
209
- }
210
-
211
- if (partial.ref) {
212
- return Object.assign({}, partial, { ref: _copyArray(partial.ref) })
213
- }
214
-
215
- return Object.assign({}, partial)
216
- }
217
-
218
- function _copyArray(array) {
219
- return array.map(entry => (typeof entry === 'object' && !(entry instanceof String) ? _copyCQNPartial(entry) : entry))
220
- }
221
-
222
- const _isValidDraftOfWhichIAmOwner = isActiveEntity => isActiveEntity.op === '=' && _isFalse(isActiveEntity.value.val)
223
-
224
- const _isValidActiveWithoutDraft = (isActiveEntity, hasDraftEntity) => {
225
- return (
226
- isActiveEntity.op === '=' &&
227
- _isTrue(isActiveEntity.value.val) &&
228
- hasDraftEntity.op === '=' &&
229
- _isFalse(hasDraftEntity.value.val)
230
- )
231
- }
232
-
233
- const _isValidWithDraftLocked = (isActiveEntity, siblingIsActive, draftInProcessByUser) => {
234
- return (
235
- isActiveEntity.op === '=' &&
236
- _isTrue(isActiveEntity.value.val) &&
237
- siblingIsActive.op === '=' &&
238
- siblingIsActive.value.val === null &&
239
- draftInProcessByUser.op === '!=' &&
240
- draftInProcessByUser.value.val === ''
241
- )
242
- }
243
-
244
- const _isValidWithDraftTimeout = (isActiveEntity, siblingIsActive, draftInProcessByUser) => {
245
- return (
246
- isActiveEntity.op === '=' &&
247
- _isTrue(isActiveEntity.value.val) &&
248
- siblingIsActive.op === '=' &&
249
- siblingIsActive.value.val === null &&
250
- draftInProcessByUser.op === '=' &&
251
- draftInProcessByUser.value.val === ''
252
- )
253
- }
254
-
255
- const _isValidExcludeActiveDraftExists = (isActiveEntity, siblingIsActive) => {
256
- return (
257
- isActiveEntity.op === '=' &&
258
- _isFalse(isActiveEntity.value.val) &&
259
- siblingIsActive.op === '=' &&
260
- siblingIsActive.value.val === null
261
- )
262
- }
263
-
264
- const _filterDraftColumnsBySelected = (draftColumns, columns) => {
265
- const _findByAlias = (draftColumn, alias) => alias && draftColumn.as && alias === draftColumn.as
266
- const _findByRef = (draftColumn, ref) => ref && draftColumn.ref && ref === draftColumn.ref[draftColumn.ref.length - 1]
267
- // include draft-specific columns only if there is no SELECT.columns or if they are selected explicitly
268
- return (
269
- (!columns && draftColumns) ||
270
- draftColumns.filter(
271
- draftColumn =>
272
- (!draftColumn.ref && !draftColumn.as) ||
273
- columns.find(col => {
274
- const ref = col.ref && col.ref[col.ref.length - 1]
275
- return _findByRef(draftColumn, ref) || _findByAlias(draftColumn, ref) || _findByAlias(draftColumn, col.as)
276
- })
277
- )
278
- )
279
- }
280
-
281
- const _isOnlyCount = columns => columns.length === 1 && (columns[0].as === '_counted_' || columns[0].as === '$count')
282
-
283
- const _getOuterMostColumns = (columnsFromRequest, additionalDraftColumns) => {
284
- if (_isOnlyCount(columnsFromRequest)) return columnsFromRequest
285
-
286
- // remove draft columns from columnsFromRequest (if present) to avoid duplicates
287
- const columns = [...columnsFromRequest.filter(ele => !ele.as || !DRAFT_COLUMNS_MAP[ele.as])]
288
- columns.push(...additionalDraftColumns)
289
- return columns
290
- }
291
-
292
- // adds base columns 'InProcessByUser' and 'CreatedByUser' to columns param if needed
293
- // those are required for calculating 'DraftIsProcessedByMe' and 'DraftIsCreatedByMe'
294
- const _ensureDraftAdminColumnsForCalculation = columns => {
295
- columns.forEach(c => {
296
- if (c.ref && c.ref[0] === 'DraftIsCreatedByMe' && !columns.find(e => e.ref && e.ref[0] === 'CreatedByUser')) {
297
- columns.push({ ref: ['CreatedByUser'] })
298
- } else if (
299
- c.ref &&
300
- c.ref[0] === 'DraftIsProcessedByMe' &&
301
- !columns.find(e => e.ref && e.ref[0] === 'InProcessByUser')
302
- ) {
303
- columns.push({ ref: ['InProcessByUser'] })
304
- }
305
- })
306
- }
307
-
308
- const _draftAdminTable = req => {
309
- const { table } = _getTableName(req)
310
-
311
- let cqn = SELECT.from(table)
312
- if (req.query.SELECT.columns) {
313
- cqn = cqn.columns(...req.query.SELECT.columns)
314
- _ensureDraftAdminColumnsForCalculation(cqn.SELECT.columns)
315
- }
316
-
317
- return {
318
- cqn: getEnrichedCQN(cqn, req.query.SELECT, req.query.SELECT.where),
319
- scenario: SCENARIO.DRAFT_ADMIN
320
- }
321
- }
322
-
323
- const _allInactive = (req, columns) => {
324
- const table = {
325
- ref: [ensureDraftsSuffix(req.query.SELECT.from.ref[0])],
326
- as: req.query.SELECT.from.as || 'drafts'
327
- }
328
-
329
- const outerMostColumns = _getOuterMostColumns(
330
- addColumnAlias(columns, table.as),
331
- _getDefaultDraftProperties({ hasDraft: false, isActive: false, withDraftUUID: false })
332
- )
333
-
334
- const isCount = columns.some(element => element.func === 'count')
335
-
336
- // ensure only own drafts are read
337
- const cqn = SELECT.from(table)
338
- .join('DRAFT.DraftAdministrativeData', 'filterAdmin')
339
- .on([{ ref: [table.as, 'DraftAdministrativeData_DraftUUID'] }, '=', { ref: ['filterAdmin', 'DraftUUID'] }])
340
- .where(_inProcessByUserWhere(req.user.id))
341
-
342
- if (isCount) {
343
- cqn.columns(...outerMostColumns)
344
- } else {
345
- cqn.columns(...outerMostColumns.filter(o => o.as !== 'HasActiveEntity'), { ref: ['HasActiveEntity'] })
346
- }
347
-
348
- cqn.where(req.query.SELECT.where)
349
- return { cqn: getEnrichedCQN(cqn, req.query.SELECT, []), scenario: SCENARIO.ALL_INACTIVE }
350
- }
351
-
352
- const _setRefAlias = (ref, as) => {
353
- if (ref && ref[0] !== as) {
354
- ref.unshift(as)
355
- }
356
- }
357
-
358
- const _buildWhere = (where, table) => {
359
- for (const entry of where) {
360
- if (entry.ref) {
361
- _setRefAlias(entry.ref, table.as)
362
- } else if (entry.func && entry.args) {
363
- _buildWhere(entry.args, table)
364
- } else if (entry.list) {
365
- _buildWhere(entry.list, table)
366
- } else if (entry.xpr) {
367
- _buildWhere(entry.xpr, table)
368
- }
369
- }
370
- }
371
-
372
- const _allActive = (req, columns) => {
373
- const { table } = _getTableName(req)
374
- if (!table.as) {
375
- table.as = 'active'
376
- }
377
-
378
- const outerMostColumns = _getOuterMostColumns(
379
- addColumnAlias(columns, table.as),
380
- _getDefaultDraftProperties({ hasDraft: null })
381
- )
382
-
383
- const ids = entity_keys(req.target)
384
- const isCount = columns.some(element => element.func === 'count')
385
- const xpr = {
386
- xpr: [
387
- 'case',
388
- 'when',
389
- 'drafts.DraftAdministrativeData_DraftUUID',
390
- 'IS NOT NULL',
391
- 'then',
392
- 'true',
393
- 'else',
394
- 'false',
395
- 'end'
396
- ],
397
- as: 'HasDraftEntity',
398
- cast: { type: 'cds.Boolean' }
399
- }
400
-
401
- const cqn = SELECT.from(table)
402
-
403
- if (isCount) {
404
- cqn.columns(..._filterDraftColumnsBySelected(outerMostColumns, req.query.SELECT.columns))
405
- } else {
406
- cqn.columns(..._filterDraftColumnsBySelected([...outerMostColumns, xpr], req.query.SELECT.columns))
407
- cqn.leftJoin(ensureDraftsSuffix(table.ref[0]) + ' as drafts').on(`${table.as}.${ids[0]} = drafts.${ids[0]}`)
408
-
409
- for (let i = 1; i < ids.length; i++) {
410
- // this 'and' belongs to the join condition and is not a where and
411
- cqn.and({ ref: [table.as, ids[i]] }, '=', { ref: ['drafts', ids[i]] })
412
- }
413
- }
414
-
415
- const scenarioAlias = 'active'
416
- req.query.SELECT.where = _getWhereWithAppendedDraftRestrictions(req.query.SELECT.where, req)
417
-
418
- if (req.query.SELECT.where) {
419
- _buildWhere(req.query.SELECT.where, table)
420
- }
421
-
422
- return {
423
- cqn: getEnrichedCQN(cqn, req.query.SELECT, req.query.SELECT.where, scenarioAlias),
424
- scenario: SCENARIO.ALL_ACTIVE
425
- }
426
- }
427
-
428
- const _activeWithoutDraft = (req, draftWhere, columns) => {
429
- const { table } = _getTableName(req, true)
430
- const draftName = table.ref[0]
431
- const active = _getTableName(req)
432
- const keys = _getTargetKeys(req)
433
-
434
- let subSelect = SELECT.from(draftName).columns(...keys)
435
- subSelect = keys.reduce(
436
- (select, key) =>
437
- subSelect.where([
438
- { ref: [active.name, key] },
439
- '=',
440
- {
441
- ref: [draftName, key]
442
- }
443
- ]),
444
- subSelect
445
- )
446
-
447
- const outerMostColumns = _getOuterMostColumns(columns, _getDefaultDraftProperties({ hasDraft: false }))
448
- const cqn = SELECT.from(active.table)
449
- .columns(...outerMostColumns)
450
- .where(['not exists', subSelect])
451
-
452
- draftWhere = _getWhereWithAppendedDraftRestrictions(draftWhere, req)
453
- return { cqn: getEnrichedCQN(cqn, req.query.SELECT, draftWhere), scenario: SCENARIO.ACTIVE_WITHOUT_DRAFT }
454
- }
455
-
456
- const _draftOfWhichIAmOwner = (req, draftWhere, columns) => {
457
- const { table, name } = _getTableName(req, true)
458
- const outerMostColumns = _getOuterMostColumns(
459
- addColumnAlias(columns, name),
460
- DRAFT_COLUMNS_CASTED_WITH_DRAFTADMIN_UUID
461
- )
462
-
463
- const cqn = SELECT.from(table)
464
- .columns(...outerMostColumns)
465
- .join('DRAFT.DraftAdministrativeData', 'filterAdmin')
466
- .on([{ ref: [name, 'DraftAdministrativeData_DraftUUID'] }, '=', { ref: ['filterAdmin', 'DraftUUID'] }])
467
- .where(_inProcessByUserWhere(req.user.id))
468
-
469
- return { cqn: getEnrichedCQN(cqn, req.query.SELECT, draftWhere), scenario: SCENARIO.DRAFT_WHICH_OWNER }
470
- }
471
-
472
- const _activeWithDraftInProcess = (req, draftWhere, columns, isLocked) => {
473
- const draft = _getTableName(req, true)
474
- const draftName = draft.table.ref[0]
475
- const active = _getTableName(req)
476
- const keys = _getTargetKeys(req)
477
- const draftColumns = _getDefaultDraftProperties({ hasDraft: true })
478
-
479
- let subSelect = SELECT.from(draftName)
480
- .columns(...keys)
481
- .join('DRAFT.DraftAdministrativeData', 'filterAdmin')
482
- .on([{ ref: [draftName, 'DraftAdministrativeData_DraftUUID'] }, '=', { ref: ['filterAdmin', 'DraftUUID'] }])
483
-
484
- const DRAFT_CANCEL_TIMEOUT_IN_SEC = ((cds.env.drafts && cds.env.drafts.cancellationTimeout) || 15) * 60
485
-
486
- subSelect = subSelect.where([
487
- { ref: ['filterAdmin', 'InProcessByUser'] },
488
- '!=',
489
- { val: req.user.id },
490
- 'and',
491
- { ref: ['filterAdmin', 'InProcessByUser'] },
492
- 'is not null',
493
- 'and',
494
- { func: 'seconds_between', args: [{ ref: ['filterAdmin', 'LastChangeDateTime'] }, 'CURRENT_TIMESTAMP'] },
495
- isLocked ? '<' : '>',
496
- { val: DRAFT_CANCEL_TIMEOUT_IN_SEC }
497
- ])
498
-
499
- subSelect = keys.reduce(
500
- (_select, key) => subSelect.where([{ ref: [active.name, key] }, '=', { ref: [draftName, key] }]),
501
- subSelect
502
- )
503
-
504
- const outerMostColumns = _getOuterMostColumns(columns, draftColumns)
505
- const cqn = SELECT.from(active.table).columns(outerMostColumns)
506
- cqn.where(_getWhereWithAppendedDraftRestrictions([], req))
507
- cqn.where(['exists', subSelect])
508
- return { cqn: getEnrichedCQN(cqn, req.query.SELECT, draftWhere), scenario: SCENARIO.DRAFT_IN_PROCESS }
509
- }
510
-
511
- const _alignAliasForUnion = (table, as, select) => {
512
- if (!as || !select.SELECT.where) {
513
- return select
514
- }
515
-
516
- for (const entry of select.SELECT.where) {
517
- if (entry.ref && entry.ref[0] === table) {
518
- entry.ref[0] = as
519
- }
520
- }
521
-
522
- return select
523
- }
524
-
525
- const isTargetRef = (el, targetAlias) => targetAlias && el.ref && el.ref.length > 1 && el.ref[0] === targetAlias
526
-
527
- const _joinFromWhere = (where, parentAlias, targetAlias) => {
528
- return where.reduce((links, el, idx, where) => {
529
- if (el.xpr) {
530
- const result = _joinFromWhere(el.xpr, parentAlias, targetAlias)
531
-
532
- if (result.length) {
533
- if (links.length) links.push('and')
534
- links.push(...result)
535
- }
536
-
537
- return links
538
- }
539
-
540
- if (el.ref && el.ref[0] === parentAlias && el.ref[el.ref.length - 1] !== 'IsActiveEntity') {
541
- if (where[idx - 1] && where[idx - 1] === '=' && isTargetRef(where[idx - 2], targetAlias)) {
542
- if (links.length) links.push('and')
543
- links.push(el, '=', where[idx - 2])
544
- }
545
-
546
- if (where[idx + 1] && where[idx + 1] === '=' && isTargetRef(where[idx + 2], targetAlias)) {
547
- if (links.length) links.push('and')
548
- links.push(el, '=', where[idx + 2])
549
- }
550
- }
551
-
552
- return links
553
- }, [])
554
- }
555
-
556
- const _findJoinInQuery = (query, parentAlias) => {
557
- const targetAlias = query.SELECT.from.as
558
- if (query.SELECT && query.SELECT.where) return _joinFromWhere(query.SELECT.where, parentAlias, targetAlias)
559
- return []
560
- }
561
-
562
- const _isDraftField = element => element.ref && element.ref.length > 1 && element.ref[0] === 'DraftAdministrativeData'
563
-
564
- const _functionContainsDraftField = obj =>
565
- typeof obj === 'object' &&
566
- obj.func &&
567
- obj.args.some(arg => {
568
- return _isDraftField(arg) || _functionContainsDraftField(arg)
569
- })
570
-
571
- const _isLogicalFunction = (where, index) => {
572
- // REVISIT
573
- const borders = ['(', ')', 'and', 'or', undefined]
574
-
575
- return borders.includes(where[index - 1]) && borders.includes(where[index + 1])
576
- }
577
-
578
- const _getWhereForActive = where => {
579
- const activeWhere = []
580
- for (let i = 0; i < where.length; i++) {
581
- if (where[i].xpr) {
582
- activeWhere.push({ xpr: _getWhereForActive(where[i].xpr) })
583
- continue
584
- }
585
-
586
- if (_isDraftField(where[i])) {
587
- activeWhere.push({ val: null })
588
- } else if (_functionContainsDraftField(where[i])) {
589
- if (_isLogicalFunction(where, i)) {
590
- activeWhere.push({ val: 1 }, '=', { val: 2 })
591
- } else {
592
- activeWhere.push({ val: null })
593
- }
594
- } else {
595
- activeWhere.push(where[i])
596
- }
597
- }
598
-
599
- for (let i = 0; i < activeWhere.length; i++) {
600
- if (
601
- activeWhere[i].val === null &&
602
- activeWhere[i + 1] === '=' &&
603
- activeWhere[i + 2] &&
604
- activeWhere[i + 2].val === null
605
- ) {
606
- activeWhere[i] = { val: 1 }
607
- activeWhere[i + 2] = { val: 1 }
608
- }
609
- }
610
-
611
- return activeWhere
612
- }
613
-
614
- const _siblingEntity = (
615
- { query, target, nav, params },
616
- columns,
617
- model,
618
- draftAdminAlias,
619
- parentQuery,
620
- siblingIndex,
621
- req
622
- ) => {
623
- const parentLinks = parentQuery ? _findJoinInQuery(query, parentQuery.SELECT.from.as) : []
624
- const keys = (nav[siblingIndex + 1].where && (params[siblingIndex] || params[0])) || {}
625
- const siblingQuery = query.SELECT.where[query.SELECT.where.indexOf('exists') + 1]
626
- const onCond = _findJoinInQuery(siblingQuery, target.as)
627
- const siblingAlias = siblingQuery.SELECT.from.as
628
- const subScenario = _siblingSubScenario(nav, siblingIndex, siblingQuery, target, params, model, onCond, req)
629
- const isSiblingDraft = subScenario
630
- ? subScenario.isSiblingActive || subScenario.scenario === 'ACTIVE' || subScenario.scenario === 'ALL_ACTIVE'
631
- : keys.IsActiveEntity && keys.IsActiveEntity !== false
632
- const { table } = _getTableName({ query, target }, isSiblingDraft)
633
- const cqn = SELECT.from(table)
634
-
635
- if (siblingIndex === 0) {
636
- const columnCqnPartial = columns.map(col => {
637
- if (col.val) {
638
- return Object.assign({}, col)
639
- }
640
- const colName = col.ref ? col.ref[col.ref.length - 1] : col
641
- const ref = col.ref ? [table.as, ...col.ref] : [table.as, colName]
642
- return Object.assign({}, col, { ref })
643
- })
644
- columnCqnPartial.push({ ref: ['draftAdmin', 'InProcessByUser'], as: 'draftAdmin_inProcessByUser' })
645
- cqn.columns(...columnCqnPartial)
646
- } else {
647
- cqn.columns([{ val: 1 }])
648
- }
649
-
650
- if (isSiblingDraft) {
651
- cqn
652
- .join(ensureNoDraftsSuffix(target.name), siblingAlias)
653
- .on(onCond)
654
- .join('DRAFT.DraftAdministrativeData', 'draftAdmin')
655
- .on(`${table.as}.DraftAdministrativeData_DraftUUID = draftAdmin.DraftUUID`)
656
- } else {
657
- cqn
658
- .join(ensureDraftsSuffix(target.name), siblingAlias)
659
- .on(onCond)
660
- .join('DRAFT.DraftAdministrativeData', 'draftAdmin')
661
- .on(`${siblingAlias}.DraftAdministrativeData_DraftUUID = draftAdmin.DraftUUID`)
662
- }
663
-
664
- for (const key in keys) {
665
- if (key !== 'IsActiveEntity') cqn.where([{ ref: [table.as, key] }, '=', { val: keys[key] }])
666
- }
667
-
668
- if (subScenario) {
669
- cqn.where(['exists', subScenario.cqn])
670
- }
671
-
672
- // in DraftAdminData scenario parent is linked via join
673
- if (draftAdminAlias) {
674
- cqn.where([{ ref: [draftAdminAlias, 'DraftUUID'] }, '=', { ref: ['draftAdmin', 'DraftUUID'] }])
675
- } else if (parentLinks.length) {
676
- cqn.where({ xpr: [...parentLinks] })
677
- }
678
-
679
- return { cqn, scenario: SCENARIO.SIBLING_ENTITY, isSiblingActive: !isSiblingDraft }
680
- }
681
-
682
- function _siblingSubScenario(nav, siblingIndex, siblingQuery, target, params, model, onCond, req) {
683
- if (nav[siblingIndex + 1].where) return
684
- let subScenario
685
- const subNav = nav.slice(siblingIndex + 1)
686
- const subSiblingIndex = subNav.indexOf('SiblingEntity')
687
- const subReq = {
688
- query: siblingQuery,
689
- target: model.definitions[target.name],
690
- params: [...params].reverse(),
691
- user: req.user
692
- }
693
-
694
- if (subSiblingIndex > -1) {
695
- subScenario = _getSiblingScenario(subReq, [{ val: 1 }], model, subSiblingIndex, subNav, params)
696
- if (subSiblingIndex > 0) {
697
- const subQuery = SELECT.from(siblingQuery.SELECT.from).columns([{ val: 1 }])
698
- _mergeSiblingIntoCQN(subQuery, subScenario, subSiblingIndex - 1)
699
- subQuery.where(onCond)
700
- subScenario.cqn = subQuery
701
- }
702
- } else {
703
- subReq.query = SELECT.from(siblingQuery.SELECT.from).columns([{ val: 1 }])
704
-
705
- const existsIdx = siblingQuery.SELECT.where.indexOf('exists')
706
- if (existsIdx > -1) subReq.query.where(siblingQuery.SELECT.where.slice(existsIdx, existsIdx + 2))
707
- const subOrigFrom = { ref: [...subNav].reverse() }
708
- subScenario = _generateCQN(subReq, [{ val: 1 }], subOrigFrom, model)
709
- subScenario.cqn.where(onCond)
710
- }
711
-
712
- return subScenario
713
- }
714
-
715
- const _getSiblingScenario = (req, columns, model, siblingIndex, nav) => {
716
- const draftAdminAlias = _isDraftAdminScenario(req) && req.query.SELECT.from.as
717
- const params = [...req.params].reverse()
718
- const _getSiblingQueryFromWhere = (query, queryIndex, parentQuery) => {
719
- if (query.SELECT && query.SELECT.where && queryIndex > 0) {
720
- for (let i = 0; i < query.SELECT.where.length; i++) {
721
- if (query.SELECT.where[i] === 'exists' && queryIndex > 0) {
722
- return _getSiblingQueryFromWhere(query.SELECT.where[i + 1], queryIndex - 1, query)
723
- }
724
- }
725
- }
726
-
727
- const target = { name: query.SELECT.from.ref[0].id || query.SELECT.from.ref[0], as: query.SELECT.from.as }
728
- return _siblingEntity(
729
- { query, target, params, nav },
730
- columns,
731
- model,
732
- draftAdminAlias,
733
- parentQuery,
734
- siblingIndex,
735
- req
736
- )
737
- }
738
-
739
- return _getSiblingQueryFromWhere(req.query, siblingIndex)
740
- }
741
-
742
- const _replaceWhereExists = (query, _siblingIndex, siblingCQN) => {
743
- if (query.SELECT && query.SELECT.where) {
744
- for (let i = 0; i < query.SELECT.where.length; i++) {
745
- const whereElement = query.SELECT.where[i]
746
- if (whereElement.xpr) {
747
- const res = _replaceWhereExists({ SELECT: { where: whereElement.xpr } }, _siblingIndex, siblingCQN)
748
- if (res) return res
749
- continue
750
- }
751
-
752
- const indexExists = query.SELECT.where.indexOf('exists')
753
- if (indexExists > -1) {
754
- query.SELECT.where.splice(indexExists + 1, 1, siblingCQN)
755
- }
756
- }
757
- }
758
- }
759
-
760
- const _mergeSiblingIntoCQN = (cqn, { cqn: siblingCQN }, siblingIndex) =>
761
- _replaceWhereExists(cqn, siblingIndex, siblingCQN)
762
-
763
- const _getDraftDoc = (req, draftName, draftWhere) => {
764
- const refDraft = req.query.SELECT.from.as ? { ref: [draftName], as: req.query.SELECT.from.as } : draftName
765
-
766
- const draftDocs = getEnrichedCQN(
767
- SELECT.from(refDraft)
768
- .join('DRAFT.DraftAdministrativeData', 'filterAdmin')
769
- .on([
770
- { ref: [req.query.SELECT.from.as || draftName, 'DraftAdministrativeData_DraftUUID'] },
771
- '=',
772
- { ref: ['filterAdmin', 'DraftUUID'] }
773
- ])
774
- .where(_inProcessByUserWhere(req.user.id)),
775
- req.query.SELECT,
776
- draftWhere,
777
- undefined,
778
- false
779
- )
780
-
781
- return draftDocs
782
- }
783
-
784
- const _getOrderByEnrichedColumns = (orderBy, columns, entity) => {
785
- const enrichedCol = []
786
-
787
- if (orderBy && orderBy.length > 1) {
788
- const colNames = columns.filter(el => el.ref).map(el => el.ref[el.ref.length - 1])
789
-
790
- // REVISIT: GET Books?$select=title&$expand=NotBooks($select=pages)&$orderby=NotBooks/title - what's then?
791
- for (const el of orderBy) {
792
- // For associations we need to 'materialise' the resulting field, otherwise we cannot access it in an outer SELECT.
793
- if (entity && entity.elements[el.ref[0]] && entity.elements[el.ref[0]].isAssociation) {
794
- enrichedCol.push({ ref: [...el.ref], as: _poorMansAlias4(el) })
795
- } else if (!(el.ref[el.ref.length - 1] in DRAFT_COLUMNS_MAP) && !colNames.includes(el.ref[el.ref.length - 1])) {
796
- enrichedCol.push({ ref: [...el.ref] })
797
- }
798
- }
799
- }
800
-
801
- return enrichedCol
802
- }
803
-
804
- const _replaceDraftAlias = where => {
805
- where.forEach(element => {
806
- if (element.xpr) {
807
- _replaceDraftAlias(element.xpr)
808
- return
809
- }
810
-
811
- if (_isDraftField(element)) {
812
- element.ref[0] = 'filterAdmin'
813
- }
814
-
815
- if (typeof element === 'object' && element.func) {
816
- _replaceDraftAlias(element.args)
817
- }
818
- })
819
- }
820
-
821
- const _poorMansAlias4 = xpr => '_' + xpr.ref.join('_') + '_'
822
-
823
- const _getUnionCQN = (req, draftName, columns, subSelect, draftWhere, model, entity) => {
824
- const draftActiveWhere = _getWhereForActive(draftWhere)
825
- const activeDocs = getEnrichedCQN(SELECT.from(req.target), req.query.SELECT, draftActiveWhere, undefined, false)
826
- activeDocs.where(_getWhereWithAppendedDraftRestrictions([], req))
827
- convertWhereExists(activeDocs.SELECT, model, {})
828
-
829
- // @restrict.where not applicable for drafts (I can ALWAYS read mine)
830
- _replaceDraftAlias(draftWhere)
831
- const draftDocs = _getDraftDoc(req, draftName, draftWhere)
832
-
833
- const union = SELECT.from({ SET: { op: 'union', all: true, args: [draftDocs, activeDocs] } })
834
- if (req.query.SELECT.count) union.SELECT.count = true
835
- if (req.query.SELECT.__countAggregated) union.SELECT.__countAggregated = true
836
-
837
- if (req.query.SELECT.from.as) {
838
- draftDocs.SELECT.from.as = req.query.SELECT.from.as
839
- activeDocs.SELECT.from.as = req.query.SELECT.from.as
840
- }
841
-
842
- if (_isOnlyCount(columns)) {
843
- draftDocs.columns(...columns)
844
- activeDocs
845
- .columns(...columns)
846
- .where([
847
- 'not exists',
848
- _alignAliasForUnion(ensureNoDraftsSuffix(req.target.name), req.query.SELECT.from.as, subSelect)
849
- ])
850
-
851
- return union.columns({ func: 'sum', args: [{ ref: ['$count'] }], as: '$count' })
852
- }
853
-
854
- const enrichedColumns = _getOrderByEnrichedColumns(req.query.SELECT.orderBy, columns, entity)
855
-
856
- for (const col of enrichedColumns) {
857
- // if we have columns for outer order by that may also be needed for joins, we need to duplicate them
858
- const element = getElementDeep(req.target, col.ref)
859
- if (element && element._foreignKey4) {
860
- columns.push({ ref: [...col.ref] })
861
- }
862
-
863
- col.as = _poorMansAlias4(col)
864
- // add alias to outer order by
865
- const ob = req.query.SELECT.orderBy.find(ele => _poorMansAlias4(ele) === col.as)
866
- ob.ref = [col.as]
867
- }
868
-
869
- const draftColumns = [
870
- ...addColumnAlias([...columns, ...enrichedColumns], req.query.SELECT.from.as || draftName),
871
- ..._filterDraftColumnsBySelected(DRAFT_COLUMNS_CASTED, req.query.SELECT.columns),
872
- 'DraftAdministrativeData_DraftUUID'
873
- ]
874
- draftDocs.columns(draftColumns)
875
-
876
- const activeName = activeDocs.SELECT.from.as || (activeDocs.SELECT.from.ref && activeDocs.SELECT.from.ref[0])
877
- const hasDraftWhere = []
878
- const targetKeys = _getTargetKeys(req)
879
- for (const key of targetKeys) {
880
- // add 'and' token if not the first iteration
881
- if (hasDraftWhere.length) hasDraftWhere.push('and')
882
- hasDraftWhere.push({ ref: [activeName, key] }, '=', { ref: [draftName, key] })
883
- }
884
-
885
- const draftColumnsBySelected = _filterDraftColumnsBySelected(
886
- _getDraftPropertiesDetermineDraft(req, hasDraftWhere, ensureDraftsSuffix(req.target.name), true),
887
- req.query.SELECT.columns
888
- )
889
-
890
- const activeColumns = [...columns, ...enrichedColumns, ...draftColumnsBySelected]
891
- activeDocs.columns(activeColumns)
892
-
893
- const aliasForUnion = _alignAliasForUnion(ensureNoDraftsSuffix(req.target.name), req.query.SELECT.from.as, subSelect)
894
- activeDocs.where(['not exists', aliasForUnion])
895
-
896
- // groupBy, orderBy and limit do not support partial CQNs
897
- if (req.query.SELECT.groupBy) {
898
- union.SELECT.groupBy = req.query.SELECT.groupBy
899
- }
900
-
901
- if (req.query.SELECT.orderBy) {
902
- union.SELECT.orderBy = req.query.SELECT.orderBy
903
- }
904
-
905
- if (req.query.SELECT.limit) {
906
- union.SELECT.limit = req.query.SELECT.limit
907
- }
908
-
909
- return union
910
- .columns(...columns.map(ref => (ref.as ? { ref: [ref.as], as: ref.as } : ref))) // needed for aliased stream property ref@odata.mediaContentType
911
- .columns(..._filterDraftColumnsBySelected(DRAFT_COLUMNS_CASTED, req.query.SELECT.columns))
912
- }
913
-
914
- const _excludeActiveDraftExists = (req, columns, draftWhere, model) => {
915
- const { table, name } = _getTableName(req, true)
916
- const draftName = table.ref[0]
917
-
918
- const subSelect = SELECT.from(draftName, [1])
919
- .join('DRAFT.DraftAdministrativeData', 'filterAdmin')
920
- .on([{ ref: [draftName, 'DraftAdministrativeData_DraftUUID'] }, '=', { ref: ['filterAdmin', 'DraftUUID'] }])
921
- .where(_inProcessByUserWhere(req.user.id))
922
-
923
- const targetName = ensureNoDraftsSuffix(req.target.name)
924
- const targetKeys = _getTargetKeys(req)
925
- for (const key of targetKeys) {
926
- subSelect.where([{ ref: [targetName, key] }, '=', { ref: [draftName, key] }])
927
- }
928
-
929
- const entity = model.definitions[targetName]
930
- draftWhere = removeIsActiveEntityRecursively(draftWhere)
931
- const cqn = _getUnionCQN(req, draftName, columns, subSelect, draftWhere, model, entity)
932
- cqn.SELECT.from.as = name
933
-
934
- if (cqn.SELECT.orderBy) {
935
- for (const entry of cqn.SELECT.orderBy || []) {
936
- if (entry.ref.length > 1 && entry.ref[0] !== name) {
937
- entry.ref[0] = name
938
- }
939
- }
940
- }
941
-
942
- return { cqn: cqn, scenario: SCENARIO.UNION }
943
- }
944
-
945
- const _readDraftParameters = where => {
946
- const obj = {
947
- isActiveEntity: readAndDeleteKeywords(['IsActiveEntity'], where),
948
- hasDraftEntity: readAndDeleteKeywords(['HasDraftEntity'], where),
949
- siblingIsActive: readAndDeleteKeywords(['SiblingEntity', 'IsActiveEntity'], where),
950
- draftInProcessByUser: readAndDeleteKeywords(['DraftAdministrativeData', 'InProcessByUser'], where)
951
- }
952
-
953
- // remove "DraftAdministrativeData/InProcessByUser ne null" from request if necessary
954
- readAndDeleteKeywords(['DraftAdministrativeData', 'InProcessByUser'], where)
955
-
956
- return obj
957
- }
958
-
959
- const _validatedActiveWithoutDraft = (req, draftWhere, draftParameters, columns) =>
960
- _isValidActiveWithoutDraft(draftParameters.isActiveEntity, draftParameters.hasDraftEntity) &&
961
- _activeWithoutDraft(req, draftWhere, columns)
962
-
963
- const _validatedWithSiblingInProcess = (req, draftWhere, draftParameters, columns, model) => {
964
- const { isActiveEntity, siblingIsActive, draftInProcessByUser } = draftParameters
965
- if (
966
- !draftInProcessByUser &&
967
- _isValidExcludeActiveDraftExists(draftParameters.isActiveEntity, draftParameters.siblingIsActive)
968
- ) {
969
- return _excludeActiveDraftExists(req, columns, draftWhere, model)
970
- }
971
-
972
- if (
973
- draftInProcessByUser &&
974
- draftInProcessByUser.op === '!=' &&
975
- _isValidWithDraftLocked(isActiveEntity, siblingIsActive, draftInProcessByUser)
976
- ) {
977
- return _activeWithDraftInProcess(req, draftWhere, columns, req.user.id)
978
- }
979
-
980
- if (draftInProcessByUser && _isValidWithDraftTimeout(isActiveEntity, siblingIsActive, draftInProcessByUser)) {
981
- return _activeWithDraftInProcess(req, draftWhere, columns, null)
982
- }
983
- }
984
-
985
- const _validatedDraftOfWhichIAmOwner = (req, draftWhere, draftParameters, columns) =>
986
- _isValidDraftOfWhichIAmOwner(draftParameters.isActiveEntity) && _draftOfWhichIAmOwner(req, draftWhere, columns)
987
-
988
- const _draftInSubSelect = (where, req) => {
989
- return where.some(({ SELECT, xpr }) => {
990
- if (xpr) {
991
- return _draftInSubSelect(xpr, req)
992
- }
993
-
994
- if (SELECT && SELECT.where) {
995
- const isActiveEntity = readAndDeleteKeywords(['IsActiveEntity'], SELECT.where, false)
996
- if (isActiveEntity) {
997
- return _isFalse(isActiveEntity.value.val)
998
- }
999
-
1000
- return _draftInSubSelect(SELECT.where, req)
1001
- }
1002
-
1003
- return false
1004
- })
1005
- }
1006
-
1007
- const _isDraftAdminScenario = req =>
1008
- req.target.query && req.target.query._target && req.target.query._target.name === 'DRAFT.DraftAdministrativeData'
1009
-
1010
- const _generateCQN = (req, columns, from, model) => {
1011
- const nav = [...from.ref].reverse() || []
1012
- let siblingIndex = nav.indexOf('SiblingEntity')
1013
-
1014
- // it can also be a property access (new parser), then we must shift it
1015
- if (siblingIndex > 0 && req.target.elements[nav[0]] && !req.target.elements[nav[0]].isAssociation) {
1016
- nav.shift()
1017
- siblingIndex = siblingIndex - 1
1018
- }
1019
-
1020
- let siblingScenario
1021
- if (siblingIndex > -1) {
1022
- siblingScenario = _getSiblingScenario(req, columns, model, siblingIndex, nav)
1023
- if (siblingIndex === 0) return siblingScenario
1024
- _mergeSiblingIntoCQN(req.query, siblingScenario, siblingIndex - 1)
1025
- }
1026
-
1027
- if (_isDraftAdminScenario(req)) return _draftAdminTable(req)
1028
- if (!req.query.SELECT.where) return _allActive(req, columns)
1029
-
1030
- // REVISIT this function does not only read, but modifies where!
1031
- const draftParameters = _readDraftParameters(req.query.SELECT.where)
1032
-
1033
- if (
1034
- draftParameters.isActiveEntity &&
1035
- _isTrue(draftParameters.isActiveEntity.value.val) &&
1036
- !(draftParameters.siblingIsActive && draftParameters.siblingIsActive.value.val === null) &&
1037
- !draftParameters.hasDraftEntity
1038
- ) {
1039
- return _allActive(req, columns)
1040
- }
1041
-
1042
- if (!draftParameters.isActiveEntity) {
1043
- if (_draftInSubSelect(req.query.SELECT.where, req) || (siblingScenario && !siblingScenario.isSiblingActive)) {
1044
- // this is only the case when navigating into tree
1045
- return _allInactive(req, columns)
1046
- }
1047
-
1048
- return _allActive(req, columns)
1049
- }
1050
-
1051
- if (draftParameters.hasDraftEntity) {
1052
- return _validatedActiveWithoutDraft(req, req.query.SELECT.where, draftParameters, columns)
1053
- }
1054
-
1055
- if (draftParameters.siblingIsActive) {
1056
- return _validatedWithSiblingInProcess(req, req.query.SELECT.where, draftParameters, columns, model)
1057
- }
1058
-
1059
- return _validatedDraftOfWhichIAmOwner(req, req.query.SELECT.where, draftParameters, columns)
1060
- }
1061
-
1062
- const _isIsActiveEntity = element => element.ref && element.ref[element.ref.length - 1] === 'IsActiveEntity'
1063
-
1064
- const _adaptSubSelects = ({ SELECT: { from, where } }, scenario) => {
1065
- if (!where) return
1066
-
1067
- if (scenario === 'ALL_INACTIVE') {
1068
- replaceRefWithDraft(from.ref)
1069
- }
1070
-
1071
- for (let i = 0; i < where.length; i++) {
1072
- const element = where[i]
1073
-
1074
- if (_isIsActiveEntity(element) && where.length > i + 2) {
1075
- if (
1076
- (scenario !== 'ALL_INACTIVE' && _isFalse(where[i + 2].val)) ||
1077
- (scenario === SCENARIO.DRAFT_ADMIN && !_isFalse(where[i + 2].val))
1078
- ) {
1079
- replaceRefWithDraft(from.ref)
1080
- }
1081
-
1082
- if (!_isIsActiveEntity(where[i + 2])) {
1083
- i = deleteCondition(i, where) - 1
1084
- } else {
1085
- i = i + 3 < where.length ? i + 2 : i + 3
1086
- }
1087
- } else if (element.SELECT) {
1088
- _adaptSubSelects(element, scenario)
1089
- } else if (element.xpr) {
1090
- for (const ele of element.xpr.filter(e => e.SELECT)) {
1091
- _adaptSubSelects(ele, scenario)
1092
- }
1093
- }
1094
- }
1095
- }
1096
-
1097
- const _calculateDraftAdminColumns = (result, user, deleteLastChangeDateTime) => {
1098
- if (!result) return
1099
- if ('InProcessByUser' in result && !draftIsLocked(result.LastChangeDateTime)) result.InProcessByUser = ''
1100
- if (deleteLastChangeDateTime) delete result.LastChangeDateTime
1101
- if ('DraftIsCreatedByMe' in result && 'CreatedByUser' in result)
1102
- result.DraftIsCreatedByMe = result.CreatedByUser === user
1103
- if ('DraftIsProcessedByMe' in result && 'InProcessByUser' in result)
1104
- result.DraftIsProcessedByMe = result.InProcessByUser === user
1105
- }
1106
-
1107
- const _adaptDraftColumnsForSiblingEntity = (result, isSiblingActive) => {
1108
- result.IsActiveEntity = isSiblingActive
1109
- result.HasDraftEntity = isSiblingActive
1110
- result.HasActiveEntity = !isSiblingActive
1111
- }
1112
-
1113
- const _collectAliases = (from, aliases) => {
1114
- if (!from) return
1115
- if (from.ref && from.as) {
1116
- // Actually table names in where annotations should be provided with '.' separator.
1117
- // Normalization to '_' is done for the exceptional case if '_' is still used (based on db table names).
1118
- aliases.set(from.ref[0].replace(/\./g, '_'), from.as)
1119
- } else if (from.args) {
1120
- from.args.forEach(arg => {
1121
- _collectAliases(arg, aliases)
1122
- })
1123
- } else if (from.SET && from.SET.args) {
1124
- from.SET.args.forEach(arg => {
1125
- _collectAliases(arg, aliases)
1126
- })
1127
- }
1128
- }
1129
-
1130
- const _adaptAnnotationAliases = cqn => {
1131
- const aliases = new Map()
1132
- _collectAliases(cqn.SELECT.from, aliases)
1133
- }
1134
-
1135
- const enhanceQueryForTimeoutIfNeeded = (scenario, columns = []) => {
1136
- if (scenario !== SCENARIO.DRAFT_ADMIN) {
1137
- const draftAdmin = columns.find(col => col.ref && col.ref[col.ref.length - 1] === 'DraftAdministrativeData')
1138
- columns = (draftAdmin && draftAdmin.expand) || []
1139
- }
1140
- const inProcessByUser = columns.find(col => col.ref && col.ref[col.ref.length - 1] === 'InProcessByUser')
1141
- const lastChangeDateTime = columns.find(col => col.ref && col.ref[col.ref.length - 1] === 'LastChangeDateTime')
1142
- if (inProcessByUser && !lastChangeDateTime) {
1143
- columns.push({ ref: [...inProcessByUser.ref.slice(0, inProcessByUser.ref.length - 1), 'LastChangeDateTime'] })
1144
- return true
1145
- }
1146
- }
1147
-
1148
- // REVISIT: HACK for sqlite support, union not yet properly supported in before handler on db
1149
- // remove once union is removed, should be part of before handler
1150
- const _getLocalizedEntity = (model, target, user) => {
1151
- const prefix = 'localized'
1152
- let localizedEntity
1153
- /*
1154
- * REVISIT: in case of not sqlite, model.definitions[`${prefix}.${user.locale}.${target.name}`] is undefined
1155
- * and the fallback lookup model.definitions[`${prefix}.${target.name}`] gets the entity -> bad coding
1156
- */
1157
- if (cds.env.i18n.for_sqlite.includes(user.locale)) {
1158
- localizedEntity = model.definitions[`${prefix}.${user.locale}.${target.name}`]
1159
- }
1160
-
1161
- return localizedEntity || model.definitions[`${prefix}.${target.name}`]
1162
- }
1163
-
1164
- const _getLastSubQuery = query => (query.SELECT.from.SELECT ? _getLastSubQuery(query.SELECT.from) : query)
1165
- const _setLastSubQuery = (query, last, prev = query) => {
1166
- if (query.SELECT.from.SELECT) return _setLastSubQuery(query.SELECT.from, last, query)
1167
- else prev.SELECT.from = _copyCQNPartial(last)
1168
- return prev
1169
- }
1170
-
1171
- const _adaptDraftAdminExpand = cqn => {
1172
- const draftAdminExpand =
1173
- cqn.SELECT.columns && cqn.SELECT.columns.find(c => c.expand && c.ref[0] === 'DraftAdministrativeData')
1174
-
1175
- if (draftAdminExpand) {
1176
- _ensureDraftAdminColumnsForCalculation(draftAdminExpand.expand)
1177
- }
1178
- }
1179
-
1180
- const _getOriginalColumns = req => {
1181
- const originalColumns = {}
1182
- // expanded columns are handled generically in db
1183
- for (const c of req.query.SELECT.columns) {
1184
- originalColumns[c.as || (c.ref && c.ref[c.ref.length - 1]) || c] = true
1185
- }
1186
-
1187
- return originalColumns
1188
- }
1189
-
1190
- // REVISIT: remove after stream_compat is removed
1191
- const _handlerStreaming = async (req, query) => {
1192
- adaptStreamCQN(query)
1193
- query._streaming = true
1194
- const result = await cds.tx(req).run(query)
1195
- return result
1196
- }
1197
-
1198
- const _postProcess = (result, req, cqnScenario, deleteLastChangeDateTime) => {
1199
- const resultAsArray = Array.isArray(result) ? result : result ? [result] : []
1200
- if (!result || !resultAsArray.length) return result
1201
-
1202
- if (cqnScenario.scenario === SCENARIO.SIBLING_ENTITY) {
1203
- if (resultAsArray[0].draftAdmin_inProcessByUser !== req.user.id) return []
1204
- delete resultAsArray[0].draftAdmin_inProcessByUser
1205
- _adaptDraftColumnsForSiblingEntity(resultAsArray[0], cqnScenario.isSiblingActive)
1206
- }
1207
-
1208
- const removeDraftUUIDIfNecessaryFn = removeDraftUUIDIfNecessary(req)
1209
- let notRequestedColumns
1210
- if (!req.query.SELECT._4odata) {
1211
- const originalColumns = _getOriginalColumns(req)
1212
- notRequestedColumns = originalColumns && Object.keys(resultAsArray[0]).filter(key => !originalColumns[key])
1213
- }
1214
-
1215
- if (cqnScenario.scenario === SCENARIO.DRAFT_ADMIN) {
1216
- _calculateDraftAdminColumns(resultAsArray[0], req.user.id, deleteLastChangeDateTime)
1217
- if (notRequestedColumns) {
1218
- for (const key of notRequestedColumns) delete resultAsArray[0][key]
1219
- }
1220
- } else {
1221
- for (const row of resultAsArray) {
1222
- removeDraftUUIDIfNecessaryFn(row)
1223
- _calculateDraftAdminColumns(row.DraftAdministrativeData, req.user.id, deleteLastChangeDateTime)
1224
- if (notRequestedColumns) {
1225
- for (const key of notRequestedColumns) delete row[key]
1226
- }
1227
- }
1228
- }
1229
-
1230
- if (result.HasActiveEntity === null) result.HasActiveEntity = false
1231
- if (result.HasDraftEntity === null) result.HasDraftEntity = false
1232
- if (result.IsActiveEntity === null) result.IsActiveEntity = false
1233
- return result
1234
- }
1235
-
1236
- const _fnCompare = elName => c => (c.as && c.as === elName) || (c.ref && c.ref[c.ref.length - 1] === elName)
1237
- const _adaptColumns4readAfterWrite = (req, cqnScenario, query4sql) => {
1238
- if (
1239
- !(req.context.event === 'EDIT' && cqnScenario.scenario === SCENARIO.DRAFT_WHICH_OWNER) &&
1240
- !(req.context.event === 'draftActivate' && cqnScenario.scenario === SCENARIO.ALL_ACTIVE)
1241
- )
1242
- return
1243
-
1244
- // cleanup columns if not requested with $select or $expand
1245
- cqnScenario.cqn.SELECT.columns = cqnScenario.cqn.SELECT.columns.reduce((columns, column) => {
1246
- const elName = column.as || (column.ref && column.ref[column.ref.length - 1])
1247
- if (query4sql.SELECT.columns.find(_fnCompare(elName)) || elName in req.target.keys) columns.push(column)
1248
- return columns
1249
- }, [])
1250
-
1251
- // add missing keys
1252
- const isActive = req.context.event === 'draftActivate'
1253
- // aliasing is fixed by scenarios
1254
- const alias = isActive ? 'active' : req.target.drafts.name
1255
- for (const key in req.target.keys) {
1256
- if (req.target.keys[key].isAssociation) continue
1257
- if (!cqnScenario.cqn.SELECT.columns.find(_fnCompare(key))) {
1258
- const column =
1259
- key === 'IsActiveEntity'
1260
- ? { val: isActive, as: 'IsActiveEntity', cast: { type: 'cds.Boolean' } }
1261
- : { ref: [alias, key] }
1262
- cqnScenario.cqn.SELECT.columns.push(column)
1263
- }
1264
- }
1265
- }
1266
-
1267
- /**
1268
- * Generic Handler for READ requests in the context of draft.
1269
- *
1270
- * @param req
1271
- */
1272
- const fioriGenericRead = async function (req, next) {
1273
- if (!req.target._isDraftEnabled) return next()
1274
-
1275
- if (!cds.db) req.reject('NO_DATABASE_CONNECTION')
1276
-
1277
- const query = req.query
1278
- const originalFrom = _copyCQNPartial(query.SELECT.from)
1279
-
1280
- // handle localized here as it was previously handled for req.target
1281
- req.target = _getLocalizedEntity(this.model, req.target, req.user) || req.target
1282
-
1283
- // REVISIT DRAFT HANDLING: cqn2cqn4sql must not be called here
1284
- const query4sql = cqn2cqn4sql(_copyCQNPartial(req.query), this.model, { _4fiori: true })
1285
-
1286
- // Clone the request. Do not clone with Object.assign as that would skip all non-enumerable properties.
1287
- // REVISIT: query4sql.clone() doesn't really clone the original query, hence _generateCQN will heavily modify
1288
- // it, e.g. IsActiveEntity is stripped. This is a problem for subsequent handlers which rely on this information.
1289
- const reqClone = { __proto__: req, query: query4sql }
1290
- // Clone draft restrictions to the cloned query.
1291
- reqClone.query._draftRestrictions = query._draftRestrictions
1292
-
1293
- if (cds.env.features.stream_compat && query._streaming) return _handlerStreaming(req, reqClone.query)
1294
-
1295
- let cqnScenario
1296
-
1297
- // to replace scenario CQNs for queries with $apply SELECT chain (new odata2cqn parser)
1298
- // just to make existing tests working with new parser. not really tested, not needed to be supported
1299
- if (reqClone.query.SELECT.from.SELECT) {
1300
- const subQueryReq = { __proto__: req, query: _copyCQNPartial(_getLastSubQuery(reqClone.query)) }
1301
- const nonDraftColumns = filterNonDraftColumns(subQueryReq.query.SELECT.columns)
1302
- cqnScenario = _generateCQN(subQueryReq, nonDraftColumns, originalFrom.SELECT.from, this.model)
1303
- cqnScenario.cqn = _setLastSubQuery(reqClone.query, cqnScenario.cqn)
1304
- } else {
1305
- const nonDraftColumns = filterNonDraftColumns(reqClone.query.SELECT.columns)
1306
- cqnScenario = _generateCQN(reqClone, nonDraftColumns, originalFrom, this.model)
1307
- }
1308
-
1309
- if (!cqnScenario) throw getError(501)
1310
-
1311
- // ensure base columns for calculation are selected in draft admin expand
1312
- _adaptDraftAdminExpand(cqnScenario.cqn)
1313
-
1314
- if (cqnScenario.scenario === SCENARIO.ALL_ACTIVE && cqnScenario.cqn.SELECT.where) {
1315
- cqnScenario.cqn.SELECT.where = removeIsActiveEntityRecursively(cqnScenario.cqn.SELECT.where)
1316
- }
1317
-
1318
- const enhancedWithLastChangeDateTime = enhanceQueryForTimeoutIfNeeded(
1319
- cqnScenario.scenario,
1320
- cqnScenario.cqn.SELECT.columns
1321
- )
1322
-
1323
- _adaptSubSelects(cqnScenario.cqn, cqnScenario.scenario)
1324
- _adaptAnnotationAliases(cqnScenario.cqn)
1325
-
1326
- // unlocalize for db and after handlers as it was before
1327
- req.target = this.model.definitions[ensureUnlocalized(req.target.name)]
1328
-
1329
- _adaptColumns4readAfterWrite(req, cqnScenario, query4sql)
1330
-
1331
- const result = await cds.tx(req).send({ query: cqnScenario.cqn, target: req.target })
1332
-
1333
- if (result == null && req._etagValidationType === 'if-match') req.reject(412)
1334
-
1335
- return _postProcess(result, reqClone, cqnScenario, enhancedWithLastChangeDateTime)
1336
- }
1337
-
1338
- module.exports = cds.service.impl(function (srv) {
1339
- srv.on('READ', '*', fioriGenericRead)
1340
- })