@sap/cds 5.6.4 → 5.7.1
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 +102 -0
- package/_i18n/i18n_fr.properties +4 -4
- package/apis/cds.d.ts +7 -10
- package/apis/connect.d.ts +3 -3
- package/apis/core.d.ts +2 -4
- package/apis/models.d.ts +2 -3
- package/apis/ql.d.ts +0 -1
- package/apis/services.d.ts +3 -3
- package/bin/build/buildTaskFactory.js +16 -10
- package/bin/build/buildTaskProviderFactory.js +3 -3
- package/bin/build/constants.js +2 -1
- package/bin/build/provider/buildTaskProviderInternal.js +14 -14
- package/bin/build/provider/hana/2migration.js +2 -3
- package/bin/build/provider/hana/index.js +34 -0
- package/bin/build/provider/hana/migrationtable.js +90 -22
- package/bin/build/provider/hana/template/undeploy.json +5 -0
- package/bin/build/provider/node-cf/index.js +9 -2
- package/bin/serve.js +16 -18
- package/lib/compile/cdsc.js +15 -5
- package/lib/compile/etc/_localized.js +4 -4
- package/lib/compile/extend.js +8 -0
- package/lib/compile/index.js +3 -1
- package/lib/compile/minify.js +61 -0
- package/lib/compile/resolve.js +4 -1
- package/lib/compile/to/gql.js +9 -0
- package/lib/compile/to/sql.js +26 -30
- package/lib/connect/index.js +1 -1
- package/lib/core/entities.js +0 -3
- package/lib/core/infer.js +1 -0
- package/lib/core/reflect.js +0 -34
- package/lib/deploy.js +25 -17
- package/lib/env/defaults.js +3 -1
- package/lib/env/index.js +8 -3
- package/lib/env/presets.js +38 -0
- package/lib/env/requires.js +16 -11
- package/lib/index.js +13 -11
- package/lib/log/format/kibana.js +3 -1
- package/lib/log/index.js +2 -2
- package/lib/req/cds-context.js +79 -0
- package/lib/req/context.js +5 -77
- package/lib/req/request.js +1 -1
- package/lib/serve/Service-api.js +8 -4
- package/lib/serve/Service-dispatch.js +0 -7
- package/lib/serve/Service-methods.js +6 -8
- package/lib/serve/Transaction.js +35 -30
- package/lib/serve/adapters.js +1 -4
- package/lib/utils/axios.js +1 -1
- package/libx/_runtime/audit/Service.js +44 -20
- package/libx/_runtime/audit/generic/personal/access.js +16 -11
- package/libx/_runtime/audit/generic/personal/modification.js +5 -5
- package/libx/_runtime/audit/generic/personal/utils.js +46 -37
- package/libx/_runtime/{common/auth → auth}/index.js +21 -7
- package/libx/_runtime/{common/auth → auth}/strategies/JWT.js +2 -2
- package/libx/_runtime/{common/auth → auth}/strategies/basic.js +2 -2
- package/libx/_runtime/{common/auth → auth}/strategies/dummy.js +1 -1
- package/libx/_runtime/{common/auth → auth}/strategies/mock.js +2 -2
- package/libx/_runtime/{common/auth → auth}/strategies/utils/uaa.js +1 -1
- package/libx/_runtime/{common/auth → auth}/strategies/utils/xssec.js +0 -0
- package/libx/_runtime/{common/auth → auth}/strategies/xsuaa.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +7 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +0 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +0 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +3 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +6 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +3 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +2 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +16 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +26 -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 +24 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +13 -10
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/ConditionalRequestControlCommand.js +0 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +0 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +54 -76
- package/libx/_runtime/cds-services/adapter/rest/RestRequest.js +0 -7
- package/libx/_runtime/cds-services/adapter/rest/handlers/create.js +3 -6
- package/libx/_runtime/cds-services/adapter/rest/handlers/delete.js +3 -6
- package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +3 -6
- package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +3 -6
- package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +3 -6
- package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +8 -4
- package/libx/_runtime/cds-services/services/Service.js +0 -6
- package/libx/_runtime/cds-services/services/utils/columns.js +10 -3
- package/libx/_runtime/cds-services/services/utils/compareJson.js +3 -6
- package/libx/_runtime/cds-services/services/utils/differ.js +4 -1
- package/libx/_runtime/cds-services/services/utils/handlerUtils.js +1 -41
- package/libx/_runtime/cds-services/util/assert.js +1 -262
- package/libx/_runtime/cds.js +6 -9
- package/libx/_runtime/common/aspects/entity.js +1 -1
- package/libx/_runtime/common/composition/delete.js +4 -2
- package/libx/_runtime/common/composition/update.js +22 -38
- package/libx/_runtime/common/composition/utils.js +3 -7
- package/libx/_runtime/common/error/standardError.js +11 -0
- package/libx/_runtime/common/generic/auth.js +61 -30
- package/libx/_runtime/common/generic/crud.js +11 -23
- package/libx/_runtime/common/generic/input.js +20 -0
- package/libx/_runtime/common/generic/put.js +4 -10
- package/libx/_runtime/common/generic/sorting.js +12 -30
- package/libx/_runtime/common/perf/index.js +24 -0
- package/libx/_runtime/common/utils/cqn.js +58 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +289 -114
- 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/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 +123 -57
- package/libx/_runtime/fiori/uiflex/index.js +1 -1
- package/libx/_runtime/fiori/uiflex/{extensibility/index.js → service.js} +3 -3
- package/libx/_runtime/fiori/utils/delete.js +7 -1
- package/libx/_runtime/hana/Service.js +1 -8
- package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -14
- package/libx/_runtime/hana/execute.js +10 -4
- package/libx/_runtime/hana/pool.js +55 -45
- package/libx/_runtime/hana/search.js +7 -6
- package/libx/_runtime/hana/search2cqn4sql.js +8 -5
- package/libx/_runtime/hana/searchToContains.js +3 -1
- package/libx/_runtime/index.js +5 -5
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +3 -3
- package/libx/_runtime/messaging/Outbox.js +53 -0
- package/libx/_runtime/messaging/common-utils/AMQPClient.js +17 -10
- package/libx/_runtime/messaging/common-utils/connections.js +14 -9
- package/libx/_runtime/messaging/common-utils/waitingTime.js +2 -0
- package/libx/_runtime/messaging/enterprise-messaging-shared.js +2 -3
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -2
- package/libx/_runtime/messaging/enterprise-messaging.js +21 -15
- package/libx/_runtime/messaging/file-based.js +5 -5
- package/libx/_runtime/messaging/message-queuing.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/odata/{odata2cqn/afterburner.js → afterburner.js} +19 -15
- package/libx/odata/{cqn2odata/index.js → cqn2odata.js} +1 -1
- package/libx/odata/{odata2cqn/grammar.pegjs → grammar.pegjs} +171 -130
- package/libx/odata/index.js +16 -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,10 @@ const _getLambdaSubSelect = (cqn, where, index, lambdaOp, model, options) => {
|
|
|
290
395
|
}
|
|
291
396
|
}
|
|
292
397
|
}
|
|
293
|
-
|
|
398
|
+
// skip for where: [{ val: 1 }]
|
|
399
|
+
if (condition.length > 1) subSelect.where(condition)
|
|
294
400
|
}
|
|
401
|
+
|
|
295
402
|
subSelect.where(queryTarget._relations[navName].join(innerAlias, outerAlias))
|
|
296
403
|
if (cds.env.effective.odata.structs) {
|
|
297
404
|
flattenStructuredSelect(subSelect, model)
|
|
@@ -300,23 +407,73 @@ const _getLambdaSubSelect = (cqn, where, index, lambdaOp, model, options) => {
|
|
|
300
407
|
|
|
301
408
|
// nested where exists needs recursive conversion
|
|
302
409
|
options.lambdaIteration++
|
|
410
|
+
|
|
303
411
|
return _convertSelect(subSelect, model, options)
|
|
304
412
|
}
|
|
305
413
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
414
|
+
/*
|
|
415
|
+
* ensures that the where exists are fully expanded and have an infix filter
|
|
416
|
+
* example: where: ['exists', { ref: ['book', 'author'] }]
|
|
417
|
+
* -> where: ['exists', { id: 'book', where: ['exists', { id: 'author', where: [{ val: 1 }] }] }]
|
|
418
|
+
*/
|
|
419
|
+
const _ensureExpandedNestedWhereExists = ({ ref }, aliases) => {
|
|
420
|
+
const acc = {}
|
|
421
|
+
let cur = acc
|
|
422
|
+
let alias
|
|
423
|
+
for (const r of ref) {
|
|
424
|
+
if (Array.isArray(cur)) {
|
|
425
|
+
if (cur.length > 1) {
|
|
426
|
+
// > element already has condition(s)
|
|
427
|
+
cur.push('and', 'exists', { ref: [{}] })
|
|
428
|
+
cur = cur[cur.length - 1].ref[0]
|
|
429
|
+
} else {
|
|
430
|
+
cur.unshift('exists')
|
|
431
|
+
cur[1] = { ref: [{}] }
|
|
432
|
+
cur = cur[1].ref[0]
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (typeof r === 'string' && aliases.includes(r)) {
|
|
436
|
+
alias = r
|
|
437
|
+
} else {
|
|
438
|
+
// use Object.assign to preserve pointers
|
|
439
|
+
if (typeof r === 'string') Object.assign(cur, { id: r, where: [{ val: 1 }] })
|
|
440
|
+
else Object.assign(cur, r)
|
|
441
|
+
cur = cur.where
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
if (alias) return { ref: [alias, acc] }
|
|
445
|
+
return { ref: [acc] }
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const convertWhereExists = (cqn, model, options) => {
|
|
309
449
|
function _recurse(where) {
|
|
310
450
|
where.forEach((element, index) => {
|
|
451
|
+
// ensure where exists are fully expanded
|
|
452
|
+
if (where && where.some(ele => ele === 'exists')) {
|
|
453
|
+
where.forEach((element, index) => {
|
|
454
|
+
if (element === 'exists' && where[index + 1].ref) {
|
|
455
|
+
const aliases = cqn.SELECT.from.as
|
|
456
|
+
? [cqn.SELECT.from.as]
|
|
457
|
+
: cqn.SELECT.from.join
|
|
458
|
+
? cqn.SELECT.from.args.map(arg => arg.as)
|
|
459
|
+
: []
|
|
460
|
+
where[index + 1] = _ensureExpandedNestedWhereExists(where[index + 1], aliases)
|
|
461
|
+
}
|
|
462
|
+
})
|
|
463
|
+
}
|
|
464
|
+
|
|
311
465
|
if (element.xpr) {
|
|
312
466
|
_recurse(element.xpr) // recursing into nested {xpr}
|
|
313
467
|
} else if (element === 'exists' && _isAny(where[index + 1])) {
|
|
314
|
-
where[index + 1] =
|
|
468
|
+
where[index + 1] = _getWhereExistsSubSelect(cqn, where, index + 1, model, options)
|
|
315
469
|
} else if (element === 'not' && where[index + 1] === 'exists' && _isAll(where[index + 2])) {
|
|
316
|
-
where[index + 2] =
|
|
470
|
+
where[index + 2] = _getWhereExistsSubSelect(cqn, where, index + 2, model, options)
|
|
317
471
|
}
|
|
318
472
|
})
|
|
319
473
|
}
|
|
474
|
+
|
|
475
|
+
const where = cqn.SELECT.where
|
|
476
|
+
if (where) _recurse(where)
|
|
320
477
|
}
|
|
321
478
|
|
|
322
479
|
const _getRefIndex = (where, index) => {
|
|
@@ -342,8 +499,8 @@ const _getRefIndex = (where, index) => {
|
|
|
342
499
|
}
|
|
343
500
|
}
|
|
344
501
|
|
|
345
|
-
const _convertNotEqual = container => {
|
|
346
|
-
const where = container
|
|
502
|
+
const _convertNotEqual = (container, partName = 'where') => {
|
|
503
|
+
const where = container[partName]
|
|
347
504
|
|
|
348
505
|
if (where) {
|
|
349
506
|
let changed
|
|
@@ -360,13 +517,13 @@ const _convertNotEqual = container => {
|
|
|
360
517
|
}
|
|
361
518
|
|
|
362
519
|
if (el && el.SELECT) {
|
|
363
|
-
_convertNotEqual(el.SELECT)
|
|
520
|
+
_convertNotEqual(el.SELECT, partName)
|
|
364
521
|
}
|
|
365
522
|
})
|
|
366
523
|
|
|
367
524
|
// delete undefined values
|
|
368
525
|
if (changed) {
|
|
369
|
-
container
|
|
526
|
+
container[partName] = where.filter(el => el)
|
|
370
527
|
}
|
|
371
528
|
}
|
|
372
529
|
|
|
@@ -374,9 +531,9 @@ const _convertNotEqual = container => {
|
|
|
374
531
|
container.columns.forEach(col => {
|
|
375
532
|
if (typeof col === 'object') {
|
|
376
533
|
if (col.SELECT) {
|
|
377
|
-
_convertNotEqual(col.SELECT)
|
|
534
|
+
_convertNotEqual(col.SELECT, partName)
|
|
378
535
|
} else if (col.where) {
|
|
379
|
-
_convertNotEqual(col)
|
|
536
|
+
_convertNotEqual(col, partName)
|
|
380
537
|
}
|
|
381
538
|
}
|
|
382
539
|
})
|
|
@@ -384,7 +541,7 @@ const _convertNotEqual = container => {
|
|
|
384
541
|
}
|
|
385
542
|
|
|
386
543
|
const _ifOrderByOrWhereSkip = (queryTarget, ref, model) => {
|
|
387
|
-
if (!queryTarget.elements[ref[0]]) return
|
|
544
|
+
if (!queryTarget || !queryTarget.elements[ref[0]]) return
|
|
388
545
|
return ref.some(el => {
|
|
389
546
|
if (typeof el !== 'string') return
|
|
390
547
|
const orderbyTarget = queryTarget.elements[el]._isStructured
|
|
@@ -398,9 +555,12 @@ const _ifOrderByOrWhereSkip = (queryTarget, ref, model) => {
|
|
|
398
555
|
})
|
|
399
556
|
}
|
|
400
557
|
|
|
401
|
-
const _skip = (queryTarget, ref, model) => {
|
|
402
|
-
|
|
403
|
-
|
|
558
|
+
const _skip = (queryTarget, ref, alias, model) => {
|
|
559
|
+
// we remove the alias, if it exists
|
|
560
|
+
const _ref = ref[0] === alias ? ref.slice(1) : ref
|
|
561
|
+
if (_ref.length === 1) return queryTarget && queryTarget.elements[_ref[0]] && queryTarget.elements[_ref[0]].virtual
|
|
562
|
+
|
|
563
|
+
return _ifOrderByOrWhereSkip(queryTarget, _ref, model)
|
|
404
564
|
}
|
|
405
565
|
|
|
406
566
|
const _convertOrderByIfSkip = (orderByCQN, index) => {
|
|
@@ -412,21 +572,22 @@ const _convertWhereIfSkip = (whereCQN, index) => {
|
|
|
412
572
|
OPERATIONS.includes(whereCQN[index + 1]) ? whereCQN.splice(index + 1, 2) : whereCQN.splice(index - 2, 2)
|
|
413
573
|
}
|
|
414
574
|
|
|
415
|
-
const _convertOrderByOrWhereCQN = (orderByOrWhereCQN, target, model, processFn) => {
|
|
575
|
+
const _convertOrderByOrWhereCQN = (orderByOrWhereCQN, target, model, alias, processFn) => {
|
|
416
576
|
const queryTarget = model.definitions[ensureNoDraftsSuffix(target)]
|
|
417
577
|
|
|
418
578
|
orderByOrWhereCQN.forEach((el, index) => {
|
|
419
|
-
if (el.ref && _skip(queryTarget, el.ref, model)) processFn(orderByOrWhereCQN, index)
|
|
579
|
+
if (el.ref && _skip(queryTarget, el.ref, alias, model)) processFn(orderByOrWhereCQN, index)
|
|
420
580
|
})
|
|
421
581
|
}
|
|
422
582
|
|
|
423
583
|
const _convertOrderByOrWhereIfSkip = (cqn, target, model) => {
|
|
584
|
+
const alias = cqn.SELECT.from.as
|
|
424
585
|
if (cqn.SELECT.orderBy && cqn.SELECT.orderBy.length > 1) {
|
|
425
|
-
_convertOrderByOrWhereCQN(cqn.SELECT.orderBy, target, model, _convertOrderByIfSkip)
|
|
586
|
+
_convertOrderByOrWhereCQN(cqn.SELECT.orderBy, target, model, alias, _convertOrderByIfSkip)
|
|
426
587
|
}
|
|
427
588
|
|
|
428
589
|
if (cqn.SELECT.where) {
|
|
429
|
-
_convertOrderByOrWhereCQN(cqn.SELECT.where, target, model, _convertWhereIfSkip)
|
|
590
|
+
_convertOrderByOrWhereCQN(cqn.SELECT.where, target, model, alias, _convertWhereIfSkip)
|
|
430
591
|
}
|
|
431
592
|
}
|
|
432
593
|
|
|
@@ -449,7 +610,7 @@ const _convertExpand = expand => {
|
|
|
449
610
|
const _convertRefWhereInExpand = columns => {
|
|
450
611
|
if (columns) {
|
|
451
612
|
columns.forEach(col => {
|
|
452
|
-
if (col.expand) {
|
|
613
|
+
if (col.expand && typeof col.expand !== 'string') {
|
|
453
614
|
_convertExpand(col.expand)
|
|
454
615
|
}
|
|
455
616
|
})
|
|
@@ -486,102 +647,115 @@ const _flattenXpr = cqn => {
|
|
|
486
647
|
cqn.forEach(_flattenCQN)
|
|
487
648
|
}
|
|
488
649
|
|
|
650
|
+
const _convertPathExpression = (SELECT, model, options = {}) => {
|
|
651
|
+
const prevAlias = SELECT.from.as
|
|
652
|
+
for (const whereEl of SELECT.where || []) {
|
|
653
|
+
if (typeof whereEl === 'object' && whereEl.SELECT) _convertPathExpression(whereEl.SELECT, model)
|
|
654
|
+
}
|
|
655
|
+
const conversion = convertPathExpressionToWhere(SELECT.from, model, options)
|
|
656
|
+
if (!conversion) return
|
|
657
|
+
const { target, alias, where, cardinality, columns, args } = conversion
|
|
658
|
+
|
|
659
|
+
// REVISIT: It's not possible to just change the reference (i.e. SELECT.from.ref = [target])
|
|
660
|
+
// as many parts of our code base still refer to SELECT.from (e.g. authorization)
|
|
661
|
+
if (args) {
|
|
662
|
+
SELECT.from = { ref: [{ id: target, args }] }
|
|
663
|
+
} else {
|
|
664
|
+
SELECT.from = { ref: [target] }
|
|
665
|
+
}
|
|
666
|
+
if (alias) {
|
|
667
|
+
SELECT.from.as = alias
|
|
668
|
+
if (SELECT.where && alias !== prevAlias) {
|
|
669
|
+
SELECT.where = _addAliasToExpression(SELECT.where, alias)
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
if (columns) {
|
|
673
|
+
// TODO: use streaming as outer property
|
|
674
|
+
if (options.isStreaming) SELECT.columns = columns
|
|
675
|
+
else {
|
|
676
|
+
if (!SELECT.columns) {
|
|
677
|
+
// Okra always wants to have the key values, remove once we relax this requirement
|
|
678
|
+
if (model.definitions[target] && model.definitions[target].keys) {
|
|
679
|
+
SELECT.columns = Object.keys(model.definitions[target].keys)
|
|
680
|
+
.filter(k => !model.definitions[target].keys[k].isAssociation)
|
|
681
|
+
.map(k => ({ ref: [k] }))
|
|
682
|
+
} else SELECT.columns = []
|
|
683
|
+
}
|
|
684
|
+
SELECT.columns.push(...columns)
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
if (cardinality && cardinality.max === 1) {
|
|
688
|
+
SELECT.one = true
|
|
689
|
+
}
|
|
690
|
+
// TODO: REVISIT: We need to add alias to subselect in .where, .columns, .from, ... etc
|
|
691
|
+
if (where) {
|
|
692
|
+
if (options.isDraft) {
|
|
693
|
+
addToWhere({ SELECT }, where)
|
|
694
|
+
} else {
|
|
695
|
+
addToWhere({ SELECT }, removeIsActiveEntityRecursively(where))
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
489
700
|
// eslint-disable-next-line complexity
|
|
490
|
-
const _convertSelect = (query, model,
|
|
491
|
-
const
|
|
492
|
-
|
|
701
|
+
const _convertSelect = (query, model, _options) => {
|
|
702
|
+
const options = Object.assign(
|
|
703
|
+
{
|
|
704
|
+
isDB: _options.service instanceof cds.DatabaseService,
|
|
705
|
+
isDraft: _options.draft,
|
|
706
|
+
isStreaming: query._streaming
|
|
707
|
+
},
|
|
708
|
+
_options
|
|
709
|
+
)
|
|
710
|
+
// ensure query is ql enabled
|
|
711
|
+
if (!(query instanceof Query)) Object.setPrototypeOf(query, Object.getPrototypeOf(SELECT()))
|
|
712
|
+
if (query.SELECT.from && query.SELECT.from.SELECT) {
|
|
713
|
+
if (query.SELECT._4odata) query.SELECT.from.SELECT._4odata = true
|
|
714
|
+
query.SELECT.from = _convertSelect(query.SELECT.from, model, options)
|
|
715
|
+
}
|
|
716
|
+
|
|
493
717
|
// REVISIT: a temporary workaround for xpr from new parser
|
|
494
718
|
if (cds.env.features.odata_new_parser) _flattenCQN(query)
|
|
495
719
|
|
|
496
720
|
// lambda functions
|
|
497
|
-
|
|
721
|
+
convertWhereExists(query, model, options)
|
|
498
722
|
|
|
499
723
|
// 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
|
|
724
|
+
if (query.SELECT._4odata) {
|
|
725
|
+
_convertNotEqual(query.SELECT, 'where')
|
|
726
|
+
_convertNotEqual(query.SELECT, 'having')
|
|
528
727
|
}
|
|
529
728
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
if (query._streaming) query.SELECT.columns = columns
|
|
535
|
-
else query.SELECT.columns.push(...columns)
|
|
729
|
+
_convertPathExpression(query.SELECT, model, options)
|
|
730
|
+
rewriteAsterisks(query, model, options.isDB, options.isDraft)
|
|
731
|
+
if (query.SELECT.where && _isCountNavigation(query.SELECT.where)) {
|
|
732
|
+
_convertCountNavigation(query.SELECT, model)
|
|
536
733
|
}
|
|
537
734
|
|
|
538
735
|
// extract where clause if it is in column expand ref
|
|
539
736
|
_convertRefWhereInExpand(query.SELECT.columns)
|
|
540
737
|
|
|
541
|
-
//
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
const select = SELECT.from(target, query.SELECT.columns)
|
|
738
|
+
// REVISIT: The following operations only work for _one_ entity.
|
|
739
|
+
// We must also enable them for joins etc.
|
|
740
|
+
const { entityName, alias } = getEntityNameFromCQN(query)
|
|
545
741
|
|
|
546
|
-
|
|
547
|
-
|
|
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
|
-
}
|
|
558
|
-
|
|
559
|
-
if (cardinality && cardinality.max === 1) {
|
|
560
|
-
query.SELECT.one = true
|
|
561
|
-
}
|
|
742
|
+
// remove virtual and with skip annotated fields in orderby and where
|
|
743
|
+
_convertOrderByOrWhereIfSkip(query, entityName, model)
|
|
562
744
|
|
|
563
|
-
if (query.SELECT.search) {
|
|
564
|
-
|
|
565
|
-
search2cqn4sql(query, model, searchOptions)
|
|
745
|
+
if (query.SELECT.search && !options.suppressSearch) {
|
|
746
|
+
search2cqn4sql(query, model, { ...query._searchOptions, ...{ entityName, alias } })
|
|
566
747
|
}
|
|
567
748
|
|
|
568
|
-
if (query.SELECT.
|
|
569
|
-
|
|
749
|
+
if (query.SELECT.columns && cds.env.effective.odata.structs) {
|
|
750
|
+
flattenStructuredSelect(query, model)
|
|
570
751
|
}
|
|
571
752
|
|
|
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)
|
|
753
|
+
// topcount with groupby
|
|
754
|
+
if (query.SELECT.columns && _isBottomTop(query.SELECT.columns)) {
|
|
755
|
+
_createWindowCQN(query.SELECT, model)
|
|
582
756
|
}
|
|
583
757
|
|
|
584
|
-
return
|
|
758
|
+
return query
|
|
585
759
|
}
|
|
586
760
|
|
|
587
761
|
const _convertInsert = (query, model, options) => {
|
|
@@ -732,5 +906,6 @@ const cqn2cqn4sql = (query, model, options = { suppressSearch: false }) => {
|
|
|
732
906
|
|
|
733
907
|
module.exports = {
|
|
734
908
|
cqn2cqn4sql,
|
|
909
|
+
convertWhereExists,
|
|
735
910
|
convertPathExpressionToWhere
|
|
736
911
|
}
|