@sap/cds 5.7.5 → 5.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +97 -0
- package/app/fiori/routes.js +1 -1
- package/bin/deploy/to-hana/cfUtil.js +251 -138
- package/bin/deploy/to-hana/gitUtil.js +55 -0
- package/bin/deploy/to-hana/hana.js +92 -93
- package/bin/deploy/to-hana/hdiDeployUtil.js +42 -27
- package/bin/deploy/to-hana/index.js +14 -13
- package/bin/mtx/in-cds.js +1 -0
- package/bin/serve.js +1 -1
- package/bin/version.js +1 -0
- package/lib/compile/cdsc.js +0 -6
- package/lib/compile/resolve.js +1 -1
- package/lib/compile/to/srvinfo.js +1 -1
- package/lib/core/classes.js +21 -1
- package/lib/env/index.js +3 -2
- package/lib/env/requires.js +4 -0
- package/lib/i18n/localize.js +5 -8
- package/lib/index.js +1 -0
- package/lib/log/errors.js +1 -1
- package/lib/log/format/kibana.js +3 -3
- package/lib/ql/SELECT.js +2 -2
- package/lib/req/cds-context.js +1 -1
- package/lib/req/context.js +1 -1
- package/lib/serve/Transaction.js +9 -5
- package/lib/serve/index.js +13 -21
- package/lib/utils/tests.js +90 -66
- package/libx/_runtime/audit/generic/personal/modification.js +0 -8
- package/libx/_runtime/auth/index.js +7 -6
- package/libx/_runtime/auth/strategies/dwc.js +43 -0
- package/libx/_runtime/auth/utils.js +24 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +11 -32
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +12 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +7 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +24 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +43 -38
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +11 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/boundToCQN.js +1 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/deleteToCQN.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/orderByToCQN.js +9 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +17 -30
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +12 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/AbstractEdmStructuredType.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +23 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriHelper.js +7 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriTokenizer.js +2 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +19 -47
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +4 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +7 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/CommandExecutor.js +0 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/ConditionalRequestControlCommand.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ContextURLFactory.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ErrorJsonSerializer.js +2 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/TrustedResourceJsonSerializer.js +2 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +6 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +41 -17
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +1 -17
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +60 -18
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +47 -10
- package/libx/_runtime/cds-services/adapter/rest/Rest.js +22 -1
- package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +8 -3
- package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +3 -0
- package/libx/_runtime/cds-services/services/utils/columns.js +5 -1
- package/libx/_runtime/cds-services/services/utils/compareJson.js +15 -16
- package/libx/_runtime/cds-services/services/utils/differ.js +2 -8
- package/libx/_runtime/common/aspects/Association.js +16 -0
- package/libx/_runtime/common/composition/data.js +28 -37
- package/libx/_runtime/common/composition/delete.js +107 -58
- package/libx/_runtime/common/composition/index.js +3 -3
- package/libx/_runtime/common/composition/insert.js +14 -27
- package/libx/_runtime/common/composition/tree.js +1 -1
- package/libx/_runtime/common/composition/update.js +39 -34
- package/libx/_runtime/common/error/frontend.js +19 -5
- package/libx/_runtime/common/generic/auth.js +20 -85
- package/libx/_runtime/common/generic/crud.js +22 -1
- package/libx/_runtime/common/i18n/messages.properties +2 -1
- package/libx/_runtime/common/utils/cqn.js +2 -6
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +95 -122
- package/libx/_runtime/common/utils/csn.js +29 -6
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +21 -1
- package/libx/_runtime/common/utils/keys.js +2 -1
- package/libx/_runtime/common/utils/path.js +1 -1
- package/libx/_runtime/common/utils/resolveView.js +12 -4
- package/libx/_runtime/common/utils/rewriteAsterisks.js +27 -13
- package/libx/_runtime/common/utils/search2cqn4sql.js +11 -6
- package/libx/_runtime/common/utils/structured.js +10 -4
- package/libx/_runtime/common/utils/vcap.js +27 -10
- package/libx/_runtime/db/data-conversion/post-processing.js +20 -13
- package/libx/_runtime/db/expand/expand-v2.js +21 -12
- package/libx/_runtime/db/expand/expandCQNToJoin.js +67 -26
- package/libx/_runtime/db/expand/index.js +3 -0
- package/libx/_runtime/db/generic/create.js +0 -10
- package/libx/_runtime/db/generic/index.js +3 -0
- package/libx/_runtime/db/generic/read.js +2 -24
- package/libx/_runtime/db/generic/rewrite.js +1 -3
- package/libx/_runtime/db/generic/update.js +1 -1
- package/libx/_runtime/db/query/delete.js +10 -4
- package/libx/_runtime/db/query/insert.js +3 -4
- package/libx/_runtime/db/query/read.js +4 -1
- package/libx/_runtime/db/query/update.js +5 -5
- package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +9 -2
- package/libx/_runtime/db/sql-builder/FunctionBuilder.js +3 -0
- package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -3
- package/libx/_runtime/db/sql-builder/index.js +3 -0
- package/libx/_runtime/db/utils/columns.js +5 -2
- package/libx/_runtime/db/utils/deep.js +16 -14
- package/libx/_runtime/db/utils/generateAliases.js +56 -6
- package/libx/_runtime/fiori/generic/before.js +73 -49
- package/libx/_runtime/fiori/generic/edit.js +14 -18
- package/libx/_runtime/fiori/generic/patch.js +8 -11
- package/libx/_runtime/fiori/generic/read.js +20 -19
- package/libx/_runtime/fiori/generic/readOverDraft.js +1 -4
- package/libx/_runtime/fiori/utils/handler.js +1 -11
- package/libx/_runtime/hana/Service.js +1 -1
- package/libx/_runtime/hana/conversion.js +12 -1
- package/libx/_runtime/hana/dynatrace.js +11 -5
- package/libx/_runtime/hana/execute.js +132 -19
- package/libx/_runtime/hana/search.js +3 -3
- package/libx/_runtime/hana/search2cqn4sql.js +23 -25
- package/libx/_runtime/hana/searchToContains.js +1 -1
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +0 -1
- package/libx/_runtime/messaging/enterprise-messaging.js +1 -0
- package/libx/_runtime/messaging/file-based.js +3 -1
- package/libx/_runtime/messaging/service.js +4 -1
- package/libx/_runtime/remote/utils/client.js +41 -24
- package/libx/_runtime/remote/utils/data.js +54 -12
- package/libx/_runtime/sqlite/Service.js +1 -1
- package/libx/_runtime/sqlite/conversion.js +10 -0
- package/libx/_runtime/types/api.js +2 -2
- package/libx/gql/resolvers/crud/update.js +8 -5
- package/libx/gql/resolvers/parse/ast/enrich.js +1 -0
- package/libx/odata/afterburner.js +29 -6
- package/libx/odata/cqn2odata.js +9 -0
- package/libx/odata/grammar.pegjs +49 -21
- package/libx/odata/index.js +2 -2
- package/libx/odata/parser.js +1 -1
- package/libx/odata/utils.js +2 -2
- package/libx/rest/RestAdapter.js +29 -1
- package/libx/rest/middleware/auth.js +1 -3
- package/libx/rest/middleware/parse.js +1 -0
- package/package.json +1 -1
- package/server.js +1 -1
- package/bin/deploy/to-hana/logger.js +0 -27
- package/bin/deploy/to-hana/runCommand.js +0 -113
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +0 -37
- package/libx/_runtime/common/utils/auth.js +0 -16
|
@@ -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
|
-
|
|
23
|
-
const
|
|
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+')
|
|
@@ -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
|
-
|
|
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
|
|
288
|
-
|
|
289
|
-
|
|
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
|
|
366
|
-
|
|
367
|
-
|
|
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
|
package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js
CHANGED
|
@@ -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
|
|
194
|
-
|
|
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
|
-
|
|
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
|
}
|
package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/CommandExecutor.js
CHANGED
|
@@ -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())) {
|
|
@@ -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
|
-
|
|
476
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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,7 +65,6 @@ const _getParamData = parameters => {
|
|
|
65
65
|
|
|
66
66
|
return paramData
|
|
67
67
|
}
|
|
68
|
-
|
|
69
68
|
const _flattenStructureKeys = structureData => {
|
|
70
69
|
const result = {}
|
|
71
70
|
for (const prop in structureData) {
|
|
@@ -80,7 +79,6 @@ const _flattenStructureKeys = structureData => {
|
|
|
80
79
|
}
|
|
81
80
|
return result
|
|
82
81
|
}
|
|
83
|
-
|
|
84
82
|
// works only for custom on condition working on keys with '=' operator
|
|
85
83
|
// and combination of multiple conditions connected with 'and'
|
|
86
84
|
const _addKeysToData = (navSourceKeyValues, onCondition, data) => {
|
|
@@ -167,8 +165,7 @@ const _getCopiedData = (odataReq, streaming, lastSegment) => {
|
|
|
167
165
|
return data
|
|
168
166
|
}
|
|
169
167
|
|
|
170
|
-
|
|
171
|
-
return data
|
|
168
|
+
return Array.isArray(data) ? deepCopyArray(data) : deepCopyObject(data)
|
|
172
169
|
}
|
|
173
170
|
|
|
174
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
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
const cds = require('../../../../cds')
|
|
2
|
+
|
|
3
|
+
const { Readable } = require('stream')
|
|
4
|
+
const { big } = require('@sap/cds-foss')
|
|
5
|
+
|
|
2
6
|
const getTemplate = require('../../../../common/utils/template')
|
|
3
7
|
const templateProcessor = require('../../../../common/utils/templateProcessor')
|
|
4
|
-
const { big } = require('@sap/cds-foss')
|
|
5
8
|
const { omitValue, applyOmitValuesPreference } = require('./omitValues')
|
|
6
9
|
|
|
7
10
|
const METADATA = {
|
|
@@ -23,36 +26,59 @@ const METADATA = {
|
|
|
23
26
|
$mediaEditLink: '*@odata.mediaEditLink',
|
|
24
27
|
$mediaReadLink: '*@odata.mediaReadLink',
|
|
25
28
|
$mediaContentType: '*@odata.mediaContentType',
|
|
29
|
+
$mediaContentDispositionFilename: '*@odata.mediaContentDispositionFilename', // > not supported by okra
|
|
30
|
+
$mediaContentDispositionType: '*@odata.mediaContentDispositionType', // > not supported by okra
|
|
26
31
|
$mediaEtag: '*@odata.mediaEtag'
|
|
27
32
|
}
|
|
28
33
|
|
|
34
|
+
const _getPropertyName = req => {
|
|
35
|
+
const segments = req._.odataReq.getUriInfo().getPathSegments()
|
|
36
|
+
if (segments[segments.length - 1].getKind().match(/\w*\.PROPERTY$/)) {
|
|
37
|
+
return segments[segments.length - 1].getProperty().getName()
|
|
38
|
+
}
|
|
39
|
+
if (
|
|
40
|
+
segments[segments.length - 1].getKind() === 'VALUE' &&
|
|
41
|
+
segments[segments.length - 2].getKind() === 'PRIMITIVE.PROPERTY'
|
|
42
|
+
) {
|
|
43
|
+
return segments[segments.length - 2].getProperty().getName()
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
29
47
|
/**
|
|
30
48
|
* Convert any result to the result object structure, which is expected of odata-v4.
|
|
31
49
|
*
|
|
32
50
|
* @param {*} result
|
|
33
|
-
* @param {*} [
|
|
51
|
+
* @param {*} [req]
|
|
34
52
|
* @returns {string | object}
|
|
35
53
|
*/
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
54
|
+
// eslint-disable-next-line complexity
|
|
55
|
+
const toODataResult = (result, req) => {
|
|
56
|
+
if (result == null) return ''
|
|
57
|
+
|
|
58
|
+
let propertyName, isStream
|
|
59
|
+
if (req) {
|
|
60
|
+
propertyName = _getPropertyName(req)
|
|
61
|
+
isStream =
|
|
62
|
+
req._.odataReq.getUriInfo().getLastSegment().getProperty() &&
|
|
63
|
+
req._.odataReq.getUriInfo().getLastSegment().getProperty().getType().toString() === 'Edm.Stream'
|
|
64
|
+
|
|
65
|
+
const isCollection = !propertyName && req._.odataReq.getUriInfo().getLastSegment().isCollection()
|
|
66
|
+
if (isCollection && !Array.isArray(result)) result = [result]
|
|
67
|
+
else if (!isCollection && Array.isArray(result)) result = result[0]
|
|
49
68
|
}
|
|
50
69
|
|
|
51
|
-
|
|
52
|
-
|
|
70
|
+
let value = result
|
|
71
|
+
if (typeof result === 'object') {
|
|
72
|
+
if ('value' in result && (result.value instanceof Readable || isStream)) value = result.value
|
|
73
|
+
else if (propertyName) value = result[propertyName]
|
|
53
74
|
}
|
|
54
75
|
|
|
76
|
+
const odataResult = { value }
|
|
77
|
+
|
|
55
78
|
if (typeof result === 'object') {
|
|
79
|
+
// backwards compatibility for content-type in stream
|
|
80
|
+
if (result['*@odata.mediaContentType']) result.$mediaContentType = result['*@odata.mediaContentType']
|
|
81
|
+
|
|
56
82
|
for (const key in METADATA) {
|
|
57
83
|
// do not set "@odata.context" as it may be inherited of remote service
|
|
58
84
|
if (key === '$context') {
|
|
@@ -60,6 +86,18 @@ const toODataResult = (result, arg) => {
|
|
|
60
86
|
continue
|
|
61
87
|
}
|
|
62
88
|
|
|
89
|
+
// REVISIT: okra doesn't support content disposition
|
|
90
|
+
if (key === '$mediaContentDispositionFilename' && result[key] && req) {
|
|
91
|
+
const cdt = result.$mediaContentDispositionType || 'attachment'
|
|
92
|
+
req._.odataRes.setHeader('Content-Disposition', `${cdt}; filename="${encodeURIComponent(result[key])}"`)
|
|
93
|
+
delete result[key]
|
|
94
|
+
continue
|
|
95
|
+
}
|
|
96
|
+
if (key === '$mediaContentDispositionType') {
|
|
97
|
+
delete result[key]
|
|
98
|
+
continue
|
|
99
|
+
}
|
|
100
|
+
|
|
63
101
|
if (key in result) {
|
|
64
102
|
odataResult[METADATA[key]] = result[key]
|
|
65
103
|
delete result[key]
|
|
@@ -208,12 +246,16 @@ const _pick = options => (element, target, parent) => {
|
|
|
208
246
|
const categories = []
|
|
209
247
|
|
|
210
248
|
if (element['@odata.etag']) categories.push('@odata.etag')
|
|
249
|
+
|
|
211
250
|
if (element.type === 'cds.Decimal') categories.push('@cds.Decimal')
|
|
251
|
+
|
|
212
252
|
categories.push(..._assocs(element, target))
|
|
253
|
+
|
|
213
254
|
if (options.omitValuesPreference) categories.push('@odata.omitValues')
|
|
214
255
|
if (options.event === 'draftActivate' && options.locale && options.locale !== 'en' && element.localized === true) {
|
|
215
256
|
categories.push('localizeAfterDraftActivate')
|
|
216
257
|
}
|
|
258
|
+
|
|
217
259
|
if (categories.length) return { categories }
|
|
218
260
|
}
|
|
219
261
|
|
|
@@ -247,7 +289,7 @@ const _getOptions = ({ headers, locale, event }) => {
|
|
|
247
289
|
const _generateCacheKey = (headers, options) => {
|
|
248
290
|
let key = 'postProcess' // default template cache key for post processing
|
|
249
291
|
if (headers.prefer) key += `:${headers.prefer}`
|
|
250
|
-
if (options.decimals) key += `:
|
|
292
|
+
if (options.decimals) key += `:ExponentialDecimals=true`
|
|
251
293
|
if (options.event === 'draftActivate' && options.locale) key += `:locale=${options.locale}`
|
|
252
294
|
return key
|
|
253
295
|
}
|