@sap/cds 5.7.3 → 5.8.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 (151) hide show
  1. package/CHANGELOG.md +111 -0
  2. package/app/fiori/routes.js +1 -1
  3. package/bin/deploy/to-hana/cfUtil.js +251 -138
  4. package/bin/deploy/to-hana/gitUtil.js +55 -0
  5. package/bin/deploy/to-hana/hana.js +92 -93
  6. package/bin/deploy/to-hana/hdiDeployUtil.js +42 -27
  7. package/bin/deploy/to-hana/index.js +14 -13
  8. package/bin/mtx/in-cds.js +1 -0
  9. package/bin/serve.js +1 -1
  10. package/bin/version.js +1 -0
  11. package/lib/compile/cdsc.js +0 -6
  12. package/lib/compile/minify.js +1 -1
  13. package/lib/compile/resolve.js +1 -1
  14. package/lib/compile/to/srvinfo.js +1 -1
  15. package/lib/core/classes.js +21 -1
  16. package/lib/env/index.js +3 -2
  17. package/lib/env/requires.js +4 -0
  18. package/lib/i18n/localize.js +5 -8
  19. package/lib/index.js +1 -0
  20. package/lib/log/errors.js +1 -1
  21. package/lib/ql/SELECT.js +2 -2
  22. package/lib/req/cds-context.js +1 -1
  23. package/lib/req/context.js +1 -1
  24. package/lib/serve/Transaction.js +9 -5
  25. package/lib/serve/index.js +13 -21
  26. package/lib/utils/tests.js +90 -66
  27. package/libx/_runtime/audit/generic/personal/modification.js +0 -8
  28. package/libx/_runtime/auth/index.js +7 -6
  29. package/libx/_runtime/auth/strategies/dwc.js +43 -0
  30. package/libx/_runtime/auth/utils.js +24 -0
  31. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +11 -38
  32. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +12 -5
  33. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +7 -4
  34. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +24 -3
  35. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +43 -38
  36. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +1 -1
  37. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +11 -5
  38. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +13 -7
  39. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/boundToCQN.js +1 -2
  40. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/deleteToCQN.js +0 -1
  41. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -2
  42. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/orderByToCQN.js +9 -0
  43. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +17 -30
  44. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +12 -1
  45. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/AbstractEdmStructuredType.js +2 -1
  46. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriHelper.js +7 -6
  47. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriTokenizer.js +5 -8
  48. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +19 -47
  49. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +4 -11
  50. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +7 -1
  51. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/CommandExecutor.js +0 -3
  52. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/ConditionalRequestControlCommand.js +0 -1
  53. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ContextURLFactory.js +2 -2
  54. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ErrorJsonSerializer.js +2 -0
  55. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/TrustedResourceJsonSerializer.js +2 -5
  56. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +6 -6
  57. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
  58. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +18 -5
  59. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +41 -17
  60. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +1 -17
  61. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +80 -21
  62. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +47 -10
  63. package/libx/_runtime/cds-services/adapter/rest/Rest.js +22 -1
  64. package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +8 -3
  65. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +3 -0
  66. package/libx/_runtime/cds-services/services/Service.js +1 -1
  67. package/libx/_runtime/cds-services/services/utils/columns.js +5 -1
  68. package/libx/_runtime/cds-services/services/utils/compareJson.js +15 -16
  69. package/libx/_runtime/cds-services/services/utils/differ.js +2 -8
  70. package/libx/_runtime/common/aspects/Association.js +16 -0
  71. package/libx/_runtime/common/composition/data.js +28 -37
  72. package/libx/_runtime/common/composition/delete.js +107 -58
  73. package/libx/_runtime/common/composition/index.js +3 -3
  74. package/libx/_runtime/common/composition/insert.js +14 -27
  75. package/libx/_runtime/common/composition/update.js +39 -34
  76. package/libx/_runtime/common/error/frontend.js +19 -5
  77. package/libx/_runtime/common/generic/auth.js +20 -85
  78. package/libx/_runtime/common/generic/crud.js +22 -1
  79. package/libx/_runtime/common/i18n/messages.properties +2 -1
  80. package/libx/_runtime/common/utils/cqn.js +2 -6
  81. package/libx/_runtime/common/utils/cqn2cqn4sql.js +95 -122
  82. package/libx/_runtime/common/utils/csn.js +15 -4
  83. package/libx/_runtime/common/utils/foreignKeyPropagations.js +18 -1
  84. package/libx/_runtime/common/utils/keys.js +2 -1
  85. package/libx/_runtime/common/utils/path.js +1 -1
  86. package/libx/_runtime/common/utils/resolveView.js +12 -4
  87. package/libx/_runtime/common/utils/rewriteAsterisks.js +27 -13
  88. package/libx/_runtime/common/utils/search2cqn4sql.js +11 -6
  89. package/libx/_runtime/common/utils/structured.js +11 -5
  90. package/libx/_runtime/common/utils/vcap.js +27 -10
  91. package/libx/_runtime/db/data-conversion/post-processing.js +42 -35
  92. package/libx/_runtime/db/expand/expand-v2.js +21 -12
  93. package/libx/_runtime/db/expand/expandCQNToJoin.js +57 -29
  94. package/libx/_runtime/db/expand/index.js +3 -0
  95. package/libx/_runtime/db/generic/create.js +0 -10
  96. package/libx/_runtime/db/generic/index.js +3 -0
  97. package/libx/_runtime/db/generic/read.js +2 -24
  98. package/libx/_runtime/db/generic/rewrite.js +1 -3
  99. package/libx/_runtime/db/generic/update.js +1 -1
  100. package/libx/_runtime/db/query/delete.js +10 -4
  101. package/libx/_runtime/db/query/insert.js +3 -4
  102. package/libx/_runtime/db/query/read.js +4 -1
  103. package/libx/_runtime/db/query/update.js +5 -5
  104. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +9 -2
  105. package/libx/_runtime/db/sql-builder/FunctionBuilder.js +3 -0
  106. package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -3
  107. package/libx/_runtime/db/sql-builder/index.js +3 -0
  108. package/libx/_runtime/db/utils/columns.js +5 -2
  109. package/libx/_runtime/db/utils/deep.js +6 -8
  110. package/libx/_runtime/db/utils/generateAliases.js +56 -6
  111. package/libx/_runtime/fiori/generic/before.js +73 -49
  112. package/libx/_runtime/fiori/generic/edit.js +14 -18
  113. package/libx/_runtime/fiori/generic/patch.js +8 -11
  114. package/libx/_runtime/fiori/generic/read.js +22 -20
  115. package/libx/_runtime/fiori/generic/readOverDraft.js +1 -4
  116. package/libx/_runtime/fiori/utils/handler.js +1 -11
  117. package/libx/_runtime/hana/Service.js +1 -1
  118. package/libx/_runtime/hana/conversion.js +12 -1
  119. package/libx/_runtime/hana/execute.js +31 -16
  120. package/libx/_runtime/hana/localized.js +1 -1
  121. package/libx/_runtime/hana/search.js +3 -3
  122. package/libx/_runtime/hana/search2cqn4sql.js +23 -25
  123. package/libx/_runtime/hana/searchToContains.js +1 -1
  124. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +4 -2
  125. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +0 -1
  126. package/libx/_runtime/messaging/enterprise-messaging.js +1 -0
  127. package/libx/_runtime/messaging/file-based.js +3 -1
  128. package/libx/_runtime/messaging/service.js +16 -7
  129. package/libx/_runtime/remote/utils/client.js +37 -20
  130. package/libx/_runtime/remote/utils/data.js +53 -12
  131. package/libx/_runtime/sqlite/Service.js +1 -1
  132. package/libx/_runtime/sqlite/conversion.js +10 -0
  133. package/libx/_runtime/sqlite/localized.js +1 -1
  134. package/libx/_runtime/types/api.js +2 -2
  135. package/libx/gql/resolvers/crud/update.js +8 -5
  136. package/libx/gql/resolvers/parse/ast/enrich.js +1 -0
  137. package/libx/odata/afterburner.js +29 -6
  138. package/libx/odata/cqn2odata.js +9 -0
  139. package/libx/odata/grammar.pegjs +50 -22
  140. package/libx/odata/index.js +2 -2
  141. package/libx/odata/parser.js +1 -1
  142. package/libx/odata/utils.js +2 -2
  143. package/libx/rest/RestAdapter.js +29 -1
  144. package/libx/rest/middleware/auth.js +1 -3
  145. package/libx/rest/middleware/parse.js +1 -0
  146. package/package.json +1 -1
  147. package/server.js +1 -1
  148. package/bin/deploy/to-hana/logger.js +0 -27
  149. package/bin/deploy/to-hana/runCommand.js +0 -113
  150. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +0 -37
  151. package/libx/_runtime/common/utils/auth.js +0 -16
@@ -22,12 +22,13 @@ class UriHelper {
22
22
  return uriLiteral.substring(1, uriLiteral.length - 1).replace(REGEXP_TWO_SINGLE_QUOTES, "'")
23
23
  }
24
24
 
25
- if (
26
- edmType === EdmPrimitiveTypeKind.Duration ||
27
- edmType === EdmPrimitiveTypeKind.Binary ||
28
- edmType.getKind() === EdmTypeKind.ENUM ||
29
- edmType.getName().startsWith('Geo')
30
- ) {
25
+ if (edmType === EdmPrimitiveTypeKind.Binary) {
26
+ // convert the URL-safe base64 encoding to the standard variant (with padding, if necessary)
27
+ let val = uriLiteral.substring(uriLiteral.indexOf("'") + 1, uriLiteral.length - 1).replace(/_/g, '/').replace(/-/g, '+')
28
+ return val.padEnd(val.length + val.length % 4, '=')
29
+ }
30
+
31
+ if (edmType === EdmPrimitiveTypeKind.Duration || edmType.getKind() === EdmTypeKind.ENUM || edmType.getName().startsWith('Geo')) {
31
32
  return uriLiteral.substring(uriLiteral.indexOf("'") + 1, uriLiteral.length - 1)
32
33
  }
33
34
 
@@ -19,11 +19,8 @@ const GUID_VALUE_REGEXP = new RegExp(
19
19
  '^(?:' + HEX_DIGIT + '{8}-' + HEX_DIGIT + '{4}-' + HEX_DIGIT + '{4}-' + HEX_DIGIT + '{4}-' + HEX_DIGIT + '{12})'
20
20
  )
21
21
 
22
- const BASE64 = '[-_A-Za-z0-9]'
23
- const BASE64B16 = BASE64 + '{2}[AEIMQUYcgkosw048]=?'
24
- const BASE64B8 = BASE64 + '[AQgw](?:==)?'
25
- const BINARY = '(?:' + BASE64 + '{4})*(?:' + BASE64B16 + '|' + BASE64B8 + ')?'
26
- const BINARY_VALUE_REGEXP = new RegExp("^[Bb][Ii][Nn][Aa][Rr][Yy]'" + BINARY + "'")
22
+ // regex for url-safe base64
23
+ const BINARY_VALUE_REGEXP = /^[Bb][Ii][Nn][Aa][Rr][Yy]'(?:[A-Za-z0-9-_]{4})*(?:[A-Za-z0-9-_]{4}|[A-Za-z0-9-_]{3}=|[A-Za-z0-9-_]{2}={2}|[A-Za-z0-9-_]{2,3})'/
27
24
 
28
25
  const UNSIGNED_INTEGER_VALUE_REGEXP = new RegExp('^\\d+')
29
26
  const INTEGER_VALUE_REGEXP = new RegExp('^[-+]?\\d+')
@@ -684,8 +681,8 @@ class UriTokenizer {
684
681
  * @returns {boolean} whether the constant has been found at the current index
685
682
  * @private
686
683
  */
687
- _nextConstant (constant) {
688
- if (this._parseString.startsWith(constant, this._index)) {
684
+ _nextConstant (constant, caseInsensitive) {
685
+ if ((caseInsensitive ? this._parseString.toLowerCase() : this._parseString).startsWith(constant, this._index)) {
689
686
  this._index += constant.length
690
687
  return true
691
688
  }
@@ -784,7 +781,7 @@ class UriTokenizer {
784
781
  * @private
785
782
  */
786
783
  _nextMethod (methodName) {
787
- return this._nextConstant(methodName) && this._nextCharacter('(')
784
+ return this._nextConstant(methodName, true) && this._nextCharacter('(')
788
785
  }
789
786
 
790
787
  /**
@@ -67,6 +67,16 @@ const MULTI_LINE_STRING_VALIDATION = new RegExp('^' + SRID + 'MultiLineString\\(
67
67
  const MULTI_POLYGON_VALIDATION = new RegExp('^' + SRID + 'MultiPolygon\\((' + MULTI_POLYGON + ')\\)$', 'i')
68
68
  const COLLECTION_VALIDATION = new RegExp('^' + SRID + 'Collection\\((' + MULTI_GEO_LITERAL + ')\\)$', 'i')
69
69
 
70
+ const BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}={2})$/
71
+
72
+ function _getBase64(val) {
73
+ // convert url-safe to standard base64 (with padding, if necessary)
74
+ val = val.replace(/_/g, '/').replace(/-/g, '+')
75
+ val = val.padEnd(val.length + val.length % 4, '=')
76
+ if (!val.match(BASE64)) return
77
+ return val
78
+ }
79
+
70
80
  /**
71
81
  * The primitive-value decoder decodes primitive values, using OData V4 primitive types.
72
82
  * The following mapping of V2 and V4 primitive types is assumed:
@@ -148,7 +158,9 @@ class PrimitiveValueDecoder {
148
158
  const type = propertyOrReturnType.getType()
149
159
 
150
160
  if (type === EdmPrimitiveTypeKind.Binary) {
151
- return this._decodeBinary(jsonValue, propertyOrReturnType.getMaxLength())
161
+ const val = _getBase64(jsonValue)
162
+ if (!val) throw new IllegalArgumentError('The value for Edm.Binary is not valid base64 content.')
163
+ return val
152
164
  }
153
165
 
154
166
  let value = jsonValue
@@ -284,12 +296,9 @@ class PrimitiveValueDecoder {
284
296
  }
285
297
 
286
298
  if (type === EdmPrimitiveTypeKind.Binary) {
287
- const maxLength =
288
- propertyOrReturnType.getMaxLength() ||
289
- (propertyOrReturnType.getType().getKind() === EdmTypeKind.DEFINITION &&
290
- propertyOrReturnType.getType().getMaxLength()) ||
291
- null
292
- return this._decodeBinary(value, maxLength)
299
+ const val = _getBase64(value)
300
+ if (!val) throw new IllegalArgumentError('The value for Edm.Binary is not valid base64 content.')
301
+ return val
293
302
  }
294
303
 
295
304
  if (type === EdmPrimitiveTypeKind.Int64 || type === EdmPrimitiveTypeKind.Decimal) {
@@ -362,14 +371,9 @@ class PrimitiveValueDecoder {
362
371
  if (type.getKind() === EdmTypeKind.DEFINITION) type = type.getUnderlyingType()
363
372
 
364
373
  if (type === EdmPrimitiveTypeKind.Binary) {
365
- const valueBuffer = Buffer.from(valueString)
366
- const maxLength =
367
- propertyOrReturnType.getMaxLength() ||
368
- (propertyOrReturnType.getType().getKind() === EdmTypeKind.DEFINITION &&
369
- propertyOrReturnType.getType().getMaxLength()) ||
370
- null
371
- this._validator.validateBinary(valueBuffer, maxLength)
372
- return valueBuffer
374
+ const val = _getBase64(valueString)
375
+ if (!val) throw new IllegalArgumentError('The value for Edm.Binary is not valid base64 content.')
376
+ return val
373
377
  }
374
378
 
375
379
  let decoded
@@ -661,38 +665,6 @@ class PrimitiveValueDecoder {
661
665
  )
662
666
  }
663
667
  }
664
-
665
- /**
666
- * Decode an OData JSON representation of a binary value into its JavaScript value.
667
- * @param {string} value the JSON value
668
- * @param {?(number|string)} maxLength the value of the Maxlength facet
669
- * @returns {Buffer} the JavaScript value
670
- * @private
671
- */
672
- _decodeBinary (value, maxLength) {
673
- const valueBuffer = Buffer.from(value, 'base64')
674
- // The method Buffer.from(...) does not throw an error on invalid input;
675
- // it simply returns the result of the conversion of the content up to the first error.
676
- // So we check if the length is correct, taking padding characters into account (see RFC 4648).
677
- // Newline or other whitespace characters are not allowed according to the OData JSON format specification.
678
- let length = (value.length * 3) / 4 // Four base64 characters result in three octets.
679
- if (value.length % 4) {
680
- // The length is not a multiple of four as it should be.
681
- length =
682
- 3 * Math.floor(value.length / 4) +
683
- // The remainder (due to missing padding characters) will result in one or two octets.
684
- Math.ceil((value.length % 4) / 2)
685
- } else {
686
- // Padding characters reduce the amount of expected octets.
687
- if (value.endsWith('==')) length--
688
- if (value.endsWith('=')) length--
689
- }
690
- if (valueBuffer.length < length) {
691
- throw new IllegalArgumentError('The value for Edm.Binary is not valid base64 content.')
692
- }
693
- this._validator.validateBinary(valueBuffer, maxLength)
694
- return valueBuffer
695
- }
696
668
  }
697
669
 
698
670
  module.exports = PrimitiveValueDecoder
@@ -186,19 +186,12 @@ class ValueConverter {
186
186
 
187
187
  /**
188
188
  * Converts value to the value of Edm.Binary type.
189
- * @param {Buffer} value - value, which should be converted
190
- * @param {number} [maxLength] - value of MaxLength facet
189
+ * @param {Buffer|string} value - value, which should be converted
191
190
  * @returns {string} Base64 string
192
191
  */
193
- convertBinary (value, maxLength) {
194
- this._valueValidator.validateBinary(value, maxLength)
195
- return (
196
- value
197
- .toString('base64')
198
- // Convert the standard base64 encoding to the URL-safe variant.
199
- .replace(PLUS_REGEXP, '-')
200
- .replace(SLASH_REGEXP, '_')
201
- )
192
+ convertBinary (value) {
193
+ // convert the standard base64 encoding to the URL-safe variant
194
+ return (Buffer.isBuffer(value) ? value.toString('base64') : value).replace(/\+/g, '-').replace(/\//g, '_')
202
195
  }
203
196
 
204
197
  /**
@@ -43,11 +43,13 @@ class ResourceJsonDeserializer {
43
43
  * @throws {DeserializationError} if provided data can not be deserialized
44
44
  */
45
45
  deserializeEntity (edmType, value, expand, additionalInformation) {
46
+ let data
46
47
  try {
47
- let data = JSON.parse(value)
48
+ data = JSON.parse(value)
48
49
  this._deserializeStructuralType(edmType, data, null, false, expand, additionalInformation)
49
50
  return data
50
51
  } catch (e) {
52
+ e._data = data
51
53
  if (e instanceof DeserializationError) throw e
52
54
  throw new DeserializationError('An error occurred during deserialization of the entity.', e)
53
55
  }
@@ -82,6 +84,7 @@ class ResourceJsonDeserializer {
82
84
  }
83
85
  return tempData
84
86
  } catch (e) {
87
+ e._data = tempData
85
88
  if (e instanceof DeserializationError) throw e
86
89
  throw new DeserializationError('An error occurred during deserialization of the collection.', e)
87
90
  }
@@ -129,6 +132,7 @@ class ResourceJsonDeserializer {
129
132
  try {
130
133
  return this._deserializePrimitive(edmProperty, tempData)
131
134
  } catch (e) {
135
+ e._data = tempData
132
136
  if (e instanceof DeserializationError) throw e
133
137
  throw new DeserializationError('An error occurred during deserialization of the property.', e)
134
138
  }
@@ -155,6 +159,7 @@ class ResourceJsonDeserializer {
155
159
  try {
156
160
  return this._deserializePrimitive(edmProperty, tempData)
157
161
  } catch (e) {
162
+ e._data = tempData
158
163
  if (e instanceof DeserializationError) throw e
159
164
  throw new DeserializationError('An error occurred during deserialization of the property.', e)
160
165
  }
@@ -174,6 +179,7 @@ class ResourceJsonDeserializer {
174
179
  try {
175
180
  return this._deserializeReference(edmType, tempData)
176
181
  } catch (e) {
182
+ e._data = tempData
177
183
  if (e instanceof DeserializationError) throw e
178
184
  throw new DeserializationError('An error occurred during deserialization of the reference.', e)
179
185
  }
@@ -82,9 +82,6 @@ class CommandExecutor {
82
82
  }
83
83
  }, this._error)
84
84
  } catch (innerError) {
85
- if (innerError.__crashOnError) {
86
- throw innerError
87
- }
88
85
  if (this._runTimeMeasurement && description) this._runTimeMeasurement.getChild(description).stop()
89
86
  callback(innerError)
90
87
  }
@@ -4,7 +4,6 @@ const commons = require('../../odata-commons')
4
4
  const StatusCodes = commons.http.HttpStatusCode.StatusCodes
5
5
  const RepresentationKinds = commons.format.RepresentationKind.Kinds
6
6
  const Command = require('./Command')
7
- const InternalServerError = require('../errors/InternalServerError')
8
7
 
9
8
  /**
10
9
  * The `next` callback to be called upon finish execution.
@@ -281,7 +281,7 @@ class ContextURLFactory {
281
281
  // If there is one '*' selected, the context URL contains only '*'.
282
282
  if (isAll) {
283
283
  value = ['*']
284
- } else if (type && type.getKind() === EdmTypeKind.ENTITY && isKeyRequired) {
284
+ } else if (type && type.getKind() === EdmTypeKind.ENTITY && isKeyRequired && !cds.env.features.odata_new_parser) {
285
285
  for (const keyName of type.getKeyPropertyRefs().keys()) {
286
286
  if (!value.includes(keyName)) value.push(keyName)
287
287
  }
@@ -352,7 +352,7 @@ class ContextURLFactory {
352
352
  // Define a local function because navigation properties must be searched recursively.
353
353
  const getExpandPaths = structuredType => {
354
354
  let structureResult = []
355
- for (const name of structuredType.getNavigationProperties().keys()) structureResult.push(name)
355
+ for (const name of structuredType.getNavigationProperties(true).keys()) structureResult.push(name)
356
356
  for (const [name, property] of structuredType.getProperties()) {
357
357
  if (property.getType().getKind() === EdmTypeKind.COMPLEX) {
358
358
  for (const innerPath of getExpandPaths(property.getType())) {
@@ -34,6 +34,8 @@ class ErrorJsonSerializer extends ErrorSerializer {
34
34
  })
35
35
  }
36
36
 
37
+ if (this._error.innererror) result.innererror = this._error.innererror
38
+
37
39
  addAnnotations(this._error, result)
38
40
 
39
41
  return JSON.stringify({ error: result })
@@ -472,11 +472,8 @@ class TrustedResourceJsonSerializer {
472
472
  if (type === EdmPrimitiveTypeKind.Decimal || type === EdmPrimitiveTypeKind.Int64) {
473
473
  result = this._formatParams.getIEEE754Setting() ? String(propertyValue) : Number(propertyValue)
474
474
  } else if (type === EdmPrimitiveTypeKind.Binary) {
475
- result = propertyValue
476
- .toString('base64')
477
- // Convert the standard base64 encoding to the URL-safe variant.
478
- .replace(new RegExp('\\+', 'g'), '-')
479
- .replace(new RegExp('/', 'g'), '_')
475
+ // convert the standard base64 encoding to the URL-safe variant
476
+ result = (Buffer.isBuffer(propertyValue) ? propertyValue.toString('base64') : propertyValue).replace(/\+/g, '-').replace(/\//g, '_')
480
477
  }
481
478
  break
482
479
  case EdmTypeKind.COMPLEX:
@@ -5,7 +5,6 @@ const HttpMethods = commons.http.HttpMethod.Methods
5
5
  const ResourceKinds = commons.uri.UriResource.ResourceKind
6
6
  const PreconditionFailedError = require('../errors/PreconditionFailedError')
7
7
  const PreconditionRequiredError = require('../errors/PreconditionRequiredError')
8
- const ConflictError = require('../errors/ConflictError')
9
8
 
10
9
  /**
11
10
  * Class to validate conditional requests.
@@ -23,11 +22,12 @@ class ConditionalRequestValidator {
23
22
  preValidate (ifMatch, ifNoneMatch, method, isConcurrentResource) {
24
23
  if (isConcurrentResource) {
25
24
  if (method !== HttpMethods.GET && !ifMatch && !ifNoneMatch) throw new PreconditionRequiredError()
26
- } else if (ifMatch || ifNoneMatch) {
27
- // PATCH and PUT can carry If-Match or If-None-Match headers to force update or insert (upsert feature).
28
- // The only allowed value in these cases is '*'. Careless clients send this also for DELETE and POST,
29
- // other careless clients send the star in doublequotes.
30
- if ([HttpMethods.PATCH, HttpMethods.PUT, HttpMethods.DELETE, HttpMethods.POST].includes(method)) {
25
+ return
26
+ }
27
+
28
+ if (ifMatch || ifNoneMatch) {
29
+ // Careless clients send this also for DELETE and POST, other careless clients send the star in double-quotes.
30
+ if ([HttpMethods.POST].includes(method)) {
31
31
  if (
32
32
  (ifMatch && ifMatch.trim() !== '*' && ifMatch.trim() !== '"*"') ||
33
33
  (ifNoneMatch && ifNoneMatch.trim() !== '*' && ifNoneMatch.trim() !== '"*"')
@@ -6,7 +6,7 @@ const Dispatcher = require('./Dispatcher')
6
6
  const { alias2ref } = require('../../../common/utils/csn')
7
7
 
8
8
  const to = service => {
9
- const edm = cds.compile.to.edm(service.model, { service: service.definition.name })
9
+ const edm = service._edm || cds.compile.to.edm(service.model, { service: service.definition.name })
10
10
  alias2ref(service, edm)
11
11
 
12
12
  const odata = new OData(edm, service.model, service.options)
@@ -65,11 +65,25 @@ const _getParamData = parameters => {
65
65
 
66
66
  return paramData
67
67
  }
68
-
68
+ const _flattenStructureKeys = structureData => {
69
+ const result = {}
70
+ for (const prop in structureData) {
71
+ if (typeof structureData[prop] === 'object') {
72
+ const nested = _flattenStructureKeys(structureData[prop])
73
+ for (const key in nested) {
74
+ result[prop + '_' + key] = nested[key]
75
+ }
76
+ } else {
77
+ result[prop] = structureData[prop]
78
+ }
79
+ }
80
+ return result
81
+ }
69
82
  // works only for custom on condition working on keys with '=' operator
70
83
  // and combination of multiple conditions connected with 'and'
71
84
  const _addKeysToData = (navSourceKeyValues, onCondition, data) => {
72
- for (const key in navSourceKeyValues) {
85
+ const flattenKeys = _flattenStructureKeys(navSourceKeyValues)
86
+ for (const key in flattenKeys) {
73
87
  // find index of source column
74
88
  const sourceIndex = onCondition.findIndex(e => e.ref && e.ref[0] === 'source' && e.ref[1] === key)
75
89
  if (sourceIndex === -1) {
@@ -82,7 +96,7 @@ const _addKeysToData = (navSourceKeyValues, onCondition, data) => {
82
96
  const {
83
97
  ref: [, target]
84
98
  } = onCondition[sourceIndex + 1] === '=' ? onCondition[sourceIndex + 2] : onCondition[sourceIndex - 2]
85
- data[target] = navSourceKeyValues[key]
99
+ data[target] = flattenKeys[key]
86
100
  }
87
101
  }
88
102
  }
@@ -151,8 +165,7 @@ const _getCopiedData = (odataReq, streaming, lastSegment) => {
151
165
  return data
152
166
  }
153
167
 
154
- data = Array.isArray(data) ? deepCopyArray(data) : deepCopyObject(data)
155
- return data
168
+ return Array.isArray(data) ? deepCopyArray(data) : deepCopyObject(data)
156
169
  }
157
170
 
158
171
  /**
@@ -4,6 +4,8 @@ const { isCustomOperation } = require('./request')
4
4
  const expandToCQN = require('../odata-to-cqn/expandToCQN')
5
5
  const QueryOptions = require('../okra/odata-server').QueryOptions
6
6
  const { COMPLEX_PROPERTY, PRIMITIVE_PROPERTY } = require('../okra/odata-server').uri.UriResource.ResourceKind
7
+ const { mergeJson } = require('../../../services/utils/compareJson')
8
+ const { getColumns } = require('../../../services/utils/columns')
7
9
 
8
10
  const _selectForFunction = (selectColumns, result, opReturnType) => {
9
11
  if (!Array.isArray(result)) result = [result]
@@ -33,31 +35,51 @@ const _expand = (model, uriInfo, options) => {
33
35
  return expandToCQN(model, expand, uriInfo.getFinalEdmType(), options)
34
36
  }
35
37
 
38
+ const _compareKeys = (first, second) => key => {
39
+ const val1 = first[key]
40
+ const val2 = second[key]
41
+ if (Array.isArray(val1) || Buffer.isBuffer(val1)) return val1.every((_, i) => _compareKeys(val1, val2)(i))
42
+ if (val1 && typeof val1 === 'object') return Object.keys(val1).every(_compareKeys(val1, val2))
43
+ return val1 === val2
44
+ }
45
+
36
46
  const _expandForFunction = async (uriInfo, result, req, srv, opReturnType) => {
37
47
  const results = Array.isArray(result) ? result : [result]
38
-
39
- const opReturnTypeName = typeof opReturnType === 'string' ? opReturnType : opReturnType.name
40
- const isDraft = srv.model.definitions[opReturnTypeName] && srv.model.definitions[opReturnTypeName]._isDraftEnabled
41
-
48
+ const isDraft = opReturnType._isDraftEnabled
42
49
  const isDraftActivate = isDraftActivateAction(req)
43
50
 
44
51
  // REVISIT: what happens here exactly?
52
+ const selectQuery = SELECT.from(
53
+ isDraft && !isDraftActivate ? ensureDraftsSuffix(opReturnType.name) : opReturnType.name
54
+ )
55
+ const keys = getColumns(opReturnType, {
56
+ onlyNames: true,
57
+ removeIgnore: true,
58
+ filterDraft: !isDraft || isDraftActivate,
59
+ filterVirtual: true,
60
+ keysOnly: true
61
+ })
62
+ const expandCqn = _expand(srv.model, uriInfo, { rewriteAsterisks: true })
63
+ selectQuery.columns(expandCqn)
64
+ selectQuery.columns(keys)
45
65
  for (const row of results) {
46
- const selectQuery = SELECT.from(isDraft && !isDraftActivate ? ensureDraftsSuffix(opReturnType.name) : opReturnType)
47
-
48
- for (const key in opReturnType.keys) {
49
- if ((!isDraft || isDraftActivate) && key === 'IsActiveEntity') {
50
- continue
51
- }
52
- selectQuery.where(key, '=', row[key])
66
+ const where = ['(']
67
+ for (const key of keys) {
68
+ where.push({ ref: [key] }, '=', { val: row[key] }, 'and')
53
69
  }
70
+ where.pop() // last 'and'
71
+ where.push(')')
72
+ if (!selectQuery.SELECT.where) selectQuery.where(where)
73
+ else selectQuery.or(where)
74
+ }
75
+ const expandedResults = await cds.tx(req).run(selectQuery)
54
76
 
55
- const expandCqn = _expand(srv.model, uriInfo, { rewriteAsterisks: true })
56
- selectQuery.columns(expandCqn)
57
-
58
- const res = await cds.tx(req).run(selectQuery)
59
- if (res) Object.assign(row, res[0])
77
+ for (let i = 0; i < results.length; i++) {
78
+ const result = results[i]
79
+ const res = expandedResults.find(r => keys.every(_compareKeys(result, r)))
80
+ if (res) results[i] = mergeJson(res, result, opReturnType)
60
81
  }
82
+ return Array.isArray(result) ? results : results[0]
61
83
  }
62
84
 
63
85
  const _cleanupResult = (result, opReturnType) => {
@@ -89,14 +111,16 @@ const getActionOrFunctionReturnType = (pathSegments, definitions) => {
89
111
  const actionAndFunctionQueries = async (req, odataReq, result, srv, opReturnType) => {
90
112
  _cleanupResult(result, opReturnType)
91
113
 
114
+ // REVISIT consider $expand columns as inline content for $select
92
115
  if (odataReq.getQueryOptions().$select) {
93
116
  _selectForFunction(odataReq.getQueryOptions().$select.split(','), result, opReturnType)
94
117
  }
95
118
 
96
119
  // REVISIT: we need to read directly from db for this, which might not be there!
97
120
  if (odataReq.getQueryOptions().$expand && cds.db) {
98
- await _expandForFunction(odataReq.getUriInfo(), result, req, srv, opReturnType)
121
+ result = await _expandForFunction(odataReq.getUriInfo(), result, req, srv, opReturnType)
99
122
  }
123
+ return result
100
124
  }
101
125
 
102
126
  const resolveStructuredName = (pathSegments, index, nameArr = []) => {
@@ -1,5 +1,4 @@
1
1
  const {
2
- QueryOptions,
3
2
  uri: {
4
3
  UriResource: {
5
4
  ResourceKind: { ENTITY, ENTITY_COLLECTION }
@@ -60,22 +59,7 @@ const validateResourcePath = (odataReq, service) => {
60
59
  }
61
60
  }
62
61
 
63
- /**
64
- * Used for pagination, where the start of the collection is defined via skip token.
65
- *
66
- * @param {object} uriInfo
67
- * @returns {number}
68
- * @private
69
- */
70
- const skipToken = uriInfo => {
71
- const token = uriInfo.getQueryOption(QueryOptions.SKIPTOKEN)
72
-
73
- // If given, the token is a string but needed as numeric value.
74
- return token ? parseInt(token) : 0
75
- }
76
-
77
62
  module.exports = {
78
63
  isCustomOperation,
79
- validateResourcePath,
80
- skipToken
64
+ validateResourcePath
81
65
  }