@@ -404,12 +404,77 @@ pub(crate) fn is_numeric_type(data_type: &str) -> bool {
404404}
405405
406406pub ( 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