@cap-js/db-service 1.1.0 → 1.2.0
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 +18 -0
- package/lib/InsertResults.js +2 -1
- package/lib/SQLService.js +64 -52
- package/lib/common/DatabaseService.js +84 -130
- package/lib/common/generic-pool.js +34 -0
- package/lib/common/session-context.js +32 -0
- package/lib/cql-functions.js +14 -1
- package/lib/cqn2sql.js +215 -171
- package/lib/cqn4sql.js +118 -56
- package/lib/deep-queries.js +4 -3
- package/lib/fill-in-keys.js +5 -4
- package/lib/infer/index.js +41 -19
- package/lib/infer/join-tree.js +2 -2
- package/package.json +2 -5
package/lib/cqn4sql.js
CHANGED
|
@@ -318,7 +318,14 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
318
318
|
transformedColumns.push(...getTransformedColumns([dollarSelfReplacement]))
|
|
319
319
|
continue
|
|
320
320
|
}
|
|
321
|
-
|
|
321
|
+
transformedColumns.push(() => {
|
|
322
|
+
const expandResult = handleExpand(col)
|
|
323
|
+
if (expandResult.length > 1) {
|
|
324
|
+
return expandResult
|
|
325
|
+
} else {
|
|
326
|
+
return expandResult[0]
|
|
327
|
+
}
|
|
328
|
+
})
|
|
322
329
|
} else if (col.inline) {
|
|
323
330
|
handleInline(col)
|
|
324
331
|
} else if (col.ref) {
|
|
@@ -330,10 +337,34 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
330
337
|
handleRef(col)
|
|
331
338
|
} else if (col === '*') {
|
|
332
339
|
handleWildcard(columns)
|
|
340
|
+
} else if (col.SELECT) {
|
|
341
|
+
handleSubquery(col)
|
|
333
342
|
} else {
|
|
334
343
|
handleDefault(col)
|
|
335
344
|
}
|
|
336
345
|
}
|
|
346
|
+
// subqueries are processed in the end
|
|
347
|
+
for (let i = 0; i < transformedColumns.length; i++) {
|
|
348
|
+
const c = transformedColumns[i]
|
|
349
|
+
if (typeof c === 'function') {
|
|
350
|
+
const res = c() || [] // target of expand / subquery could also be skipped -> no result
|
|
351
|
+
if (res.length !== undefined) {
|
|
352
|
+
transformedColumns.splice(i, 1, ...res)
|
|
353
|
+
i += res.length - 1
|
|
354
|
+
} else {
|
|
355
|
+
const replaceWith = res.as
|
|
356
|
+
? transformedColumns.findIndex(t => (t.as || t.ref?.[t.ref.length - 1]) === res.as)
|
|
357
|
+
: -1
|
|
358
|
+
if (replaceWith === -1) transformedColumns.splice(i, 1, res)
|
|
359
|
+
else {
|
|
360
|
+
transformedColumns.splice(replaceWith, 1, res)
|
|
361
|
+
transformedColumns.splice(i, 1)
|
|
362
|
+
// When removing an element, the next element moves to the current index
|
|
363
|
+
i--
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
337
368
|
|
|
338
369
|
if (transformedColumns.length === 0 && columns.length) {
|
|
339
370
|
handleEmptyColumns(columns)
|
|
@@ -341,16 +372,35 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
341
372
|
|
|
342
373
|
return transformedColumns
|
|
343
374
|
|
|
375
|
+
function handleSubquery(col) {
|
|
376
|
+
if (isLocalized(inferred.target)) col.SELECT.localized = true
|
|
377
|
+
if (!col.SELECT.from.as) {
|
|
378
|
+
const uniqueSubqueryAlias = inferred.joinTree.addNextAvailableTableAlias(
|
|
379
|
+
getLastStringSegment(col.SELECT.from.ref[col.SELECT.from.ref.length - 1]),
|
|
380
|
+
originalQuery.outerQueries,
|
|
381
|
+
)
|
|
382
|
+
Object.defineProperty(col.SELECT.from, 'uniqueSubqueryAlias', { value: uniqueSubqueryAlias })
|
|
383
|
+
}
|
|
384
|
+
transformedColumns.push(() => {
|
|
385
|
+
const res = transformSubquery(col)
|
|
386
|
+
if (col.as) res.as = col.as
|
|
387
|
+
return res
|
|
388
|
+
})
|
|
389
|
+
}
|
|
390
|
+
|
|
344
391
|
function handleExpand(col) {
|
|
345
392
|
const { $refLinks } = col
|
|
393
|
+
const res = []
|
|
346
394
|
const last = $refLinks?.[$refLinks.length - 1]
|
|
347
395
|
if (last && !last.skipExpand && last.definition.isAssociation) {
|
|
348
396
|
const expandedSubqueryColumn = expandColumn(col)
|
|
349
|
-
|
|
397
|
+
setElementOnColumns(expandedSubqueryColumn, col.element)
|
|
398
|
+
res.push(expandedSubqueryColumn)
|
|
350
399
|
} else if (!last?.skipExpand) {
|
|
351
400
|
const expandCols = nestedProjectionOnStructure(col, 'expand')
|
|
352
|
-
|
|
401
|
+
res.push(...expandCols)
|
|
353
402
|
}
|
|
403
|
+
return res
|
|
354
404
|
}
|
|
355
405
|
|
|
356
406
|
function handleInline(col) {
|
|
@@ -383,10 +433,11 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
383
433
|
|
|
384
434
|
if (col.$refLinks.some(link => link.definition._target?.['@cds.persistence.skip'] === true)) return
|
|
385
435
|
|
|
436
|
+
const getName = col => col.as || col.ref?.at(-1)
|
|
386
437
|
const flatColumns = getFlatColumnsFor(col, { baseName, columnAlias, tableAlias })
|
|
387
438
|
flatColumns.forEach(flatColumn => {
|
|
388
|
-
const
|
|
389
|
-
if (!
|
|
439
|
+
const name = getName(flatColumn)
|
|
440
|
+
if (!transformedColumns.some(inserted => getName(inserted) === name)) transformedColumns.push(flatColumn)
|
|
390
441
|
})
|
|
391
442
|
}
|
|
392
443
|
|
|
@@ -404,7 +455,9 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
404
455
|
let transformedColumn = getTransformedColumn(col)
|
|
405
456
|
if (col.as) transformedColumn.as = col.as
|
|
406
457
|
|
|
407
|
-
const replaceWith = transformedColumns.findIndex(
|
|
458
|
+
const replaceWith = transformedColumns.findIndex(
|
|
459
|
+
t => (t.as || t.ref?.[t.ref.length - 1]) === transformedColumn.as,
|
|
460
|
+
)
|
|
408
461
|
if (replaceWith === -1) transformedColumns.push(transformedColumn)
|
|
409
462
|
else transformedColumns.splice(replaceWith, 1, transformedColumn)
|
|
410
463
|
|
|
@@ -412,17 +465,7 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
412
465
|
}
|
|
413
466
|
|
|
414
467
|
function getTransformedColumn(col) {
|
|
415
|
-
if (col.
|
|
416
|
-
if (isLocalized(inferred.target)) col.SELECT.localized = true
|
|
417
|
-
if (!col.SELECT.from.as) {
|
|
418
|
-
const uniqueSubqueryAlias = inferred.joinTree.addNextAvailableTableAlias(
|
|
419
|
-
getLastStringSegment(col.SELECT.from.ref[col.SELECT.from.ref.length - 1]),
|
|
420
|
-
originalQuery.outerQueries,
|
|
421
|
-
)
|
|
422
|
-
Object.defineProperty(col.SELECT.from, 'uniqueSubqueryAlias', { value: uniqueSubqueryAlias })
|
|
423
|
-
}
|
|
424
|
-
return transformSubquery(col)
|
|
425
|
-
} else if (col.xpr) {
|
|
468
|
+
if (col.xpr) {
|
|
426
469
|
return { xpr: getTransformedTokenStream(col.xpr) }
|
|
427
470
|
} else if (col.func) {
|
|
428
471
|
return {
|
|
@@ -447,7 +490,13 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
447
490
|
if (column.$refLinks) {
|
|
448
491
|
const { $refLinks } = column
|
|
449
492
|
value = $refLinks[$refLinks.length - 1].definition.value
|
|
450
|
-
|
|
493
|
+
if (column.$refLinks.length > 1) {
|
|
494
|
+
baseLink =
|
|
495
|
+
[...$refLinks].reverse().find($refLink => $refLink.definition.isAssociation) ||
|
|
496
|
+
// if there is no association in the path, the table alias is the base link
|
|
497
|
+
// TA might refer to subquery -> we need to propagate the alias to all paths of the calc element
|
|
498
|
+
column.$refLinks[0]
|
|
499
|
+
}
|
|
451
500
|
} else {
|
|
452
501
|
value = column.value
|
|
453
502
|
}
|
|
@@ -460,7 +509,7 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
460
509
|
res = { xpr: getTransformedTokenStream(value.xpr, baseLink) }
|
|
461
510
|
} else if (val) {
|
|
462
511
|
res = { val }
|
|
463
|
-
} else if (func) res = { args: getTransformedTokenStream(value.args), func: value.func }
|
|
512
|
+
} else if (func) res = { args: getTransformedTokenStream(value.args, baseLink), func: value.func }
|
|
464
513
|
if (!omitAlias) res.as = column.as || column.name || column.flatName
|
|
465
514
|
return res
|
|
466
515
|
}
|
|
@@ -499,7 +548,7 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
499
548
|
dollarSelfColumn.ref = [...referencedColumn.ref, ...dollarSelfColumn.ref.slice(2)]
|
|
500
549
|
Object.defineProperties(dollarSelfColumn, {
|
|
501
550
|
flatName: {
|
|
502
|
-
value: dollarSelfColumn.ref.join('_'),
|
|
551
|
+
value: referencedColumn.$refLinks[0].definition.kind === 'entity' ? dollarSelfColumn.ref.slice(1).join('_') : dollarSelfColumn.ref.join('_'),
|
|
503
552
|
},
|
|
504
553
|
isJoinRelevant: {
|
|
505
554
|
value: referencedColumn.isJoinRelevant,
|
|
@@ -544,7 +593,12 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
544
593
|
if (nestedProjection.ref) {
|
|
545
594
|
const augmentedInlineCol = { ...nestedProjection }
|
|
546
595
|
augmentedInlineCol.ref = col.ref ? [...col.ref, ...nestedProjection.ref] : nestedProjection.ref
|
|
547
|
-
if (
|
|
596
|
+
if (
|
|
597
|
+
col.as ||
|
|
598
|
+
nestedProjection.as ||
|
|
599
|
+
nestedProjection.$refLinks[nestedProjection.$refLinks.length - 1].definition.value ||
|
|
600
|
+
nestedProjection.isJoinRelevant
|
|
601
|
+
) {
|
|
548
602
|
augmentedInlineCol.as = nameParts.join('_')
|
|
549
603
|
}
|
|
550
604
|
Object.defineProperties(augmentedInlineCol, {
|
|
@@ -857,7 +911,7 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
857
911
|
const { index, tableAlias } = inferred.$combinedElements[k][0]
|
|
858
912
|
const element = tableAlias.elements[k]
|
|
859
913
|
// ignore FK for odata csn / ignore blobs from wildcard expansion
|
|
860
|
-
if (
|
|
914
|
+
if (isManagedAssocInFlatMode(element) || (element['@Core.MediaType'] && !element['@Core.IsURL'])) return
|
|
861
915
|
// for wildcard on subquery in from, just reference the elements
|
|
862
916
|
if (tableAlias.SELECT && !element.elements && !element.target) {
|
|
863
917
|
wildcardColumns.push(index ? { ref: [index, k] } : { ref: [k] })
|
|
@@ -871,13 +925,13 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
871
925
|
return wildcardColumns
|
|
872
926
|
|
|
873
927
|
/**
|
|
874
|
-
*
|
|
875
|
-
* not excluding
|
|
928
|
+
* foreign keys are already part of the elements in a flat model
|
|
929
|
+
* not excluding the associations from the wildcard columns would cause duplicate columns upon foreign key expansion
|
|
876
930
|
* @param {CSN.element} e
|
|
877
|
-
* @returns {boolean} true if the element is a
|
|
931
|
+
* @returns {boolean} true if the element is a managed association and the model is flat
|
|
878
932
|
*/
|
|
879
|
-
function
|
|
880
|
-
return
|
|
933
|
+
function isManagedAssocInFlatMode(e) {
|
|
934
|
+
return model.meta.transformation === 'odata' && e.isAssociation && e.keys
|
|
881
935
|
}
|
|
882
936
|
}
|
|
883
937
|
|
|
@@ -960,6 +1014,8 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
960
1014
|
// we need to provide the correct table alias
|
|
961
1015
|
tableAlias = getQuerySourceName(replacedBy)
|
|
962
1016
|
|
|
1017
|
+
if (replacedBy.expand) return [{ as: baseName }]
|
|
1018
|
+
|
|
963
1019
|
return getFlatColumnsFor(replacedBy, { baseName, columnAlias: replacedBy.as, tableAlias }, csnPath)
|
|
964
1020
|
}
|
|
965
1021
|
|
|
@@ -1013,7 +1069,10 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
1013
1069
|
else flatColumn = { ref: [fkBaseName] }
|
|
1014
1070
|
if (tableAlias) flatColumn.ref.unshift(tableAlias)
|
|
1015
1071
|
|
|
1016
|
-
|
|
1072
|
+
// in a flat model, we must assign the foreign key rather than the key in the target
|
|
1073
|
+
const flatForeignKey = model.definitions[element.parent.name]?.elements[fkBaseName]
|
|
1074
|
+
|
|
1075
|
+
setElementOnColumns(flatColumn, flatForeignKey || fkElement)
|
|
1017
1076
|
Object.defineProperty(flatColumn, '_csnPath', { value: csnPath, writable: true })
|
|
1018
1077
|
flatColumns.push(flatColumn)
|
|
1019
1078
|
}
|
|
@@ -1195,6 +1254,8 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
1195
1254
|
let result = is_regexp(token?.val) ? token : copy(token) // REVISIT: too expensive! //
|
|
1196
1255
|
if (token.ref) {
|
|
1197
1256
|
const { definition } = token.$refLinks[token.$refLinks.length - 1]
|
|
1257
|
+
// Add definition to result
|
|
1258
|
+
setElementOnColumns(result, definition)
|
|
1198
1259
|
if (isCalculatedOnRead(definition)) {
|
|
1199
1260
|
const calculatedElement = resolveCalculatedElement(token, true, $baseLink)
|
|
1200
1261
|
transformedTokenStream.push(calculatedElement)
|
|
@@ -1563,6 +1624,34 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
1563
1624
|
continue
|
|
1564
1625
|
}
|
|
1565
1626
|
const rhs = result[i + 2]
|
|
1627
|
+
if (rhs?.ref || lhs.ref) {
|
|
1628
|
+
// if we have refs on each side of the comparison, we might need to perform tuple expansion
|
|
1629
|
+
// or flatten the structures
|
|
1630
|
+
const refLinkFaker = thing => {
|
|
1631
|
+
const { ref } = thing
|
|
1632
|
+
const assocHost = getParentEntity(assocRefLink.definition)
|
|
1633
|
+
Object.defineProperty(thing, '$refLinks', {
|
|
1634
|
+
value: [],
|
|
1635
|
+
writable: true,
|
|
1636
|
+
})
|
|
1637
|
+
ref.reduce((prev, res, i) => {
|
|
1638
|
+
if (res === '$self')
|
|
1639
|
+
// next is resolvable in entity
|
|
1640
|
+
return prev
|
|
1641
|
+
const definition = prev?.elements?.[res] || prev?._target?.elements[res] || pseudos.elements[res]
|
|
1642
|
+
const target = getParentEntity(definition)
|
|
1643
|
+
thing.$refLinks[i] = { definition, target, alias: definition.name }
|
|
1644
|
+
return prev?.elements?.[res] || prev?._target?.elements[res] || pseudos.elements[res]
|
|
1645
|
+
}, assocHost)
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
// comparison in on condition needs to be expanded...
|
|
1649
|
+
// re-use existing algorithm for that
|
|
1650
|
+
// we need to fake some $refLinks for that to work though...
|
|
1651
|
+
lhs?.ref && !lhs.$refLinks && refLinkFaker(lhs)
|
|
1652
|
+
rhs?.ref && !rhs.$refLinks && refLinkFaker(rhs)
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1566
1655
|
let backlink
|
|
1567
1656
|
if (rhs?.ref && lhs?.ref) {
|
|
1568
1657
|
if (lhs?.ref?.length === 1 && lhs.ref[0] === '$self')
|
|
@@ -1576,32 +1665,6 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
1576
1665
|
lhs.ref[0] in { $self: true, $projection: true } ? getParentEntity(assocRefLink.definition) : target,
|
|
1577
1666
|
)
|
|
1578
1667
|
else {
|
|
1579
|
-
// if we have refs on each side of the comparison, we might need to perform tuple expansion
|
|
1580
|
-
// or flatten the structures
|
|
1581
|
-
// REVISIT: this whole section needs a refactoring, it is too complex and some edge cases may still be not considered...
|
|
1582
|
-
const refLinkFaker = thing => {
|
|
1583
|
-
const { ref } = thing
|
|
1584
|
-
const assocHost = getParentEntity(assocRefLink.definition)
|
|
1585
|
-
Object.defineProperty(thing, '$refLinks', {
|
|
1586
|
-
value: [],
|
|
1587
|
-
writable: true,
|
|
1588
|
-
})
|
|
1589
|
-
ref.reduce((prev, res, i) => {
|
|
1590
|
-
if (res === '$self')
|
|
1591
|
-
// next is resolvable in entity
|
|
1592
|
-
return prev
|
|
1593
|
-
const definition = prev?.elements?.[res] || prev?._target?.elements[res] || pseudos.elements[res]
|
|
1594
|
-
const target = getParentEntity(definition)
|
|
1595
|
-
thing.$refLinks[i] = { definition, target, alias: definition.name }
|
|
1596
|
-
return prev?.elements?.[res] || prev?._target?.elements[res] || pseudos.elements[res]
|
|
1597
|
-
}, assocHost)
|
|
1598
|
-
}
|
|
1599
|
-
|
|
1600
|
-
// comparison in on condition needs to be expanded...
|
|
1601
|
-
// re-use existing algorithm for that
|
|
1602
|
-
// we need to fake some $refLinks for that to work though...
|
|
1603
|
-
lhs?.ref && refLinkFaker(lhs)
|
|
1604
|
-
rhs?.ref && refLinkFaker(rhs)
|
|
1605
1668
|
const lhsLeafArt = lhs.ref && lhs.$refLinks[lhs.$refLinks.length - 1].definition
|
|
1606
1669
|
const rhsLeafArt = rhs.ref && rhs.$refLinks[rhs.$refLinks.length - 1].definition
|
|
1607
1670
|
if (lhsLeafArt?.target || rhsLeafArt?.target) {
|
|
@@ -1625,7 +1688,7 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
1625
1688
|
lhs.$refLinks[0].definition ===
|
|
1626
1689
|
getParentEntity(assocRefLink.definition).elements[assocRefLink.definition.name]
|
|
1627
1690
|
)
|
|
1628
|
-
result[i].ref = [
|
|
1691
|
+
result[i].ref = [assocRefLink.alias, lhs.ref.slice(1).join('_')]
|
|
1629
1692
|
// naive assumption: if the path starts with an association which is not the association from
|
|
1630
1693
|
// which the on-condition originates, it must be a foreign key and hence resolvable in the source
|
|
1631
1694
|
else if (lhs.$refLinks[0].definition.target) result[i].ref = [result[i].ref.join('_')]
|
|
@@ -1876,7 +1939,6 @@ function cqn4sql(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
1876
1939
|
if (node.isJoinRelevant) {
|
|
1877
1940
|
return getJoinRelevantAlias(node)
|
|
1878
1941
|
}
|
|
1879
|
-
|
|
1880
1942
|
return getSelectOrEntityAlias(node) || getCombinedElementAlias(node)
|
|
1881
1943
|
function getBaseLinkAlias($baseLink) {
|
|
1882
1944
|
return $baseLink.alias
|
package/lib/deep-queries.js
CHANGED
|
@@ -189,7 +189,7 @@ const getDeepQueries = (query, dbData, target) => {
|
|
|
189
189
|
diff = [diff]
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
-
return _getDeepQueries(diff, target)
|
|
192
|
+
return _getDeepQueries(diff, target, true)
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
const _hasManagedElements = target => {
|
|
@@ -199,9 +199,10 @@ const _hasManagedElements = target => {
|
|
|
199
199
|
/**
|
|
200
200
|
* @param {unknown[]} diff
|
|
201
201
|
* @param {import('@sap/cds/apis/csn').Definition} target
|
|
202
|
+
* @param {boolean} [root=false]
|
|
202
203
|
* @returns {import('@sap/cds/apis/cqn').Query[]}
|
|
203
204
|
*/
|
|
204
|
-
const _getDeepQueries = (diff, target) => {
|
|
205
|
+
const _getDeepQueries = (diff, target, root = false) => {
|
|
205
206
|
const queries = []
|
|
206
207
|
|
|
207
208
|
for (const diffEntry of diff) {
|
|
@@ -240,7 +241,7 @@ const _getDeepQueries = (diff, target) => {
|
|
|
240
241
|
queries.push(INSERT.into(target).entries(diffEntry))
|
|
241
242
|
} else if (op === 'delete') {
|
|
242
243
|
queries.push(DELETE.from(target).where(diffEntry))
|
|
243
|
-
} else if (op === 'update' || (op === undefined && subQueries.length && _hasManagedElements(target))) {
|
|
244
|
+
} else if (op === 'update' || (op === undefined && (root || subQueries.length) && _hasManagedElements(target))) {
|
|
244
245
|
// TODO do we need the where here?
|
|
245
246
|
const keys = target.keys
|
|
246
247
|
const cqn = UPDATE(target).with(diffEntry)
|
package/lib/fill-in-keys.js
CHANGED
|
@@ -23,6 +23,11 @@ const generateUUIDandPropagateKeys = (target, data, event) => {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
if (elements[element].is2one || elements[element].is2many) {
|
|
26
|
+
// propagate own foreign keys to propagate further to sub data
|
|
27
|
+
propagateForeignKeys(element, data, elements[element]._foreignKeys, elements[element].isComposition, {
|
|
28
|
+
deleteAssocs: true,
|
|
29
|
+
})
|
|
30
|
+
|
|
26
31
|
let subData = data[element]
|
|
27
32
|
if (subData) {
|
|
28
33
|
if (!Array.isArray(subData)) {
|
|
@@ -33,10 +38,6 @@ const generateUUIDandPropagateKeys = (target, data, event) => {
|
|
|
33
38
|
generateUUIDandPropagateKeys(elements[element]._target, sub, 'CREATE')
|
|
34
39
|
}
|
|
35
40
|
}
|
|
36
|
-
|
|
37
|
-
propagateForeignKeys(element, data, elements[element]._foreignKeys, elements[element].isComposition, {
|
|
38
|
-
deleteAssocs: true,
|
|
39
|
-
})
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
}
|
package/lib/infer/index.js
CHANGED
|
@@ -451,7 +451,7 @@ function infer(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
451
451
|
*/
|
|
452
452
|
|
|
453
453
|
function inferQueryElement(column, insertIntoQueryElements = true, $baseLink = null, context) {
|
|
454
|
-
const { inExists, inExpr, inNestedProjection, inCalcElement } = context || {}
|
|
454
|
+
const { inExists, inExpr, inNestedProjection, inCalcElement, baseColumn } = context || {}
|
|
455
455
|
if (column.param) return // parameter references are only resolved into values on execution e.g. :val, :1 or ?
|
|
456
456
|
if (column.args) column.args.forEach(arg => inferQueryElement(arg, false, $baseLink, context)) // e.g. function in expression
|
|
457
457
|
if (column.list) column.list.forEach(arg => inferQueryElement(arg, false, $baseLink, context))
|
|
@@ -493,11 +493,11 @@ function infer(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
493
493
|
const elements = definition.elements || definition._target?.elements
|
|
494
494
|
if (elements && id in elements) {
|
|
495
495
|
const element = elements[id]
|
|
496
|
-
if (!
|
|
496
|
+
if (!inNestedProjection && !inCalcElement && element.target) {
|
|
497
497
|
// only fk access in infix filter
|
|
498
498
|
const nextStep = column.ref[1]?.id || column.ref[1]
|
|
499
499
|
// no unmanaged assoc in infix filter path
|
|
500
|
-
if (element.on)
|
|
500
|
+
if (!inExists && element.on)
|
|
501
501
|
throw new Error(
|
|
502
502
|
`"${element.name}" in path "${column.ref
|
|
503
503
|
.map(idOnly)
|
|
@@ -577,7 +577,7 @@ function infer(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
577
577
|
}
|
|
578
578
|
|
|
579
579
|
if (step.where) {
|
|
580
|
-
const danglingFilter = !(column.ref[i + 1] || column.expand || inExists)
|
|
580
|
+
const danglingFilter = !(column.ref[i + 1] || column.expand || column.inline || inExists)
|
|
581
581
|
if (!column.$refLinks[i].definition.target || danglingFilter)
|
|
582
582
|
throw new Error(/A filter can only be provided when navigating along associations/)
|
|
583
583
|
if (!column.expand) Object.defineProperty(column, 'isJoinRelevant', { value: true })
|
|
@@ -657,16 +657,22 @@ function infer(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
657
657
|
const leafArt = column.$refLinks[column.$refLinks.length - 1].definition
|
|
658
658
|
const virtual = (leafArt.virtual || !isPersisted) && !inExpr
|
|
659
659
|
// check if we need to merge the column `ref` into the join tree of the query
|
|
660
|
-
if (!inExists && !virtual && !inCalcElement
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
660
|
+
if (!inExists && !virtual && !inCalcElement) {
|
|
661
|
+
// for a ref inside an `inline` we need to consider the column `ref` which has the `inline` prop
|
|
662
|
+
const colWithBase = baseColumn
|
|
663
|
+
? { ref: [...baseColumn.ref, ...column.ref], $refLinks: [...baseColumn.$refLinks, ...column.$refLinks] }
|
|
664
|
+
: column
|
|
665
|
+
if (isColumnJoinRelevant(colWithBase)) {
|
|
666
|
+
if (originalQuery.UPDATE)
|
|
667
|
+
throw cds.error(
|
|
668
|
+
'Path expressions for UPDATE statements are not supported. Use “where exists” with infix filters instead.',
|
|
669
|
+
)
|
|
670
|
+
Object.defineProperty(column, 'isJoinRelevant', { value: true })
|
|
671
|
+
joinTree.mergeColumn(colWithBase, originalQuery.outerQueries)
|
|
672
|
+
}
|
|
667
673
|
}
|
|
668
674
|
if (leafArt.value && !leafArt.value.stored) {
|
|
669
|
-
resolveCalculatedElement(
|
|
675
|
+
resolveCalculatedElement(column, $baseLink, baseColumn)
|
|
670
676
|
}
|
|
671
677
|
|
|
672
678
|
/**
|
|
@@ -689,7 +695,7 @@ function infer(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
689
695
|
const $leafLink = $refLinks[$refLinks.length - 1]
|
|
690
696
|
let elements = {}
|
|
691
697
|
inline.forEach(inlineCol => {
|
|
692
|
-
inferQueryElement(inlineCol, false, $leafLink, { inExpr: true, inNestedProjection: true })
|
|
698
|
+
inferQueryElement(inlineCol, false, $leafLink, { inExpr: true, inNestedProjection: true, baseColumn: col })
|
|
693
699
|
if (inlineCol === '*') {
|
|
694
700
|
const wildCardElements = {}
|
|
695
701
|
// either the `.elements´ of the struct or the `.elements` of the assoc target
|
|
@@ -795,17 +801,27 @@ function infer(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
795
801
|
throw new Error(err)
|
|
796
802
|
}
|
|
797
803
|
}
|
|
798
|
-
function resolveCalculatedElement(
|
|
804
|
+
function resolveCalculatedElement(column, baseLink, baseColumn) {
|
|
805
|
+
const calcElement = column.$refLinks?.[column.$refLinks.length - 1].definition || column
|
|
799
806
|
if (alreadySeenCalcElements.has(calcElement)) return
|
|
800
807
|
else alreadySeenCalcElements.add(calcElement)
|
|
801
808
|
const { ref, xpr, func } = calcElement.value
|
|
802
809
|
if (ref || xpr) {
|
|
803
|
-
|
|
810
|
+
baseLink = baseLink || { definition: calcElement.parent, target: calcElement.parent }
|
|
811
|
+
attachRefLinksToArg(calcElement.value, baseLink, true)
|
|
812
|
+
const basePath = { $refLinks: [], ref: [] }
|
|
813
|
+
if (baseColumn) {
|
|
814
|
+
basePath.$refLinks.push(...baseColumn.$refLinks)
|
|
815
|
+
basePath.ref.push(...baseColumn.ref)
|
|
816
|
+
}
|
|
804
817
|
// column is now fully linked, now we need to find out if we need to merge it into the join tree
|
|
805
818
|
// for that, we calculate all paths from a calc element and merge them into the join tree
|
|
806
|
-
mergePathsIntoJoinTree(calcElement.value)
|
|
819
|
+
mergePathsIntoJoinTree(calcElement.value, basePath)
|
|
807
820
|
}
|
|
808
|
-
if (func)
|
|
821
|
+
if (func)
|
|
822
|
+
calcElement.value.args?.forEach(arg =>
|
|
823
|
+
inferQueryElement(arg, false, { definition: calcElement.parent, target: calcElement.parent }),
|
|
824
|
+
) // {func}.args are optional
|
|
809
825
|
function mergePathsIntoJoinTree(e, basePath = null) {
|
|
810
826
|
basePath = basePath || { $refLinks: [], ref: [] }
|
|
811
827
|
if (e.ref) {
|
|
@@ -841,8 +857,14 @@ function infer(originalQuery, model = cds.context?.model || cds.model) {
|
|
|
841
857
|
function mergePathIfNecessary(p, step) {
|
|
842
858
|
const calcElementIsJoinRelevant = isColumnJoinRelevant(p)
|
|
843
859
|
if (calcElementIsJoinRelevant) {
|
|
844
|
-
if (!calcElement.value.isColumnJoinRelevant)
|
|
845
|
-
|
|
860
|
+
if (!calcElement.value.isColumnJoinRelevant)
|
|
861
|
+
Object.defineProperty(step, 'isJoinRelevant', { value: true, writable: true })
|
|
862
|
+
joinTree.mergeColumn(p, originalQuery.outerQueries)
|
|
863
|
+
} else {
|
|
864
|
+
// we need to explicitly set the value to false in this case,
|
|
865
|
+
// e.g. `SELECT from booksCalc.Books { ID, author.{name }, author {name } }`
|
|
866
|
+
// --> for the inline column, the name is join relevant, while for the expand, it is not
|
|
867
|
+
Object.defineProperty(step, 'isJoinRelevant', { value: false, writable: true })
|
|
846
868
|
}
|
|
847
869
|
}
|
|
848
870
|
}
|
package/lib/infer/join-tree.js
CHANGED
|
@@ -154,7 +154,7 @@ class JoinTree {
|
|
|
154
154
|
* @param {object} col - The column object to be merged into the existing join tree. This object should have the properties $refLinks and ref.
|
|
155
155
|
* @returns {boolean} - Always returns true, indicating the column has been successfully merged into the join tree.
|
|
156
156
|
*/
|
|
157
|
-
mergeColumn(col) {
|
|
157
|
+
mergeColumn(col, outerQueries = null) {
|
|
158
158
|
if (this.isInitial) this.isInitial = false
|
|
159
159
|
const head = col.$refLinks[0]
|
|
160
160
|
let node = this._roots.get(head.alias)
|
|
@@ -196,7 +196,7 @@ class JoinTree {
|
|
|
196
196
|
} else {
|
|
197
197
|
child.$refLink.onlyForeignKeyAccess = true
|
|
198
198
|
}
|
|
199
|
-
child.$refLink.alias = this.addNextAvailableTableAlias($refLink.alias)
|
|
199
|
+
child.$refLink.alias = this.addNextAvailableTableAlias($refLink.alias, outerQueries)
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
const foreignKeys = node.$refLink?.definition.foreignKeys
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js/db-service",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "CDS base database service",
|
|
5
5
|
"homepage": "https://github.com/cap-js/cds-dbs/tree/main/db-service#cds-base-database-service",
|
|
6
6
|
"repository": {
|
|
@@ -26,10 +26,7 @@
|
|
|
26
26
|
"npm": ">=8"
|
|
27
27
|
},
|
|
28
28
|
"scripts": {
|
|
29
|
-
"
|
|
30
|
-
"test": "npm run build && npx jest --silent",
|
|
31
|
-
"build": "tsc && find lib/ -type f -name '*.d.ts' -exec cp '{}' 'dist/{}' ';' && cp ts.eslintrc.cjs dist/.eslintrc.cjs && npx eslint ./dist --ext .d.ts",
|
|
32
|
-
"lint": "npx eslint . && npx prettier --check . "
|
|
29
|
+
"test": "jest --silent"
|
|
33
30
|
},
|
|
34
31
|
"peerDependencies": {
|
|
35
32
|
"@sap/cds": ">=7.1.1"
|