@sap/cds 5.7.2 → 5.8.0
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 +108 -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/minify.js +1 -1
- 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/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/handlers/action.js +11 -38
- 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 -42
- 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/ExpressionToCQN.js +18 -8
- 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 +7 -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 +21 -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/UriHelper.js +7 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriTokenizer.js +5 -8
- 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 +1 -1
- 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 +18 -5
- 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 +80 -21
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +7 -5
- 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/Service.js +1 -1
- 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 +2 -1
- package/libx/_runtime/common/composition/insert.js +13 -13
- package/libx/_runtime/common/composition/update.js +39 -34
- package/libx/_runtime/common/error/frontend.js +17 -2
- 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 +3 -0
- package/libx/_runtime/common/utils/cqn.js +2 -6
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +97 -123
- package/libx/_runtime/common/utils/csn.js +14 -3
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +18 -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 +1 -1
- package/libx/_runtime/common/utils/vcap.js +27 -10
- package/libx/_runtime/db/data-conversion/post-processing.js +42 -35
- package/libx/_runtime/db/expand/expand-v2.js +21 -12
- package/libx/_runtime/db/expand/expandCQNToJoin.js +27 -29
- 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 -3
- package/libx/_runtime/db/query/read.js +15 -8
- 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/index.js +3 -0
- package/libx/_runtime/db/utils/columns.js +5 -2
- package/libx/_runtime/db/utils/deep.js +6 -8
- 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 +22 -17
- package/libx/_runtime/fiori/generic/readOverDraft.js +1 -4
- package/libx/_runtime/hana/Service.js +1 -1
- package/libx/_runtime/hana/conversion.js +10 -0
- package/libx/_runtime/hana/execute.js +33 -16
- package/libx/_runtime/hana/localized.js +1 -1
- package/libx/_runtime/hana/search.js +3 -3
- package/libx/_runtime/hana/search2cqn4sql.js +22 -21
- package/libx/_runtime/hana/searchToContains.js +1 -1
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +4 -2
- 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/message-queuing-utils/options-messaging.js +1 -0
- package/libx/_runtime/messaging/service.js +16 -7
- package/libx/_runtime/remote/utils/client.js +33 -20
- package/libx/_runtime/remote/utils/data.js +53 -12
- package/libx/_runtime/sqlite/Service.js +1 -1
- package/libx/_runtime/sqlite/conversion.js +10 -0
- package/libx/_runtime/sqlite/localized.js +1 -1
- package/libx/_runtime/types/api.js +2 -2
- 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 +101 -45
- package/libx/odata/index.js +7 -1
- 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
|
@@ -5,7 +5,7 @@ const SANITIZE_VALUES = process.env.NODE_ENV === 'production' && cds.env.log.san
|
|
|
5
5
|
|
|
6
6
|
const cdsLocale = require('../../../../lib/req/locale')
|
|
7
7
|
|
|
8
|
-
const { convertV2ResponseData, deepSanitize } = require('./data')
|
|
8
|
+
const { convertV2ResponseData, deepSanitize, convertV2PayloadData } = require('./data')
|
|
9
9
|
|
|
10
10
|
let _cloudSdkCore
|
|
11
11
|
|
|
@@ -30,6 +30,7 @@ const _executeHttpRequest = async ({ requestConfig, destination, destinationOpti
|
|
|
30
30
|
const destinationName = typeof destination === 'string' && destination
|
|
31
31
|
if (destinationName) {
|
|
32
32
|
destination = await getDestination(destinationName, resolveDestinationOptions(destinationOptions, jwt))
|
|
33
|
+
if (!destination) throw new Error(`Cannot resolve destination "${destinationName}"`)
|
|
33
34
|
} else if (destination.forwardAuthToken) {
|
|
34
35
|
destination = {
|
|
35
36
|
...destination,
|
|
@@ -166,10 +167,10 @@ const _purgeODataV2 = (data, target, reqHeaders) => {
|
|
|
166
167
|
if (typeof data !== 'object' || !data.d) return data
|
|
167
168
|
|
|
168
169
|
data = data.d
|
|
169
|
-
const
|
|
170
|
-
const
|
|
170
|
+
const ieee754Compatible = reqHeaders.accept && reqHeaders.accept.includes('IEEE754Compatible=true')
|
|
171
|
+
const exponentialDecimals = ieee754Compatible && reqHeaders.accept.includes('ExponentialDecimals=true')
|
|
171
172
|
const purgedResponse = data.results || data
|
|
172
|
-
const convertedResponse = convertV2ResponseData(purgedResponse, target, ieee754Compatible)
|
|
173
|
+
const convertedResponse = convertV2ResponseData(purgedResponse, target, ieee754Compatible, exponentialDecimals)
|
|
173
174
|
return _normalizeMetadata(/^__/, data, convertedResponse)
|
|
174
175
|
}
|
|
175
176
|
|
|
@@ -321,32 +322,37 @@ const getJwt = req => {
|
|
|
321
322
|
return null
|
|
322
323
|
}
|
|
323
324
|
|
|
324
|
-
const _cqnToReqOptions = (query, kind, model) => {
|
|
325
|
+
const _cqnToReqOptions = (query, kind, model, target) => {
|
|
325
326
|
const queryObject = cds.odata.urlify(query, { kind, model })
|
|
326
|
-
|
|
327
|
+
const reqOptions = {
|
|
327
328
|
method: queryObject.method,
|
|
328
329
|
url: encodeURI(
|
|
329
330
|
queryObject.path
|
|
330
331
|
// ugly workaround for Okra not allowing spaces in ( x eq 1 )
|
|
331
332
|
.replace(/\( /g, '(')
|
|
332
333
|
.replace(/ \)/g, ')')
|
|
333
|
-
)
|
|
334
|
-
|
|
334
|
+
)
|
|
335
|
+
}
|
|
336
|
+
if (queryObject.method !== 'GET' && queryObject.method !== 'HEAD') {
|
|
337
|
+
reqOptions.data = kind === 'odata-v2' ? convertV2PayloadData(queryObject.body, target) : queryObject.body
|
|
335
338
|
}
|
|
339
|
+
return reqOptions
|
|
336
340
|
}
|
|
337
341
|
|
|
338
|
-
const _stringToReqOptions = (query, data) => {
|
|
342
|
+
const _stringToReqOptions = (query, data, target) => {
|
|
339
343
|
const cleanQuery = query.trim()
|
|
340
344
|
const blankIndex = cleanQuery.substring(0, 8).indexOf(' ')
|
|
341
345
|
const reqOptions = {
|
|
342
346
|
method: cleanQuery.substring(0, blankIndex).toUpperCase(),
|
|
343
347
|
url: encodeURI(formatPath(cleanQuery.substring(blankIndex, cleanQuery.length).trim()))
|
|
344
348
|
}
|
|
345
|
-
if (data && reqOptions.method !== 'GET' && reqOptions.method !== 'HEAD')
|
|
349
|
+
if (data && reqOptions.method !== 'GET' && reqOptions.method !== 'HEAD') {
|
|
350
|
+
reqOptions.data = this.kind === 'odata-v2' ? Object.assign({}, convertV2PayloadData(data, target)) : data
|
|
351
|
+
}
|
|
346
352
|
return reqOptions
|
|
347
353
|
}
|
|
348
354
|
|
|
349
|
-
const _pathToReqOptions = (method, path, data) => {
|
|
355
|
+
const _pathToReqOptions = (method, path, data, target) => {
|
|
350
356
|
let url = path
|
|
351
357
|
if (!url.startsWith('/')) {
|
|
352
358
|
// extract entity name and instance identifier (either in "()" or after "/") from fully qualified path
|
|
@@ -358,7 +364,9 @@ const _pathToReqOptions = (method, path, data) => {
|
|
|
358
364
|
url = url.replace(/^\/\//, '/')
|
|
359
365
|
}
|
|
360
366
|
const reqOptions = { method, url }
|
|
361
|
-
if (data && reqOptions.method !== 'GET' && reqOptions.method !== 'HEAD')
|
|
367
|
+
if (data && reqOptions.method !== 'GET' && reqOptions.method !== 'HEAD') {
|
|
368
|
+
reqOptions.data = this.kind === 'odata-v2' ? Object.assign({}, convertV2PayloadData(data, target)) : data
|
|
369
|
+
}
|
|
362
370
|
return reqOptions
|
|
363
371
|
}
|
|
364
372
|
|
|
@@ -367,13 +375,14 @@ const _hasHeader = (headers, header) =>
|
|
|
367
375
|
.map(k => k.toLowerCase())
|
|
368
376
|
.includes(header)
|
|
369
377
|
|
|
378
|
+
// eslint-disable-next-line complexity
|
|
370
379
|
const getReqOptions = (req, query, service) => {
|
|
371
380
|
const reqOptions =
|
|
372
381
|
typeof query === 'object'
|
|
373
|
-
? _cqnToReqOptions(query, service.kind, service.model)
|
|
382
|
+
? _cqnToReqOptions(query, service.kind, service.model, req.target)
|
|
374
383
|
: typeof query === 'string'
|
|
375
|
-
? _stringToReqOptions(query, req.data)
|
|
376
|
-
: _pathToReqOptions(req.method, req.path, req.data)
|
|
384
|
+
? _stringToReqOptions(query, req.data, req.target)
|
|
385
|
+
: _pathToReqOptions(req.method, req.path, req.data, req.target)
|
|
377
386
|
|
|
378
387
|
reqOptions.headers = { accept: 'application/json,text/plain' }
|
|
379
388
|
reqOptions.timeout = service.requestTimeout
|
|
@@ -387,6 +396,12 @@ const getReqOptions = (req, query, service) => {
|
|
|
387
396
|
if (locale) reqOptions.headers['accept-language'] = locale
|
|
388
397
|
}
|
|
389
398
|
|
|
399
|
+
// forward all dwc-* headers
|
|
400
|
+
if (service.options.forward_dwc_headers) {
|
|
401
|
+
const originalHeaders = (req.context && req.context._ && req.context._.req && req.context._.req.headers) || {}
|
|
402
|
+
for (const k in originalHeaders) if (k.match(/^dwc-/)) reqOptions.headers[k] = originalHeaders[k]
|
|
403
|
+
}
|
|
404
|
+
|
|
390
405
|
if (reqOptions.data && reqOptions.method !== 'GET' && reqOptions.method !== 'HEAD') {
|
|
391
406
|
reqOptions.headers['content-type'] = 'application/json'
|
|
392
407
|
reqOptions.headers['content-length'] = Buffer.byteLength(JSON.stringify(reqOptions.data))
|
|
@@ -394,11 +409,9 @@ const getReqOptions = (req, query, service) => {
|
|
|
394
409
|
reqOptions.url = formatPath(reqOptions.url)
|
|
395
410
|
|
|
396
411
|
// batch envelope if needed
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
reqOptions.url.length > ((cds.env.remote && cds.env.remote.max_get_url_length) || 1028)
|
|
401
|
-
) {
|
|
412
|
+
const maxGetUrlLength =
|
|
413
|
+
service.options.max_get_url_length || (cds.env.remote && cds.env.remote.max_get_url_length) || 1028
|
|
414
|
+
if (KINDS_SUPPORTING_BATCH[service.kind] && reqOptions.method === 'GET' && reqOptions.url.length > maxGetUrlLength) {
|
|
402
415
|
reqOptions._autoBatch = true
|
|
403
416
|
reqOptions.data = [
|
|
404
417
|
'--batch1',
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const { big } = require('@sap/cds-foss')
|
|
2
|
+
|
|
1
3
|
// Code adopted from @sap/cds-odata-v2-adapter-proxy
|
|
2
4
|
// https://www.w3.org/TR/xmlschema11-2/#nt-duDTFrag
|
|
3
5
|
const DurationRegex = /^P(?:(\d)Y)?(?:(\d{1,2})M)?(?:(\d{1,2})D)?T(?:(\d{1,2})H)?(?:(\d{2})M)?(?:(\d{2}(?:\.\d+)?)S)?$/i
|
|
@@ -20,15 +22,16 @@ const DataTypeOData = {
|
|
|
20
22
|
Time: 'cds.TimeOfDay'
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
const _convertData = (data, target,
|
|
25
|
+
const _convertData = (data, target, convertValueFn) => {
|
|
26
|
+
const _convertRecordFn = _getConvertRecordFn(target, convertValueFn)
|
|
24
27
|
if (Array.isArray(data)) {
|
|
25
|
-
return data.map(
|
|
28
|
+
return data.map(_convertRecordFn)
|
|
26
29
|
}
|
|
27
30
|
|
|
28
|
-
return
|
|
31
|
+
return _convertRecordFn(data)
|
|
29
32
|
}
|
|
30
33
|
|
|
31
|
-
const _getConvertRecordFn = (target,
|
|
34
|
+
const _getConvertRecordFn = (target, convertValueFn) => record => {
|
|
32
35
|
for (const key in record) {
|
|
33
36
|
if (key === '__metadata') continue
|
|
34
37
|
|
|
@@ -36,24 +39,26 @@ const _getConvertRecordFn = (target, ieee754Compatible) => record => {
|
|
|
36
39
|
if (!element) continue
|
|
37
40
|
|
|
38
41
|
const recordValue = record[key]
|
|
39
|
-
const type = _elementType(element)
|
|
40
42
|
const value = (recordValue && recordValue.results) || recordValue
|
|
41
43
|
|
|
42
44
|
if (value && (element.isAssociation || Array.isArray(value))) {
|
|
43
|
-
record[key] = _convertData(value, element._target,
|
|
45
|
+
record[key] = _convertData(value, element._target, convertValueFn)
|
|
44
46
|
} else {
|
|
45
|
-
record[key] =
|
|
47
|
+
record[key] = convertValueFn(value, element)
|
|
46
48
|
}
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
return record
|
|
50
52
|
}
|
|
51
53
|
|
|
52
|
-
|
|
54
|
+
// eslint-disable-next-line complexity
|
|
55
|
+
const _convertValue = (ieee754Compatible, exponentialDecimals) => (value, element) => {
|
|
53
56
|
if (value == null) {
|
|
54
57
|
return value
|
|
55
58
|
}
|
|
56
59
|
|
|
60
|
+
const type = _elementType(element)
|
|
61
|
+
|
|
57
62
|
if (['cds.Boolean'].includes(type)) {
|
|
58
63
|
if (value === 'true') {
|
|
59
64
|
value = true
|
|
@@ -63,7 +68,14 @@ const _convertValue = (value, type, ieee754Compatible) => {
|
|
|
63
68
|
} else if (['cds.Integer'].includes(type)) {
|
|
64
69
|
value = parseInt(value, 10)
|
|
65
70
|
} else if (['cds.Decimal', 'cds.Integer64', 'cds.DecimalFloat'].includes(type)) {
|
|
66
|
-
|
|
71
|
+
const bigValue = big(value)
|
|
72
|
+
if (ieee754Compatible) {
|
|
73
|
+
// TODO test with arrayed => element.items.scale?
|
|
74
|
+
value = exponentialDecimals ? bigValue.toExponential(element.scale) : bigValue.toFixed(element.scale)
|
|
75
|
+
} else {
|
|
76
|
+
// OData V2 does not even mention ieee754Compatible, but V4 requires JSON number if ieee754Compatible=false
|
|
77
|
+
value = bigValue.toNumber()
|
|
78
|
+
}
|
|
67
79
|
} else if (['cds.Double'].includes(type)) {
|
|
68
80
|
value = parseFloat(value)
|
|
69
81
|
} else if (['cds.Time'].includes(type)) {
|
|
@@ -90,6 +102,29 @@ const _convertValue = (value, type, ieee754Compatible) => {
|
|
|
90
102
|
return value
|
|
91
103
|
}
|
|
92
104
|
|
|
105
|
+
const _convertPayloadValue = (value, element) => {
|
|
106
|
+
const type = _elementType(element)
|
|
107
|
+
|
|
108
|
+
// see https://www.odata.org/documentation/odata-version-2-0/json-format/
|
|
109
|
+
if (value == null) return value
|
|
110
|
+
switch (type) {
|
|
111
|
+
case 'cds.Date':
|
|
112
|
+
case 'cds.DateTime':
|
|
113
|
+
return `/Date(${new Date(value).getTime()})/`
|
|
114
|
+
case 'cds.Binary':
|
|
115
|
+
case 'cds.LargeBinary':
|
|
116
|
+
return Buffer.isBuffer(value) ? value.toString('base64') : value
|
|
117
|
+
case 'cds.Timestamp':
|
|
118
|
+
// According to OData V2 spec, and as cds.DateTime => (V2) Edm.DateTimeOffset => cds.Timestamp,
|
|
119
|
+
// cds.Timestamp should be converted into Edm.DateTimeOffset literal form `datetimeoffset'${new Date(value).toISOString()}'`
|
|
120
|
+
// However, odata-v2-proxy forwards it literaly as `datetimeoffset'...'` which is rejected by okra.
|
|
121
|
+
// Note that OData V2 spec example also does not contain 'datetimeoffset' predicate.
|
|
122
|
+
return new Date(value).toISOString()
|
|
123
|
+
default:
|
|
124
|
+
return value
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
93
128
|
const _calculateTicksOffsetSum = text => {
|
|
94
129
|
return (text.replace(/\s/g, '').match(/[+-]?([0-9]+)/g) || []).reduce((sum, value, index) => {
|
|
95
130
|
return sum + parseFloat(value) * (index === 0 ? 1 : 60 * 1000) // ticks are milliseconds (0), offset are minutes (1)
|
|
@@ -115,14 +150,19 @@ const _elementType = element => {
|
|
|
115
150
|
return type
|
|
116
151
|
}
|
|
117
152
|
|
|
118
|
-
const convertV2ResponseData = (data, target, ieee754Compatible) => {
|
|
153
|
+
const convertV2ResponseData = (data, target, ieee754Compatible, exponentialDecimals) => {
|
|
154
|
+
if (!target || !target.elements) return data
|
|
155
|
+
return _convertData(data, target, _convertValue(ieee754Compatible, exponentialDecimals))
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const convertV2PayloadData = (data, target) => {
|
|
119
159
|
if (!target || !target.elements) return data
|
|
120
|
-
return _convertData(data, target,
|
|
160
|
+
return _convertData(data, target, _convertPayloadValue)
|
|
121
161
|
}
|
|
122
162
|
|
|
123
163
|
const deepSanitize = arg => {
|
|
124
164
|
if (Array.isArray(arg)) return arg.map(deepSanitize)
|
|
125
|
-
if (typeof arg === 'object')
|
|
165
|
+
if (typeof arg === 'object' && arg !== null)
|
|
126
166
|
return Object.keys(arg).reduce((acc, cur) => {
|
|
127
167
|
acc[cur] = deepSanitize(arg[cur])
|
|
128
168
|
return acc
|
|
@@ -132,5 +172,6 @@ const deepSanitize = arg => {
|
|
|
132
172
|
|
|
133
173
|
module.exports = {
|
|
134
174
|
convertV2ResponseData,
|
|
175
|
+
convertV2PayloadData,
|
|
135
176
|
deepSanitize
|
|
136
177
|
}
|
|
@@ -39,7 +39,7 @@ module.exports = class SQLiteDatabase extends DatabaseService {
|
|
|
39
39
|
this._insert = this._queries.insert(execute.insert)
|
|
40
40
|
this._read = this._queries.read(execute.select, execute.stream)
|
|
41
41
|
this._update = this._queries.update(execute.update, execute.select)
|
|
42
|
-
this._delete = this._queries.delete(execute.delete)
|
|
42
|
+
this._delete = this._queries.delete(execute.delete, execute.update)
|
|
43
43
|
this._run = this._queries.run(this._insert, this._read, this._update, this._delete, execute.cqn, execute.sql)
|
|
44
44
|
|
|
45
45
|
this.dbcs = new Map()
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const cds = require('../cds')
|
|
2
|
+
|
|
1
3
|
const convertToBoolean = boolean => {
|
|
2
4
|
if (boolean === null) return null
|
|
3
5
|
|
|
@@ -41,4 +43,12 @@ const SQLITE_TYPE_CONVERSION_MAP = new Map([
|
|
|
41
43
|
['cds.Timestamp', convertToISOTime]
|
|
42
44
|
])
|
|
43
45
|
|
|
46
|
+
if (cds.env.features.bigjs) {
|
|
47
|
+
const Big = require('big.js')
|
|
48
|
+
const convertToBig = value => new Big(value)
|
|
49
|
+
|
|
50
|
+
SQLITE_TYPE_CONVERSION_MAP.set('cds.Integer64', convertToBig)
|
|
51
|
+
SQLITE_TYPE_CONVERSION_MAP.set('cds.Decimal', convertToBig)
|
|
52
|
+
}
|
|
53
|
+
|
|
44
54
|
module.exports = { SQLITE_TYPE_CONVERSION_MAP }
|
|
@@ -41,7 +41,7 @@ const _handler = function (req) {
|
|
|
41
41
|
|
|
42
42
|
// suppress localization in "select for update" n/a for sqlite
|
|
43
43
|
|
|
44
|
-
redirect(query
|
|
44
|
+
redirect(query, getLocalize(req.locale, this.model))
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
_handler._initial = true
|
|
@@ -94,14 +94,14 @@
|
|
|
94
94
|
|
|
95
95
|
/**
|
|
96
96
|
* @typedef {object} search2cqnOptions
|
|
97
|
-
* @property {ColumnRefs} [columns] The columns to
|
|
98
|
-
* be searched
|
|
97
|
+
* @property {ColumnRefs} [columns] The columns to be searched
|
|
99
98
|
* @property {string} locale The user locale
|
|
100
99
|
*/
|
|
101
100
|
|
|
102
101
|
/**
|
|
103
102
|
* @typedef {object} cqn2cqn4sqlOptions
|
|
104
103
|
* @property {boolean} [suppressSearch=false] Indicates whether the search handler is called.
|
|
104
|
+
* @property {boolean} [_4fiori]
|
|
105
105
|
* @property {import('../db/Service')} [service]
|
|
106
106
|
*/
|
|
107
107
|
|
|
@@ -45,6 +45,7 @@ const traverseField = (info, field) => {
|
|
|
45
45
|
const traverseFieldNodes = (info, fieldNodes) => fieldNodes.map(fieldNode => traverseField(info, fieldNode))
|
|
46
46
|
|
|
47
47
|
module.exports = info => {
|
|
48
|
+
// REVISIT: JSON.parse(JSON.stringify(obj)) breaks buffers
|
|
48
49
|
const deepClonedFieldNodes = JSON.parse(JSON.stringify(info.fieldNodes))
|
|
49
50
|
traverseFieldNodes(info, deepClonedFieldNodes)
|
|
50
51
|
return deepClonedFieldNodes
|
|
@@ -3,12 +3,23 @@ const cds = require('../_runtime/cds')
|
|
|
3
3
|
const { where2obj } = require('../_runtime/common/utils/cqn')
|
|
4
4
|
const { findCsnTargetFor } = require('../_runtime/common/utils/csn')
|
|
5
5
|
|
|
6
|
+
const _addKeysDeep = (keys, keysCollector) => {
|
|
7
|
+
for (const keyName in keys) {
|
|
8
|
+
const key = keys[keyName]
|
|
9
|
+
if (key.type === 'cds.Association' || key['@odata.foreignKey4'] === 'up_') continue
|
|
10
|
+
if ('elements' in key) {
|
|
11
|
+
_addKeysDeep(key.elements, keysCollector)
|
|
12
|
+
continue
|
|
13
|
+
}
|
|
14
|
+
keysCollector.push(keyName)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
6
18
|
function _keysOf(entity) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
: []
|
|
19
|
+
if (!entity || !entity.keys) return
|
|
20
|
+
const keysCollector = []
|
|
21
|
+
_addKeysDeep(entity.keys, keysCollector)
|
|
22
|
+
return keysCollector
|
|
12
23
|
}
|
|
13
24
|
|
|
14
25
|
function _getDefinition(definition, name, namespace) {
|
|
@@ -49,7 +60,7 @@ function _processSegments(cqn, model, namespace) {
|
|
|
49
60
|
let one
|
|
50
61
|
for (let i = 0; i < ref.length; i++) {
|
|
51
62
|
const seg = ref[i].id || ref[i]
|
|
52
|
-
|
|
63
|
+
let params = ref[i].where && where2obj(ref[i].where)
|
|
53
64
|
|
|
54
65
|
if (incompleteKeys) {
|
|
55
66
|
// > key
|
|
@@ -91,6 +102,9 @@ function _processSegments(cqn, model, namespace) {
|
|
|
91
102
|
if (ref[i].where) {
|
|
92
103
|
keyCount += addRefToWhereIfNecessary(ref[i].where, current)
|
|
93
104
|
_resolveAliasInWhere(ref[i].where, current)
|
|
105
|
+
// in case of Foo(1), params will be {} (before addRefToWhereIfNecessary was called)
|
|
106
|
+
if (!Object.keys(params).length) params = where2obj(ref[i].where)
|
|
107
|
+
_checkAllKeysProvided(params, current)
|
|
94
108
|
}
|
|
95
109
|
} else if ({ action: 1, function: 1 }[current.kind]) {
|
|
96
110
|
// > action or function
|
|
@@ -146,6 +160,15 @@ function _processSegments(cqn, model, namespace) {
|
|
|
146
160
|
|
|
147
161
|
const _resolveFrom = from => (from.SELECT ? _resolveFrom(from.SELECT.from) : from)
|
|
148
162
|
|
|
163
|
+
const _checkAllKeysProvided = (params, entity) => {
|
|
164
|
+
const keysOfEntity = _keysOf(entity)
|
|
165
|
+
if (!keysOfEntity) return
|
|
166
|
+
for (const keyOfEntity of keysOfEntity) {
|
|
167
|
+
if (!(keyOfEntity in params))
|
|
168
|
+
throw Object.assign(new Error(`Key "${keyOfEntity}" is missing for entity "${entity.name}"`), { status: 400 })
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
149
172
|
function _4service(service) {
|
|
150
173
|
const { namespace, model } = service
|
|
151
174
|
|
package/libx/odata/cqn2odata.js
CHANGED
|
@@ -357,6 +357,15 @@ function $orderBy(orderBy) {
|
|
|
357
357
|
if (hasValidProps(cur, 'ref')) {
|
|
358
358
|
res.push(cur.ref.join('/'))
|
|
359
359
|
}
|
|
360
|
+
|
|
361
|
+
if (hasValidProps(cur, 'func', 'sort')) {
|
|
362
|
+
res.push(`${cur.func}(${_args(cur.args)})` + ' ' + cur.sort)
|
|
363
|
+
continue
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (hasValidProps(cur, 'func')) {
|
|
367
|
+
res.push(`${cur.func}(${_args(cur.args)})`)
|
|
368
|
+
}
|
|
360
369
|
}
|
|
361
370
|
|
|
362
371
|
return '$orderby=' + res.join(',')
|