@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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const cds = require('../../cds')
|
|
2
2
|
const { SELECT, INSERT, DELETE, UPDATE } = cds.ql
|
|
3
|
+
const Query = require('../../../../lib/ql/Query')
|
|
3
4
|
|
|
4
5
|
const { resolveView } = require('./resolveView')
|
|
5
6
|
const { ensureNoDraftsSuffix } = require('./draft')
|
|
@@ -9,7 +10,9 @@ const { getEntityNameFromCQN } = require('./entityFromCqn')
|
|
|
9
10
|
const getError = require('../../common/error')
|
|
10
11
|
const { rewriteAsterisks } = require('./rewriteAsterisks')
|
|
11
12
|
const { getPathFromRef, getEntityFromPath } = require('../../common/utils/path')
|
|
13
|
+
const { addToWhere } = require('../../common/utils/cqn')
|
|
12
14
|
const { removeIsActiveEntityRecursively } = require('../../fiori/utils/where')
|
|
15
|
+
const { addRefToWhereIfNecessary } = require('../../../odata/afterburner')
|
|
13
16
|
|
|
14
17
|
const PARENT_ALIAS = '_parent_'
|
|
15
18
|
const PARENT_ALIAS_REGEX = new RegExp('^' + PARENT_ALIAS + '\\d*$')
|
|
@@ -76,14 +79,15 @@ const _getEntityName = (fromClause, entity, i) => {
|
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
const convertPathExpressionToWhere = (fromClause, model, options) => {
|
|
82
|
+
if (!fromClause.ref) return
|
|
79
83
|
if (fromClause.ref.length === 1) {
|
|
80
84
|
const target = _getTargetFromRef(fromClause.ref[0])
|
|
81
85
|
const alias = fromClause.as
|
|
82
|
-
const { where, cardinality } = fromClause.ref[0]
|
|
83
|
-
return { target, alias, where, cardinality }
|
|
86
|
+
const { where, cardinality, args } = fromClause.ref[0]
|
|
87
|
+
return { target, alias, where, cardinality, args }
|
|
84
88
|
}
|
|
85
89
|
|
|
86
|
-
let previousSelect, previousEntityName, previousTableAlias, structParent
|
|
90
|
+
let previousSelect, previousEntityName, previousTableAlias, structParent, previousArgs
|
|
87
91
|
let prefix = []
|
|
88
92
|
let columns
|
|
89
93
|
for (let i = 0; i < fromClause.ref.length; i++) {
|
|
@@ -111,6 +115,9 @@ const convertPathExpressionToWhere = (fromClause, model, options) => {
|
|
|
111
115
|
currentSelect.where(_addAliasToExpression(fromClause.ref[i].where, tableAlias))
|
|
112
116
|
}
|
|
113
117
|
|
|
118
|
+
// REVISIT: Only args in last segment are handled, intermediate ones are ignored
|
|
119
|
+
const currentArgs = fromClause.ref[i].args
|
|
120
|
+
|
|
114
121
|
if (i !== fromClause.ref.length - 1) {
|
|
115
122
|
currentSelect.columns([1])
|
|
116
123
|
}
|
|
@@ -129,12 +136,14 @@ const convertPathExpressionToWhere = (fromClause, model, options) => {
|
|
|
129
136
|
previousTableAlias = tableAlias
|
|
130
137
|
previousSelect = currentSelect
|
|
131
138
|
previousEntityName = currentEntityName
|
|
139
|
+
previousArgs = currentArgs
|
|
132
140
|
}
|
|
133
141
|
|
|
134
142
|
return {
|
|
135
143
|
target: previousEntityName,
|
|
136
144
|
alias: previousTableAlias,
|
|
137
145
|
where: previousSelect && previousSelect.SELECT && previousSelect.SELECT.where,
|
|
146
|
+
args: previousArgs,
|
|
138
147
|
columns
|
|
139
148
|
}
|
|
140
149
|
}
|
|
@@ -179,24 +188,31 @@ const _getOrderByForWindowFn = bottomTop => {
|
|
|
179
188
|
|
|
180
189
|
const _getWindowXpr = (groupBy, bottomTop) => {
|
|
181
190
|
const xpr = [{ func: 'ROW_NUMBER', args: [] }, 'OVER', '(']
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
+
if (groupBy)
|
|
192
|
+
xpr.push(
|
|
193
|
+
'PARTITION BY',
|
|
194
|
+
...groupBy.reduce((acc, el, i) => {
|
|
195
|
+
if (i < groupBy.length - 1) {
|
|
196
|
+
acc.push(el)
|
|
197
|
+
acc.push(',')
|
|
198
|
+
} else {
|
|
199
|
+
acc.push(el)
|
|
200
|
+
}
|
|
191
201
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
202
|
+
return acc
|
|
203
|
+
}, [])
|
|
204
|
+
)
|
|
195
205
|
xpr.push('ORDER BY', _getOrderByForWindowFn(bottomTop))
|
|
196
206
|
xpr.push(')')
|
|
197
207
|
return { xpr: xpr, as: 'rowNumber' }
|
|
198
208
|
}
|
|
199
209
|
|
|
210
|
+
const _isNavCountFunc = el => el.func && el.func === 'count' && el.args[0] !== '*'
|
|
211
|
+
|
|
212
|
+
const _isCountNavigation = columns => {
|
|
213
|
+
return columns.some(_isNavCountFunc)
|
|
214
|
+
}
|
|
215
|
+
|
|
200
216
|
const _isBottomTop = columns => {
|
|
201
217
|
return columns.some(el => el.func && (el.func === 'topcount' || el.func === 'bottomcount'))
|
|
202
218
|
}
|
|
@@ -205,6 +221,89 @@ const _getWindColumns = (columns, groupBy, bottomTop) => {
|
|
|
205
221
|
return [].concat(columns, _getWindowXpr(groupBy, bottomTop))
|
|
206
222
|
}
|
|
207
223
|
|
|
224
|
+
const _convertCountNavigation = (SELECT, model) => {
|
|
225
|
+
const entityName = SELECT.from.ref[0].id || SELECT.from.ref[0]
|
|
226
|
+
const entity = model.definitions[entityName]
|
|
227
|
+
|
|
228
|
+
const newWhere = []
|
|
229
|
+
for (let i = 0; i < SELECT.where.length; i++) {
|
|
230
|
+
const element = SELECT.where[i]
|
|
231
|
+
|
|
232
|
+
if (!element.func) {
|
|
233
|
+
newWhere.push(element)
|
|
234
|
+
continue
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const navigations = element.args[0].ref
|
|
238
|
+
const navigationName = navigations[0].id || navigations[0]
|
|
239
|
+
if (entity.elements[navigationName]) {
|
|
240
|
+
let currentEntity = entity
|
|
241
|
+
let topQuery
|
|
242
|
+
let lastQuery
|
|
243
|
+
|
|
244
|
+
for (let j = 0; j < navigations.length; j++) {
|
|
245
|
+
if (navigations[j].where) {
|
|
246
|
+
addRefToWhereIfNecessary(navigations[j].where, currentEntity)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const nextNavigation = navigations[j]
|
|
250
|
+
const nestedExistsQuery = _buildExistsQuery(
|
|
251
|
+
currentEntity,
|
|
252
|
+
nextNavigation,
|
|
253
|
+
j > 0 ? `ALIAS_${j - 1}` : currentEntity.name,
|
|
254
|
+
`ALIAS_${j}`
|
|
255
|
+
)
|
|
256
|
+
if (!topQuery) {
|
|
257
|
+
topQuery = nestedExistsQuery
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const nestedNavigationName = nextNavigation.id || nextNavigation
|
|
261
|
+
if (j === navigations.length - 1) {
|
|
262
|
+
nestedExistsQuery.columns('count(1) as counted')
|
|
263
|
+
if (lastQuery) {
|
|
264
|
+
lastQuery.where([nestedExistsQuery, SELECT.where[i + 1], SELECT.where[i + 2]])
|
|
265
|
+
newWhere.push('exists', topQuery)
|
|
266
|
+
} else {
|
|
267
|
+
newWhere.push(nestedExistsQuery, SELECT.where[i + 1], SELECT.where[i + 2])
|
|
268
|
+
}
|
|
269
|
+
} else {
|
|
270
|
+
if (lastQuery) {
|
|
271
|
+
lastQuery.where('exists', nestedExistsQuery)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
lastQuery = nestedExistsQuery
|
|
275
|
+
currentEntity = currentEntity.elements[nestedNavigationName]._target
|
|
276
|
+
}
|
|
277
|
+
i += 2
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
SELECT.where = newWhere
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const _addTableName = (where, tableName) => {
|
|
284
|
+
return where.map(ref => {
|
|
285
|
+
if (ref.ref) {
|
|
286
|
+
ref.ref.unshift(tableName)
|
|
287
|
+
}
|
|
288
|
+
return ref
|
|
289
|
+
})
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const _buildExistsQuery = (entity, navigation, currentAlias, nextAlias) => {
|
|
293
|
+
const target = entity.elements[navigation.id || navigation].target
|
|
294
|
+
|
|
295
|
+
const existsQuery = cds.ql.SELECT.from(target)
|
|
296
|
+
existsQuery.SELECT.from.as = nextAlias
|
|
297
|
+
|
|
298
|
+
if (navigation && navigation.where) {
|
|
299
|
+
existsQuery.where(_addTableName(navigation.where, nextAlias))
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const onCond = entity.elements[navigation.id || navigation]._relations.join(nextAlias, currentAlias)
|
|
303
|
+
|
|
304
|
+
return existsQuery.where(onCond)
|
|
305
|
+
}
|
|
306
|
+
|
|
208
307
|
const _createWindowCQN = (SELECT, model) => {
|
|
209
308
|
const bottomTop = SELECT.columns.filter(el => _isBottomTop([el]))
|
|
210
309
|
const columns = (SELECT.columns = [])
|
|
@@ -237,7 +336,7 @@ const _isAll = element => {
|
|
|
237
336
|
}
|
|
238
337
|
|
|
239
338
|
// eslint-disable-next-line complexity
|
|
240
|
-
const
|
|
339
|
+
const _getWhereExistsSubSelect = (cqn, where, index, model, options) => {
|
|
241
340
|
const _unshiftRefsWithNavigation = nav => el => {
|
|
242
341
|
if (el.ref) return { ref: [...nav, ...el.ref] }
|
|
243
342
|
if (el.xpr) return { xpr: el.xpr.map(_unshiftRefsWithNavigation(nav)) }
|
|
@@ -254,10 +353,11 @@ const _getLambdaSubSelect = (cqn, where, index, lambdaOp, model, options) => {
|
|
|
254
353
|
const ref = cqn.SELECT.from.ref || (cqn.SELECT.from.args && cqn.SELECT.from.args[0].ref)
|
|
255
354
|
const queryTarget = getEntityFromPath(getPathFromRef(ref), model)
|
|
256
355
|
|
|
257
|
-
|
|
356
|
+
let nav = where[index].ref.map(el => (el.id ? el.id : el))
|
|
258
357
|
const last = where[index].ref.slice(-1)[0]
|
|
259
358
|
const navName = queryTarget.elements[nav[0]] ? nav[0] : nav[nav.length - 1]
|
|
260
359
|
const navElement = queryTarget.elements[navName]
|
|
360
|
+
|
|
261
361
|
const lastElement = nav.reduce((csn, segment) => {
|
|
262
362
|
if (csn.items) return csn // arrayed not supported
|
|
263
363
|
if (csn.elements) {
|
|
@@ -267,9 +367,14 @@ const _getLambdaSubSelect = (cqn, where, index, lambdaOp, model, options) => {
|
|
|
267
367
|
return next
|
|
268
368
|
}
|
|
269
369
|
}, queryTarget)
|
|
270
|
-
|
|
271
|
-
|
|
370
|
+
|
|
371
|
+
if (lastElement.items) {
|
|
372
|
+
// > arrayed
|
|
272
373
|
throw getError(501, `Condition expression with arrayed elements is not supported`)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (nav[0] === outerAlias) nav = nav.slice(1)
|
|
377
|
+
|
|
273
378
|
const condition = last.where
|
|
274
379
|
? nav.length > 1
|
|
275
380
|
? last.where.map(_unshiftRefsWithNavigation(nav.slice(1)))
|
|
@@ -290,8 +395,11 @@ const _getLambdaSubSelect = (cqn, where, index, lambdaOp, model, options) => {
|
|
|
290
395
|
}
|
|
291
396
|
}
|
|
292
397
|
}
|
|
293
|
-
|
|
398
|
+
// skip for where: [{ val: 1 }]
|
|
399
|
+
// where: [{ func: 'contains', args: [] }] must be evaluated
|
|
400
|
+
if (condition.length > 1 || (condition.length === 1 && !('val' in condition[0]))) subSelect.where(condition)
|
|
294
401
|
}
|
|
402
|
+
|
|
295
403
|
subSelect.where(queryTarget._relations[navName].join(innerAlias, outerAlias))
|
|
296
404
|
if (cds.env.effective.odata.structs) {
|
|
297
405
|
flattenStructuredSelect(subSelect, model)
|
|
@@ -300,25 +408,76 @@ const _getLambdaSubSelect = (cqn, where, index, lambdaOp, model, options) => {
|
|
|
300
408
|
|
|
301
409
|
// nested where exists needs recursive conversion
|
|
302
410
|
options.lambdaIteration++
|
|
411
|
+
|
|
303
412
|
return _convertSelect(subSelect, model, options)
|
|
304
413
|
}
|
|
305
414
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
415
|
+
/*
|
|
416
|
+
* ensures that the where exists are fully expanded and have an infix filter
|
|
417
|
+
* example: where: ['exists', { ref: ['book', 'author'] }]
|
|
418
|
+
* -> where: ['exists', { id: 'book', where: ['exists', { id: 'author', where: [{ val: 1 }] }] }]
|
|
419
|
+
*/
|
|
420
|
+
const _ensureExpandedNestedWhereExists = ({ ref }, aliases) => {
|
|
421
|
+
const acc = {}
|
|
422
|
+
let cur = acc
|
|
423
|
+
let alias
|
|
424
|
+
for (const r of ref) {
|
|
425
|
+
if (Array.isArray(cur)) {
|
|
426
|
+
if (cur.length > 1) {
|
|
427
|
+
// > element already has condition(s)
|
|
428
|
+
cur.push('and', 'exists', { ref: [{}] })
|
|
429
|
+
cur = cur[cur.length - 1].ref[0]
|
|
430
|
+
} else {
|
|
431
|
+
cur.unshift('exists')
|
|
432
|
+
cur[1] = { ref: [{}] }
|
|
433
|
+
cur = cur[1].ref[0]
|
|
317
434
|
}
|
|
318
|
-
}
|
|
435
|
+
}
|
|
436
|
+
if (typeof r === 'string' && aliases.includes(r)) {
|
|
437
|
+
alias = r
|
|
438
|
+
} else {
|
|
439
|
+
// use Object.assign to preserve pointers
|
|
440
|
+
if (typeof r === 'string') Object.assign(cur, { id: r, where: [{ val: 1 }] })
|
|
441
|
+
else Object.assign(cur, r)
|
|
442
|
+
cur = cur.where
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
if (alias) return { ref: [alias, acc] }
|
|
446
|
+
return { ref: [acc] }
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const _convertWhereExists = (where, cqn, model, options) => {
|
|
450
|
+
for (let i = 0; i < where.length; i++) {
|
|
451
|
+
const element = where[i]
|
|
452
|
+
|
|
453
|
+
// ensure where exists are fully expanded
|
|
454
|
+
if (
|
|
455
|
+
(element === 'exists' && where[i + 1].ref && where[i > 1 ? i - 1 : 0] !== 'not') ||
|
|
456
|
+
(element === 'not' && where[i + 1] === 'exists' && where[i + 2].ref)
|
|
457
|
+
) {
|
|
458
|
+
const offset = element === 'not' ? 2 : 1
|
|
459
|
+
const aliases = cqn.SELECT.from.as
|
|
460
|
+
? [cqn.SELECT.from.as]
|
|
461
|
+
: cqn.SELECT.from.join
|
|
462
|
+
? cqn.SELECT.from.args.map(arg => arg.as)
|
|
463
|
+
: []
|
|
464
|
+
where[i + offset] = _ensureExpandedNestedWhereExists(where[i + offset], aliases)
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (element.xpr) {
|
|
468
|
+
_convertWhereExists(element.xpr) // > recursing into nested {xpr}
|
|
469
|
+
} else if (element === 'exists' && _isAny(where[i + 1])) {
|
|
470
|
+
where[i + 1] = _getWhereExistsSubSelect(cqn, where, i + 1, model, options)
|
|
471
|
+
} else if (element === 'not' && where[i + 1] === 'exists' && _isAll(where[i + 2])) {
|
|
472
|
+
where[i + 2] = _getWhereExistsSubSelect(cqn, where, i + 2, model, options)
|
|
473
|
+
}
|
|
319
474
|
}
|
|
320
475
|
}
|
|
321
476
|
|
|
477
|
+
const convertWhereExists = (cqn, model, options) => {
|
|
478
|
+
if (cqn.SELECT.where) _convertWhereExists(cqn.SELECT.where, cqn, model, options)
|
|
479
|
+
}
|
|
480
|
+
|
|
322
481
|
const _getRefIndex = (where, index) => {
|
|
323
482
|
if (
|
|
324
483
|
where[index - 1].ref &&
|
|
@@ -342,8 +501,8 @@ const _getRefIndex = (where, index) => {
|
|
|
342
501
|
}
|
|
343
502
|
}
|
|
344
503
|
|
|
345
|
-
const _convertNotEqual = container => {
|
|
346
|
-
const where = container
|
|
504
|
+
const _convertNotEqual = (container, partName = 'where') => {
|
|
505
|
+
const where = container[partName]
|
|
347
506
|
|
|
348
507
|
if (where) {
|
|
349
508
|
let changed
|
|
@@ -360,13 +519,13 @@ const _convertNotEqual = container => {
|
|
|
360
519
|
}
|
|
361
520
|
|
|
362
521
|
if (el && el.SELECT) {
|
|
363
|
-
_convertNotEqual(el.SELECT)
|
|
522
|
+
_convertNotEqual(el.SELECT, partName)
|
|
364
523
|
}
|
|
365
524
|
})
|
|
366
525
|
|
|
367
526
|
// delete undefined values
|
|
368
527
|
if (changed) {
|
|
369
|
-
container
|
|
528
|
+
container[partName] = where.filter(el => el)
|
|
370
529
|
}
|
|
371
530
|
}
|
|
372
531
|
|
|
@@ -374,9 +533,9 @@ const _convertNotEqual = container => {
|
|
|
374
533
|
container.columns.forEach(col => {
|
|
375
534
|
if (typeof col === 'object') {
|
|
376
535
|
if (col.SELECT) {
|
|
377
|
-
_convertNotEqual(col.SELECT)
|
|
536
|
+
_convertNotEqual(col.SELECT, partName)
|
|
378
537
|
} else if (col.where) {
|
|
379
|
-
_convertNotEqual(col)
|
|
538
|
+
_convertNotEqual(col, partName)
|
|
380
539
|
}
|
|
381
540
|
}
|
|
382
541
|
})
|
|
@@ -384,7 +543,7 @@ const _convertNotEqual = container => {
|
|
|
384
543
|
}
|
|
385
544
|
|
|
386
545
|
const _ifOrderByOrWhereSkip = (queryTarget, ref, model) => {
|
|
387
|
-
if (!queryTarget.elements[ref[0]]) return
|
|
546
|
+
if (!queryTarget || !queryTarget.elements[ref[0]]) return
|
|
388
547
|
return ref.some(el => {
|
|
389
548
|
if (typeof el !== 'string') return
|
|
390
549
|
const orderbyTarget = queryTarget.elements[el]._isStructured
|
|
@@ -398,9 +557,12 @@ const _ifOrderByOrWhereSkip = (queryTarget, ref, model) => {
|
|
|
398
557
|
})
|
|
399
558
|
}
|
|
400
559
|
|
|
401
|
-
const _skip = (queryTarget, ref, model) => {
|
|
402
|
-
|
|
403
|
-
|
|
560
|
+
const _skip = (queryTarget, ref, alias, model) => {
|
|
561
|
+
// we remove the alias, if it exists
|
|
562
|
+
const _ref = ref[0] === alias ? ref.slice(1) : ref
|
|
563
|
+
if (_ref.length === 1) return queryTarget && queryTarget.elements[_ref[0]] && queryTarget.elements[_ref[0]].virtual
|
|
564
|
+
|
|
565
|
+
return _ifOrderByOrWhereSkip(queryTarget, _ref, model)
|
|
404
566
|
}
|
|
405
567
|
|
|
406
568
|
const _convertOrderByIfSkip = (orderByCQN, index) => {
|
|
@@ -412,21 +574,22 @@ const _convertWhereIfSkip = (whereCQN, index) => {
|
|
|
412
574
|
OPERATIONS.includes(whereCQN[index + 1]) ? whereCQN.splice(index + 1, 2) : whereCQN.splice(index - 2, 2)
|
|
413
575
|
}
|
|
414
576
|
|
|
415
|
-
const _convertOrderByOrWhereCQN = (orderByOrWhereCQN, target, model, processFn) => {
|
|
577
|
+
const _convertOrderByOrWhereCQN = (orderByOrWhereCQN, target, model, alias, processFn) => {
|
|
416
578
|
const queryTarget = model.definitions[ensureNoDraftsSuffix(target)]
|
|
417
579
|
|
|
418
580
|
orderByOrWhereCQN.forEach((el, index) => {
|
|
419
|
-
if (el.ref && _skip(queryTarget, el.ref, model)) processFn(orderByOrWhereCQN, index)
|
|
581
|
+
if (el.ref && _skip(queryTarget, el.ref, alias, model)) processFn(orderByOrWhereCQN, index)
|
|
420
582
|
})
|
|
421
583
|
}
|
|
422
584
|
|
|
423
585
|
const _convertOrderByOrWhereIfSkip = (cqn, target, model) => {
|
|
586
|
+
const alias = cqn.SELECT.from.as
|
|
424
587
|
if (cqn.SELECT.orderBy && cqn.SELECT.orderBy.length > 1) {
|
|
425
|
-
_convertOrderByOrWhereCQN(cqn.SELECT.orderBy, target, model, _convertOrderByIfSkip)
|
|
588
|
+
_convertOrderByOrWhereCQN(cqn.SELECT.orderBy, target, model, alias, _convertOrderByIfSkip)
|
|
426
589
|
}
|
|
427
590
|
|
|
428
591
|
if (cqn.SELECT.where) {
|
|
429
|
-
_convertOrderByOrWhereCQN(cqn.SELECT.where, target, model, _convertWhereIfSkip)
|
|
592
|
+
_convertOrderByOrWhereCQN(cqn.SELECT.where, target, model, alias, _convertWhereIfSkip)
|
|
430
593
|
}
|
|
431
594
|
}
|
|
432
595
|
|
|
@@ -449,7 +612,7 @@ const _convertExpand = expand => {
|
|
|
449
612
|
const _convertRefWhereInExpand = columns => {
|
|
450
613
|
if (columns) {
|
|
451
614
|
columns.forEach(col => {
|
|
452
|
-
if (col.expand) {
|
|
615
|
+
if (col.expand && typeof col.expand !== 'string') {
|
|
453
616
|
_convertExpand(col.expand)
|
|
454
617
|
}
|
|
455
618
|
})
|
|
@@ -486,102 +649,115 @@ const _flattenXpr = cqn => {
|
|
|
486
649
|
cqn.forEach(_flattenCQN)
|
|
487
650
|
}
|
|
488
651
|
|
|
652
|
+
const _convertPathExpression = (SELECT, model, options = {}) => {
|
|
653
|
+
const prevAlias = SELECT.from.as
|
|
654
|
+
for (const whereEl of SELECT.where || []) {
|
|
655
|
+
if (typeof whereEl === 'object' && whereEl.SELECT) _convertPathExpression(whereEl.SELECT, model)
|
|
656
|
+
}
|
|
657
|
+
const conversion = convertPathExpressionToWhere(SELECT.from, model, options)
|
|
658
|
+
if (!conversion) return
|
|
659
|
+
const { target, alias, where, cardinality, columns, args } = conversion
|
|
660
|
+
|
|
661
|
+
// REVISIT: It's not possible to just change the reference (i.e. SELECT.from.ref = [target])
|
|
662
|
+
// as many parts of our code base still refer to SELECT.from (e.g. authorization)
|
|
663
|
+
if (args) {
|
|
664
|
+
SELECT.from = { ref: [{ id: target, args }] }
|
|
665
|
+
} else {
|
|
666
|
+
SELECT.from = { ref: [target] }
|
|
667
|
+
}
|
|
668
|
+
if (alias) {
|
|
669
|
+
SELECT.from.as = alias
|
|
670
|
+
if (SELECT.where && alias !== prevAlias) {
|
|
671
|
+
SELECT.where = _addAliasToExpression(SELECT.where, alias)
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
if (columns) {
|
|
675
|
+
// TODO: use streaming as outer property
|
|
676
|
+
if (options.isStreaming) SELECT.columns = columns
|
|
677
|
+
else {
|
|
678
|
+
if (!SELECT.columns) {
|
|
679
|
+
// Okra always wants to have the key values, remove once we relax this requirement
|
|
680
|
+
if (model.definitions[target] && model.definitions[target].keys) {
|
|
681
|
+
SELECT.columns = Object.keys(model.definitions[target].keys)
|
|
682
|
+
.filter(k => !model.definitions[target].keys[k].isAssociation)
|
|
683
|
+
.map(k => ({ ref: [k] }))
|
|
684
|
+
} else SELECT.columns = []
|
|
685
|
+
}
|
|
686
|
+
SELECT.columns.push(...columns)
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
if (cardinality && cardinality.max === 1) {
|
|
690
|
+
SELECT.one = true
|
|
691
|
+
}
|
|
692
|
+
// TODO: REVISIT: We need to add alias to subselect in .where, .columns, .from, ... etc
|
|
693
|
+
if (where) {
|
|
694
|
+
if (options.isDraft) {
|
|
695
|
+
addToWhere({ SELECT }, where)
|
|
696
|
+
} else {
|
|
697
|
+
addToWhere({ SELECT }, removeIsActiveEntityRecursively(where))
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
489
702
|
// eslint-disable-next-line complexity
|
|
490
|
-
const _convertSelect = (query, model,
|
|
491
|
-
const
|
|
492
|
-
|
|
703
|
+
const _convertSelect = (query, model, _options) => {
|
|
704
|
+
const options = Object.assign(
|
|
705
|
+
{
|
|
706
|
+
isDB: _options.service instanceof cds.DatabaseService,
|
|
707
|
+
isDraft: _options.draft,
|
|
708
|
+
isStreaming: query._streaming
|
|
709
|
+
},
|
|
710
|
+
_options
|
|
711
|
+
)
|
|
712
|
+
// ensure query is ql enabled
|
|
713
|
+
if (!(query instanceof Query)) Object.setPrototypeOf(query, Object.getPrototypeOf(SELECT()))
|
|
714
|
+
if (query.SELECT.from && query.SELECT.from.SELECT) {
|
|
715
|
+
if (query.SELECT._4odata) query.SELECT.from.SELECT._4odata = true
|
|
716
|
+
query.SELECT.from = _convertSelect(query.SELECT.from, model, options)
|
|
717
|
+
}
|
|
718
|
+
|
|
493
719
|
// REVISIT: a temporary workaround for xpr from new parser
|
|
494
720
|
if (cds.env.features.odata_new_parser) _flattenCQN(query)
|
|
495
721
|
|
|
496
722
|
// lambda functions
|
|
497
|
-
|
|
723
|
+
convertWhereExists(query, model, options)
|
|
498
724
|
|
|
499
725
|
// add 'or is null' in case of '!='
|
|
500
|
-
if (query.SELECT._4odata)
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
const cqnSelectFromRef = query.SELECT.from.ref
|
|
504
|
-
|
|
505
|
-
// no path expression
|
|
506
|
-
if (!cqnSelectFromRef || (cqnSelectFromRef.length === 1 && !cqnSelectFromRef[0].where)) {
|
|
507
|
-
rewriteAsterisks(query, cqnSelectFromRef && model.definitions[cqnSelectFromRef[0]], isDB, isDraft)
|
|
508
|
-
// extract where clause if it is in column expand ref
|
|
509
|
-
_convertRefWhereInExpand(query.SELECT.columns)
|
|
510
|
-
// remove virtual and with skip annotated fields in orderby and where
|
|
511
|
-
_convertOrderByOrWhereIfSkip(query, getEntityNameFromCQN(query), model)
|
|
512
|
-
|
|
513
|
-
if (cqnSelectFromRef && query.SELECT.search && !options.suppressSearch) {
|
|
514
|
-
searchOptions = { ...searchOptions, ...{ targetName: cqnSelectFromRef[0] } }
|
|
515
|
-
search2cqn4sql(query, model, searchOptions)
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
if (query.SELECT.columns && cds.env.effective.odata.structs) {
|
|
519
|
-
flattenStructuredSelect(query, model)
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
// topcount with groupby
|
|
523
|
-
if (query.SELECT.columns && _isBottomTop(query.SELECT.columns)) {
|
|
524
|
-
_createWindowCQN(query.SELECT, model)
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
return query
|
|
726
|
+
if (query.SELECT._4odata) {
|
|
727
|
+
_convertNotEqual(query.SELECT, 'where')
|
|
728
|
+
_convertNotEqual(query.SELECT, 'having')
|
|
528
729
|
}
|
|
529
730
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
if (query._streaming) query.SELECT.columns = columns
|
|
535
|
-
else query.SELECT.columns.push(...columns)
|
|
731
|
+
_convertPathExpression(query.SELECT, model, options)
|
|
732
|
+
rewriteAsterisks(query, model, options.isDB, options.isDraft)
|
|
733
|
+
if (query.SELECT.where && _isCountNavigation(query.SELECT.where)) {
|
|
734
|
+
_convertCountNavigation(query.SELECT, model)
|
|
536
735
|
}
|
|
537
736
|
|
|
538
737
|
// extract where clause if it is in column expand ref
|
|
539
738
|
_convertRefWhereInExpand(query.SELECT.columns)
|
|
540
739
|
|
|
541
|
-
//
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
const select = SELECT.from(target, query.SELECT.columns)
|
|
545
|
-
|
|
546
|
-
if (alias) {
|
|
547
|
-
select.SELECT.from.as = alias
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
// TODO: REVISIT: We need to add alias to subselect in .where, .columns, .from, ... etc
|
|
551
|
-
if (where) {
|
|
552
|
-
if (!isDraft) {
|
|
553
|
-
select.where(removeIsActiveEntityRecursively(where))
|
|
554
|
-
} else {
|
|
555
|
-
select.where(where)
|
|
556
|
-
}
|
|
557
|
-
}
|
|
740
|
+
// REVISIT: The following operations only work for _one_ entity.
|
|
741
|
+
// We must also enable them for joins etc.
|
|
742
|
+
const { entityName, alias } = getEntityNameFromCQN(query)
|
|
558
743
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
}
|
|
744
|
+
// remove virtual and with skip annotated fields in orderby and where
|
|
745
|
+
_convertOrderByOrWhereIfSkip(query, entityName, model)
|
|
562
746
|
|
|
563
|
-
if (query.SELECT.search) {
|
|
564
|
-
|
|
565
|
-
search2cqn4sql(query, model, searchOptions)
|
|
747
|
+
if (query.SELECT.search && !options.suppressSearch) {
|
|
748
|
+
search2cqn4sql(query, model, { ...query._searchOptions, ...{ entityName, alias } })
|
|
566
749
|
}
|
|
567
750
|
|
|
568
|
-
if (query.SELECT.
|
|
569
|
-
|
|
751
|
+
if (query.SELECT.columns && cds.env.effective.odata.structs) {
|
|
752
|
+
flattenStructuredSelect(query, model)
|
|
570
753
|
}
|
|
571
754
|
|
|
572
|
-
//
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
columns: select.SELECT.columns,
|
|
576
|
-
from: select.SELECT.from,
|
|
577
|
-
where: select.SELECT.where
|
|
578
|
-
})
|
|
579
|
-
|
|
580
|
-
if (select.SELECT.columns && cds.env.effective.odata.structs) {
|
|
581
|
-
flattenStructuredSelect(select, model)
|
|
755
|
+
// topcount with groupby
|
|
756
|
+
if (query.SELECT.columns && _isBottomTop(query.SELECT.columns)) {
|
|
757
|
+
_createWindowCQN(query.SELECT, model)
|
|
582
758
|
}
|
|
583
759
|
|
|
584
|
-
return
|
|
760
|
+
return query
|
|
585
761
|
}
|
|
586
762
|
|
|
587
763
|
const _convertInsert = (query, model, options) => {
|
|
@@ -732,5 +908,6 @@ const cqn2cqn4sql = (query, model, options = { suppressSearch: false }) => {
|
|
|
732
908
|
|
|
733
909
|
module.exports = {
|
|
734
910
|
cqn2cqn4sql,
|
|
911
|
+
convertWhereExists,
|
|
735
912
|
convertPathExpressionToWhere
|
|
736
913
|
}
|