1use std::collections::{BTreeSet, HashMap, HashSet};
21
22use crate::analyzer::type_coercion::TypeCoercionRewriter;
23use arrow::array::{Array, RecordBatch, new_null_array};
24use arrow::datatypes::{DataType, Field, Schema};
25use datafusion_common::TableReference;
26use datafusion_common::cast::as_boolean_array;
27use datafusion_common::tree_node::{TransformedResult, TreeNode, TreeNodeRecursion};
28use datafusion_common::{Column, DFSchema, Result, ScalarValue};
29use datafusion_expr::execution_props::ExecutionProps;
30use datafusion_expr::expr::{Exists, InSubquery, SetComparison};
31use datafusion_expr::expr_rewriter::replace_col;
32use datafusion_expr::physical_planning_context::PhysicalPlanningContext;
33use datafusion_expr::{ColumnarValue, Expr, logical_plan::LogicalPlan};
34use datafusion_physical_expr::create_physical_expr;
35use log::{debug, trace};
36use std::sync::Arc;
37
38pub use datafusion_expr::expr_rewriter::NamePreserver;
41
42pub(crate) fn for_each_referenced_index(
58 expr: &Expr,
59 schema: &DFSchema,
60 mut f: impl FnMut(usize),
61) -> Result<()> {
62 visit_referenced_indices(expr, schema, &mut f)
63}
64
65fn visit_referenced_indices(
66 expr: &Expr,
67 schema: &DFSchema,
68 f: &mut dyn FnMut(usize),
69) -> Result<()> {
70 expr.apply(|expr| {
71 match expr {
72 Expr::Column(column) | Expr::OuterReferenceColumn(_, column) => {
73 if let Some(idx) = schema.maybe_index_of_column(column) {
74 f(idx);
75 }
76 }
77 Expr::Exists(Exists { subquery, .. })
78 | Expr::InSubquery(InSubquery { subquery, .. })
79 | Expr::SetComparison(SetComparison { subquery, .. })
80 | Expr::ScalarSubquery(subquery) => {
81 for outer in &subquery.outer_ref_columns {
82 visit_referenced_indices(outer, schema, f)?;
83 }
84 }
85 _ => {}
86 }
87 Ok(TreeNodeRecursion::Continue)
88 })?;
89 Ok(())
90}
91
92pub(crate) fn has_all_column_refs(
94 expr: &Expr,
95 schema_cols: &HashSet<ColumnReference>,
96) -> bool {
97 let column_refs = expr.column_refs();
98 column_refs
100 .iter()
101 .filter(|c| {
102 schema_cols.contains(&ColumnReference::new(c.relation.as_ref(), c.name()))
103 })
104 .count()
105 == column_refs.len()
106}
107
108pub(crate) fn replace_qualified_name(
109 expr: Expr,
110 cols: &BTreeSet<Column>,
111 subquery_alias: &str,
112) -> Result<Expr> {
113 let alias_cols: Vec<Column> = cols
114 .iter()
115 .map(|col| Column::new(Some(subquery_alias), &col.name))
116 .collect();
117 let replace_map: HashMap<&Column, &Column> =
118 cols.iter().zip(alias_cols.iter()).collect();
119
120 replace_col(expr, &replace_map)
121}
122
123#[derive(PartialEq, Eq, Hash, Debug)]
125pub(crate) struct ColumnReference<'a> {
126 pub relation: Option<&'a TableReference>,
127 pub name: &'a str,
128}
129
130impl<'a> ColumnReference<'a> {
131 pub fn new(relation: Option<&'a TableReference>, name: &'a str) -> Self {
132 Self { relation, name }
133 }
134
135 pub fn new_unqualified(name: &'a str) -> Self {
136 Self {
137 relation: None,
138 name,
139 }
140 }
141}
142
143pub(crate) fn schema_columns<'a>(schema: &'a DFSchema) -> HashSet<ColumnReference<'a>> {
145 schema
146 .iter()
147 .flat_map(|(qualifier, field)| {
148 [
149 ColumnReference::new(qualifier, field.name()),
150 ColumnReference::new_unqualified(field.name()),
152 ]
153 })
154 .collect::<HashSet<_>>()
155}
156
157pub fn log_plan(description: &str, plan: &LogicalPlan) {
159 debug!("{description}:\n{}\n", plan.display_indent());
160 trace!("{description}::\n{}\n", plan.display_indent_schema());
161}
162
163pub fn is_restrict_null_predicate<'a>(
167 predicate: Expr,
168 join_cols_of_predicate: impl IntoIterator<Item = &'a Column>,
169) -> Result<bool> {
170 if matches!(predicate, Expr::Column(_)) {
171 return Ok(true);
172 }
173
174 Ok(
177 match evaluate_expr_with_null_column(predicate, join_cols_of_predicate)? {
178 ColumnarValue::Array(array) => {
179 if array.len() == 1 {
180 let boolean_array = as_boolean_array(&array)?;
181 boolean_array.is_null(0) || !boolean_array.value(0)
182 } else {
183 false
184 }
185 }
186 ColumnarValue::Scalar(scalar) => matches!(
187 scalar,
188 ScalarValue::Boolean(None) | ScalarValue::Boolean(Some(false))
189 ),
190 },
191 )
192}
193
194pub fn evaluates_to_null<'a>(
199 predicate: Expr,
200 null_columns: impl IntoIterator<Item = &'a Column>,
201) -> Result<bool> {
202 if matches!(predicate, Expr::Column(_)) {
203 return Ok(true);
204 }
205
206 Ok(
207 match evaluate_expr_with_null_column(predicate, null_columns)? {
208 ColumnarValue::Array(_) => false,
209 ColumnarValue::Scalar(scalar) => scalar.is_null(),
210 },
211 )
212}
213
214fn evaluate_expr_with_null_column<'a>(
215 predicate: Expr,
216 null_columns: impl IntoIterator<Item = &'a Column>,
217) -> Result<ColumnarValue> {
218 static DUMMY_COL_NAME: &str = "?";
219 let schema = Arc::new(Schema::new(vec![Field::new(
220 DUMMY_COL_NAME,
221 DataType::Null,
222 true,
223 )]));
224 let input_schema = DFSchema::try_from(Arc::clone(&schema))?;
225 let column = new_null_array(&DataType::Null, 1);
226 let input_batch = RecordBatch::try_new(schema, vec![column])?;
227 let execution_props = ExecutionProps::default();
228 let null_column = Column::from_name(DUMMY_COL_NAME);
229
230 let join_cols_to_replace = null_columns
231 .into_iter()
232 .map(|column| (column, &null_column))
233 .collect::<HashMap<_, _>>();
234
235 let replaced_predicate = replace_col(predicate, &join_cols_to_replace)?;
236 let coerced_predicate = coerce(replaced_predicate, &input_schema)?;
237 create_physical_expr(
238 &coerced_predicate,
239 &input_schema,
240 &execution_props,
241 &PhysicalPlanningContext::default(),
242 )?
243 .evaluate(&input_batch)
244}
245
246fn coerce(expr: Expr, schema: &DFSchema) -> Result<Expr> {
247 let mut expr_rewrite = TypeCoercionRewriter { schema };
248 expr.rewrite(&mut expr_rewrite).data()
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254 use datafusion_expr::{Operator, binary_expr, case, col, in_list, is_null, lit};
255
256 #[test]
257 fn expr_is_restrict_null_predicate() -> Result<()> {
258 let test_cases = vec![
259 (col("a"), true),
261 (is_null(col("a")), false),
263 (Expr::IsNotNull(Box::new(col("a"))), true),
265 (
267 binary_expr(
268 col("a"),
269 Operator::Eq,
270 Expr::Literal(ScalarValue::Null, None),
271 ),
272 true,
273 ),
274 (binary_expr(col("a"), Operator::Gt, lit(8i64)), true),
276 (binary_expr(col("a"), Operator::LtEq, lit(8i32)), true),
278 (
280 case(col("a"))
281 .when(lit(1i64), lit(true))
282 .when(lit(0i64), lit(false))
283 .otherwise(lit(ScalarValue::Null))?,
284 true,
285 ),
286 (
288 case(col("a"))
289 .when(lit(1i64), lit(true))
290 .otherwise(lit(false))?,
291 true,
292 ),
293 (
295 case(col("a"))
296 .when(lit(0i64), lit(false))
297 .otherwise(lit(true))?,
298 false,
299 ),
300 (
302 binary_expr(
303 case(col("a"))
304 .when(lit(0i64), lit(false))
305 .otherwise(lit(true))?,
306 Operator::Or,
307 lit(false),
308 ),
309 false,
310 ),
311 (
313 binary_expr(
314 case(col("a"))
315 .when(lit(0i64), lit(true))
316 .otherwise(lit(false))?,
317 Operator::Or,
318 lit(false),
319 ),
320 true,
321 ),
322 (
324 in_list(col("a"), vec![lit(1i64), lit(2i64), lit(3i64)], false),
325 true,
326 ),
327 (
329 in_list(col("a"), vec![lit(1i64), lit(2i64), lit(3i64)], true),
330 true,
331 ),
332 (
334 in_list(
335 col("a"),
336 vec![Expr::Literal(ScalarValue::Null, None)],
337 false,
338 ),
339 true,
340 ),
341 (
343 in_list(col("a"), vec![Expr::Literal(ScalarValue::Null, None)], true),
344 true,
345 ),
346 ];
347
348 let column_a = Column::from_name("a");
349 for (predicate, expected) in test_cases {
350 let join_cols_of_predicate = std::iter::once(&column_a);
351 let actual =
352 is_restrict_null_predicate(predicate.clone(), join_cols_of_predicate)?;
353 assert_eq!(actual, expected, "{predicate}");
354 }
355
356 Ok(())
357 }
358}