Plans `needle ALL(haystack)` with proper SQL NULL semantics. CASE/WHEN structure: WHEN arr IS NULL → NULL WHEN empty → TRUE WHEN lhs IS NULL → NULL WHEN decisive_condition → FALSE WHEN has_nulls → NULL ELSE → TRUE
(
needle: &Expr,
haystack: &Expr,
compare_op: &BinaryOperator,
)
| 1313 | /// WHEN has_nulls → NULL |
| 1314 | /// ELSE → TRUE |
| 1315 | fn plan_all_op( |
| 1316 | needle: &Expr, |
| 1317 | haystack: &Expr, |
| 1318 | compare_op: &BinaryOperator, |
| 1319 | ) -> Result<Expr> { |
| 1320 | let null_arr_check = haystack.clone().is_null(); |
| 1321 | let empty_check = cardinality(haystack.clone()).eq(lit(0u64)); |
| 1322 | let null_lhs_check = needle.clone().is_null(); |
| 1323 | // DataFusion's array_position uses is_null() checks internally (not equality), |
| 1324 | // so it can locate NULL elements even though NULL = NULL is NULL in standard SQL. |
| 1325 | let has_nulls = |
| 1326 | array_position(haystack.clone(), lit(ScalarValue::Null), lit(1i64)).is_not_null(); |
| 1327 | |
| 1328 | let decisive_condition = match compare_op { |
| 1329 | BinaryOperator::NotEq => array_has(haystack.clone(), needle.clone()), |
| 1330 | BinaryOperator::Eq => { |
| 1331 | let all_equal = array_min(haystack.clone()) |
| 1332 | .eq(needle.clone()) |
| 1333 | .and(array_max(haystack.clone()).eq(needle.clone())); |
| 1334 | Expr::Not(Box::new(all_equal)) |
| 1335 | } |
| 1336 | BinaryOperator::Gt => { |
| 1337 | Expr::Not(Box::new(needle.clone().gt(array_max(haystack.clone())))) |
| 1338 | } |
| 1339 | BinaryOperator::Lt => { |
| 1340 | Expr::Not(Box::new(needle.clone().lt(array_min(haystack.clone())))) |
| 1341 | } |
| 1342 | BinaryOperator::GtEq => { |
| 1343 | Expr::Not(Box::new(needle.clone().gt_eq(array_max(haystack.clone())))) |
| 1344 | } |
| 1345 | BinaryOperator::LtEq => { |
| 1346 | Expr::Not(Box::new(needle.clone().lt_eq(array_min(haystack.clone())))) |
| 1347 | } |
| 1348 | _ => { |
| 1349 | return plan_err!( |
| 1350 | "Unsupported AllOp: '{compare_op}', only '=', '<>', '>', '<', '>=', '<=' are supported" |
| 1351 | ); |
| 1352 | } |
| 1353 | }; |
| 1354 | |
| 1355 | let null_bool = lit(ScalarValue::Boolean(None)); |
| 1356 | when(null_arr_check, null_bool.clone()) |
| 1357 | .when(empty_check, lit(true)) |
| 1358 | .when(null_lhs_check, null_bool.clone()) |
| 1359 | .when(decisive_condition, lit(false)) |
| 1360 | .when(has_nulls, null_bool) |
| 1361 | .otherwise(lit(true)) |
| 1362 | } |
| 1363 | |
| 1364 | #[cfg(test)] |
| 1365 | mod tests { |
no test coverage detected
searching dependent graphs…