Skip to content

Commit ccc60bb

Browse files
committed
feat: use LIKE for contain filter
1 parent 899cc03 commit ccc60bb

File tree

1 file changed

+70
-5
lines changed

1 file changed

+70
-5
lines changed

src/databend/core.rs

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -404,12 +404,77 @@ pub(crate) fn is_numeric_type(data_type: &str) -> bool {
404404
}
405405

406406
pub(crate) fn line_filter_clause(line_col: String, filter: &LineFilter) -> String {
407-
let value = escape(&filter.value);
408407
match filter.op {
409-
LineFilterOp::Contains => format!("position('{value}' in {line_col}) > 0"),
410-
LineFilterOp::NotContains => format!("position('{value}' in {line_col}) = 0"),
411-
LineFilterOp::Regex => format!("match({line_col}, '{value}')"),
412-
LineFilterOp::NotRegex => format!("NOT match({line_col}, '{value}')"),
408+
LineFilterOp::Contains => like_contains_clause(line_col, &filter.value, false),
409+
LineFilterOp::NotContains => like_contains_clause(line_col, &filter.value, true),
410+
LineFilterOp::Regex => {
411+
let value = escape(&filter.value);
412+
format!("match({line_col}, '{value}')")
413+
}
414+
LineFilterOp::NotRegex => {
415+
let value = escape(&filter.value);
416+
format!("NOT match({line_col}, '{value}')")
417+
}
418+
}
419+
}
420+
421+
fn like_contains_clause(line_col: String, value: &str, negate: bool) -> String {
422+
let pattern = escape_like_pattern(value);
423+
let op = if negate { "NOT LIKE" } else { "LIKE" };
424+
format!("{line_col} {op} '%{pattern}%' ESCAPE '\\\\'")
425+
}
426+
427+
fn escape_like_pattern(value: &str) -> String {
428+
let mut escaped = String::with_capacity(value.len());
429+
for ch in value.chars() {
430+
match ch {
431+
'%' | '_' | '\\' => {
432+
escaped.push('\\');
433+
escaped.push(ch);
434+
}
435+
'\'' => escaped.push_str("''"),
436+
_ => escaped.push(ch),
437+
}
438+
}
439+
escaped
440+
}
441+
442+
#[cfg(test)]
443+
mod tests {
444+
use super::*;
445+
use crate::logql::{LineFilter, LineFilterOp};
446+
447+
#[test]
448+
fn contains_uses_like_with_escaping() {
449+
let filter = LineFilter {
450+
op: LineFilterOp::Contains,
451+
value: "foo%_\\'bar".into(),
452+
};
453+
let clause = line_filter_clause("line_col".into(), &filter);
454+
assert_eq!(
455+
clause,
456+
"line_col LIKE '%foo\\%\\_\\\\''bar%' ESCAPE '\\\\'"
457+
);
458+
}
459+
460+
#[test]
461+
fn not_contains_negates_like() {
462+
let filter = LineFilter {
463+
op: LineFilterOp::NotContains,
464+
value: "needle".into(),
465+
};
466+
let clause = line_filter_clause("line_col".into(), &filter);
467+
assert_eq!(clause, "line_col NOT LIKE '%needle%' ESCAPE '\\\\'");
468+
}
469+
470+
#[test]
471+
fn regex_filters_keep_match_calls() {
472+
let filter = LineFilter {
473+
op: LineFilterOp::Regex,
474+
value: "foo.*".into(),
475+
};
476+
let clause = line_filter_clause("line_col".into(), &filter);
477+
assert_eq!(clause, "match(line_col, 'foo.*')");
413478
}
414479
}
415480

0 commit comments

Comments
 (0)