From 821d81dc7aed8389fa0a62f07a1c49036031748a Mon Sep 17 00:00:00 2001 From: Kenterfie Date: Wed, 25 Sep 2024 13:18:02 +0200 Subject: [PATCH] Adds support for IN operator as adhoc filter (#986) Co-authored-by: Andreas Christou --- CHANGELOG.md | 1 + src/data/adHocFilter.test.ts | 33 +++++++++++++++++++++++++++++++++ src/data/adHocFilter.ts | 17 +++++++++++++++-- 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 418f9d13..89607cea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Added "Labels" column selector to the log query builder - Datasource OTel configuration will now set default table names for logs and traces. +- Added support for IN operator in adhoc filters ### Fixes diff --git a/src/data/adHocFilter.test.ts b/src/data/adHocFilter.test.ts index 75e48984..367804ef 100644 --- a/src/data/adHocFilter.test.ts +++ b/src/data/adHocFilter.test.ts @@ -152,6 +152,39 @@ describe('AdHocManager', () => { ); }); + it('apply ad hoc filter IN operator with string values', () => { + const ahm = new AdHocFilter(); + ahm.setTargetTableFromQuery('SELECT * FROM foo'); + const val = ahm.apply('SELECT stuff FROM foo WHERE col = test', [ + { key: 'key', operator: 'IN', value: '(\'val1\', \'val2\')' }, + ] as AdHocVariableFilter[]); + expect(val).toEqual( + `SELECT stuff FROM foo WHERE col = test settings additional_table_filters={'foo' : ' key IN (\\'val1\\', \\'val2\\') '}` + ); + }); + + it('apply ad hoc filter IN operator without parentheses', () => { + const ahm = new AdHocFilter(); + ahm.setTargetTableFromQuery('SELECT * FROM foo'); + const val = ahm.apply('SELECT stuff FROM foo WHERE col = test', [ + { key: 'key', operator: 'IN', value: '\'val1\', \'val2\'' }, + ] as AdHocVariableFilter[]); + expect(val).toEqual( + `SELECT stuff FROM foo WHERE col = test settings additional_table_filters={'foo' : ' key IN (\\'val1\\', \\'val2\\') '}` + ); + }); + + it('apply ad hoc filter IN operator with integer values', () => { + const ahm = new AdHocFilter(); + ahm.setTargetTableFromQuery('SELECT * FROM foo'); + const val = ahm.apply('SELECT stuff FROM foo WHERE col = test', [ + { key: 'key', operator: 'IN', value: '(1, 2, 3)' }, + ] as AdHocVariableFilter[]); + expect(val).toEqual( + `SELECT stuff FROM foo WHERE col = test settings additional_table_filters={'foo' : ' key IN (1, 2, 3) '}` + ); + }); + it('does not apply an adhoc filter without "operator"', () => { const ahm = new AdHocFilter(); ahm.setTargetTableFromQuery('SELECT * FROM foo'); diff --git a/src/data/adHocFilter.ts b/src/data/adHocFilter.ts index 2a4dfc94..eadb8927 100644 --- a/src/data/adHocFilter.ts +++ b/src/data/adHocFilter.ts @@ -38,7 +38,7 @@ export class AdHocFilter { }) .map((f, i) => { const key = f.key.includes('.') ? f.key.split('.')[1] : f.key; - const value = `\\'${f.value}\\'`; + const value = escapeValueBasedOnOperator(f.value, f.operator); const condition = i !== adHocFilters.length - 1 ? (f.condition ? f.condition : 'AND') : ''; const operator = convertOperatorToClickHouseOperator(f.operator); return ` ${key} ${operator} ${value} ${condition}`; @@ -58,6 +58,19 @@ function isValid(filter: AdHocVariableFilter): boolean { return filter.key !== undefined && filter.operator !== undefined && filter.value !== undefined; } +function escapeValueBasedOnOperator(s: string, operator: AdHocVariableFilterOperator): string { + if (operator === 'IN') { + // Allow list of values without parentheses + if (s.length > 2 && s[0] !== '(' && s[s.length - 1] !== ')') { + s = `(${s})` + } + + return s.replace(/'/g, "\\'"); + } else { + return `\\'${s}\\'`; + } +} + function convertOperatorToClickHouseOperator(operator: AdHocVariableFilterOperator): string { if (operator === '=~') { return 'ILIKE'; @@ -68,7 +81,7 @@ function convertOperatorToClickHouseOperator(operator: AdHocVariableFilterOperat return operator; } -type AdHocVariableFilterOperator = '>' | '<' | '=' | '!=' | '=~' | '!~'; +type AdHocVariableFilterOperator = '>' | '<' | '=' | '!=' | '=~' | '!~' | 'IN'; export type AdHocVariableFilter = { key: string;