@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.
- package/CHANGELOG.md +111 -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 -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/ExpressionToCQN.js +13 -7
- 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/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 +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 +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 +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/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 +3 -3
- package/libx/_runtime/common/composition/insert.js +14 -27
- 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 +15 -4
- 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 +11 -5
- 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 +57 -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 -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 +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 -20
- 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/execute.js +31 -16
- package/libx/_runtime/hana/localized.js +1 -1
- 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 +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/service.js +16 -7
- package/libx/_runtime/remote/utils/client.js +37 -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/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 +50 -22
- 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
|
@@ -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
|
|
|
@@ -245,6 +246,7 @@ const run = async (
|
|
|
245
246
|
|
|
246
247
|
LOG._warn && LOG.warn(sanitizedError)
|
|
247
248
|
|
|
249
|
+
// REVISIT: switch from innererror to reason in cds^6
|
|
248
250
|
throw Object.assign(new Error(e.message), { statusCode: 502, innererror: sanitizedError })
|
|
249
251
|
}
|
|
250
252
|
|
|
@@ -266,6 +268,7 @@ const run = async (
|
|
|
266
268
|
|
|
267
269
|
LOG._warn && LOG.warn(sanitizedError)
|
|
268
270
|
|
|
271
|
+
// REVISIT: switch from innererror to reason in cds^6
|
|
269
272
|
throw Object.assign(new Error(`Error during request to remote service: ${e.message}`), {
|
|
270
273
|
statusCode: 502,
|
|
271
274
|
innererror: sanitizedError
|
|
@@ -293,6 +296,8 @@ const run = async (
|
|
|
293
296
|
: 'Request to remote service failed.'
|
|
294
297
|
const sanitizedError = _getSanitizedError(contentJSON, requestConfig)
|
|
295
298
|
LOG._warn && LOG.warn(sanitizedError)
|
|
299
|
+
|
|
300
|
+
// REVISIT: switch from innererror to reason in cds^6
|
|
296
301
|
throw Object.assign(new Error(contentJSON.message), { statusCode: 502, innererror: sanitizedError })
|
|
297
302
|
}
|
|
298
303
|
}
|
|
@@ -321,32 +326,37 @@ const getJwt = req => {
|
|
|
321
326
|
return null
|
|
322
327
|
}
|
|
323
328
|
|
|
324
|
-
const _cqnToReqOptions = (query, kind, model) => {
|
|
329
|
+
const _cqnToReqOptions = (query, kind, model, target) => {
|
|
325
330
|
const queryObject = cds.odata.urlify(query, { kind, model })
|
|
326
|
-
|
|
331
|
+
const reqOptions = {
|
|
327
332
|
method: queryObject.method,
|
|
328
333
|
url: encodeURI(
|
|
329
334
|
queryObject.path
|
|
330
335
|
// ugly workaround for Okra not allowing spaces in ( x eq 1 )
|
|
331
336
|
.replace(/\( /g, '(')
|
|
332
337
|
.replace(/ \)/g, ')')
|
|
333
|
-
)
|
|
334
|
-
data: queryObject.body
|
|
338
|
+
)
|
|
335
339
|
}
|
|
340
|
+
if (queryObject.method !== 'GET' && queryObject.method !== 'HEAD') {
|
|
341
|
+
reqOptions.data = kind === 'odata-v2' ? convertV2PayloadData(queryObject.body, target) : queryObject.body
|
|
342
|
+
}
|
|
343
|
+
return reqOptions
|
|
336
344
|
}
|
|
337
345
|
|
|
338
|
-
const _stringToReqOptions = (query, data) => {
|
|
346
|
+
const _stringToReqOptions = (query, data, target) => {
|
|
339
347
|
const cleanQuery = query.trim()
|
|
340
348
|
const blankIndex = cleanQuery.substring(0, 8).indexOf(' ')
|
|
341
349
|
const reqOptions = {
|
|
342
350
|
method: cleanQuery.substring(0, blankIndex).toUpperCase(),
|
|
343
351
|
url: encodeURI(formatPath(cleanQuery.substring(blankIndex, cleanQuery.length).trim()))
|
|
344
352
|
}
|
|
345
|
-
if (data && reqOptions.method !== 'GET' && reqOptions.method !== 'HEAD')
|
|
353
|
+
if (data && reqOptions.method !== 'GET' && reqOptions.method !== 'HEAD') {
|
|
354
|
+
reqOptions.data = this.kind === 'odata-v2' ? Object.assign({}, convertV2PayloadData(data, target)) : data
|
|
355
|
+
}
|
|
346
356
|
return reqOptions
|
|
347
357
|
}
|
|
348
358
|
|
|
349
|
-
const _pathToReqOptions = (method, path, data) => {
|
|
359
|
+
const _pathToReqOptions = (method, path, data, target) => {
|
|
350
360
|
let url = path
|
|
351
361
|
if (!url.startsWith('/')) {
|
|
352
362
|
// extract entity name and instance identifier (either in "()" or after "/") from fully qualified path
|
|
@@ -358,7 +368,9 @@ const _pathToReqOptions = (method, path, data) => {
|
|
|
358
368
|
url = url.replace(/^\/\//, '/')
|
|
359
369
|
}
|
|
360
370
|
const reqOptions = { method, url }
|
|
361
|
-
if (data && reqOptions.method !== 'GET' && reqOptions.method !== 'HEAD')
|
|
371
|
+
if (data && reqOptions.method !== 'GET' && reqOptions.method !== 'HEAD') {
|
|
372
|
+
reqOptions.data = this.kind === 'odata-v2' ? Object.assign({}, convertV2PayloadData(data, target)) : data
|
|
373
|
+
}
|
|
362
374
|
return reqOptions
|
|
363
375
|
}
|
|
364
376
|
|
|
@@ -367,13 +379,14 @@ const _hasHeader = (headers, header) =>
|
|
|
367
379
|
.map(k => k.toLowerCase())
|
|
368
380
|
.includes(header)
|
|
369
381
|
|
|
382
|
+
// eslint-disable-next-line complexity
|
|
370
383
|
const getReqOptions = (req, query, service) => {
|
|
371
384
|
const reqOptions =
|
|
372
385
|
typeof query === 'object'
|
|
373
|
-
? _cqnToReqOptions(query, service.kind, service.model)
|
|
386
|
+
? _cqnToReqOptions(query, service.kind, service.model, req.target)
|
|
374
387
|
: typeof query === 'string'
|
|
375
|
-
? _stringToReqOptions(query, req.data)
|
|
376
|
-
: _pathToReqOptions(req.method, req.path, req.data)
|
|
388
|
+
? _stringToReqOptions(query, req.data, req.target)
|
|
389
|
+
: _pathToReqOptions(req.method, req.path, req.data, req.target)
|
|
377
390
|
|
|
378
391
|
reqOptions.headers = { accept: 'application/json,text/plain' }
|
|
379
392
|
reqOptions.timeout = service.requestTimeout
|
|
@@ -387,6 +400,12 @@ const getReqOptions = (req, query, service) => {
|
|
|
387
400
|
if (locale) reqOptions.headers['accept-language'] = locale
|
|
388
401
|
}
|
|
389
402
|
|
|
403
|
+
// forward all dwc-* headers
|
|
404
|
+
if (service.options.forward_dwc_headers) {
|
|
405
|
+
const originalHeaders = (req.context && req.context._ && req.context._.req && req.context._.req.headers) || {}
|
|
406
|
+
for (const k in originalHeaders) if (k.match(/^dwc-/)) reqOptions.headers[k] = originalHeaders[k]
|
|
407
|
+
}
|
|
408
|
+
|
|
390
409
|
if (reqOptions.data && reqOptions.method !== 'GET' && reqOptions.method !== 'HEAD') {
|
|
391
410
|
reqOptions.headers['content-type'] = 'application/json'
|
|
392
411
|
reqOptions.headers['content-length'] = Buffer.byteLength(JSON.stringify(reqOptions.data))
|
|
@@ -394,11 +413,9 @@ const getReqOptions = (req, query, service) => {
|
|
|
394
413
|
reqOptions.url = formatPath(reqOptions.url)
|
|
395
414
|
|
|
396
415
|
// batch envelope if needed
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
reqOptions.url.length > ((cds.env.remote && cds.env.remote.max_get_url_length) || 1028)
|
|
401
|
-
) {
|
|
416
|
+
const maxGetUrlLength =
|
|
417
|
+
service.options.max_get_url_length || (cds.env.remote && cds.env.remote.max_get_url_length) || 1028
|
|
418
|
+
if (KINDS_SUPPORTING_BATCH[service.kind] && reqOptions.method === 'GET' && reqOptions.url.length > maxGetUrlLength) {
|
|
402
419
|
reqOptions._autoBatch = true
|
|
403
420
|
reqOptions.data = [
|
|
404
421
|
'--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
|
|
|
@@ -5,16 +5,14 @@ const { entriesStructureToEntityStructure } = require('./utils')
|
|
|
5
5
|
module.exports = async (service, entityFQN, selection) => {
|
|
6
6
|
const filter = getArgumentByName(selection.arguments, ARGUMENT.FILTER)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
const queryBeforeUpdate = service.read(entityFQN)
|
|
9
9
|
queryBeforeUpdate.columns(astToColumns(selection.selectionSet.selections))
|
|
10
10
|
|
|
11
11
|
if (filter) {
|
|
12
12
|
queryBeforeUpdate.where(astToWhere(filter))
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
let query = service.update(entityFQN)
|
|
15
|
+
const query = service.update(entityFQN)
|
|
18
16
|
|
|
19
17
|
if (filter) {
|
|
20
18
|
query.where(astToWhere(filter))
|
|
@@ -24,7 +22,12 @@ module.exports = async (service, entityFQN, selection) => {
|
|
|
24
22
|
const entries = entriesStructureToEntityStructure(service, entityFQN, astToEntries(input))
|
|
25
23
|
query.with(entries)
|
|
26
24
|
|
|
27
|
-
|
|
25
|
+
let resultBeforeUpdate
|
|
26
|
+
const result = await service.tx(async tx => {
|
|
27
|
+
// read needs to be done before the update, otherwise the where clause might become invalid (case that properties in where clause are updated by the mutation)
|
|
28
|
+
resultBeforeUpdate = await service.tx(tx => tx.run(queryBeforeUpdate))
|
|
29
|
+
return tx.run(query)
|
|
30
|
+
})
|
|
28
31
|
|
|
29
32
|
// Merge selected fields with updated data
|
|
30
33
|
return resultBeforeUpdate.map(original => ({ ...original, ...result }))
|
|
@@ -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(',')
|
package/libx/odata/grammar.pegjs
CHANGED
|
@@ -38,6 +38,11 @@
|
|
|
38
38
|
const n = Number(str)
|
|
39
39
|
return Number.isSafeInteger(n) ? n : str
|
|
40
40
|
}
|
|
41
|
+
const standardBase64 = options.standardBase64 || function (str) {
|
|
42
|
+
// convert url-safe to standard base64 (with padding, if necessary)
|
|
43
|
+
str = str.replace(/_/g, '/').replace(/-/g, '+')
|
|
44
|
+
return str.padEnd(str.length + str.length % 4, '=')
|
|
45
|
+
}
|
|
41
46
|
|
|
42
47
|
const _compareRefs = col => exp => col === exp ||
|
|
43
48
|
(col.as && exp.as && col.as === exp.as) ||
|
|
@@ -178,18 +183,29 @@
|
|
|
178
183
|
= "$count" {count = true}
|
|
179
184
|
/ rv:$("$ref"/"$value") {return !TECHNICAL_OPTS.includes(rv) && {from: {ref: [rv]}}}
|
|
180
185
|
/ head:(
|
|
181
|
-
(identifier filter:(OPEN CLOSE/OPEN args CLOSE)? !segment) / val:
|
|
186
|
+
(identifier filter:(OPEN CLOSE/OPEN args CLOSE)? !segment) / val:segment{return [val]}
|
|
182
187
|
) tail:( '/' p:path {return p} )? {
|
|
183
188
|
const [id, filter] = head
|
|
184
189
|
// minimal: val also as path segment
|
|
185
|
-
const ref = [
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
190
|
+
const ref = []
|
|
191
|
+
if (filter) {
|
|
192
|
+
if (filter.length > 2) {
|
|
193
|
+
ref.push({ id, where: filter[1].map(f => f.val && f.val.match && f.val.match(/^"(.*)"$/) ? { val: f.val.match(/^"(.*)"$/)[1] } : f) })
|
|
194
|
+
} else {
|
|
195
|
+
ref.push({ id, where: [] })
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
if (minimal) {
|
|
199
|
+
ref.push(`${typeof id === 'object' && 'val' in id ? id.val : id}`)
|
|
200
|
+
} else {
|
|
201
|
+
// REVISIT: keep 123 as number?
|
|
202
|
+
if (typeof id === 'object' && typeof id.val === 'string' && id.val.match(/^[1-9]\d*$|^0$/)) {
|
|
203
|
+
ref.push({ val: safeNumber(id.val) })
|
|
204
|
+
} else {
|
|
205
|
+
ref.push(id)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
193
209
|
if (tail && tail.from) {
|
|
194
210
|
const more = tail.from.ref
|
|
195
211
|
if (Object.prototype.hasOwnProperty.call(more[0], 'val')) ref[ref.length-1] = { id:ref[ref.length-1], where:[more.shift()] }
|
|
@@ -314,7 +330,13 @@
|
|
|
314
330
|
where_clause = p:( n:NOT? {return n?[n]:[]} )(
|
|
315
331
|
OPEN xpr:where_clause CLOSE {p.push({xpr})}
|
|
316
332
|
/ comp:comparison {p.push(...comp)}
|
|
317
|
-
/ lambda:lambda {
|
|
333
|
+
/ lambda:lambda {
|
|
334
|
+
if (p[p.length - 1] === 'not' && lambda[0] === 'not') {
|
|
335
|
+
p.push('(', ...lambda, ')')
|
|
336
|
+
} else {
|
|
337
|
+
p.push(...lambda)
|
|
338
|
+
}
|
|
339
|
+
}
|
|
318
340
|
/ func:boolish {p.push(func)}
|
|
319
341
|
/ val:bool {p.push({val})}
|
|
320
342
|
)( ao:(AND/OR) more:where_clause {p.push(ao,...more)} )*
|
|
@@ -369,7 +391,8 @@
|
|
|
369
391
|
all = "all" OPEN p:lambda_clause CLOSE { return p }
|
|
370
392
|
|
|
371
393
|
orderby
|
|
372
|
-
= ref:(function/ref) sort:( _ s:$("asc"/"desc") {return s})? {
|
|
394
|
+
= ref:(lambda{const err = new Error("ORDERBY_LAMBDA_UNSUPPORTED");err.statusCode=501;throw err;}/function/ref) sort:( _ s:$("asc"/"desc") {return s})? {
|
|
395
|
+
// TODO: Lambda support
|
|
373
396
|
const appendObj = $(ref, sort && {sort});
|
|
374
397
|
SELECT.orderBy = SELECT.orderBy ?
|
|
375
398
|
[...SELECT.orderBy, appendObj] :
|
|
@@ -398,7 +421,7 @@
|
|
|
398
421
|
= operand (_ ("add" / "sub" / "mul" / "div" / "mod") _ operand)*
|
|
399
422
|
|
|
400
423
|
operand "an operand"
|
|
401
|
-
= navigationCount / function /
|
|
424
|
+
= navigationCount / function / val / ref / jsonObject / jsonArray / list
|
|
402
425
|
|
|
403
426
|
navigationCount "navigation with $count"
|
|
404
427
|
= navigationPath:(head:identifier key:(OPEN keyArgs:args CLOSE {return keyArgs;})? '/' {
|
|
@@ -425,6 +448,7 @@
|
|
|
425
448
|
/ val:guid {return {val}}
|
|
426
449
|
/ val:number {return typeof val === 'number' ? {val} : { val, literal:'number' }}
|
|
427
450
|
/ val:string {return {val}}
|
|
451
|
+
/ val:binary {return {val}}
|
|
428
452
|
|
|
429
453
|
jsonObject = val:$("{" (jsonObject / [^}])* "}") {return {val}}
|
|
430
454
|
|
|
@@ -561,6 +585,7 @@
|
|
|
561
585
|
concatTrafo = OPEN o apply (o COMMA o apply)+ o CLOSE
|
|
562
586
|
|
|
563
587
|
computeTrafo = OPEN o computeExpr (o COMMA o computeExpr)* o CLOSE
|
|
588
|
+
|
|
564
589
|
computeExpr = where_clause asAlias
|
|
565
590
|
|
|
566
591
|
commonFuncTrafo = OPEN o first:operand o COMMA o second:operand o CLOSE { return [first, second] }
|
|
@@ -582,34 +607,37 @@
|
|
|
582
607
|
{return s.replace(/\\\\/g,"\\").replace(/\\"/g,'"')}
|
|
583
608
|
|
|
584
609
|
word
|
|
585
|
-
=
|
|
610
|
+
= $([^ \t\n()"&;]+)
|
|
586
611
|
|
|
587
612
|
date
|
|
588
613
|
= s:$( [0-9]+"-"[0-9][0-9]"-"[0-9][0-9] // date
|
|
589
|
-
("T"[0-9][0-9]":"[0-9][0-9](":"[0-9][0-9]("."[0-9]+)?)? // time
|
|
614
|
+
( "T"[0-9][0-9]":"[0-9][0-9](":"[0-9][0-9]("."[0-9]+)?)? // time
|
|
590
615
|
( "Z" / (("+" / "-")[0-9][0-9]":"[0-9][0-9]) )? // timezone (Z or +-hh:mm)
|
|
591
616
|
)?)
|
|
592
617
|
|
|
593
618
|
number
|
|
594
|
-
= s:$( [+-]? [0-9]+ ("."[0-9]+)? ("e"[0-9]+)? ) {return safeNumber(s)}
|
|
619
|
+
= s:$( [+-]? [0-9]+ ("."[0-9]+)? ("e"[0-9]+)? ) { return safeNumber(s) }
|
|
595
620
|
|
|
596
621
|
integer
|
|
597
|
-
= s:$( [+-]? [0-9]+ ) {return parseInt(s)}
|
|
622
|
+
= s:$( [+-]? [0-9]+ ) { return parseInt(s) }
|
|
598
623
|
|
|
599
624
|
identifier
|
|
600
|
-
= !bool !guid s:$([_a-zA-Z][_a-zA-Z0-9"."]*) {return s}
|
|
625
|
+
= !bool !guid s:$([_a-zA-Z][_a-zA-Z0-9"."]*) { return s }
|
|
601
626
|
|
|
602
627
|
guid
|
|
603
|
-
= $(hex16 hex16 "-"? hex16 "-"? hex16 "-"? hex16 "-"? hex16 hex16 hex16)
|
|
628
|
+
= $( hex16 hex16 "-"? hex16 "-"? hex16 "-"? hex16 "-"? hex16 hex16 hex16 )
|
|
604
629
|
|
|
605
630
|
hex16
|
|
606
|
-
= $([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])
|
|
631
|
+
= $( [0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F] )
|
|
607
632
|
|
|
608
|
-
segment
|
|
609
|
-
= val:$([
|
|
633
|
+
segment // > everything except / and ?
|
|
634
|
+
= val:$( [^/?]+ ) { return { val } }
|
|
610
635
|
|
|
611
636
|
skiptokenChars
|
|
612
|
-
= $([a-zA-Z0-9-"."_~!$'()*+,;=:@"/""?"]+)
|
|
637
|
+
= $( [a-zA-Z0-9-"."_~!$'()*+,;=:@"/""?"]+ )
|
|
638
|
+
|
|
639
|
+
binary // > url-safe base64
|
|
640
|
+
= "binary'" s:$([a-zA-Z0-9-_]+ ("=="/"=")?) "'" { return standardBase64(s) }
|
|
613
641
|
|
|
614
642
|
//
|
|
615
643
|
// ---------- Punctuation ----------
|
package/libx/odata/index.js
CHANGED
|
@@ -63,7 +63,7 @@ module.exports = {
|
|
|
63
63
|
try {
|
|
64
64
|
cqn = odata2cqn(url, options)
|
|
65
65
|
} catch (err) {
|
|
66
|
-
if (err.message === 'EXPAND_COUNT_UNSUPPORTED') {
|
|
66
|
+
if (err.message === 'EXPAND_COUNT_UNSUPPORTED' || err.message === 'ORDERBY_LAMBDA_UNSUPPORTED') {
|
|
67
67
|
throw getError(err.statusCode || 400, err.message)
|
|
68
68
|
}
|
|
69
69
|
|
|
@@ -79,7 +79,7 @@ module.exports = {
|
|
|
79
79
|
|
|
80
80
|
// REVISIT: _target vs __target, i.e., pseudo csn vs actual csn
|
|
81
81
|
// DO NOT USE __target outside of libx/rest!!!
|
|
82
|
-
query.__target = cqn.__target
|
|
82
|
+
if (cqn.__target) query.__target = cqn.__target
|
|
83
83
|
|
|
84
84
|
return query
|
|
85
85
|
},
|