Convert `column LIKE literal` where P is a constant prefix of the literal to a range check on the column: `P <= column && column < P'`, where P' is the lowest string after all P* strings.
(
expr_builder: &mut PruningExpressionBuilder,
)
| 1820 | /// to a range check on the column: `P <= column && column < P'`, where P' is the |
| 1821 | /// lowest string after all P* strings. |
| 1822 | fn build_like_match( |
| 1823 | expr_builder: &mut PruningExpressionBuilder, |
| 1824 | ) -> Option<Arc<dyn PhysicalExpr>> { |
| 1825 | // column LIKE literal => (min, max) LIKE literal split at unescaped % => min <= split literal && split literal <= max |
| 1826 | // column LIKE 'foo%' => min <= 'foo' && 'foo' <= max |
| 1827 | // column LIKE 'foo\_%' => min <= 'foo_' && 'foo_' <= max (the _ is escaped) |
| 1828 | // column LIKE 'foo\%%' => min <= 'foo%' && 'foo%' <= max (the % is escaped) |
| 1829 | // column LIKE '%foo' => min <= '' && '' <= max => true |
| 1830 | // column LIKE '%foo%' => min <= '' && '' <= max => true |
| 1831 | // column LIKE 'foo' => min <= 'foo' && 'foo' <= max |
| 1832 | |
| 1833 | // TODO Handle ILIKE perhaps by making the min lowercase and max uppercase |
| 1834 | // this may involve building the physical expressions that call lower() and upper() |
| 1835 | let min_column_expr = expr_builder.min_column_expr().ok()?; |
| 1836 | let max_column_expr = expr_builder.max_column_expr().ok()?; |
| 1837 | let scalar_expr = expr_builder.scalar_expr(); |
| 1838 | // check that the scalar is a string literal |
| 1839 | let s = extract_string_literal(scalar_expr)?; |
| 1840 | // ANSI SQL specifies two wildcards: % and _. % matches zero or more characters, _ matches exactly one character. |
| 1841 | let (decoded_prefix, rest) = split_constant_prefix(s); |
| 1842 | let has_wildcard = !rest.is_empty(); |
| 1843 | if has_wildcard && decoded_prefix.is_empty() { |
| 1844 | // there's no filtering we could possibly do, return None and have this be handled by the unhandled hook |
| 1845 | return None; |
| 1846 | } |
| 1847 | let (lower_bound, upper_bound) = if has_wildcard { |
| 1848 | let incremented_prefix = increment_utf8(&decoded_prefix)?; |
| 1849 | let lower_bound_lit = Arc::new(phys_expr::Literal::new(ScalarValue::Utf8(Some( |
| 1850 | decoded_prefix, |
| 1851 | )))); |
| 1852 | let upper_bound_lit = Arc::new(phys_expr::Literal::new(ScalarValue::Utf8(Some( |
| 1853 | incremented_prefix, |
| 1854 | )))); |
| 1855 | (lower_bound_lit, upper_bound_lit) |
| 1856 | } else { |
| 1857 | // the like expression is a literal and can be converted into a comparison |
| 1858 | let bound = Arc::new(phys_expr::Literal::new(ScalarValue::Utf8(Some( |
| 1859 | decoded_prefix, |
| 1860 | )))); |
| 1861 | (Arc::clone(&bound), bound) |
| 1862 | }; |
| 1863 | let lower_bound_expr = Arc::new(phys_expr::BinaryExpr::new( |
| 1864 | lower_bound, |
| 1865 | Operator::LtEq, |
| 1866 | Arc::clone(&max_column_expr), |
| 1867 | )); |
| 1868 | let upper_bound_expr = Arc::new(phys_expr::BinaryExpr::new( |
| 1869 | Arc::clone(&min_column_expr), |
| 1870 | Operator::LtEq, |
| 1871 | upper_bound, |
| 1872 | )); |
| 1873 | let combined = Arc::new(phys_expr::BinaryExpr::new( |
| 1874 | upper_bound_expr, |
| 1875 | Operator::And, |
| 1876 | lower_bound_expr, |
| 1877 | )); |
| 1878 | Some(combined) |
| 1879 | } |
no test coverage detected
searching dependent graphs…