@sap/cds 5.8.4 → 5.9.2

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 (248) hide show
  1. package/CHANGELOG.md +198 -77
  2. package/app/fiori/preview.js +16 -11
  3. package/app/fiori/routes.js +15 -8
  4. package/app/index.js +1 -1
  5. package/bin/build/buildTaskFactory.js +3 -3
  6. package/bin/build/buildTaskProviderFactory.js +1 -1
  7. package/bin/build/constants.js +1 -1
  8. package/bin/build/provider/buildTaskHandlerEdmx.js +12 -7
  9. package/bin/build/provider/buildTaskHandlerInternal.js +1 -1
  10. package/bin/build/provider/buildTaskProviderInternal.js +8 -2
  11. package/bin/build/provider/hana/2migration.js +27 -24
  12. package/bin/build/provider/hana/index.js +17 -18
  13. package/bin/build/provider/hana/migrationtable.js +9 -10
  14. package/bin/build/provider/java-cf/index.js +4 -5
  15. package/bin/build/provider/node-cf/index.js +99 -6
  16. package/bin/cds.js +17 -18
  17. package/bin/deploy/to-hana/cfUtil.js +16 -19
  18. package/bin/deploy/to-hana/hana.js +7 -24
  19. package/bin/deploy/to-hana/hdiDeployUtil.js +8 -4
  20. package/bin/mtx/in-cds.js +2 -2
  21. package/bin/serve.js +10 -3
  22. package/bin/utils/modules.js +7 -0
  23. package/bin/version.js +56 -3
  24. package/lib/compile/cdsc.js +7 -2
  25. package/lib/compile/etc/_localized.js +37 -25
  26. package/lib/compile/etc/csv.js +8 -8
  27. package/lib/compile/for/drafts.js +9 -0
  28. package/lib/compile/for/java.js +16 -0
  29. package/lib/compile/for/nodejs.js +12 -0
  30. package/lib/compile/index.js +3 -0
  31. package/lib/compile/minify.js +16 -2
  32. package/lib/compile/parse.js +2 -2
  33. package/lib/compile/resolve.js +35 -18
  34. package/lib/compile/to/json.js +3 -1
  35. package/lib/compile/to/sql.js +2 -2
  36. package/lib/compile/to/srvinfo.js +4 -2
  37. package/lib/connect/bindings.js +1 -1
  38. package/lib/connect/index.js +3 -4
  39. package/lib/core/entities.js +15 -14
  40. package/lib/core/index.js +39 -36
  41. package/lib/core/reflect.js +4 -2
  42. package/lib/deploy.js +114 -127
  43. package/lib/env/defaults.js +1 -0
  44. package/lib/env/index.js +165 -165
  45. package/lib/env/presets.js +1 -0
  46. package/lib/env/requires.js +121 -50
  47. package/lib/index.js +2 -0
  48. package/lib/log/format/kibana.js +2 -2
  49. package/lib/ql/SELECT.js +10 -0
  50. package/lib/ql/parse.js +1 -0
  51. package/lib/req/cds-context.js +4 -1
  52. package/lib/req/context.js +50 -56
  53. package/lib/req/event.js +1 -6
  54. package/lib/req/locale.js +6 -5
  55. package/lib/req/request.js +2 -0
  56. package/lib/req/user.js +7 -5
  57. package/lib/serve/Service-api.js +10 -7
  58. package/lib/serve/Service-dispatch.js +9 -11
  59. package/lib/serve/Service-methods.js +30 -41
  60. package/lib/serve/Transaction.js +10 -7
  61. package/lib/serve/adapters.js +11 -9
  62. package/lib/serve/factory.js +14 -9
  63. package/lib/serve/index.js +28 -15
  64. package/lib/utils/data.js +1 -1
  65. package/lib/utils/index.js +27 -30
  66. package/lib/utils/resources/index.js +101 -0
  67. package/lib/utils/resources/tar.js +71 -0
  68. package/lib/utils/resources/utils.js +11 -0
  69. package/libx/_runtime/audit/Service.js +36 -39
  70. package/libx/_runtime/audit/generic/personal/access.js +3 -4
  71. package/libx/_runtime/audit/generic/personal/modification.js +3 -4
  72. package/libx/_runtime/audit/utils/v2.js +1 -2
  73. package/libx/_runtime/auth/index.js +126 -84
  74. package/libx/_runtime/auth/strategies/JWT.js +12 -19
  75. package/libx/_runtime/auth/strategies/dummy.js +1 -5
  76. package/libx/_runtime/auth/strategies/dwc.js +11 -9
  77. package/libx/_runtime/auth/strategies/mock.js +0 -4
  78. package/libx/_runtime/auth/strategies/{utils/xssec.js → xssecUtils.js} +7 -4
  79. package/libx/_runtime/auth/strategies/xsuaa.js +12 -19
  80. package/libx/_runtime/auth/utils.js +22 -1
  81. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +104 -98
  82. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +8 -3
  83. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -1
  84. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -1
  85. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/language.js +2 -8
  86. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +4 -29
  87. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +2 -1
  88. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +3 -2
  89. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +2 -2
  90. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +4 -6
  91. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +24 -21
  92. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +8 -2
  93. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +2 -0
  94. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/DispatcherCommand.js +2 -6
  95. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -12
  96. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +33 -9
  97. package/libx/_runtime/cds-services/adapter/odata-v4/utils/dispatcherUtils.js +56 -0
  98. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +2 -2
  99. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +10 -3
  100. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +9 -11
  101. package/libx/_runtime/cds-services/adapter/rest/RestRequest.js +6 -3
  102. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +4 -2
  103. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/utils.js +1 -1
  104. package/libx/_runtime/cds-services/adapter/rest/utils/binary.js +1 -1
  105. package/libx/_runtime/cds-services/adapter/rest/utils/key-value-utils.js +2 -3
  106. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +6 -4
  107. package/libx/_runtime/cds-services/adapter/rest/utils/result.js +1 -0
  108. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +8 -5
  109. package/libx/_runtime/cds-services/services/Service.js +40 -0
  110. package/libx/_runtime/cds-services/services/utils/columns.js +4 -3
  111. package/libx/_runtime/cds-services/services/utils/compareJson.js +4 -4
  112. package/libx/_runtime/cds-services/services/utils/differ.js +3 -3
  113. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +4 -4
  114. package/libx/_runtime/cds-services/util/assert.js +20 -14
  115. package/libx/_runtime/cds.js +9 -1
  116. package/libx/_runtime/common/aspects/any.js +5 -0
  117. package/libx/_runtime/common/aspects/entity.js +25 -7
  118. package/libx/_runtime/common/aspects/utils.js +2 -2
  119. package/libx/_runtime/common/composition/data.js +6 -0
  120. package/libx/_runtime/common/composition/insert.js +3 -2
  121. package/libx/_runtime/common/composition/tree.js +4 -10
  122. package/libx/_runtime/common/composition/update.js +4 -4
  123. package/libx/_runtime/common/constants/draft.js +29 -26
  124. package/libx/_runtime/common/error/constants.js +2 -2
  125. package/libx/_runtime/common/error/frontend.js +7 -15
  126. package/libx/_runtime/common/generic/auth/capabilities.js +59 -0
  127. package/libx/_runtime/common/generic/auth/constants.js +20 -0
  128. package/libx/_runtime/common/generic/auth/expand.js +54 -0
  129. package/libx/_runtime/common/generic/auth/index.js +32 -0
  130. package/libx/_runtime/common/generic/auth/insertOnly.js +15 -0
  131. package/libx/_runtime/common/generic/auth/readOnly.js +26 -0
  132. package/libx/_runtime/common/generic/auth/requires.js +34 -0
  133. package/libx/_runtime/common/generic/auth/restrict.js +298 -0
  134. package/libx/_runtime/common/generic/auth/restrictions.js +85 -0
  135. package/libx/_runtime/common/generic/auth/utils.js +213 -0
  136. package/libx/_runtime/common/generic/crud.js +8 -6
  137. package/libx/_runtime/common/generic/etag.js +1 -1
  138. package/libx/_runtime/common/generic/input.js +35 -35
  139. package/libx/_runtime/common/generic/sorting.js +2 -3
  140. package/libx/_runtime/common/generic/temporal.js +2 -2
  141. package/libx/_runtime/common/i18n/messages.properties +1 -1
  142. package/libx/_runtime/common/toggles/handler.js +21 -0
  143. package/libx/_runtime/common/utils/copy.js +10 -1
  144. package/libx/_runtime/common/utils/cqn2cqn4sql.js +111 -35
  145. package/libx/_runtime/common/utils/csn.js +63 -1
  146. package/libx/_runtime/common/utils/dollar.js +10 -1
  147. package/libx/_runtime/common/utils/draft.js +46 -7
  148. package/libx/_runtime/common/utils/entityFromCqn.js +13 -9
  149. package/libx/_runtime/common/utils/extensibilityUtils.js +18 -0
  150. package/libx/_runtime/common/utils/foreignKeyPropagations.js +88 -104
  151. package/libx/_runtime/common/utils/generateOnCond.js +4 -1
  152. package/libx/_runtime/common/utils/quotingStyles.js +2 -0
  153. package/libx/_runtime/common/utils/resolveStructured.js +25 -9
  154. package/libx/_runtime/common/utils/resolveView.js +4 -1
  155. package/libx/_runtime/common/utils/rewriteAsterisks.js +3 -16
  156. package/libx/_runtime/common/utils/structured.js +33 -37
  157. package/libx/_runtime/common/utils/template.js +17 -8
  158. package/libx/_runtime/common/utils/templateProcessor.js +28 -28
  159. package/libx/_runtime/db/data-conversion/post-processing.js +118 -412
  160. package/libx/_runtime/db/expand/expandCQNToJoin.js +45 -41
  161. package/libx/_runtime/db/expand/rawToExpanded.js +29 -8
  162. package/libx/_runtime/db/generic/index.js +1 -3
  163. package/libx/_runtime/db/generic/input.js +5 -10
  164. package/libx/_runtime/db/generic/rewrite.js +5 -2
  165. package/libx/_runtime/db/generic/structured.js +2 -2
  166. package/libx/_runtime/db/query/delete.js +2 -2
  167. package/libx/_runtime/db/query/insert.js +1 -1
  168. package/libx/_runtime/db/query/update.js +9 -14
  169. package/libx/_runtime/db/sql-builder/CreateBuilder.js +4 -3
  170. package/libx/_runtime/db/sql-builder/FunctionBuilder.js +8 -8
  171. package/libx/_runtime/db/sql-builder/InsertBuilder.js +14 -1
  172. package/libx/_runtime/db/sql-builder/SelectBuilder.js +3 -2
  173. package/libx/_runtime/db/sql-builder/dataTypes.js +3 -3
  174. package/libx/_runtime/db/utils/columns.js +3 -3
  175. package/libx/_runtime/db/utils/normalizeTimeData.js +2 -2
  176. package/libx/_runtime/db/utils/propagateForeignKeys.js +6 -2
  177. package/libx/_runtime/extensibility/mps/index.js +5 -0
  178. package/libx/_runtime/extensibility/mps/service.js +111 -0
  179. package/libx/_runtime/extensibility/mps/tar.js +42 -0
  180. package/libx/_runtime/extensibility/mps/utils.js +11 -0
  181. package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformREAD.js +0 -0
  182. package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformRESULT.js +17 -5
  183. package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformWRITE.js +1 -0
  184. package/libx/_runtime/extensibility/uiflex/index.js +54 -0
  185. package/libx/_runtime/extensibility/uiflex/service.js +276 -0
  186. package/libx/_runtime/{fiori → extensibility}/uiflex/utils.js +22 -7
  187. package/libx/_runtime/fiori/generic/activate.js +2 -2
  188. package/libx/_runtime/fiori/generic/before.js +4 -4
  189. package/libx/_runtime/fiori/generic/new.js +3 -3
  190. package/libx/_runtime/fiori/generic/patch.js +1 -1
  191. package/libx/_runtime/fiori/generic/read.js +58 -66
  192. package/libx/_runtime/fiori/generic/readOverDraft.js +74 -16
  193. package/libx/_runtime/fiori/utils/handler.js +6 -13
  194. package/libx/_runtime/fiori/utils/where.js +6 -5
  195. package/libx/_runtime/hana/Service.js +4 -10
  196. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +1 -1
  197. package/libx/_runtime/hana/driver.js +2 -2
  198. package/libx/_runtime/hana/execute.js +45 -75
  199. package/libx/_runtime/hana/pool.js +1 -1
  200. package/libx/_runtime/hana/streaming.js +2 -1
  201. package/libx/_runtime/index.js +6 -6
  202. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +5 -21
  203. package/libx/_runtime/messaging/Outbox.js +2 -2
  204. package/libx/_runtime/messaging/common-utils/AMQPClient.js +4 -14
  205. package/libx/_runtime/messaging/common-utils/connections.js +5 -7
  206. package/libx/_runtime/messaging/common-utils/normalizeIncomingMessage.js +30 -0
  207. package/libx/_runtime/messaging/enterprise-messaging-shared.js +2 -1
  208. package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +36 -30
  209. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +19 -12
  210. package/libx/_runtime/messaging/enterprise-messaging.js +8 -8
  211. package/libx/_runtime/messaging/file-based.js +5 -5
  212. package/libx/_runtime/messaging/message-queuing.js +14 -12
  213. package/libx/_runtime/messaging/outbox/utils.js +18 -19
  214. package/libx/_runtime/messaging/redis-messaging.js +91 -0
  215. package/libx/_runtime/messaging/service.js +8 -6
  216. package/libx/_runtime/remote/Service.js +44 -8
  217. package/libx/_runtime/remote/utils/client.js +24 -19
  218. package/libx/_runtime/remote/utils/data.js +11 -11
  219. package/libx/_runtime/sqlite/Service.js +6 -9
  220. package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +5 -2
  221. package/libx/_runtime/types/api.js +10 -2
  222. package/libx/common/utils/ucsn.js +109 -0
  223. package/libx/gql/resolvers/crud/update.js +5 -0
  224. package/libx/gql/resolvers/parse/ast2cqn/columns.js +3 -1
  225. package/libx/gql/schema/typeDefMap.js +2 -2
  226. package/libx/odata/afterburner.js +110 -16
  227. package/libx/odata/cqn2odata.js +24 -27
  228. package/libx/odata/grammar.pegjs +9 -1
  229. package/libx/odata/parseToCqn.js +39 -0
  230. package/libx/odata/parser.js +1 -1
  231. package/libx/rest/RestAdapter.js +9 -1
  232. package/libx/rest/middleware/input.js +54 -0
  233. package/libx/rest/middleware/operation.js +14 -1
  234. package/libx/rest/middleware/parse.js +11 -7
  235. package/package.json +2 -2
  236. package/server.js +34 -19
  237. package/srv/audit-log.cds +2 -2
  238. package/srv/flex.cds +8 -2
  239. package/srv/flex.js +1 -1
  240. package/srv/mps.cds +23 -0
  241. package/srv/mps.js +1 -0
  242. package/libx/_runtime/auth/strategies/utils/uaa.js +0 -21
  243. package/libx/_runtime/common/generic/auth.js +0 -874
  244. package/libx/_runtime/common/toggles/alpha.js +0 -43
  245. package/libx/_runtime/db/generic/arrayed.js +0 -33
  246. package/libx/_runtime/fiori/uiflex/index.js +0 -35
  247. package/libx/_runtime/fiori/uiflex/service.js +0 -150
  248. package/libx/rest/utils/data.js +0 -60
@@ -8,7 +8,9 @@ const { isDraftActivateAction, ensureNoDraftsSuffix, ensureDraftsSuffix, draftIs
8
8
 
9
9
  const { isCustomOperation } = require('../../cds-services/adapter/odata-v4/utils/request')
10
10
 
11
- const { DRAFT_COLUMNS_ADMIN } = require('../../common/constants/draft')
11
+ const { DRAFT_COLUMNS_ADMIN_MAP } = require('../../common/constants/draft')
12
+ const DRAFT_COLUMNS_ADMIN = Object.keys(DRAFT_COLUMNS_ADMIN_MAP)
13
+ const PREFIX_DRAFT_COLUMNS = DRAFT_COLUMNS_ADMIN.map(col => ({ ref: ['DRAFT_DraftAdministrativeData', col] }))
12
14
 
13
15
  // copied from adapter/odata-v4/utils/context-object
14
16
  const _getTargetEntityName = (service, pathSegments) => {
@@ -93,10 +95,8 @@ const _addDraftDataToContext = (req, result) => {
93
95
  req.data.DraftAdministrativeData_DraftUUID = result[0].DraftUUID
94
96
  }
95
97
 
96
- const _prefixDraftColumns = () => DRAFT_COLUMNS_ADMIN.map(col => ({ ref: ['DRAFT_DraftAdministrativeData', col] }))
97
-
98
98
  const _getSelectDraftDataCqn = (entityName, where) => {
99
- return SELECT.from(ensureDraftsSuffix(entityName), _prefixDraftColumns())
99
+ return SELECT.from(ensureDraftsSuffix(entityName), PREFIX_DRAFT_COLUMNS)
100
100
  .join('DRAFT.DraftAdministrativeData')
101
101
  .on('DraftAdministrativeData_DraftUUID =', { ref: ['DRAFT.DraftAdministrativeData', 'DraftUUID'] })
102
102
  .where(where)
@@ -5,7 +5,7 @@ const onDraftActivate = require('./activate')._handler
5
5
  const { isNavigationToMany } = require('../utils/req')
6
6
  const { getKeysCondition } = require('../utils/where')
7
7
  const { removeDraftUUIDIfNecessary, ensureDraftsSuffix } = require('../utils/handler')
8
- const { DRAFT_COLUMNS } = require('../../common/constants/draft')
8
+ const { DRAFT_COLUMNS_MAP } = require('../../common/constants/draft')
9
9
 
10
10
  const _getUpdateDraftAdminCQN = ({ user, timestamp }, draftUUID) => {
11
11
  return UPDATE('DRAFT.DraftAdministrativeData')
@@ -60,7 +60,7 @@ const _handler = async function (req, next) {
60
60
  const elements = req.target.elements
61
61
  for (const column in elements) {
62
62
  const col = elements[column]
63
- if (col.default !== undefined && !DRAFT_COLUMNS.includes(column)) {
63
+ if (col.default !== undefined && !(column in DRAFT_COLUMNS_MAP)) {
64
64
  if ('val' in col.default) req.data[col.name] = col.default.val
65
65
  else if ('ref' in col.default) req.data[col.name] = col.default.ref[0]
66
66
  else req.data[col.name] = col.default
@@ -91,7 +91,7 @@ const _handler = async function (req, next) {
91
91
  req.reject(404)
92
92
  }
93
93
 
94
- removeDraftUUIDIfNecessary(result[0], req)
94
+ removeDraftUUIDIfNecessary(req)(result[0])
95
95
 
96
96
  return result[0]
97
97
  }
@@ -82,7 +82,7 @@ const _handler = async function (req) {
82
82
  await Promise.all([dbtx.run(updateDraftCQN), dbtx.run(updateDraftAdminCQN)])
83
83
  result = await dbtx.run(_getSelectCQN(this.model, req, keysCondition, false))
84
84
  if (result.length === 0) req.reject(404)
85
- removeDraftUUIDIfNecessary(result[0], req)
85
+ removeDraftUUIDIfNecessary(req)(result[0])
86
86
  return result[0]
87
87
  }
88
88
 
@@ -3,7 +3,7 @@ const { SELECT } = cds.ql
3
3
 
4
4
  const { cqn2cqn4sql, convertWhereExists } = require('../../common/utils/cqn2cqn4sql')
5
5
  const { getElementDeep } = require('../../common/utils/csn')
6
- const { DRAFT_COLUMNS, DRAFT_COLUMNS_MAP, SCENARIO } = require('../../common/constants/draft')
6
+ const { DRAFT_COLUMNS_MAP, SCENARIO } = require('../../common/constants/draft')
7
7
  const {
8
8
  addColumnAlias,
9
9
  draftIsLocked,
@@ -786,7 +786,7 @@ const _getOrderByEnrichedColumns = (orderBy, columns, entity) => {
786
786
  // For associations we need to 'materialise' the resulting field, otherwise we cannot access it in an outer SELECT.
787
787
  if (entity && entity.elements[el.ref[0]] && entity.elements[el.ref[0]].isAssociation) {
788
788
  enrichedCol.push({ ref: [...el.ref], as: _poorMansAlias4(el) })
789
- } else if (!DRAFT_COLUMNS.includes(el.ref[el.ref.length - 1]) && !colNames.includes(el.ref[el.ref.length - 1])) {
789
+ } else if (!(el.ref[el.ref.length - 1] in DRAFT_COLUMNS_MAP) && !colNames.includes(el.ref[el.ref.length - 1])) {
790
790
  enrichedCol.push({ ref: [...el.ref] })
791
791
  }
792
792
  }
@@ -844,7 +844,9 @@ const _getUnionCQN = (req, draftName, columns, subSelect, draftWhere, model, ent
844
844
  for (const col of enrichedColumns) {
845
845
  // if we have columns for outer order by that may also be needed for joins, we need to duplicate them
846
846
  const element = getElementDeep(req.target, col.ref)
847
- if (element && element['@odata.foreignKey4']) columns.push({ ref: [...col.ref] })
847
+ if (element && element._foreignKey4) {
848
+ columns.push({ ref: [...col.ref] })
849
+ }
848
850
 
849
851
  col.as = _poorMansAlias4(col)
850
852
  // add alias to outer order by
@@ -1059,8 +1061,7 @@ const _getColumns = ({ query: { SELECT }, target }, model) => {
1059
1061
  return SELECT.columns
1060
1062
  ? SELECT.columns.filter(
1061
1063
  col =>
1062
- (col.ref && !DRAFT_COLUMNS.includes(col.ref[col.ref.length - 1])) ||
1063
- (!col.ref && !DRAFT_COLUMNS.includes(col))
1064
+ (col.ref && !(col.ref[col.ref.length - 1] in DRAFT_COLUMNS_MAP)) || (!col.ref && !(col in DRAFT_COLUMNS_MAP))
1064
1065
  )
1065
1066
  : getColumns(target, { onlyNames: true, removeIgnore: true })
1066
1067
  }
@@ -1100,20 +1101,14 @@ const _adaptSubSelects = ({ SELECT: { from, where } }, scenario) => {
1100
1101
  }
1101
1102
  }
1102
1103
 
1103
- const _calculateDraftAdminColumns = (result, user) => {
1104
- if (
1105
- Object.prototype.hasOwnProperty.call(result, 'DraftIsCreatedByMe') &&
1106
- Object.prototype.hasOwnProperty.call(result, 'CreatedByUser')
1107
- ) {
1104
+ const _calculateDraftAdminColumns = (result, user, deleteLastChangeDateTime) => {
1105
+ if (!result) return
1106
+ if ('InProcessByUser' in result && !draftIsLocked(result.LastChangeDateTime)) result.InProcessByUser = ''
1107
+ if (deleteLastChangeDateTime) delete result.LastChangeDateTime
1108
+ if ('DraftIsCreatedByMe' in result && 'CreatedByUser' in result)
1108
1109
  result.DraftIsCreatedByMe = result.CreatedByUser === user
1109
- }
1110
-
1111
- if (
1112
- Object.prototype.hasOwnProperty.call(result, 'DraftIsProcessedByMe') &&
1113
- Object.prototype.hasOwnProperty.call(result, 'InProcessByUser')
1114
- ) {
1110
+ if ('DraftIsProcessedByMe' in result && 'InProcessByUser' in result)
1115
1111
  result.DraftIsProcessedByMe = result.InProcessByUser === user
1116
- }
1117
1112
  }
1118
1113
 
1119
1114
  const _adaptDraftColumnsForSiblingEntity = (result, isSiblingActive) => {
@@ -1145,30 +1140,6 @@ const _adaptAnnotationAliases = cqn => {
1145
1140
  _collectAliases(cqn.SELECT.from, aliases)
1146
1141
  }
1147
1142
 
1148
- const calculateDraftTimeout = (scenario, result, deleteLastChangeDateTime) => {
1149
- if (scenario === SCENARIO.DRAFT_ADMIN) {
1150
- if (!draftIsLocked(result[0].LastChangeDateTime)) {
1151
- result[0].InProcessByUser = ''
1152
- }
1153
- if (deleteLastChangeDateTime) delete result[0].LastChangeDateTime
1154
-
1155
- return
1156
- }
1157
-
1158
- // non empty result that and DraftAdministrativeData was expanded
1159
- if (result.length && Object.prototype.hasOwnProperty.call(result[0], 'DraftAdministrativeData')) {
1160
- result.forEach(row => {
1161
- if (!row.DraftAdministrativeData) return
1162
- if (Object.prototype.hasOwnProperty.call(row.DraftAdministrativeData, 'InProcessByUser')) {
1163
- if (!draftIsLocked(row.DraftAdministrativeData.LastChangeDateTime)) {
1164
- row.DraftAdministrativeData.InProcessByUser = ''
1165
- }
1166
- }
1167
- if (deleteLastChangeDateTime) delete row.DraftAdministrativeData.LastChangeDateTime
1168
- })
1169
- }
1170
- }
1171
-
1172
1143
  const enhanceQueryForTimeoutIfNeeded = (scenario, columns = []) => {
1173
1144
  if (scenario !== SCENARIO.DRAFT_ADMIN) {
1174
1145
  const draftAdmin = columns.find(col => col.ref && col.ref[col.ref.length - 1] === 'DraftAdministrativeData')
@@ -1212,6 +1183,51 @@ const _adaptDraftAdminExpand = cqn => {
1212
1183
  }
1213
1184
  }
1214
1185
 
1186
+ const _getOriginalColumns = req => {
1187
+ const originalColumns = {}
1188
+ // expanded columns are handled generically in db
1189
+ for (const c of req.query.SELECT.columns) {
1190
+ originalColumns[c.ref ? c.ref[c.ref.length - 1] : c.as || c] = true
1191
+ }
1192
+ return originalColumns
1193
+ }
1194
+
1195
+ const _postProcess = (result, req, cqnScenario, deleteLastChangeDateTime) => {
1196
+ const resultAsArray = Array.isArray(result) ? result : result ? [result] : []
1197
+
1198
+ if (!result || !resultAsArray.length) return result
1199
+
1200
+ if (cqnScenario.scenario === SCENARIO.SIBLING_ENTITY) {
1201
+ if (resultAsArray[0].draftAdmin_inProcessByUser !== req.user.id) return []
1202
+ delete resultAsArray[0].draftAdmin_inProcessByUser
1203
+ _adaptDraftColumnsForSiblingEntity(resultAsArray[0], cqnScenario.isSiblingActive)
1204
+ }
1205
+
1206
+ const removeDraftUUIDIfNecessaryFn = removeDraftUUIDIfNecessary(req)
1207
+ let notRequestedColumns
1208
+ // REVISIT: remove flag cds.env.features.auto_fetch_expand_keys after two month grace period
1209
+ if (!req.query.SELECT._4odata && !cds.env.features.auto_fetch_expand_keys) {
1210
+ const originalColumns = _getOriginalColumns(req)
1211
+ notRequestedColumns = originalColumns && Object.keys(resultAsArray[0]).filter(key => !originalColumns[key])
1212
+ }
1213
+ if (cqnScenario.scenario === SCENARIO.DRAFT_ADMIN) {
1214
+ _calculateDraftAdminColumns(resultAsArray[0], req.user.id, deleteLastChangeDateTime)
1215
+ if (notRequestedColumns) {
1216
+ for (const key of notRequestedColumns) delete resultAsArray[0][key]
1217
+ }
1218
+ } else {
1219
+ for (const row of resultAsArray) {
1220
+ removeDraftUUIDIfNecessaryFn(row)
1221
+ _calculateDraftAdminColumns(row.DraftAdministrativeData, req.user.id, deleteLastChangeDateTime)
1222
+ if (notRequestedColumns) {
1223
+ for (const key of notRequestedColumns) delete row[key]
1224
+ }
1225
+ }
1226
+ }
1227
+
1228
+ return result
1229
+ }
1230
+
1215
1231
  /**
1216
1232
  * Generic Handler for READ requests in the context of draft.
1217
1233
  *
@@ -1274,32 +1290,8 @@ const _handler = async function (req) {
1274
1290
  req.target = this.model.definitions[ensureUnlocalized(req.target.name)]
1275
1291
 
1276
1292
  const result = await cds.tx(req).send({ query: cqnScenario.cqn, target: req.target })
1277
- const resultAsArray = Array.isArray(result) ? result : result ? [result] : []
1278
- removeDraftUUIDIfNecessary(resultAsArray, req)
1279
-
1280
- if (cqnScenario.scenario === SCENARIO.DRAFT_ADMIN) {
1281
- if (!result || (Array.isArray(result) && !result.length)) return result
1282
1293
 
1283
- _calculateDraftAdminColumns(resultAsArray[0], req.user.id)
1284
- }
1285
-
1286
- calculateDraftTimeout(cqnScenario.scenario, resultAsArray, enhancedWithLastChangeDateTime)
1287
-
1288
- if (cqnScenario.scenario === SCENARIO.SIBLING_ENTITY) {
1289
- if (!result || (Array.isArray(result) && !result.length)) return result
1290
- if (resultAsArray[0].draftAdmin_inProcessByUser !== req.user.id) return []
1291
-
1292
- delete resultAsArray[0].draftAdmin_inProcessByUser
1293
- _adaptDraftColumnsForSiblingEntity(resultAsArray[0], cqnScenario.isSiblingActive)
1294
- }
1295
-
1296
- if (resultAsArray.length && Object.prototype.hasOwnProperty.call(resultAsArray[0], 'DraftAdministrativeData')) {
1297
- resultAsArray.forEach(row => {
1298
- row.DraftAdministrativeData && _calculateDraftAdminColumns(row.DraftAdministrativeData, req.user.id)
1299
- })
1300
- }
1301
-
1302
- return result
1294
+ return _postProcess(result, req, cqnScenario, enhancedWithLastChangeDateTime)
1303
1295
  }
1304
1296
 
1305
1297
  module.exports = cds.service.impl(function () {
@@ -3,6 +3,7 @@ const { SELECT } = cds.ql
3
3
  const { getEnrichedCQN, hasDraft, ensureDraftsSuffix } = require('../utils/handler')
4
4
  const { readAndDeleteKeywords } = require('../utils/where')
5
5
  const { cqn2cqn4sql } = require('../../common/utils/cqn2cqn4sql')
6
+ const { isActiveEntityRequested } = require('../../../_runtime/fiori/utils/where')
6
7
 
7
8
  const _modifyCQN = (cqnDraft, where, context) => {
8
9
  const whereDraft = [...where]
@@ -10,9 +11,8 @@ const _modifyCQN = (cqnDraft, where, context) => {
10
11
  cqnDraft.where(whereDraft)
11
12
 
12
13
  if (result && result.value.val === false) {
13
- cqnDraft.SELECT.from.ref[cqnDraft.SELECT.from.ref.length - 1] = ensureDraftsSuffix(
14
- cqnDraft.SELECT.from.ref[cqnDraft.SELECT.from.ref.length - 1]
15
- )
14
+ const fromRef = cqnDraft.SELECT.from.ref
15
+ cqnDraft.SELECT.from.ref[fromRef.length - 1] = ensureDraftsSuffix(fromRef[fromRef.length - 1])
16
16
  }
17
17
 
18
18
  for (let i = 0; i < cqnDraft.SELECT.where.length; i++) {
@@ -33,32 +33,92 @@ const _modifyCQN = (cqnDraft, where, context) => {
33
33
  }
34
34
  }
35
35
 
36
+ const _hasNavToNonDraftEnclosedAssoc = (pathSegments, definitions, excludeAssoc) => {
37
+ if (pathSegments.length < 2) return false
38
+ const entity = definitions[pathSegments[0]]
39
+ const nav = entity.elements[pathSegments[1]]
40
+
41
+ if (nav._isAssociationStrict) {
42
+ if (nav['@odata.draft.enclosed']) return false
43
+ if (excludeAssoc == null) return true
44
+ if (!excludeAssoc(nav)) return true
45
+ }
46
+
47
+ // At this point we know that nav is a composition, so we have to recursively
48
+ // follow the navigation until we reach the end or find an association.
49
+ pathSegments.shift()
50
+ pathSegments[0] = nav.target
51
+ return _hasNavToNonDraftEnclosedAssoc(pathSegments, definitions, excludeAssoc)
52
+ }
53
+
54
+ const _shouldReadOverDraft = (req, definitions) => {
55
+ const SELECT = req.query.SELECT
56
+ const fromRef = SELECT.from.ref
57
+
58
+ if (!fromRef) return false
59
+
60
+ // read over the draft if the navigation target is non-draft-enabled
61
+ if (!req.target._isDraftEnabled) return true
62
+
63
+ // read over the draft only for navigation scenarios
64
+ if (fromRef.length === 1) return false
65
+
66
+ const firstFromRef = fromRef[0]
67
+ const rootEntityName = typeof firstFromRef === 'string' ? firstFromRef : firstFromRef.id
68
+ const rootEntity = definitions[rootEntityName]
69
+
70
+ // read over the draft only if the root entity is draft-enabled
71
+ if (!rootEntity._isDraftEnabled) return false
72
+
73
+ // read over the draft only if the navigation starts from a draft entity, e.g.,
74
+ // /Books(ID=1, IsActiveEntity=false)
75
+ if (isActiveEntityRequested(firstFromRef.where)) return false
76
+
77
+ const pathSegments = fromRef.map(path => (typeof path === 'string' ? path : path.id))
78
+ const excludeAssoc = assoc => {
79
+ if (assoc.name === 'DraftAdministrativeData' || assoc.name === 'SiblingEntity') return true
80
+ return false
81
+ }
82
+
83
+ // Read over the draft only if:
84
+ // - the navigation target is an association and
85
+ // - isn't annotated with the @odata.draft.enclosed annotation
86
+ return _hasNavToNonDraftEnclosedAssoc(pathSegments, definitions, excludeAssoc)
87
+ }
88
+
36
89
  /**
37
90
  * Generic Handler for READ requests.
38
91
  *
39
- * @param req
92
+ * @param {import('../../cds-services/adapter/odata-v4/ODataRequest')} req
93
+ * @param next
40
94
  * @returns {Promise<Array>}
41
95
  */
42
- const _handler = async function (req) {
43
- if (req.query.SELECT.limit && req.query.SELECT.limit.rows && req.query.SELECT.limit.rows.val === 0) {
44
- return Promise.resolve([])
45
- }
96
+ const _readOverDraftHandler = async function (req, next) {
97
+ const definitions = this.model.definitions
98
+
99
+ // determine whether the request is handled here (read over draft handler),
100
+ // or whether it is passed to the next registered handler/route
101
+ if (!_shouldReadOverDraft(req, definitions)) return next()
102
+
103
+ const rows = req.query.SELECT.limit && req.query.SELECT.limit.rows
104
+ if (rows && rows.val === 0) return Promise.resolve([])
46
105
 
47
106
  // REVISIT DRAFT HANDLING: cqn2cqn4sql must not be called here
48
- const sqlQuery = cqn2cqn4sql(req.query, this.model, { _4fiori: true })
107
+ const sqlQuery = cqn2cqn4sql(req.query, this.model, { _4db: req.target._isDraftEnabled, _4fiori: true })
108
+
49
109
  if (req.query._streaming) {
50
110
  sqlQuery._streaming = true
51
111
  }
52
112
 
53
- const hasDraftEntity = hasDraft(this.model.definitions, sqlQuery)
113
+ const hasDraftEntity = hasDraft(definitions, sqlQuery)
54
114
 
55
- if (hasDraftEntity && sqlQuery.SELECT.where && sqlQuery.SELECT.where.length !== 0) {
115
+ if (hasDraftEntity && sqlQuery.SELECT.where && sqlQuery.SELECT.where.length > 0) {
56
116
  let cqnDraft = SELECT.from({
57
117
  ref: [...sqlQuery.SELECT.from.ref],
58
118
  as: sqlQuery.SELECT.from.as
59
119
  })
60
- cqnDraft.SELECT.columns = sqlQuery.SELECT.columns
61
120
 
121
+ cqnDraft.SELECT.columns = sqlQuery.SELECT.columns
62
122
  _modifyCQN(cqnDraft, sqlQuery.SELECT.where, req)
63
123
  cqnDraft = getEnrichedCQN(cqnDraft, sqlQuery.SELECT, [])
64
124
  return cds.tx(req).run(cqnDraft)
@@ -67,8 +127,6 @@ const _handler = async function (req) {
67
127
  return cds.tx(req).run(sqlQuery)
68
128
  }
69
129
 
70
- module.exports = cds.service.impl(function () {
71
- for (const entity of Object.values(this.entities).filter(e => !e._isDraftEnabled)) {
72
- this.on('READ', entity, _handler)
73
- }
130
+ module.exports = cds.service.impl(function readOverDraft() {
131
+ this.on('READ', '*', _readOverDraftHandler)
74
132
  })
@@ -4,7 +4,7 @@ const { getColumns } = require('../../cds-services/services/utils/columns')
4
4
  const { ensureNoDraftsSuffix, ensureDraftsSuffix, ensureUnlocalized } = require('../../common/utils/draft')
5
5
  const getTemplate = require('../../common/utils/template')
6
6
 
7
- const { DRAFT_COLUMNS } = require('../../common/constants/draft')
7
+ const { DRAFT_COLUMNS_MAP } = require('../../common/constants/draft')
8
8
 
9
9
  // unofficial config!
10
10
  const MAX_RECURSION_DEPTH = (cds.env.features.recursion_depth && Number(cds.env.features.recursion_depth)) || 2
@@ -157,7 +157,7 @@ const getEnrichedCQN = (cqn, select, draftWhere, scenarioAlias, addLimitOrder =
157
157
  const _aliasRef = (ref, alias) => {
158
158
  const newRef = [...ref]
159
159
  // we skip draft columns because they are mostly calculated later on
160
- if (alias && !DRAFT_COLUMNS.includes(ref[ref.length - 1])) {
160
+ if (alias && !(ref[ref.length - 1] in DRAFT_COLUMNS_MAP)) {
161
161
  newRef.unshift(alias)
162
162
  }
163
163
  return newRef
@@ -184,17 +184,10 @@ const setStatusCodeAndHeader = (response, keys, entityName, isActiveEntity) => {
184
184
  response.setHeader('location', `../${entityName}(${keysString},IsActiveEntity=${isActiveEntity})`)
185
185
  }
186
186
 
187
- const removeDraftUUIDIfNecessary = (result, req) => {
188
- if (req._.req && req._.req.headers && req._.req.headers['x-cds-odata-version'] === 'v2') return
189
-
190
- if (Array.isArray(result)) {
191
- for (const row of result) {
192
- delete row.DraftAdministrativeData_DraftUUID
193
- }
194
- } else {
195
- delete result.DraftAdministrativeData_DraftUUID
196
- }
197
- }
187
+ const removeDraftUUIDIfNecessary = req =>
188
+ req._.req && req._.req.headers && req._.req.headers['x-cds-odata-version'] === 'v2'
189
+ ? () => {}
190
+ : result => delete result.DraftAdministrativeData_DraftUUID
198
191
 
199
192
  const isDraftActivateAction = req => {
200
193
  // REVISIT: get rid of getUrlObject
@@ -1,3 +1,5 @@
1
+ const AND_OR = { and: 1, or: 1 }
2
+
1
3
  const _removeMultipleBrackets = (index, whereCondition, isXpr) => {
2
4
  if (isXpr) return index
3
5
 
@@ -20,19 +22,18 @@ const _removeMultipleBrackets = (index, whereCondition, isXpr) => {
20
22
  }
21
23
 
22
24
  const _calculateSpliceArgs = (index, whereCondition, isXpr = false) => {
23
- const AND_OR = ['and', 'or']
24
25
  const len = isXpr ? 1 : 3
25
- if (AND_OR.includes(whereCondition[index - 1])) {
26
+ if (whereCondition[index - 1] in AND_OR) {
26
27
  return { index: index - 1, count: 1 + len }
27
28
  }
28
- if (AND_OR.includes(whereCondition[index + len])) {
29
+ if (whereCondition[index + len] in AND_OR) {
29
30
  return { index: index, count: len + 1 }
30
31
  }
31
32
  if (whereCondition[index - 1] === '(' && whereCondition[index + len] === ')') {
32
- if (AND_OR.includes(whereCondition[index - 2])) {
33
+ if (whereCondition[index - 2] in AND_OR) {
33
34
  return { index: index - 2, count: len + 3 }
34
35
  }
35
- if (AND_OR.includes(whereCondition[index + len + 1])) {
36
+ if (whereCondition[index + len + 1] in AND_OR) {
36
37
  return { index: index - 1, count: len + 3 }
37
38
  }
38
39
 
@@ -107,17 +107,12 @@ class HanaDatabase extends DatabaseService {
107
107
 
108
108
  _registerAfterHandlers() {
109
109
  // REVISIT: after phase runs in parallel -> side effects possible!
110
- const { effective } = cds.env
110
+ const { effective, features } = cds.env
111
111
 
112
- if (effective.odata.structs) {
112
+ if (effective.odata.structs && !features.ucsn_struct_conversion) {
113
113
  // REVISIT: only register for entities that contain structured or navigation to it
114
114
  this.after(['READ'], '*', this._structured)
115
115
  }
116
-
117
- if (effective.odata.version !== 'v2') {
118
- // REVISIT: only register for entities that contain arrayed or navigation to it
119
- this.after(['READ'], '*', this._arrayed)
120
- }
121
116
  }
122
117
 
123
118
  /*
@@ -125,14 +120,13 @@ class HanaDatabase extends DatabaseService {
125
120
  */
126
121
  // eslint-disable-next-line complexity
127
122
  async acquire(arg) {
128
- // REVISIT: remove fallback arg.user.tenant with cds^6
123
+ // REVISIT: remove fallback arg.user.tenant with cds^6 (still needed for cds-mtx)
129
124
  const tenant = (typeof arg === 'string' ? arg : arg.tenant || (arg.user && arg.user.tenant)) || 'anonymous'
130
125
  const dbc = await pool.acquire(tenant, this)
131
126
 
132
127
  if (typeof arg !== 'string') {
133
128
  _setSessionContext(dbc, 'APPLICATIONUSER', arg.user.id || 'ANONYMOUS')
134
- // REVISIT: remove fallback arg.user.locale with cds^6
135
- _setSessionContext(dbc, 'LOCALE', arg.locale || (arg.user && arg.user.locale) || 'en')
129
+ _setSessionContext(dbc, 'LOCALE', arg.locale || 'en')
136
130
  // REVISIT: stable access
137
131
  const validFrom = (arg.context && arg.context._ && arg.context._['VALID-FROM']) || (arg._ && arg._['VALID-FROM'])
138
132
  const validto = (arg.context && arg.context._ && arg.context._['VALID-TO']) || (arg._ && arg._['VALID-TO'])
@@ -56,7 +56,7 @@ class CustomSelectBuilder extends SelectBuilder {
56
56
  select.orderBy.every(o => {
57
57
  const k = o.ref && o.ref.length === 1 && o.ref[0]
58
58
  const element = (k && entity.elements[k]) || {}
59
- return element.type !== 'cds.String'
59
+ return element._type !== 'cds.String'
60
60
  }))
61
61
  } catch (e) {
62
62
  if (LOG._warn) {
@@ -50,7 +50,7 @@ function _connectHdb(creds, tenant) {
50
50
  if (err) {
51
51
  err = _ensureError(err)
52
52
  err.message = `Could not establish connection for tenant "${tenant}" due to error: ` + err.message
53
- LOG._error && LOG.error(err)
53
+ LOG.error(err)
54
54
 
55
55
  // error on .connect shall lead to pool drain
56
56
  err._connectError = true
@@ -109,7 +109,7 @@ function _connectHanaClient(creds, tenant) {
109
109
  if (err) {
110
110
  err = _ensureError(err)
111
111
  err.message = `Could not establish connection for tenant "${tenant}" due to error: ` + err.message
112
- LOG._error && LOG.error(err)
112
+ LOG.error(err)
113
113
 
114
114
  // error on .connect shall lead to pool drain
115
115
  err._connectError = true