@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,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,25 +407,76 @@ 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
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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]
|
|
317
433
|
}
|
|
318
|
-
}
|
|
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 = (where, cqn, model, options) => {
|
|
449
|
+
for (let i = 0; i < where.length; i++) {
|
|
450
|
+
const element = where[i]
|
|
451
|
+
|
|
452
|
+
// ensure where exists are fully expanded
|
|
453
|
+
if (
|
|
454
|
+
(element === 'exists' && where[i + 1].ref && where[i > 1 ? i - 1 : 0] !== 'not') ||
|
|
455
|
+
(element === 'not' && where[i + 1] === 'exists' && where[i + 2].ref)
|
|
456
|
+
) {
|
|
457
|
+
const offset = element === 'not' ? 2 : 1
|
|
458
|
+
const aliases = cqn.SELECT.from.as
|
|
459
|
+
? [cqn.SELECT.from.as]
|
|
460
|
+
: cqn.SELECT.from.join
|
|
461
|
+
? cqn.SELECT.from.args.map(arg => arg.as)
|
|
462
|
+
: []
|
|
463
|
+
where[i + offset] = _ensureExpandedNestedWhereExists(where[i + offset], aliases)
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (element.xpr) {
|
|
467
|
+
_convertWhereExists(element.xpr) // > recursing into nested {xpr}
|
|
468
|
+
} else if (element === 'exists' && _isAny(where[i + 1])) {
|
|
469
|
+
where[i + 1] = _getWhereExistsSubSelect(cqn, where, i + 1, model, options)
|
|
470
|
+
} else if (element === 'not' && where[i + 1] === 'exists' && _isAll(where[i + 2])) {
|
|
471
|
+
where[i + 2] = _getWhereExistsSubSelect(cqn, where, i + 2, model, options)
|
|
472
|
+
}
|
|
319
473
|
}
|
|
320
474
|
}
|
|
321
475
|
|
|
476
|
+
const convertWhereExists = (cqn, model, options) => {
|
|
477
|
+
if (cqn.SELECT.where) _convertWhereExists(cqn.SELECT.where, cqn, model, options)
|
|
478
|
+
}
|
|
479
|
+
|
|
322
480
|
const _getRefIndex = (where, index) => {
|
|
323
481
|
if (
|
|
324
482
|
where[index - 1].ref &&
|
|
@@ -342,8 +500,8 @@ const _getRefIndex = (where, index) => {
|
|
|
342
500
|
}
|
|
343
501
|
}
|
|
344
502
|
|
|
345
|
-
const _convertNotEqual = container => {
|
|
346
|
-
const where = container
|
|
503
|
+
const _convertNotEqual = (container, partName = 'where') => {
|
|
504
|
+
const where = container[partName]
|
|
347
505
|
|
|
348
506
|
if (where) {
|
|
349
507
|
let changed
|
|
@@ -360,13 +518,13 @@ const _convertNotEqual = container => {
|
|
|
360
518
|
}
|
|
361
519
|
|
|
362
520
|
if (el && el.SELECT) {
|
|
363
|
-
_convertNotEqual(el.SELECT)
|
|
521
|
+
_convertNotEqual(el.SELECT, partName)
|
|
364
522
|
}
|
|
365
523
|
})
|
|
366
524
|
|
|
367
525
|
// delete undefined values
|
|
368
526
|
if (changed) {
|
|
369
|
-
container
|
|
527
|
+
container[partName] = where.filter(el => el)
|
|
370
528
|
}
|
|
371
529
|
}
|
|
372
530
|
|
|
@@ -374,9 +532,9 @@ const _convertNotEqual = container => {
|
|
|
374
532
|
container.columns.forEach(col => {
|
|
375
533
|
if (typeof col === 'object') {
|
|
376
534
|
if (col.SELECT) {
|
|
377
|
-
_convertNotEqual(col.SELECT)
|
|
535
|
+
_convertNotEqual(col.SELECT, partName)
|
|
378
536
|
} else if (col.where) {
|
|
379
|
-
_convertNotEqual(col)
|
|
537
|
+
_convertNotEqual(col, partName)
|
|
380
538
|
}
|
|
381
539
|
}
|
|
382
540
|
})
|
|
@@ -384,7 +542,7 @@ const _convertNotEqual = container => {
|
|
|
384
542
|
}
|
|
385
543
|
|
|
386
544
|
const _ifOrderByOrWhereSkip = (queryTarget, ref, model) => {
|
|
387
|
-
if (!queryTarget.elements[ref[0]]) return
|
|
545
|
+
if (!queryTarget || !queryTarget.elements[ref[0]]) return
|
|
388
546
|
return ref.some(el => {
|
|
389
547
|
if (typeof el !== 'string') return
|
|
390
548
|
const orderbyTarget = queryTarget.elements[el]._isStructured
|
|
@@ -398,9 +556,12 @@ const _ifOrderByOrWhereSkip = (queryTarget, ref, model) => {
|
|
|
398
556
|
})
|
|
399
557
|
}
|
|
400
558
|
|
|
401
|
-
const _skip = (queryTarget, ref, model) => {
|
|
402
|
-
|
|
403
|
-
|
|
559
|
+
const _skip = (queryTarget, ref, alias, model) => {
|
|
560
|
+
// we remove the alias, if it exists
|
|
561
|
+
const _ref = ref[0] === alias ? ref.slice(1) : ref
|
|
562
|
+
if (_ref.length === 1) return queryTarget && queryTarget.elements[_ref[0]] && queryTarget.elements[_ref[0]].virtual
|
|
563
|
+
|
|
564
|
+
return _ifOrderByOrWhereSkip(queryTarget, _ref, model)
|
|
404
565
|
}
|
|
405
566
|
|
|
406
567
|
const _convertOrderByIfSkip = (orderByCQN, index) => {
|
|
@@ -412,21 +573,22 @@ const _convertWhereIfSkip = (whereCQN, index) => {
|
|
|
412
573
|
OPERATIONS.includes(whereCQN[index + 1]) ? whereCQN.splice(index + 1, 2) : whereCQN.splice(index - 2, 2)
|
|
413
574
|
}
|
|
414
575
|
|
|
415
|
-
const _convertOrderByOrWhereCQN = (orderByOrWhereCQN, target, model, processFn) => {
|
|
576
|
+
const _convertOrderByOrWhereCQN = (orderByOrWhereCQN, target, model, alias, processFn) => {
|
|
416
577
|
const queryTarget = model.definitions[ensureNoDraftsSuffix(target)]
|
|
417
578
|
|
|
418
579
|
orderByOrWhereCQN.forEach((el, index) => {
|
|
419
|
-
if (el.ref && _skip(queryTarget, el.ref, model)) processFn(orderByOrWhereCQN, index)
|
|
580
|
+
if (el.ref && _skip(queryTarget, el.ref, alias, model)) processFn(orderByOrWhereCQN, index)
|
|
420
581
|
})
|
|
421
582
|
}
|
|
422
583
|
|
|
423
584
|
const _convertOrderByOrWhereIfSkip = (cqn, target, model) => {
|
|
585
|
+
const alias = cqn.SELECT.from.as
|
|
424
586
|
if (cqn.SELECT.orderBy && cqn.SELECT.orderBy.length > 1) {
|
|
425
|
-
_convertOrderByOrWhereCQN(cqn.SELECT.orderBy, target, model, _convertOrderByIfSkip)
|
|
587
|
+
_convertOrderByOrWhereCQN(cqn.SELECT.orderBy, target, model, alias, _convertOrderByIfSkip)
|
|
426
588
|
}
|
|
427
589
|
|
|
428
590
|
if (cqn.SELECT.where) {
|
|
429
|
-
_convertOrderByOrWhereCQN(cqn.SELECT.where, target, model, _convertWhereIfSkip)
|
|
591
|
+
_convertOrderByOrWhereCQN(cqn.SELECT.where, target, model, alias, _convertWhereIfSkip)
|
|
430
592
|
}
|
|
431
593
|
}
|
|
432
594
|
|
|
@@ -449,7 +611,7 @@ const _convertExpand = expand => {
|
|
|
449
611
|
const _convertRefWhereInExpand = columns => {
|
|
450
612
|
if (columns) {
|
|
451
613
|
columns.forEach(col => {
|
|
452
|
-
if (col.expand) {
|
|
614
|
+
if (col.expand && typeof col.expand !== 'string') {
|
|
453
615
|
_convertExpand(col.expand)
|
|
454
616
|
}
|
|
455
617
|
})
|
|
@@ -486,102 +648,115 @@ const _flattenXpr = cqn => {
|
|
|
486
648
|
cqn.forEach(_flattenCQN)
|
|
487
649
|
}
|
|
488
650
|
|
|
651
|
+
const _convertPathExpression = (SELECT, model, options = {}) => {
|
|
652
|
+
const prevAlias = SELECT.from.as
|
|
653
|
+
for (const whereEl of SELECT.where || []) {
|
|
654
|
+
if (typeof whereEl === 'object' && whereEl.SELECT) _convertPathExpression(whereEl.SELECT, model)
|
|
655
|
+
}
|
|
656
|
+
const conversion = convertPathExpressionToWhere(SELECT.from, model, options)
|
|
657
|
+
if (!conversion) return
|
|
658
|
+
const { target, alias, where, cardinality, columns, args } = conversion
|
|
659
|
+
|
|
660
|
+
// REVISIT: It's not possible to just change the reference (i.e. SELECT.from.ref = [target])
|
|
661
|
+
// as many parts of our code base still refer to SELECT.from (e.g. authorization)
|
|
662
|
+
if (args) {
|
|
663
|
+
SELECT.from = { ref: [{ id: target, args }] }
|
|
664
|
+
} else {
|
|
665
|
+
SELECT.from = { ref: [target] }
|
|
666
|
+
}
|
|
667
|
+
if (alias) {
|
|
668
|
+
SELECT.from.as = alias
|
|
669
|
+
if (SELECT.where && alias !== prevAlias) {
|
|
670
|
+
SELECT.where = _addAliasToExpression(SELECT.where, alias)
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
if (columns) {
|
|
674
|
+
// TODO: use streaming as outer property
|
|
675
|
+
if (options.isStreaming) SELECT.columns = columns
|
|
676
|
+
else {
|
|
677
|
+
if (!SELECT.columns) {
|
|
678
|
+
// Okra always wants to have the key values, remove once we relax this requirement
|
|
679
|
+
if (model.definitions[target] && model.definitions[target].keys) {
|
|
680
|
+
SELECT.columns = Object.keys(model.definitions[target].keys)
|
|
681
|
+
.filter(k => !model.definitions[target].keys[k].isAssociation)
|
|
682
|
+
.map(k => ({ ref: [k] }))
|
|
683
|
+
} else SELECT.columns = []
|
|
684
|
+
}
|
|
685
|
+
SELECT.columns.push(...columns)
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
if (cardinality && cardinality.max === 1) {
|
|
689
|
+
SELECT.one = true
|
|
690
|
+
}
|
|
691
|
+
// TODO: REVISIT: We need to add alias to subselect in .where, .columns, .from, ... etc
|
|
692
|
+
if (where) {
|
|
693
|
+
if (options.isDraft) {
|
|
694
|
+
addToWhere({ SELECT }, where)
|
|
695
|
+
} else {
|
|
696
|
+
addToWhere({ SELECT }, removeIsActiveEntityRecursively(where))
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
489
701
|
// eslint-disable-next-line complexity
|
|
490
|
-
const _convertSelect = (query, model,
|
|
491
|
-
const
|
|
492
|
-
|
|
702
|
+
const _convertSelect = (query, model, _options) => {
|
|
703
|
+
const options = Object.assign(
|
|
704
|
+
{
|
|
705
|
+
isDB: _options.service instanceof cds.DatabaseService,
|
|
706
|
+
isDraft: _options.draft,
|
|
707
|
+
isStreaming: query._streaming
|
|
708
|
+
},
|
|
709
|
+
_options
|
|
710
|
+
)
|
|
711
|
+
// ensure query is ql enabled
|
|
712
|
+
if (!(query instanceof Query)) Object.setPrototypeOf(query, Object.getPrototypeOf(SELECT()))
|
|
713
|
+
if (query.SELECT.from && query.SELECT.from.SELECT) {
|
|
714
|
+
if (query.SELECT._4odata) query.SELECT.from.SELECT._4odata = true
|
|
715
|
+
query.SELECT.from = _convertSelect(query.SELECT.from, model, options)
|
|
716
|
+
}
|
|
717
|
+
|
|
493
718
|
// REVISIT: a temporary workaround for xpr from new parser
|
|
494
719
|
if (cds.env.features.odata_new_parser) _flattenCQN(query)
|
|
495
720
|
|
|
496
721
|
// lambda functions
|
|
497
|
-
|
|
722
|
+
convertWhereExists(query, model, options)
|
|
498
723
|
|
|
499
724
|
// 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
|
|
725
|
+
if (query.SELECT._4odata) {
|
|
726
|
+
_convertNotEqual(query.SELECT, 'where')
|
|
727
|
+
_convertNotEqual(query.SELECT, 'having')
|
|
528
728
|
}
|
|
529
729
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
if (query._streaming) query.SELECT.columns = columns
|
|
535
|
-
else query.SELECT.columns.push(...columns)
|
|
730
|
+
_convertPathExpression(query.SELECT, model, options)
|
|
731
|
+
rewriteAsterisks(query, model, options.isDB, options.isDraft)
|
|
732
|
+
if (query.SELECT.where && _isCountNavigation(query.SELECT.where)) {
|
|
733
|
+
_convertCountNavigation(query.SELECT, model)
|
|
536
734
|
}
|
|
537
735
|
|
|
538
736
|
// extract where clause if it is in column expand ref
|
|
539
737
|
_convertRefWhereInExpand(query.SELECT.columns)
|
|
540
738
|
|
|
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
|
-
}
|
|
739
|
+
// REVISIT: The following operations only work for _one_ entity.
|
|
740
|
+
// We must also enable them for joins etc.
|
|
741
|
+
const { entityName, alias } = getEntityNameFromCQN(query)
|
|
558
742
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
}
|
|
743
|
+
// remove virtual and with skip annotated fields in orderby and where
|
|
744
|
+
_convertOrderByOrWhereIfSkip(query, entityName, model)
|
|
562
745
|
|
|
563
|
-
if (query.SELECT.search) {
|
|
564
|
-
|
|
565
|
-
search2cqn4sql(query, model, searchOptions)
|
|
746
|
+
if (query.SELECT.search && !options.suppressSearch) {
|
|
747
|
+
search2cqn4sql(query, model, { ...query._searchOptions, ...{ entityName, alias } })
|
|
566
748
|
}
|
|
567
749
|
|
|
568
|
-
if (query.SELECT.
|
|
569
|
-
|
|
750
|
+
if (query.SELECT.columns && cds.env.effective.odata.structs) {
|
|
751
|
+
flattenStructuredSelect(query, model)
|
|
570
752
|
}
|
|
571
753
|
|
|
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)
|
|
754
|
+
// topcount with groupby
|
|
755
|
+
if (query.SELECT.columns && _isBottomTop(query.SELECT.columns)) {
|
|
756
|
+
_createWindowCQN(query.SELECT, model)
|
|
582
757
|
}
|
|
583
758
|
|
|
584
|
-
return
|
|
759
|
+
return query
|
|
585
760
|
}
|
|
586
761
|
|
|
587
762
|
const _convertInsert = (query, model, options) => {
|
|
@@ -732,5 +907,6 @@ const cqn2cqn4sql = (query, model, options = { suppressSearch: false }) => {
|
|
|
732
907
|
|
|
733
908
|
module.exports = {
|
|
734
909
|
cqn2cqn4sql,
|
|
910
|
+
convertWhereExists,
|
|
735
911
|
convertPathExpressionToWhere
|
|
736
912
|
}
|