For predicate `col NOT LIKE 'const_prefix%'`, we rewrite it as `(col_min NOT LIKE 'const_prefix%' OR col_max NOT LIKE 'const_prefix%')`. The intuition is that if both `col_min` and `col_max` begin with `const_prefix` that means all** data in this row group begins with `const_prefix` as well (and therefore the predicate looking for rows that don't begin with `const_prefix` can never be true)
(
expr_builder: &mut PruningExpressionBuilder<'_>,
)
| 1884 | // **all** data in this row group begins with `const_prefix` as well (and therefore the predicate |
| 1885 | // looking for rows that don't begin with `const_prefix` can never be true) |
| 1886 | fn build_not_like_match( |
| 1887 | expr_builder: &mut PruningExpressionBuilder<'_>, |
| 1888 | ) -> Result<Arc<dyn PhysicalExpr>> { |
| 1889 | // col NOT LIKE 'const_prefix%' -> !(col_min LIKE 'const_prefix%' && col_max LIKE 'const_prefix%') -> (col_min NOT LIKE 'const_prefix%' || col_max NOT LIKE 'const_prefix%') |
| 1890 | |
| 1891 | let min_column_expr = expr_builder.min_column_expr()?; |
| 1892 | let max_column_expr = expr_builder.max_column_expr()?; |
| 1893 | |
| 1894 | let scalar_expr = expr_builder.scalar_expr(); |
| 1895 | |
| 1896 | let pattern = extract_string_literal(scalar_expr).ok_or_else(|| { |
| 1897 | plan_datafusion_err!("cannot extract literal from NOT LIKE expression") |
| 1898 | })?; |
| 1899 | |
| 1900 | let (const_prefix, remaining) = split_constant_prefix(pattern); |
| 1901 | if const_prefix.is_empty() || remaining != "%" { |
| 1902 | // we can not handle `%` at the beginning or in the middle of the pattern |
| 1903 | // Example: For pattern "foo%bar", the row group might include values like |
| 1904 | // ["foobar", "food", "foodbar"], making it unsafe to prune. |
| 1905 | // Even if the min/max values in the group (e.g., "foobar" and "foodbar") |
| 1906 | // match the pattern, intermediate values like "food" may not |
| 1907 | // match the full pattern "foo%bar", making pruning unsafe. |
| 1908 | // (truncate foo%bar to foo% have same problem) |
| 1909 | |
| 1910 | // we can not handle pattern containing `_` |
| 1911 | // Example: For pattern "foo_", row groups might contain ["fooa", "fooaa", "foob"], |
| 1912 | // which means not every row is guaranteed to match the pattern. |
| 1913 | return Err(plan_datafusion_err!( |
| 1914 | "NOT LIKE expressions only support constant_prefix+wildcard`%`" |
| 1915 | )); |
| 1916 | } |
| 1917 | |
| 1918 | let min_col_not_like_epxr = Arc::new(phys_expr::LikeExpr::new( |
| 1919 | true, |
| 1920 | false, |
| 1921 | Arc::clone(&min_column_expr), |
| 1922 | Arc::clone(scalar_expr), |
| 1923 | )); |
| 1924 | |
| 1925 | let max_col_not_like_expr = Arc::new(phys_expr::LikeExpr::new( |
| 1926 | true, |
| 1927 | false, |
| 1928 | Arc::clone(&max_column_expr), |
| 1929 | Arc::clone(scalar_expr), |
| 1930 | )); |
| 1931 | |
| 1932 | Ok(Arc::new(phys_expr::BinaryExpr::new( |
| 1933 | min_col_not_like_epxr, |
| 1934 | Operator::Or, |
| 1935 | max_col_not_like_expr, |
| 1936 | ))) |
| 1937 | } |
| 1938 | |
| 1939 | /// Returns unescaped constant prefix of a LIKE pattern (possibly empty) and the remaining pattern (possibly empty) |
| 1940 | fn split_constant_prefix(pattern: &str) -> (String, &str) { |
no test coverage detected
searching dependent graphs…