@sap/cds 5.6.3 → 5.7.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 +135 -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 +4 -2
- package/lib/log/index.js +2 -2
- package/lib/ql/Whereable.js +1 -0
- 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 +9 -5
- 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/rest-to-cqn/index.js +2 -2
- package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +8 -4
- package/libx/_runtime/cds-services/services/Service.js +0 -6
- package/libx/_runtime/cds-services/services/utils/columns.js +10 -3
- package/libx/_runtime/cds-services/services/utils/compareJson.js +4 -7
- 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/paging.js +2 -2
- 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/expand/expand-v2.js +130 -0
- package/libx/_runtime/db/expand/expandCQNToJoin.js +38 -52
- package/libx/_runtime/db/expand/index.js +3 -1
- package/libx/_runtime/db/generic/arrayed.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/handler/transformRESULT.js +0 -4
- package/libx/_runtime/fiori/uiflex/index.js +1 -1
- package/libx/_runtime/fiori/uiflex/{extensibility/index.js → service.js} +6 -4
- 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} +28 -16
- package/libx/odata/{cqn2odata/index.js → cqn2odata.js} +1 -1
- package/libx/odata/{odata2cqn/grammar.pegjs → grammar.pegjs} +222 -130
- package/libx/odata/index.js +23 -14
- 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,79 +39,102 @@
|
|
|
42
39
|
return Number.isSafeInteger(n) ? n : str
|
|
43
40
|
}
|
|
44
41
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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)
|
|
52
72
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
73
|
+
}
|
|
74
|
+
}
|
|
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 } }
|
|
80
|
+
}
|
|
81
|
+
if (apply.where) {
|
|
82
|
+
if (cqn.where) cqn.from.SELECT.where = apply.where
|
|
83
|
+
else cqn.where = apply.where
|
|
84
|
+
}
|
|
85
|
+
if (apply.search) {
|
|
86
|
+
if (cqn.search) cqn.from.SELECT.search = apply.search
|
|
87
|
+
else cqn.search = apply.search
|
|
88
|
+
}
|
|
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)
|
|
56
93
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
+
}
|
|
66
111
|
}
|
|
67
|
-
//
|
|
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
|
+
}, [])
|
|
68
119
|
}
|
|
69
|
-
|
|
70
|
-
return changedWhere;
|
|
120
|
+
if (apply.apply) _handleApply(cqn.from.SELECT, apply.apply)
|
|
71
121
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const where = SELECT.where;
|
|
76
|
-
const columns = SELECT.columns || [];
|
|
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
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// handle $apply=groupby((someProp,?...))&$select=?...
|
|
90
|
-
if (groupBy) {
|
|
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;
|
|
122
|
+
const _setLimitOffset = val => {
|
|
123
|
+
if (SELECT.limit && SELECT.limit.offset && SELECT.limit.offset.val) {
|
|
124
|
+
val += SELECT.limit.offset.val
|
|
96
125
|
}
|
|
126
|
+
(SELECT.limit || (SELECT.limit={})).offset = {val}
|
|
127
|
+
}
|
|
97
128
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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) }
|
|
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)
|
|
110
134
|
}
|
|
111
135
|
|
|
112
|
-
return
|
|
136
|
+
return elements
|
|
113
137
|
}
|
|
114
|
-
|
|
115
|
-
const _compareRefs = col => exp => col === exp ||
|
|
116
|
-
(col.as && exp.as && col.as === exp.as) ||
|
|
117
|
-
(col.ref && exp.ref && col.ref.join('') === exp.ref.join(''))
|
|
118
138
|
}
|
|
119
139
|
|
|
120
140
|
// ---------- Entity Paths ---------------
|
|
@@ -129,11 +149,19 @@
|
|
|
129
149
|
delete SELECT.expand
|
|
130
150
|
delete SELECT.limit
|
|
131
151
|
delete SELECT.orderBy
|
|
152
|
+
if (SELECT.apply) {
|
|
153
|
+
SELECT.apply = { apply: SELECT.apply }
|
|
154
|
+
_handleApply(SELECT, SELECT.apply)
|
|
155
|
+
}
|
|
132
156
|
return { SELECT }
|
|
133
157
|
}
|
|
158
|
+
let onlyColumnsFromExpand
|
|
134
159
|
if (SELECT.expand) {
|
|
135
160
|
// Books?$expand=author w/o $select=author
|
|
136
|
-
if (!SELECT.columns)
|
|
161
|
+
if (!SELECT.columns) {
|
|
162
|
+
SELECT.columns = ['*']
|
|
163
|
+
onlyColumnsFromExpand = true
|
|
164
|
+
}
|
|
137
165
|
for (const exp of SELECT.expand) {
|
|
138
166
|
const idx = SELECT.columns.findIndex(_compareRefs(exp))
|
|
139
167
|
if (idx > -1) SELECT.columns.splice(idx, 1)
|
|
@@ -141,22 +169,26 @@
|
|
|
141
169
|
}
|
|
142
170
|
delete SELECT.expand
|
|
143
171
|
}
|
|
144
|
-
SELECT
|
|
145
|
-
|
|
172
|
+
if (SELECT.count && SELECT.apply) SELECT.__countAggregated = true
|
|
173
|
+
_handleApply(SELECT, SELECT.apply, onlyColumnsFromExpand)
|
|
146
174
|
return { SELECT }
|
|
147
175
|
}
|
|
148
176
|
|
|
149
177
|
path
|
|
150
178
|
= "$count" {count = true}
|
|
151
179
|
/ rv:$("$ref"/"$value") {return !TECHNICAL_OPTS.includes(rv) && {from: {ref: [rv]}}}
|
|
152
|
-
/ head:(
|
|
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
|
|
153
184
|
// minimal: val also as path segment
|
|
154
185
|
const ref = [
|
|
155
186
|
filter
|
|
156
187
|
? filter.length > 2
|
|
157
|
-
? { id
|
|
158
|
-
: { id
|
|
159
|
-
|
|
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 )
|
|
160
192
|
]
|
|
161
193
|
if (tail && tail.from) {
|
|
162
194
|
const more = tail.from.ref
|
|
@@ -170,7 +202,7 @@
|
|
|
170
202
|
|
|
171
203
|
args
|
|
172
204
|
= val:val {return [val]}
|
|
173
|
-
/ ref:ref o"="o val:
|
|
205
|
+
/ ref:ref o"="o val:val more:( COMMA args )? {
|
|
174
206
|
const args = [ ref, '=', val ]
|
|
175
207
|
if (more) args.push ('and', ...more[1])
|
|
176
208
|
return args
|
|
@@ -179,18 +211,23 @@
|
|
|
179
211
|
//
|
|
180
212
|
// ---------- Query Options ------------
|
|
181
213
|
|
|
182
|
-
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."
|
|
183
222
|
ExpandOption =
|
|
184
223
|
"$select=" o select ( COMMA select )* /
|
|
185
224
|
"$expand=" o expand ( COMMA expand )* /
|
|
186
|
-
"$filter=" o filter /
|
|
225
|
+
"$filter=" o f:filter{SELECT.where = f} /
|
|
187
226
|
"$orderby=" o orderby ( COMMA orderby )* /
|
|
188
227
|
"$top=" o top /
|
|
189
228
|
"$skip=" o skip /
|
|
190
|
-
"$search=" o search /
|
|
191
|
-
"$count=" o count
|
|
192
|
-
"$apply=" o apply /
|
|
193
|
-
custom
|
|
229
|
+
"$search=" o s:search {if (s) SELECT.search = s} /
|
|
230
|
+
"$count=" o count
|
|
194
231
|
|
|
195
232
|
|
|
196
233
|
select
|
|
@@ -233,19 +270,32 @@
|
|
|
233
270
|
})
|
|
234
271
|
)? // --- end of nested query options
|
|
235
272
|
( COMMA expand )?
|
|
273
|
+
("/$count" {
|
|
274
|
+
const err = new Error("EXPAND_COUNT_UNSUPPORTED");
|
|
275
|
+
err.statusCode=501;
|
|
276
|
+
throw err;
|
|
277
|
+
})?
|
|
236
278
|
|
|
237
279
|
top
|
|
238
280
|
= val:integer {
|
|
239
281
|
(SELECT.limit || (SELECT.limit={})).rows = {val}
|
|
240
282
|
}
|
|
241
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
|
+
|
|
242
291
|
skip
|
|
243
292
|
= val:integer {
|
|
244
|
-
(
|
|
293
|
+
_setLimitOffset(val)
|
|
245
294
|
}
|
|
246
295
|
|
|
247
296
|
search
|
|
248
|
-
= p:search_clause {
|
|
297
|
+
= p:search_clause {return p}
|
|
298
|
+
/ o // Do not add search property for space only
|
|
249
299
|
|
|
250
300
|
search_clause
|
|
251
301
|
= p:( n:NOT? {return n?[n]:[]} )(
|
|
@@ -259,7 +309,7 @@
|
|
|
259
309
|
{return p}
|
|
260
310
|
|
|
261
311
|
filter
|
|
262
|
-
= p:where_clause {
|
|
312
|
+
= p:where_clause { return p }
|
|
263
313
|
|
|
264
314
|
where_clause = p:( n:NOT? {return n?[n]:[]} )(
|
|
265
315
|
OPEN xpr:where_clause CLOSE {p.push({xpr})}
|
|
@@ -304,18 +354,14 @@
|
|
|
304
354
|
p:( n:NOT? { return n ? [n] : [] } )(
|
|
305
355
|
OPEN xpr:inner_lambda CLOSE { p.push('(', ...xpr, ')') }
|
|
306
356
|
/ comp:comparison { p.push(...comp) }
|
|
357
|
+
/ func:function { p.push(func) }
|
|
307
358
|
/ lambda:lambda { p.push(...lambda)}
|
|
308
359
|
)
|
|
309
360
|
( ao:(AND/OR) more:inner_lambda { p.push(ao, ...more) } )*
|
|
310
361
|
{ return p }
|
|
311
362
|
|
|
312
363
|
lambda_clause = prefix:identifier ":" inner:inner_lambda {
|
|
313
|
-
|
|
314
|
-
// remove the prefix identifier
|
|
315
|
-
if (e.ref && e.ref[0] === prefix) e.ref.shift()
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
return inner
|
|
364
|
+
return _removeLambdaPrefix(prefix, inner)
|
|
319
365
|
}
|
|
320
366
|
|
|
321
367
|
any = "any" OPEN p:lambda_clause? CLOSE { return p }
|
|
@@ -352,7 +398,17 @@
|
|
|
352
398
|
= operand (_ ("add" / "sub" / "mul" / "div" / "mod") _ operand)*
|
|
353
399
|
|
|
354
400
|
operand "an operand"
|
|
355
|
-
= 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}]} }
|
|
356
412
|
|
|
357
413
|
ref "a reference"
|
|
358
414
|
= head:identifier tail:( '/' n:identifier {return n})*
|
|
@@ -366,7 +422,7 @@
|
|
|
366
422
|
|
|
367
423
|
val
|
|
368
424
|
= val:(bool / date) {return {val}}
|
|
369
|
-
/ guid
|
|
425
|
+
/ val:guid {return {val}}
|
|
370
426
|
/ val:number {return typeof val === 'number' ? {val} : { val, literal:'number' }}
|
|
371
427
|
/ val:string {return {val}}
|
|
372
428
|
|
|
@@ -379,14 +435,16 @@
|
|
|
379
435
|
{ return { list: any.replace(/"/g,'').split(',').map(ele => ({ val: ele })) } }
|
|
380
436
|
|
|
381
437
|
function "a function call"
|
|
382
|
-
= func:$[a-
|
|
383
|
-
if (strict && !(func in strict.functions))
|
|
384
|
-
|
|
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] }
|
|
385
443
|
}
|
|
386
444
|
|
|
387
445
|
boolish "a boolean function"
|
|
388
|
-
= func:("contains"/"endswith"/"startswith") OPEN a:operand COMMA b:operand CLOSE
|
|
389
|
-
{ 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] }}
|
|
390
448
|
|
|
391
449
|
NOT = o "NOT"i _ {return 'not'}
|
|
392
450
|
AND = _ "AND"i _ {return 'and'}
|
|
@@ -402,7 +460,6 @@
|
|
|
402
460
|
"aggregate" aggregateTrafo /
|
|
403
461
|
"groupby" groupbyTrafo /
|
|
404
462
|
"filter" filterTrafo /
|
|
405
|
-
countTrafo /
|
|
406
463
|
|
|
407
464
|
// REVISIT: All transformations below need improvment
|
|
408
465
|
// and should supported by CAP
|
|
@@ -410,57 +467,77 @@
|
|
|
410
467
|
"search" searchTrafo /
|
|
411
468
|
"concat" concatTrafo /
|
|
412
469
|
"compute" computeTrafo /
|
|
413
|
-
"bottompercent" commonFuncTrafo
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
+
} /
|
|
417
485
|
identityTrafo
|
|
418
486
|
// customFunction
|
|
419
487
|
)
|
|
420
488
|
|
|
421
489
|
aggregateTrafo
|
|
422
|
-
|
|
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
|
+
}
|
|
423
499
|
aggregateItem
|
|
424
|
-
= res:("$count"
|
|
500
|
+
= res:("$count" as:asAlias { return { func: 'count', args: [{ val: 1 }], as } }
|
|
425
501
|
/ aggregateExpr
|
|
426
|
-
) {
|
|
427
|
-
SELECT.columns = Array.isArray(SELECT.columns) ? SELECT.columns : []
|
|
428
|
-
if (!SELECT.columns.find(_compareRefs(res))) SELECT.columns.push(res)
|
|
429
|
-
return res
|
|
430
|
-
}
|
|
502
|
+
) { return res }
|
|
431
503
|
aggregateExpr
|
|
432
504
|
= path:(
|
|
433
505
|
ref
|
|
434
506
|
// / mathCalc - needs CAP support
|
|
435
507
|
)
|
|
436
|
-
func:aggregateWith aggregateFrom?
|
|
437
|
-
{ return { func, args: [ path ], as
|
|
508
|
+
func:aggregateWith aggregateFrom? as:asAlias
|
|
509
|
+
{ return { func, args: [ path ], as } }
|
|
438
510
|
/ identifier OPEN aggregateExpr CLOSE // needs CAP support
|
|
439
511
|
// / customAggregate // needs CAP support
|
|
440
512
|
aggregateWith
|
|
441
|
-
= _ "with" _ func:$[a-
|
|
513
|
+
= _ "with" _ func:$[a-zA-Z]+ { return func.toLowerCase(); }
|
|
442
514
|
aggregateFrom
|
|
443
515
|
= _ "from" _ ref aggregateWith aggregateFrom? // needs CAP support
|
|
444
516
|
asAlias
|
|
445
517
|
= _ "as" _ alias:identifier { return alias; }
|
|
446
518
|
|
|
447
519
|
groupbyTrafo
|
|
448
|
-
= 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
|
|
449
528
|
groupByElem
|
|
450
|
-
=
|
|
451
|
-
{ (SELECT.groupBy || (SELECT.groupBy = [])).push(val) }
|
|
529
|
+
= c:(rollupSpec / ref) { return c }
|
|
452
530
|
rollupSpec // TODO fix this + add CAP support
|
|
453
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;}
|
|
454
532
|
|
|
455
|
-
filterTrafo = OPEN o filter
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
const oredrObj = { ...ref, sort: trafo === 'topcount' ? 'desc' : 'asc' };
|
|
461
|
-
SELECT.orderBy = SELECT.orderBy ? [...SELECT.orderBy, oredrObj] : [oredrObj];
|
|
462
|
-
(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
|
|
463
538
|
}
|
|
539
|
+
_apply.where = where
|
|
540
|
+
}) o CLOSE
|
|
464
541
|
|
|
465
542
|
|
|
466
543
|
// All transformations below need improvment
|
|
@@ -471,14 +548,22 @@
|
|
|
471
548
|
/ filterTrafo (o COMMA expandTrafo)*
|
|
472
549
|
) o CLOSE
|
|
473
550
|
|
|
474
|
-
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
|
|
475
560
|
|
|
476
561
|
concatTrafo = OPEN o apply (o COMMA o apply)+ o CLOSE
|
|
477
562
|
|
|
478
563
|
computeTrafo = OPEN o computeExpr (o COMMA o computeExpr)* o CLOSE
|
|
479
564
|
computeExpr = where_clause asAlias
|
|
480
565
|
|
|
481
|
-
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] }
|
|
482
567
|
|
|
483
568
|
identityTrafo = "identity"
|
|
484
569
|
|
|
@@ -506,18 +591,25 @@
|
|
|
506
591
|
)?)
|
|
507
592
|
|
|
508
593
|
number
|
|
509
|
-
= s:$( [+-]? [0-9]+ ("."[0-9]+)? ("e"[0-9]+)? )
|
|
510
|
-
{return safeNumber(s)}
|
|
594
|
+
= s:$( [+-]? [0-9]+ ("."[0-9]+)? ("e"[0-9]+)? ) {return safeNumber(s)}
|
|
511
595
|
|
|
512
596
|
integer
|
|
513
|
-
= s:$( [+-]? [0-9]+ )
|
|
514
|
-
{return parseInt(s)}
|
|
597
|
+
= s:$( [+-]? [0-9]+ ) {return parseInt(s)}
|
|
515
598
|
|
|
516
599
|
identifier
|
|
517
600
|
= !bool !guid s:$([_a-zA-Z][_a-zA-Z0-9"."]*) {return s}
|
|
518
601
|
|
|
519
|
-
guid
|
|
520
|
-
|
|
602
|
+
guid
|
|
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])
|
|
607
|
+
|
|
608
|
+
segment
|
|
609
|
+
= val:$([a-zA-Z0-9-"."_~!$&'()*+,;=:@]+){return {val}}
|
|
610
|
+
|
|
611
|
+
skiptokenChars
|
|
612
|
+
= $([a-zA-Z0-9-"."_~!$'()*+,;=:@"/""?"]+)
|
|
521
613
|
|
|
522
614
|
//
|
|
523
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
|
*/
|
|
@@ -39,6 +52,7 @@ module.exports = {
|
|
|
39
52
|
if (url.url) url = url.url
|
|
40
53
|
// REVISIT: for okra, remove when no longer needed
|
|
41
54
|
else if (url.getIncomingRequest) url = url.getIncomingRequest().url
|
|
55
|
+
|
|
42
56
|
url = decodeURIComponent(url)
|
|
43
57
|
|
|
44
58
|
options = options === 'strict' ? { strict } : options.strict ? { ...options, strict } : options
|
|
@@ -48,25 +62,20 @@ module.exports = {
|
|
|
48
62
|
let cqn
|
|
49
63
|
try {
|
|
50
64
|
cqn = odata2cqn(url, options)
|
|
51
|
-
} catch (
|
|
52
|
-
|
|
53
|
-
|
|
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 */
|
|
65
|
+
} catch (err) {
|
|
66
|
+
if (err.message === 'EXPAND_COUNT_UNSUPPORTED') {
|
|
67
|
+
throw getError(err.statusCode || 400, err.message)
|
|
60
68
|
}
|
|
69
|
+
|
|
70
|
+
// TODO adjust this to behave like above
|
|
61
71
|
err.message = 'Parsing URL failed with error: ' + err.message
|
|
62
72
|
err.statusCode = err.statusCode || 400
|
|
63
73
|
throw err
|
|
64
74
|
}
|
|
65
75
|
|
|
66
|
-
if (
|
|
76
|
+
if (options.afterburner) cqn = options.afterburner(cqn)
|
|
67
77
|
|
|
68
|
-
const query =
|
|
69
|
-
Object.assign(query.SELECT, cqn.SELECT)
|
|
78
|
+
const query = _2query(cqn)
|
|
70
79
|
|
|
71
80
|
// REVISIT: _target vs __target, i.e., pseudo csn vs actual csn
|
|
72
81
|
// DO NOT USE __target outside of libx/rest!!!
|