Skip to content

Commit

Permalink
Merge branch 'main' into fix/deep-groupby-expand
Browse files Browse the repository at this point in the history
  • Loading branch information
johannes-vogel authored Sep 2, 2024
2 parents 084a152 + f70abcb commit 16f3b4b
Show file tree
Hide file tree
Showing 10 changed files with 118 additions and 27 deletions.
18 changes: 12 additions & 6 deletions db-service/lib/cql-functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,16 @@ const StandardFunctions = {
search: function (ref, arg) {
if (!('val' in arg)) throw new Error(`Only single value arguments are allowed for $search`)
// only apply first search term, rest is ignored
const sub= /("")|("(?:[^"]|\\")*(?:[^\\]|\\\\)")|(\S*)/.exec(arg.val)
arg.val = arg.__proto__.val = (sub[2] ? JSON.parse(sub[2]) : sub[3]) || ''
const refs = ref.list || [ref],
{ toString } = ref
const sub = /("")|("(?:[^"]|\\")*(?:[^\\]|\\\\)")|(\S*)/.exec(arg.val)
let val
try {
val = (sub[2] ? JSON.parse(sub[2]) : sub[3]) || ''
} catch {
val = sub[2] || sub[3] || ''
}
arg.val = arg.__proto__.val = val
const refs = ref.list || [ref]
const { toString } = ref
return '(' + refs.map(ref2 => this.contains(this.tolower(toString(ref2)), this.tolower(arg))).join(' or ') + ')'
},
/**
Expand Down Expand Up @@ -159,8 +165,8 @@ const StandardFunctions = {
* Generates SQL statement that produces current point in time (date and time with time zone)
* @returns {string}
*/
now: function() {
return this.session_context({val: '$now'})
now: function () {
return this.session_context({ val: '$now' })
},
/**
* Generates SQL statement that produces the year of a given timestamp
Expand Down
12 changes: 6 additions & 6 deletions db-service/lib/cqn2sql.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ const BINARY_TYPES = {
const { Readable } = require('stream')

const DEBUG = (() => {
let DEBUG = cds.debug('sql-json')
if (DEBUG) return DEBUG
else DEBUG = cds.debug('sql|sqlite')
if (DEBUG) {
return DEBUG
const LOG = cds.log('sql-json')
if (LOG._debug) return cds.debug('sql-json')
return cds.debug('sql|sqlite')
//if (DEBUG) {
// return DEBUG
// (sql, ...more) => DEBUG (sql.replace(/(?:SELECT[\n\r\s]+(json_group_array\()?[\n\r\s]*json_insert\((\n|\r|.)*?\)[\n\r\s]*\)?[\n\r\s]+as[\n\r\s]+_json_[\n\r\s]+FROM[\n\r\s]*\(|\)[\n\r\s]*(\)[\n\r\s]+AS )|\)$)/gim,(a,b,c,d) => d || ''), ...more)
// FIXME: looses closing ) on INSERT queries
}
//}
})()

class CQN2SQLRenderer {
Expand Down
2 changes: 1 addition & 1 deletion db-service/lib/cqn4sql.js
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,7 @@ function cqn4sql(originalQuery, model) {
res = getTransformedTokenStream([value], baseLink)[0]
} else if (xpr) {
res = { xpr: getTransformedTokenStream(value.xpr, baseLink) }
} else if (val) {
} else if (val !== undefined) {
res = { val }
} else if (func) {
res = { args: getTransformedFunctionArgs(value.args, baseLink), func: value.func }
Expand Down
8 changes: 8 additions & 0 deletions hana/lib/HANAService.js
Original file line number Diff line number Diff line change
Expand Up @@ -984,6 +984,13 @@ class HANAService extends SQLService {
this.values.push(JSON.stringify(list.list.map(l => l.list.reduce((l, c, i) => { l[`V${i}`] = c.val; return l }, {}))))
return `(SELECT * FROM JSON_TABLE(?, '$' COLUMNS(${extraction})))`
}
// If the list only contains of vals it is replaced with a json function and a placeholder
if (this.values && first.val) {
const v = first
const extraction = `"val" ${this.constructor.InsertTypeMap[typeof v.val]()} PATH '$.val'`
this.values.push(JSON.stringify(list.list))
return `(SELECT * FROM JSON_TABLE(?, '$' COLUMNS(${extraction})))`
}
// Call super for normal SQL behavior
return super.list(list)
}
Expand Down Expand Up @@ -1131,6 +1138,7 @@ class HANAService extends SQLService {

static OutputConverters = {
...super.OutputConverters,
LargeString: cds.env.features.sql_simple_queries > 0 ? e => `TO_NVARCHAR(${e})` : undefined,
// REVISIT: binaries should use BASE64_ENCODE, but this results in BASE64_ENCODE(BINTONHEX(${e}))
Binary: e => `BINTONHEX(${e})`,
Date: e => `to_char(${e}, 'YYYY-MM-DD')`,
Expand Down
24 changes: 12 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 30 additions & 0 deletions test/compliance/resources/db/complex/computed.cds
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
namespace complex.computed;

entity static {
value : Integer;
integer : Integer = 1;
double : Double = 0.1;
string : String = '';
}

entity dynamic {
integer : Integer;
@(Core.Computed: true,readonly)
![case] : String = (
case
when
integer = 0
then
'zero'
when
integer = 1
then
'one'
when
integer = 2
then
'two'
end
);
lambda : String = (integer = 0 ? 'none' : 'some')
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module.exports = [
{
integer: 0,
'=case': 'zero',
'=lambda': 'none',
},
{
integer: 1,
'=case': 'one',
'=lambda': 'some',
},
{
integer: 2,
'=case': 'two',
'=lambda': 'some',
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = [
{
'=integer': 1,
'=double': 0.1,
'=string': '',
}
]
1 change: 1 addition & 0 deletions test/compliance/resources/db/complex/index.cds
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
namespace complex;

using from './computed';
using from './associations';
using from './associationsUnmanaged';
26 changes: 24 additions & 2 deletions test/scenarios/bookshop/search.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ describe.skip('Bookshop - Search', () => {
// ad-hoc search expression
Books['@cds.search.authorsAddress'] = true

let res = await SELECT.from(Books).columns('author.name as author', 'title').search('1 Main Street, Bradford')
let res = await SELECT.from(Books).columns('author.name as author', 'title').search('"1 Main Street, Bradford"')
// author name in res[0] must match "Emily Brontë"
expect(res.length).to.be.eq(1)
expect(res[0].author).to.be.eq('Emily Brontë')
Expand All @@ -91,12 +91,34 @@ describe.skip('Bookshop - Search', () => {
Books['@cds.search.author'] = true
Authors['@cds.search.address'] = true // address is a calculated element

let res = await SELECT.from(Books).columns('author.name as author', 'title').search('1 Main Street, Bradford')
let res = await SELECT.from(Books).columns('author.name as author', 'title').search('"1 Main Street, Bradford"')
// author name in res[0] must match "Emily Brontë"
expect(res.length).to.be.eq(1)
expect(res[0].author).to.be.eq('Emily Brontë')
})

test('Search escaped character in search literal', async () => {
const { Books } = cds.entities
const { Authors } = cds.entities
// ad-hoc search expression
Books['@cds.search.author'] = true
Authors['@cds.search.address'] = true // address is a calculated element

let res = await SELECT.from(Books).columns('author.name as author', 'title').search('"\\"\\\\"')
expect(res.length).to.be.eq(0)
})

test('Search improperly escaped character in search literal', async () => {
const { Books } = cds.entities
const { Authors } = cds.entities
// ad-hoc search expression
Books['@cds.search.author'] = true
Authors['@cds.search.address'] = true // address is a calculated element

let res = await SELECT.from(Books).columns('author.name as author', 'title').search('"\\q"')
expect(res.length).to.be.eq(0)
})

test('search on result of subselect', async () => {
const res = await cds.run(
SELECT.from(SELECT.from({ ref: ['sap.capire.bookshop.Books'] }).columns('title'))
Expand Down

0 comments on commit 16f3b4b

Please sign in to comment.