@sap/cds 6.1.3 → 6.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/CHANGELOG.md +77 -8
  2. package/apis/cds.d.ts +18 -6
  3. package/apis/connect.d.ts +1 -1
  4. package/apis/cqn.d.ts +1 -1
  5. package/apis/log.d.ts +23 -5
  6. package/apis/ql.d.ts +128 -61
  7. package/apis/services.d.ts +11 -0
  8. package/apis/test.d.ts +61 -0
  9. package/apis/utils.d.ts +15 -0
  10. package/app/fiori/preview.js +1 -0
  11. package/bin/build/buildTaskEngine.js +70 -22
  12. package/bin/build/buildTaskFactory.js +18 -11
  13. package/bin/build/buildTaskHandler.js +1 -1
  14. package/bin/build/buildTaskProviderFactory.js +3 -13
  15. package/bin/build/constants.js +0 -1
  16. package/bin/build/index.js +14 -6
  17. package/bin/build/provider/buildTaskHandlerEdmx.js +2 -3
  18. package/bin/build/provider/buildTaskHandlerFeatureToggles.js +2 -2
  19. package/bin/build/provider/buildTaskHandlerInternal.js +3 -6
  20. package/bin/build/provider/buildTaskProviderInternal.js +51 -39
  21. package/bin/build/provider/fiori/index.js +3 -3
  22. package/bin/build/provider/hana/2migration.js +1 -1
  23. package/bin/build/provider/hana/index.js +34 -27
  24. package/bin/build/provider/java/index.js +6 -7
  25. package/bin/build/provider/mtx/index.js +20 -18
  26. package/bin/build/provider/mtx/resourcesTarBuilder.js +8 -11
  27. package/bin/build/provider/mtx-sidecar/index.js +13 -17
  28. package/bin/build/provider/nodejs/index.js +8 -7
  29. package/bin/build/util.js +22 -4
  30. package/bin/cds.js +8 -4
  31. package/bin/deploy/to-hana/cfUtil.js +53 -18
  32. package/bin/mtx/in-cds.js +1 -0
  33. package/bin/serve.js +37 -30
  34. package/lib/auth/basic-auth.js +33 -0
  35. package/lib/auth/dummy-auth.js +7 -0
  36. package/lib/auth/ias-auth.js +2 -0
  37. package/lib/auth/index.js +31 -0
  38. package/lib/auth/jwt-auth.js +3 -0
  39. package/lib/auth/mocked-users.js +72 -0
  40. package/lib/auth/passport-basic.js +12 -0
  41. package/lib/auth/passport-digest.js +14 -0
  42. package/lib/auth/xsuaa-auth.js +3 -0
  43. package/lib/compile/cds-compile.js +3 -3
  44. package/lib/compile/to/cdl.js +5 -1
  45. package/lib/compile/to/edm.js +8 -0
  46. package/lib/compile/to/gql.js +1 -0
  47. package/lib/compile/to/json.js +30 -5
  48. package/lib/compile/to/sql.js +3 -1
  49. package/lib/core/index.js +5 -1
  50. package/lib/dbs/cds-deploy.js +36 -6
  51. package/lib/env/cds-env.js +15 -5
  52. package/lib/env/cds-requires.js +51 -58
  53. package/lib/env/defaults.js +1 -0
  54. package/lib/env/schemas/cds-package.json +4 -0
  55. package/lib/env/schemas/cds-rc.json +63 -77
  56. package/lib/i18n/localize.js +16 -5
  57. package/lib/index.js +9 -4
  58. package/lib/log/cds-error.js +4 -6
  59. package/lib/log/cds-log.js +89 -53
  60. package/lib/log/service/index.js +1 -0
  61. package/lib/ql/CREATE.js +2 -5
  62. package/lib/ql/DELETE.js +1 -1
  63. package/lib/ql/DROP.js +1 -3
  64. package/lib/ql/INSERT.js +3 -3
  65. package/lib/ql/Query.js +10 -23
  66. package/lib/ql/SELECT.js +1 -2
  67. package/lib/ql/UPDATE.js +2 -2
  68. package/lib/ql/Whereable.js +7 -15
  69. package/lib/ql/cds-ql.js +9 -3
  70. package/lib/req/cds-context.js +11 -3
  71. package/lib/req/context.js +29 -23
  72. package/lib/req/locale.js +9 -5
  73. package/lib/req/request.js +1 -0
  74. package/lib/req/user.js +2 -1
  75. package/lib/srv/cds-connect.js +1 -1
  76. package/lib/srv/cds-serve.js +21 -14
  77. package/lib/srv/middlewares/cds-context.js +29 -0
  78. package/lib/srv/middlewares/ctx-model.js +24 -0
  79. package/lib/srv/middlewares/errors.js +9 -0
  80. package/lib/srv/middlewares/index.js +22 -0
  81. package/lib/srv/middlewares/sap-statistics.js +13 -0
  82. package/lib/srv/middlewares/trace.js +102 -0
  83. package/lib/srv/protocols/_legacy.js +42 -0
  84. package/lib/srv/protocols/graphql.js +39 -0
  85. package/lib/srv/protocols/hcql.js +37 -0
  86. package/lib/srv/protocols/index.js +86 -0
  87. package/lib/srv/protocols/odata-v2-proxy.js +3767 -0
  88. package/lib/srv/protocols/odata-v2.js +26 -0
  89. package/lib/srv/protocols/odata-v4.js +16 -0
  90. package/lib/srv/protocols/rest.js +13 -0
  91. package/lib/srv/srv-api.js +5 -0
  92. package/lib/srv/srv-models.js +4 -6
  93. package/lib/utils/axios.js +3 -2
  94. package/lib/utils/cds-test.js +27 -21
  95. package/lib/utils/cds-utils.js +19 -20
  96. package/lib/utils/tar.js +175 -0
  97. package/libx/_runtime/audit/generic/personal/utils.js +18 -7
  98. package/libx/_runtime/audit/utils/v2.js +1 -0
  99. package/libx/_runtime/auth/index.js +4 -0
  100. package/libx/_runtime/auth/strategies/ias-auth.js +76 -0
  101. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +8 -3
  102. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +15 -4
  103. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -1
  104. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/orderByToCQN.js +1 -1
  105. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +1 -1
  106. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +9 -0
  107. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriInfo.js +5 -1
  108. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +12 -0
  109. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/BufferedWriter.js +6 -2
  110. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/RequestValidator.js +47 -7
  111. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
  112. package/libx/_runtime/cds-services/util/assert.js +4 -0
  113. package/libx/_runtime/common/aspects/relation.js +1 -1
  114. package/libx/_runtime/common/composition/data.js +61 -15
  115. package/libx/_runtime/common/composition/delete.js +0 -1
  116. package/libx/_runtime/common/composition/insert.js +0 -1
  117. package/libx/_runtime/common/composition/tree.js +4 -10
  118. package/libx/_runtime/common/composition/update.js +44 -21
  119. package/libx/_runtime/common/generic/auth/capabilities.js +8 -10
  120. package/libx/_runtime/common/generic/crud.js +1 -2
  121. package/libx/_runtime/common/generic/etag.js +4 -4
  122. package/libx/_runtime/common/generic/input.js +4 -4
  123. package/libx/_runtime/common/generic/paging.js +3 -3
  124. package/libx/_runtime/common/generic/put.js +3 -3
  125. package/libx/_runtime/common/generic/sorting.js +4 -4
  126. package/libx/_runtime/common/generic/temporal.js +3 -3
  127. package/libx/_runtime/common/i18n/messages.properties +0 -7
  128. package/libx/_runtime/common/utils/cqn2cqn4sql.js +11 -6
  129. package/libx/_runtime/common/utils/csn.js +0 -28
  130. package/libx/_runtime/common/utils/draft.js +8 -1
  131. package/libx/_runtime/common/utils/path.js +7 -1
  132. package/libx/_runtime/common/utils/resolveView.js +2 -3
  133. package/libx/_runtime/db/data-conversion/post-processing.js +3 -44
  134. package/libx/_runtime/db/generic/input.js +3 -3
  135. package/libx/_runtime/db/sql-builder/dataTypes.js +4 -0
  136. package/libx/_runtime/fiori/generic/activate.js +2 -2
  137. package/libx/_runtime/fiori/generic/before.js +40 -72
  138. package/libx/_runtime/fiori/generic/cancel.js +2 -2
  139. package/libx/_runtime/fiori/generic/delete.js +2 -2
  140. package/libx/_runtime/fiori/generic/edit.js +2 -2
  141. package/libx/_runtime/fiori/generic/new.js +2 -2
  142. package/libx/_runtime/fiori/generic/patch.js +49 -37
  143. package/libx/_runtime/fiori/generic/prepare.js +2 -2
  144. package/libx/_runtime/fiori/generic/read.js +27 -37
  145. package/libx/_runtime/fiori/utils/where.js +4 -2
  146. package/libx/_runtime/hana/Service.js +1 -3
  147. package/libx/_runtime/hana/conversion.js +3 -0
  148. package/libx/_runtime/hana/driver.js +33 -3
  149. package/libx/_runtime/hana/dynatrace.js +1 -0
  150. package/libx/_runtime/hana/search2Contains.js +12 -1
  151. package/libx/_runtime/hana/search2cqn4sql.js +10 -27
  152. package/libx/_runtime/hana/streaming.js +1 -0
  153. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +4 -2
  154. package/libx/_runtime/messaging/common-utils/AMQPClient.js +1 -0
  155. package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +5 -2
  156. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -0
  157. package/libx/_runtime/messaging/enterprise-messaging.js +62 -3
  158. package/libx/_runtime/messaging/outbox/utils.js +1 -1
  159. package/libx/_runtime/messaging/redis-messaging.js +1 -0
  160. package/libx/_runtime/remote/Service.js +2 -2
  161. package/libx/_runtime/remote/utils/client.js +8 -3
  162. package/libx/_runtime/remote/utils/data.js +7 -2
  163. package/libx/_runtime/sqlite/Service.js +18 -7
  164. package/libx/_runtime/sqlite/conversion.js +3 -0
  165. package/libx/_runtime/sqlite/convertAssocToOneManaged.js +3 -3
  166. package/libx/_runtime/sqlite/localized.js +8 -8
  167. package/libx/odata/afterburner.js +39 -7
  168. package/libx/odata/cqn2odata.js +6 -3
  169. package/libx/odata/grammar.pegjs +66 -18
  170. package/libx/odata/index.js +3 -2
  171. package/libx/odata/parser.js +1 -1
  172. package/libx/odata/utils.js +2 -0
  173. package/libx/rest/RestAdapter.js +62 -43
  174. package/libx/rest/middleware/parse.js +2 -1
  175. package/libx/rest/middleware/update.js +1 -1
  176. package/package.json +2 -2
  177. package/server.js +5 -4
  178. package/srv/mtx.cds +1 -1
  179. package/srv/mtx.js +4 -33
  180. package/lib/srv/adapters.js +0 -85
  181. package/lib/utils/resources/index.js +0 -48
  182. package/lib/utils/resources/tar.js +0 -49
  183. package/lib/utils/resources/utils.js +0 -11
  184. package/libx/_runtime/extensibility/activate.js +0 -69
  185. package/libx/_runtime/extensibility/add.js +0 -50
  186. package/libx/_runtime/extensibility/addExtension.js +0 -72
  187. package/libx/_runtime/extensibility/defaults.js +0 -34
  188. package/libx/_runtime/extensibility/handler/transformREAD.js +0 -121
  189. package/libx/_runtime/extensibility/handler/transformRESULT.js +0 -51
  190. package/libx/_runtime/extensibility/handler/transformWRITE.js +0 -64
  191. package/libx/_runtime/extensibility/linter/allowlist_checker.js +0 -373
  192. package/libx/_runtime/extensibility/linter/annotations_checker.js +0 -113
  193. package/libx/_runtime/extensibility/linter/checker_base.js +0 -20
  194. package/libx/_runtime/extensibility/linter/namespace_checker.js +0 -180
  195. package/libx/_runtime/extensibility/linter.js +0 -32
  196. package/libx/_runtime/extensibility/push.js +0 -118
  197. package/libx/_runtime/extensibility/service.js +0 -38
  198. package/libx/_runtime/extensibility/token.js +0 -57
  199. package/libx/_runtime/extensibility/utils.js +0 -131
  200. package/libx/_runtime/extensibility/validation.js +0 -50
  201. package/libx/_runtime/extensibility/views.js +0 -12
  202. package/srv/extensibility-service.cds +0 -60
  203. package/srv/extensibility-service.js +0 -1
  204. package/srv/extensions.cds +0 -8
  205. package/srv/model-provider.cds +0 -61
  206. package/srv/model-provider.js +0 -143
@@ -43,7 +43,7 @@ const _getStaticOrders = req => {
43
43
  *
44
44
  * @param req
45
45
  */
46
- const _handler = function (req) {
46
+ const commonGenericSorting = function (req) {
47
47
  if (!req.query || !req.query.SELECT || req.query.SELECT.one) return
48
48
 
49
49
  let select = req.query.SELECT
@@ -77,9 +77,9 @@ const _handler = function (req) {
77
77
  * handler registration
78
78
  */
79
79
  module.exports = cds.service.impl(function () {
80
- _handler._initial = true
81
- this.before('READ', '*', _handler)
80
+ commonGenericSorting._initial = true
81
+ this.before('READ', '*', commonGenericSorting)
82
82
  })
83
83
 
84
84
  // REVISIT: remove (currently needed for test)
85
- module.exports.handler = _handler
85
+ module.exports.handler = commonGenericSorting
@@ -38,7 +38,7 @@ const _getTimeDelta = (target, queryOption) => {
38
38
  *
39
39
  * @param req
40
40
  */
41
- const _handler = function (req) {
41
+ const commonGenericTemporal = function (req) {
42
42
  // REVISIT: public API for query options
43
43
  const { _queryOptions } = req
44
44
 
@@ -67,7 +67,7 @@ const _handler = function (req) {
67
67
  * handler registration
68
68
  */
69
69
  module.exports = cds.service.impl(function () {
70
- _handler._initial = true
70
+ commonGenericTemporal._initial = true
71
71
  // always run to allow interaction with temporal data in custom handlers
72
- this.before('*', _handler)
72
+ this.before('*', commonGenericTemporal)
73
73
  })
@@ -49,10 +49,6 @@ ASSERT_REFERENCE_INTEGRITY=Reference integrity is violated for association "{0}"
49
49
  ASSERT_TARGET="Value doesn't exist"
50
50
  ASSERT_DEEP_ASSOCIATION=It is not allowed to modify sub documents in {0} Association "{1}"
51
51
 
52
- # persistence
53
- PERSISTENCE_SKIP_NO_GENERIC_CRUD=Entity "{0}" is annotated with "@sap.persistence.skip" and cannot be served generically.
54
- NON_WRITABLE_VIEW={0} on views with join and/or union is not supported
55
-
56
52
  # db
57
53
  NO_DATABASE_CONNECTION=No database connection
58
54
  ENTITY_ALREADY_EXISTS=Entity already exists
@@ -74,9 +70,6 @@ ENTITY_IS_NOT_CRUD=Entity "{0}" is not {1}
74
70
  ENTITY_IS_NOT_CRUD_VIA_NAVIGATION=Entity "{0}" is not {1} via navigation "{2}"
75
71
  ENTITY_IS_AUTOEXPOSED=Entity "{0}" is not explicitly exposed as part of the service
76
72
  EXPAND_IS_RESTRICTED=Navigation property "{0}" is not allowed for expand operation
77
- EXPAND_COUNT_UNSUPPORTED="/$count" is not supported for expand operation
78
- ORDERBY_LAMBDA_UNSUPPORTED="$orderby" does not support lambda
79
- EXPAND_APPLY_UNSUPPORTED="$apply" is not supported for expand operation
80
73
 
81
74
  # rest protocol adapter
82
75
  INVALID_RESOURCE="{0}" is not a valid resource
@@ -5,7 +5,7 @@ const { SELECT, INSERT, DELETE, UPDATE } = cds.ql
5
5
  const Query = require('../../../../lib/ql/Query')
6
6
 
7
7
  const { resolveView } = require('./resolveView')
8
- const { ensureNoDraftsSuffix, getDraftColumnsCQNForDraft } = require('./draft')
8
+ const { ensureNoDraftsSuffix, getDraftColumnsCQNForDraft, ensureDraftsSuffix } = require('./draft')
9
9
  const { flattenStructuredSelect, OPERATIONS_MAP } = require('./structured')
10
10
  const search2cqn4sql = require('./search2cqn4sql')
11
11
  const { getEntityNameFromCQN } = require('./entityFromCqn')
@@ -43,11 +43,11 @@ const convertPathExpressionToWhere = (fromClause, model, options) => {
43
43
  return { target, alias, where, cardinality, args }
44
44
  }
45
45
 
46
- let previousSelect, previousEntityName, previousTableAlias, structParent, previousArgs
46
+ let previousSelect, previousEntityName, previousTableAlias, structParent, previousArgs, draft
47
47
  let prefix = []
48
48
  let columns
49
49
  for (let i = 0; i < fromClause.ref.length; i++) {
50
- const entity = structParent || model.definitions[previousEntityName]
50
+ const entity = structParent || (previousEntityName && model.definitions[ensureNoDraftsSuffix(previousEntityName)])
51
51
  const element = _elementFromRef(fromClause.ref[i], entity)
52
52
 
53
53
  if (element && element._isStructured) {
@@ -55,6 +55,7 @@ const convertPathExpressionToWhere = (fromClause, model, options) => {
55
55
  structParent = element
56
56
  continue
57
57
  } else if (element && element.isAssociation) {
58
+ if (element._isAssociationStrict && !element['@odata.draft.enclosed']) draft = false
58
59
  _modifyNavigationInWhere(fromClause.ref[i].where, element._target)
59
60
  } else if (element && previousSelect && i === fromClause.ref.length - 1) {
60
61
  columns = [{ ref: [...prefix, element.name] }]
@@ -62,8 +63,10 @@ const convertPathExpressionToWhere = (fromClause, model, options) => {
62
63
  continue
63
64
  }
64
65
 
65
- const currentEntityName = _getEntityName(fromClause, entity, i)
66
+ const entityName = _getEntityName(fromClause, entity, i)
67
+ const currentEntityName = draft ? entityName && ensureDraftsSuffix(entityName) : entityName
66
68
  if (!currentEntityName) continue
69
+ if (!draft && currentEntityName.endsWith('_drafts')) draft = true
67
70
  const tableAlias = `T${i}`
68
71
  const currentSelect = SELECT.from(`${currentEntityName} as ${tableAlias}`)
69
72
 
@@ -81,7 +84,10 @@ const convertPathExpressionToWhere = (fromClause, model, options) => {
81
84
  if (previousSelect) {
82
85
  const navigation = _getTargetFromRef(fromClause.ref[i])
83
86
  previousSelect.where(
84
- model.definitions[previousEntityName]._relations[[...prefix, navigation]].join(tableAlias, previousTableAlias)
87
+ model.definitions[ensureNoDraftsSuffix(previousEntityName)]._relations[[...prefix, navigation]].join(
88
+ tableAlias,
89
+ previousTableAlias
90
+ )
85
91
  )
86
92
  _convertSelect(previousSelect, model, options)
87
93
  currentSelect.where('exists', previousSelect)
@@ -613,7 +619,6 @@ const _convertPathExpression = (query, model, options = {}) => {
613
619
  for (const whereEl of query.SELECT.where || []) {
614
620
  if (typeof whereEl === 'object' && whereEl.SELECT) _convertPathExpression(whereEl, model)
615
621
  }
616
-
617
622
  const conversion = convertPathExpressionToWhere(query.SELECT.from, model, options)
618
623
  if (!conversion) return
619
624
  const { target, alias, where, cardinality, columns, args } = conversion
@@ -1,8 +1,6 @@
1
1
  const cds = require('../../cds')
2
2
  const resolveStructured = require('../../common/utils/resolveStructured')
3
3
 
4
- const { ensureNoDraftsSuffix } = require('./draft')
5
-
6
4
  const getEtagElement = entity => {
7
5
  return Object.values(entity.elements).find(element => element['@odata.etag'])
8
6
  }
@@ -165,31 +163,6 @@ const getElementDeep = (entity, ref) => {
165
163
  return current
166
164
  }
167
165
 
168
- const isRootEntity = (definitions, entityName) => {
169
- const entity = definitions[entityName]
170
- if (!entity) return false
171
-
172
- // TODO: There can be unmanaged relations to some parent -> not detected by the following code
173
- const associationElements = Object.keys(entity.elements)
174
- .map(key => entity.elements[key])
175
- .filter(element => element._isAssociationStrict)
176
-
177
- for (const { target } of associationElements) {
178
- const parentEntity = definitions[target]
179
- for (const parentElementName in parentEntity.elements) {
180
- const parentElement = parentEntity.elements[parentElementName]
181
- if (
182
- parentElement.isComposition &&
183
- parentElement.target === entityName &&
184
- !(parentElement.parent && ensureNoDraftsSuffix(parentElement.parent.name) === entityName)
185
- ) {
186
- return false
187
- }
188
- }
189
- }
190
- return true
191
- }
192
-
193
166
  const _setAlias2ref = entity => {
194
167
  const _ref2alias = {}
195
168
  const _alias2ref = {}
@@ -270,7 +243,6 @@ module.exports = {
270
243
  getEtagElement,
271
244
  findCsnTargetFor,
272
245
  getElementDeep,
273
- isRootEntity,
274
246
  getDataSubject,
275
247
  alias2ref,
276
248
  getComp2oneParents,
@@ -1,4 +1,5 @@
1
1
  const cds = require('../../cds')
2
+ const { DRAFT_COLUMNS_MAP } = require('../../common/constants/draft')
2
3
 
3
4
  const _4sqlite = cds.env.i18n && Array.isArray(cds.env.i18n.for_sqlite) ? cds.env.i18n.for_sqlite : []
4
5
  // compiler reserves 'localized' and raises a corresponding exception if used in models
@@ -65,10 +66,16 @@ const getDraftColumnsCQNForDraft = () => {
65
66
  ]
66
67
  }
67
68
 
69
+ const filterNonDraftColumns = columns =>
70
+ columns.filter(
71
+ col => (col.ref && !(col.ref[col.ref.length - 1] in DRAFT_COLUMNS_MAP)) || (!col.ref && !(col in DRAFT_COLUMNS_MAP))
72
+ )
73
+
68
74
  module.exports = {
69
75
  ensureUnlocalized,
70
76
  ensureDraftsSuffix,
71
77
  ensureNoDraftsSuffix,
72
78
  getDraftColumnsCQNForActive,
73
- getDraftColumnsCQNForDraft
79
+ getDraftColumnsCQNForDraft,
80
+ filterNonDraftColumns
74
81
  }
@@ -1,4 +1,5 @@
1
1
  const cds = require('../../cds')
2
+ const { ensureNoDraftsSuffix } = require('./draft')
2
3
 
3
4
  /*
4
5
  * returns path like <service>.<entity>:<prop1>.<prop2> for ref = [{ id: '<service>.<entity>' }, '<prop1>', '<prop2>']
@@ -22,7 +23,12 @@ const getEntityFromPath = (path, def) => {
22
23
  path = typeof path === 'string' ? cds.parse.path(path) : path
23
24
  const segments = [...path.ref]
24
25
  while (segments.length) {
25
- const segment = segments.shift()
26
+ let segment = segments.shift()
27
+ if (segment.id && typeof segment.id === 'string') {
28
+ segment.id = ensureNoDraftsSuffix(segment.id)
29
+ } else if (typeof segment === 'string') {
30
+ segment = ensureNoDraftsSuffix(segment)
31
+ }
26
32
  current = current.elements[segment.id || segment]
27
33
  if (current && current.target) current = current._target
28
34
  }
@@ -501,9 +501,8 @@ const _checkForForbiddenViews = queryTarget => {
501
501
  if (!select.from || select.from.join || select.from.length > 1) {
502
502
  throw getError({
503
503
  code: 501,
504
- message: 'NON_WRITABLE_VIEW',
505
- target: queryTarget.name,
506
- args: [_event || 'INSERT|UPDATE|DELETE']
504
+ message: `${_event || 'INSERT|UPDATE|DELETE'} on views with join and/or union is not supported`,
505
+ target: queryTarget.name
507
506
  })
508
507
  }
509
508
  if (select.where) {
@@ -3,55 +3,14 @@ const { ensureNoDraftsSuffix } = require('../../common/utils/draft')
3
3
  const { proxifyIfFlattened } = require('../../../common/utils/ucsn')
4
4
  const cds = require('../../../../lib')
5
5
 
6
- const _refs = (refs, as) => {
7
- const arr = []
8
- for (const element of refs) {
9
- // multiple join are nested, so we need to find all the table names in there as well
10
- if (Object.prototype.hasOwnProperty.call(element, 'join')) {
11
- arr.push(..._extractRefs(element))
12
- // Likely a union
13
- } else if (Object.prototype.hasOwnProperty.call(element, 'SELECT')) {
14
- arr.push(..._extractRefs(element.SELECT.from, element.as))
15
- } else {
16
- arr.push(..._extractRefs(element, as))
17
- }
18
- }
19
-
20
- return arr
21
- }
22
-
23
- const _getActiveFromUnion = refs => {
24
- if (refs.length !== 2) return
25
- const [maybeDraft, maybeActive] = refs
26
- if (ensureNoDraftsSuffix(maybeDraft.ref[0]) === maybeActive.ref[0]) return maybeActive
27
- if (ensureNoDraftsSuffix(maybeActive.ref[0]) === maybeDraft.ref[0]) return maybeDraft
28
- }
29
-
30
- const _extractRefs = (from, as) => {
31
- if (from.SELECT) {
32
- return _extractRefs(from.SELECT.from, as || from.SELECT.as)
33
- }
34
- if (Object.prototype.hasOwnProperty.call(from, 'join')) {
35
- // cqn with join in from
36
- return _refs(from.args)
37
- }
38
- if (Object.prototype.hasOwnProperty.call(from, 'SET')) {
39
- let refs = _refs(from.SET.args).filter(a => !a.as || a.as !== 'filterAdmin')
40
- refs = _getActiveFromUnion(refs) ? [_getActiveFromUnion(refs)] : refs
41
- if (as) return refs.map(({ ref }) => ({ as, ref }))
42
- return refs
43
- }
44
- if (!from.ref) return []
45
- const ref = { ref: [...from.ref] }
46
- if (as || from.as) ref.as = as || from.as
47
- return [ref]
48
- }
49
-
50
6
  const _getCastFunction = ({ type }) => {
51
7
  switch (type) {
52
8
  case 'cds.Boolean':
53
9
  return Boolean
54
10
  case 'cds.Integer':
11
+ case 'cds.UInt8':
12
+ case 'cds.Int16':
13
+ case 'cds.Int32':
55
14
  return Number
56
15
  default:
57
16
  return String
@@ -197,7 +197,7 @@ const _pickDraft = element => {
197
197
  if (categories.length) return { categories }
198
198
  }
199
199
 
200
- function _handler(req) {
200
+ function dbGenericInput(req) {
201
201
  if (!this.model || typeof req.query === 'string' || !req.target) return
202
202
 
203
203
  // call with this for this.model
@@ -231,6 +231,6 @@ function _handler(req) {
231
231
  }
232
232
  }
233
233
 
234
- _handler._initial = true
234
+ dbGenericInput._initial = true
235
235
 
236
- module.exports = _handler
236
+ module.exports = dbGenericInput
@@ -3,7 +3,11 @@ const typeConversionMap = new Map()
3
3
  typeConversionMap.set('cds.UUID', { type: 'NVARCHAR', length: 36 })
4
4
  typeConversionMap.set('cds.Boolean', 'BOOLEAN')
5
5
  typeConversionMap.set('cds.Integer', 'INTEGER')
6
+ typeConversionMap.set('cds.UInt8', 'INTEGER')
7
+ typeConversionMap.set('cds.Int16', 'INTEGER')
8
+ typeConversionMap.set('cds.Int32', 'INTEGER')
6
9
  typeConversionMap.set('cds.Integer64', 'BIGINT')
10
+ typeConversionMap.set('cds.Int64', 'BIGINT')
7
11
  typeConversionMap.set('cds.Decimal', { type: 'DECIMAL' })
8
12
  typeConversionMap.set('cds.DecimalFloat', { type: 'DECIMAL' })
9
13
  typeConversionMap.set('cds.Double', 'DOUBLE')
@@ -106,7 +106,7 @@ const _draftCompositionTree = async (service, req) => {
106
106
  *
107
107
  * @param req
108
108
  */
109
- const _handler = async function (req) {
109
+ const fioriGenericActivate = async function (req) {
110
110
  if (
111
111
  isActiveEntityRequested(req.query.SELECT.from.ref[0].where || []) ||
112
112
  req.query.SELECT.from.ref.length > 2 ||
@@ -178,5 +178,5 @@ const _handler = async function (req) {
178
178
  }
179
179
 
180
180
  module.exports = cds.service.impl(function (srv, entity) {
181
- srv.on('draftActivate', entity, _handler)
181
+ srv.on('draftActivate', entity, fioriGenericActivate)
182
182
  })
@@ -3,30 +3,14 @@ const cds = require('../../cds')
3
3
  const { SELECT } = cds.ql
4
4
 
5
5
  const { isNavigationToMany } = require('../utils/req')
6
- const { getKeysCondition } = require('../utils/where')
6
+ const { getKeysCondition, removeIsActiveEntityRecursively } = require('../utils/where')
7
7
  const { isDraftActivateAction, ensureNoDraftsSuffix, ensureDraftsSuffix, draftIsLocked } = require('../utils/handler')
8
8
 
9
- const { isCustomOperation } = require('../../cds-services/adapter/odata-v4/utils/request')
10
-
11
9
  const { DRAFT_COLUMNS_ADMIN_MAP } = require('../../common/constants/draft')
10
+ const { deepCopyArray } = require('../../common/utils/copy')
12
11
  const DRAFT_COLUMNS_ADMIN = Object.keys(DRAFT_COLUMNS_ADMIN_MAP)
13
12
  const PREFIX_DRAFT_COLUMNS = DRAFT_COLUMNS_ADMIN.map(col => ({ ref: ['DRAFT_DraftAdministrativeData', col] }))
14
13
 
15
- // copied from adapter/odata-v4/utils/context-object
16
- const _getTargetEntityName = (service, pathSegments) => {
17
- if (isCustomOperation(pathSegments, false)) return
18
-
19
- let navSegmentName
20
- let entityName = `${service.name}.${pathSegments[0].getEntitySet().getName()}`
21
-
22
- for (const navSegment of pathSegments.filter(segment => segment.getNavigationProperty() !== null)) {
23
- navSegmentName = navSegment.getNavigationProperty().getName()
24
- entityName = service.model.definitions[entityName].elements[navSegmentName].target
25
- }
26
-
27
- return entityName
28
- }
29
-
30
14
  /**
31
15
  * Provide information about the parent entity, i.e. the entity that has the to-many composition element.
32
16
  * Limitation: only works for one key (besides IsActiveEntity)
@@ -36,34 +20,6 @@ const _getTargetEntityName = (service, pathSegments) => {
36
20
  * @returns {object}
37
21
  * @private
38
22
  */
39
- const _getParent = (req, service) => {
40
- // REVISIT: get rid of getUriInfo
41
- if (!req.getUriInfo) return
42
-
43
- const segments = req.getUriInfo().getPathSegments()
44
- if (segments.length === 1) return
45
-
46
- const parent = {
47
- entityName: _getTargetEntityName(service, segments.slice(0, segments.length - 1))
48
- }
49
-
50
- const parentKeyPredicates = segments[segments.length - 2].getKeyPredicates()
51
- let keyPredicateName, keyPredicateText
52
-
53
- for (const keyPredicate of parentKeyPredicates) {
54
- keyPredicateName = keyPredicate.getEdmRef().getName()
55
- keyPredicateText = keyPredicate.getText()
56
-
57
- if (keyPredicateName === 'IsActiveEntity') {
58
- parent.IsActiveEntity = keyPredicateText === 'true'
59
- } else {
60
- parent.keyName = keyPredicateName
61
- parent.keyValue = keyPredicateText
62
- }
63
- }
64
-
65
- return parent
66
- }
67
23
 
68
24
  const _validateDraft = (req, draftResult, isBoundAction) => {
69
25
  if (!draftResult || draftResult.length === 0) req.reject(404)
@@ -102,36 +58,48 @@ const _getSelectDraftDataCqn = (entityName, where) => {
102
58
  .where(where)
103
59
  }
104
60
 
105
- const _getDraftDataFromExistingDraft = async (req, service, parent = _getParent(req, service)) => {
106
- if (parent) {
107
- if (parent.IsActiveEntity === false) {
108
- const parentWhere = [{ ref: [parent.keyName] }, '=', { val: parent.keyValue }]
109
- const query = _getSelectDraftDataCqn(parent.entityName, parentWhere)
110
- const result = await cds.tx(req).run(query)
111
- return result
61
+ const _getRoot = req => {
62
+ if (!req.query) return
63
+ const refObj = req.query.SELECT?.from || req.query.UPDATE?.entity || req.query.INSERT?.into || req.query.DELETE?.from
64
+ const root = {
65
+ entityName: ensureDraftsSuffix(refObj.ref[0].id),
66
+ where: removeIsActiveEntityRecursively(deepCopyArray(refObj.ref[0].where))
67
+ }
68
+
69
+ for (const item of refObj.ref[0].where) {
70
+ if (item.ref && item.ref[item.ref.length - 1] === 'IsActiveEntity') {
71
+ const index = refObj.ref[0].where.indexOf(item)
72
+ root.IsActiveEntity = refObj.ref[0].where[index + 2].val
73
+ break
112
74
  }
75
+ }
76
+
77
+ return root
78
+ }
113
79
 
114
- return []
80
+ const _getDraftDataFromExistingDraft = async (req, root) => {
81
+ if (!root) return []
82
+ if (root?.IsActiveEntity === false) {
83
+ const query = _getSelectDraftDataCqn(root.entityName, root.where)
84
+ const result = await cds.tx(req).run(query)
85
+ return result
115
86
  }
116
87
 
117
- const rootWhere = getKeysCondition(req.target, req.data)
88
+ const rootWhere = getKeysCondition(req)
118
89
  const query = _getSelectDraftDataCqn(ensureNoDraftsSuffix(req.target.name), rootWhere)
119
90
  const result = await cds.tx(req).run(query)
120
91
  return result
121
92
  }
122
93
 
123
- const _addDraftDataFromExistingDraft = async (req, service) => {
124
- const parent = _getParent(req, service)
125
- const result = await _getDraftDataFromExistingDraft(req, service, parent)
126
-
127
- if (parent) {
128
- if (parent.IsActiveEntity === false) {
129
- _validateDraft(req, result)
130
- _addDraftDataToContext(req, result)
131
- return result
132
- }
94
+ const _addDraftDataFromExistingDraft = async req => {
95
+ const root = _getRoot(req)
96
+ const result = await _getDraftDataFromExistingDraft(req, root)
133
97
 
134
- return []
98
+ if (!root) return []
99
+ if (root.IsActiveEntity === false) {
100
+ _validateDraft(req, result)
101
+ _addDraftDataToContext(req, result)
102
+ return result
135
103
  }
136
104
 
137
105
  if (result && result.length > 0) _validateDraft(req, result)
@@ -147,7 +115,7 @@ const _new = async function (req) {
147
115
 
148
116
  if (isNavigationToMany(req)) {
149
117
  // REVISIT: How can NEW be a navigation to many?
150
- const result = await _addDraftDataFromExistingDraft(req, this)
118
+ const result = await _addDraftDataFromExistingDraft(req)
151
119
 
152
120
  // in order to fix corner case where active subitems are created in draft case
153
121
  if (result.length === 0) req.reject(404)
@@ -165,7 +133,7 @@ const _new = async function (req) {
165
133
  const _patchUpdate = async function (req) {
166
134
  if (isDraftActivateAction(req)) return
167
135
 
168
- const result = await _addDraftDataFromExistingDraft(req, this)
136
+ const result = await _addDraftDataFromExistingDraft(req)
169
137
 
170
138
  // means that the draft does not exists
171
139
  if (result.length === 0) req.reject(404)
@@ -175,11 +143,11 @@ const _patchUpdate = async function (req) {
175
143
  * Generic Handler for before DELETE and CANCEL requests.
176
144
  */
177
145
  const _deleteCancel = async function (req) {
178
- await _addDraftDataFromExistingDraft(req, this)
146
+ await _addDraftDataFromExistingDraft(req)
179
147
  }
180
148
 
181
- const _validateDraftBoundAction = async function (req, srv) {
182
- const result = await _getDraftDataFromExistingDraft(req, srv)
149
+ const _validateDraftBoundAction = async function (req) {
150
+ const result = await _getDraftDataFromExistingDraft(req, _getRoot(req))
183
151
  const isBoundAction = true
184
152
  if (result && result.length > 0) _validateDraft(req, result, isBoundAction)
185
153
  }
@@ -196,7 +164,7 @@ const _registerBoundActionHandlers = function (entityName, actions) {
196
164
  )
197
165
 
198
166
  for (const action of boundActions) {
199
- this.before(action.name, entityName, req => _validateDraftBoundAction(req, this))
167
+ this.before(action.name, entityName, req => _validateDraftBoundAction(req))
200
168
  }
201
169
  }
202
170
 
@@ -8,10 +8,10 @@ const { deleteDraft } = require('../utils/delete')
8
8
  *
9
9
  * @param req
10
10
  */
11
- const _handler = function (req) {
11
+ const fioriGenericCancel = function (req) {
12
12
  return deleteDraft(req, this)
13
13
  }
14
14
 
15
15
  module.exports = cds.service.impl(function (srv, entity) {
16
- srv.on('CANCEL', entity, _handler)
16
+ srv.on('CANCEL', entity, fioriGenericCancel)
17
17
  })
@@ -8,12 +8,12 @@ const { deleteDraft } = require('../utils/delete')
8
8
  *
9
9
  * @param req
10
10
  */
11
- const _handler = function (req) {
11
+ const fioriGenericDelete = function (req) {
12
12
  // we should call deleteDraft only for draft tables and call next
13
13
  // to pass delete of active tables to our general implementation
14
14
  return deleteDraft(req, this, true)
15
15
  }
16
16
 
17
17
  module.exports = cds.service.impl(function (srv, entity) {
18
- srv.on('DELETE', entity, _handler)
18
+ srv.on('DELETE', entity, fioriGenericDelete)
19
19
  })
@@ -68,7 +68,7 @@ const _select = async (lockRecordCQN, draftExistsCQN, selectCQNs, req, dbtx) =>
68
68
  *
69
69
  * @param req
70
70
  */
71
- const _handler = async function (req) {
71
+ const fioriGenericEdit = async function (req) {
72
72
  if (!isActiveEntityRequested(req.query.SELECT.where || [])) {
73
73
  req.reject(400)
74
74
  }
@@ -160,5 +160,5 @@ const _handler = async function (req) {
160
160
  }
161
161
 
162
162
  module.exports = cds.service.impl(function (srv, entity) {
163
- srv.on('EDIT', entity, _handler)
163
+ srv.on('EDIT', entity, fioriGenericEdit)
164
164
  })
@@ -48,7 +48,7 @@ const _getInsertDataCQN = (req, draftUUID) => {
48
48
  * @param req
49
49
  * @param next
50
50
  */
51
- const _handler = async function (req, next) {
51
+ const fioriGenericNew = async function (req, next) {
52
52
  if (!req._draftMetadata) {
53
53
  // REVISIT: when is this the case?
54
54
  return onDraftActivate(req, next)
@@ -70,5 +70,5 @@ const _handler = async function (req, next) {
70
70
  }
71
71
 
72
72
  module.exports = cds.service.impl(function (srv, entity) {
73
- srv.on('NEW', entity, _handler)
73
+ srv.on('NEW', entity, fioriGenericNew)
74
74
  })