Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: deep delete for views #496

Merged
merged 63 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from 54 commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
c6eceb7
test for deep delete fix
larslutz96 Jan 30, 2024
de82ca1
Update SQLService.js
larslutz96 Jan 30, 2024
50d6530
Update SQLService.js
larslutz96 Jan 30, 2024
2f23c30
Update DELETE.test.js
larslutz96 Jan 31, 2024
dc6a9fa
Merge branch 'main' into fix-deep-delete
larslutz96 Jan 31, 2024
fe1b0e8
remove unused lines
larslutz96 Jan 31, 2024
af76a17
Merge branch 'main' into fix-deep-delete
larslutz96 Feb 1, 2024
8d221a6
update test & fix subselect
larslutz96 Feb 2, 2024
60b0088
Merge branch 'fix-deep-delete' of https://github.com/cap-js/cds-dbs i…
larslutz96 Feb 2, 2024
22d9885
Merge branch 'main' into fix-deep-delete
larslutz96 Feb 2, 2024
176e4e3
Merge branch 'main' into fix-deep-delete
larslutz96 Feb 2, 2024
7045ba7
Merge branch 'main' into fix-deep-delete
larslutz96 Feb 5, 2024
cba0175
remove servicem & update test
larslutz96 Feb 6, 2024
3226782
Update SQLService.js
larslutz96 Feb 6, 2024
ff8ba58
Merge branch 'main' into fix-deep-delete
larslutz96 Feb 6, 2024
c72041a
Update DELETE.test.js
larslutz96 Feb 6, 2024
0bde6fc
Update SQLService.js
larslutz96 Feb 6, 2024
944064a
Update SQLService.js
larslutz96 Feb 7, 2024
b516094
Merge branch 'main' into fix-deep-delete
larslutz96 Feb 7, 2024
3b8e822
fix for entitys with no keys
larslutz96 Feb 7, 2024
8c6466f
update testsuite
larslutz96 Feb 12, 2024
ab99535
Update DELETE.test.js
larslutz96 Feb 12, 2024
20e02d7
Merge branch 'main' into fix-deep-delete
larslutz96 Feb 12, 2024
e064521
Merge branch 'main' into fix-deep-delete
larslutz96 Feb 14, 2024
7a2a128
fix: unique alias for scoped subqueries
patricebender Feb 14, 2024
c6a9216
add test
patricebender Feb 14, 2024
a94ddab
Merge remote-tracking branch 'origin/fix-subquery' into fix-deep-delete
larslutz96 Feb 14, 2024
b7a1280
Merge branch 'main' into fix-deep-delete
patricebender Feb 16, 2024
f2a10f0
Merge branch 'main' into fix-deep-delete
larslutz96 Feb 22, 2024
ba65014
Merge branch 'main' into fix-deep-delete
larslutz96 Feb 27, 2024
0226235
delete test
larslutz96 Feb 27, 2024
11b0266
Update DELETE.test.js
larslutz96 Feb 27, 2024
9d7f99d
Merge branch 'main' into fix-deep-delete
larslutz96 Feb 27, 2024
05ca371
Merge branch 'main' into fix-deep-delete
larslutz96 Feb 28, 2024
4b1ef4b
check virtual instead of IsActiveEntity
larslutz96 Feb 28, 2024
95ffd6f
Merge branch 'main' into fix-deep-delete
larslutz96 Feb 28, 2024
ebd0800
remove object.keys
larslutz96 Feb 28, 2024
1b5c740
unify physical elements check
I543501 Feb 29, 2024
4260741
Merge branch 'main' into fix-deep-delete
larslutz96 Feb 29, 2024
225cfd5
update test: child with were clause
I543501 Mar 4, 2024
71796a3
Merge branch 'main' into fix-deep-delete
larslutz96 Mar 5, 2024
dc78158
Merge branch 'main' of https://github.com/cap-js/cds-dbs into fix-dee…
BobdenOs Mar 5, 2024
ba505f8
Merge branch 'fix-deep-delete' of https://github.com/cap-js/cds-dbs i…
BobdenOs Mar 5, 2024
9d1ffde
Remove srv.exists overwrite
BobdenOs Mar 5, 2024
f8d4d1d
Update SQLService.js
I543501 Mar 5, 2024
68bb428
Merge branch 'main' into fix-deep-delete
larslutz96 Mar 5, 2024
8f27249
Merge branch 'main' into fix-deep-delete
larslutz96 Mar 6, 2024
5546468
Update SQLService.js
larslutz96 Mar 6, 2024
6e780cf
Update SQLService.js
larslutz96 Mar 6, 2024
d0e2c37
Merge branch 'main' into fix-deep-delete
BobdenOs Mar 6, 2024
4de456b
Merge branch 'main' into fix-deep-delete
larslutz96 Mar 28, 2024
d5a14db
Merge branch 'main' into fix-deep-delete
larslutz96 Apr 9, 2024
67dc1c3
Merge branch 'main' into fix-deep-delete
larslutz96 Apr 12, 2024
28a87d5
Merge branch 'main' into fix-deep-delete
larslutz96 Apr 18, 2024
1043198
Update SQLService.js
larslutz96 May 3, 2024
08f152c
Merge branch 'main' into fix-deep-delete
larslutz96 May 3, 2024
202b532
Update index.cds
larslutz96 May 3, 2024
0755d5d
Merge branch 'main' into fix-deep-delete
larslutz96 May 27, 2024
ca2d2ca
Merge remote-tracking branch 'origin/main' into fix-deep-delete
johannes-vogel Oct 24, 2024
28f6d0a
fix merge
johannes-vogel Oct 24, 2024
aa11d43
use ql
johannes-vogel Oct 24, 2024
7920fb6
better test using keys
johannes-vogel Oct 24, 2024
4107b76
Merge branch 'main' into fix-deep-delete
johannes-vogel Oct 25, 2024
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
47 changes: 39 additions & 8 deletions db-service/lib/SQLService.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const cds = require('@sap/cds/lib'),
DEBUG = cds.debug('sql|db')
const { Readable } = require('stream')
const { resolveView } = require('@sap/cds/libx/_runtime/common/utils/resolveView')
const { resolveView, getDBTable, getTransition } = require('@sap/cds/libx/_runtime/common/utils/resolveView')
const DatabaseService = require('./common/DatabaseService')
const cqn4sql = require('./cqn4sql')

Expand All @@ -25,14 +25,16 @@ class SQLService extends DatabaseService {
this.on(['INSERT', 'UPSERT', 'UPDATE'], require('./deep-queries').onDeep)
if (cds.env.features.db_strict) {
this.before(['INSERT', 'UPSERT', 'UPDATE'], ({ query }) => {
const elements = query.target?.elements; if (!elements) return
const elements = query.target?.elements
if (!elements) return
const kind = query.kind || Object.keys(query)[0]
const operation = query[kind]
if (!operation.columns && !operation.entries && !operation.data) return
const columns =
operation.columns ||
Object.keys(
operation.data || operation.entries?.reduce((acc, obj) => {
operation.data ||
operation.entries?.reduce((acc, obj) => {
return Object.assign(acc, obj)
}, {}),
)
Expand Down Expand Up @@ -115,7 +117,11 @@ class SQLService extends DatabaseService {
*/
async onSELECT({ query, data }) {
if (!query.target) {
try { this.infer(query) } catch (e) { /**/ }
try {
this.infer(query)
} catch (e) {
/**/
}
}
if (query.target && !query.target._unresolved) {
// Will return multiple rows with objects inside
Expand Down Expand Up @@ -210,7 +216,31 @@ class SQLService extends DatabaseService {
// REVISIT: It's not yet 100 % clear under which circumstances we can rely on db constraints
return (super.onDELETE = /* cds.env.features.assert_integrity === 'db' ? this.onSIMPLE : */ deep_delete)
async function deep_delete(/** @type {Request} */ req) {
let { compositions } = req.target
const transitions = getTransition(req.query.target, this, false, req.query.cmd || 'DELETE')
if (transitions.target !== transitions.queryTarget) {
const keys = []
const transitionsTarget = transitions.queryTarget.keys || transitions.queryTarget.elements
for (const key in transitionsTarget) {
const exists = e => e && !e.virtual && !e.value && !e.isAssociation
if (exists(transitionsTarget[key])) keys.push(key)
}
const matchedKeys = keys.filter(key => transitions.mapping.has(key)).map(k => ({ ref: [k] }))
const query = DELETE.from({
ref: [
{
id: transitions.target.name,
where: [
{ list: matchedKeys.map(k => transitions.mapping.get(k.ref[0])) },
'in',
SELECT.from(req.query.DELETE.from).columns(matchedKeys).where(req.query.DELETE.where),
],
},
],
})
return this.onDELETE({ query })
}
const table = getDBTable(req.query.target)
const { compositions } = table
if (compositions) {
// Transform CQL`DELETE from Foo[p1] WHERE p2` into CQL`DELETE from Foo[p1 and p2]`
let { from, where } = req.query.DELETE
Expand All @@ -222,11 +252,11 @@ class SQLService extends DatabaseService {
}
// Process child compositions depth-first
let { depth = 0, visited = [] } = req
visited.push(req.target.name)
visited.push(req.query.target.name)
await Promise.all(
Object.values(compositions).map(c => {
if (c._target['@cds.persistence.skip'] === true) return
if (c._target === req.target) {
if (c._target === req.query.target) {
// the Genre.children case
if (++depth > (c['@depth'] || 3)) return
} else if (visited.includes(c._target.name))
Expand All @@ -237,7 +267,8 @@ 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] })
return this.onDELETE({ query, depth, visited: [...visited], target: c._target })
query.target = c._target
return this.onDELETE({ query, depth, visited: [...visited] })
}),
)
}
Expand Down
72 changes: 72 additions & 0 deletions test/compliance/DELETE.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,77 @@
const cds = require('../../test/cds.js')
const complex = cds.utils.path.resolve(__dirname, '../compliance/resources')
const Root = 'complex.Root'
const Child = 'complex.Child'
const GrandChild = 'complex.GrandChild'
const RootPWithKeys = 'complex.RootPWithKeys'
const ChildPWithWhere = 'complex.ChildPWithWhere'

describe('DELETE', () => {
const { expect } = cds.test(complex)
describe('from', () => {
describe('deep', () => {
beforeEach(async () => {
const inserts = [
INSERT.into(Root).entries([
{
ID: 5,
fooRoot: 'bar',
children: [
{
ID: 6,
fooChild: 'bar',
children: [
{
ID: 8,
fooGrandChild: 'bar',
},
],
},
{
ID: 7,
fooChild: 'foo',
children: [
{
ID: 9,
fooGrandChild: 'foo',
},
],
},
],
},
]),
]
const insertsResp = await cds.run(inserts)
expect(insertsResp[0].affectedRows).to.be.eq(1)
})

test('on root with keys', async () => {
const deepDelete = await cds.run(DELETE.from(RootPWithKeys).where({ ID: 5 }))
expect(deepDelete).to.be.eq(1)

const root = await cds.run(SELECT.one.from(Root).where({ ID: 5 }))
expect(root).to.not.exist

const child = await cds.run(SELECT.from(Child).where({ ID: 6, or: { ID: 7 } }))
expect(child.length).to.be.eq(0)

const grandchild = await cds.run(SELECT.from(GrandChild).where({ ID: 8, or: { ID: 9 } }))
expect(grandchild.length).to.be.eq(0)
})

test('on child with where', async () => {
// only delete entries where fooChild = 'bar'
const deepDelete = await cds.run(DELETE.from(ChildPWithWhere))
expect(deepDelete).to.be.eq(1)

const child = await cds.run(SELECT.from(Child).where({ ID: 6, or: { ID: 7 } }))
expect(child[0].ID).to.be.eq(7)

const grandchild = await cds.run(SELECT.from(GrandChild).where({ ID: 8, or: { ID: 9 } }))
expect(grandchild[0].ID).to.be.eq(9)
})
})

test.skip('missing', () => {
throw new Error('not supported')
})
Expand Down
50 changes: 44 additions & 6 deletions test/compliance/resources/db/complex/index.cds
Original file line number Diff line number Diff line change
@@ -1,13 +1,51 @@
namespace complex;

entity Books {
key ID : Integer;
title : String(111);
author : Association to Authors;
key ID : Integer;
title : String(111);
author : Association to Authors;
}

entity Authors {
key ID : Integer;
name : String(111);
books : Association to many Books on books.author = $self;
key ID : Integer;
name : String(111);
books : Association to many Books
on books.author = $self;
}

entity Root {
key ID : Integer;
fooRoot : String;
children : Composition of many Child
on children.parent = $self;
}

entity Child {
key ID : Integer;
fooChild : String;
parent : Association to one Root;
children : Composition of many GrandChild
on children.parent = $self
}

entity GrandChild {
key ID : Integer;
fooGrandChild : String;
parent : Association to one Child;
}

entity RootPWithKeys as
projection on Root {
key ID,
fooRoot,
children
}

entity ChildP as
projection on Child {
key ID,
fooChild,
parent
}

entity ChildPWithWhere as projection on Child where fooChild = 'bar'
Loading