Skip to content

Commit

Permalink
fix: dont use virtual key for UPDATE … where (<key>) in <subquery> (#…
Browse files Browse the repository at this point in the history
…800)

For a draft enabled model:

```cds
namespace my;

entity Books {
  key ID : Integer;
  title  : String;
  stock  : Integer;
  author : Association to Authors;
}

entity Authors {
  key ID : Integer;
  name   : String;
  alive  : Boolean;
}

service CatalogService {
    @odata.draft.enabled
    @readonly entity Books as projection on my.Books;

    @readonly entity Authors as projection on my.Authors;
}
```

`my.Books` will have a field:
```json
        "IsActiveEntity": {
          "type": "cds.Boolean",
          "key": true,
          "default": {
            "val": true
          },
          "@UI.Hidden": true
        },
```

which is **marked as virtual by the lean draft implementation**. This
virtual key must not be part of the primary key matching which is done
for a path expression in a `UPDATE.where`:


https://github.com/cap-js/cds-dbs/blob/982b8b796da4b577c5641039d2036816209c0437/db-service/test/cqn4sql/UPDATE.test.js#L73-L97

TODO:

- [x] test this
  • Loading branch information
patricebender authored Sep 12, 2024
1 parent 99a12f3 commit d25af70
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 3 deletions.
4 changes: 3 additions & 1 deletion db-service/lib/cqn4sql.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,9 @@ function cqn4sql(originalQuery, model) {
const queryTarget = Object.values(inferred.sources)[0].definition
const keys = Object.values(queryTarget.elements).filter(e => e.key === true)
const primaryKey = { list: [] }
keys.forEach(k => {
keys
.filter(k => !k.virtual) // e.g. draft column `isActiveEntity` is virtual and key
.forEach(k => {
// cqn4sql will add the table alias to the column later, no need to add it here
subquery.SELECT.columns.push({ ref: [k.name] })

Expand Down
34 changes: 33 additions & 1 deletion db-service/test/cqn4sql/UPDATE.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ describe('UPDATE', () => {
left join bookshop.Books as books on books.author_ID = Authors.ID
where books.title LIKE '%Heights%'
)
`
`,
]
expected.UPDATE.entity = {
as: 'Authors2',
Expand Down Expand Up @@ -169,3 +169,35 @@ describe('UPDATE', () => {
expect(query.UPDATE).to.deep.equal(expected.UPDATE)
})
})
describe('UPDATE with path expression', () => {
let model
beforeAll(async () => {
model = cds.model = await cds.load(__dirname + '/model/update').then(cds.linked)
model = cds.compile.for.nodejs(model)
})

it('with path expressions with draft enabled entity', () => {
const { UPDATE } = cds.ql
let u = UPDATE.entity({ ref: ['bookshop.CatalogService.Books'] }).where(`author.name LIKE '%Bron%'`)

let expected = UPDATE.entity({ ref: ['bookshop.CatalogService.Books'] })

// dont use virtual key `isActiveEntity` in `UPDATE … where (<key>) in <subquery>`
expected.UPDATE.where = [
{ list: [{ ref: ['Books2', 'ID'] }] },
'in',
CQL`
(SELECT Books.ID from bookshop.CatalogService.Books as Books
left join bookshop.CatalogService.Authors as author on author.ID = Books.author_ID
where author.name LIKE '%Bron%'
)
`,
]
expected.UPDATE.entity = {
as: 'Books2',
ref: ['bookshop.CatalogService.Books'],
}
let res = cqn4sql(u, model)
expect(JSON.parse(JSON.stringify(res))).to.deep.equal(JSON.parse(JSON.stringify(expected)))
})
})
23 changes: 23 additions & 0 deletions db-service/test/cqn4sql/model/update.cds
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// dont use virtual key `isActiveEntity` in `UPDATE … where (<key>) in <subquery>`
// in case of path expressions
namespace bookshop;

entity Books {
key ID : Integer;
title : String;
stock : Integer;
author : Association to Authors;
}

entity Authors {
key ID : Integer;
name : String;
alive : Boolean;
}

service CatalogService {
@odata.draft.enabled
entity Books as projection on bookshop.Books;

entity Authors as projection on bookshop.Authors;
}
6 changes: 5 additions & 1 deletion test/bookshop/srv/draft-enabled-service.cds
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
using { sap.capire.bookshop as my } from '../db/schema';
service DraftService {
@odata.draft.enabled
entity DraftEnabledBooks
{
key ID : Integer;
title : String;
}
}

@odata.draft.enabled
entity MoreDraftEnabledBooks as projection on my.Books;
}
14 changes: 14 additions & 0 deletions test/scenarios/bookshop/update.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,18 @@ describe('Bookshop - Update', () => {
const res = await UPSERT.into('DraftService.DraftEnabledBooks').entries({ ID: 42, title: 'Foo' })
expect(res).to.equal(1)
})

test('with path expressions on draft enabled service entity', async () => {
// make sure `isActiveEntity` is not used in `UPDATE … where (<key>) in <subquery>`
// as it is a virtual <key>
const { MoreDraftEnabledBooks } = cds.entities('DraftService')
const updateRichardsBooks = UPDATE.entity(MoreDraftEnabledBooks)
.where(`author.name = 'Richard Carpenter'`)
.set('ID = 42')
const selectRichardsBooks = CQL`SELECT * FROM ${MoreDraftEnabledBooks} where author.name = 'Richard Carpenter'`

await cds.run(updateRichardsBooks)
const afterUpdate = await cds.db.run(selectRichardsBooks)
expect(afterUpdate[0]).to.have.property('ID').that.equals(42)
})
})

0 comments on commit d25af70

Please sign in to comment.