From 34f95ea0e91e8e7fe5cbf9ff1f807b2f83035d28 Mon Sep 17 00:00:00 2001 From: oc7o Date: Thu, 25 Jun 2026 16:42:41 +0200 Subject: [PATCH 1/3] fix: translate SQL wildcards in SIMILAR TO patterns (#22263) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `SIMILAR TO` previously passed the pattern straight to Arrow's regex engine, so SQL wildcards were never translated and matches were unanchored: SELECT 'abc' SIMILAR TO 'a%'; -- returned false SELECT 'x' SIMILAR TO '_'; -- returned false Translate `%` to `.*` and `_` to `.`, then wrap the pattern in `^(?:...)$` so the regex matches the entire string. Other regex metacharacters (`|`, `(`, `)`, `*`, `+`, `?`) pass through unchanged, matching `SIMILAR TO`'s superset-of-regex semantics. The translation only fires for literal `Utf8`, `LargeUtf8`, and `Utf8View` patterns. Non-literal patterns return a `not_impl_err!` — silently wrong results are worse than an honest error, and this mirrors how DataFusion already handles the unsupported `ESCAPE` clause. NULL patterns pass through unchanged. Existing tests in `binary.rs` were relying on the bug by passing raw regex strings as `SIMILAR TO` patterns; they have been rewritten to use SQL wildcard syntax, and new cases cover `%`, `_`, full-string anchoring, and regex-metacharacter passthrough. End-to-end coverage added in `strings.slt`. --- .../physical-expr/src/expressions/binary.rs | 132 +++++++++++++++--- .../sqllogictest/test_files/strings.slt | 21 +++ 2 files changed, 133 insertions(+), 20 deletions(-) diff --git a/datafusion/physical-expr/src/expressions/binary.rs b/datafusion/physical-expr/src/expressions/binary.rs index 6f0b60556a751..e4819892c4c88 100644 --- a/datafusion/physical-expr/src/expressions/binary.rs +++ b/datafusion/physical-expr/src/expressions/binary.rs @@ -1099,6 +1099,20 @@ pub fn binary( Ok(Arc::new(BinaryExpr::new(lhs, op, rhs))) } +fn sql_similar_to_regex(pattern: &str) -> String { + let mut result = String::with_capacity(pattern.len() + 6); + result.push_str("^(?:"); + for ch in pattern.chars() { + match ch { + '%' => result.push_str(".*"), + '_' => result.push('.'), + c => result.push(c), + } + } + result.push_str(")$"); + result +} + /// Create a similar to expression pub fn similar_to( negated: bool, @@ -1112,7 +1126,39 @@ pub fn similar_to( (true, false) => Operator::RegexNotMatch, (true, true) => Operator::RegexNotIMatch, }; - Ok(Arc::new(BinaryExpr::new(expr, binary_op, pattern))) + + let translated_pattern = match pattern.downcast_ref::() { + Some(literal) => match literal.value() { + ScalarValue::Utf8(Some(s)) => Arc::new(crate::expressions::Literal::new( + ScalarValue::Utf8(Some(sql_similar_to_regex(s.as_str()))), + )) as Arc, + ScalarValue::LargeUtf8(Some(s)) => Arc::new(crate::expressions::Literal::new( + ScalarValue::LargeUtf8(Some(sql_similar_to_regex(s.as_str()))), + )) as Arc, + ScalarValue::Utf8View(Some(s)) => Arc::new(crate::expressions::Literal::new( + ScalarValue::Utf8View(Some(sql_similar_to_regex(s.as_str()))), + )) as Arc, + ScalarValue::Utf8(None) + | ScalarValue::LargeUtf8(None) + | ScalarValue::Utf8View(None) => pattern, + other => { + return not_impl_err!( + "SIMILAR TO with a non-string literal pattern is not supported: {other:?}" + ); + } + }, + None => { + return not_impl_err!( + "SIMILAR TO with a non-literal pattern is not yet supported" + ); + } + }; + + Ok(Arc::new(BinaryExpr::new( + expr, + binary_op, + translated_pattern, + ))) } #[cfg(test)] @@ -4800,25 +4846,17 @@ mod tests { Ok(()) } - /// Test helper for SIMILAR TO binary operation fn apply_similar_to( schema: &SchemaRef, va: Vec<&str>, - vb: Vec<&str>, + pattern: &str, negated: bool, case_insensitive: bool, expected: &BooleanArray, ) -> Result<()> { let a = StringArray::from(va); - let b = StringArray::from(vb); - let op = similar_to( - negated, - case_insensitive, - col("a", schema)?, - col("b", schema)?, - )?; - let batch = - RecordBatch::try_new(Arc::clone(schema), vec![Arc::new(a), Arc::new(b)])?; + let op = similar_to(negated, case_insensitive, col("a", schema)?, lit(pattern))?; + let batch = RecordBatch::try_new(Arc::clone(schema), vec![Arc::new(a)])?; let result = op .evaluate(&batch)? .into_array(batch.num_rows()) @@ -4830,32 +4868,86 @@ mod tests { #[test] fn test_similar_to() { - let schema = Arc::new(Schema::new(vec![ - Field::new("a", DataType::Utf8, false), - Field::new("b", DataType::Utf8, false), - ])); + let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Utf8, false)])); + // `%` matches any sequence; case-sensitive let expected = [Some(true), Some(false)].iter().collect(); - // case-sensitive apply_similar_to( &schema, vec!["hello world", "Hello World"], - vec!["hello.*", "hello.*"], + "hello%", false, false, &expected, ) .unwrap(); - // case-insensitive + + // `%` matches any sequence; case-insensitive + let expected = [Some(true), Some(false)].iter().collect(); apply_similar_to( &schema, vec!["hello world", "bye"], - vec!["hello.*", "hello.*"], + "hello%", false, true, &expected, ) .unwrap(); + + // `_` matches exactly one character + let expected = [Some(true), Some(false), Some(false)].iter().collect(); + apply_similar_to(&schema, vec!["x", "xy", ""], "_", false, false, &expected) + .unwrap(); + + // Match must cover the entire string (no implicit substring match) + let expected = [Some(false), Some(true)].iter().collect(); + apply_similar_to(&schema, vec!["abc", "a"], "a", false, false, &expected) + .unwrap(); + + // Regex metacharacters pass through unchanged + let expected = [Some(true), Some(true), Some(false)].iter().collect(); + apply_similar_to(&schema, vec!["a", "b", "c"], "a|b", false, false, &expected) + .unwrap(); + } + + #[test] + fn test_similar_to_non_literal_pattern_errors() { + let schema = Arc::new(Schema::new(vec![ + Field::new("a", DataType::Utf8, false), + Field::new("b", DataType::Utf8, false), + ])); + let err = similar_to( + false, + false, + col("a", &schema).unwrap(), + col("b", &schema).unwrap(), + ) + .expect_err("non-literal pattern should error"); + assert!( + err.to_string().contains("non-literal pattern"), + "unexpected error message: {err}" + ); + } + + #[test] + fn test_similar_to_null_pattern() { + let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Utf8, false)])); + let a = StringArray::from(vec!["hello"]); + let op = similar_to( + false, + false, + col("a", &schema).unwrap(), + lit(ScalarValue::Utf8(None)), + ) + .unwrap(); + let batch = RecordBatch::try_new(Arc::clone(&schema), vec![Arc::new(a)]).unwrap(); + let result = op + .evaluate(&batch) + .unwrap() + .into_array(batch.num_rows()) + .unwrap(); + let expected: BooleanArray = [None].iter().collect(); + assert_eq!(result.as_ref(), &expected); } pub fn binary_expr( diff --git a/datafusion/sqllogictest/test_files/strings.slt b/datafusion/sqllogictest/test_files/strings.slt index 9fa453fa02523..177f20653a735 100644 --- a/datafusion/sqllogictest/test_files/strings.slt +++ b/datafusion/sqllogictest/test_files/strings.slt @@ -91,6 +91,27 @@ P1e1 P1m1e1 e1 +# SIMILAR TO with `%` wildcard (zero or more characters) +query T rowsort +SELECT s FROM test WHERE s SIMILAR TO 'p1%'; +---- +p1 +p1e1 +p1m1e1 + +# SIMILAR TO with `_` wildcard (exactly one character) +query T rowsort +SELECT s FROM test WHERE s SIMILAR TO 'p_'; +---- +p1 +p2 + +# SIMILAR TO requires full-string match (no implicit substring) +query T rowsort +SELECT s FROM test WHERE s SIMILAR TO 'p1'; +---- +p1 + # NOT LIKE query T rowsort SELECT s FROM test WHERE s NOT LIKE 'p1%'; From c3a618b3598365161200b05a1d58531e8033f798 Mon Sep 17 00:00:00 2001 From: oc7o Date: Tue, 30 Jun 2026 18:02:21 +0200 Subject: [PATCH 2/3] removed regex passthrough, fixed new line matching and added edge cases --- .../physical-expr/src/expressions/binary.rs | 110 ++++++++++++++++-- .../sqllogictest/test_files/strings.slt | 82 +++++++++---- 2 files changed, 165 insertions(+), 27 deletions(-) diff --git a/datafusion/physical-expr/src/expressions/binary.rs b/datafusion/physical-expr/src/expressions/binary.rs index e4819892c4c88..07adc39731f43 100644 --- a/datafusion/physical-expr/src/expressions/binary.rs +++ b/datafusion/physical-expr/src/expressions/binary.rs @@ -1099,14 +1099,28 @@ pub fn binary( Ok(Arc::new(BinaryExpr::new(lhs, op, rhs))) } +fn regex_escape_char(ch: char) -> &'static str { + match ch { + '\\' | '.' | '+' | '*' | '?' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' + | '$' => "\\", + _ => "", + } +} + +// Translates a SQL `SIMILAR TO` pattern to a Rust regex. Only `%` and `_` +// are wildcards; everything else is a literal. The wildcard translations +// are wrapped in `(?s:...)` so newlines match. fn sql_similar_to_regex(pattern: &str) -> String { - let mut result = String::with_capacity(pattern.len() + 6); + let mut result = String::with_capacity(pattern.len() + 10); result.push_str("^(?:"); for ch in pattern.chars() { match ch { - '%' => result.push_str(".*"), - '_' => result.push('.'), - c => result.push(c), + '%' => result.push_str("(?s:.*)"), + '_' => result.push_str("(?s:.)"), + c => { + result.push_str(regex_escape_char(c)); + result.push(c); + } } } result.push_str(")$"); @@ -4904,9 +4918,91 @@ mod tests { apply_similar_to(&schema, vec!["abc", "a"], "a", false, false, &expected) .unwrap(); - // Regex metacharacters pass through unchanged - let expected = [Some(true), Some(true), Some(false)].iter().collect(); - apply_similar_to(&schema, vec!["a", "b", "c"], "a|b", false, false, &expected) + // `%` matches zero or more, so the empty string matches. + let expected = [Some(true), Some(true)].iter().collect(); + apply_similar_to(&schema, vec!["", "anything"], "%", false, false, &expected) + .unwrap(); + + // `_` requires exactly one character, so the empty string does not + // match. + let expected = [Some(false), Some(true)].iter().collect(); + apply_similar_to(&schema, vec!["", "x"], "_", false, false, &expected).unwrap(); + + // `%` at the start of the pattern is still anchored: the string + // must end where the trailing literal begins. + let expected = [Some(true), Some(false)].iter().collect(); + apply_similar_to(&schema, vec!["abc", "abd"], "%c", false, false, &expected) + .unwrap(); + + // `%` and `_` together: `%` matches zero or more (including the + // empty string), `_` matches exactly one character. + let expected = [Some(true), Some(true)].iter().collect(); + apply_similar_to(&schema, vec!["a", "abc"], "a%", false, false, &expected) + .unwrap(); + let expected = [Some(true), Some(false)].iter().collect(); + apply_similar_to(&schema, vec!["axb", "abc"], "a_b", false, false, &expected) + .unwrap(); + } + + // Regression: regex metacharacters in a SIMILAR TO pattern must be + // treated as SQL literals, not as regex operators. Without escaping, + // `a.` would match any `a` followed by any character (`ab`, `a1`, ...). + #[test] + fn test_similar_to_regex_metachars_are_literals() { + let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Utf8, false)])); + + // `.` is a literal, not the regex "any character" operator. + let expected = [Some(true), Some(false), Some(false)].iter().collect(); + apply_similar_to( + &schema, + vec!["a.", "ab", "a"], + "a.", + false, + false, + &expected, + ) + .unwrap(); + + // `^` and `$` are literals and only match the literal `^` and `$`. + let expected = [Some(true), Some(false)].iter().collect(); + apply_similar_to(&schema, vec!["^x$", "x"], r"^x$", false, false, &expected) + .unwrap(); + + // `|`, `(`, `)`, `*`, `+`, `?`, `[`, `]`, `{`, `}`, `\` are all + // literals too. + let expected = [Some(true), Some(false)].iter().collect(); + apply_similar_to(&schema, vec!["a|b", "a"], "a|b", false, false, &expected) + .unwrap(); + let expected = [Some(true), Some(false)].iter().collect(); + apply_similar_to(&schema, vec!["(a)", "a"], "(a)", false, false, &expected) + .unwrap(); + let expected = [Some(true), Some(false)].iter().collect(); + apply_similar_to(&schema, vec!["a*", "aa"], "a*", false, false, &expected) + .unwrap(); + let expected = [Some(true), Some(false)].iter().collect(); + apply_similar_to(&schema, vec!["[ab]", "a"], "[ab]", false, false, &expected) + .unwrap(); + let expected = [Some(true), Some(false)].iter().collect(); + apply_similar_to(&schema, vec![r"a\d", "ad"], r"a\d", false, false, &expected) + .unwrap(); + } + + // Regression: `%` and `_` must match newlines, matching SQL semantics + // where these wildcards match "any character". + #[test] + fn test_similar_to_wildcards_match_newlines() { + let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Utf8, false)])); + + // `%` crosses a newline. (`%` also matches zero characters, so `ab` + // matches `a%b` as well.) + let expected = [Some(true), Some(true)].iter().collect(); + apply_similar_to(&schema, vec!["a\nb", "ab"], "a%b", false, false, &expected) + .unwrap(); + + // `_` matches a single newline. (`_` requires exactly one character, + // so `ab` does not match `a_b`.) + let expected = [Some(true), Some(false)].iter().collect(); + apply_similar_to(&schema, vec!["a\nb", "ab"], "a_b", false, false, &expected) .unwrap(); } diff --git a/datafusion/sqllogictest/test_files/strings.slt b/datafusion/sqllogictest/test_files/strings.slt index 177f20653a735..d2cd2cb59f47d 100644 --- a/datafusion/sqllogictest/test_files/strings.slt +++ b/datafusion/sqllogictest/test_files/strings.slt @@ -71,26 +71,6 @@ p2 p2e1 p2m1e1 -# SIMILAR TO -query T rowsort -SELECT s FROM test WHERE s SIMILAR TO 'p[12].*'; ----- -p1 -p1e1 -p1m1e1 -p2 -p2e1 -p2m1e1 - -# NOT SIMILAR TO -query T rowsort -SELECT s FROM test WHERE s NOT SIMILAR TO 'p[12].*'; ----- -P1 -P1e1 -P1m1e1 -e1 - # SIMILAR TO with `%` wildcard (zero or more characters) query T rowsort SELECT s FROM test WHERE s SIMILAR TO 'p1%'; @@ -112,6 +92,68 @@ SELECT s FROM test WHERE s SIMILAR TO 'p1'; ---- p1 +# SIMILAR TO treats regex metacharacters as literals (`. ^ $ | * + ? ( ) [ ] \`). +query B +SELECT 'a.' SIMILAR TO 'a.'; +---- +true + +query B +SELECT 'ab' SIMILAR TO 'a.'; +---- +false + +query B +SELECT 'a1' SIMILAR TO 'a.'; +---- +false + +query B +SELECT '^x$' SIMILAR TO '^x$'; +---- +true + +query B +SELECT 'x' SIMILAR TO '^x$'; +---- +false + +query B +SELECT 'a|b' SIMILAR TO 'a|b'; +---- +true + +query B +SELECT 'a' SIMILAR TO 'a|b'; +---- +false + +query B +SELECT 'a*' SIMILAR TO 'a*'; +---- +true + +query B +SELECT 'aa' SIMILAR TO 'a*'; +---- +false + +# SIMILAR TO wildcards match newlines. +query B +SELECT 'a' || chr(10) || 'b' SIMILAR TO 'a%b'; +---- +true + +query B +SELECT 'a' || chr(10) || 'b' SIMILAR TO 'a_b'; +---- +true + +query B +SELECT 'ab' SIMILAR TO 'a_b'; +---- +false + # NOT LIKE query T rowsort SELECT s FROM test WHERE s NOT LIKE 'p1%'; From 5d9faab490c43a8718ca663a341f56100dfb3a2d Mon Sep 17 00:00:00 2001 From: oc7o Date: Tue, 30 Jun 2026 18:45:25 +0200 Subject: [PATCH 3/3] re-added supported regex chars to be passed to arrow --- .../physical-expr/src/expressions/binary.rs | 109 +++++++++++++----- .../sqllogictest/test_files/strings.slt | 77 +++++++++++-- 2 files changed, 150 insertions(+), 36 deletions(-) diff --git a/datafusion/physical-expr/src/expressions/binary.rs b/datafusion/physical-expr/src/expressions/binary.rs index 07adc39731f43..87d41cd837ec6 100644 --- a/datafusion/physical-expr/src/expressions/binary.rs +++ b/datafusion/physical-expr/src/expressions/binary.rs @@ -1099,28 +1099,35 @@ pub fn binary( Ok(Arc::new(BinaryExpr::new(lhs, op, rhs))) } -fn regex_escape_char(ch: char) -> &'static str { - match ch { - '\\' | '.' | '+' | '*' | '?' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' - | '$' => "\\", - _ => "", - } -} - -// Translates a SQL `SIMILAR TO` pattern to a Rust regex. Only `%` and `_` -// are wildcards; everything else is a literal. The wildcard translations -// are wrapped in `(?s:...)` so newlines match. +// Translates a SQL `SIMILAR TO` pattern to a Rust regex. `%` and `_` are +// LIKE-style wildcards (wrapped in `(?s:...)` so they match newlines). +// The POSIX metacharacters `| * + ? ( ) { } [ ]` pass through to the +// regex. `. ^ $ \` are SQL literals and are escaped. fn sql_similar_to_regex(pattern: &str) -> String { let mut result = String::with_capacity(pattern.len() + 10); result.push_str("^(?:"); + let mut in_bracket = false; for ch in pattern.chars() { - match ch { - '%' => result.push_str("(?s:.*)"), - '_' => result.push_str("(?s:.)"), - c => { - result.push_str(regex_escape_char(c)); - result.push(c); + match (ch, in_bracket) { + ('%', false) => result.push_str("(?s:.*)"), + ('_', false) => result.push_str("(?s:.)"), + ('[', false) => { + result.push('['); + in_bracket = true; + } + (']', true) => { + result.push(']'); + in_bracket = false; } + // `. ^ $` are SQL literals but regex metachars when not inside + // a `[...]` bracket expression (inside one, regex already treats + // them as literals). `\` is a regex escape character in all + // positions, so it always needs escaping. + ('.' | '^' | '$', false) | ('\\', _) => { + result.push('\\'); + result.push(ch); + } + (c, _) => result.push(c), } } result.push_str(")$"); @@ -4944,11 +4951,11 @@ mod tests { .unwrap(); } - // Regression: regex metacharacters in a SIMILAR TO pattern must be - // treated as SQL literals, not as regex operators. Without escaping, - // `a.` would match any `a` followed by any character (`ab`, `a1`, ...). + // Regression: regex metacharacters that are NOT SIMILAR TO metacharacters + // (`. ^ $ \`) must be treated as SQL literals. Without escaping, `a.` + // would match any `a` followed by any character (`ab`, `a1`, ...). #[test] - fn test_similar_to_regex_metachars_are_literals() { + fn test_similar_to_sql_literal_metachars() { let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Utf8, false)])); // `.` is a literal, not the regex "any character" operator. @@ -4968,22 +4975,68 @@ mod tests { apply_similar_to(&schema, vec!["^x$", "x"], r"^x$", false, false, &expected) .unwrap(); - // `|`, `(`, `)`, `*`, `+`, `?`, `[`, `]`, `{`, `}`, `\` are all - // literals too. + // `\` is a literal backslash (we don't support the ESCAPE clause). let expected = [Some(true), Some(false)].iter().collect(); - apply_similar_to(&schema, vec!["a|b", "a"], "a|b", false, false, &expected) + apply_similar_to(&schema, vec![r"a\b", "ab"], r"a\b", false, false, &expected) .unwrap(); + } + + // SIMILAR TO borrows POSIX metacharacters from regular expressions: + // `| * + ? ( ) { } [ ]`. The translator passes them through to the + // underlying regex engine. + #[test] + fn test_similar_to_posix_metachars() { + let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Utf8, false)])); + + // `|` alternation. + let expected = [Some(true), Some(false), Some(true)].iter().collect(); + apply_similar_to(&schema, vec!["a", "c", "b"], "a|b", false, false, &expected) + .unwrap(); + + // `*` zero or more. + let expected = [Some(true), Some(true), Some(false)].iter().collect(); + apply_similar_to(&schema, vec!["", "aa", "ab"], "a*", false, false, &expected) + .unwrap(); + + // `+` one or more. + let expected = [Some(false), Some(true)].iter().collect(); + apply_similar_to(&schema, vec!["", "aa"], "a+", false, false, &expected).unwrap(); + + // `?` zero or one. + let expected = [Some(true), Some(true), Some(false)].iter().collect(); + apply_similar_to(&schema, vec!["", "a", "aa"], "a?", false, false, &expected) + .unwrap(); + + // `()` grouping. + let expected = [Some(true), Some(true), Some(false)].iter().collect(); + apply_similar_to( + &schema, + vec!["ab", "abc", "ac"], + "(ab)c?", + false, + false, + &expected, + ) + .unwrap(); + + // `{m}` exact count. let expected = [Some(true), Some(false)].iter().collect(); - apply_similar_to(&schema, vec!["(a)", "a"], "(a)", false, false, &expected) + apply_similar_to(&schema, vec!["aaa", "aa"], "a{3}", false, false, &expected) .unwrap(); + + // `[...]` character class. let expected = [Some(true), Some(false)].iter().collect(); - apply_similar_to(&schema, vec!["a*", "aa"], "a*", false, false, &expected) + apply_similar_to(&schema, vec!["a", "c"], "[ab]", false, false, &expected) .unwrap(); + + // `[^...]` negated character class. let expected = [Some(true), Some(false)].iter().collect(); - apply_similar_to(&schema, vec!["[ab]", "a"], "[ab]", false, false, &expected) + apply_similar_to(&schema, vec!["c", "a"], "[^ab]", false, false, &expected) .unwrap(); + + // `[a-z]` range inside a character class. let expected = [Some(true), Some(false)].iter().collect(); - apply_similar_to(&schema, vec![r"a\d", "ad"], r"a\d", false, false, &expected) + apply_similar_to(&schema, vec!["m", "1"], "[a-z]", false, false, &expected) .unwrap(); } diff --git a/datafusion/sqllogictest/test_files/strings.slt b/datafusion/sqllogictest/test_files/strings.slt index d2cd2cb59f47d..908559021b2a0 100644 --- a/datafusion/sqllogictest/test_files/strings.slt +++ b/datafusion/sqllogictest/test_files/strings.slt @@ -92,7 +92,7 @@ SELECT s FROM test WHERE s SIMILAR TO 'p1'; ---- p1 -# SIMILAR TO treats regex metacharacters as literals (`. ^ $ | * + ? ( ) [ ] \`). +# SIMILAR TO treats `. ^ $` as SQL literals (not as regex metachars). query B SELECT 'a.' SIMILAR TO 'a.'; ---- @@ -104,37 +104,98 @@ SELECT 'ab' SIMILAR TO 'a.'; false query B -SELECT 'a1' SIMILAR TO 'a.'; +SELECT '^x$' SIMILAR TO '^x$'; +---- +true + +query B +SELECT 'x' SIMILAR TO '^x$'; ---- false +# SIMILAR TO supports the POSIX metacharacters `| * + ? ( ) { } [ ]`. query B -SELECT '^x$' SIMILAR TO '^x$'; +SELECT 'a' SIMILAR TO 'a|b'; ---- true query B -SELECT 'x' SIMILAR TO '^x$'; +SELECT 'c' SIMILAR TO 'a|b'; ---- false query B -SELECT 'a|b' SIMILAR TO 'a|b'; +SELECT '' SIMILAR TO 'a*'; ---- true query B -SELECT 'a' SIMILAR TO 'a|b'; +SELECT 'aa' SIMILAR TO 'a*'; +---- +true + +query B +SELECT 'ab' SIMILAR TO 'a*'; ---- false query B -SELECT 'a*' SIMILAR TO 'a*'; +SELECT 'a' SIMILAR TO 'a+'; ---- true query B -SELECT 'aa' SIMILAR TO 'a*'; +SELECT '' SIMILAR TO 'a+'; +---- +false + +query B +SELECT '' SIMILAR TO 'a?'; +---- +true + +query B +SELECT 'a' SIMILAR TO 'a?'; +---- +true + +query B +SELECT 'aa' SIMILAR TO 'a?'; +---- +false + +query B +SELECT 'ab' SIMILAR TO '(ab)'; +---- +true + +query B +SELECT 'a' SIMILAR TO 'a{2}'; +---- +false + +query B +SELECT 'aa' SIMILAR TO 'a{2}'; +---- +true + +query B +SELECT 'a' SIMILAR TO '[ab]'; +---- +true + +query B +SELECT 'c' SIMILAR TO '[ab]'; +---- +false + +query B +SELECT 'c' SIMILAR TO '[^ab]'; +---- +true + +query B +SELECT 'a' SIMILAR TO '[^ab]'; ---- false