@sap/cds 5.6.2 → 5.7.2
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 +133 -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 +7 -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 +13 -4
- 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 -65
- 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 +26 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +5 -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 -35
- 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/perf/index.js +24 -0
- package/libx/_runtime/common/utils/cqn.js +58 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +297 -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/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 +155 -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.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} +182 -118
- package/libx/odata/index.js +18 -15
- 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
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
const cds = require('
|
|
1
|
+
const cds = require('../_runtime/cds')
|
|
2
2
|
|
|
3
|
-
const { where2obj } = require('
|
|
4
|
-
const { findCsnTargetFor } = require('
|
|
3
|
+
const { where2obj } = require('../_runtime/common/utils/cqn')
|
|
4
|
+
const { findCsnTargetFor } = require('../_runtime/common/utils/csn')
|
|
5
5
|
|
|
6
6
|
function _keysOf(entity) {
|
|
7
7
|
return entity && entity.keys
|
|
@@ -11,11 +11,11 @@ function _keysOf(entity) {
|
|
|
11
11
|
: []
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
function _getDefinition(definition, name) {
|
|
14
|
+
function _getDefinition(definition, name, namespace) {
|
|
15
15
|
return (
|
|
16
16
|
(definition.definitions && definition.definitions[name]) ||
|
|
17
17
|
(definition.elements && definition.elements[name]) ||
|
|
18
|
-
(definition.actions && definition.actions[name]) ||
|
|
18
|
+
(definition.actions && (definition.actions[name] || definition.actions[name.replace(namespace + '.', '')])) ||
|
|
19
19
|
definition[name]
|
|
20
20
|
)
|
|
21
21
|
}
|
|
@@ -29,7 +29,7 @@ function _resolveAliasInWhere(where, entity) {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
// case: single key without name, e.g., Foo(1)
|
|
32
|
-
function
|
|
32
|
+
function addRefToWhereIfNecessary(where, entity) {
|
|
33
33
|
if (!where || where.length !== 1) return 0
|
|
34
34
|
const keys = _keysOf(entity)
|
|
35
35
|
if (keys.length !== 1) return 0
|
|
@@ -37,8 +37,9 @@ function _addRefToWhereIfNecessary(where, entity) {
|
|
|
37
37
|
return 1
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
function _processSegments(cqn, model) {
|
|
41
|
-
const
|
|
40
|
+
function _processSegments(cqn, model, namespace) {
|
|
41
|
+
const from = _resolveFrom(cqn.SELECT.from)
|
|
42
|
+
const ref = from.ref
|
|
42
43
|
|
|
43
44
|
let current = model
|
|
44
45
|
let path
|
|
@@ -59,7 +60,15 @@ function _processSegments(cqn, model) {
|
|
|
59
60
|
let base = ref[i - keyCount]
|
|
60
61
|
if (!base.id) base = { id: base, where: [] }
|
|
61
62
|
if (base.where.length) base.where.push('and')
|
|
62
|
-
|
|
63
|
+
if (ref[i].id) {
|
|
64
|
+
// > fix case key value parsed to collection with filter
|
|
65
|
+
const val = `${ref[i].id}(${Object.keys(params)
|
|
66
|
+
.map(k => `${k}='${params[k]}'`)
|
|
67
|
+
.join(',')})`
|
|
68
|
+
base.where.push({ ref: [key] }, '=', { val })
|
|
69
|
+
} else {
|
|
70
|
+
base.where.push({ ref: [key] }, '=', { val: element.type === 'cds.Integer' ? Number(seg) : seg })
|
|
71
|
+
}
|
|
63
72
|
ref[i] = null
|
|
64
73
|
ref[i - keyCount] = base
|
|
65
74
|
incompleteKeys = keyCount < keys.length
|
|
@@ -71,7 +80,7 @@ function _processSegments(cqn, model) {
|
|
|
71
80
|
|
|
72
81
|
path = path ? path + `${path.match(/:/) ? '.' : ':'}${seg}` : seg
|
|
73
82
|
// REVISIT: replace use case: <namespace>.<entity>_history is at <namespace>.<entity>.history
|
|
74
|
-
current = _getDefinition(current, seg) || _getDefinition(current, seg.replace(/_/g, '.'))
|
|
83
|
+
current = _getDefinition(current, seg, namespace) || _getDefinition(current, seg.replace(/_/g, '.'), namespace)
|
|
75
84
|
// REVISIT: 404 or 400?
|
|
76
85
|
if (!current) cds.error(`Invalid resource path "${path}"`, { code: 404 })
|
|
77
86
|
|
|
@@ -80,7 +89,7 @@ function _processSegments(cqn, model) {
|
|
|
80
89
|
one = !!(ref[i].where || current._isSingleton)
|
|
81
90
|
incompleteKeys = ref[i].where ? false : i === ref.length - 1 || one ? false : true
|
|
82
91
|
if (ref[i].where) {
|
|
83
|
-
keyCount +=
|
|
92
|
+
keyCount += addRefToWhereIfNecessary(ref[i].where, current)
|
|
84
93
|
_resolveAliasInWhere(ref[i].where, current)
|
|
85
94
|
}
|
|
86
95
|
} else if ({ action: 1, function: 1 }[current.kind]) {
|
|
@@ -98,7 +107,7 @@ function _processSegments(cqn, model) {
|
|
|
98
107
|
incompleteKeys = one || i === ref.length - 1 ? false : true
|
|
99
108
|
current = model.definitions[current.target]
|
|
100
109
|
if (ref[i].where) {
|
|
101
|
-
keyCount +=
|
|
110
|
+
keyCount += addRefToWhereIfNecessary(ref[i].where, current)
|
|
102
111
|
_resolveAliasInWhere(ref[i].where, current)
|
|
103
112
|
}
|
|
104
113
|
} else if (current._isStructured) {
|
|
@@ -125,7 +134,7 @@ function _processSegments(cqn, model) {
|
|
|
125
134
|
}
|
|
126
135
|
|
|
127
136
|
// remove all nulled refs
|
|
128
|
-
|
|
137
|
+
from.ref = ref.filter(r => r)
|
|
129
138
|
|
|
130
139
|
// one?
|
|
131
140
|
if (one) cqn.SELECT.one = true
|
|
@@ -135,11 +144,13 @@ function _processSegments(cqn, model) {
|
|
|
135
144
|
cqn.__target = current
|
|
136
145
|
}
|
|
137
146
|
|
|
147
|
+
const _resolveFrom = from => (from.SELECT ? _resolveFrom(from.SELECT.from) : from)
|
|
148
|
+
|
|
138
149
|
function _4service(service) {
|
|
139
150
|
const { namespace, model } = service
|
|
140
151
|
|
|
141
152
|
return cqn => {
|
|
142
|
-
const { ref } = cqn.SELECT.from
|
|
153
|
+
const { ref } = _resolveFrom(cqn.SELECT.from)
|
|
143
154
|
|
|
144
155
|
// REVISIT: shouldn't be necessary
|
|
145
156
|
/*
|
|
@@ -154,7 +165,7 @@ function _4service(service) {
|
|
|
154
165
|
/*
|
|
155
166
|
* key vs. path segments (/Books/1/author/books/2/...) and more
|
|
156
167
|
*/
|
|
157
|
-
_processSegments(cqn, model)
|
|
168
|
+
_processSegments(cqn, model, namespace)
|
|
158
169
|
|
|
159
170
|
return cqn
|
|
160
171
|
}
|
|
@@ -166,5 +177,6 @@ module.exports = {
|
|
|
166
177
|
for: service => {
|
|
167
178
|
if (!cache.has(service)) cache.set(service, _4service(service))
|
|
168
179
|
return cache.get(service)
|
|
169
|
-
}
|
|
180
|
+
},
|
|
181
|
+
addRefToWhereIfNecessary
|
|
170
182
|
}
|
|
@@ -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,86 @@
|
|
|
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)
|
|
113
121
|
}
|
|
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
122
|
}
|
|
119
123
|
|
|
120
124
|
// ---------- Entity Paths ---------------
|
|
@@ -129,11 +133,19 @@
|
|
|
129
133
|
delete SELECT.expand
|
|
130
134
|
delete SELECT.limit
|
|
131
135
|
delete SELECT.orderBy
|
|
136
|
+
if (SELECT.apply) {
|
|
137
|
+
SELECT.apply = { apply: SELECT.apply }
|
|
138
|
+
_handleApply(SELECT, SELECT.apply)
|
|
139
|
+
}
|
|
132
140
|
return { SELECT }
|
|
133
141
|
}
|
|
142
|
+
let onlyColumnsFromExpand
|
|
134
143
|
if (SELECT.expand) {
|
|
135
144
|
// Books?$expand=author w/o $select=author
|
|
136
|
-
if (!SELECT.columns)
|
|
145
|
+
if (!SELECT.columns) {
|
|
146
|
+
SELECT.columns = ['*']
|
|
147
|
+
onlyColumnsFromExpand = true
|
|
148
|
+
}
|
|
137
149
|
for (const exp of SELECT.expand) {
|
|
138
150
|
const idx = SELECT.columns.findIndex(_compareRefs(exp))
|
|
139
151
|
if (idx > -1) SELECT.columns.splice(idx, 1)
|
|
@@ -141,22 +153,25 @@
|
|
|
141
153
|
}
|
|
142
154
|
delete SELECT.expand
|
|
143
155
|
}
|
|
144
|
-
SELECT
|
|
145
|
-
|
|
156
|
+
_handleApply(SELECT, SELECT.apply, onlyColumnsFromExpand)
|
|
146
157
|
return { SELECT }
|
|
147
158
|
}
|
|
148
159
|
|
|
149
160
|
path
|
|
150
161
|
= "$count" {count = true}
|
|
151
162
|
/ rv:$("$ref"/"$value") {return !TECHNICAL_OPTS.includes(rv) && {from: {ref: [rv]}}}
|
|
152
|
-
/ head:(
|
|
163
|
+
/ head:(
|
|
164
|
+
(identifier filter:(OPEN CLOSE/OPEN args CLOSE)? !segment) / val:(s:(!string val){return s[1]} / segment){return [val]}
|
|
165
|
+
) tail:( '/' p:path {return p} )? {
|
|
166
|
+
const [id, filter] = head
|
|
153
167
|
// minimal: val also as path segment
|
|
154
168
|
const ref = [
|
|
155
169
|
filter
|
|
156
170
|
? filter.length > 2
|
|
157
|
-
? { id
|
|
158
|
-
: { id
|
|
159
|
-
|
|
171
|
+
? { id, where: filter[1].map(f => f.val && f.val.match && f.val.match(/^"(.*)"$/) ? { val: f.val.match(/^"(.*)"$/)[1] } : f) }
|
|
172
|
+
: { id, where: [] }
|
|
173
|
+
// hasOwnProperty in case of '{val:0}' and so on
|
|
174
|
+
: ( minimal ? `${typeof id === 'object' && Object.prototype.hasOwnProperty.call(id, 'val') ? id.val : id}` : id )
|
|
160
175
|
]
|
|
161
176
|
if (tail && tail.from) {
|
|
162
177
|
const more = tail.from.ref
|
|
@@ -170,7 +185,7 @@
|
|
|
170
185
|
|
|
171
186
|
args
|
|
172
187
|
= val:val {return [val]}
|
|
173
|
-
/ ref:ref o"="o val:
|
|
188
|
+
/ ref:ref o"="o val:val more:( COMMA args )? {
|
|
174
189
|
const args = [ ref, '=', val ]
|
|
175
190
|
if (more) args.push ('and', ...more[1])
|
|
176
191
|
return args
|
|
@@ -183,13 +198,16 @@
|
|
|
183
198
|
ExpandOption =
|
|
184
199
|
"$select=" o select ( COMMA select )* /
|
|
185
200
|
"$expand=" o expand ( COMMA expand )* /
|
|
186
|
-
"$filter=" o filter /
|
|
201
|
+
"$filter=" o f:filter{SELECT.where = f} /
|
|
187
202
|
"$orderby=" o orderby ( COMMA orderby )* /
|
|
188
203
|
"$top=" o top /
|
|
189
204
|
"$skip=" o skip /
|
|
190
|
-
"$
|
|
205
|
+
"$skiptoken=" o skip /
|
|
206
|
+
"$search=" o s:search {if (s) SELECT.search = s} /
|
|
191
207
|
"$count=" o count /
|
|
192
|
-
"$apply=" o
|
|
208
|
+
"$apply=" (o{
|
|
209
|
+
SELECT.apply = {}
|
|
210
|
+
}) apply /
|
|
193
211
|
custom
|
|
194
212
|
|
|
195
213
|
|
|
@@ -241,11 +259,15 @@
|
|
|
241
259
|
|
|
242
260
|
skip
|
|
243
261
|
= val:integer {
|
|
262
|
+
if (SELECT.limit && SELECT.limit.offset && SELECT.limit.offset.val) {
|
|
263
|
+
val += SELECT.limit.offset.val
|
|
264
|
+
}
|
|
244
265
|
(SELECT.limit || (SELECT.limit={})).offset = {val}
|
|
245
266
|
}
|
|
246
267
|
|
|
247
268
|
search
|
|
248
|
-
= p:search_clause {
|
|
269
|
+
= p:search_clause {return p}
|
|
270
|
+
/ o // Do not add search property for space only
|
|
249
271
|
|
|
250
272
|
search_clause
|
|
251
273
|
= p:( n:NOT? {return n?[n]:[]} )(
|
|
@@ -259,7 +281,7 @@
|
|
|
259
281
|
{return p}
|
|
260
282
|
|
|
261
283
|
filter
|
|
262
|
-
= p:where_clause {
|
|
284
|
+
= p:where_clause { return p }
|
|
263
285
|
|
|
264
286
|
where_clause = p:( n:NOT? {return n?[n]:[]} )(
|
|
265
287
|
OPEN xpr:where_clause CLOSE {p.push({xpr})}
|
|
@@ -352,7 +374,17 @@
|
|
|
352
374
|
= operand (_ ("add" / "sub" / "mul" / "div" / "mod") _ operand)*
|
|
353
375
|
|
|
354
376
|
operand "an operand"
|
|
355
|
-
= function / ref / val / jsonObject / jsonArray / list
|
|
377
|
+
= navigationCount / function / ref / val / jsonObject / jsonArray / list
|
|
378
|
+
|
|
379
|
+
navigationCount "navigation with $count"
|
|
380
|
+
= navigationPath:(head:identifier key:(OPEN keyArgs:args CLOSE {return keyArgs;})? '/' {
|
|
381
|
+
if (key) {
|
|
382
|
+
// we have key in xpr
|
|
383
|
+
return {id: head, where: key}
|
|
384
|
+
}
|
|
385
|
+
return head;
|
|
386
|
+
})+ count: '$count'
|
|
387
|
+
{ return {func: 'count', as: '$count', args: [{ref: navigationPath}]} }
|
|
356
388
|
|
|
357
389
|
ref "a reference"
|
|
358
390
|
= head:identifier tail:( '/' n:identifier {return n})*
|
|
@@ -366,7 +398,7 @@
|
|
|
366
398
|
|
|
367
399
|
val
|
|
368
400
|
= val:(bool / date) {return {val}}
|
|
369
|
-
/ guid
|
|
401
|
+
/ val:guid {return {val}}
|
|
370
402
|
/ val:number {return typeof val === 'number' ? {val} : { val, literal:'number' }}
|
|
371
403
|
/ val:string {return {val}}
|
|
372
404
|
|
|
@@ -380,7 +412,9 @@
|
|
|
380
412
|
|
|
381
413
|
function "a function call"
|
|
382
414
|
= func:$[a-z]+ OPEN a:operand more:( COMMA o:operand {return o} )* CLOSE {
|
|
383
|
-
if (strict && !(func in strict.functions))
|
|
415
|
+
if (strict && !(func in strict.functions)) {
|
|
416
|
+
throw Object.assign(new Error(`"${func}" is an unknown function in OData URL spec (strict mode)`), { statusCode: 400 })
|
|
417
|
+
}
|
|
384
418
|
return { func, args:[a,...more] }
|
|
385
419
|
}
|
|
386
420
|
|
|
@@ -402,7 +436,6 @@
|
|
|
402
436
|
"aggregate" aggregateTrafo /
|
|
403
437
|
"groupby" groupbyTrafo /
|
|
404
438
|
"filter" filterTrafo /
|
|
405
|
-
countTrafo /
|
|
406
439
|
|
|
407
440
|
// REVISIT: All transformations below need improvment
|
|
408
441
|
// and should supported by CAP
|
|
@@ -410,31 +443,45 @@
|
|
|
410
443
|
"search" searchTrafo /
|
|
411
444
|
"concat" concatTrafo /
|
|
412
445
|
"compute" computeTrafo /
|
|
413
|
-
"bottompercent" commonFuncTrafo
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
446
|
+
func:("topcount"/"bottomcount"/"topsum"/"bottomsum"/"toppercent"/"bottompercent") args:commonFuncTrafo {
|
|
447
|
+
const SUPPORTED_APPLY_TRANSFORMATIONS = {
|
|
448
|
+
"topcount": true,
|
|
449
|
+
"bottomcount": true,
|
|
450
|
+
"topsum": false,
|
|
451
|
+
"bottomsum": false,
|
|
452
|
+
"toppercent": false,
|
|
453
|
+
"bottompercent": false
|
|
454
|
+
}
|
|
455
|
+
if (!SUPPORTED_APPLY_TRANSFORMATIONS[func]) {
|
|
456
|
+
throw Object.assign(new Error(`Transformation "${func}" in $apply is not supported yet.`), { statusCode: 501 })
|
|
457
|
+
}
|
|
458
|
+
(SELECT.apply.aggregate || (SELECT.apply.aggregate = [])).push({ func, args })
|
|
459
|
+
} /
|
|
417
460
|
identityTrafo
|
|
418
461
|
// customFunction
|
|
419
462
|
)
|
|
420
463
|
|
|
421
464
|
aggregateTrafo
|
|
422
|
-
|
|
465
|
+
= OPEN o head:aggregateItem tail:(o COMMA o p:aggregateItem {return p})* o CLOSE {
|
|
466
|
+
let _apply = SELECT.apply
|
|
467
|
+
if (_apply.aggregate) {
|
|
468
|
+
SELECT.apply = { apply: _apply }
|
|
469
|
+
if (_apply.groupBy) SELECT.apply.groupBy = _apply.groupBy
|
|
470
|
+
_apply = SELECT.apply
|
|
471
|
+
}
|
|
472
|
+
_apply.aggregate = [head, ...tail]
|
|
473
|
+
}
|
|
423
474
|
aggregateItem
|
|
424
|
-
= res:("$count"
|
|
475
|
+
= res:("$count" as:asAlias { return { func: 'count', args: [{ val: 1 }], as } }
|
|
425
476
|
/ 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
|
-
}
|
|
477
|
+
) { return res }
|
|
431
478
|
aggregateExpr
|
|
432
479
|
= path:(
|
|
433
480
|
ref
|
|
434
481
|
// / mathCalc - needs CAP support
|
|
435
482
|
)
|
|
436
|
-
func:aggregateWith aggregateFrom?
|
|
437
|
-
{ return { func, args: [ path ], as
|
|
483
|
+
func:aggregateWith aggregateFrom? as:asAlias
|
|
484
|
+
{ return { func, args: [ path ], as } }
|
|
438
485
|
/ identifier OPEN aggregateExpr CLOSE // needs CAP support
|
|
439
486
|
// / customAggregate // needs CAP support
|
|
440
487
|
aggregateWith
|
|
@@ -445,22 +492,27 @@
|
|
|
445
492
|
= _ "as" _ alias:identifier { return alias; }
|
|
446
493
|
|
|
447
494
|
groupbyTrafo
|
|
448
|
-
= OPEN
|
|
495
|
+
= OPEN OPEN head:groupByElem tail:(COMMA p:groupByElem {return p})* (CLOSE {
|
|
496
|
+
let _apply = SELECT.apply
|
|
497
|
+
if (_apply.groupBy || _apply.where || _apply.search) {
|
|
498
|
+
SELECT.apply = { apply: _apply }
|
|
499
|
+
_apply = SELECT.apply
|
|
500
|
+
}
|
|
501
|
+
_apply.groupBy = [head, ...tail]
|
|
502
|
+
}) (COMMA apply)? CLOSE
|
|
449
503
|
groupByElem
|
|
450
|
-
=
|
|
451
|
-
{ (SELECT.groupBy || (SELECT.groupBy = [])).push(val) }
|
|
504
|
+
= c:(rollupSpec / ref) { return c }
|
|
452
505
|
rollupSpec // TODO fix this + add CAP support
|
|
453
506
|
= 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
507
|
|
|
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};
|
|
508
|
+
filterTrafo = OPEN o (where:filter{
|
|
509
|
+
let _apply = SELECT.apply
|
|
510
|
+
if (_apply.where) {
|
|
511
|
+
SELECT.apply = { apply: _apply }
|
|
512
|
+
_apply = SELECT.apply
|
|
463
513
|
}
|
|
514
|
+
_apply.where = where
|
|
515
|
+
}) o CLOSE
|
|
464
516
|
|
|
465
517
|
|
|
466
518
|
// All transformations below need improvment
|
|
@@ -471,14 +523,22 @@
|
|
|
471
523
|
/ filterTrafo (o COMMA expandTrafo)*
|
|
472
524
|
) o CLOSE
|
|
473
525
|
|
|
474
|
-
searchTrafo = OPEN o search
|
|
526
|
+
searchTrafo = OPEN o (search:search{
|
|
527
|
+
if (!search) return
|
|
528
|
+
let _apply = SELECT.apply
|
|
529
|
+
if (_apply.search) {
|
|
530
|
+
SELECT.apply = { apply: _apply }
|
|
531
|
+
_apply = SELECT.apply
|
|
532
|
+
}
|
|
533
|
+
_apply.search = search
|
|
534
|
+
}) o CLOSE
|
|
475
535
|
|
|
476
536
|
concatTrafo = OPEN o apply (o COMMA o apply)+ o CLOSE
|
|
477
537
|
|
|
478
538
|
computeTrafo = OPEN o computeExpr (o COMMA o computeExpr)* o CLOSE
|
|
479
539
|
computeExpr = where_clause asAlias
|
|
480
540
|
|
|
481
|
-
commonFuncTrafo = OPEN o operand o COMMA o operand o CLOSE
|
|
541
|
+
commonFuncTrafo = OPEN o first:operand o COMMA o second:operand o CLOSE { return [first, second] }
|
|
482
542
|
|
|
483
543
|
identityTrafo = "identity"
|
|
484
544
|
|
|
@@ -506,18 +566,22 @@
|
|
|
506
566
|
)?)
|
|
507
567
|
|
|
508
568
|
number
|
|
509
|
-
= s:$( [+-]? [0-9]+ ("."[0-9]+)? ("e"[0-9]+)? )
|
|
510
|
-
{return safeNumber(s)}
|
|
569
|
+
= s:$( [+-]? [0-9]+ ("."[0-9]+)? ("e"[0-9]+)? ) {return safeNumber(s)}
|
|
511
570
|
|
|
512
571
|
integer
|
|
513
|
-
= s:$( [+-]? [0-9]+ )
|
|
514
|
-
{return parseInt(s)}
|
|
572
|
+
= s:$( [+-]? [0-9]+ ) {return parseInt(s)}
|
|
515
573
|
|
|
516
574
|
identifier
|
|
517
575
|
= !bool !guid s:$([_a-zA-Z][_a-zA-Z0-9"."]*) {return s}
|
|
518
576
|
|
|
519
|
-
guid
|
|
520
|
-
|
|
577
|
+
guid
|
|
578
|
+
= $(hex16 hex16 "-"? hex16 "-"? hex16 "-"? hex16 "-"? hex16 hex16 hex16)
|
|
579
|
+
|
|
580
|
+
hex16
|
|
581
|
+
= $([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])
|
|
582
|
+
|
|
583
|
+
segment
|
|
584
|
+
= val:$([a-zA-Z0-9-"."_~!$&'()*+,;=:@]+){return {val}}
|
|
521
585
|
|
|
522
586
|
//
|
|
523
587
|
// ---------- Punctuation ----------
|
package/libx/odata/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
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
9
|
|
|
10
10
|
const strict = {
|
|
@@ -30,6 +30,18 @@ const strict = {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
const _2query = cqn => {
|
|
34
|
+
if (cqn.SELECT.from.SELECT) cqn.SELECT.from = _2query(cqn.SELECT.from)
|
|
35
|
+
const query = cqn.SELECT.one ? SELECT.one(cqn.SELECT.from) : SELECT.from(cqn.SELECT.from)
|
|
36
|
+
for (const prop in cqn.SELECT) {
|
|
37
|
+
if (prop === 'from') continue
|
|
38
|
+
// explicitly use cds.ql to get '_includes_or__' magic for cqn.SELECT.where containing 'or'
|
|
39
|
+
if (prop === 'where' || prop === 'having') query[prop](cqn.SELECT[prop])
|
|
40
|
+
else query.SELECT[prop] = cqn.SELECT[prop]
|
|
41
|
+
}
|
|
42
|
+
return query
|
|
43
|
+
}
|
|
44
|
+
|
|
33
45
|
/*
|
|
34
46
|
* cds.odata API
|
|
35
47
|
*/
|
|
@@ -39,6 +51,7 @@ module.exports = {
|
|
|
39
51
|
if (url.url) url = url.url
|
|
40
52
|
// REVISIT: for okra, remove when no longer needed
|
|
41
53
|
else if (url.getIncomingRequest) url = url.getIncomingRequest().url
|
|
54
|
+
|
|
42
55
|
url = decodeURIComponent(url)
|
|
43
56
|
|
|
44
57
|
options = options === 'strict' ? { strict } : options.strict ? { ...options, strict } : options
|
|
@@ -48,25 +61,15 @@ module.exports = {
|
|
|
48
61
|
let cqn
|
|
49
62
|
try {
|
|
50
63
|
cqn = odata2cqn(url, options)
|
|
51
|
-
} catch (
|
|
52
|
-
// REVISIT: additional try in catch isn't nice -> find better way
|
|
53
|
-
// known gaps -> e.message is a stringified error -> use that
|
|
54
|
-
// unknown errors -> e is the error to keep
|
|
55
|
-
let err = e
|
|
56
|
-
try {
|
|
57
|
-
err = JSON.parse(e.message)
|
|
58
|
-
} catch {
|
|
59
|
-
/* nothing to do */
|
|
60
|
-
}
|
|
64
|
+
} catch (err) {
|
|
61
65
|
err.message = 'Parsing URL failed with error: ' + err.message
|
|
62
66
|
err.statusCode = err.statusCode || 400
|
|
63
67
|
throw err
|
|
64
68
|
}
|
|
65
69
|
|
|
66
|
-
if (
|
|
70
|
+
if (options.afterburner) cqn = options.afterburner(cqn)
|
|
67
71
|
|
|
68
|
-
const query =
|
|
69
|
-
Object.assign(query.SELECT, cqn.SELECT)
|
|
72
|
+
const query = _2query(cqn)
|
|
70
73
|
|
|
71
74
|
// REVISIT: _target vs __target, i.e., pseudo csn vs actual csn
|
|
72
75
|
// DO NOT USE __target outside of libx/rest!!!
|