Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
0739267
Update delete.test.js
larsplessing May 27, 2025
bee671f
experimental-deep-delete-hierarchies
David-Kunz May 27, 2025
b3ee20f
Merge remote-tracking branch 'origin/experiment-deep-delete-hierarchi…
larsplessing May 28, 2025
840af99
impl
larsplessing May 28, 2025
5682c42
Merge branch 'main' into feat-deep-delete-for-hierachies
larsplessing May 28, 2025
420ec75
.
larsplessing May 28, 2025
51bbb48
.
I543501 May 28, 2025
2f7ff6b
.
larsplessing May 28, 2025
8af388c
Merge branch 'main' into feat-deep-delete-for-hierachies
larsplessing Jun 4, 2025
63365a0
Merge branch 'main' into feat-deep-delete-for-hierachies
larsplessing Jun 18, 2025
9970b03
Merge branch 'main' into feat-deep-delete-for-hierachies
larsplessing Jul 2, 2025
4731904
fix(hierarchy): only modify where if existent
David-Kunz Jul 3, 2025
af498d2
.
David-Kunz Jul 3, 2025
25d11d2
.
David-Kunz Jul 3, 2025
96cdef2
.
David-Kunz Jul 3, 2025
d2e224d
.
larsplessing Jul 4, 2025
242588b
Merge branch 'fix-hierarchy-where' into feat-deep-delete-for-hierachies
larsplessing Jul 4, 2025
da41da3
.
larsplessing Jul 9, 2025
a72cc30
.
larsplessing Jul 10, 2025
da293a2
Merge branch 'main' into feat-deep-delete-for-hierachies
larsplessing Jul 10, 2025
da4f512
from recursive
larsplessing Jul 11, 2025
22c9d29
use map
larsplessing Jul 11, 2025
702157f
Merge branch 'main' into feat-deep-delete-for-hierachies
larsplessing Jul 11, 2025
bcbfc7e
Update DELETE.test.js
larsplessing Jul 11, 2025
2916ad8
Update SQLService.js
larsplessing Jul 11, 2025
abd2819
Update DELETE.test.js
larsplessing Jul 11, 2025
4d8d26b
recursive compositions of one
larsplessing Jul 11, 2025
5f4006c
Update SQLService.js
larsplessing Jul 11, 2025
96c91ef
use associations.cds
larsplessing Jul 14, 2025
f467dbc
Merge branch 'main' into feat-deep-delete-for-hierachies
larsplessing Jul 14, 2025
b09ee99
use Root instead of Books
larsplessing Jul 14, 2025
8a404af
Merge branch 'feat-deep-delete-for-hierachies' of https://github.com/…
larsplessing Jul 14, 2025
1d63e88
Merge branch 'main' into feat-deep-delete-for-hierachies
larsplessing Jul 15, 2025
bc81a78
Merge branch 'main' into feat-deep-delete-for-hierachies
larsplessing Jul 16, 2025
bae98ab
.
larsplessing Jul 16, 2025
c6339fe
rec to one
larsplessing Jul 18, 2025
5df37e6
Update SQLService.js
larsplessing Jul 18, 2025
1100a46
Update SQLService.js
larsplessing Jul 18, 2025
a6aac38
use backwards
larsplessing Jul 28, 2025
5ad43dc
Merge branch 'main' into feat-deep-delete-non-recursive
larsplessing Jul 28, 2025
703038c
Update cqn2sql.js
larsplessing Jul 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 9 additions & 24 deletions db-service/lib/SQLService.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,9 +257,7 @@ class SQLService extends DatabaseService {
const table = getDBTable(req.target)
const { compositions } = table

// Check if we are in the hierarchy case
let isHierarchy = req.target.elements?.LimitedDescendantCount
const recursiveBacklinks = []
const recursiveComps = []

if (compositions) {
// Transform CQL`DELETE from Foo[p1] WHERE p2` into CQL`DELETE from Foo[p1 and p2]`
Expand All @@ -271,28 +269,14 @@ class SQLService extends DatabaseService {
from = { ref: [...from.ref.slice(0, -1), { id: last, where }] }
}
// Process child compositions depth-first
let { depth = 0, visited = [] } = req
let { visited = [] } = req
visited.push(req.target.name)
await Promise.all(
Object.values(compositions).map(c => {
if (c._target['@cds.persistence.skip'] === true) return
if (c._target === req.target) {
// deep delete for hierarchies
if (isHierarchy) {
function _getBacklinkName(on) {
const i = on.findIndex(e => e.ref && e.ref[0] === '$self')
if (i === -1) return
let ref
if (on[i + 1] && on[i + 1] === '=') ref = on[i + 2].ref
if (on[i - 1] && on[i - 1] === '=') ref = on[i - 2].ref
return ref && ref[ref.length - 1]
}
const backlinkName = _getBacklinkName(c.on)
recursiveBacklinks.push(backlinkName)
return
}
// the Genre.children case
if (++depth > (c['@depth'] || 3)) return
recursiveComps.push(c.name)
return
} else if (visited.includes(c._target.name))
throw new Error(
`Transitive circular composition detected: \n\n` +
Expand All @@ -302,10 +286,10 @@ class SQLService extends DatabaseService {
// Prepare and run deep query, à la CQL`DELETE from Foo[pred]:comp1.comp2...`
const query = DELETE.from({ ref: [...from.ref, c.name] })
query._target = c._target
return this.onDELETE({ query, depth, visited: [...visited], target: c._target })
return this.onDELETE({ query, visited: [...visited], target: c._target })
}),
)
if (recursiveBacklinks.length) {
if (recursiveComps.length) {
let key
// For hierarchies, only a single key is supported
for (const _key in req.target.keys) {
Expand All @@ -314,10 +298,11 @@ class SQLService extends DatabaseService {
break
}
const _where = []
for (const backlink of recursiveBacklinks) {
for (const comp of recursiveComps) {
const _recursiveQuery = SELECT.from(table).columns(key)
_recursiveQuery.SELECT.recurse = {
ref: [backlink],
ref: [comp],
backward: false,
where: from.ref[0].where,
}
_where.push({ ref: [key] }, 'in', _recursiveQuery, 'or')
Expand Down
4 changes: 3 additions & 1 deletion db-service/lib/cql-functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@ const HANAFunctions = {
// Ensure that the orderBy column are exposed by the source for hierarchy sorting
const orderBy = args.xpr.find((_, i, arr) => /ORDER/i.test(arr[i - 2]) && /BY/i.test(arr[i - 1]))

const startWhere = args.xpr.find((_, i, arr) => /START/i.test(arr[i - 2]) && /WHERE/i.test(arr[i - 1]))

const passThroughColumns = src.SELECT.columns.map(c => ({ ref: ['Source', this.column_name(c)] }))
src.as = 'H' + (uniqueCounter++)
src = this.expr(this.with(src))
Expand All @@ -213,7 +215,7 @@ SELECT
1 as HIERARCHY_LEVEL,
NODE_ID as HIERARCHY_ROOT_ID
FROM ${src} AS Source
WHERE parent_ID IS NULL
WHERE ${startWhere ? this.expr(startWhere) : 'parent_ID IS NULL'}
UNION ALL
SELECT
Parent.HIERARCHY_LEVEL + 1,
Expand Down
10 changes: 5 additions & 5 deletions db-service/lib/cqn2sql.js
Original file line number Diff line number Diff line change
Expand Up @@ -386,11 +386,11 @@ class CQN2SQLRenderer {
const alias = stableFrom.as
const source = () => {
return ({
func: 'HIERARCHY',
args: [{ xpr: ['SOURCE', { SELECT: { columns: columnsIn, from: stableFrom } }, ...(orderBy ? ['SIBLING', 'ORDER', 'BY', `${this.orderBy(orderBy)}`] : [])] }],
as: alias
})
}
func: 'HIERARCHY',
args: [{ xpr: ['SOURCE', { SELECT: { columns: columnsIn, from: stableFrom } }, ...(orderBy ? ['SIBLING', 'ORDER', 'BY', `${this.orderBy(orderBy)}`] : [])] }],
as: alias
})
}

const expandedByNr = { list: [] } // DistanceTo(...,null)
const expandedByOne = { list: [] } // DistanceTo(...,1)
Expand Down
46 changes: 41 additions & 5 deletions test/compliance/DELETE.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,47 @@ const recusiveData = [
],
},
],
recursiveToOne: {
ID: 103,
fooRoot: 'Recursive to one Horror',
children: [
{
ID: 1031,
fooChild: 'bar',
children: [
{
ID: 10311,
fooGrandChild: 'bar',
},
],
recursiveToOneChild: {
ID: 1032,
fooChild: 'Recursive to one Horror 2',
},
},
],
recursiveToOne: {
ID: 1033,
fooRoot: 'Recursive to one Horror 3',
recursiveToOne: {
ID: 10331,
fooRoot: 'Recursive to one Horror 3',
recursiveToOne: {
ID: 103311,
fooRoot: 'Recursive to one Horror 3',
recursiveToOne: {
ID: 1033111,
fooRoot: 'Recursive to one Horror 3',
},
},
},
},
},
},
{
ID: 11,
fooRoot: 'Low Horror',
parent_ID: 10,
recParent_ID: 10,
children: [
{
ID: 111,
Expand All @@ -61,7 +97,7 @@ const recusiveData = [
{
ID: 12,
fooRoot: 'Medium Horror',
parent_ID: 11,
recParent_ID: 11,
children: [
{
ID: 121,
Expand All @@ -88,7 +124,7 @@ const recusiveData = [
{
ID: 13,
fooRoot: 'Hard Horror',
parent_ID: 11,
recParent_ID: 11,
children: [
{
ID: 131,
Expand All @@ -115,7 +151,7 @@ const recusiveData = [
{
ID: 14,
fooRoot: 'Very Hard Horror',
parent_ID: 12,
recParent_ID: 12,
children: [
{
ID: 141,
Expand All @@ -142,7 +178,7 @@ const recusiveData = [
{
ID: 15,
fooRoot: 'Very Very Hard Horror',
parent_ID: 14,
recParent_ID: 14,
children: [
{
ID: 151,
Expand Down
5 changes: 3 additions & 2 deletions test/compliance/resources/db/complex/associations.cds
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ namespace complex.associations;
entity Root {
key ID : Integer;
fooRoot : String;
parent : Association to Root;
recursiveToOne : Composition of one Root;
recParent : Association to Root;
recursive : Composition of many Root
on recursive.parent = $self;
on recursive.recParent = $self;
children : Composition of many Child
on children.parent = $self;
}
Expand Down
Loading