-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: improve !=
and ==
implementation for @cap-js/hana
#426
Changes from all commits
2772cad
2d77648
0eba9bb
6678fa5
7389322
9b220bf
d9ec35c
f0472fc
23edcad
4e8d0f0
4814f78
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -636,46 +636,50 @@ class HANAService extends SQLService { | |||||||||||
} | ||||||||||||
|
||||||||||||
where(xpr) { | ||||||||||||
return this.xpr({ xpr }, ' = TRUE') | ||||||||||||
return this.xpr({ xpr: [...xpr, 'THEN'] }).slice(0, -4) | ||||||||||||
} | ||||||||||||
|
||||||||||||
having(xpr) { | ||||||||||||
return this.xpr({ xpr }, ' = TRUE') | ||||||||||||
return this.where(xpr) | ||||||||||||
} | ||||||||||||
|
||||||||||||
xpr({ xpr, _internal }, caseSuffix = '') { | ||||||||||||
xpr(_xpr, caseSuffix = '') { | ||||||||||||
const { xpr, _internal } = _xpr | ||||||||||||
// Maps the compare operators to what to return when both sides are null | ||||||||||||
const compareOperators = { | ||||||||||||
'=': true, | ||||||||||||
'==': true, | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would mean to change what we decided and specified? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this change makes it as followed:
As part of the reported issue it was determined that Hope that makes this a bit more clear. |
||||||||||||
'!=': false, | ||||||||||||
// These operators are not allowed in column expressions | ||||||||||||
/* REVISIT: Only adjust these operators when inside the column expression | ||||||||||||
'=': null, | ||||||||||||
'>': null, | ||||||||||||
'<': null, | ||||||||||||
'<>': null, | ||||||||||||
'>=': null, | ||||||||||||
'<=': null, | ||||||||||||
'!<': null, | ||||||||||||
'!>': null, | ||||||||||||
*/ | ||||||||||||
} | ||||||||||||
|
||||||||||||
if (!_internal) { | ||||||||||||
for (let i = 0; i < xpr.length; i++) { | ||||||||||||
const x = xpr[i] | ||||||||||||
let x = xpr[i] | ||||||||||||
if (typeof x === 'string') { | ||||||||||||
// Convert =, == and != into is (not) null operator where required | ||||||||||||
x = xpr[i] = super.operator(xpr[i], i, xpr) | ||||||||||||
|
||||||||||||
// HANA does not support comparators in all clauses (e.g. SELECT 1>0 FROM DUMMY) | ||||||||||||
// HANA does not have an 'IS' or 'IS NOT' operator | ||||||||||||
if (x in compareOperators) { | ||||||||||||
const left = xpr[i - 1] | ||||||||||||
const right = xpr[i + 1] | ||||||||||||
const ifNull = compareOperators[x] | ||||||||||||
|
||||||||||||
const compare = { | ||||||||||||
xpr: [left, x, right], | ||||||||||||
_internal: true, | ||||||||||||
} | ||||||||||||
const compare = [left, x, right] | ||||||||||||
|
||||||||||||
const expression = { | ||||||||||||
xpr: ['CASE', 'WHEN', compare, 'Then', { val: true }, 'WHEN', 'NOT', compare, 'Then', { val: false }], | ||||||||||||
xpr: ['CASE', 'WHEN', ...compare, 'THEN', { val: true }, 'WHEN', 'NOT', ...compare, 'THEN', { val: false }], | ||||||||||||
_internal: true, | ||||||||||||
} | ||||||||||||
|
||||||||||||
|
@@ -686,11 +690,8 @@ class HANAService extends SQLService { | |||||||||||
xpr: [ | ||||||||||||
'CASE', | ||||||||||||
'WHEN', | ||||||||||||
{ | ||||||||||||
xpr: [left, 'IS', 'NULL', 'AND', right, 'IS', 'NULL'], | ||||||||||||
_internal: true, | ||||||||||||
}, | ||||||||||||
'Then', | ||||||||||||
...[left, 'IS', 'NULL', 'AND', right, 'IS', 'NULL'], | ||||||||||||
'THEN', | ||||||||||||
{ val: ifNull }, | ||||||||||||
'ELSE', | ||||||||||||
{ val: !ifNull }, | ||||||||||||
|
@@ -703,7 +704,7 @@ class HANAService extends SQLService { | |||||||||||
|
||||||||||||
xpr[i - 1] = '' | ||||||||||||
xpr[i] = expression | ||||||||||||
xpr[i + 1] = caseSuffix || '' | ||||||||||||
xpr[i + 1] = '' | ||||||||||||
} | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
@@ -720,27 +721,61 @@ class HANAService extends SQLService { | |||||||||||
} | ||||||||||||
|
||||||||||||
// HANA does not allow WHERE TRUE so when the expression is only a single entry "= TRUE" is appended | ||||||||||||
if (caseSuffix && xpr.length === 1) { | ||||||||||||
if (caseSuffix && ( | ||||||||||||
xpr.length === 1 || xpr.at(-1) === '')) { | ||||||||||||
sql.push(caseSuffix) | ||||||||||||
} | ||||||||||||
|
||||||||||||
return `${sql.join(' ')}` | ||||||||||||
} | ||||||||||||
|
||||||||||||
operator(x, i, xpr) { | ||||||||||||
const up = x.toUpperCase() | ||||||||||||
// Add "= TRUE" before THEN in case statements | ||||||||||||
// As all valid comparators are converted to booleans as SQL specifies | ||||||||||||
if (x in { THEN: 1, then: 1 }) return ` = TRUE ${x}` | ||||||||||||
if ((x in { LIKE: 1, like: 1 } && is_regexp(xpr[i + 1]?.val)) || x === 'regexp') return 'LIKE_REGEXPR' | ||||||||||||
if ( | ||||||||||||
up in logicOperators && | ||||||||||||
!this.is_comparator({ xpr }, i - 1) | ||||||||||||
) { | ||||||||||||
return ` = TRUE ${x}` | ||||||||||||
} | ||||||||||||
if ( | ||||||||||||
(up === 'LIKE' && is_regexp(xpr[i + 1]?.val)) || | ||||||||||||
up === 'REGEXP' | ||||||||||||
) return 'LIKE_REGEXPR' | ||||||||||||
else return x | ||||||||||||
} | ||||||||||||
|
||||||||||||
get is_distinct_from_() { return '!=' } | ||||||||||||
get is_not_distinct_from_() { return '==' } | ||||||||||||
|
||||||||||||
/** | ||||||||||||
* Checks if the xpr is a comparison or a value | ||||||||||||
* @param {} xpr | ||||||||||||
* @returns | ||||||||||||
*/ | ||||||||||||
is_comparator({ xpr }, start) { | ||||||||||||
for (let i = start ?? xpr.length; i > -1; i--) { | ||||||||||||
const cur = xpr[i] | ||||||||||||
if (cur == null) continue | ||||||||||||
if (typeof cur === 'string') { | ||||||||||||
const up = cur.toUpperCase() | ||||||||||||
// When a compare operator is found the expression is a comparison | ||||||||||||
if (up in compareOperators) return true | ||||||||||||
// When a case operator is found it is the start of the expression | ||||||||||||
if (up in caseOperators) break | ||||||||||||
continue | ||||||||||||
} | ||||||||||||
if ('xpr' in cur) return this.is_comparator(cur) | ||||||||||||
} | ||||||||||||
return false | ||||||||||||
} | ||||||||||||
|
||||||||||||
list(list) { | ||||||||||||
const first = list.list[0] | ||||||||||||
// If the list only contains of lists it is replaced with a json function and a placeholder | ||||||||||||
if (this.values && first.list && !first.list.find(v => !v.val)) { | ||||||||||||
const extraction = first.list.map((v, i) => `"${i}" ${this.constructor.InsertTypeMap[typeof v.val]()} PATH '$.${i}'`) | ||||||||||||
this.values.push(JSON.stringify(list.list.map(l => l.list.reduce((l, c, i) => { l[i] = c.val; return l }, {})))) | ||||||||||||
const extraction = first.list.map((v, i) => `"${i}" ${this.constructor.InsertTypeMap[typeof v.val]()} PATH '$.V${i}'`) | ||||||||||||
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})))` | ||||||||||||
} | ||||||||||||
// Call super for normal SQL behavior | ||||||||||||
|
@@ -959,7 +994,7 @@ class HANAService extends SQLService { | |||||||||||
|
||||||||||||
const stmt = await this.dbc.prepare(createContainerDatabase) | ||||||||||||
const res = await stmt.run([creds.user, creds.password, creds.containerGroup, !clean]) | ||||||||||||
DEBUG?.(res.map(r => r.MESSAGE).join('\n')) | ||||||||||||
res && DEBUG?.(res.changes.map(r => r.MESSAGE).join('\n')) | ||||||||||||
} finally { | ||||||||||||
if (this.dbc) { | ||||||||||||
// Release table lock | ||||||||||||
|
@@ -1001,7 +1036,7 @@ class HANAService extends SQLService { | |||||||||||
|
||||||||||||
const stmt = await this.dbc.prepare(createContainerTenant.replaceAll('{{{GROUP}}}', creds.containerGroup)) | ||||||||||||
const res = await stmt.run([creds.user, creds.password, creds.schema, !clean]) | ||||||||||||
res && DEBUG?.(res.map(r => r.MESSAGE).join('\n')) | ||||||||||||
res && DEBUG?.(res.changes.map(r => r.MESSAGE).join('\n')) | ||||||||||||
} finally { | ||||||||||||
await this.dbc.disconnect() | ||||||||||||
delete this.dbc | ||||||||||||
|
@@ -1028,4 +1063,34 @@ const _managed = { | |||||||||||
$now: '$now', | ||||||||||||
} | ||||||||||||
|
||||||||||||
const caseOperators = { | ||||||||||||
'CASE': 1, | ||||||||||||
'WHEN': 1, | ||||||||||||
'THEN': 1, | ||||||||||||
'ELSE': 1, | ||||||||||||
} | ||||||||||||
const logicOperators = { | ||||||||||||
'THEN': 1, | ||||||||||||
'AND': 1, | ||||||||||||
'OR': 1, | ||||||||||||
} | ||||||||||||
const compareOperators = { | ||||||||||||
'=': 1, | ||||||||||||
'==': 1, | ||||||||||||
'!=': 1, | ||||||||||||
'>': 1, | ||||||||||||
'<': 1, | ||||||||||||
'<>': 1, | ||||||||||||
'>=': 1, | ||||||||||||
'<=': 1, | ||||||||||||
'!<': 1, | ||||||||||||
'!>': 1, | ||||||||||||
'IS': 1, | ||||||||||||
'IN': 1, | ||||||||||||
'LIKE': 1, | ||||||||||||
'IS NOT': 1, | ||||||||||||
'EXISTS': 1, | ||||||||||||
'BETWEEN': 1, | ||||||||||||
} | ||||||||||||
|
||||||||||||
module.exports = HANAService |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,3 @@ | ||
// TODO: make lean-draft test run with HANA as well | ||
describe('lean-draft', () => { | ||
test('TODO', () => {}) | ||
require('../../sqlite/test/lean-draft.test') | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hard to understand → What is that doing?