Skip to content

Commit

Permalink
fix: starts endswith for null values (#975)
Browse files Browse the repository at this point in the history
Wrap `startswith` and `endswith` with `coalesce` to return false when
element is `NULL`.
  • Loading branch information
larslutz96 authored Jan 15, 2025
1 parent b953480 commit f0330bc
Show file tree
Hide file tree
Showing 3 changed files with 22 additions and 4 deletions.
4 changes: 2 additions & 2 deletions db-service/lib/cql-functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,15 @@ const StandardFunctions = {
* @param {string} y
* @returns {string}
*/
startswith: (x, y) => `instr(${x},${y}) = 1`, // sqlite instr is 1 indexed
startswith: (x, y) => `coalesce(instr(${x},${y}) = 1,false)`, // sqlite instr is 1 indexed
// takes the end of the string of the size of the target and compares it with the target
/**
* Generates SQL statement that produces a boolean value indicating whether the first string ends with the second string
* @param {string} x
* @param {string} y
* @returns {string}
*/
endswith: (x, y) => `substr(${x}, length(${x}) + 1 - length(${y})) = ${y}`,
endswith: (x, y) => `coalesce(substr(${x}, length(${x}) + 1 - length(${y})) = ${y},false)`,
/**
* Generates SQL statement that produces the substring of a given string
* @example
Expand Down
4 changes: 2 additions & 2 deletions postgres/lib/cql-functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ const StandardFunctions = {
countdistinct: x => `count(distinct ${x.val || x || '*'})`,
contains: (...args) => `(coalesce(strpos(${args}),0) > 0)`,
indexof: (x, y) => `strpos(${x},${y}) - 1`, // strpos is 1 indexed
startswith: (x, y) => `strpos(${x},${y}) = 1`, // strpos is 1 indexed
endswith: (x, y) => `substr(${x},length(${x}) + 1 - length(${y})) = ${y}`,
startswith: (x, y) => `coalesce(strpos(${x},${y}) = 1,false)`, // strpos is 1 indexed
endswith: (x, y) => `coalesce(substr(${x},length(${x}) + 1 - length(${y})) = ${y},false)`,
matchesPattern: (x, y) => `regexp_like(${x}, ${y})`,
matchespattern: (x, y) => `regexp_like(${x}, ${y})`,

Expand Down
18 changes: 18 additions & 0 deletions test/scenarios/bookshop/funcs.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@ describe('Bookshop - Functions', () => {
expect(wrong.data.value.length).to.be.eq(0)
})

test('not endswith finds null', async () => {
const { Books } = cds.entities('sap.capire.bookshop')
await cds.run(INSERT({ ID: 123, title: 'Harry Potter', stock: undefined }).into(Books))
const res = await GET(`/browse/Books?$filter=not endswith(author,'Poe')`)
expect(res.status).to.be.eq(200)
expect(res.data.value.some(item => item.ID === 123)).to.be.true
await cds.run(DELETE.from(Books).where({ ID: 123 }))
})

test('indexof', async () => {
const res = await GET(`/browse/Books?$filter=indexof(author,'Allen') eq 6`)

Expand All @@ -91,6 +100,15 @@ describe('Bookshop - Functions', () => {
expect(wrong.data.value.length).to.be.eq(0)
})

test('not startswith finds null', async () => {
const { Books } = cds.entities('sap.capire.bookshop')
await cds.run(INSERT({ ID: 123, title: 'Harry Potter', stock: undefined }).into(Books))
const res = await GET(`/browse/Books?$filter=not startswith(author,'Poe')`)
expect(res.status).to.be.eq(200)
expect(res.data.value.some(item => item.ID === 123)).to.be.true
await cds.run(DELETE.from(Books).where({ ID: 123 }))
})

test('substring', async () => {
const [three, two, negative] = await Promise.all([
GET(`/browse/Books?$filter=substring(author,1,2) eq 'dg'`),
Expand Down

0 comments on commit f0330bc

Please sign in to comment.