From c6eceb7f4881dcece1bf083198f65f58a1a54a44 Mon Sep 17 00:00:00 2001 From: I543501 <56645452+larslutz96@users.noreply.github.com> Date: Tue, 30 Jan 2024 10:07:45 +0100 Subject: [PATCH 01/22] test for deep delete fix --- db-service/lib/SQLService.js | 10 +++- test/compliance/DELETE.test.js | 52 +++++++++++++++++++ .../compliance/resources/db/complex/index.cds | 17 ++++++ test/compliance/resources/srv/index.cds | 15 ++++++ 4 files changed, 92 insertions(+), 2 deletions(-) diff --git a/db-service/lib/SQLService.js b/db-service/lib/SQLService.js index 462dbc6bb..7ae8986f8 100644 --- a/db-service/lib/SQLService.js +++ b/db-service/lib/SQLService.js @@ -1,9 +1,10 @@ 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 } = require('@sap/cds/libx/_runtime/common/utils/resolveView') const DatabaseService = require('./common/DatabaseService') const cqn4sql = require('./cqn4sql') +const { getEnabledCategories } = require('trace_events') const BINARY_TYPES = { 'cds.Binary': 1, @@ -172,7 +173,12 @@ 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 + if(true){ // first + const cqn = this.cqn4sql(req.query, req.data) + req.query.DELETE.from.ref[0] = { id: getDBTable(req.target).name, where: cqn.DELETE.where } + } + const table = getDBTable(req.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 diff --git a/test/compliance/DELETE.test.js b/test/compliance/DELETE.test.js index 8c659a6f5..38980b384 100644 --- a/test/compliance/DELETE.test.js +++ b/test/compliance/DELETE.test.js @@ -1,5 +1,57 @@ +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' + describe('DELETE', () => { + const { expect, GET, DELETE } = cds.test(complex) describe('from', () => { + test('deep delete', async () => { + const inserts = [ + INSERT.into(Root).entries([ + { + ID: 5, + children: [ + { + ID: 6, + children: [ + { + ID: 7, + }, + ], + }, + ], + }, + ]), + ] + await cds.run(inserts) + const root = await GET('/comp/RootP') + expect(root.status).to.be.eq(200) + expect(root.data.value.length).to.be.eq(1) + + const child = await GET('/comp/ChildP') + expect(child.status).to.be.eq(200) + expect(child.data.value.length).to.be.eq(1) + + const grandchild = await cds.run(SELECT.from(GrandChild).where({ ID: 7 })) + expect(grandchild.length).to.be.eq(1) + + const deepDelete = await DELETE('/comp/RootP(5)') + expect(deepDelete.status).to.be.eq(204) + + const root2 = await GET('/comp/RootP') + expect(root2.status).to.be.eq(200) + expect(root2.data.value.length).to.be.eq(0) + + const child2 = await GET('/comp/ChildP') + expect(child2.status).to.be.eq(200) + expect(child2.data.value.length).to.be.eq(0) + + const grandchild2 = await cds.run(SELECT.from(GrandChild)) + expect(grandchild2.length).to.be.eq(0) + }) + test.skip('missing', () => { throw new Error('not supported') }) diff --git a/test/compliance/resources/db/complex/index.cds b/test/compliance/resources/db/complex/index.cds index 2f24a820d..e71c462c2 100644 --- a/test/compliance/resources/db/complex/index.cds +++ b/test/compliance/resources/db/complex/index.cds @@ -11,3 +11,20 @@ entity Authors { name : String(111); books : Association to many Books on books.author = $self; } + + +entity Root { + key ID : Integer; + children : Composition of many Child on children.parent = $self; +} + +entity Child { + key ID: Integer; + parent: Association to one Root; + children: Composition of many GrandChild on children.parent = $self +} + +entity GrandChild { + key ID: Integer; + parent: Association to one Child; +} diff --git a/test/compliance/resources/srv/index.cds b/test/compliance/resources/srv/index.cds index e69de29bb..40079bebb 100644 --- a/test/compliance/resources/srv/index.cds +++ b/test/compliance/resources/srv/index.cds @@ -0,0 +1,15 @@ +using {complex as my} from '../db/complex'; + +service ComplexService @(path:'/comp') { + entity RootP as + projection on my.Root { + key ID, + children + }; + + entity ChildP as + projection on my.Child { + key ID, + parent + } +} From de82ca1bff33e67ea27f4ba5c03b1309b4e736d3 Mon Sep 17 00:00:00 2001 From: I543501 <56645452+larslutz96@users.noreply.github.com> Date: Tue, 30 Jan 2024 11:58:48 +0100 Subject: [PATCH 02/22] Update SQLService.js --- db-service/lib/SQLService.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/db-service/lib/SQLService.js b/db-service/lib/SQLService.js index 7ae8986f8..19fac1b55 100644 --- a/db-service/lib/SQLService.js +++ b/db-service/lib/SQLService.js @@ -1,7 +1,7 @@ const cds = require('@sap/cds/lib'), DEBUG = cds.debug('sql|db') const { Readable } = require('stream') -const { resolveView, getDBTable } = 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') const { getEnabledCategories } = require('trace_events') @@ -173,9 +173,15 @@ 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) { - if(true){ // first - const cqn = this.cqn4sql(req.query, req.data) - req.query.DELETE.from.ref[0] = { id: getDBTable(req.target).name, where: cqn.DELETE.where } + const transitions = getTransition(req.target, this) + if (transitions.target !== transitions.queryTarget) { + const query = DELETE.from({ ref: [ + { + id: transitions.target.name, + where: [{list:Object.keys(transitions.target.keys || {}).map(k => ({ref:[k]}))},'in',SELECT.from(DELETE.from).where(DELETE.where)] + } + ] }) + return this.onDELETE({query, target: query.target}) } const table = getDBTable(req.target) const {compositions} = table From 50d6530e94b00fa1d0132b29f7ee02b68f371656 Mon Sep 17 00:00:00 2001 From: I543501 <56645452+larslutz96@users.noreply.github.com> Date: Tue, 30 Jan 2024 12:14:28 +0100 Subject: [PATCH 03/22] Update SQLService.js --- db-service/lib/SQLService.js | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/db-service/lib/SQLService.js b/db-service/lib/SQLService.js index 19fac1b55..6ad7eb63b 100644 --- a/db-service/lib/SQLService.js +++ b/db-service/lib/SQLService.js @@ -173,17 +173,23 @@ 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) { - const transitions = getTransition(req.target, this) + const transitions = getTransition(req.query.target, this) if (transitions.target !== transitions.queryTarget) { - const query = DELETE.from({ ref: [ - { - id: transitions.target.name, - where: [{list:Object.keys(transitions.target.keys || {}).map(k => ({ref:[k]}))},'in',SELECT.from(DELETE.from).where(DELETE.where)] - } - ] }) - return this.onDELETE({query, target: query.target}) + const query = DELETE.from({ + ref: [ + { + id: transitions.target.name, + where: [ + { list: Object.keys(transitions.target.keys || {}).map(k => ({ ref: [k] })) }, + 'in', + SELECT.from(req.query.DELETE.from).where(req.query.DELETE.where), + ], + }, + ], + }) + return this.onDELETE({ query }) } - const table = getDBTable(req.target) + 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]` @@ -196,11 +202,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)) @@ -211,7 +217,7 @@ 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 }) + return this.onDELETE({ query, depth, visited: [...visited] }) }), ) } From 2f23c302118c18eb80dd856ff8479792a581fd9e Mon Sep 17 00:00:00 2001 From: I543501 <56645452+larslutz96@users.noreply.github.com> Date: Wed, 31 Jan 2024 15:54:34 +0100 Subject: [PATCH 04/22] Update DELETE.test.js --- test/compliance/DELETE.test.js | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/test/compliance/DELETE.test.js b/test/compliance/DELETE.test.js index 38980b384..2e32f5724 100644 --- a/test/compliance/DELETE.test.js +++ b/test/compliance/DELETE.test.js @@ -25,17 +25,8 @@ describe('DELETE', () => { }, ]), ] - await cds.run(inserts) - const root = await GET('/comp/RootP') - expect(root.status).to.be.eq(200) - expect(root.data.value.length).to.be.eq(1) - - const child = await GET('/comp/ChildP') - expect(child.status).to.be.eq(200) - expect(child.data.value.length).to.be.eq(1) - - const grandchild = await cds.run(SELECT.from(GrandChild).where({ ID: 7 })) - expect(grandchild.length).to.be.eq(1) + const insertsResp = await cds.run(inserts) + expect(insertsResp[0].affectedRows).to.be.eq(1) const deepDelete = await DELETE('/comp/RootP(5)') expect(deepDelete.status).to.be.eq(204) From fe1b0e89b14038a11ae73fd1755b36280342eb7e Mon Sep 17 00:00:00 2001 From: I543501 <56645452+larslutz96@users.noreply.github.com> Date: Wed, 31 Jan 2024 15:59:15 +0100 Subject: [PATCH 05/22] remove unused lines --- db-service/lib/SQLService.js | 1 - test/compliance/DELETE.test.js | 1 - 2 files changed, 2 deletions(-) diff --git a/db-service/lib/SQLService.js b/db-service/lib/SQLService.js index 06620d861..e32f38dc2 100644 --- a/db-service/lib/SQLService.js +++ b/db-service/lib/SQLService.js @@ -4,7 +4,6 @@ const { Readable } = require('stream') const { resolveView, getDBTable, getTransition } = require('@sap/cds/libx/_runtime/common/utils/resolveView') const DatabaseService = require('./common/DatabaseService') const cqn4sql = require('./cqn4sql') -const { getEnabledCategories } = require('trace_events') const BINARY_TYPES = { 'cds.Binary': 1, diff --git a/test/compliance/DELETE.test.js b/test/compliance/DELETE.test.js index 2e32f5724..357a64618 100644 --- a/test/compliance/DELETE.test.js +++ b/test/compliance/DELETE.test.js @@ -1,7 +1,6 @@ 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' describe('DELETE', () => { From 8d221a6d3b314c33ffc27668fdc5dd1de4da1f8c Mon Sep 17 00:00:00 2001 From: I543501 <56645452+larslutz96@users.noreply.github.com> Date: Fri, 2 Feb 2024 10:00:34 +0100 Subject: [PATCH 06/22] update test & fix subselect --- db-service/lib/SQLService.js | 4 +++- test/compliance/resources/db/complex/index.cds | 3 +++ test/compliance/resources/srv/index.cds | 6 ++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/db-service/lib/SQLService.js b/db-service/lib/SQLService.js index e32f38dc2..410af8806 100644 --- a/db-service/lib/SQLService.js +++ b/db-service/lib/SQLService.js @@ -194,6 +194,8 @@ class SQLService extends DatabaseService { async function deep_delete(/** @type {Request} */ req) { const transitions = getTransition(req.query.target, this) if (transitions.target !== transitions.queryTarget) { + const targetKeys = transitions.queryTarget.keys + const matchedKeys = Object.keys(targetKeys).filter(key => transitions.mapping.has(key)) const query = DELETE.from({ ref: [ { @@ -201,7 +203,7 @@ class SQLService extends DatabaseService { where: [ { list: Object.keys(transitions.target.keys || {}).map(k => ({ ref: [k] })) }, 'in', - SELECT.from(req.query.DELETE.from).where(req.query.DELETE.where), + SELECT.from(req.query.DELETE.from).columns(matchedKeys).where(req.query.DELETE.where), ], }, ], diff --git a/test/compliance/resources/db/complex/index.cds b/test/compliance/resources/db/complex/index.cds index e71c462c2..5d679031e 100644 --- a/test/compliance/resources/db/complex/index.cds +++ b/test/compliance/resources/db/complex/index.cds @@ -15,16 +15,19 @@ entity Authors { 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; } diff --git a/test/compliance/resources/srv/index.cds b/test/compliance/resources/srv/index.cds index 40079bebb..fd00cb077 100644 --- a/test/compliance/resources/srv/index.cds +++ b/test/compliance/resources/srv/index.cds @@ -4,12 +4,14 @@ service ComplexService @(path:'/comp') { entity RootP as projection on my.Root { key ID, - children + fooRoot, + children }; entity ChildP as projection on my.Child { key ID, - parent + fooChild, + parent } } From cba01752dc613b7683971c9141d919934507e7d6 Mon Sep 17 00:00:00 2001 From: I543501 <56645452+larslutz96@users.noreply.github.com> Date: Tue, 6 Feb 2024 15:33:58 +0100 Subject: [PATCH 07/22] remove servicem & update test --- db-service/lib/SQLService.js | 13 ++++++------ test/compliance/DELETE.test.js | 21 +++++++++++-------- .../compliance/resources/db/complex/index.cds | 20 +++++++++++++++--- test/compliance/resources/srv/index.cds | 17 --------------- 4 files changed, 36 insertions(+), 35 deletions(-) diff --git a/db-service/lib/SQLService.js b/db-service/lib/SQLService.js index 09b7abf96..d0a98f637 100644 --- a/db-service/lib/SQLService.js +++ b/db-service/lib/SQLService.js @@ -194,14 +194,15 @@ class SQLService extends DatabaseService { async function deep_delete(/** @type {Request} */ req) { const transitions = getTransition(req.query.target, this) if (transitions.target !== transitions.queryTarget) { - const targetKeys = transitions.queryTarget.keys - const matchedKeys = Object.keys(targetKeys).filter(key => transitions.mapping.has(key)) + const matchedKeys = Object.keys(transitions.queryTarget.keys) + .filter(key => transitions.mapping.has(key)) + .map(k => ({ ref: [k] })) const query = DELETE.from({ ref: [ { id: transitions.target.name, where: [ - { list: Object.keys(transitions.target.keys || {}).map(k => ({ ref: [k] })) }, + { list: matchedKeys }, 'in', SELECT.from(req.query.DELETE.from).columns(matchedKeys).where(req.query.DELETE.where), ], @@ -211,7 +212,7 @@ class SQLService extends DatabaseService { return this.onDELETE({ query }) } const table = getDBTable(req.query.target) - const {compositions} = table + 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 @@ -233,8 +234,8 @@ class SQLService extends DatabaseService { } else if (visited.includes(c._target.name)) throw new Error( `Transitive circular composition detected: \n\n` + - ` ${visited.join(' > ')} > ${c._target.name} \n\n` + - `These are not supported by deep delete.`, + ` ${visited.join(' > ')} > ${c._target.name} \n\n` + + `These are not supported by deep delete.`, ) // Prepare and run deep query, à la CQL`DELETE from Foo[pred]:comp1.comp2...` const query = DELETE.from({ ref: [...from.ref, c.name] }) diff --git a/test/compliance/DELETE.test.js b/test/compliance/DELETE.test.js index 357a64618..cc6b17910 100644 --- a/test/compliance/DELETE.test.js +++ b/test/compliance/DELETE.test.js @@ -2,9 +2,11 @@ const cds = require('../../test/cds.js') const complex = cds.utils.path.resolve(__dirname, '../compliance/resources') const Root = 'complex.Root' const GrandChild = 'complex.GrandChild' +const RootP = 'complex.RootP' +const ChildP = 'complex.ChildP' describe('DELETE', () => { - const { expect, GET, DELETE } = cds.test(complex) + const { expect } = cds.test(complex) describe('from', () => { test('deep delete', async () => { const inserts = [ @@ -27,16 +29,17 @@ describe('DELETE', () => { const insertsResp = await cds.run(inserts) expect(insertsResp[0].affectedRows).to.be.eq(1) - const deepDelete = await DELETE('/comp/RootP(5)') - expect(deepDelete.status).to.be.eq(204) + //const deepDelete = await DELETE('/comp/RootP(5)') + const deepDelete = await cds.run(DELETE.from(RootP).where({ ID: 5 })) + expect(deepDelete).to.be.eq(1) - const root2 = await GET('/comp/RootP') - expect(root2.status).to.be.eq(200) - expect(root2.data.value.length).to.be.eq(0) + //const root2 = await GET('/comp/RootP') + const root = await cds.run(SELECT.one.from(RootP).where({ ID: 5 })) + expect(root).to.be.eq(undefined) - const child2 = await GET('/comp/ChildP') - expect(child2.status).to.be.eq(200) - expect(child2.data.value.length).to.be.eq(0) + //const child2 = await GET('/comp/ChildP') + const child = await cds.run(SELECT.one.from(ChildP).where({ ID: 6 })) + expect(child).to.be.eq(undefined) const grandchild2 = await cds.run(SELECT.from(GrandChild)) expect(grandchild2.length).to.be.eq(0) diff --git a/test/compliance/resources/db/complex/index.cds b/test/compliance/resources/db/complex/index.cds index 5d679031e..f1cc32999 100644 --- a/test/compliance/resources/db/complex/index.cds +++ b/test/compliance/resources/db/complex/index.cds @@ -27,7 +27,21 @@ entity Child { } entity GrandChild { - key ID: Integer; - fooGrandChild: String; - parent: Association to one Child; + key ID : Integer; + fooGrandChild : String; + parent : Association to one Child; } + +entity RootP as + projection on Root { + key ID, + fooRoot, + children + }; + +entity ChildP as + projection on Child { + key ID, + fooChild, + parent + } diff --git a/test/compliance/resources/srv/index.cds b/test/compliance/resources/srv/index.cds index fd00cb077..e69de29bb 100644 --- a/test/compliance/resources/srv/index.cds +++ b/test/compliance/resources/srv/index.cds @@ -1,17 +0,0 @@ -using {complex as my} from '../db/complex'; - -service ComplexService @(path:'/comp') { - entity RootP as - projection on my.Root { - key ID, - fooRoot, - children - }; - - entity ChildP as - projection on my.Child { - key ID, - fooChild, - parent - } -} From 322678229920c0873d048dd775b83c937a94dfcc Mon Sep 17 00:00:00 2001 From: I543501 <56645452+larslutz96@users.noreply.github.com> Date: Tue, 6 Feb 2024 16:05:15 +0100 Subject: [PATCH 08/22] Update SQLService.js --- db-service/lib/SQLService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db-service/lib/SQLService.js b/db-service/lib/SQLService.js index d0a98f637..4042ccd10 100644 --- a/db-service/lib/SQLService.js +++ b/db-service/lib/SQLService.js @@ -202,7 +202,7 @@ class SQLService extends DatabaseService { { id: transitions.target.name, where: [ - { list: matchedKeys }, + { list: Object.keys(transitions.target.keys || {}).map(k => ({ ref: [k] })) }, 'in', SELECT.from(req.query.DELETE.from).columns(matchedKeys).where(req.query.DELETE.where), ], From c72041a332f4dfce11b9f36adce1a18881c5d491 Mon Sep 17 00:00:00 2001 From: I543501 <56645452+larslutz96@users.noreply.github.com> Date: Tue, 6 Feb 2024 16:30:46 +0100 Subject: [PATCH 09/22] Update DELETE.test.js --- test/compliance/DELETE.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/compliance/DELETE.test.js b/test/compliance/DELETE.test.js index cc6b17910..73dc7d17c 100644 --- a/test/compliance/DELETE.test.js +++ b/test/compliance/DELETE.test.js @@ -35,14 +35,14 @@ describe('DELETE', () => { //const root2 = await GET('/comp/RootP') const root = await cds.run(SELECT.one.from(RootP).where({ ID: 5 })) - expect(root).to.be.eq(undefined) + expect(root).to.not.exist //const child2 = await GET('/comp/ChildP') const child = await cds.run(SELECT.one.from(ChildP).where({ ID: 6 })) - expect(child).to.be.eq(undefined) + expect(child).to.not.exist - const grandchild2 = await cds.run(SELECT.from(GrandChild)) - expect(grandchild2.length).to.be.eq(0) + const grandchild2 = await cds.run(SELECT.one.from(GrandChild).where({ ID: 7 })) + expect(grandchild2).to.not.exist }) test.skip('missing', () => { From 0bde6fc487519666aa302cce8c09ed4a3bb5f882 Mon Sep 17 00:00:00 2001 From: I543501 <56645452+larslutz96@users.noreply.github.com> Date: Tue, 6 Feb 2024 16:51:35 +0100 Subject: [PATCH 10/22] Update SQLService.js --- db-service/lib/SQLService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db-service/lib/SQLService.js b/db-service/lib/SQLService.js index 4042ccd10..4527ab128 100644 --- a/db-service/lib/SQLService.js +++ b/db-service/lib/SQLService.js @@ -202,7 +202,7 @@ class SQLService extends DatabaseService { { id: transitions.target.name, where: [ - { list: Object.keys(transitions.target.keys || {}).map(k => ({ ref: [k] })) }, + { list: matchedKeys.map(k => transitions.mapping.get(k.ref[0])) }, 'in', SELECT.from(req.query.DELETE.from).columns(matchedKeys).where(req.query.DELETE.where), ], From 944064ab8f73ecc5d8c22e1ead75e8d240789071 Mon Sep 17 00:00:00 2001 From: I543501 <56645452+larslutz96@users.noreply.github.com> Date: Wed, 7 Feb 2024 14:37:53 +0100 Subject: [PATCH 11/22] Update SQLService.js --- db-service/lib/SQLService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db-service/lib/SQLService.js b/db-service/lib/SQLService.js index 4527ab128..4042ccd10 100644 --- a/db-service/lib/SQLService.js +++ b/db-service/lib/SQLService.js @@ -202,7 +202,7 @@ class SQLService extends DatabaseService { { id: transitions.target.name, where: [ - { list: matchedKeys.map(k => transitions.mapping.get(k.ref[0])) }, + { list: Object.keys(transitions.target.keys || {}).map(k => ({ ref: [k] })) }, 'in', SELECT.from(req.query.DELETE.from).columns(matchedKeys).where(req.query.DELETE.where), ], From 3b8e822e9c0760d316fc7fe9d507808ef6e6f8f3 Mon Sep 17 00:00:00 2001 From: I543501 <56645452+larslutz96@users.noreply.github.com> Date: Wed, 7 Feb 2024 15:48:06 +0100 Subject: [PATCH 12/22] fix for entitys with no keys --- db-service/lib/SQLService.js | 11 ++- test/compliance/DELETE.test.js | 85 +++++++++++-------- .../compliance/resources/db/complex/index.cds | 40 +++++---- 3 files changed, 83 insertions(+), 53 deletions(-) diff --git a/db-service/lib/SQLService.js b/db-service/lib/SQLService.js index 4042ccd10..13bb13887 100644 --- a/db-service/lib/SQLService.js +++ b/db-service/lib/SQLService.js @@ -194,15 +194,20 @@ class SQLService extends DatabaseService { async function deep_delete(/** @type {Request} */ req) { const transitions = getTransition(req.query.target, this) if (transitions.target !== transitions.queryTarget) { - const matchedKeys = Object.keys(transitions.queryTarget.keys) - .filter(key => transitions.mapping.has(key)) + const elements = transitions.queryTarget.keys + ? Object.keys(transitions.queryTarget.keys) + : Object.keys(transitions.queryTarget.elements).filter( + key => !transitions.queryTarget.elements[key].isAssociation, + ) + const matchedKeys = elements + .filter(key => key !== 'IsActiveEntity' && transitions.mapping.has(key)) .map(k => ({ ref: [k] })) const query = DELETE.from({ ref: [ { id: transitions.target.name, where: [ - { list: Object.keys(transitions.target.keys || {}).map(k => ({ ref: [k] })) }, + { list: matchedKeys.map(k => transitions.mapping.get(k.ref[0])) }, 'in', SELECT.from(req.query.DELETE.from).columns(matchedKeys).where(req.query.DELETE.where), ], diff --git a/test/compliance/DELETE.test.js b/test/compliance/DELETE.test.js index 73dc7d17c..948a666a2 100644 --- a/test/compliance/DELETE.test.js +++ b/test/compliance/DELETE.test.js @@ -2,47 +2,64 @@ const cds = require('../../test/cds.js') const complex = cds.utils.path.resolve(__dirname, '../compliance/resources') const Root = 'complex.Root' const GrandChild = 'complex.GrandChild' -const RootP = 'complex.RootP' +const RootWithKeys = 'complex.RootWithKeys' +const RootNoKeys = 'complex.RootNoKeys' const ChildP = 'complex.ChildP' describe('DELETE', () => { const { expect } = cds.test(complex) describe('from', () => { - test('deep delete', async () => { - const inserts = [ - INSERT.into(Root).entries([ - { - ID: 5, - children: [ - { - ID: 6, - children: [ - { - ID: 7, - }, - ], - }, - ], - }, - ]), - ] - const insertsResp = await cds.run(inserts) - expect(insertsResp[0].affectedRows).to.be.eq(1) + describe('deep', () => { - //const deepDelete = await DELETE('/comp/RootP(5)') - const deepDelete = await cds.run(DELETE.from(RootP).where({ ID: 5 })) - expect(deepDelete).to.be.eq(1) + beforeEach(async () => { + const inserts = [ + INSERT.into(Root).entries([ + { + ID: 5, + children: [ + { + ID: 6, + children: [ + { + ID: 7, + }, + ], + }, + ], + }, + ]), + ] + const insertsResp = await cds.run(inserts) + expect(insertsResp[0].affectedRows).to.be.eq(1) + }) - //const root2 = await GET('/comp/RootP') - const root = await cds.run(SELECT.one.from(RootP).where({ ID: 5 })) - expect(root).to.not.exist - - //const child2 = await GET('/comp/ChildP') - const child = await cds.run(SELECT.one.from(ChildP).where({ ID: 6 })) - expect(child).to.not.exist - - const grandchild2 = await cds.run(SELECT.one.from(GrandChild).where({ ID: 7 })) - expect(grandchild2).to.not.exist + test('on root with keys', async () => { + const deepDelete = await cds.run(DELETE.from(RootWithKeys).where({ ID: 5 })) + expect(deepDelete).to.be.eq(1) + + const root = await cds.run(SELECT.one.from(RootWithKeys).where({ ID: 5 })) + expect(root).to.not.exist + + const child = await cds.run(SELECT.one.from(ChildP).where({ ID: 6 })) + expect(child).to.not.exist + + const grandchild2 = await cds.run(SELECT.one.from(GrandChild).where({ ID: 7 })) + expect(grandchild2).to.not.exist + }) + + test('on root with no keys', async () => { + const deepDelete = await cds.run(DELETE.from(RootNoKeys).where({ ID: 5 })) + expect(deepDelete).to.be.eq(1) + + const root = await cds.run(SELECT.one.from(RootNoKeys).where({ ID: 5 })) + expect(root).to.not.exist + + const child = await cds.run(SELECT.one.from(ChildP).where({ ID: 6 })) + expect(child).to.not.exist + + const grandchild2 = await cds.run(SELECT.one.from(GrandChild).where({ ID: 7 })) + expect(grandchild2).to.not.exist + }) }) test.skip('missing', () => { diff --git a/test/compliance/resources/db/complex/index.cds b/test/compliance/resources/db/complex/index.cds index f1cc32999..5d23c3049 100644 --- a/test/compliance/resources/db/complex/index.cds +++ b/test/compliance/resources/db/complex/index.cds @@ -1,29 +1,31 @@ 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; + 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 + key ID : Integer; + fooChild : String; + parent : Association to one Root; + children : Composition of many GrandChild + on children.parent = $self } entity GrandChild { @@ -32,14 +34,20 @@ entity GrandChild { parent : Association to one Child; } -entity RootP as +entity RootWithKeys as projection on Root { key ID, fooRoot, children }; -entity ChildP as +entity RootNoKeys as + projection on Root { + fooRoot, + children + } + +entity ChildP as projection on Child { key ID, fooChild, From 8c6466f9dd987f1c7aaaed0defba44864beef92d Mon Sep 17 00:00:00 2001 From: I543501 <56645452+larslutz96@users.noreply.github.com> Date: Mon, 12 Feb 2024 14:17:51 +0100 Subject: [PATCH 13/22] update testsuite --- test/compliance/DELETE.test.js | 67 +++++++++++++------ .../compliance/resources/db/complex/index.cds | 8 ++- 2 files changed, 51 insertions(+), 24 deletions(-) diff --git a/test/compliance/DELETE.test.js b/test/compliance/DELETE.test.js index 948a666a2..e1e6eb6c0 100644 --- a/test/compliance/DELETE.test.js +++ b/test/compliance/DELETE.test.js @@ -1,27 +1,40 @@ 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 RootWithKeys = 'complex.RootWithKeys' -const RootNoKeys = 'complex.RootNoKeys' -const ChildP = 'complex.ChildP' +const RootPWithKeys = 'complex.RootPWithKeys' +const RootPNoKeys = 'complex.RootPNoKeys' +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: 7, + ID: 9, + fooGrandChild: 'foo', }, ], }, @@ -34,31 +47,43 @@ describe('DELETE', () => { }) test('on root with keys', async () => { - const deepDelete = await cds.run(DELETE.from(RootWithKeys).where({ ID: 5 })) + 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(RootWithKeys).where({ ID: 5 })) + + const root = await cds.run(SELECT.one.from(Root).where({ ID: 5 })) expect(root).to.not.exist - - const child = await cds.run(SELECT.one.from(ChildP).where({ ID: 6 })) + + const child = await cds.run(SELECT.one.from(Child).where({ ID: 6, or: { ID: 7 } })) expect(child).to.not.exist - - const grandchild2 = await cds.run(SELECT.one.from(GrandChild).where({ ID: 7 })) + + const grandchild2 = await cds.run(SELECT.one.from(GrandChild).where({ ID: 8, or: { ID: 9 } })) expect(grandchild2).to.not.exist }) - + test('on root with no keys', async () => { - const deepDelete = await cds.run(DELETE.from(RootNoKeys).where({ ID: 5 })) + const deepDelete = await cds.run(DELETE.from(RootPNoKeys).where({ fooRoot: 'bar' })) expect(deepDelete).to.be.eq(1) - - const root = await cds.run(SELECT.one.from(RootNoKeys).where({ ID: 5 })) + + const root = await cds.run(SELECT.one.from(Root).where({ ID: 5 })) expect(root).to.not.exist - - const child = await cds.run(SELECT.one.from(ChildP).where({ ID: 6 })) + + const child = await cds.run(SELECT.one.from(Child).where({ ID: 6, or: { ID: 7 } })) expect(child).to.not.exist - - const grandchild2 = await cds.run(SELECT.one.from(GrandChild).where({ ID: 7 })) - expect(grandchild2).to.not.exist + + const grandchild = await cds.run(SELECT.one.from(GrandChild).where({ ID: 8, or: { ID: 9 } })) + expect(grandchild).to.not.exist + }) + + 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.one.from(Child).where({ ID: 6, or: { ID: 7 } })) + expect(child.ID).to.be.eq(7) + + const grandchild = await cds.run(SELECT.one.from(GrandChild).where({ ID: 8, or: { ID: 9 } })) + expect(grandchild.ID).to.be.eq(9) }) }) diff --git a/test/compliance/resources/db/complex/index.cds b/test/compliance/resources/db/complex/index.cds index 5d23c3049..400fc1c3d 100644 --- a/test/compliance/resources/db/complex/index.cds +++ b/test/compliance/resources/db/complex/index.cds @@ -34,22 +34,24 @@ entity GrandChild { parent : Association to one Child; } -entity RootWithKeys as +entity RootPWithKeys as projection on Root { key ID, fooRoot, children }; -entity RootNoKeys as +entity RootPNoKeys as projection on Root { fooRoot, children } -entity ChildP as +entity ChildP as projection on Child { key ID, fooChild, parent } + +entity ChildPWithWhere as projection on Child where fooChild = 'bar' \ No newline at end of file From ab995355da0dd4e25d93e66ad70befb1a39cc9dc Mon Sep 17 00:00:00 2001 From: I543501 <56645452+larslutz96@users.noreply.github.com> Date: Mon, 12 Feb 2024 15:29:24 +0100 Subject: [PATCH 14/22] Update DELETE.test.js --- test/compliance/DELETE.test.js | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/test/compliance/DELETE.test.js b/test/compliance/DELETE.test.js index e1e6eb6c0..de3460245 100644 --- a/test/compliance/DELETE.test.js +++ b/test/compliance/DELETE.test.js @@ -24,13 +24,13 @@ describe('DELETE', () => { children: [ { ID: 8, - fooGrandChild: 'bar', + fooGrandChild: 'foo', }, ], }, { ID: 7, - fooChild: 'foo', + fooChild: 'bar', children: [ { ID: 9, @@ -53,11 +53,11 @@ describe('DELETE', () => { const root = await cds.run(SELECT.one.from(Root).where({ ID: 5 })) expect(root).to.not.exist - const child = await cds.run(SELECT.one.from(Child).where({ ID: 6, or: { ID: 7 } })) - expect(child).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 grandchild2 = await cds.run(SELECT.one.from(GrandChild).where({ ID: 8, or: { ID: 9 } })) - expect(grandchild2).to.not.exist + const grandchild = await cds.run(SELECT.from(GrandChild).where({ ID: 8, or: { ID: 9 } })) + expect(grandchild.length).to.be.eq(0) }) test('on root with no keys', async () => { @@ -67,23 +67,23 @@ describe('DELETE', () => { const root = await cds.run(SELECT.one.from(Root).where({ ID: 5 })) expect(root).to.not.exist - const child = await cds.run(SELECT.one.from(Child).where({ ID: 6, or: { ID: 7 } })) - expect(child).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.one.from(GrandChild).where({ ID: 8, or: { ID: 9 } })) - expect(grandchild).to.not.exist + 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)) + const deepDelete = await cds.run(DELETE.from(ChildPWithWhere).where({ ID: 6 })) expect(deepDelete).to.be.eq(1) - const child = await cds.run(SELECT.one.from(Child).where({ ID: 6, or: { ID: 7 } })) - expect(child.ID).to.be.eq(7) + 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.one.from(GrandChild).where({ ID: 8, or: { ID: 9 } })) - expect(grandchild.ID).to.be.eq(9) + const grandchild = await cds.run(SELECT.from(GrandChild).where({ ID: 8, or: { ID: 9 } })) + expect(grandchild[0].ID).to.be.eq(9) }) }) From 7a2a1283310b6661eb07c78151c77281ca5a8b70 Mon Sep 17 00:00:00 2001 From: Patrice Bender Date: Wed, 14 Feb 2024 16:41:08 +0100 Subject: [PATCH 15/22] fix: unique alias for scoped subqueries --- db-service/lib/infer/index.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/db-service/lib/infer/index.js b/db-service/lib/infer/index.js index 7a3d1f521..03f9006b1 100644 --- a/db-service/lib/infer/index.js +++ b/db-service/lib/infer/index.js @@ -114,6 +114,8 @@ function infer(originalQuery, model = cds.context?.model || cds.model) { (ref.length === 1 ? first.match(/[^.]+$/)[0] : ref[ref.length - 1].id || ref[ref.length - 1]) if (alias in querySources) throw new Error(`Duplicate alias "${alias}"`) querySources[alias] = target + const last = from.$refLinks.at(-1) + last.alias = alias } else if (from.args) { from.args.forEach(a => inferTarget(a, querySources)) } else if (from.SELECT) { @@ -543,8 +545,7 @@ function infer(originalQuery, model = cds.context?.model || cds.model) { const target = definition._target || column.$refLinks[i - 1].target if (element) { - if($baseLink) - rejectNonFkAccess(element) + if ($baseLink) rejectNonFkAccess(element) const $refLink = { definition: elements[id], target } column.$refLinks.push($refLink) } else if (firstStepIsSelf) { @@ -660,9 +661,7 @@ function infer(originalQuery, model = cds.context?.model || cds.model) { // no unmanaged assoc in infix filter path if (!inExists && assoc.on) throw new Error( - `"${assoc.name}" in path "${column.ref - .map(idOnly) - .join('.')}" must not be an unmanaged association` + `"${assoc.name}" in path "${column.ref.map(idOnly).join('.')}" must not be an unmanaged association`, ) // no non-fk traversal in infix filter in non-exists path if (nextStep && !assoc.on && !isForeignKeyOf(nextStep, assoc)) @@ -896,7 +895,7 @@ function infer(originalQuery, model = cds.context?.model || cds.model) { }) mergePathIfNecessary(subPath, step) } else if (step.args || step.xpr) { - const nestedProp = step.xpr ? 'xpr' : 'args' + const nestedProp = step.xpr ? 'xpr' : 'args' step[nestedProp].forEach(a => { mergePathsIntoJoinTree(a, subPath) }) @@ -1136,7 +1135,7 @@ function infer(originalQuery, model = cds.context?.model || cds.model) { * where association do not have foreign keys anymore. */ function isForeignKeyOf(e, assoc) { - if(!assoc.isAssociation) return false + if (!assoc.isAssociation) return false return e in (assoc.elements || assoc.foreignKeys) } const idOnly = ref => ref.id || ref From c6a9216a3ebc881f7bf3cbb85fc24fb0d7713bdd Mon Sep 17 00:00:00 2001 From: Patrice Bender Date: Wed, 14 Feb 2024 16:52:31 +0100 Subject: [PATCH 16/22] add test --- db-service/test/bookshop/db/schema.cds | 5 +++++ db-service/test/cqn4sql/table-alias.test.js | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/db-service/test/bookshop/db/schema.cds b/db-service/test/bookshop/db/schema.cds index a4d924085..ae22b5cc8 100644 --- a/db-service/test/bookshop/db/schema.cds +++ b/db-service/test/bookshop/db/schema.cds @@ -408,3 +408,8 @@ entity Unmanaged { // needs to be expanded in join-conditions toSelf: Association to Unmanaged on struct = toSelf.struct; } + +entity Item { + key ID: Integer; + item: Association to Item; +} diff --git a/db-service/test/cqn4sql/table-alias.test.js b/db-service/test/cqn4sql/table-alias.test.js index 840d17edb..836d86259 100644 --- a/db-service/test/cqn4sql/table-alias.test.js +++ b/db-service/test/cqn4sql/table-alias.test.js @@ -482,6 +482,18 @@ describe('table alias access', () => { } where Books.ID = 1`, ) }) + it('in a scoped subquery, always assign unique subquery aliases', () => { + const query = CQL`SELECT ID from bookshop.Item where exists (select ID from bookshop.Item:item)` + const res = cqn4sql(query) + const expected = CQL` + SELECT Item.ID from bookshop.Item as Item where exists ( + SELECT item2.ID from bookshop.Item as item2 where exists ( + SELECT 1 from bookshop.Item as Item3 where Item3.item_ID = item2.ID + ) + ) + ` + expect(res).to.deep.eql(expected) + }) it('in expand subquery', () => { let query = cqn4sql( CQL`SELECT from bookshop.Books { From 02262351aac6dbe3be3d861612cb54c43961eec9 Mon Sep 17 00:00:00 2001 From: I543501 <56645452+larslutz96@users.noreply.github.com> Date: Tue, 27 Feb 2024 11:56:43 +0100 Subject: [PATCH 17/22] delete test --- test/compliance/DELETE.test.js | 14 -------------- test/compliance/resources/db/complex/index.cds | 6 ------ 2 files changed, 20 deletions(-) diff --git a/test/compliance/DELETE.test.js b/test/compliance/DELETE.test.js index de3460245..b9ac8241d 100644 --- a/test/compliance/DELETE.test.js +++ b/test/compliance/DELETE.test.js @@ -60,20 +60,6 @@ describe('DELETE', () => { expect(grandchild.length).to.be.eq(0) }) - test('on root with no keys', async () => { - const deepDelete = await cds.run(DELETE.from(RootPNoKeys).where({ fooRoot: 'bar' })) - 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).where({ ID: 6 })) diff --git a/test/compliance/resources/db/complex/index.cds b/test/compliance/resources/db/complex/index.cds index 400fc1c3d..26321e080 100644 --- a/test/compliance/resources/db/complex/index.cds +++ b/test/compliance/resources/db/complex/index.cds @@ -39,12 +39,6 @@ entity RootPWithKeys as key ID, fooRoot, children - }; - -entity RootPNoKeys as - projection on Root { - fooRoot, - children } entity ChildP as From 11b026619ec57507d686ced33664a7039bff6ca9 Mon Sep 17 00:00:00 2001 From: I543501 <56645452+larslutz96@users.noreply.github.com> Date: Tue, 27 Feb 2024 11:57:56 +0100 Subject: [PATCH 18/22] Update DELETE.test.js --- test/compliance/DELETE.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/compliance/DELETE.test.js b/test/compliance/DELETE.test.js index b9ac8241d..76ea0d60a 100644 --- a/test/compliance/DELETE.test.js +++ b/test/compliance/DELETE.test.js @@ -4,7 +4,6 @@ const Root = 'complex.Root' const Child = 'complex.Child' const GrandChild = 'complex.GrandChild' const RootPWithKeys = 'complex.RootPWithKeys' -const RootPNoKeys = 'complex.RootPNoKeys' const ChildPWithWhere = 'complex.ChildPWithWhere' describe('DELETE', () => { From 4b1ef4b8a1e647ef8be8e2e72913d617fe410db3 Mon Sep 17 00:00:00 2001 From: I543501 <56645452+larslutz96@users.noreply.github.com> Date: Wed, 28 Feb 2024 09:30:32 +0100 Subject: [PATCH 19/22] check virtual instead of IsActiveEntity --- db-service/lib/SQLService.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/db-service/lib/SQLService.js b/db-service/lib/SQLService.js index 7e2b2d7ff..3d9a0e0b6 100644 --- a/db-service/lib/SQLService.js +++ b/db-service/lib/SQLService.js @@ -202,13 +202,11 @@ class SQLService extends DatabaseService { const transitions = getTransition(req.query.target, this) if (transitions.target !== transitions.queryTarget) { const elements = transitions.queryTarget.keys - ? Object.keys(transitions.queryTarget.keys) + ? Object.keys(transitions.queryTarget.keys).filter(key => !transitions.queryTarget.keys[key].virtual) : Object.keys(transitions.queryTarget.elements).filter( key => !transitions.queryTarget.elements[key].isAssociation, ) - const matchedKeys = elements - .filter(key => key !== 'IsActiveEntity' && transitions.mapping.has(key)) - .map(k => ({ ref: [k] })) + const matchedKeys = elements.filter(key => transitions.mapping.has(key)).map(k => ({ ref: [k] })) const query = DELETE.from({ ref: [ { From ebd08009f64d0ced477492e9dd667c75a54a526e Mon Sep 17 00:00:00 2001 From: I543501 <56645452+larslutz96@users.noreply.github.com> Date: Wed, 28 Feb 2024 13:21:32 +0100 Subject: [PATCH 20/22] remove object.keys --- db-service/lib/SQLService.js | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/db-service/lib/SQLService.js b/db-service/lib/SQLService.js index 3d9a0e0b6..1ddda67a2 100644 --- a/db-service/lib/SQLService.js +++ b/db-service/lib/SQLService.js @@ -201,12 +201,20 @@ class SQLService extends DatabaseService { async function deep_delete(/** @type {Request} */ req) { const transitions = getTransition(req.query.target, this) if (transitions.target !== transitions.queryTarget) { - const elements = transitions.queryTarget.keys - ? Object.keys(transitions.queryTarget.keys).filter(key => !transitions.queryTarget.keys[key].virtual) - : Object.keys(transitions.queryTarget.elements).filter( - key => !transitions.queryTarget.elements[key].isAssociation, - ) - const matchedKeys = elements.filter(key => transitions.mapping.has(key)).map(k => ({ ref: [k] })) + const keys = [] + const transitionsTarget = transitions.queryTarget.keys || transitions.queryTarget.elements + for (const key in transitionsTarget) { + let isKeyValid + if (transitions.queryTarget.keys) { + isKeyValid = !transitionsTarget[key].virtual + } else { + isKeyValid = !transitionsTarget[key].isAssociation + } + if (isKeyValid) { + keys.push(key) + } + } + const matchedKeys = keys.filter(key => transitions.mapping.has(key)).map(k => ({ ref: [k] })) const query = DELETE.from({ ref: [ { From 1b5c740f0b767a2686081fb2cf5b354744d261c6 Mon Sep 17 00:00:00 2001 From: I543501 Date: Thu, 29 Feb 2024 14:56:56 +0100 Subject: [PATCH 21/22] unify physical elements check --- db-service/lib/SQLService.js | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/db-service/lib/SQLService.js b/db-service/lib/SQLService.js index 1ddda67a2..962c09552 100644 --- a/db-service/lib/SQLService.js +++ b/db-service/lib/SQLService.js @@ -25,16 +25,18 @@ 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) => { - return Object.assign(acc, obj) - }, {}), + operation.data || + operation.entries?.reduce((acc, obj) => { + return Object.assign(acc, obj) + }, {}), ) const invalidColumns = columns.filter(c => !(c in elements)) @@ -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 @@ -195,6 +201,10 @@ class SQLService extends DatabaseService { return (await ps.run(values)).changes } + exists(e) { + return e && !e.virtual && !e.value && !e.isAssociation + } + get onDELETE() { // 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) @@ -204,15 +214,7 @@ class SQLService extends DatabaseService { const keys = [] const transitionsTarget = transitions.queryTarget.keys || transitions.queryTarget.elements for (const key in transitionsTarget) { - let isKeyValid - if (transitions.queryTarget.keys) { - isKeyValid = !transitionsTarget[key].virtual - } else { - isKeyValid = !transitionsTarget[key].isAssociation - } - if (isKeyValid) { - keys.push(key) - } + if (this.exists(transitionsTarget[key])) keys.push(key) } const matchedKeys = keys.filter(key => transitions.mapping.has(key)).map(k => ({ ref: [k] })) const query = DELETE.from({ From 225cfd5623d4d47117227ed09a0c206d4b2b49da Mon Sep 17 00:00:00 2001 From: I543501 Date: Mon, 4 Mar 2024 16:34:20 +0100 Subject: [PATCH 22/22] update test: child with were clause --- test/compliance/DELETE.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/compliance/DELETE.test.js b/test/compliance/DELETE.test.js index 76ea0d60a..6e7d99b48 100644 --- a/test/compliance/DELETE.test.js +++ b/test/compliance/DELETE.test.js @@ -23,13 +23,13 @@ describe('DELETE', () => { children: [ { ID: 8, - fooGrandChild: 'foo', + fooGrandChild: 'bar', }, ], }, { ID: 7, - fooChild: 'bar', + fooChild: 'foo', children: [ { ID: 9, @@ -61,7 +61,7 @@ describe('DELETE', () => { test('on child with where', async () => { // only delete entries where fooChild = 'bar' - const deepDelete = await cds.run(DELETE.from(ChildPWithWhere).where({ ID: 6 })) + 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 } }))