Skip to content

Commit

Permalink
reject backlinks w/o fks for joins etc
Browse files Browse the repository at this point in the history
  • Loading branch information
patricebender committed Sep 13, 2024
1 parent 1a62434 commit d6af9ed
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 40 deletions.
7 changes: 6 additions & 1 deletion db-service/lib/cqn4sql.js
Original file line number Diff line number Diff line change
Expand Up @@ -1827,7 +1827,6 @@ function cqn4sql(originalQuery, model) {
const { on, keys } = assocRefLink.definition
const target = getDefinition(assocRefLink.definition.target)
let res
// technically we could have multiple backlinks
if (keys) {
const fkPkPairs = getParentKeyForeignKeyPairs(assocRefLink.definition, targetSideRefLink, true)
const transformedOn = []
Expand Down Expand Up @@ -1960,6 +1959,12 @@ function cqn4sql(originalQuery, model) {
}
})
} else if (backlink.keys) {
// sanity check: error out if we can't produce a join
if (backlink.keys.length === 0) {
throw new Error(
`Path step “${assocRefLink.alias}” is a self comparison with “${getFullName(backlink)}” that has no foreign keys`,
)
}
// managed backlink -> calculate fk-pk pairs
const fkPkPairs = getParentKeyForeignKeyPairs(backlink, targetSideRefLink)
fkPkPairs.forEach((pair, j) => {
Expand Down
113 changes: 74 additions & 39 deletions db-service/test/cqn4sql/keyless.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,61 +12,96 @@ describe('keyless entities', () => {
beforeAll(async () => {
model = await cds.load(__dirname + '/model/keyless').then(cds.linked)
})

it('no foreign keys for join', () => {
const { Books } = model.entities
const q = SELECT.from(Books).where(`author[ID = 42].book[ID = 42].author.name LIKE 'King'`)
expect(() => cqn4sql(q, model)).to.throw(
'Path step “author” of “author[…].book[…].author.name” has no foreign keys',
)
// ok if explicit foreign key is used
const qOk = SELECT.columns('ID').from(Books).where(`authorWithExplicitForeignKey[ID = 42].name LIKE 'King'`)
expect(cqn4sql(qOk, model)).to.eql(
CQL`SELECT Books.ID FROM Books as Books
describe('managed assocs', () => {
it('no foreign keys for join', () => {
const { Books } = model.entities
const q = SELECT.from(Books).where(`author[ID = 42].book[ID = 42].author.name LIKE 'King'`)
expect(() => cqn4sql(q, model)).to.throw(
'Path step “author” of “author[…].book[…].author.name” has no foreign keys',
)
// ok if explicit foreign key is used
const qOk = SELECT.columns('ID').from(Books).where(`authorWithExplicitForeignKey[ID = 42].name LIKE 'King'`)
expect(cqn4sql(qOk, model)).to.eql(
CQL`SELECT Books.ID FROM Books as Books
left join Authors as authorWithExplicitForeignKey
on authorWithExplicitForeignKey.ID = Books.authorWithExplicitForeignKey_ID
and authorWithExplicitForeignKey.ID = 42
where authorWithExplicitForeignKey.name LIKE 'King'`,
)
})
it('scoped query leading to where exists subquery cant be constructed', () => {
const q = SELECT.from('Books:author')
expect(() => cqn4sql(q, model)).to.throw(`Path step “author” of “Books:author” has no foreign keys`)
)
})
it('no foreign keys for join (2)', () => {
const { Authors } = model.entities
const q = SELECT.from(Authors).where(`book.authorWithExplicitForeignKey.book.my.author LIKE 'King'`)
expect(() => cqn4sql(q, model)).to.throw(
'Path step “author” of “book.authorWithExplicitForeignKey.book.my.author” has no foreign keys',
)
})
it('scoped query leading to where exists subquery cant be constructed', () => {
const q = SELECT.from('Books:author')
expect(() => cqn4sql(q, model)).to.throw(`Path step “author” of “Books:author” has no foreign keys`)

// ok if explicit foreign key is used
const qOk = SELECT.from('Books:authorWithExplicitForeignKey').columns('ID')
expect(cqn4sql(qOk, model)).to.eql(
CQL`SELECT authorWithExplicitForeignKey.ID FROM Authors as authorWithExplicitForeignKey
// ok if explicit foreign key is used
const qOk = SELECT.from('Books:authorWithExplicitForeignKey').columns('ID')
expect(cqn4sql(qOk, model)).to.eql(
CQL`SELECT authorWithExplicitForeignKey.ID FROM Authors as authorWithExplicitForeignKey
where exists (
SELECT 1 from Books as Books where Books.authorWithExplicitForeignKey_ID = authorWithExplicitForeignKey.ID
)`,
)
})
it('where exists predicate cant be transformed to subquery', () => {
const q = SELECT.from('Books').where('exists author')
expect(() => cqn4sql(q, model)).to.throw(`Path step “author” of “author” has no foreign keys`)
// ok if explicit foreign key is used
const qOk = SELECT.from('Books').columns('ID').where('exists authorWithExplicitForeignKey')
expect(cqn4sql(qOk, model)).to.eql(
CQL`SELECT Books.ID FROM Books as Books
)
})
it('where exists predicate cant be transformed to subquery', () => {
const q = SELECT.from('Books').where('exists author')
expect(() => cqn4sql(q, model)).to.throw(`Path step “author” of “author” has no foreign keys`)
// ok if explicit foreign key is used
const qOk = SELECT.from('Books').columns('ID').where('exists authorWithExplicitForeignKey')
expect(cqn4sql(qOk, model)).to.eql(
CQL`SELECT Books.ID FROM Books as Books
where exists (
SELECT 1 from Authors as authorWithExplicitForeignKey where authorWithExplicitForeignKey.ID = Books.authorWithExplicitForeignKey_ID
)`,
)
})
it('correlated subquery for expand cant be constructed', () => {
const q = CQL`SELECT author { name } from Books`
expect(() => cqn4sql(q, model)).to.throw(`Can't expand “author” as it has no foreign keys`)
// ok if explicit foreign key is used
const qOk = CQL`SELECT authorWithExplicitForeignKey { name } from Books`
expect(JSON.parse(JSON.stringify(cqn4sql(qOk, model)))).to.eql(
CQL`
)
})
it('correlated subquery for expand cant be constructed', () => {
const q = CQL`SELECT author { name } from Books`
expect(() => cqn4sql(q, model)).to.throw(`Can't expand “author” as it has no foreign keys`)
// ok if explicit foreign key is used
const qOk = CQL`SELECT authorWithExplicitForeignKey { name } from Books`
expect(JSON.parse(JSON.stringify(cqn4sql(qOk, model)))).to.eql(
CQL`
SELECT
(
SELECT authorWithExplicitForeignKey.name from Authors as authorWithExplicitForeignKey
where Books.authorWithExplicitForeignKey_ID = authorWithExplicitForeignKey.ID
) as authorWithExplicitForeignKey
from Books as Books`,
)
)
})
})
describe('managed assocs as backlinks', () => {
it('backlink has no foreign keys for join', () => {
const { Authors } = model.entities
const q = SELECT.from(Authors).where(`bookWithBackLink.title LIKE 'Potter'`)
expect(() => cqn4sql(q, model)).to.throw(
`Path step “bookWithBackLink” is a self comparison with “author” that has no foreign keys`,
)
})
it('backlink has no foreign keys for scoped query', () => {
const q = SELECT.from('Authors:bookWithBackLink')
expect(() => cqn4sql(q, model)).to.throw(
`Path step “bookWithBackLink” is a self comparison with “author” that has no foreign keys`,
)
})
it('backlink has no foreign keys for where exists subquery', () => {
const q = SELECT.from('Authors').where('exists bookWithBackLink')
expect(() => cqn4sql(q, model)).to.throw(
`Path step “bookWithBackLink” is a self comparison with “author” that has no foreign keys`,
)
})
it('backlink has no foreign keys for expand subquery', () => {
const q = CQL`SELECT bookWithBackLink { title } from Authors`
expect(() => cqn4sql(q, model)).to.throw(
`Path step “bookWithBackLink” is a self comparison with “author” that has no foreign keys`,
)
})
})
})
2 changes: 2 additions & 0 deletions db-service/test/cqn4sql/model/keyless.cds
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ entity Authors {
ID : Integer;
name : String;
book: Association to Books;
// backlink has no foreign keys...
bookWithBackLink: Association to Books on bookWithBackLink.author = $self;
}

0 comments on commit d6af9ed

Please sign in to comment.