@sap/cds 5.5.5 → 5.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +107 -1
- package/apis/services.d.ts +27 -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 +3 -1
- package/lib/env/index.js +175 -41
- package/lib/env/requires.js +16 -1
- package/lib/i18n/localize.js +31 -4
- 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 +10 -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/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 +48 -18
- 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 +14 -19
- 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 +1 -12
- 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/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 +10 -24
- package/libx/_runtime/db/expand/rawToExpanded.js +2 -1
- 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 +2 -2
- 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 +17 -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 +9 -1
- 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 +14 -2
- 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 +20 -2
- 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
|
@@ -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
|
+
}
|
|
@@ -24,12 +24,16 @@
|
|
|
24
24
|
* Books?$select=ID,title&$expand=author($select=name)&$filter=stock gt 1&$orderby=title
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
|
+
//
|
|
27
28
|
// ---------- JavaScript Helpers -------------
|
|
28
29
|
{
|
|
29
|
-
|
|
30
|
+
const exception = (message, code = 400) => {
|
|
31
|
+
error(JSON.stringify({ code, message }))
|
|
32
|
+
}
|
|
33
|
+
const $ = Object.assign
|
|
30
34
|
const { strict, minimal } = options
|
|
31
|
-
const stack=[]
|
|
32
|
-
let SELECT
|
|
35
|
+
const stack = []
|
|
36
|
+
let SELECT, count
|
|
33
37
|
const TECHNICAL_OPTS = ['$value'] // odata parts to be handled somewhere else
|
|
34
38
|
|
|
35
39
|
// we keep that here to allow for usage in https://pegjs.org/online
|
|
@@ -38,71 +42,17 @@
|
|
|
38
42
|
return Number.isSafeInteger(n) ? n : str
|
|
39
43
|
}
|
|
40
44
|
|
|
41
|
-
// check whether we have any
|
|
42
|
-
// - $select
|
|
43
|
-
// - aggregate(... as someProp)
|
|
44
|
-
const hasNOTAny$select = (columns) => {
|
|
45
|
-
return columns.filter(cur => cur.ref ? !cur.expand : cur.as).length === 0;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const adaptColumns = (SELECT) => {
|
|
49
|
-
const columns = SELECT.columns
|
|
50
|
-
if (!columns) return SELECT
|
|
51
|
-
|
|
52
|
-
// when $select and $expand was NOT defined
|
|
53
|
-
if (!hasNOTAny$select(columns)) {
|
|
54
|
-
const columnsMap = new Map()
|
|
55
|
-
|
|
56
|
-
columns.forEach(column => {
|
|
57
|
-
|
|
58
|
-
if (column === '*') {
|
|
59
|
-
columnsMap.set(column, column)
|
|
60
|
-
return
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (!column.ref) {
|
|
64
|
-
columnsMap.set(column.as, column)
|
|
65
|
-
return
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const columnRefName = column.ref.join('/')
|
|
69
|
-
|
|
70
|
-
if (!columnsMap.has(columnRefName) || column.expand) {
|
|
71
|
-
columnsMap.set(columnRefName, column)
|
|
72
|
-
}
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
if (columnsMap.size > 0) {
|
|
76
|
-
SELECT.columns = Array.from(columnsMap.values())
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return SELECT
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (columns.length === 1 && columns[0] === '*') {
|
|
83
|
-
delete SELECT.columns
|
|
84
|
-
return SELECT
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// in case when have $expand but have NOT $select
|
|
88
|
-
if (!columns.includes('*')) {
|
|
89
|
-
SELECT.columns.unshift('*')
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return SELECT
|
|
93
|
-
}
|
|
94
|
-
|
|
95
45
|
// NOTe: mutation of the object property, it's NOT a pure function
|
|
96
46
|
const correctAggAwayWhere = (where, colNames) => {
|
|
97
|
-
|
|
47
|
+
const changedWhere = [...where];
|
|
98
48
|
|
|
99
49
|
for (const item of changedWhere) {
|
|
100
|
-
|
|
101
|
-
|
|
50
|
+
if (item.xpr) {
|
|
51
|
+
item.xpr = correctAggAwayWhere(item.xpr, colNames)
|
|
102
52
|
}
|
|
103
53
|
|
|
104
54
|
if (item.args) {
|
|
105
|
-
|
|
55
|
+
item.args = correctAggAwayWhere(item.args, colNames)
|
|
106
56
|
}
|
|
107
57
|
|
|
108
58
|
// $filter ohne $apply -> input set = entity -> kein null setzen
|
|
@@ -112,7 +62,7 @@
|
|
|
112
62
|
|
|
113
63
|
// TODO fix this for $apply
|
|
114
64
|
if(item.ref && !colNames.includes(item.ref.join(''))) {
|
|
115
|
-
|
|
65
|
+
// item.ref = null;
|
|
116
66
|
}
|
|
117
67
|
// REVISIT: { val:null } for should be also implemented
|
|
118
68
|
}
|
|
@@ -126,7 +76,7 @@
|
|
|
126
76
|
const columns = SELECT.columns || [];
|
|
127
77
|
const aggregates = columns.filter((cur) => cur.as);
|
|
128
78
|
|
|
129
|
-
|
|
79
|
+
let fromAggregate = [];
|
|
130
80
|
let fromGroupBy = [];
|
|
131
81
|
|
|
132
82
|
// handle $apply=aggregate(... as someProp)&$select=someProp,?...
|
|
@@ -138,28 +88,33 @@
|
|
|
138
88
|
|
|
139
89
|
// handle $apply=groupby((someProp,?...))&$select=?...
|
|
140
90
|
if (groupBy) {
|
|
141
|
-
const allowedNames = groupBy.map(({ ref }) => ref.join(''));
|
|
91
|
+
const allowedNames = groupBy.map(({ ref }) => ref && ref.join(''));
|
|
142
92
|
const allowedColumns = columns.filter((cur) =>
|
|
143
|
-
|
|
93
|
+
cur.ref && allowedNames.includes(cur.ref.join(''))
|
|
144
94
|
);
|
|
145
95
|
fromGroupBy = allowedColumns.length === 0 ? [...groupBy] : allowedColumns;
|
|
146
96
|
}
|
|
147
97
|
|
|
148
98
|
const newColumns = fromAggregate.length !== 0 || fromGroupBy.length !== 0
|
|
149
|
-
|
|
99
|
+
? [...fromGroupBy, ...fromAggregate]
|
|
150
100
|
: SELECT.columns;
|
|
151
101
|
|
|
152
|
-
let result = { ...SELECT
|
|
102
|
+
let result = { ...SELECT }
|
|
103
|
+
if (newColumns) result.columns = newColumns
|
|
153
104
|
let newWhere = [];
|
|
154
105
|
|
|
155
106
|
if (where && (groupBy || aggregates.length !== 0)) {
|
|
156
|
-
|
|
157
|
-
|
|
107
|
+
// changing { ref: null } for aggregated-away props
|
|
108
|
+
const colNames = columns.map((cur) => cur.ref && cur.ref.join('') || cur.as);
|
|
158
109
|
result = { ...result, where: correctAggAwayWhere(where, colNames) }
|
|
159
|
-
|
|
110
|
+
}
|
|
160
111
|
|
|
161
112
|
return result;
|
|
162
113
|
}
|
|
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(''))
|
|
163
118
|
}
|
|
164
119
|
|
|
165
120
|
// ---------- Entity Paths ---------------
|
|
@@ -167,42 +122,49 @@
|
|
|
167
122
|
ODataRelativeURI // Note: case-sensitive!
|
|
168
123
|
= '/'? (p:path { SELECT = p })
|
|
169
124
|
( o"?"o QueryOption ( o'&'o QueryOption )* )? o {
|
|
170
|
-
if (
|
|
125
|
+
if (count) {
|
|
171
126
|
// columns set because of $count: ignore $select, $expand, $top, $skip, $orderby
|
|
172
|
-
// REVISIT: don't ignore query options but throw bad request (as okra did)?
|
|
127
|
+
// REVISIT: don't ignore query options but throw bad request (as okra did)?
|
|
128
|
+
SELECT.columns = [{ args: [{ val: 1 }], as: '$count', func: 'count' }]
|
|
173
129
|
delete SELECT.expand
|
|
174
130
|
delete SELECT.limit
|
|
175
131
|
delete SELECT.orderBy
|
|
176
132
|
return { SELECT }
|
|
177
133
|
}
|
|
178
134
|
if (SELECT.expand) {
|
|
179
|
-
|
|
135
|
+
// Books?$expand=author w/o $select=author
|
|
136
|
+
if (!SELECT.columns) SELECT.columns = ['*']
|
|
137
|
+
for (const exp of SELECT.expand) {
|
|
138
|
+
const idx = SELECT.columns.findIndex(_compareRefs(exp))
|
|
139
|
+
if (idx > -1) SELECT.columns.splice(idx, 1)
|
|
140
|
+
SELECT.columns.push(exp)
|
|
141
|
+
}
|
|
180
142
|
delete SELECT.expand
|
|
181
143
|
}
|
|
182
|
-
|
|
183
144
|
SELECT = correctAggAwayColumns(SELECT)
|
|
184
|
-
SELECT = adaptColumns(SELECT)
|
|
185
|
-
|
|
186
|
-
// REVISIT: shouldn't be necessary
|
|
187
|
-
if (!SELECT.columns) delete SELECT.columns
|
|
188
145
|
|
|
189
146
|
return { SELECT }
|
|
190
147
|
}
|
|
191
148
|
|
|
192
149
|
path
|
|
193
|
-
= "$count" {
|
|
150
|
+
= "$count" {count = true}
|
|
194
151
|
/ rv:$("$ref"/"$value") {return !TECHNICAL_OPTS.includes(rv) && {from: {ref: [rv]}}}
|
|
195
|
-
/ head:(identifier/val) filter:(OPEN args CLOSE)? tail:( '/' p:path {return p} )? {
|
|
152
|
+
/ head:(identifier/val) filter:(OPEN CLOSE/OPEN args CLOSE)? tail:( '/' p:path {return p} )? {
|
|
196
153
|
// minimal: val also as path segment
|
|
197
|
-
const ref = [
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
154
|
+
const ref = [
|
|
155
|
+
filter
|
|
156
|
+
? filter.length > 2
|
|
157
|
+
? { id: head, where: filter[1].map(f => f.val && f.val.match && f.val.match(/^"(.*)"$/) ? { val: f.val.match(/^"(.*)"$/)[1] } : f) }
|
|
158
|
+
: { id: head, where: [] }
|
|
159
|
+
: ( minimal ? `${Object.prototype.hasOwnProperty.call(head, 'val') ? head.val : head}` : head )
|
|
160
|
+
]
|
|
161
|
+
if (tail && tail.from) {
|
|
162
|
+
const more = tail.from.ref
|
|
163
|
+
if (Object.prototype.hasOwnProperty.call(more[0], 'val')) ref[ref.length-1] = { id:ref[ref.length-1], where:[more.shift()] }
|
|
164
|
+
ref.push (...more)
|
|
202
165
|
}
|
|
203
|
-
|
|
204
166
|
const res = {from: {ref}}
|
|
205
|
-
if (tail) res.columns = tail.columns
|
|
167
|
+
if (tail && tail.columns) res.columns = tail.columns
|
|
206
168
|
return res
|
|
207
169
|
}
|
|
208
170
|
|
|
@@ -214,45 +176,57 @@
|
|
|
214
176
|
return args
|
|
215
177
|
}
|
|
216
178
|
|
|
217
|
-
|
|
179
|
+
//
|
|
218
180
|
// ---------- Query Options ------------
|
|
219
181
|
|
|
220
182
|
QueryOption = ExpandOption
|
|
221
183
|
ExpandOption =
|
|
222
|
-
"$select="
|
|
223
|
-
"$expand="
|
|
224
|
-
"$filter="
|
|
225
|
-
"$orderby="
|
|
226
|
-
"$top="
|
|
227
|
-
"$skip="
|
|
228
|
-
"$search="
|
|
229
|
-
"$count="
|
|
184
|
+
"$select=" o select ( COMMA select )* /
|
|
185
|
+
"$expand=" o expand ( COMMA expand )* /
|
|
186
|
+
"$filter=" o filter /
|
|
187
|
+
"$orderby=" o orderby ( COMMA orderby )* /
|
|
188
|
+
"$top=" o top /
|
|
189
|
+
"$skip=" o skip /
|
|
190
|
+
"$search=" o search /
|
|
191
|
+
"$count=" o count /
|
|
230
192
|
"$apply=" o apply /
|
|
231
193
|
custom
|
|
232
194
|
|
|
233
195
|
|
|
234
196
|
select
|
|
235
|
-
= col:(
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
) {
|
|
239
|
-
SELECT.expand = SELECT.expand || []
|
|
240
|
-
SELECT.expand.push(col)
|
|
197
|
+
= col:('*'/ref) {
|
|
198
|
+
SELECT.columns = Array.isArray(SELECT.columns) ? SELECT.columns : []
|
|
199
|
+
if (!SELECT.columns.find(_compareRefs(col))) SELECT.columns.push(col)
|
|
241
200
|
return col
|
|
242
201
|
}
|
|
243
202
|
|
|
244
203
|
expand =
|
|
245
|
-
(
|
|
204
|
+
(
|
|
205
|
+
c:('*'/ref) {
|
|
206
|
+
const col = c === '*' ? {} : c
|
|
207
|
+
col.expand = '*'
|
|
208
|
+
if (!Array.isArray(SELECT.expand)) SELECT.expand = []
|
|
209
|
+
if (!SELECT.expand.find(_compareRefs(col))) SELECT.expand.push(col)
|
|
210
|
+
return col
|
|
211
|
+
}
|
|
212
|
+
)
|
|
246
213
|
( // --- nested query options, if any
|
|
247
214
|
(OPEN {
|
|
248
215
|
stack.push (SELECT)
|
|
249
|
-
SELECT = SELECT.expand[SELECT.expand.length
|
|
250
|
-
SELECT.expand =
|
|
216
|
+
SELECT = SELECT.expand[SELECT.expand.length-1]
|
|
217
|
+
SELECT.expand = '*' // by default expand everything
|
|
251
218
|
})(
|
|
252
|
-
|
|
219
|
+
expandOptions:( o ";"? o ExpandOption)*
|
|
253
220
|
{
|
|
254
|
-
|
|
255
|
-
if (
|
|
221
|
+
if (SELECT.columns) {
|
|
222
|
+
if (SELECT.expand === '*') SELECT.expand = []
|
|
223
|
+
for (const col of SELECT.columns) {
|
|
224
|
+
if (!SELECT.expand.find(_compareRefs(col))) SELECT.expand.push(col)
|
|
225
|
+
}
|
|
226
|
+
delete SELECT.columns
|
|
227
|
+
} else {
|
|
228
|
+
if (Array.isArray(SELECT.expand) && SELECT.expand.indexOf('*') === -1) SELECT.expand.unshift('*')
|
|
229
|
+
}
|
|
256
230
|
}
|
|
257
231
|
)(CLOSE {
|
|
258
232
|
SELECT = stack.pop()
|
|
@@ -272,7 +246,7 @@
|
|
|
272
246
|
|
|
273
247
|
search
|
|
274
248
|
= p:search_clause {SELECT.search = p}
|
|
275
|
-
|
|
249
|
+
|
|
276
250
|
search_clause
|
|
277
251
|
= p:( n:NOT? {return n?[n]:[]} )(
|
|
278
252
|
OPEN xpr:search_clause CLOSE {p.push({xpr})}
|
|
@@ -292,6 +266,7 @@
|
|
|
292
266
|
/ comp:comparison {p.push(...comp)}
|
|
293
267
|
/ lambda:lambda {p.push(...lambda)}
|
|
294
268
|
/ func:boolish {p.push(func)}
|
|
269
|
+
/ val:bool {p.push({val})}
|
|
295
270
|
)( ao:(AND/OR) more:where_clause {p.push(ao,...more)} )*
|
|
296
271
|
{return p}
|
|
297
272
|
|
|
@@ -351,7 +326,7 @@
|
|
|
351
326
|
= ref:(function/ref) sort:( _ s:$("asc"/"desc") {return s})? {
|
|
352
327
|
const appendObj = $(ref, sort && {sort});
|
|
353
328
|
SELECT.orderBy = SELECT.orderBy ?
|
|
354
|
-
|
|
329
|
+
[...SELECT.orderBy, appendObj] :
|
|
355
330
|
[appendObj]
|
|
356
331
|
}
|
|
357
332
|
|
|
@@ -390,13 +365,13 @@
|
|
|
390
365
|
}
|
|
391
366
|
|
|
392
367
|
val
|
|
393
|
-
|
|
368
|
+
= val:(bool / date) {return {val}}
|
|
394
369
|
/ guid
|
|
395
370
|
/ val:number {return typeof val === 'number' ? {val} : { val, literal:'number' }}
|
|
396
371
|
/ val:string {return {val}}
|
|
397
372
|
|
|
398
373
|
jsonObject = val:$("{" (jsonObject / [^}])* "}") {return {val}}
|
|
399
|
-
|
|
374
|
+
|
|
400
375
|
jsonArray = val:$("[" o "]" / "[" o "{" (jsonArray / [^\]])* "]") {return {val}}
|
|
401
376
|
|
|
402
377
|
list
|
|
@@ -405,7 +380,7 @@
|
|
|
405
380
|
|
|
406
381
|
function "a function call"
|
|
407
382
|
= func:$[a-z]+ OPEN a:operand more:( COMMA o:operand {return o} )* CLOSE {
|
|
408
|
-
if (strict && !(func in strict.functions))
|
|
383
|
+
if (strict && !(func in strict.functions)) exception("'"+ func +"' is an unknown function in OData URL spec (strict mode)")
|
|
409
384
|
return { func, args:[a,...more] }
|
|
410
385
|
}
|
|
411
386
|
|
|
@@ -419,7 +394,7 @@
|
|
|
419
394
|
OR = _ "OR"i _ {return 'or'}
|
|
420
395
|
|
|
421
396
|
|
|
422
|
-
|
|
397
|
+
//
|
|
423
398
|
// ---------- Transformations ------------
|
|
424
399
|
|
|
425
400
|
applyTrafo
|
|
@@ -448,7 +423,11 @@
|
|
|
448
423
|
aggregateItem
|
|
449
424
|
= res:("$count" alias:asAlias { return { func: 'count', args: ['*'], as: alias } }
|
|
450
425
|
/ aggregateExpr
|
|
451
|
-
) {
|
|
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
|
+
}
|
|
452
431
|
aggregateExpr
|
|
453
432
|
= path:(
|
|
454
433
|
ref
|
|
@@ -470,8 +449,8 @@
|
|
|
470
449
|
groupByElem
|
|
471
450
|
= val:(rollupSpec / ref)
|
|
472
451
|
{ (SELECT.groupBy || (SELECT.groupBy = [])).push(val) }
|
|
473
|
-
rollupSpec //
|
|
474
|
-
= "rollup" OPEN o ('$all' / ref) (o COMMA ref)+ o CLOSE
|
|
452
|
+
rollupSpec // TODO fix this + add CAP support
|
|
453
|
+
= 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;}
|
|
475
454
|
|
|
476
455
|
filterTrafo = OPEN o filter o CLOSE
|
|
477
456
|
|
|
@@ -504,7 +483,7 @@
|
|
|
504
483
|
identityTrafo = "identity"
|
|
505
484
|
|
|
506
485
|
|
|
507
|
-
|
|
486
|
+
//
|
|
508
487
|
// ---------- Literals -----------
|
|
509
488
|
|
|
510
489
|
bool = b:("true" / "false") { return b === 'true'}
|
|
@@ -518,7 +497,7 @@
|
|
|
518
497
|
{return s.replace(/\\\\/g,"\\").replace(/\\"/g,'"')}
|
|
519
498
|
|
|
520
499
|
word
|
|
521
|
-
= s:$([
|
|
500
|
+
= s:$([a-zA-Z0-9.+-]+)
|
|
522
501
|
|
|
523
502
|
date
|
|
524
503
|
= s:$( [0-9]+"-"[0-9][0-9]"-"[0-9][0-9] // date
|
|
@@ -538,9 +517,9 @@
|
|
|
538
517
|
= !bool !guid s:$([_a-zA-Z][_a-zA-Z0-9"."]*) {return s}
|
|
539
518
|
|
|
540
519
|
guid = val:$([0-9a-zA-Z]+ "-" ([0-9a-zA-Z]+ "-"?)+)
|
|
541
|
-
|
|
520
|
+
{return {val}}
|
|
542
521
|
|
|
543
|
-
|
|
522
|
+
//
|
|
544
523
|
// ---------- Punctuation ----------
|
|
545
524
|
|
|
546
525
|
COLON = o":"o
|
|
@@ -549,11 +528,11 @@
|
|
|
549
528
|
OPEN = o"("o
|
|
550
529
|
CLOSE = o")"
|
|
551
530
|
|
|
552
|
-
|
|
531
|
+
//
|
|
553
532
|
// ---------- Whitespaces -----------
|
|
554
533
|
|
|
555
534
|
o "optional whitespaces" = $[ \t\n]*
|
|
556
535
|
_ "mandatory whitespaces" = $[ \t\n]+
|
|
557
536
|
|
|
558
|
-
|
|
537
|
+
//
|
|
559
538
|
// ------------------------------------
|