@sap/cds 5.5.5 → 5.6.3
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 +139 -1
- package/apis/services.d.ts +31 -1
- package/app/index.js +22 -11
- package/bin/build/buildTaskFactory.js +1 -1
- package/bin/build/provider/buildTaskProviderInternal.js +1 -1
- package/bin/build/provider/fiori/index.js +1 -1
- package/bin/build/provider/hana/2migration.js +8 -7
- package/bin/build/provider/java-cf/index.js +1 -1
- package/bin/deploy/to-hana/hana.js +1 -17
- package/common.cds +8 -0
- package/lib/compile/to/sql.js +22 -2
- package/lib/connect/bindings.js +2 -1
- package/lib/core/reflect.js +4 -1
- package/lib/env/index.js +180 -42
- package/lib/env/requires.js +16 -1
- package/lib/i18n/localize.js +33 -5
- package/lib/index.js +3 -3
- package/lib/log/format/kibana.js +6 -2
- package/lib/ql/Query.js +1 -0
- package/lib/ql/SELECT.js +15 -8
- package/lib/ql/Whereable.js +5 -0
- package/lib/req/context.js +13 -5
- package/lib/serve/Service-dispatch.js +8 -1
- package/lib/utils/axios.js +7 -0
- package/lib/utils/data.js +1 -1
- package/lib/utils/tests.js +1 -1
- package/libx/_runtime/audit/Service.js +18 -18
- package/libx/_runtime/audit/generic/personal/access.js +1 -1
- package/libx/_runtime/audit/generic/personal/modification.js +3 -2
- package/libx/_runtime/audit/generic/personal/utils.js +23 -63
- package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +4 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +37 -35
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +3 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +5 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +84 -34
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +12 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +9 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +8 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +13 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +11 -95
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +17 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/SetResponseHeadersCommand.js +1 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +6 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +3 -34
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +64 -31
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +10 -5
- package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +1 -1
- package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +1 -1
- package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +1 -3
- package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +20 -21
- package/libx/_runtime/cds-services/services/utils/columns.js +6 -1
- package/libx/_runtime/cds-services/services/utils/compareJson.js +1 -8
- package/libx/_runtime/cds-services/services/utils/differ.js +7 -26
- package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -4
- package/libx/_runtime/cds-services/util/assert.js +29 -13
- package/libx/_runtime/cds.js +2 -1
- package/libx/_runtime/common/aspects/Association.js +72 -0
- package/libx/_runtime/common/aspects/any.js +8 -45
- package/libx/_runtime/common/aspects/entity.js +0 -1
- package/libx/_runtime/common/aspects/relation.js +40 -0
- package/libx/_runtime/common/aspects/utils.js +73 -1
- package/libx/_runtime/common/auth/strategies/utils/uaa.js +10 -14
- package/libx/_runtime/common/composition/data.js +3 -2
- package/libx/_runtime/common/composition/delete.js +3 -1
- package/libx/_runtime/common/composition/tree.js +23 -18
- package/libx/_runtime/common/composition/update.js +9 -1
- package/libx/_runtime/common/composition/utils.js +34 -8
- package/libx/_runtime/common/error/frontend.js +6 -1
- package/libx/_runtime/common/generic/auth.js +5 -9
- package/libx/_runtime/common/generic/crud.js +2 -2
- package/libx/_runtime/common/generic/etag.js +11 -8
- package/libx/_runtime/common/generic/input.js +3 -3
- package/libx/_runtime/common/generic/paging.js +9 -5
- package/libx/_runtime/common/generic/put.js +3 -2
- package/libx/_runtime/common/generic/sorting.js +3 -3
- package/libx/_runtime/common/generic/temporal.js +3 -3
- package/libx/_runtime/common/utils/cqn.js +20 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +125 -139
- package/libx/_runtime/common/utils/csn.js +50 -52
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +41 -176
- package/libx/_runtime/common/utils/generateOnCond.js +40 -70
- package/libx/_runtime/common/utils/{enrichWithKeysFromWhere.js → keys.js} +29 -28
- package/libx/_runtime/common/utils/postProcessing.js +3 -0
- package/libx/_runtime/common/utils/propagateForeignKeys.js +84 -0
- package/libx/_runtime/common/utils/resolveStructured.js +1 -1
- package/libx/_runtime/common/utils/resolveView.js +7 -5
- package/libx/_runtime/common/utils/rewriteAsterisks.js +94 -0
- package/libx/_runtime/common/utils/search2cqn4sql.js +9 -8
- package/libx/_runtime/common/utils/template.js +54 -46
- package/libx/_runtime/db/Service.js +9 -2
- package/libx/_runtime/db/expand/expandCQNToJoin.js +54 -33
- package/libx/_runtime/db/expand/rawToExpanded.js +2 -1
- package/libx/_runtime/db/generic/arrayed.js +13 -28
- package/libx/_runtime/db/generic/create.js +1 -0
- package/libx/_runtime/db/generic/input.js +7 -11
- package/libx/_runtime/db/generic/integrity.js +2 -2
- package/libx/_runtime/db/generic/rewrite.js +2 -5
- package/libx/_runtime/db/generic/update.js +1 -0
- package/libx/_runtime/db/query/read.js +9 -4
- package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -2
- package/libx/_runtime/db/sql-builder/annotations.js +1 -0
- package/libx/_runtime/db/utils/columns.js +14 -43
- package/libx/_runtime/fiori/generic/activate.js +3 -2
- package/libx/_runtime/fiori/generic/before.js +2 -2
- package/libx/_runtime/fiori/generic/cancel.js +3 -2
- package/libx/_runtime/fiori/generic/delete.js +3 -2
- package/libx/_runtime/fiori/generic/edit.js +3 -3
- package/libx/_runtime/fiori/generic/new.js +2 -2
- package/libx/_runtime/fiori/generic/patch.js +2 -2
- package/libx/_runtime/fiori/generic/prepare.js +2 -2
- package/libx/_runtime/fiori/generic/read.js +45 -63
- package/libx/_runtime/fiori/generic/readOverDraft.js +4 -4
- package/libx/_runtime/fiori/uiflex/extensibility/index.cds +15 -0
- package/libx/_runtime/fiori/uiflex/extensibility/index.js +148 -0
- package/libx/_runtime/fiori/uiflex/handler/transformREAD.js +119 -0
- package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +43 -0
- package/libx/_runtime/fiori/uiflex/handler/transformWRITE.js +62 -0
- package/libx/_runtime/fiori/uiflex/index.js +35 -0
- package/libx/_runtime/fiori/uiflex/utils.js +78 -0
- package/libx/_runtime/fiori/utils/handler.js +3 -13
- package/libx/_runtime/fiori/utils/where.js +6 -1
- package/libx/_runtime/hana/pool.js +12 -11
- package/libx/_runtime/hana/search2cqn4sql.js +34 -43
- package/libx/_runtime/hana/searchToContains.js +3 -3
- package/libx/_runtime/index.js +5 -2
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
- package/libx/_runtime/messaging/common-utils/AMQPClient.js +16 -3
- package/libx/_runtime/messaging/common-utils/connections.js +11 -14
- package/libx/_runtime/messaging/common-utils/naming-conventions.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -1
- package/libx/_runtime/messaging/message-queuing.js +18 -0
- package/libx/_runtime/remote/Service.js +20 -4
- package/libx/_runtime/remote/utils/client-types.d.ts +7 -0
- package/libx/_runtime/remote/utils/client.js +117 -23
- package/libx/_runtime/sqlite/Service.js +2 -2
- package/libx/_runtime/sqlite/convertAssocToOneManaged.js +1 -3
- package/libx/gql/GraphQLAdapter.js +33 -0
- package/libx/gql/constants/adapter.js +69 -0
- package/libx/gql/constants/cds.js +18 -0
- package/libx/gql/constants/graphql.js +33 -0
- package/libx/gql/resolvers/crud/create.js +15 -0
- package/libx/gql/resolvers/crud/delete.js +24 -0
- package/libx/gql/resolvers/crud/index.js +6 -0
- package/libx/gql/resolvers/crud/read.js +25 -0
- package/libx/gql/resolvers/crud/update.js +31 -0
- package/libx/gql/resolvers/crud/utils/index.js +36 -0
- package/libx/gql/resolvers/field.js +5 -0
- package/libx/gql/resolvers/index.js +7 -0
- package/libx/gql/resolvers/mutation.js +23 -0
- package/libx/gql/resolvers/parse/ast/enrich.js +51 -0
- package/libx/gql/resolvers/parse/ast/fragment.js +11 -0
- package/libx/gql/resolvers/parse/ast/fromObject.js +39 -0
- package/libx/gql/resolvers/parse/ast/index.js +3 -0
- package/libx/gql/resolvers/parse/ast/meta.js +4 -0
- package/libx/gql/resolvers/parse/ast/variable.js +7 -0
- package/libx/gql/resolvers/parse/ast2cqn/columns.js +42 -0
- package/libx/gql/resolvers/parse/ast2cqn/entries.js +31 -0
- package/libx/gql/resolvers/parse/ast2cqn/index.js +8 -0
- package/libx/gql/resolvers/parse/ast2cqn/limit.js +6 -0
- package/libx/gql/resolvers/parse/ast2cqn/orderBy.js +24 -0
- package/libx/gql/resolvers/parse/ast2cqn/utils/index.js +3 -0
- package/libx/gql/resolvers/parse/ast2cqn/where.js +70 -0
- package/libx/gql/resolvers/parse/utils/index.js +8 -0
- package/libx/gql/resolvers/query.js +13 -0
- package/libx/gql/resolvers/root.js +34 -0
- package/libx/gql/schema/generate.js +18 -0
- package/libx/gql/schema/index.js +5 -0
- package/libx/gql/schema/mutation.js +76 -0
- package/libx/gql/schema/query.js +108 -0
- package/libx/gql/schema/typeDefMap.js +45 -0
- package/libx/gql/schema/utils/index.js +54 -0
- package/libx/gql/utils/index.js +12 -0
- package/libx/{_runtime/odata/cqn2odata.js → odata/cqn2odata/index.js} +39 -100
- package/libx/odata/index.js +80 -0
- package/libx/odata/odata2cqn/afterburner.js +170 -0
- package/libx/{_runtime/odata/odata2cqn.pegjs → odata/odata2cqn/grammar.pegjs} +102 -123
- package/libx/odata/odata2cqn/index.js +3 -0
- package/libx/odata/odata2cqn/parser.js +1 -0
- package/libx/odata/utils/index.js +64 -0
- package/libx/rest/RestAdapter.js +101 -0
- package/libx/rest/RestRequest.js +30 -0
- package/libx/rest/index.js +3 -0
- package/libx/rest/middleware/auth.js +22 -0
- package/libx/rest/middleware/content.js +15 -0
- package/libx/rest/middleware/create.js +40 -0
- package/libx/rest/middleware/delete.js +20 -0
- package/libx/rest/middleware/error.js +56 -0
- package/libx/rest/middleware/operation.js +39 -0
- package/libx/rest/middleware/parse.js +90 -0
- package/libx/rest/middleware/read.js +29 -0
- package/libx/rest/middleware/update.js +42 -0
- package/libx/rest/utils/data.js +65 -0
- package/package.json +4 -1
- package/server.js +29 -7
- package/libx/_runtime/cds-services/services/utils/diff.js +0 -53
- package/libx/_runtime/cds-services/util/auditlog.js +0 -247
- package/libx/_runtime/cds-services/util/xsenv.js +0 -51
- package/libx/_runtime/common/utils/backlinks.js +0 -83
- package/libx/_runtime/common/utils/rewriteAsterisk.js +0 -72
- package/libx/_runtime/odata/index.js +0 -55
- package/libx/_runtime/odata/odata2cqn.js +0 -1
- package/libx/_runtime/odata/readToCqn.js +0 -129
- package/libx/_runtime/remote/cqn2odata/index.js +0 -2
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const { formatVal } = require('../utils')
|
|
2
|
+
|
|
1
3
|
const OPERATORS = {
|
|
2
4
|
'=': 'eq',
|
|
3
5
|
'!=': 'ne',
|
|
@@ -10,23 +12,13 @@ const OPERATORS = {
|
|
|
10
12
|
|
|
11
13
|
const LAMBDA_VARIABLE = 'd'
|
|
12
14
|
|
|
13
|
-
const getSafeNumber = str => {
|
|
14
|
-
const n = Number(str)
|
|
15
|
-
return Number.isSafeInteger(n) || String(n) === str ? n : str
|
|
16
|
-
}
|
|
17
|
-
|
|
18
15
|
const needArrayProps = Object.fromEntries(
|
|
19
|
-
['where', 'search', 'xpr', 'columns', '
|
|
16
|
+
['where', 'search', 'xpr', 'columns', 'orderBy', 'ref', 'args'].map(propName => [
|
|
20
17
|
propName,
|
|
21
|
-
cur =>
|
|
22
|
-
(Array.isArray(cur) && (cur.length !== 0 || propName === 'expand' || propName === 'ref')) ||
|
|
23
|
-
(propName === 'expand' && cur === '*')
|
|
18
|
+
cur => Array.isArray(cur) && (cur.length !== 0 || propName === 'expand' || propName === 'ref')
|
|
24
19
|
])
|
|
25
20
|
)
|
|
26
21
|
|
|
27
|
-
const V4UUIDREGEXP = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
|
28
|
-
const isV4UUID = val => V4UUIDREGEXP.test(val)
|
|
29
|
-
|
|
30
22
|
const validators = {
|
|
31
23
|
SELECT: SELECT => SELECT && SELECT.from,
|
|
32
24
|
INSERT: INSERT => {
|
|
@@ -50,63 +42,13 @@ const validators = {
|
|
|
50
42
|
func: func => typeof func === 'string',
|
|
51
43
|
one: count => typeof count === 'boolean',
|
|
52
44
|
as: any => typeof any === 'string',
|
|
45
|
+
expand: any => any === '*' || Array.isArray(any),
|
|
53
46
|
...needArrayProps
|
|
54
47
|
}
|
|
55
48
|
|
|
56
49
|
// strip service & namespace prefixes
|
|
57
50
|
const _entityUrl = path => path.match(/^(\w*\.)*(.*)$/)[2]
|
|
58
51
|
|
|
59
|
-
const formatVal = (val, element, csnTarget, kind) => {
|
|
60
|
-
if (val === null || val === 'null') return 'null'
|
|
61
|
-
if (typeof val === 'boolean') return val
|
|
62
|
-
if (typeof val === 'number') return getSafeNumber(val)
|
|
63
|
-
if (!csnTarget && typeof val === 'string' && isV4UUID(val)) return kind === 'odata-v2' ? `guid'${val}'` : val
|
|
64
|
-
|
|
65
|
-
const csnElement = (csnTarget && csnTarget.elements && csnTarget.elements[element]) || { type: undefined }
|
|
66
|
-
return kind === 'odata-v2' ? _odataV2Val(val, csnElement.type) : _val(val, csnElement.type)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const _isTimestamp = val =>
|
|
70
|
-
/^\d+-\d\d-\d\d(T\d\d:\d\d(:\d\d(\.\d+)?)?(Z|([+-]{1}\d\d:\d\d))?)?$/.test(val) && !isNaN(Date.parse(val))
|
|
71
|
-
|
|
72
|
-
const _odataV2Val = (val, type) => {
|
|
73
|
-
switch (type) {
|
|
74
|
-
case 'cds.Binary':
|
|
75
|
-
case 'cds.LargeBinary':
|
|
76
|
-
return `binary'${val}'`
|
|
77
|
-
case 'cds.Date':
|
|
78
|
-
case 'cds.DateTime':
|
|
79
|
-
return `datetime'${val}'`
|
|
80
|
-
case 'cds.Time':
|
|
81
|
-
// eslint-disable-next-line no-case-declarations
|
|
82
|
-
const [hh, mm, ss] = val.split(':')
|
|
83
|
-
return `time'PT${hh}H${mm}M${ss}S'`
|
|
84
|
-
case 'cds.Timestamp':
|
|
85
|
-
return `datetimeoffset'${val}'`
|
|
86
|
-
case 'cds.UUID':
|
|
87
|
-
return `guid'${val}'`
|
|
88
|
-
default:
|
|
89
|
-
return `'${val}'`
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const _val = (val, type) => {
|
|
94
|
-
switch (type) {
|
|
95
|
-
case 'cds.Decimal':
|
|
96
|
-
case 'cds.Integer64':
|
|
97
|
-
return getSafeNumber(val)
|
|
98
|
-
case 'cds.Boolean':
|
|
99
|
-
case 'cds.DateTime':
|
|
100
|
-
case 'cds.Date':
|
|
101
|
-
case 'cds.Timestamp':
|
|
102
|
-
case 'cds.Time':
|
|
103
|
-
case 'cds.UUID':
|
|
104
|
-
return val
|
|
105
|
-
default:
|
|
106
|
-
return _isTimestamp(val) ? val : `'${val}'`
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
52
|
function getProp(obj, propName) {
|
|
111
53
|
const validate = validators[propName]
|
|
112
54
|
const isValid = validate && validate(obj[propName])
|
|
@@ -166,13 +108,25 @@ const _in = (column, /* in */ collection, target, kind, isLambda) => {
|
|
|
166
108
|
}
|
|
167
109
|
}
|
|
168
110
|
|
|
111
|
+
const _odataV2Func = (func, args) => {
|
|
112
|
+
switch (func) {
|
|
113
|
+
case 'contains':
|
|
114
|
+
// this doesn't support the contains signature with two collections as args, introduced in odata v4.01
|
|
115
|
+
return `substringof(${_args([args[1], args[0]])})`
|
|
116
|
+
default:
|
|
117
|
+
return `${func}(${_args(args)})`
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
169
121
|
const _format = (cur, element, target, kind, isLambda) => {
|
|
170
122
|
if (typeof cur !== 'object') return formatVal(cur, element, target, kind)
|
|
171
123
|
if (hasValidProps(cur, 'ref')) return isLambda ? [LAMBDA_VARIABLE, ...cur.ref].join('/') : cur.ref.join('/')
|
|
172
124
|
if (hasValidProps(cur, 'val')) return formatVal(cur.val, element, target, kind)
|
|
173
125
|
if (hasValidProps(cur, 'xpr')) return `(${_xpr(cur.xpr, target, kind, isLambda)})`
|
|
174
126
|
// REVISIT: How to detect the types for all functions?
|
|
175
|
-
if (hasValidProps(cur, 'func', 'args'))
|
|
127
|
+
if (hasValidProps(cur, 'func', 'args')) {
|
|
128
|
+
return kind === 'odata-v2' ? _odataV2Func(cur.func, cur.args) : `${cur.func}(${_args(cur.args)})`
|
|
129
|
+
}
|
|
176
130
|
}
|
|
177
131
|
|
|
178
132
|
const _isLambda = (cur, next) => {
|
|
@@ -328,62 +282,48 @@ const _parseColumnsV2 = (columns, prefix = []) => {
|
|
|
328
282
|
return { select, expand }
|
|
329
283
|
}
|
|
330
284
|
|
|
331
|
-
const _parseColumns =
|
|
332
|
-
const isExpand = options.expand
|
|
285
|
+
const _parseColumns = columns => {
|
|
333
286
|
const select = []
|
|
334
287
|
const expand = []
|
|
335
288
|
|
|
336
|
-
if (columns === '*') {
|
|
337
|
-
return { select, expand }
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
const isSelectAll = select =>
|
|
341
|
-
select.length === 1 && (select[0] === '*' || (select[0].ref && select[0].ref[0] === '*'))
|
|
342
|
-
|
|
343
289
|
for (const column of columns) {
|
|
344
290
|
if (hasValidProps(column, 'ref')) {
|
|
345
|
-
|
|
346
|
-
let refNameWithOptions = refName
|
|
347
|
-
|
|
291
|
+
let refName = column.ref.join('/')
|
|
348
292
|
if (hasValidProps(column, 'expand')) {
|
|
293
|
+
// REVISIT: incomplete, see test Foo?$expand=invoices($count=true;$expand=item($search="some"))
|
|
294
|
+
if (!columns.some(c => !c.expand)) select.push(refName)
|
|
349
295
|
const curOptions = getOptions(column).join(';')
|
|
350
|
-
|
|
351
|
-
expand.push(
|
|
352
|
-
if (!isSelectAll(select) && !isExpand) select.push(refName)
|
|
296
|
+
refName += curOptions ? `(${curOptions})` : ''
|
|
297
|
+
expand.push(refName)
|
|
353
298
|
// REVISIT: expand to one & limit in options
|
|
354
|
-
// > const expanded = $expand(
|
|
355
|
-
// > expand.push(expanded ? `${
|
|
299
|
+
// > const expanded = $expand(col.expand)
|
|
300
|
+
// > expand.push(expanded ? `${ref}(${expanded})` : ref)
|
|
356
301
|
// see xtest('READ with expand'... in custom handler test
|
|
357
302
|
} else {
|
|
358
|
-
select.push(
|
|
303
|
+
select.push(refName)
|
|
359
304
|
}
|
|
305
|
+
} else if (hasValidProps(column, 'expand') && column.expand === '*') {
|
|
306
|
+
expand.push('*')
|
|
360
307
|
}
|
|
361
|
-
|
|
362
308
|
if (column === '*') {
|
|
363
309
|
select.push(column)
|
|
364
310
|
}
|
|
365
311
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
if (isSelectAll(select)) {
|
|
312
|
+
// omit '$select' option if contains only '*'
|
|
313
|
+
if (select.length === 1 && (select[0] === '*' || (select[0].ref && select[0].ref[0] === '*'))) {
|
|
369
314
|
select.pop()
|
|
370
315
|
}
|
|
371
|
-
|
|
372
|
-
const uniqueSelect = [...new Set(select)]
|
|
373
|
-
return { select: uniqueSelect, expand }
|
|
316
|
+
return { select, expand }
|
|
374
317
|
}
|
|
375
318
|
|
|
376
|
-
function $select(columns, kind, separator = '&'
|
|
377
|
-
const { select, expand } =
|
|
378
|
-
kind === 'odata-v2' ? _parseColumnsV2(columns) : _parseColumns(columns, { expand: isExpand })
|
|
379
|
-
|
|
319
|
+
function $select(columns, kind, separator = '&') {
|
|
320
|
+
const { select, expand } = kind === 'odata-v2' ? _parseColumnsV2(columns) : _parseColumns(columns)
|
|
380
321
|
const res = []
|
|
381
322
|
if (expand.length) res.unshift('$expand=' + expand.join(','))
|
|
382
323
|
if (select.length) res.unshift('$select=' + select.join(','))
|
|
383
324
|
return res.join(separator)
|
|
384
325
|
}
|
|
385
|
-
|
|
386
|
-
const $expand = columns => $select(columns, 'odata', ';', true)
|
|
326
|
+
const $expand = columns => $select(columns, 'odata', ';')
|
|
387
327
|
|
|
388
328
|
function $count(count, kind) {
|
|
389
329
|
if (count !== true) return ''
|
|
@@ -476,7 +416,9 @@ const _isOdataUrlWithKeys = (url, kind) => kind !== 'rest' && /^[\w\.]+\(.*\)/.t
|
|
|
476
416
|
const parsers = {
|
|
477
417
|
columns: (cqnPart, url, kind, target, isCount) => !isCount && $select(cqnPart, kind),
|
|
478
418
|
expand: (cqnPart, url, kind, target, isCount) => !isCount && $expand(cqnPart),
|
|
419
|
+
// eslint-disable-next-line no-unused-vars
|
|
479
420
|
where: (cqnPart, url, kind, target, isCount) => $where(cqnPart, target, kind),
|
|
421
|
+
// eslint-disable-next-line no-unused-vars
|
|
480
422
|
search: (cqnPart, url, kind, target, isCount) => $search(cqnPart, kind),
|
|
481
423
|
orderBy: (cqnPart, url, kind, target, isCount) => !isCount && $orderBy(cqnPart),
|
|
482
424
|
count: (cqnPart, url, kind, target, isCount) => !isCount && $count(cqnPart, kind),
|
|
@@ -503,9 +445,7 @@ function getOptions(cqnPart, url, kind, target, isCount) {
|
|
|
503
445
|
const _isCount = SELECT => {
|
|
504
446
|
if (SELECT.columns) {
|
|
505
447
|
const columns = getProp(SELECT, 'columns')
|
|
506
|
-
return columns.some(
|
|
507
|
-
c => c.func === 'count' && c.as === '$count' && c.args && c.args.length === 1 && c.args[0] === '*'
|
|
508
|
-
)
|
|
448
|
+
return columns.some(c => c.func === 'count' && c.as === '$count')
|
|
509
449
|
}
|
|
510
450
|
return false
|
|
511
451
|
}
|
|
@@ -535,7 +475,6 @@ const _copyData = data => {
|
|
|
535
475
|
? data[property].val
|
|
536
476
|
: data[property]
|
|
537
477
|
}
|
|
538
|
-
|
|
539
478
|
return copied
|
|
540
479
|
}
|
|
541
480
|
|
|
@@ -581,4 +520,4 @@ function cqn2odata(cqn, kind, model) {
|
|
|
581
520
|
throw new Error('Unknown CQN object cannot be translated to URL: ' + JSON.stringify(cqn))
|
|
582
521
|
}
|
|
583
522
|
|
|
584
|
-
module.exports =
|
|
523
|
+
module.exports = cqn2odata
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const cds = require('../_runtime/cds')
|
|
2
|
+
const { SELECT } = cds.ql
|
|
3
|
+
|
|
4
|
+
const odata2cqn = require('./odata2cqn')
|
|
5
|
+
const cqn2odata = require('./cqn2odata')
|
|
6
|
+
|
|
7
|
+
const afterburner = require('./odata2cqn/afterburner')
|
|
8
|
+
const { getSafeNumber: safeNumber } = require('./utils')
|
|
9
|
+
|
|
10
|
+
const strict = {
|
|
11
|
+
functions: {
|
|
12
|
+
contains: 1,
|
|
13
|
+
startswith: 1,
|
|
14
|
+
endswith: 1,
|
|
15
|
+
tolower: 1,
|
|
16
|
+
toupper: 1,
|
|
17
|
+
length: 1,
|
|
18
|
+
indexof: 1,
|
|
19
|
+
substring: 1,
|
|
20
|
+
trim: 1,
|
|
21
|
+
concat: 1,
|
|
22
|
+
year: 1,
|
|
23
|
+
month: 1,
|
|
24
|
+
day: 1,
|
|
25
|
+
hour: 1,
|
|
26
|
+
minute: 1,
|
|
27
|
+
second: 1,
|
|
28
|
+
time: 1,
|
|
29
|
+
now: 1
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/*
|
|
34
|
+
* cds.odata API
|
|
35
|
+
*/
|
|
36
|
+
module.exports = {
|
|
37
|
+
parse: (url, options = {}) => {
|
|
38
|
+
// first arg may also be req
|
|
39
|
+
if (url.url) url = url.url
|
|
40
|
+
// REVISIT: for okra, remove when no longer needed
|
|
41
|
+
else if (url.getIncomingRequest) url = url.getIncomingRequest().url
|
|
42
|
+
url = decodeURIComponent(url)
|
|
43
|
+
|
|
44
|
+
options = options === 'strict' ? { strict } : options.strict ? { ...options, strict } : options
|
|
45
|
+
if (options.service) Object.assign(options, { minimal: true, afterburner: afterburner.for(options.service) })
|
|
46
|
+
options.safeNumber = safeNumber
|
|
47
|
+
|
|
48
|
+
let cqn
|
|
49
|
+
try {
|
|
50
|
+
cqn = odata2cqn(url, options)
|
|
51
|
+
} catch (e) {
|
|
52
|
+
// REVISIT: additional try in catch isn't nice -> find better way
|
|
53
|
+
// known gaps -> e.message is a stringified error -> use that
|
|
54
|
+
// unknown errors -> e is the error to keep
|
|
55
|
+
let err = e
|
|
56
|
+
try {
|
|
57
|
+
err = JSON.parse(e.message)
|
|
58
|
+
} catch {
|
|
59
|
+
/* nothing to do */
|
|
60
|
+
}
|
|
61
|
+
err.message = 'Parsing URL failed with error: ' + err.message
|
|
62
|
+
err.statusCode = err.statusCode || 400
|
|
63
|
+
throw err
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (typeof options.afterburner === 'function') cqn = options.afterburner(cqn)
|
|
67
|
+
|
|
68
|
+
const query = cqn.SELECT.one ? SELECT.one(cqn.SELECT.from) : SELECT.from(cqn.SELECT.from)
|
|
69
|
+
Object.assign(query.SELECT, cqn.SELECT)
|
|
70
|
+
|
|
71
|
+
// REVISIT: _target vs __target, i.e., pseudo csn vs actual csn
|
|
72
|
+
// DO NOT USE __target outside of libx/rest!!!
|
|
73
|
+
query.__target = cqn.__target
|
|
74
|
+
|
|
75
|
+
return query
|
|
76
|
+
},
|
|
77
|
+
urlify: (cqn, options = {}) => {
|
|
78
|
+
return cqn2odata(cqn, options.kind, options.model)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
const cds = require('../../_runtime/cds')
|
|
2
|
+
|
|
3
|
+
const { where2obj } = require('../../_runtime/common/utils/cqn')
|
|
4
|
+
const { findCsnTargetFor } = require('../../_runtime/common/utils/csn')
|
|
5
|
+
|
|
6
|
+
function _keysOf(entity) {
|
|
7
|
+
return entity && entity.keys
|
|
8
|
+
? Object.keys(entity.keys).filter(
|
|
9
|
+
k => entity.elements[k].type !== 'cds.Association' && entity.elements[k]['@odata.foreignKey4'] !== 'up_'
|
|
10
|
+
)
|
|
11
|
+
: []
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function _getDefinition(definition, name) {
|
|
15
|
+
return (
|
|
16
|
+
(definition.definitions && definition.definitions[name]) ||
|
|
17
|
+
(definition.elements && definition.elements[name]) ||
|
|
18
|
+
(definition.actions && definition.actions[name]) ||
|
|
19
|
+
definition[name]
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function _resolveAliasInWhere(where, entity) {
|
|
24
|
+
if (!entity._alias2ref) return
|
|
25
|
+
for (let i = 0; i < where.length; i++) {
|
|
26
|
+
if (!where[i].ref || where[i].ref.length > 1 || entity.keys[where[i].ref[0]]) continue
|
|
27
|
+
where[i].ref = entity._alias2ref[where[i].ref[0]] || where[i].ref
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// case: single key without name, e.g., Foo(1)
|
|
32
|
+
function _addRefToWhereIfNecessary(where, entity) {
|
|
33
|
+
if (!where || where.length !== 1) return 0
|
|
34
|
+
const keys = _keysOf(entity)
|
|
35
|
+
if (keys.length !== 1) return 0
|
|
36
|
+
where.unshift(...[{ ref: [keys[0]] }, '='])
|
|
37
|
+
return 1
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function _processSegments(cqn, model) {
|
|
41
|
+
const { ref } = cqn.SELECT.from
|
|
42
|
+
|
|
43
|
+
let current = model
|
|
44
|
+
let path
|
|
45
|
+
let keys = null
|
|
46
|
+
let keyCount = 0
|
|
47
|
+
let incompleteKeys
|
|
48
|
+
let one
|
|
49
|
+
for (let i = 0; i < ref.length; i++) {
|
|
50
|
+
const seg = ref[i].id || ref[i]
|
|
51
|
+
const params = ref[i].where && where2obj(ref[i].where)
|
|
52
|
+
|
|
53
|
+
if (incompleteKeys) {
|
|
54
|
+
// > key
|
|
55
|
+
keys = keys || _keysOf(current)
|
|
56
|
+
const key = keys[keyCount++]
|
|
57
|
+
one = true
|
|
58
|
+
const element = current.elements[key]
|
|
59
|
+
let base = ref[i - keyCount]
|
|
60
|
+
if (!base.id) base = { id: base, where: [] }
|
|
61
|
+
if (base.where.length) base.where.push('and')
|
|
62
|
+
base.where.push({ ref: [key] }, '=', { val: element.type === 'cds.Integer' ? Number(seg) : seg })
|
|
63
|
+
ref[i] = null
|
|
64
|
+
ref[i - keyCount] = base
|
|
65
|
+
incompleteKeys = keyCount < keys.length
|
|
66
|
+
} else {
|
|
67
|
+
// > entity or property (incl. nested) or navigation or action or function
|
|
68
|
+
keys = null
|
|
69
|
+
keyCount = 0
|
|
70
|
+
one = false
|
|
71
|
+
|
|
72
|
+
path = path ? path + `${path.match(/:/) ? '.' : ':'}${seg}` : seg
|
|
73
|
+
// REVISIT: replace use case: <namespace>.<entity>_history is at <namespace>.<entity>.history
|
|
74
|
+
current = _getDefinition(current, seg) || _getDefinition(current, seg.replace(/_/g, '.'))
|
|
75
|
+
// REVISIT: 404 or 400?
|
|
76
|
+
if (!current) cds.error(`Invalid resource path "${path}"`, { code: 404 })
|
|
77
|
+
|
|
78
|
+
if (current.kind === 'entity') {
|
|
79
|
+
// > entity
|
|
80
|
+
one = !!(ref[i].where || current._isSingleton)
|
|
81
|
+
incompleteKeys = ref[i].where ? false : i === ref.length - 1 || one ? false : true
|
|
82
|
+
if (ref[i].where) {
|
|
83
|
+
keyCount += _addRefToWhereIfNecessary(ref[i].where, current)
|
|
84
|
+
_resolveAliasInWhere(ref[i].where, current)
|
|
85
|
+
}
|
|
86
|
+
} else if ({ action: 1, function: 1 }[current.kind]) {
|
|
87
|
+
// > action or function
|
|
88
|
+
if (i !== ref.length - 1) {
|
|
89
|
+
const msg = `${i ? 'Unbound' : 'Bound'} ${current.kind} are only supported as the last path segment.`
|
|
90
|
+
throw Object.assign(new Error(msg), { statusCode: 501 })
|
|
91
|
+
}
|
|
92
|
+
ref[i] = { operation: current.name }
|
|
93
|
+
if (params) ref[i].args = params
|
|
94
|
+
if (current.returns && current.returns.type) one = true
|
|
95
|
+
} else if (current.isAssociation) {
|
|
96
|
+
// > navigation
|
|
97
|
+
one = !!(current.is2one || ref[i].where)
|
|
98
|
+
incompleteKeys = one || i === ref.length - 1 ? false : true
|
|
99
|
+
current = model.definitions[current.target]
|
|
100
|
+
if (ref[i].where) {
|
|
101
|
+
keyCount += _addRefToWhereIfNecessary(ref[i].where, current)
|
|
102
|
+
_resolveAliasInWhere(ref[i].where, current)
|
|
103
|
+
}
|
|
104
|
+
} else if (current._isStructured) {
|
|
105
|
+
// > nested property
|
|
106
|
+
one = true
|
|
107
|
+
current = current.elements
|
|
108
|
+
} else {
|
|
109
|
+
// > property
|
|
110
|
+
one = true
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (incompleteKeys) {
|
|
116
|
+
// > last segment not fully qualified
|
|
117
|
+
throw Object.assign(
|
|
118
|
+
new Error(
|
|
119
|
+
`Entity "${current.name}" has ${_keysOf(current).length} keys. Only ${keyCount} ${
|
|
120
|
+
keyCount === 1 ? 'was' : 'were'
|
|
121
|
+
} provided.`
|
|
122
|
+
),
|
|
123
|
+
{ status: 400 }
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// remove all nulled refs
|
|
128
|
+
cqn.SELECT.from.ref = ref.filter(r => r)
|
|
129
|
+
|
|
130
|
+
// one?
|
|
131
|
+
if (one) cqn.SELECT.one = true
|
|
132
|
+
|
|
133
|
+
// REVISIT: better
|
|
134
|
+
// set target (csn definition) for later retrieval
|
|
135
|
+
cqn.__target = current
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function _4service(service) {
|
|
139
|
+
const { namespace, model } = service
|
|
140
|
+
|
|
141
|
+
return cqn => {
|
|
142
|
+
const { ref } = cqn.SELECT.from
|
|
143
|
+
|
|
144
|
+
// REVISIT: shouldn't be necessary
|
|
145
|
+
/*
|
|
146
|
+
* make first path segment fully qualified
|
|
147
|
+
*/
|
|
148
|
+
const root = findCsnTargetFor(ref[0].id || ref[0], model, namespace)
|
|
149
|
+
// REVISIT: 404 or 400?
|
|
150
|
+
if (!root) cds.error(`Invalid resource path "${namespace}.${ref[0].id || ref[0]}"`, { code: 404 })
|
|
151
|
+
if (ref[0].id) ref[0].id = root.name
|
|
152
|
+
else ref[0] = root.name
|
|
153
|
+
|
|
154
|
+
/*
|
|
155
|
+
* key vs. path segments (/Books/1/author/books/2/...) and more
|
|
156
|
+
*/
|
|
157
|
+
_processSegments(cqn, model)
|
|
158
|
+
|
|
159
|
+
return cqn
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const cache = new WeakMap()
|
|
164
|
+
|
|
165
|
+
module.exports = {
|
|
166
|
+
for: service => {
|
|
167
|
+
if (!cache.has(service)) cache.set(service, _4service(service))
|
|
168
|
+
return cache.get(service)
|
|
169
|
+
}
|
|
170
|
+
}
|