@sap/cds 6.8.4 → 7.0.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 (217) hide show
  1. package/CHANGELOG.md +66 -4
  2. package/README.md +0 -1
  3. package/bin/cds-serve.js +50 -3
  4. package/bin/deploy/to-hana.js +1 -0
  5. package/bin/serve.js +16 -20
  6. package/lib/auth/basic-auth.js +6 -4
  7. package/lib/auth/index.js +4 -3
  8. package/lib/auth/jwt-auth.js +2 -5
  9. package/lib/compile/cds-compile.js +34 -89
  10. package/lib/compile/cdsc.js +11 -0
  11. package/lib/compile/etc/properties.js +2 -2
  12. package/lib/compile/for/lean_drafts.js +36 -69
  13. package/lib/compile/for/nodejs.js +2 -1
  14. package/lib/compile/load.js +3 -3
  15. package/lib/compile/minify.js +2 -0
  16. package/lib/compile/to/csn.js +74 -0
  17. package/{bin/build/provider/hana/2tabledata.js → lib/compile/to/hdbtabledata.js} +4 -6
  18. package/lib/compile/to/json.js +1 -1
  19. package/lib/compile/to/sql.js +8 -6
  20. package/lib/dbs/cds-deploy.js +174 -114
  21. package/lib/env/cds-env.js +64 -79
  22. package/lib/env/cds-requires.js +11 -28
  23. package/lib/env/defaults.js +13 -3
  24. package/lib/env/plugins.js +1 -12
  25. package/lib/env/presets.js +25 -21
  26. package/lib/index.js +121 -147
  27. package/lib/{core/reflect.js → linked/models.js} +2 -2
  28. package/lib/{core/infer.js → linked/queries.js} +2 -0
  29. package/lib/{core/index.js → linked/types.js} +2 -1
  30. package/lib/log/cds-error.js +13 -7
  31. package/lib/log/format/cf.js +1 -1
  32. package/lib/plugins.js +49 -0
  33. package/lib/ql/Query.js +0 -9
  34. package/lib/ql/STREAM.js +0 -1
  35. package/lib/req/context.js +2 -7
  36. package/lib/req/request.js +6 -2
  37. package/lib/req/response.js +23 -10
  38. package/lib/srv/middlewares/ctx-model.js +2 -2
  39. package/lib/srv/middlewares/errors.js +1 -1
  40. package/lib/srv/protocols/_legacy.js +1 -0
  41. package/lib/srv/protocols/graphql.js +7 -16
  42. package/lib/srv/protocols/index.js +59 -45
  43. package/lib/srv/protocols/odata-v2-proxy.js +2 -70
  44. package/lib/srv/protocols/odata-v4.js +9 -4
  45. package/lib/srv/srv-api.js +9 -3
  46. package/lib/srv/srv-dispatch.js +12 -9
  47. package/lib/srv/srv-models.js +4 -21
  48. package/lib/srv/srv-tx.js +15 -12
  49. package/lib/utils/cds-test.js +14 -9
  50. package/lib/utils/cds-utils.js +2 -12
  51. package/lib/utils/check-version.js +17 -0
  52. package/{bin/build → lib/utils}/csv-reader.js +23 -24
  53. package/libx/_runtime/auth/index.js +27 -23
  54. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +15 -72
  55. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -1
  56. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +0 -2
  57. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +33 -63
  58. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +14 -18
  59. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +15 -5
  60. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +5 -4
  61. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +37 -40
  62. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/updateToCQN.js +7 -1
  63. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +101 -38
  64. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/errors/AbstractError.js +5 -1
  65. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriTokenizer.js +5 -8
  66. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +2 -1
  67. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +9 -8
  68. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
  69. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +15 -11
  70. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +4 -0
  71. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +5 -2
  72. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +1 -123
  73. package/libx/_runtime/cds-services/services/Service.js +79 -107
  74. package/libx/_runtime/cds-services/services/utils/columns.js +23 -19
  75. package/libx/_runtime/cds-services/services/utils/compareJson.js +11 -1
  76. package/libx/_runtime/cds-services/services/utils/differ.js +7 -2
  77. package/libx/_runtime/cds-services/util/assert.js +65 -2
  78. package/libx/_runtime/common/composition/data.js +1 -0
  79. package/libx/_runtime/common/generic/auth/expand.js +1 -1
  80. package/libx/_runtime/common/generic/auth/restrict.js +5 -10
  81. package/libx/_runtime/common/generic/auth/restrictions.js +40 -0
  82. package/libx/_runtime/common/generic/auth/utils.js +1 -2
  83. package/libx/_runtime/common/generic/crud.js +32 -16
  84. package/libx/_runtime/common/generic/etag.js +133 -104
  85. package/libx/_runtime/common/generic/input.js +6 -21
  86. package/libx/_runtime/common/generic/put.js +1 -1
  87. package/libx/_runtime/common/generic/stream.js +52 -0
  88. package/libx/_runtime/common/generic/temporal.js +25 -8
  89. package/libx/_runtime/common/i18n/messages.properties +0 -2
  90. package/libx/_runtime/common/utils/cqn.js +1 -1
  91. package/libx/_runtime/common/utils/cqn2cqn4sql.js +5 -2
  92. package/libx/_runtime/common/utils/csn.js +0 -51
  93. package/libx/_runtime/common/utils/etag.js +30 -0
  94. package/libx/_runtime/common/utils/keys.js +1 -1
  95. package/libx/_runtime/common/utils/normalizeTimestamp.js +25 -0
  96. package/libx/_runtime/common/utils/path.js +1 -1
  97. package/libx/_runtime/common/utils/resolveView.js +2 -1
  98. package/libx/_runtime/common/utils/rewriteAsterisks.js +6 -4
  99. package/libx/_runtime/common/utils/search2cqn4sql.js +12 -16
  100. package/libx/_runtime/common/utils/stream.js +140 -0
  101. package/libx/_runtime/common/utils/streamProp.js +29 -12
  102. package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +0 -2
  103. package/libx/_runtime/db/generic/index.js +0 -2
  104. package/libx/_runtime/db/query/delete.js +2 -2
  105. package/libx/_runtime/db/query/insert.js +2 -2
  106. package/libx/_runtime/db/query/read.js +2 -2
  107. package/libx/_runtime/db/query/run.js +2 -2
  108. package/libx/_runtime/db/query/update.js +2 -2
  109. package/libx/_runtime/db/sql-builder/BaseBuilder.js +0 -6
  110. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +23 -12
  111. package/libx/_runtime/db/sql-builder/FunctionBuilder.js +18 -6
  112. package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -0
  113. package/libx/_runtime/db/sql-builder/SelectBuilder.js +3 -7
  114. package/libx/_runtime/db/sql-builder/UpsertBuilder.js +1 -0
  115. package/libx/_runtime/db/utils/normalizeTimeData.js +7 -3
  116. package/libx/_runtime/fiori/draft.js +2 -0
  117. package/libx/_runtime/fiori/generic/activate.js +8 -9
  118. package/libx/_runtime/fiori/generic/before.js +30 -20
  119. package/libx/_runtime/fiori/generic/cancel.js +5 -3
  120. package/libx/_runtime/fiori/generic/delete.js +5 -3
  121. package/libx/_runtime/fiori/generic/edit.js +7 -7
  122. package/libx/_runtime/fiori/generic/index.js +10 -16
  123. package/libx/_runtime/fiori/generic/new.js +5 -3
  124. package/libx/_runtime/fiori/generic/patch.js +11 -8
  125. package/libx/_runtime/fiori/generic/prepare.js +13 -6
  126. package/libx/_runtime/fiori/generic/read.js +12 -6
  127. package/libx/_runtime/fiori/lean-draft.js +207 -152
  128. package/libx/_runtime/fiori/utils/delete.js +10 -5
  129. package/libx/_runtime/fiori/utils/req.js +17 -5
  130. package/libx/_runtime/fiori/utils/stream.js +36 -0
  131. package/libx/_runtime/hana/Service.js +12 -9
  132. package/libx/_runtime/hana/conversion.js +10 -15
  133. package/libx/_runtime/hana/driver.js +2 -0
  134. package/libx/_runtime/hana/execute.js +28 -6
  135. package/libx/_runtime/hana/pool.js +36 -122
  136. package/libx/_runtime/hana/search2cqn4sql.js +34 -36
  137. package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +2 -6
  138. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +3 -1
  139. package/libx/_runtime/messaging/enterprise-messaging.js +10 -58
  140. package/libx/_runtime/messaging/outbox/utils.js +1 -1
  141. package/libx/_runtime/remote/Service.js +20 -1
  142. package/libx/_runtime/remote/utils/client.js +3 -5
  143. package/libx/_runtime/sqlite/Service.js +4 -6
  144. package/libx/_runtime/sqlite/conversion.js +3 -13
  145. package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +9 -6
  146. package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +6 -1
  147. package/libx/_runtime/sqlite/execute.js +5 -16
  148. package/libx/odata/afterburner.js +22 -6
  149. package/libx/odata/grammar.pegjs +6 -1
  150. package/libx/odata/parser.js +1 -1
  151. package/libx/rest/RestAdapter.js +16 -9
  152. package/libx/rest/RestRequest.js +1 -1
  153. package/libx/rest/middleware/input.js +2 -1
  154. package/libx/rest/middleware/operation.js +1 -0
  155. package/libx/rest/middleware/parse.js +3 -2
  156. package/libx/rest/middleware/payload.js +9 -8
  157. package/libx/rest/middleware/read.js +1 -0
  158. package/package.json +9 -16
  159. package/server.js +1 -1
  160. package/app/fiori/preview.js +0 -270
  161. package/app/fiori/routes.js +0 -59
  162. package/bin/build/buildTaskEngine.js +0 -360
  163. package/bin/build/buildTaskFactory.js +0 -283
  164. package/bin/build/buildTaskHandler.js +0 -241
  165. package/bin/build/buildTaskProvider.js +0 -22
  166. package/bin/build/buildTaskProviderFactory.js +0 -175
  167. package/bin/build/cds.js +0 -5
  168. package/bin/build/constants.js +0 -66
  169. package/bin/build/index.js +0 -58
  170. package/bin/build/provider/buildTaskHandlerEdmx.js +0 -82
  171. package/bin/build/provider/buildTaskHandlerFeatureToggles.js +0 -131
  172. package/bin/build/provider/buildTaskHandlerInternal.js +0 -254
  173. package/bin/build/provider/buildTaskProviderInternal.js +0 -383
  174. package/bin/build/provider/fiori/index.js +0 -171
  175. package/bin/build/provider/hana/2migration.js +0 -179
  176. package/bin/build/provider/hana/index.js +0 -505
  177. package/bin/build/provider/hana/migrationtable.js +0 -472
  178. package/bin/build/provider/hana/template/.hdiconfig-haas +0 -163
  179. package/bin/build/provider/hana/template/.hdiconfig-hanacloud +0 -137
  180. package/bin/build/provider/hana/template/.hdinamespace +0 -4
  181. package/bin/build/provider/hana/template/package.json +0 -12
  182. package/bin/build/provider/hana/template/undeploy.json +0 -5
  183. package/bin/build/provider/java/index.js +0 -111
  184. package/bin/build/provider/java-cf/index.js +0 -1
  185. package/bin/build/provider/mtx/index.js +0 -268
  186. package/bin/build/provider/mtx/resourcesTarBuilder.js +0 -95
  187. package/bin/build/provider/mtx-extension/index.js +0 -131
  188. package/bin/build/provider/mtx-sidecar/index.js +0 -137
  189. package/bin/build/provider/node-cf/index.js +0 -1
  190. package/bin/build/provider/nodejs/index.js +0 -192
  191. package/bin/build/util.js +0 -299
  192. package/bin/cds.js +0 -125
  193. package/bin/deploy/to-hana/cfUtil.js +0 -355
  194. package/bin/deploy/to-hana/gitUtil.js +0 -57
  195. package/bin/deploy/to-hana/hana.js +0 -306
  196. package/bin/deploy/to-hana/hdiDeployUtil.js +0 -153
  197. package/bin/deploy/to-hana/index.js +0 -16
  198. package/bin/deploy/to-hana/mtaUtil.js +0 -170
  199. package/bin/mtx/in-cds.js +0 -17
  200. package/bin/plugins.js +0 -32
  201. package/bin/run.js +0 -24
  202. package/bin/utils/log.js +0 -24
  203. package/bin/version.js +0 -178
  204. package/libx/_runtime/audit/Service.js +0 -222
  205. package/libx/_runtime/audit/generic/personal/access.js +0 -61
  206. package/libx/_runtime/audit/generic/personal/index.js +0 -56
  207. package/libx/_runtime/audit/generic/personal/modification.js +0 -132
  208. package/libx/_runtime/audit/generic/personal/utils.js +0 -186
  209. package/libx/_runtime/audit/utils/log.js +0 -23
  210. package/libx/_runtime/audit/utils/v2.js +0 -176
  211. package/libx/_runtime/db/data-conversion/timestamp.js +0 -9
  212. package/libx/_runtime/db/generic/integrity.js +0 -455
  213. package/srv/audit-log.cds +0 -87
  214. package/srv/mtx.cds +0 -2
  215. package/srv/mtx.js +0 -8
  216. /package/lib/{core → linked}/classes.js +0 -0
  217. /package/lib/{core → linked}/entities.js +0 -0
@@ -69,6 +69,11 @@ class FunctionBuilder extends BaseBuilder {
69
69
  const functionName = this._functionName()
70
70
  const args = this._functionArgs()
71
71
 
72
+ if (this._obj.func === 'indexof') {
73
+ this._handleIndexof(args, functionName)
74
+ return
75
+ }
76
+
72
77
  if (!args) {
73
78
  // > arg-less func such as current_date
74
79
  this._outputObj.sql.push(functionName)
@@ -114,6 +119,17 @@ class FunctionBuilder extends BaseBuilder {
114
119
  this._handleLikewiseFunc(args)
115
120
  }
116
121
 
122
+ _handleIndexof(args, funcName) {
123
+ this._outputObj.sql.push('(')
124
+ this._outputObj.sql.push(funcName, '(')
125
+ if (typeof args === 'string') this._outputObj.sql.push(args)
126
+ else this._addFunctionArgs(args)
127
+ this._outputObj.sql.push(')')
128
+ this._outputObj.sql.push('-')
129
+ this._outputObj.sql.push('1')
130
+ this._outputObj.sql.push(')')
131
+ }
132
+
117
133
  _handleLikewiseFunc(args) {
118
134
  const functionName = this._functionName()
119
135
  const not = functionName.startsWith('not') ? 'NOT ' : ''
@@ -193,12 +209,8 @@ class FunctionBuilder extends BaseBuilder {
193
209
  res.push(sql)
194
210
  this._outputObj.values.push(...values)
195
211
  } else if (Object.prototype.hasOwnProperty.call(arg, 'val')) {
196
- if (typeof arg.val === 'number' && !this._parameterizedNumbers) {
197
- res.push(arg.val)
198
- } else {
199
- res.push(this._options.placeholder)
200
- this._outputObj.values.push(arg.val)
201
- }
212
+ res.push(this._options.placeholder)
213
+ this._outputObj.values.push(arg.val)
202
214
  } else if (arg.list) {
203
215
  this._addFunctionArgs(arg.list, true)
204
216
  // _addFunctionArgs adds the arguments list already to the output object
@@ -259,6 +259,7 @@ class InsertBuilder extends BaseBuilder {
259
259
  _getValue(column, { entry, flattenColumn, insertAnnotatedColumns }) {
260
260
  let val = entry
261
261
  if (!flattenColumn && this.uuidKeys.includes(column)) {
262
+ if (this._UPSERT) return undefined
262
263
  val = cds.utils.uuid()
263
264
  } else {
264
265
  for (const key of flattenColumn) {
@@ -413,7 +413,7 @@ class SelectBuilder extends BaseBuilder {
413
413
  _addRows() {
414
414
  if (this._obj.SELECT.limit) {
415
415
  if (this._obj.SELECT.limit.rows !== undefined) {
416
- // limit (no placeholder for statement caching)
416
+ // limit (no placeholder for statement caching) - hana does not know how to optimize
417
417
  this._outputObj.sql.push('LIMIT', this._obj.SELECT.limit.rows.val)
418
418
  } else {
419
419
  // rows parameter is mandatory for SQL
@@ -429,12 +429,8 @@ class SelectBuilder extends BaseBuilder {
429
429
  _addOffset() {
430
430
  // offset
431
431
  if (this._obj.SELECT.limit && this._obj.SELECT.limit.offset !== undefined) {
432
- if (typeof this._obj.SELECT.limit.offset.val === 'number' && !this._parameterizedNumbers) {
433
- this._outputObj.sql.push('OFFSET', this._obj.SELECT.limit.offset.val)
434
- } else {
435
- this._outputObj.sql.push('OFFSET', '?')
436
- this._outputObj.values.push(this._obj.SELECT.limit.offset.val)
437
- }
432
+ this._outputObj.sql.push('OFFSET', '?')
433
+ this._outputObj.values.push(this._obj.SELECT.limit.offset.val)
438
434
  }
439
435
  }
440
436
 
@@ -4,6 +4,7 @@ const getAnnotatedColumns = require('./annotations')
4
4
  class UpsertBuilder extends InsertBuilder {
5
5
  constructor(obj, options, csn) {
6
6
  super(obj, options, csn)
7
+ this._UPSERT = true
7
8
  }
8
9
 
9
10
  annotatedColumns(entityName, csn) {
@@ -1,11 +1,15 @@
1
1
  const cds = require('../../cds')
2
2
 
3
3
  const { ensureNoDraftsSuffix } = require('../../common/utils/draft.js')
4
+ const normalizeTimestamp = require('../../common/utils/normalizeTimestamp')
4
5
 
5
6
  const _convertDateTimeElement = (value, element) => {
6
- value = new Date(value).toISOString()
7
- if (element._type === 'cds.DateTime') value = value.replace(/\.\d\d\d/, '')
8
- return value
7
+ switch (element._type) {
8
+ case 'cds.DateTime':
9
+ return new Date(value).toISOString().replace(/\.\d\d\d/, '')
10
+ case 'cds.Timestamp':
11
+ return normalizeTimestamp(value)
12
+ }
9
13
  }
10
14
 
11
15
  const _isToConvert = type => type === 'cds.DateTime' || type === 'cds.Timestamp'
@@ -0,0 +1,2 @@
1
+ const cds = require('../cds')
2
+ module.exports = cds.env.fiori.lean_draft ? require('./lean-draft') : require('./generic')
@@ -106,7 +106,9 @@ const _draftCompositionTree = async (service, req) => {
106
106
  *
107
107
  * @param req
108
108
  */
109
- const fioriGenericActivate = async function (req) {
109
+ const fioriGenericActivate = async function (req, next) {
110
+ if (!req.target._isDraftEnabled) return next()
111
+
110
112
  if (
111
113
  isActiveEntityRequested(req.query.SELECT.from.ref[0].where || []) ||
112
114
  req.query.SELECT.from.ref.length > 2 ||
@@ -120,10 +122,9 @@ const fioriGenericActivate = async function (req) {
120
122
  const { draftData, activeData, adminData } = await _draftCompositionTree(this, req)
121
123
 
122
124
  if (!draftData) req.reject(404)
123
- if (adminData.InProcessByUser !== req.user.id) {
124
- // REVISIT: security log?
125
+
126
+ if (adminData.InProcessByUser !== req.user.id)
125
127
  req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER', [adminData.InProcessByUser])
126
- }
127
128
 
128
129
  /*
129
130
  * create or update
@@ -146,8 +147,6 @@ const fioriGenericActivate = async function (req) {
146
147
 
147
148
  // REVISIT: should not be necessary
148
149
  r._ = Object.assign(r._, req._)
149
- if (req.getUriInfo) r.getUriInfo = () => req.getUriInfo()
150
- if (req.getUrlObject) r.getUrlObject = () => req.getUrlObject()
151
150
  r._.params = req.params
152
151
  r._.query = req.query
153
152
 
@@ -178,11 +177,11 @@ const fioriGenericActivate = async function (req) {
178
177
 
179
178
  // REVISIT: we need to use okra API here because it must be set in the batched request
180
179
  // status code must be set in handler to allow overriding for FE V2
181
- req?._?.odataRes?.setStatusCode(201)
180
+ if (event === 'CREATE') req?._?.odataRes?.setStatusCode(201, { overwrite: true })
182
181
 
183
182
  return result
184
183
  }
185
184
 
186
- module.exports = cds.service.impl(function (srv, entity) {
187
- srv.on('draftActivate', entity, fioriGenericActivate)
185
+ module.exports = cds.service.impl(function (srv) {
186
+ srv.on('draftActivate', '*', fioriGenericActivate)
188
187
  })
@@ -23,6 +23,7 @@ const PREFIX_DRAFT_COLUMNS = DRAFT_COLUMNS_ADMIN.map(col => ({ ref: ['DRAFT_Draf
23
23
 
24
24
  const _validateDraft = (req, draftResult, isBoundAction) => {
25
25
  if (!draftResult || draftResult.length === 0) req.reject(404)
26
+
26
27
  const draftAdminData = draftResult[0]
27
28
 
28
29
  // the same user that locked the entity can always delete/update it
@@ -118,7 +119,9 @@ const _addDraftDataFromExistingDraft = async req => {
118
119
  * Generic Handler for before NEW requests.
119
120
  */
120
121
  const _new = async function (req) {
121
- if (isNavigationToMany(req)) {
122
+ if (!req.target._isDraftEnabled) return
123
+
124
+ if (isNavigationToMany(req, this.model)) {
122
125
  const result = await _addDraftDataFromExistingDraft(req)
123
126
 
124
127
  // in order to fix corner case where active subitems are created in draft case
@@ -136,6 +139,8 @@ const _new = async function (req) {
136
139
  * Generic Handler for before PATCH and UPDATE requests.
137
140
  */
138
141
  const _patch = async function (req) {
142
+ if (!req.target._isDraftEnabled) return
143
+
139
144
  const result = await _addDraftDataFromExistingDraft(req)
140
145
 
141
146
  // no result means that the draft does not exist
@@ -146,6 +151,8 @@ const _patch = async function (req) {
146
151
  * Generic Handler for before DELETE and CANCEL requests.
147
152
  */
148
153
  const _cancel = async function (req) {
154
+ if (!req.target._isDraftEnabled) return
155
+
149
156
  await _addDraftDataFromExistingDraft(req)
150
157
  }
151
158
 
@@ -162,20 +169,22 @@ const _allowEntityCollectionOnAction = action => {
162
169
  )
163
170
  }
164
171
 
165
- const _registerBoundActionHandlers = function (entityName, actions) {
166
- if (!actions) return
167
-
168
- const boundActions = Object.values(actions).filter(
169
- action =>
170
- action.kind === 'action' &&
171
- action.name !== 'draftPrepare' &&
172
- action.name !== 'draftEdit' &&
173
- action.name !== 'draftActivate' &&
174
- !_allowEntityCollectionOnAction(action)
175
- )
176
-
177
- for (const action of boundActions) {
178
- this.before(action.name, entityName, req => _validateDraftBoundAction(req))
172
+ const _registerBoundActionHandlers = function (entities) {
173
+ for (let entity of entities) {
174
+ if (!entity.actions) return
175
+
176
+ const boundActions = Object.values(entity.actions).filter(
177
+ action =>
178
+ action.kind === 'action' &&
179
+ action.name !== 'draftPrepare' &&
180
+ action.name !== 'draftEdit' &&
181
+ action.name !== 'draftActivate' &&
182
+ !_allowEntityCollectionOnAction(action)
183
+ )
184
+
185
+ for (const action of boundActions) {
186
+ this.before(action.name, entity.name, req => _validateDraftBoundAction(req))
187
+ }
179
188
  }
180
189
  }
181
190
 
@@ -183,9 +192,10 @@ _new._initial = true
183
192
  _patch._initial = true
184
193
  _cancel._initial = true
185
194
 
186
- module.exports = cds.service.impl((srv, entity) => {
187
- srv.before('NEW', entity, _new)
188
- srv.before('PATCH', entity, _patch)
189
- srv.before('CANCEL', entity, _cancel)
190
- _registerBoundActionHandlers.call(srv, entity.name, entity.actions)
195
+ module.exports = cds.service.impl(function (srv) {
196
+ srv.before('NEW', '*', _new)
197
+ srv.before('PATCH', '*', _patch)
198
+ srv.before('CANCEL', '*', _cancel)
199
+ const entities = Object.values(srv.entities || {}).filter(entity => entity._isDraftEnabled)
200
+ _registerBoundActionHandlers.call(srv, entities)
191
201
  })
@@ -8,10 +8,12 @@ const { deleteDraft } = require('../utils/delete')
8
8
  *
9
9
  * @param req
10
10
  */
11
- const fioriGenericCancel = function (req) {
11
+ const fioriGenericCancel = function (req, next) {
12
+ if (!req.target._isDraftEnabled) return next()
13
+
12
14
  return deleteDraft(req, this)
13
15
  }
14
16
 
15
- module.exports = cds.service.impl(function (srv, entity) {
16
- srv.on('CANCEL', entity, fioriGenericCancel)
17
+ module.exports = cds.service.impl(function (srv) {
18
+ srv.on('CANCEL', '*', fioriGenericCancel)
17
19
  })
@@ -8,12 +8,14 @@ const { deleteDraft } = require('../utils/delete')
8
8
  *
9
9
  * @param req
10
10
  */
11
- const fioriGenericDelete = function (req) {
11
+ const fioriGenericDelete = function (req, next) {
12
+ if (!req.target._isDraftEnabled) return next()
13
+
12
14
  // we should call deleteDraft only for draft tables and call next
13
15
  // to pass delete of active tables to our general implementation
14
16
  return deleteDraft(req, this, true)
15
17
  }
16
18
 
17
- module.exports = cds.service.impl(function (srv, entity) {
18
- srv.on('DELETE', entity, fioriGenericDelete)
19
+ module.exports = cds.service.impl(function (srv) {
20
+ srv.on('DELETE', '*', fioriGenericDelete)
19
21
  })
@@ -68,7 +68,9 @@ const _select = async (lockRecordCQN, draftExistsCQN, selectCQNs, req, dbtx) =>
68
68
  *
69
69
  * @param req
70
70
  */
71
- const fioriGenericEdit = async function (req) {
71
+ const fioriGenericEdit = async function (req, next) {
72
+ if (!req.target._isDraftEnabled) return next()
73
+
72
74
  if (!isActiveEntityRequested(req.query.SELECT.where || [])) {
73
75
  req.reject(400, 'Action "draftEdit" can only be called on the active entity')
74
76
  }
@@ -123,9 +125,7 @@ const fioriGenericEdit = async function (req) {
123
125
  // REVISIT: Use service.read with expand **
124
126
  const [draftExists, ...results] = await _select(lockRecordCQN, draftExistsCQN, [...selectCQNs], req, dbtx)
125
127
 
126
- if (!results[0].length) {
127
- req.reject(404)
128
- }
128
+ if (!results[0].length) req.reject(404)
129
129
 
130
130
  if (draftExists.length) {
131
131
  const adminData = await dbtx.run(
@@ -164,11 +164,11 @@ const fioriGenericEdit = async function (req) {
164
164
 
165
165
  // REVISIT: we need to use okra API here because it must be set in the batched request
166
166
  // status code must be set in handler to allow overriding for FE V2
167
- req?._?.odataRes?.setStatusCode(201)
167
+ req?._?.odataRes?.setStatusCode(201, { overwrite: true })
168
168
 
169
169
  return results[0][0]
170
170
  }
171
171
 
172
- module.exports = cds.service.impl(function (srv, entity) {
173
- srv.on('EDIT', entity, fioriGenericEdit)
172
+ module.exports = cds.service.impl(function (srv) {
173
+ srv.on('EDIT', '*', fioriGenericEdit)
174
174
  })
@@ -1,22 +1,16 @@
1
1
  const cds = require('../../cds')
2
2
 
3
3
  exports.impl = cds.service.impl(function () {
4
- const entities = Object.values(this.entities).filter(entity => entity._isDraftEnabled)
5
- if (entities.length === 0) return
6
-
7
- for (const each of entities) {
8
- _before(this, each)
9
- _new(this, each)
10
- _patch(this, each)
11
- _cancel(this, each)
12
- _edit(this, each)
13
- _prepare(this, each)
14
- _activate(this, each)
15
- _delete(this, each)
16
- }
17
-
18
- _readOverDraft(this) // registers an on READ * handler
19
- for (const each of entities) _read(this, each) // have to go last
4
+ _before(this)
5
+ _new(this)
6
+ _patch(this)
7
+ _cancel(this)
8
+ _edit(this)
9
+ _prepare(this)
10
+ _activate(this)
11
+ _delete(this)
12
+ _readOverDraft(this)
13
+ _read(this) // have to go last
20
14
  })
21
15
 
22
16
  const _before = require('../../fiori/generic/before')
@@ -49,6 +49,8 @@ const _getInsertDataCQN = (req, draftUUID) => {
49
49
  * @param next
50
50
  */
51
51
  const fioriGenericNew = async function (req, next) {
52
+ if (!req.target._isDraftEnabled) return next()
53
+
52
54
  if (!req._draftMetadata) {
53
55
  // REVISIT: when is this the case?
54
56
  return onDraftActivate(req, next)
@@ -60,7 +62,7 @@ const fioriGenericNew = async function (req, next) {
60
62
  // Only allowed for pseudo draft roots (entities with this action)
61
63
  if (isRoot && !req.target['@Common.DraftRoot.ActivationAction']) req.reject(403, 'DRAFT_MODIFICATION_ONLY_VIA_ROOT')
62
64
 
63
- const navigationToMany = isNavigationToMany(req)
65
+ const navigationToMany = isNavigationToMany(req, this.model)
64
66
 
65
67
  const adminDataCQN = navigationToMany
66
68
  ? _getUpdateDraftAdminCQN(req, req.data.DraftAdministrativeData_DraftUUID)
@@ -75,6 +77,6 @@ const fioriGenericNew = async function (req, next) {
75
77
  return { ...req.data, IsActiveEntity: false }
76
78
  }
77
79
 
78
- module.exports = cds.service.impl(function (srv, entity) {
79
- srv.on('NEW', entity, fioriGenericNew)
80
+ module.exports = cds.service.impl(function (srv) {
81
+ srv.on('NEW', '*', fioriGenericNew)
80
82
  })
@@ -58,7 +58,9 @@ const _joinDraftAdministrativeData = (selectResolved, target) => {
58
58
  *
59
59
  * @param req
60
60
  */
61
- const fioriGenericPatch = async function (req) {
61
+ const fioriGenericPatch = async function (req, next) {
62
+ if (!req.target._isDraftEnabled) return next()
63
+
62
64
  if (req.data.IsActiveEntity === true) req.reject(400, 'Patch can only be applied to a draft entity')
63
65
 
64
66
  if (!cds.db) req.reject('NO_DATABASE_CONNECTION')
@@ -70,17 +72,18 @@ const fioriGenericPatch = async function (req) {
70
72
  const targetName = selectResolved.SELECT.from.ref[selectResolved.SELECT.from.ref.length - 1]
71
73
  const alias = selectResolved.SELECT.from.as
72
74
  const selectWithAdmin = _joinDraftAdministrativeData(selectResolved, alias || targetName)
75
+
76
+ if (req._etagValidationClause) selectWithAdmin.where(removeIsActiveEntityRecursively(req._etagValidationClause))
77
+
73
78
  const results = await dbtx.run(selectWithAdmin)
74
79
 
75
- if (results.length === 0) req.reject(404)
80
+ if (results.length === 0) req.reject(req._etagValidationType ? 412 : 404)
76
81
 
77
82
  const result = results[0]
78
83
 
79
84
  // Potential timeout scenario supported
80
- if (result.draftAdmin_inProcessByUser && result.draftAdmin_inProcessByUser !== req.user.id) {
81
- // REVISIT: security log?
82
- req.reject(403)
83
- }
85
+ if (result.draftAdmin_inProcessByUser && result.draftAdmin_inProcessByUser !== req.user.id) req.reject(403)
86
+
84
87
  const updateDraftCQN = _getUpdateDraftCQN(req, selectResolved.SELECT.where, {
85
88
  ref: [targetName],
86
89
  as: alias || targetName
@@ -93,6 +96,6 @@ const fioriGenericPatch = async function (req) {
93
96
  return { ...req.data, IsActiveEntity: false }
94
97
  }
95
98
 
96
- module.exports = cds.service.impl(function (srv, entity) {
97
- srv.on('PATCH', entity, fioriGenericPatch)
99
+ module.exports = cds.service.impl(function (srv) {
100
+ srv.on('PATCH', '*', fioriGenericPatch)
98
101
  })
@@ -11,7 +11,9 @@ const { getColumns } = require('../../cds-services/services/utils/columns')
11
11
  *
12
12
  * @param req
13
13
  */
14
- const fioriGenericPrepare = async function (req) {
14
+ const fioriGenericPrepare = async function (req, next) {
15
+ if (!req.target._isDraftEnabled) return next()
16
+
15
17
  if (req.query.SELECT.from.ref.length > 1 || isActiveEntityRequested(req.query.SELECT.from.ref[0].where || [])) {
16
18
  req.reject(400, 'Action "draftPrepare" can only be called on a draft entity')
17
19
  }
@@ -19,6 +21,7 @@ const fioriGenericPrepare = async function (req) {
19
21
  if (!cds.db) req.reject('NO_DATABASE_CONNECTION')
20
22
 
21
23
  const target = ensureDraftsSuffix(req.target.name)
24
+
22
25
  const columns = getColumns(this.model.definitions[ensureNoDraftsSuffix(req.target.name)], {
23
26
  keysOnly: true,
24
27
  removeIgnore: true,
@@ -26,6 +29,7 @@ const fioriGenericPrepare = async function (req) {
26
29
  onlyNames: true
27
30
  })
28
31
  columns.push({ ref: ['DRAFT.DraftAdministrativeData', 'inProcessByUser'], as: 'draftAdmin_inProcessByUser' })
32
+
29
33
  const select = SELECT.one(target)
30
34
  .columns(columns)
31
35
  .join('DRAFT.DraftAdministrativeData')
@@ -35,16 +39,19 @@ const fioriGenericPrepare = async function (req) {
35
39
  { ref: ['DRAFT.DraftAdministrativeData', 'DraftUUID'] }
36
40
  ])
37
41
  .where(req.query.SELECT.from.ref[0].where)
42
+
38
43
  const result = await cds.tx(req).run(select)
44
+
39
45
  if (!result) req.reject(404)
40
- if (result.draftAdmin_inProcessByUser !== req.user.id) {
41
- // REVISIT: security log?
46
+
47
+ if (result.draftAdmin_inProcessByUser !== req.user.id)
42
48
  req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER', [result.draftAdmin_inProcessByUser])
43
- }
49
+
44
50
  delete result.draftAdmin_inProcessByUser
51
+
45
52
  return result
46
53
  }
47
54
 
48
- module.exports = cds.service.impl(function (srv, entity) {
49
- srv.on('draftPrepare', entity, fioriGenericPrepare)
55
+ module.exports = cds.service.impl(function (srv) {
56
+ srv.on('draftPrepare', '*', fioriGenericPrepare)
50
57
  })
@@ -17,7 +17,7 @@ const {
17
17
  filterKeys
18
18
  } = require('../utils/handler')
19
19
  const { deleteCondition, readAndDeleteKeywords, removeIsActiveEntityRecursively } = require('../utils/where')
20
- const { adaptStreamCQN } = require('../../cds-services/adapter/odata-v4/utils/stream')
20
+ const { adaptStreamCQN } = require('../utils/stream')
21
21
  const getError = require('../../common/error')
22
22
 
23
23
  const _findSubselect = where => {
@@ -1189,10 +1189,11 @@ const _getOriginalColumns = req => {
1189
1189
  return originalColumns
1190
1190
  }
1191
1191
 
1192
- const _handlerStreaming = (req, query) => {
1192
+ const _handlerStreaming = async (req, query) => {
1193
1193
  adaptStreamCQN(query)
1194
1194
  query._streaming = true
1195
- return cds.tx(req).run(query)
1195
+ const result = await cds.tx(req).run(query)
1196
+ return result
1196
1197
  }
1197
1198
 
1198
1199
  const _postProcess = (result, req, cqnScenario, deleteLastChangeDateTime) => {
@@ -1269,7 +1270,9 @@ const _adaptColumns4readAfterWrite = (req, cqnScenario, query4sql) => {
1269
1270
  *
1270
1271
  * @param req
1271
1272
  */
1272
- const fioriGenericRead = async function (req) {
1273
+ const fioriGenericRead = async function (req, next) {
1274
+ if (!req.target._isDraftEnabled) return next()
1275
+
1273
1276
  if (!cds.db) req.reject('NO_DATABASE_CONNECTION')
1274
1277
 
1275
1278
  const query = req.query
@@ -1327,9 +1330,12 @@ const fioriGenericRead = async function (req) {
1327
1330
  _adaptColumns4readAfterWrite(req, cqnScenario, query4sql)
1328
1331
 
1329
1332
  const result = await cds.tx(req).send({ query: cqnScenario.cqn, target: req.target })
1333
+
1334
+ if (result == null && req._etagValidationType === 'if-match') req.reject(412)
1335
+
1330
1336
  return _postProcess(result, reqClone, cqnScenario, enhancedWithLastChangeDateTime)
1331
1337
  }
1332
1338
 
1333
- module.exports = cds.service.impl(function (srv, entity) {
1334
- srv.on('READ', entity, fioriGenericRead)
1339
+ module.exports = cds.service.impl(function (srv) {
1340
+ srv.on('READ', '*', fioriGenericRead)
1335
1341
  })