@sap/cds 7.0.3 → 7.1.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 +54 -3
- package/_i18n/i18n_ar.properties +3 -0
- package/_i18n/i18n_cs.properties +4 -1
- package/_i18n/i18n_da.properties +3 -0
- package/_i18n/i18n_de.properties +3 -0
- package/_i18n/i18n_en.properties +3 -0
- package/_i18n/i18n_es.properties +3 -0
- package/_i18n/i18n_fi.properties +3 -0
- package/_i18n/i18n_fr.properties +3 -0
- package/_i18n/i18n_it.properties +3 -0
- package/_i18n/i18n_ja.properties +3 -0
- package/_i18n/i18n_ko.properties +3 -0
- package/_i18n/i18n_ms.properties +3 -0
- package/_i18n/i18n_nl.properties +3 -0
- package/_i18n/i18n_no.properties +3 -0
- package/_i18n/i18n_pl.properties +3 -0
- package/_i18n/i18n_pt.properties +3 -0
- package/_i18n/i18n_ro.properties +3 -0
- package/_i18n/i18n_ru.properties +3 -0
- package/_i18n/i18n_sv.properties +3 -0
- package/_i18n/i18n_th.properties +3 -0
- package/_i18n/i18n_zh_CN.properties +3 -0
- package/_i18n/i18n_zh_TW.properties +3 -0
- package/apis/core.d.ts +26 -30
- package/apis/cqn.d.ts +1 -0
- package/apis/ql.d.ts +2 -0
- package/apis/serve.d.ts +9 -0
- package/apis/services.d.ts +3 -2
- package/bin/serve.js +2 -2
- package/lib/compile/for/lean_drafts.js +21 -18
- package/lib/compile/to/srvinfo.js +1 -17
- package/lib/dbs/cds-deploy.js +11 -6
- package/lib/env/cds-env.js +3 -4
- package/lib/env/presets.js +14 -9
- package/lib/env/schemas/cds-package.json +3 -1
- package/lib/env/schemas/cds-rc.json +0 -4
- package/lib/linked/classes.js +112 -12
- package/lib/linked/entities.js +3 -0
- package/lib/ql/Whereable.js +1 -0
- package/lib/srv/cds-serve.js +2 -1
- package/lib/srv/protocols/_legacy.js +7 -6
- package/lib/srv/protocols/index.js +30 -72
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/format/ResponseContentNegotiator.js +12 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/SerializerFactory.js +3 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/MetadataCache.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +1 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +10 -4
- package/libx/_runtime/cds-services/services/utils/columns.js +8 -2
- package/libx/_runtime/cds-services/services/utils/differ.js +1 -1
- package/libx/_runtime/common/composition/data.js +49 -29
- package/libx/_runtime/common/composition/update.js +0 -1
- package/libx/_runtime/common/composition/utils.js +1 -1
- package/libx/_runtime/common/generic/crud.js +2 -2
- package/libx/_runtime/common/generic/input.js +18 -13
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +6 -2
- package/libx/_runtime/common/utils/resolveView.js +115 -35
- package/libx/_runtime/common/utils/rewriteAsterisks.js +21 -0
- package/libx/_runtime/common/utils/search2cqn4sql.js +1 -1
- package/libx/_runtime/db/generic/rewrite.js +5 -4
- package/libx/_runtime/db/query/read.js +10 -9
- package/libx/_runtime/db/query/update.js +9 -18
- package/libx/_runtime/db/utils/deep.js +6 -5
- package/libx/_runtime/db/utils/localized.js +1 -1
- package/libx/_runtime/db/utils/normalizeTimeData.js +1 -1
- package/libx/_runtime/fiori/generic/activate.js +14 -19
- package/libx/_runtime/fiori/generic/edit.js +3 -6
- package/libx/_runtime/fiori/lean-draft.js +3 -2
- package/libx/_runtime/hana/localized.js +1 -1
- package/libx/_runtime/hana/search2cqn4sql.js +3 -1
- package/libx/_runtime/remote/utils/client.js +9 -5
- package/libx/_runtime/sqlite/localized.js +1 -1
- package/libx/odata/afterburner.js +5 -2
- package/libx/rest/middleware/operation.js +1 -1
- package/package.json +3 -3
- package/lib/srv/protocols/graphql.js +0 -30
|
@@ -36,6 +36,7 @@ const _inverseTransition = transition => {
|
|
|
36
36
|
const nested = inverseTransition.mapping.get(ref0) || {}
|
|
37
37
|
if (!nested.transition) nested.transition = { mapping: new Map() }
|
|
38
38
|
let current = nested.transition.mapping
|
|
39
|
+
|
|
39
40
|
for (let i = 1; i < value.ref.length; i++) {
|
|
40
41
|
const last = i === value.ref.length - 1
|
|
41
42
|
const obj = last ? { ref: [key] } : { transition: { mapping: new Map() } }
|
|
@@ -63,16 +64,19 @@ const revertData = (data, transition, service) => {
|
|
|
63
64
|
|
|
64
65
|
const _newSubData = (newData, key, transition, el, inverse, service) => {
|
|
65
66
|
const val = newData[key]
|
|
67
|
+
|
|
66
68
|
if ((!Array.isArray(val) && typeof val === 'object') || (Array.isArray(val) && val.length !== 0)) {
|
|
67
69
|
let mapped = transition.mapping.get(key)
|
|
68
70
|
if (!mapped) {
|
|
69
71
|
mapped = {}
|
|
70
72
|
transition.mapping.set(key, mapped)
|
|
71
73
|
}
|
|
74
|
+
|
|
72
75
|
if (!mapped.transition) {
|
|
73
76
|
const subTransition = getTransition(el._target, service)
|
|
74
77
|
mapped.transition = inverse ? _inverseTransition(subTransition) : subTransition
|
|
75
78
|
}
|
|
79
|
+
|
|
76
80
|
if (Array.isArray(val)) {
|
|
77
81
|
newData[key] = val.map(singleVal => _newData(singleVal, mapped.transition, inverse, service))
|
|
78
82
|
} else {
|
|
@@ -85,6 +89,7 @@ const _newNestedData = (queryTarget, newData, ref, value) => {
|
|
|
85
89
|
const parent = queryTarget.query && queryTarget.query._target
|
|
86
90
|
let currentEntity = parent
|
|
87
91
|
let currentData = newData
|
|
92
|
+
|
|
88
93
|
for (let i = 0; i < ref.length; i++) {
|
|
89
94
|
currentEntity = currentEntity.elements[ref[i]]
|
|
90
95
|
if (currentEntity.isAssociation) {
|
|
@@ -101,6 +106,7 @@ const _newNestedData = (queryTarget, newData, ref, value) => {
|
|
|
101
106
|
// eslint-disable-next-line complexity
|
|
102
107
|
const _newData = (data, transition, inverse, service) => {
|
|
103
108
|
if (data === null) return null
|
|
109
|
+
|
|
104
110
|
// no transition -> nothing to do
|
|
105
111
|
if (transition.target && transition.target.name === transition.queryTarget.name) return data
|
|
106
112
|
|
|
@@ -109,8 +115,9 @@ const _newData = (data, transition, inverse, service) => {
|
|
|
109
115
|
const queryTarget = transition.queryTarget
|
|
110
116
|
|
|
111
117
|
for (const key in newData) {
|
|
112
|
-
const el = queryTarget && queryTarget
|
|
118
|
+
const el = queryTarget && queryTarget?.elements[key]
|
|
113
119
|
const isAssoc = el && el.isAssociation
|
|
120
|
+
|
|
114
121
|
if (isAssoc) {
|
|
115
122
|
if (newData[key] || (newData[key] === null && service.name === 'db')) {
|
|
116
123
|
_newSubData(newData, key, transition, el, inverse, service)
|
|
@@ -136,6 +143,7 @@ const _newData = (data, transition, inverse, service) => {
|
|
|
136
143
|
const value = newData[key]
|
|
137
144
|
delete newData[key]
|
|
138
145
|
const { ref } = mapped
|
|
146
|
+
|
|
139
147
|
if (ref.length === 1) {
|
|
140
148
|
newData[ref[0]] = value
|
|
141
149
|
if (mapped.alternatives) mapped.alternatives.forEach(({ ref }) => (newData[ref[0]] = value))
|
|
@@ -159,6 +167,7 @@ const _newColumns = (columns = [], transition, service, withAlias = false) => {
|
|
|
159
167
|
newColumns.push(newColumn)
|
|
160
168
|
return newColumns
|
|
161
169
|
}
|
|
170
|
+
|
|
162
171
|
const mapped = column.ref && transition.mapping.get(column.ref[0])
|
|
163
172
|
|
|
164
173
|
if (mapped && mapped.ref) {
|
|
@@ -193,24 +202,31 @@ const _newColumns = (columns = [], transition, service, withAlias = false) => {
|
|
|
193
202
|
const expandTarget = def._target
|
|
194
203
|
const subtransition = getTransition(expandTarget, service)
|
|
195
204
|
mapped.transition = subtransition
|
|
196
|
-
|
|
197
205
|
newColumn.expand = _newColumns(column.expand, subtransition, service, withAlias)
|
|
198
206
|
}
|
|
207
|
+
|
|
199
208
|
newColumns.push(newColumn)
|
|
200
209
|
})
|
|
201
210
|
|
|
202
211
|
return newColumns
|
|
203
212
|
}
|
|
204
213
|
|
|
214
|
+
const _resolveColumn = (column, transition) => {
|
|
215
|
+
const mapped = transition.mapping.get(column)
|
|
216
|
+
if (mapped && mapped.ref) {
|
|
217
|
+
return mapped.ref[0]
|
|
218
|
+
} else if (!mapped) {
|
|
219
|
+
return column
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
205
223
|
const _newInsertColumns = (columns = [], transition) => {
|
|
206
224
|
const newColumns = []
|
|
207
225
|
|
|
208
226
|
columns.forEach(column => {
|
|
209
|
-
const
|
|
210
|
-
if (
|
|
211
|
-
newColumns.push(
|
|
212
|
-
} else if (!mapped) {
|
|
213
|
-
newColumns.push(column)
|
|
227
|
+
const resolvedColumn = _resolveColumn(column, transition)
|
|
228
|
+
if (resolvedColumn) {
|
|
229
|
+
newColumns.push(resolvedColumn)
|
|
214
230
|
}
|
|
215
231
|
})
|
|
216
232
|
|
|
@@ -220,6 +236,7 @@ const _newInsertColumns = (columns = [], transition) => {
|
|
|
220
236
|
// REVISIT: this hard-coding on ref indexes does not support path expressions
|
|
221
237
|
const _newWhereRef = (newWhereElement, transition, alias, tableName, isSubSelect) => {
|
|
222
238
|
const newRef = Array.isArray(newWhereElement.ref) ? [...newWhereElement.ref] : [newWhereElement.ref]
|
|
239
|
+
|
|
223
240
|
if (newRef[0] === alias) {
|
|
224
241
|
const mapped = transition.mapping.get(newRef[1])
|
|
225
242
|
if (mapped) newRef[1] = mapped.ref[0]
|
|
@@ -236,6 +253,7 @@ const _newWhereRef = (newWhereElement, transition, alias, tableName, isSubSelect
|
|
|
236
253
|
if (mapped) newRef[0] = mapped.ref[0]
|
|
237
254
|
}
|
|
238
255
|
}
|
|
256
|
+
|
|
239
257
|
newWhereElement.ref = newRef
|
|
240
258
|
}
|
|
241
259
|
|
|
@@ -250,20 +268,23 @@ const _newWhere = (where = [], transition, tableName, alias, isSubselect = false
|
|
|
250
268
|
|
|
251
269
|
const newWhereElement = { ...whereElement }
|
|
252
270
|
if (!whereElement.ref && !whereElement.SELECT && !whereElement.func) return whereElement
|
|
271
|
+
|
|
253
272
|
if (whereElement.SELECT && whereElement.SELECT.where && !whereElement._doNotResolve) {
|
|
254
273
|
newWhereElement.SELECT.where = _newWhere(whereElement.SELECT.where, transition, tableName, alias, true)
|
|
255
274
|
return newWhereElement
|
|
256
|
-
} else {
|
|
257
|
-
if (newWhereElement.ref) {
|
|
258
|
-
_newWhereRef(newWhereElement, transition, alias, tableName, isSubselect)
|
|
259
|
-
return newWhereElement
|
|
260
|
-
} else if (newWhereElement.func) {
|
|
261
|
-
newWhereElement.args = _newWhere(newWhereElement.args, transition, tableName, alias)
|
|
262
|
-
return newWhereElement
|
|
263
|
-
} else {
|
|
264
|
-
return whereElement
|
|
265
|
-
}
|
|
266
275
|
}
|
|
276
|
+
|
|
277
|
+
if (newWhereElement.ref) {
|
|
278
|
+
_newWhereRef(newWhereElement, transition, alias, tableName, isSubselect)
|
|
279
|
+
return newWhereElement
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (newWhereElement.func) {
|
|
283
|
+
newWhereElement.args = _newWhere(newWhereElement.args, transition, tableName, alias)
|
|
284
|
+
return newWhereElement
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return whereElement
|
|
267
288
|
})
|
|
268
289
|
|
|
269
290
|
return newWhere
|
|
@@ -277,6 +298,7 @@ const _initialColumns = transition => {
|
|
|
277
298
|
if (!transition.queryTarget.elements[transitionEl] || transition.queryTarget.elements[transitionEl].isAssociation) {
|
|
278
299
|
continue
|
|
279
300
|
}
|
|
301
|
+
|
|
280
302
|
columns.push({ ref: [transitionEl] })
|
|
281
303
|
}
|
|
282
304
|
|
|
@@ -318,9 +340,10 @@ const _rewriteQueryPath = (path, transitions) => {
|
|
|
318
340
|
}
|
|
319
341
|
|
|
320
342
|
const _newUpdate = (query, transitions, service) => {
|
|
321
|
-
const targetTransition = transitions
|
|
343
|
+
const targetTransition = transitions.at(-1)
|
|
322
344
|
const targetName = targetTransition.target.name
|
|
323
345
|
const newUpdate = Object.create(query.UPDATE)
|
|
346
|
+
|
|
324
347
|
newUpdate.entity = newUpdate.entity.ref
|
|
325
348
|
? {
|
|
326
349
|
...newUpdate.entity,
|
|
@@ -341,16 +364,18 @@ const _newUpdate = (query, transitions, service) => {
|
|
|
341
364
|
enumerable: false,
|
|
342
365
|
value: transitions
|
|
343
366
|
})
|
|
367
|
+
|
|
344
368
|
return newUpdate
|
|
345
369
|
}
|
|
346
370
|
|
|
347
371
|
const _newSelect = (query, transitions, service) => {
|
|
348
|
-
const targetTransition = transitions
|
|
372
|
+
const targetTransition = transitions.at(-1)
|
|
349
373
|
const newSelect = Object.create(query.SELECT)
|
|
350
374
|
newSelect.from = {
|
|
351
375
|
...newSelect.from,
|
|
352
376
|
ref: _rewriteQueryPath(query.SELECT.from, transitions)
|
|
353
377
|
}
|
|
378
|
+
|
|
354
379
|
if (!newSelect.columns && targetTransition.mapping.size) newSelect.columns = _initialColumns(targetTransition)
|
|
355
380
|
if (newSelect.columns) {
|
|
356
381
|
rewriteAsterisks({ SELECT: query.SELECT }, service.model, {
|
|
@@ -359,6 +384,7 @@ const _newSelect = (query, transitions, service) => {
|
|
|
359
384
|
})
|
|
360
385
|
newSelect.columns = _newColumns(newSelect.columns, targetTransition, service, service.kind !== 'app-service')
|
|
361
386
|
}
|
|
387
|
+
|
|
362
388
|
if (newSelect.having) newSelect.having = _newColumns(newSelect.having, targetTransition)
|
|
363
389
|
if (newSelect.groupBy) newSelect.groupBy = _newColumns(newSelect.groupBy, targetTransition)
|
|
364
390
|
if (newSelect.orderBy) newSelect.orderBy = _newColumns(newSelect.orderBy, targetTransition)
|
|
@@ -370,17 +396,42 @@ const _newSelect = (query, transitions, service) => {
|
|
|
370
396
|
query.SELECT.from && query.SELECT.from.as
|
|
371
397
|
)
|
|
372
398
|
}
|
|
399
|
+
|
|
373
400
|
Object.defineProperty(newSelect, '_transitions', {
|
|
374
401
|
enumerable: false,
|
|
375
402
|
value: transitions
|
|
376
403
|
})
|
|
404
|
+
|
|
377
405
|
return newSelect
|
|
378
406
|
}
|
|
379
407
|
|
|
408
|
+
const _newStream = (query, transitions) => {
|
|
409
|
+
const targetTransition = transitions[transitions.length - 1]
|
|
410
|
+
const targetName = targetTransition.target.name
|
|
411
|
+
const newStream = Object.create(query.STREAM)
|
|
412
|
+
if (newStream.into) {
|
|
413
|
+
const refObject = newStream.into.ref ? newStream.into : { ref: [query.STREAM.into] }
|
|
414
|
+
newStream.into = {
|
|
415
|
+
...refObject,
|
|
416
|
+
ref: _rewriteQueryPath(refObject, transitions)
|
|
417
|
+
}
|
|
418
|
+
if (!query.STREAM.into.ref) newStream.into = newStream.into.ref[0] // leave as string
|
|
419
|
+
} else {
|
|
420
|
+
newStream.into = targetName
|
|
421
|
+
}
|
|
422
|
+
if (newStream.column) newStream.column = _resolveColumn(newStream.column, targetTransition)
|
|
423
|
+
Object.defineProperty(newStream, '_transitions', {
|
|
424
|
+
enumerable: false,
|
|
425
|
+
value: transitions
|
|
426
|
+
})
|
|
427
|
+
return newStream
|
|
428
|
+
}
|
|
429
|
+
|
|
380
430
|
const _newInsert = (query, transitions, service) => {
|
|
381
431
|
const targetTransition = transitions[transitions.length - 1]
|
|
382
432
|
const targetName = targetTransition.target.name
|
|
383
433
|
const newInsert = Object.create(query.INSERT)
|
|
434
|
+
|
|
384
435
|
if (newInsert.into) {
|
|
385
436
|
const refObject = newInsert.into.ref ? newInsert.into : { ref: [query.INSERT.into] }
|
|
386
437
|
newInsert.into = {
|
|
@@ -391,12 +442,14 @@ const _newInsert = (query, transitions, service) => {
|
|
|
391
442
|
} else {
|
|
392
443
|
newInsert.into = targetName
|
|
393
444
|
}
|
|
445
|
+
|
|
394
446
|
if (newInsert.columns) newInsert.columns = _newInsertColumns(newInsert.columns, targetTransition)
|
|
395
447
|
if (newInsert.entries) newInsert.entries = _newEntries(newInsert.entries, targetTransition, service)
|
|
396
448
|
Object.defineProperty(newInsert, '_transitions', {
|
|
397
449
|
enumerable: false,
|
|
398
450
|
value: transitions
|
|
399
451
|
})
|
|
452
|
+
|
|
400
453
|
return newInsert
|
|
401
454
|
}
|
|
402
455
|
|
|
@@ -404,6 +457,7 @@ const _newUpsert = (query, transitions, service) => {
|
|
|
404
457
|
const targetTransition = transitions[transitions.length - 1]
|
|
405
458
|
const targetName = targetTransition.target.name
|
|
406
459
|
const newUpsert = Object.create(query.UPSERT)
|
|
460
|
+
|
|
407
461
|
newUpsert.into = newUpsert.into.ref
|
|
408
462
|
? {
|
|
409
463
|
...newUpsert.into,
|
|
@@ -416,6 +470,7 @@ const _newUpsert = (query, transitions, service) => {
|
|
|
416
470
|
enumerable: false,
|
|
417
471
|
value: transitions
|
|
418
472
|
})
|
|
473
|
+
|
|
419
474
|
return newUpsert
|
|
420
475
|
}
|
|
421
476
|
|
|
@@ -423,12 +478,14 @@ const _newDelete = (query, transitions) => {
|
|
|
423
478
|
const targetTransition = transitions[transitions.length - 1]
|
|
424
479
|
const targetName = targetTransition.target.name
|
|
425
480
|
const newDelete = Object.create(query.DELETE)
|
|
481
|
+
|
|
426
482
|
newDelete.from = newDelete.from.ref
|
|
427
483
|
? {
|
|
428
484
|
...newDelete.from,
|
|
429
485
|
ref: _rewriteQueryPath(query.DELETE.from, transitions)
|
|
430
486
|
}
|
|
431
487
|
: targetName
|
|
488
|
+
|
|
432
489
|
if (newDelete.where) {
|
|
433
490
|
newDelete.where = _newWhere(
|
|
434
491
|
newDelete.where,
|
|
@@ -437,10 +494,12 @@ const _newDelete = (query, transitions) => {
|
|
|
437
494
|
query.DELETE.from.as
|
|
438
495
|
)
|
|
439
496
|
}
|
|
497
|
+
|
|
440
498
|
Object.defineProperty(newDelete, '_transitions', {
|
|
441
499
|
enumerable: false,
|
|
442
500
|
value: transitions
|
|
443
501
|
})
|
|
502
|
+
|
|
444
503
|
return newDelete
|
|
445
504
|
}
|
|
446
505
|
|
|
@@ -451,32 +510,41 @@ const _findRenamed = (cqnColumns, column) =>
|
|
|
451
510
|
cqnColumns.find(
|
|
452
511
|
cqnColumn =>
|
|
453
512
|
cqnColumn.as &&
|
|
454
|
-
(
|
|
513
|
+
(column?.ref?.at(-1) === cqnColumn.as ||
|
|
455
514
|
(column.as === cqnColumn.as && Object.prototype.hasOwnProperty.call(cqnColumn, 'val')))
|
|
456
515
|
)
|
|
457
516
|
|
|
458
517
|
const _queryColumns = (target, columns = [], persistenceTable = false, force = false) => {
|
|
459
518
|
if (!(target && target.query && target.query.SELECT)) return columns
|
|
519
|
+
|
|
460
520
|
const cqnColumns = target.query.SELECT.columns || []
|
|
461
521
|
const from = target.query.SELECT.from
|
|
462
|
-
const isTargetAliased = from.as && cqnColumns.some(c => c.ref
|
|
522
|
+
const isTargetAliased = from.as && cqnColumns.some(c => c.ref?.[0] === from.as)
|
|
523
|
+
|
|
463
524
|
if (!columns.length) columns = Object.keys(target.elements).map(e => ({ ref: [e], as: e }))
|
|
464
|
-
|
|
525
|
+
|
|
526
|
+
const queryColumns = columns.reduce((res, column) => {
|
|
465
527
|
const renamed = _findRenamed(cqnColumns, column)
|
|
528
|
+
|
|
466
529
|
if (renamed) {
|
|
467
530
|
if (renamed.val) return res.concat({ as: renamed.as, val: renamed.val })
|
|
531
|
+
|
|
468
532
|
// There could be some `where` clause inside `ref` which we don't support yet
|
|
469
533
|
if (!renamed.ref || renamed.ref.some(e => typeof e !== 'string') || renamed.xpr) return res
|
|
470
534
|
if (isTargetAliased) renamed.ref.shift()
|
|
535
|
+
|
|
471
536
|
// If the entity is annotated with the annotation `@cds.persistence.table`
|
|
472
537
|
// and elements aliases exist, the aliases must be used as column references.
|
|
473
538
|
// The reason is that in this scenario, the cds compiler generate a table
|
|
474
539
|
// instead of a view. If forced, skip this.
|
|
475
540
|
column.ref = !force && persistenceTable ? [renamed.as] : [...renamed.ref]
|
|
476
541
|
}
|
|
542
|
+
|
|
477
543
|
res.push(column)
|
|
478
544
|
return _appendForeignKeys(res, target, columns, column)
|
|
479
545
|
}, [])
|
|
546
|
+
|
|
547
|
+
return queryColumns
|
|
480
548
|
}
|
|
481
549
|
|
|
482
550
|
const _mappedValue = (col, alias) => {
|
|
@@ -498,7 +566,8 @@ const getDBTable = target => {
|
|
|
498
566
|
}
|
|
499
567
|
|
|
500
568
|
const _appendForeignKeys = (newColumns, target, columns, { as, ref = [] }) => {
|
|
501
|
-
const el = target.elements[as] || target.query._target.elements[ref
|
|
569
|
+
const el = target.elements[as] || target.query._target.elements[ref.at(-1)]
|
|
570
|
+
|
|
502
571
|
if (el && el.isAssociation && el.keys) {
|
|
503
572
|
for (const key of el.keys) {
|
|
504
573
|
// .as and .ref has a different meaning here
|
|
@@ -506,6 +575,7 @@ const _appendForeignKeys = (newColumns, target, columns, { as, ref = [] }) => {
|
|
|
506
575
|
const keyName = key.as || key.ref[0]
|
|
507
576
|
const keyAlias = key.ref[0]
|
|
508
577
|
const found = columns.find(col => col.as === `${as}_${keyAlias}`)
|
|
578
|
+
|
|
509
579
|
if (found) {
|
|
510
580
|
found.ref = [`${ref.join('_')}_${keyName}`]
|
|
511
581
|
} else {
|
|
@@ -516,11 +586,13 @@ const _appendForeignKeys = (newColumns, target, columns, { as, ref = [] }) => {
|
|
|
516
586
|
}
|
|
517
587
|
}
|
|
518
588
|
}
|
|
589
|
+
|
|
519
590
|
return newColumns
|
|
520
591
|
}
|
|
521
592
|
|
|
522
593
|
const _checkForForbiddenViews = queryTarget => {
|
|
523
594
|
const select = queryTarget && queryTarget.query && queryTarget.query.SELECT
|
|
595
|
+
|
|
524
596
|
if (select) {
|
|
525
597
|
if (!select.from || select.from.join || select.from.length > 1) {
|
|
526
598
|
throw getError({
|
|
@@ -529,6 +601,7 @@ const _checkForForbiddenViews = queryTarget => {
|
|
|
529
601
|
target: queryTarget.name
|
|
530
602
|
})
|
|
531
603
|
}
|
|
604
|
+
|
|
532
605
|
if (select.where) {
|
|
533
606
|
LOG._debug &&
|
|
534
607
|
LOG.debug(`Ignoring where clause during ${_event || 'INSERT|UPDATE|DELETE'} on view "${queryTarget.name}".`)
|
|
@@ -543,22 +616,28 @@ const _getTransitionData = (target, columns, service, skipForbiddenViewCheck) =>
|
|
|
543
616
|
const persistenceTable = _isPersistenceTable(target)
|
|
544
617
|
const isDatabaseService = service.isDatabaseService
|
|
545
618
|
columns = _queryColumns(target, columns, persistenceTable, !isDatabaseService && !targetStartsWithSrvName)
|
|
619
|
+
|
|
546
620
|
// REVISIT: Change once we expose database service
|
|
547
621
|
if (persistenceTable && isDatabaseService) {
|
|
548
622
|
return { target, transitionColumns: columns }
|
|
549
623
|
}
|
|
624
|
+
|
|
550
625
|
// stop projection resolving if it starts with the service name prefix
|
|
551
626
|
if (!isDatabaseService && targetStartsWithSrvName) {
|
|
552
627
|
return { target, transitionColumns: columns }
|
|
553
628
|
}
|
|
629
|
+
|
|
554
630
|
// continue projection resolving if the target is a projection
|
|
555
631
|
if (target.query && target.query._target) {
|
|
556
632
|
const newTarget = target.query._target
|
|
633
|
+
|
|
557
634
|
if (isDatabaseService || !(service.namespace && newTarget.name.startsWith(`${service.namespace}.`))) {
|
|
558
635
|
return _getTransitionData(newTarget, columns, service, skipForbiddenViewCheck)
|
|
559
636
|
}
|
|
637
|
+
|
|
560
638
|
return { target: newTarget, transitionColumns: columns }
|
|
561
639
|
}
|
|
640
|
+
|
|
562
641
|
return { target, transitionColumns: columns }
|
|
563
642
|
}
|
|
564
643
|
|
|
@@ -607,14 +686,14 @@ const _entityTransitionsForTarget = (from, model, service) => {
|
|
|
607
686
|
// > assoc
|
|
608
687
|
previousEntity = entity
|
|
609
688
|
return getTransition(entity, service)
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// > struct
|
|
692
|
+
previousEntity = previousEntity.elements[element]
|
|
693
|
+
return {
|
|
694
|
+
target: previousEntity,
|
|
695
|
+
queryTarget: previousEntity,
|
|
696
|
+
mapping: new Map()
|
|
618
697
|
}
|
|
619
698
|
}
|
|
620
699
|
})
|
|
@@ -626,7 +705,8 @@ const _newQuery = (query, event, model, service) => {
|
|
|
626
705
|
INSERT: ['into', _newInsert],
|
|
627
706
|
UPSERT: ['into', _newUpsert],
|
|
628
707
|
UPDATE: ['entity', _newUpdate],
|
|
629
|
-
DELETE: ['from', _newDelete]
|
|
708
|
+
DELETE: ['from', _newDelete],
|
|
709
|
+
STREAM: ['into', _newStream]
|
|
630
710
|
}[event]
|
|
631
711
|
const newQuery = Object.create(query)
|
|
632
712
|
const transitions = _entityTransitionsForTarget(query[event][_prop], model, service)
|
|
@@ -647,13 +727,13 @@ const resolveView = (query, model, service) => {
|
|
|
647
727
|
else if (query.UPSERT) _event = 'UPSERT'
|
|
648
728
|
else if (query.UPDATE) _event = 'UPDATE'
|
|
649
729
|
else if (query.DELETE) _event = 'DELETE'
|
|
730
|
+
else if (query.STREAM) _event = 'STREAM'
|
|
650
731
|
|
|
651
732
|
const newQuery = _newQuery(query, _event, model, service)
|
|
652
733
|
|
|
653
734
|
// restore logger and clear _event
|
|
654
735
|
LOG = _LOG
|
|
655
736
|
_event = undefined
|
|
656
|
-
|
|
657
737
|
return newQuery
|
|
658
738
|
}
|
|
659
739
|
|
|
@@ -664,10 +744,10 @@ const resolveView = (query, model, service) => {
|
|
|
664
744
|
* @param {*} req
|
|
665
745
|
*/
|
|
666
746
|
const restoreLink = req => {
|
|
667
|
-
if (req.query.INSERT
|
|
747
|
+
if (req.query.INSERT?.entries) {
|
|
668
748
|
if (Array.isArray(req.query.INSERT.entries)) req.data = req.query.INSERT.entries[0]
|
|
669
749
|
else req.data = req.query.INSERT.entries
|
|
670
|
-
} else if (req.query.UPDATE
|
|
750
|
+
} else if (req.query.UPDATE?.data) {
|
|
671
751
|
req.data = req.query.UPDATE.data
|
|
672
752
|
}
|
|
673
753
|
}
|
|
@@ -27,7 +27,28 @@ const _expandColumn = (column, target, _4db) => {
|
|
|
27
27
|
return column
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
const _resolveTarget = (ref, target) => {
|
|
31
|
+
if (ref.length > 1) {
|
|
32
|
+
if (target.elements[ref[0]]) {
|
|
33
|
+
// structured
|
|
34
|
+
return _resolveTarget(ref.slice(1), target.elements[ref[0]])
|
|
35
|
+
} else {
|
|
36
|
+
// in case there is an alias, try with the next entry
|
|
37
|
+
return _resolveTarget(ref.slice(1), target)
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
return target.elements[ref[0].id || ref[0]]._target
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
30
44
|
const rewriteExpandAsterisk = (columns, target) => {
|
|
45
|
+
// check all nested expands to resolve nested expand asterisks first
|
|
46
|
+
for (const column of columns) {
|
|
47
|
+
if (column.expand && column.ref) {
|
|
48
|
+
rewriteExpandAsterisk(column.expand, _resolveTarget(column.ref, target))
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
31
52
|
const expandAllColIdx = columns.findIndex(col => {
|
|
32
53
|
if (col.ref || !col.expand) return
|
|
33
54
|
return col.expand.includes('*')
|
|
@@ -20,7 +20,7 @@ const search2cqn4sql = (query, model, options = {}) => {
|
|
|
20
20
|
|
|
21
21
|
// Call custom (optimized search to cqn for sql implementation) that tries
|
|
22
22
|
// to optimize the search behavior for a specific database service.
|
|
23
|
-
if (typeof search2cqn4sql === 'function' && entity
|
|
23
|
+
if (typeof search2cqn4sql === 'function' && entity?.associations?.localized && !aggregated) {
|
|
24
24
|
const search2cqnOptions = { columns: computeColumnsToBeSearched(query, entity), locale: options.locale }
|
|
25
25
|
return search2cqn4sql(query, entity, search2cqnOptions)
|
|
26
26
|
}
|
|
@@ -3,10 +3,12 @@ const { generateAliases } = require('../utils/generateAliases')
|
|
|
3
3
|
const { restoreLink } = require('../../common/utils/resolveView')
|
|
4
4
|
|
|
5
5
|
const _isLinked = req => {
|
|
6
|
-
if (req.query.INSERT
|
|
6
|
+
if (req.query.INSERT?.entries) {
|
|
7
7
|
if (Array.isArray(req.query.INSERT.entries)) return req.data === req.query.INSERT.entries[0]
|
|
8
|
-
|
|
9
|
-
}
|
|
8
|
+
return req.data === req.query.INSERT.entries
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (req.query.UPDATE?.data) {
|
|
10
12
|
return req.data === req.query.UPDATE.data
|
|
11
13
|
}
|
|
12
14
|
}
|
|
@@ -36,7 +38,6 @@ function handler(req) {
|
|
|
36
38
|
if (linked) restoreLink(req)
|
|
37
39
|
|
|
38
40
|
if (streaming) req.query._streaming = streaming
|
|
39
|
-
|
|
40
41
|
generateAliases(req.query)
|
|
41
42
|
}
|
|
42
43
|
|
|
@@ -45,6 +45,7 @@ const read = (executeSelectCQN, executeStreamCQN) => (model, dbc, query, req) =>
|
|
|
45
45
|
if (!query.SELECT || (query.SELECT && !query.SELECT.columns)) {
|
|
46
46
|
throw getError(500, 'Invalid SELECT statement for streaming')
|
|
47
47
|
}
|
|
48
|
+
|
|
48
49
|
return executeStreamCQN(model, dbc, query, user, locale, isoTs)
|
|
49
50
|
}
|
|
50
51
|
|
|
@@ -55,20 +56,20 @@ const read = (executeSelectCQN, executeStreamCQN) => (model, dbc, query, req) =>
|
|
|
55
56
|
if (query.SELECT.limit) {
|
|
56
57
|
const countQuery = _createCountQuery(query)
|
|
57
58
|
const countResultPromise = executeSelectCQN(model, dbc, countQuery, user, locale, isoTs)
|
|
58
|
-
if (query.SELECT.limit
|
|
59
|
+
if (query.SELECT.limit?.rows?.val === 0) {
|
|
59
60
|
// We don't need to perform our result query
|
|
60
61
|
return countResultPromise.then(countResults => _arrayWithCount([], countValue(countResults)))
|
|
61
|
-
} else {
|
|
62
|
-
const resultPromise = executeSelectCQN(model, dbc, query, user, locale, isoTs)
|
|
63
|
-
return Promise.all([countResultPromise, resultPromise]).then(([countResults, result]) =>
|
|
64
|
-
_arrayWithCount(result, countValue(countResults))
|
|
65
|
-
)
|
|
66
62
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
63
|
+
|
|
64
|
+
const resultPromise = executeSelectCQN(model, dbc, query, user, locale, isoTs)
|
|
65
|
+
return Promise.all([countResultPromise, resultPromise]).then(([countResults, result]) =>
|
|
66
|
+
_arrayWithCount(result, countValue(countResults))
|
|
70
67
|
)
|
|
71
68
|
}
|
|
69
|
+
|
|
70
|
+
return executeSelectCQN(model, dbc, query, user, locale, isoTs).then(result =>
|
|
71
|
+
_arrayWithCount(result, result.length)
|
|
72
|
+
)
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
return executeSelectCQN(model, dbc, query, user, locale, isoTs)
|
|
@@ -19,14 +19,13 @@ const _getFilteredCqns = (cqns, model) => {
|
|
|
19
19
|
const entity = model && cqn.UPDATE && model.definitions[cqn.UPDATE.entity]
|
|
20
20
|
if (!entity) continue
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
* - a composition target is updated as well
|
|
26
|
-
*/
|
|
22
|
+
// do not filter if:
|
|
23
|
+
// - there is a property that is not managed or managed but filled by custom handler (i.e., its value doesn't start
|
|
24
|
+
// with $) a composition target is updated as well
|
|
27
25
|
let moreThanManaged = Object.keys(cqn.UPDATE.data).some(
|
|
28
|
-
|
|
26
|
+
key => entity.elements[key]['@cds.on.update'] === undefined || !cqn.UPDATE.data[key]?.startsWith('$')
|
|
29
27
|
)
|
|
28
|
+
|
|
30
29
|
if (moreThanManaged) continue
|
|
31
30
|
|
|
32
31
|
const comps = Object.values(entity.associations || {}).filter(assoc => assoc.isComposition)
|
|
@@ -36,6 +35,7 @@ const _getFilteredCqns = (cqns, model) => {
|
|
|
36
35
|
break
|
|
37
36
|
}
|
|
38
37
|
}
|
|
38
|
+
|
|
39
39
|
if (moreThanManaged) continue
|
|
40
40
|
|
|
41
41
|
// remove current cqn
|
|
@@ -50,16 +50,8 @@ const update = executeUpdateCQN => async (model, dbc, req) => {
|
|
|
50
50
|
const isoTs = normalizeTimestamp(timestamp)
|
|
51
51
|
|
|
52
52
|
if (model && hasDeepUpdate(model, query)) {
|
|
53
|
-
// REVISIT:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (selectData) {
|
|
57
|
-
selectData = [selectData]
|
|
58
|
-
} else {
|
|
59
|
-
// REVISIT: avoid additional read
|
|
60
|
-
selectData = await selectDeepUpdateData(cds.db, model, req)
|
|
61
|
-
}
|
|
62
|
-
|
|
53
|
+
// REVISIT: avoid additional read
|
|
54
|
+
const selectData = await selectDeepUpdateData(cds.db, model, req)
|
|
63
55
|
let cqns = await getDeepUpdateCQNs(model, req, selectData)
|
|
64
56
|
|
|
65
57
|
// the delete chunks, i.e., how many deletes can be processed in parallel
|
|
@@ -70,11 +62,10 @@ const update = executeUpdateCQN => async (model, dbc, req) => {
|
|
|
70
62
|
cqns = _getFilteredCqns(getFlatArray(cqns), model)
|
|
71
63
|
|
|
72
64
|
if (cqns.length === 0) return 0
|
|
73
|
-
|
|
74
65
|
const results = await processCQNs(executeUpdateCQN, cqns, model, dbc, user, locale, isoTs, chunks)
|
|
66
|
+
|
|
75
67
|
// return number of affected rows of "root cqn", if an update, 1 otherwise (as not update of root but its children)
|
|
76
68
|
if (cqns[0].UPDATE) return results[0]
|
|
77
|
-
|
|
78
69
|
return 1
|
|
79
70
|
}
|
|
80
71
|
|
|
@@ -3,9 +3,11 @@ const _flattenDeep = (arr, res) => {
|
|
|
3
3
|
res.push(arr)
|
|
4
4
|
return res
|
|
5
5
|
}
|
|
6
|
+
|
|
6
7
|
for (const a of arr) {
|
|
7
8
|
_flattenDeep(a, res)
|
|
8
9
|
}
|
|
10
|
+
|
|
9
11
|
return res
|
|
10
12
|
}
|
|
11
13
|
|
|
@@ -20,20 +22,18 @@ async function _processChunk(processFn, model, dbc, cqns, user, locale, ts, inde
|
|
|
20
22
|
const promisesResults = await Promise.allSettled(promises)
|
|
21
23
|
const firstRejected = promisesResults.find(r => r.status === 'rejected')
|
|
22
24
|
if (firstRejected) throw firstRejected.reason
|
|
25
|
+
|
|
23
26
|
// put results of queries into correct place of return results
|
|
24
27
|
for (let i = 0; i < promisesResults.length; i++) results[indexes[i]] = promisesResults[i].value
|
|
25
28
|
}
|
|
26
29
|
|
|
30
|
+
// execute deletes first, but keep results in order of cqns
|
|
27
31
|
async function processCQNs(processFn, cqns, model, dbc, user, locale, ts, chunks) {
|
|
28
|
-
/*
|
|
29
|
-
* execute deletes first, but keep results in order of cqns
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
32
|
const results = new Array(cqns.length)
|
|
33
|
-
|
|
34
33
|
const deletes = []
|
|
35
34
|
const updatesForDeletes = []
|
|
36
35
|
const others = []
|
|
36
|
+
|
|
37
37
|
for (let i = 0; i < cqns.length; i++) {
|
|
38
38
|
if (cqns[i].DELETE) deletes.push(i)
|
|
39
39
|
else if (cqns[i].__4delete) updatesForDeletes.push(i)
|
|
@@ -43,6 +43,7 @@ async function processCQNs(processFn, cqns, model, dbc, user, locale, ts, chunks
|
|
|
43
43
|
if (deletes.length > 0) {
|
|
44
44
|
if (chunks) {
|
|
45
45
|
let offset = 0
|
|
46
|
+
|
|
46
47
|
for (const amount of chunks) {
|
|
47
48
|
const indexes = deletes.slice(offset, offset + amount)
|
|
48
49
|
await _processChunk(processFn, model, dbc, cqns, user, locale, ts, indexes, results)
|