@sap/cds 5.6.4 → 5.7.4
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 +134 -0
- package/_i18n/i18n_fr.properties +4 -4
- package/apis/cds.d.ts +7 -10
- package/apis/connect.d.ts +3 -3
- package/apis/core.d.ts +2 -4
- package/apis/models.d.ts +2 -3
- package/apis/ql.d.ts +0 -1
- package/apis/services.d.ts +3 -3
- package/bin/build/buildTaskFactory.js +16 -10
- package/bin/build/buildTaskProviderFactory.js +3 -3
- package/bin/build/constants.js +2 -1
- package/bin/build/provider/buildTaskProviderInternal.js +14 -14
- package/bin/build/provider/hana/2migration.js +2 -3
- package/bin/build/provider/hana/index.js +34 -0
- package/bin/build/provider/hana/migrationtable.js +90 -22
- package/bin/build/provider/hana/template/undeploy.json +5 -0
- package/bin/build/provider/node-cf/index.js +9 -2
- package/bin/serve.js +16 -18
- package/lib/compile/cdsc.js +15 -5
- package/lib/compile/etc/_localized.js +4 -4
- package/lib/compile/extend.js +8 -0
- package/lib/compile/index.js +3 -1
- package/lib/compile/minify.js +61 -0
- package/lib/compile/resolve.js +4 -1
- package/lib/compile/to/gql.js +9 -0
- package/lib/compile/to/sql.js +26 -30
- package/lib/connect/index.js +1 -1
- package/lib/core/entities.js +0 -3
- package/lib/core/infer.js +1 -0
- package/lib/core/reflect.js +0 -34
- package/lib/deploy.js +25 -17
- package/lib/env/defaults.js +3 -1
- package/lib/env/index.js +8 -3
- package/lib/env/presets.js +38 -0
- package/lib/env/requires.js +16 -11
- package/lib/index.js +13 -11
- package/lib/log/format/kibana.js +3 -1
- package/lib/log/index.js +2 -2
- package/lib/req/cds-context.js +79 -0
- package/lib/req/context.js +5 -77
- package/lib/req/request.js +1 -1
- package/lib/serve/Service-api.js +8 -4
- package/lib/serve/Service-dispatch.js +0 -7
- package/lib/serve/Service-methods.js +6 -8
- package/lib/serve/Transaction.js +35 -30
- package/lib/serve/adapters.js +1 -4
- package/lib/utils/axios.js +1 -1
- package/libx/_runtime/audit/Service.js +44 -20
- package/libx/_runtime/audit/generic/personal/access.js +16 -11
- package/libx/_runtime/audit/generic/personal/modification.js +5 -5
- package/libx/_runtime/audit/generic/personal/utils.js +46 -37
- package/libx/_runtime/{common/auth → auth}/index.js +21 -7
- package/libx/_runtime/{common/auth → auth}/strategies/JWT.js +2 -2
- package/libx/_runtime/{common/auth → auth}/strategies/basic.js +2 -2
- package/libx/_runtime/{common/auth → auth}/strategies/dummy.js +1 -1
- package/libx/_runtime/{common/auth → auth}/strategies/mock.js +2 -2
- package/libx/_runtime/{common/auth → auth}/strategies/utils/uaa.js +1 -1
- package/libx/_runtime/{common/auth → auth}/strategies/utils/xssec.js +0 -0
- package/libx/_runtime/{common/auth → auth}/strategies/xsuaa.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +7 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +0 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +0 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +3 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +6 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +3 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +2 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +16 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +26 -69
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +0 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +3 -66
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +29 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +7 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +4 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +13 -10
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/ConditionalRequestControlCommand.js +0 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +0 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +54 -76
- package/libx/_runtime/cds-services/adapter/rest/RestRequest.js +0 -7
- package/libx/_runtime/cds-services/adapter/rest/handlers/create.js +3 -6
- package/libx/_runtime/cds-services/adapter/rest/handlers/delete.js +3 -6
- package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +3 -6
- package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +3 -6
- package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +3 -6
- package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +8 -4
- package/libx/_runtime/cds-services/services/Service.js +1 -7
- package/libx/_runtime/cds-services/services/utils/columns.js +10 -3
- package/libx/_runtime/cds-services/services/utils/compareJson.js +3 -6
- package/libx/_runtime/cds-services/services/utils/differ.js +4 -1
- package/libx/_runtime/cds-services/services/utils/handlerUtils.js +1 -41
- package/libx/_runtime/cds-services/util/assert.js +1 -262
- package/libx/_runtime/cds.js +6 -9
- package/libx/_runtime/common/aspects/entity.js +1 -1
- package/libx/_runtime/common/composition/delete.js +4 -2
- package/libx/_runtime/common/composition/update.js +22 -38
- package/libx/_runtime/common/composition/utils.js +3 -7
- package/libx/_runtime/common/error/standardError.js +11 -0
- package/libx/_runtime/common/generic/auth.js +63 -33
- package/libx/_runtime/common/generic/crud.js +11 -23
- package/libx/_runtime/common/generic/input.js +20 -0
- package/libx/_runtime/common/generic/put.js +4 -10
- package/libx/_runtime/common/generic/sorting.js +12 -30
- package/libx/_runtime/common/i18n/messages.properties +2 -0
- package/libx/_runtime/common/perf/index.js +24 -0
- package/libx/_runtime/common/utils/cqn.js +58 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +298 -121
- package/libx/_runtime/common/utils/csn.js +38 -56
- package/libx/_runtime/common/utils/entityFromCqn.js +6 -6
- package/libx/_runtime/common/utils/resolveView.js +4 -5
- package/libx/_runtime/common/utils/rewriteAsterisks.js +46 -5
- package/libx/_runtime/common/utils/search2cqn4sql.js +21 -9
- package/libx/_runtime/common/utils/structured.js +35 -25
- package/libx/_runtime/db/Service.js +0 -6
- package/libx/_runtime/db/data-conversion/post-processing.js +22 -22
- package/libx/_runtime/db/expand/expand-v2.js +130 -0
- package/libx/_runtime/db/expand/expandCQNToJoin.js +57 -75
- package/libx/_runtime/db/expand/index.js +3 -1
- package/libx/_runtime/db/generic/input.js +52 -10
- package/libx/_runtime/db/generic/integrity.js +367 -26
- package/libx/_runtime/db/generic/virtual.js +51 -13
- package/libx/_runtime/db/query/read.js +12 -8
- package/libx/_runtime/db/query/update.js +9 -3
- package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +8 -9
- package/libx/_runtime/{common → db}/utils/propagateForeignKeys.js +11 -14
- package/libx/_runtime/fiori/generic/activate.js +1 -0
- package/libx/_runtime/fiori/generic/before.js +2 -1
- package/libx/_runtime/fiori/generic/edit.js +1 -0
- package/libx/_runtime/fiori/generic/patch.js +1 -1
- package/libx/_runtime/fiori/generic/read.js +128 -57
- package/libx/_runtime/fiori/uiflex/index.js +1 -1
- package/libx/_runtime/fiori/uiflex/{extensibility/index.js → service.js} +3 -3
- package/libx/_runtime/fiori/utils/delete.js +7 -1
- package/libx/_runtime/hana/Service.js +1 -8
- package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -14
- package/libx/_runtime/hana/execute.js +10 -4
- package/libx/_runtime/hana/pool.js +55 -45
- package/libx/_runtime/hana/search.js +7 -6
- package/libx/_runtime/hana/search2cqn4sql.js +8 -5
- package/libx/_runtime/hana/searchToContains.js +3 -1
- package/libx/_runtime/index.js +5 -5
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +3 -3
- package/libx/_runtime/messaging/Outbox.js +53 -0
- package/libx/_runtime/messaging/common-utils/AMQPClient.js +17 -10
- package/libx/_runtime/messaging/common-utils/connections.js +14 -9
- package/libx/_runtime/messaging/common-utils/waitingTime.js +2 -0
- package/libx/_runtime/messaging/enterprise-messaging-shared.js +2 -3
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -2
- package/libx/_runtime/messaging/enterprise-messaging.js +21 -15
- package/libx/_runtime/messaging/file-based.js +5 -5
- package/libx/_runtime/messaging/message-queuing-utils/options-messaging.js +1 -0
- package/libx/_runtime/messaging/message-queuing.js +2 -3
- package/libx/_runtime/messaging/outbox/OutboxRunner.js +75 -0
- package/libx/_runtime/messaging/outbox/utils.js +192 -0
- package/libx/_runtime/messaging/service.js +16 -30
- package/libx/_runtime/remote/Service.js +15 -0
- package/libx/_runtime/remote/utils/client.js +15 -3
- package/libx/_runtime/remote/utils/{dataConversion.js → data.js} +12 -2
- package/libx/_runtime/sqlite/Service.js +7 -10
- package/libx/_runtime/sqlite/customBuilder/CustomExpressionBuilder.js +19 -0
- package/libx/_runtime/sqlite/execute.js +18 -12
- package/libx/_runtime/types/api.js +2 -1
- package/libx/gql/resolvers/parse/ast2cqn/columns.js +1 -1
- package/libx/odata/{odata2cqn/afterburner.js → afterburner.js} +19 -15
- package/libx/odata/{cqn2odata/index.js → cqn2odata.js} +1 -1
- package/libx/odata/{odata2cqn/grammar.pegjs → grammar.pegjs} +217 -148
- package/libx/odata/index.js +21 -13
- package/libx/odata/parser.js +1 -0
- package/libx/odata/utils.js +57 -0
- package/libx/rest/RestAdapter.js +2 -6
- package/libx/rest/utils/data.js +1 -6
- package/package.json +4 -3
- package/server.js +4 -5
- package/srv/audit-log.cds +87 -0
- package/{libx/_runtime/fiori/uiflex/extensibility/index.cds → srv/flex.cds} +0 -0
- package/srv/flex.js +1 -0
- package/srv/outbox.cds +11 -0
- package/srv/outbox.js +0 -0
- package/libx/_runtime/cds-services/adapter/perf/performance.js +0 -104
- package/libx/_runtime/cds-services/adapter/perf/performanceMeasurement.js +0 -33
- package/libx/odata/odata2cqn/index.js +0 -3
- package/libx/odata/odata2cqn/parser.js +0 -1
- package/libx/odata/readme.md +0 -1
- package/libx/odata/utils/index.js +0 -64
|
@@ -27,9 +27,6 @@
|
|
|
27
27
|
//
|
|
28
28
|
// ---------- JavaScript Helpers -------------
|
|
29
29
|
{
|
|
30
|
-
const exception = (message, code = 400) => {
|
|
31
|
-
error(JSON.stringify({ code, message }))
|
|
32
|
-
}
|
|
33
30
|
const $ = Object.assign
|
|
34
31
|
const { strict, minimal } = options
|
|
35
32
|
const stack = []
|
|
@@ -42,94 +39,101 @@
|
|
|
42
39
|
return Number.isSafeInteger(n) ? n : str
|
|
43
40
|
}
|
|
44
41
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
42
|
+
const _compareRefs = col => exp => col === exp ||
|
|
43
|
+
(col.as && exp.as && col.as === exp.as) ||
|
|
44
|
+
(exp.as && col.ref && exp.as === col.ref[col.ref.length - 1]) ||
|
|
45
|
+
(col.ref && exp.ref && col.ref.join('') === exp.ref.join(''))
|
|
46
|
+
const _remapFunc = columns => c => {
|
|
47
|
+
if (Array.isArray(c)) return c.map(_remapFunc(columns))
|
|
48
|
+
const fnObj = c.ref && columns.find(col => col.as && col.func && col.as === c.ref[0])
|
|
49
|
+
if (fnObj) return fnObj
|
|
50
|
+
return c
|
|
51
|
+
}
|
|
52
|
+
const _replaceNullRef = groupBy => c => {
|
|
53
|
+
if (Array.isArray(c)) return c.map(_replaceNullRef(groupBy))
|
|
54
|
+
if (c.ref && !groupBy.find(_compareRefs(c))) return { val: null }
|
|
55
|
+
return c
|
|
56
|
+
}
|
|
57
|
+
const _expand = (columns, col) => {
|
|
58
|
+
if (col.ref.length === 1) {
|
|
59
|
+
if (!columns.find(_compareRefs(col))) columns.push(col)
|
|
60
|
+
} else {
|
|
61
|
+
const assoc = col.ref.shift()
|
|
62
|
+
const exp = columns.find(c => c.ref && c.ref[0] === assoc)
|
|
63
|
+
if (exp) {
|
|
64
|
+
_expand(exp.expand, col)
|
|
65
|
+
} else {
|
|
66
|
+
const exp = {
|
|
67
|
+
ref: [assoc],
|
|
68
|
+
expand: []
|
|
69
|
+
}
|
|
70
|
+
_expand(exp.expand, col)
|
|
71
|
+
columns.push(exp)
|
|
66
72
|
}
|
|
67
|
-
// REVISIT: { val:null } for should be also implemented
|
|
68
73
|
}
|
|
69
|
-
|
|
70
|
-
return changedWhere;
|
|
71
74
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const aggregates = columns.filter((cur) => cur.as);
|
|
78
|
-
|
|
79
|
-
let fromAggregate = [];
|
|
80
|
-
let fromGroupBy = [];
|
|
81
|
-
|
|
82
|
-
// handle $apply=aggregate(... as someProp)&$select=someProp,?...
|
|
83
|
-
if (aggregates.length !== 0) {
|
|
84
|
-
fromAggregate = columns.filter((cur) =>
|
|
85
|
-
cur.ref ? aggregates.includes(cur.ref.join('')) : true
|
|
86
|
-
);
|
|
75
|
+
const _handleApply = (cqn, apply, onlyColumnsFromExpand = false) => {
|
|
76
|
+
if (!apply) return
|
|
77
|
+
if (cqn.apply) delete cqn.apply
|
|
78
|
+
if (apply.apply || (apply.where && cqn.where) || (apply.search && cqn.search)) {
|
|
79
|
+
cqn.from = { SELECT: { from: cqn.from } }
|
|
87
80
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const allowedNames = groupBy.map(({ ref }) => ref && ref.join(''));
|
|
92
|
-
const allowedColumns = columns.filter((cur) =>
|
|
93
|
-
cur.ref && allowedNames.includes(cur.ref.join(''))
|
|
94
|
-
);
|
|
95
|
-
fromGroupBy = allowedColumns.length === 0 ? [...groupBy] : allowedColumns;
|
|
81
|
+
if (apply.where) {
|
|
82
|
+
if (cqn.where) cqn.from.SELECT.where = apply.where
|
|
83
|
+
else cqn.where = apply.where
|
|
96
84
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
: SELECT.columns;
|
|
101
|
-
|
|
102
|
-
let result = { ...SELECT }
|
|
103
|
-
if (newColumns) result.columns = newColumns
|
|
104
|
-
let newWhere = [];
|
|
105
|
-
|
|
106
|
-
if (where && (groupBy || aggregates.length !== 0)) {
|
|
107
|
-
// changing { ref: null } for aggregated-away props
|
|
108
|
-
const colNames = columns.map((cur) => cur.ref && cur.ref.join('') || cur.as);
|
|
109
|
-
result = { ...result, where: correctAggAwayWhere(where, colNames) }
|
|
85
|
+
if (apply.search) {
|
|
86
|
+
if (cqn.search) cqn.from.SELECT.search = apply.search
|
|
87
|
+
else cqn.search = apply.search
|
|
110
88
|
}
|
|
111
|
-
|
|
112
|
-
|
|
89
|
+
if (apply.groupBy) {
|
|
90
|
+
cqn.groupBy = []
|
|
91
|
+
for (const col of apply.groupBy) {
|
|
92
|
+
if (!cqn.groupBy.find(_compareRefs(col))) cqn.groupBy.push(col)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const aggregatedColumns = [...(cqn.groupBy || []), ...(apply.aggregate || [])]
|
|
96
|
+
if (aggregatedColumns.length) {
|
|
97
|
+
cqn.columns = cqn.columns && !onlyColumnsFromExpand
|
|
98
|
+
// using .reduce instead of .filter since columns order might be important
|
|
99
|
+
? cqn.columns.reduce((columns, col) => {
|
|
100
|
+
const aggregatedColumn = aggregatedColumns.find(_compareRefs(col))
|
|
101
|
+
if (aggregatedColumn) columns.push(aggregatedColumn)
|
|
102
|
+
return columns
|
|
103
|
+
}, [])
|
|
104
|
+
: aggregatedColumns
|
|
105
|
+
if (cqn.where) {
|
|
106
|
+
cqn.where = cqn.where.map(_remapFunc(cqn.columns))
|
|
107
|
+
if (cqn.groupBy) {
|
|
108
|
+
cqn.having = cqn.where.map(_replaceNullRef(cqn.groupBy))
|
|
109
|
+
delete cqn.where
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// expand navigation refs in aggregated columns
|
|
113
|
+
cqn.columns = cqn.columns.reduce((columns, col) => {
|
|
114
|
+
if (col.ref && col.ref.length > 1 && aggregatedColumns.find(_compareRefs(col))) {
|
|
115
|
+
_expand(columns, { ref: [...col.ref] })
|
|
116
|
+
} else columns.push(col)
|
|
117
|
+
return columns
|
|
118
|
+
}, [])
|
|
119
|
+
}
|
|
120
|
+
if (apply.apply) _handleApply(cqn.from.SELECT, apply.apply)
|
|
121
|
+
}
|
|
122
|
+
const _setLimitOffset = val => {
|
|
123
|
+
if (SELECT.limit && SELECT.limit.offset && SELECT.limit.offset.val) {
|
|
124
|
+
val += SELECT.limit.offset.val
|
|
125
|
+
}
|
|
126
|
+
(SELECT.limit || (SELECT.limit={})).offset = {val}
|
|
113
127
|
}
|
|
114
128
|
|
|
115
|
-
const
|
|
116
|
-
(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (!head) head = ''
|
|
121
|
-
if (filter) {
|
|
122
|
-
head += filter.reduce((acc, cur) => {
|
|
123
|
-
for (const c of cur) {
|
|
124
|
-
if (typeof c === 'string') acc += c
|
|
125
|
-
else acc += c.val ? typeof c.val === 'string' ? `'${c.val}'` : c.val : c.ref[0]
|
|
126
|
-
}
|
|
127
|
-
return acc
|
|
128
|
-
}, '')
|
|
129
|
-
filter = undefined
|
|
129
|
+
const _removeLambdaPrefix = (prefix, elements) => {
|
|
130
|
+
for (const e of elements) {
|
|
131
|
+
// remove the prefix identifier
|
|
132
|
+
if (e.ref && e.ref[0] === prefix) e.ref.shift()
|
|
133
|
+
if (e.func) _removeLambdaPrefix(prefix, e.args)
|
|
130
134
|
}
|
|
131
|
-
|
|
132
|
-
return
|
|
135
|
+
|
|
136
|
+
return elements
|
|
133
137
|
}
|
|
134
138
|
}
|
|
135
139
|
|
|
@@ -145,11 +149,19 @@
|
|
|
145
149
|
delete SELECT.expand
|
|
146
150
|
delete SELECT.limit
|
|
147
151
|
delete SELECT.orderBy
|
|
152
|
+
if (SELECT.apply) {
|
|
153
|
+
SELECT.apply = { apply: SELECT.apply }
|
|
154
|
+
_handleApply(SELECT, SELECT.apply)
|
|
155
|
+
}
|
|
148
156
|
return { SELECT }
|
|
149
157
|
}
|
|
158
|
+
let onlyColumnsFromExpand
|
|
150
159
|
if (SELECT.expand) {
|
|
151
160
|
// Books?$expand=author w/o $select=author
|
|
152
|
-
if (!SELECT.columns)
|
|
161
|
+
if (!SELECT.columns) {
|
|
162
|
+
SELECT.columns = ['*']
|
|
163
|
+
onlyColumnsFromExpand = true
|
|
164
|
+
}
|
|
153
165
|
for (const exp of SELECT.expand) {
|
|
154
166
|
const idx = SELECT.columns.findIndex(_compareRefs(exp))
|
|
155
167
|
if (idx > -1) SELECT.columns.splice(idx, 1)
|
|
@@ -157,24 +169,26 @@
|
|
|
157
169
|
}
|
|
158
170
|
delete SELECT.expand
|
|
159
171
|
}
|
|
160
|
-
SELECT
|
|
161
|
-
|
|
172
|
+
if (SELECT.count && SELECT.apply) SELECT.__countAggregated = true
|
|
173
|
+
_handleApply(SELECT, SELECT.apply, onlyColumnsFromExpand)
|
|
162
174
|
return { SELECT }
|
|
163
175
|
}
|
|
164
176
|
|
|
165
177
|
path
|
|
166
178
|
= "$count" {count = true}
|
|
167
179
|
/ rv:$("$ref"/"$value") {return !TECHNICAL_OPTS.includes(rv) && {from: {ref: [rv]}}}
|
|
168
|
-
/ head:(
|
|
169
|
-
|
|
170
|
-
|
|
180
|
+
/ head:(
|
|
181
|
+
(identifier filter:(OPEN CLOSE/OPEN args CLOSE)? !segment) / val:(s:(!string val){return s[1]} / segment){return [val]}
|
|
182
|
+
) tail:( '/' p:path {return p} )? {
|
|
183
|
+
const [id, filter] = head
|
|
171
184
|
// minimal: val also as path segment
|
|
172
185
|
const ref = [
|
|
173
186
|
filter
|
|
174
187
|
? filter.length > 2
|
|
175
|
-
? { id
|
|
176
|
-
: { id
|
|
177
|
-
|
|
188
|
+
? { id, where: filter[1].map(f => f.val && f.val.match && f.val.match(/^"(.*)"$/) ? { val: f.val.match(/^"(.*)"$/)[1] } : f) }
|
|
189
|
+
: { id, where: [] }
|
|
190
|
+
// hasOwnProperty in case of '{val:0}' and so on
|
|
191
|
+
: ( minimal ? `${typeof id === 'object' && Object.prototype.hasOwnProperty.call(id, 'val') ? id.val : id}` : id )
|
|
178
192
|
]
|
|
179
193
|
if (tail && tail.from) {
|
|
180
194
|
const more = tail.from.ref
|
|
@@ -188,7 +202,7 @@
|
|
|
188
202
|
|
|
189
203
|
args
|
|
190
204
|
= val:val {return [val]}
|
|
191
|
-
/ ref:ref o"="o val:
|
|
205
|
+
/ ref:ref o"="o val:val more:( COMMA args )? {
|
|
192
206
|
const args = [ ref, '=', val ]
|
|
193
207
|
if (more) args.push ('and', ...more[1])
|
|
194
208
|
return args
|
|
@@ -197,19 +211,23 @@
|
|
|
197
211
|
//
|
|
198
212
|
// ---------- Query Options ------------
|
|
199
213
|
|
|
200
|
-
QueryOption = ExpandOption
|
|
214
|
+
QueryOption = ExpandOption /
|
|
215
|
+
"$skiptoken=" o skiptoken /
|
|
216
|
+
"$apply=" (o{
|
|
217
|
+
SELECT.apply = {}
|
|
218
|
+
}) apply /
|
|
219
|
+
custom
|
|
220
|
+
// @OData spec for $expand:
|
|
221
|
+
// "Allowed system query options are $filter, $select, $orderby, $skip, $top, $count, $search, and $expand."
|
|
201
222
|
ExpandOption =
|
|
202
223
|
"$select=" o select ( COMMA select )* /
|
|
203
224
|
"$expand=" o expand ( COMMA expand )* /
|
|
204
|
-
"$filter=" o filter /
|
|
225
|
+
"$filter=" o f:filter{SELECT.where = f} /
|
|
205
226
|
"$orderby=" o orderby ( COMMA orderby )* /
|
|
206
227
|
"$top=" o top /
|
|
207
228
|
"$skip=" o skip /
|
|
208
|
-
"$
|
|
209
|
-
"$
|
|
210
|
-
"$count=" o count /
|
|
211
|
-
"$apply=" o apply /
|
|
212
|
-
custom
|
|
229
|
+
"$search=" o s:search {if (s) SELECT.search = s} /
|
|
230
|
+
"$count=" o count
|
|
213
231
|
|
|
214
232
|
|
|
215
233
|
select
|
|
@@ -252,22 +270,32 @@
|
|
|
252
270
|
})
|
|
253
271
|
)? // --- end of nested query options
|
|
254
272
|
( COMMA expand )?
|
|
273
|
+
("/$count" {
|
|
274
|
+
const err = new Error("EXPAND_COUNT_UNSUPPORTED");
|
|
275
|
+
err.statusCode=501;
|
|
276
|
+
throw err;
|
|
277
|
+
})?
|
|
255
278
|
|
|
256
279
|
top
|
|
257
280
|
= val:integer {
|
|
258
281
|
(SELECT.limit || (SELECT.limit={})).rows = {val}
|
|
259
282
|
}
|
|
260
283
|
|
|
284
|
+
skiptoken
|
|
285
|
+
= val:integer? skiptoken:skiptokenChars? {
|
|
286
|
+
// REVISIT ignore non-numeric $skiptoken as not supported by CQN
|
|
287
|
+
if (skiptoken) return
|
|
288
|
+
_setLimitOffset(val)
|
|
289
|
+
}
|
|
290
|
+
|
|
261
291
|
skip
|
|
262
292
|
= val:integer {
|
|
263
|
-
|
|
264
|
-
val += SELECT.limit.offset.val
|
|
265
|
-
}
|
|
266
|
-
(SELECT.limit || (SELECT.limit={})).offset = {val}
|
|
293
|
+
_setLimitOffset(val)
|
|
267
294
|
}
|
|
268
295
|
|
|
269
296
|
search
|
|
270
|
-
= p:search_clause {
|
|
297
|
+
= p:search_clause {return p}
|
|
298
|
+
/ o // Do not add search property for space only
|
|
271
299
|
|
|
272
300
|
search_clause
|
|
273
301
|
= p:( n:NOT? {return n?[n]:[]} )(
|
|
@@ -281,7 +309,7 @@
|
|
|
281
309
|
{return p}
|
|
282
310
|
|
|
283
311
|
filter
|
|
284
|
-
= p:where_clause {
|
|
312
|
+
= p:where_clause { return p }
|
|
285
313
|
|
|
286
314
|
where_clause = p:( n:NOT? {return n?[n]:[]} )(
|
|
287
315
|
OPEN xpr:where_clause CLOSE {p.push({xpr})}
|
|
@@ -326,18 +354,14 @@
|
|
|
326
354
|
p:( n:NOT? { return n ? [n] : [] } )(
|
|
327
355
|
OPEN xpr:inner_lambda CLOSE { p.push('(', ...xpr, ')') }
|
|
328
356
|
/ comp:comparison { p.push(...comp) }
|
|
357
|
+
/ func:function { p.push(func) }
|
|
329
358
|
/ lambda:lambda { p.push(...lambda)}
|
|
330
359
|
)
|
|
331
360
|
( ao:(AND/OR) more:inner_lambda { p.push(ao, ...more) } )*
|
|
332
361
|
{ return p }
|
|
333
362
|
|
|
334
363
|
lambda_clause = prefix:identifier ":" inner:inner_lambda {
|
|
335
|
-
|
|
336
|
-
// remove the prefix identifier
|
|
337
|
-
if (e.ref && e.ref[0] === prefix) e.ref.shift()
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
return inner
|
|
364
|
+
return _removeLambdaPrefix(prefix, inner)
|
|
341
365
|
}
|
|
342
366
|
|
|
343
367
|
any = "any" OPEN p:lambda_clause? CLOSE { return p }
|
|
@@ -374,7 +398,17 @@
|
|
|
374
398
|
= operand (_ ("add" / "sub" / "mul" / "div" / "mod") _ operand)*
|
|
375
399
|
|
|
376
400
|
operand "an operand"
|
|
377
|
-
= function / ref / val / jsonObject / jsonArray / list
|
|
401
|
+
= navigationCount / function / ref / val / jsonObject / jsonArray / list
|
|
402
|
+
|
|
403
|
+
navigationCount "navigation with $count"
|
|
404
|
+
= navigationPath:(head:identifier key:(OPEN keyArgs:args CLOSE {return keyArgs;})? '/' {
|
|
405
|
+
if (key) {
|
|
406
|
+
// we have key in xpr
|
|
407
|
+
return {id: head, where: key}
|
|
408
|
+
}
|
|
409
|
+
return head;
|
|
410
|
+
})+ count: '$count'
|
|
411
|
+
{ return {func: 'count', as: '$count', args: [{ref: navigationPath}]} }
|
|
378
412
|
|
|
379
413
|
ref "a reference"
|
|
380
414
|
= head:identifier tail:( '/' n:identifier {return n})*
|
|
@@ -388,7 +422,7 @@
|
|
|
388
422
|
|
|
389
423
|
val
|
|
390
424
|
= val:(bool / date) {return {val}}
|
|
391
|
-
/ guid
|
|
425
|
+
/ val:guid {return {val}}
|
|
392
426
|
/ val:number {return typeof val === 'number' ? {val} : { val, literal:'number' }}
|
|
393
427
|
/ val:string {return {val}}
|
|
394
428
|
|
|
@@ -401,14 +435,16 @@
|
|
|
401
435
|
{ return { list: any.replace(/"/g,'').split(',').map(ele => ({ val: ele })) } }
|
|
402
436
|
|
|
403
437
|
function "a function call"
|
|
404
|
-
= func:$[a-
|
|
405
|
-
if (strict && !(func in strict.functions))
|
|
406
|
-
|
|
438
|
+
= func:$[a-zA-Z]+ OPEN a:operand more:( COMMA o:operand {return o} )* CLOSE {
|
|
439
|
+
if (strict && !(func.toLowerCase() in strict.functions)) {
|
|
440
|
+
throw Object.assign(new Error(`"${func}" is an unknown function in OData URL spec (strict mode)`), { statusCode: 400 })
|
|
441
|
+
}
|
|
442
|
+
return { func: func.toLowerCase(), args:[a,...more] }
|
|
407
443
|
}
|
|
408
444
|
|
|
409
445
|
boolish "a boolean function"
|
|
410
|
-
= func:("contains"/"endswith"/"startswith") OPEN a:operand COMMA b:operand CLOSE
|
|
411
|
-
{ return { func, args:[a,b] }}
|
|
446
|
+
= func:("contains"i/"endswith"i/"startswith"i) OPEN a:operand COMMA b:operand CLOSE
|
|
447
|
+
{ return { func: func.toLowerCase(), args:[a,b] }}
|
|
412
448
|
|
|
413
449
|
NOT = o "NOT"i _ {return 'not'}
|
|
414
450
|
AND = _ "AND"i _ {return 'and'}
|
|
@@ -424,7 +460,6 @@
|
|
|
424
460
|
"aggregate" aggregateTrafo /
|
|
425
461
|
"groupby" groupbyTrafo /
|
|
426
462
|
"filter" filterTrafo /
|
|
427
|
-
countTrafo /
|
|
428
463
|
|
|
429
464
|
// REVISIT: All transformations below need improvment
|
|
430
465
|
// and should supported by CAP
|
|
@@ -432,57 +467,77 @@
|
|
|
432
467
|
"search" searchTrafo /
|
|
433
468
|
"concat" concatTrafo /
|
|
434
469
|
"compute" computeTrafo /
|
|
435
|
-
"bottompercent" commonFuncTrafo
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
470
|
+
func:("topcount"i/"bottomcount"i/"topsum"i/"bottomsum"i/"toppercent"i/"bottompercent"i) args:commonFuncTrafo {
|
|
471
|
+
const SUPPORTED_APPLY_TRANSFORMATIONS = {
|
|
472
|
+
"topcount": true,
|
|
473
|
+
"bottomcount": true,
|
|
474
|
+
"topsum": false,
|
|
475
|
+
"bottomsum": false,
|
|
476
|
+
"toppercent": false,
|
|
477
|
+
"bottompercent": false
|
|
478
|
+
}
|
|
479
|
+
func = func.toLowerCase()
|
|
480
|
+
if (!SUPPORTED_APPLY_TRANSFORMATIONS[func]) {
|
|
481
|
+
throw Object.assign(new Error(`Transformation "${func}" in $apply is not supported yet.`), { statusCode: 501 })
|
|
482
|
+
}
|
|
483
|
+
(SELECT.apply.aggregate || (SELECT.apply.aggregate = [])).push({ func, args })
|
|
484
|
+
} /
|
|
439
485
|
identityTrafo
|
|
440
486
|
// customFunction
|
|
441
487
|
)
|
|
442
488
|
|
|
443
489
|
aggregateTrafo
|
|
444
|
-
|
|
490
|
+
= OPEN o head:aggregateItem tail:(o COMMA o p:aggregateItem {return p})* o CLOSE {
|
|
491
|
+
let _apply = SELECT.apply
|
|
492
|
+
if (_apply.aggregate) {
|
|
493
|
+
SELECT.apply = { apply: _apply }
|
|
494
|
+
if (_apply.groupBy) SELECT.apply.groupBy = _apply.groupBy
|
|
495
|
+
_apply = SELECT.apply
|
|
496
|
+
}
|
|
497
|
+
_apply.aggregate = [head, ...tail]
|
|
498
|
+
}
|
|
445
499
|
aggregateItem
|
|
446
|
-
= res:("$count"
|
|
500
|
+
= res:("$count" as:asAlias { return { func: 'count', args: [{ val: 1 }], as } }
|
|
447
501
|
/ aggregateExpr
|
|
448
|
-
) {
|
|
449
|
-
SELECT.columns = Array.isArray(SELECT.columns) ? SELECT.columns : []
|
|
450
|
-
if (!SELECT.columns.find(_compareRefs(res))) SELECT.columns.push(res)
|
|
451
|
-
return res
|
|
452
|
-
}
|
|
502
|
+
) { return res }
|
|
453
503
|
aggregateExpr
|
|
454
504
|
= path:(
|
|
455
505
|
ref
|
|
456
506
|
// / mathCalc - needs CAP support
|
|
457
507
|
)
|
|
458
|
-
func:aggregateWith aggregateFrom?
|
|
459
|
-
{ return { func, args: [ path ], as
|
|
508
|
+
func:aggregateWith aggregateFrom? as:asAlias
|
|
509
|
+
{ return { func, args: [ path ], as } }
|
|
460
510
|
/ identifier OPEN aggregateExpr CLOSE // needs CAP support
|
|
461
511
|
// / customAggregate // needs CAP support
|
|
462
512
|
aggregateWith
|
|
463
|
-
= _ "with" _ func:$[a-
|
|
513
|
+
= _ "with" _ func:$[a-zA-Z]+ { return func.toLowerCase(); }
|
|
464
514
|
aggregateFrom
|
|
465
515
|
= _ "from" _ ref aggregateWith aggregateFrom? // needs CAP support
|
|
466
516
|
asAlias
|
|
467
517
|
= _ "as" _ alias:identifier { return alias; }
|
|
468
518
|
|
|
469
519
|
groupbyTrafo
|
|
470
|
-
= OPEN
|
|
520
|
+
= OPEN OPEN head:groupByElem tail:(COMMA p:groupByElem {return p})* (CLOSE {
|
|
521
|
+
let _apply = SELECT.apply
|
|
522
|
+
if (_apply.groupBy || _apply.where || _apply.search) {
|
|
523
|
+
SELECT.apply = { apply: _apply }
|
|
524
|
+
_apply = SELECT.apply
|
|
525
|
+
}
|
|
526
|
+
_apply.groupBy = [head, ...tail]
|
|
527
|
+
}) (COMMA apply)? CLOSE
|
|
471
528
|
groupByElem
|
|
472
|
-
=
|
|
473
|
-
{ (SELECT.groupBy || (SELECT.groupBy = [])).push(val) }
|
|
529
|
+
= c:(rollupSpec / ref) { return c }
|
|
474
530
|
rollupSpec // TODO fix this + add CAP support
|
|
475
531
|
= rollup:("rollup" OPEN o ('$all' / ref) (o COMMA ref)+ o CLOSE) {const err = new Error("Rollup in groupby is not supported yet.");err.statusCode=501;throw err;}
|
|
476
532
|
|
|
477
|
-
filterTrafo = OPEN o filter
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
const oredrObj = { ...ref, sort: trafo === 'topcount' ? 'desc' : 'asc' };
|
|
483
|
-
SELECT.orderBy = SELECT.orderBy ? [...SELECT.orderBy, oredrObj] : [oredrObj];
|
|
484
|
-
(SELECT.limit || (SELECT.limit={})).rows = {val};
|
|
533
|
+
filterTrafo = OPEN o (where:filter{
|
|
534
|
+
let _apply = SELECT.apply
|
|
535
|
+
if (_apply.where) {
|
|
536
|
+
SELECT.apply = { apply: _apply }
|
|
537
|
+
_apply = SELECT.apply
|
|
485
538
|
}
|
|
539
|
+
_apply.where = where
|
|
540
|
+
}) o CLOSE
|
|
486
541
|
|
|
487
542
|
|
|
488
543
|
// All transformations below need improvment
|
|
@@ -493,14 +548,22 @@
|
|
|
493
548
|
/ filterTrafo (o COMMA expandTrafo)*
|
|
494
549
|
) o CLOSE
|
|
495
550
|
|
|
496
|
-
searchTrafo = OPEN o search
|
|
551
|
+
searchTrafo = OPEN o (search:search{
|
|
552
|
+
if (!search) return
|
|
553
|
+
let _apply = SELECT.apply
|
|
554
|
+
if (_apply.search) {
|
|
555
|
+
SELECT.apply = { apply: _apply }
|
|
556
|
+
_apply = SELECT.apply
|
|
557
|
+
}
|
|
558
|
+
_apply.search = search
|
|
559
|
+
}) o CLOSE
|
|
497
560
|
|
|
498
561
|
concatTrafo = OPEN o apply (o COMMA o apply)+ o CLOSE
|
|
499
562
|
|
|
500
563
|
computeTrafo = OPEN o computeExpr (o COMMA o computeExpr)* o CLOSE
|
|
501
564
|
computeExpr = where_clause asAlias
|
|
502
565
|
|
|
503
|
-
commonFuncTrafo = OPEN o operand o COMMA o operand o CLOSE
|
|
566
|
+
commonFuncTrafo = OPEN o first:operand o COMMA o second:operand o CLOSE { return [first, second] }
|
|
504
567
|
|
|
505
568
|
identityTrafo = "identity"
|
|
506
569
|
|
|
@@ -537,10 +600,16 @@
|
|
|
537
600
|
= !bool !guid s:$([_a-zA-Z][_a-zA-Z0-9"."]*) {return s}
|
|
538
601
|
|
|
539
602
|
guid
|
|
540
|
-
=
|
|
603
|
+
= $(hex16 hex16 "-"? hex16 "-"? hex16 "-"? hex16 "-"? hex16 hex16 hex16)
|
|
604
|
+
|
|
605
|
+
hex16
|
|
606
|
+
= $([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])
|
|
541
607
|
|
|
542
608
|
segment
|
|
543
|
-
=
|
|
609
|
+
= val:$([a-zA-Z0-9-"."_~!$&'()*+,;=:@]+){return {val}}
|
|
610
|
+
|
|
611
|
+
skiptokenChars
|
|
612
|
+
= $([a-zA-Z0-9-"."_~!$'()*+,;=:@"/""?"]+)
|
|
544
613
|
|
|
545
614
|
//
|
|
546
615
|
// ---------- Punctuation ----------
|
package/libx/odata/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
const cds = require('../_runtime/cds')
|
|
2
2
|
const { SELECT } = cds.ql
|
|
3
3
|
|
|
4
|
-
const odata2cqn = require('./
|
|
4
|
+
const odata2cqn = require('./parser').parse
|
|
5
5
|
const cqn2odata = require('./cqn2odata')
|
|
6
6
|
|
|
7
|
-
const afterburner = require('./
|
|
7
|
+
const afterburner = require('./afterburner')
|
|
8
8
|
const { getSafeNumber: safeNumber } = require('./utils')
|
|
9
|
+
const getError = require('../_runtime/common/error')
|
|
9
10
|
|
|
10
11
|
const strict = {
|
|
11
12
|
functions: {
|
|
@@ -30,6 +31,18 @@ const strict = {
|
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
const _2query = cqn => {
|
|
35
|
+
if (cqn.SELECT.from.SELECT) cqn.SELECT.from = _2query(cqn.SELECT.from)
|
|
36
|
+
const query = cqn.SELECT.one ? SELECT.one(cqn.SELECT.from) : SELECT.from(cqn.SELECT.from)
|
|
37
|
+
for (const prop in cqn.SELECT) {
|
|
38
|
+
if (prop === 'from') continue
|
|
39
|
+
// explicitly use cds.ql to get '_includes_or__' magic for cqn.SELECT.where containing 'or'
|
|
40
|
+
if (prop === 'where' || prop === 'having') query[prop](cqn.SELECT[prop])
|
|
41
|
+
else query.SELECT[prop] = cqn.SELECT[prop]
|
|
42
|
+
}
|
|
43
|
+
return query
|
|
44
|
+
}
|
|
45
|
+
|
|
33
46
|
/*
|
|
34
47
|
* cds.odata API
|
|
35
48
|
*/
|
|
@@ -49,16 +62,12 @@ module.exports = {
|
|
|
49
62
|
let cqn
|
|
50
63
|
try {
|
|
51
64
|
cqn = odata2cqn(url, options)
|
|
52
|
-
} catch (
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
// unknown errors -> e is the error to keep
|
|
56
|
-
let err = e
|
|
57
|
-
try {
|
|
58
|
-
err = JSON.parse(e.message)
|
|
59
|
-
} catch {
|
|
60
|
-
/* nothing to do */
|
|
65
|
+
} catch (err) {
|
|
66
|
+
if (err.message === 'EXPAND_COUNT_UNSUPPORTED') {
|
|
67
|
+
throw getError(err.statusCode || 400, err.message)
|
|
61
68
|
}
|
|
69
|
+
|
|
70
|
+
// TODO adjust this to behave like above
|
|
62
71
|
err.message = 'Parsing URL failed with error: ' + err.message
|
|
63
72
|
err.statusCode = err.statusCode || 400
|
|
64
73
|
throw err
|
|
@@ -66,8 +75,7 @@ module.exports = {
|
|
|
66
75
|
|
|
67
76
|
if (options.afterburner) cqn = options.afterburner(cqn)
|
|
68
77
|
|
|
69
|
-
const query =
|
|
70
|
-
Object.assign(query.SELECT, cqn.SELECT)
|
|
78
|
+
const query = _2query(cqn)
|
|
71
79
|
|
|
72
80
|
// REVISIT: _target vs __target, i.e., pseudo csn vs actual csn
|
|
73
81
|
// DO NOT USE __target outside of libx/rest!!!
|