blob: 745ae855efee28431d5b5881cc7c6064fe282315 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
mod guarantee;
pub use guarantee::{Guarantee, LiteralGuarantee};
use std::borrow::Borrow;
use std::sync::Arc;
use crate::expressions::{BinaryExpr, Column};
use crate::tree_node::ExprContext;
use crate::PhysicalExpr;
use crate::PhysicalSortExpr;
use arrow::datatypes::Schema;
use datafusion_common::tree_node::{
Transformed, TransformedResult, TreeNode, TreeNodeRecursion,
};
use datafusion_common::{HashMap, HashSet, Result};
use datafusion_expr::Operator;
use petgraph::graph::NodeIndex;
use petgraph::stable_graph::StableGraph;
/// Assume the predicate is in the form of CNF, split the predicate to a Vec of PhysicalExprs.
///
/// For example, split "a1 = a2 AND b1 <= b2 AND c1 != c2" into ["a1 = a2", "b1 <= b2", "c1 != c2"]
pub fn split_conjunction(
predicate: &Arc<dyn PhysicalExpr>,
) -> Vec<&Arc<dyn PhysicalExpr>> {
split_impl(Operator::And, predicate, vec![])
}
/// Create a conjunction of the given predicates.
/// If the input is empty, return a literal true.
/// If the input contains a single predicate, return the predicate.
/// Otherwise, return a conjunction of the predicates (e.g. `a AND b AND c`).
pub fn conjunction(
predicates: impl IntoIterator<Item = Arc<dyn PhysicalExpr>>,
) -> Arc<dyn PhysicalExpr> {
conjunction_opt(predicates).unwrap_or_else(|| crate::expressions::lit(true))
}
/// Create a conjunction of the given predicates.
/// If the input is empty or the return None.
/// If the input contains a single predicate, return Some(predicate).
/// Otherwise, return a Some(..) of a conjunction of the predicates (e.g. `Some(a AND b AND c)`).
pub fn conjunction_opt(
predicates: impl IntoIterator<Item = Arc<dyn PhysicalExpr>>,
) -> Option<Arc<dyn PhysicalExpr>> {
predicates
.into_iter()
.fold(None, |acc, predicate| match acc {
None => Some(predicate),
Some(acc) => Some(Arc::new(BinaryExpr::new(acc, Operator::And, predicate))),
})
}
/// Assume the predicate is in the form of DNF, split the predicate to a Vec of PhysicalExprs.
///
/// For example, split "a1 = a2 OR b1 <= b2 OR c1 != c2" into ["a1 = a2", "b1 <= b2", "c1 != c2"]
pub fn split_disjunction(
predicate: &Arc<dyn PhysicalExpr>,
) -> Vec<&Arc<dyn PhysicalExpr>> {
split_impl(Operator::Or, predicate, vec![])
}
fn split_impl<'a>(
operator: Operator,
predicate: &'a Arc<dyn PhysicalExpr>,
mut exprs: Vec<&'a Arc<dyn PhysicalExpr>>,
) -> Vec<&'a Arc<dyn PhysicalExpr>> {
match predicate.as_any().downcast_ref::<BinaryExpr>() {
Some(binary) if binary.op() == &operator => {
let exprs = split_impl(operator, binary.left(), exprs);
split_impl(operator, binary.right(), exprs)
}
Some(_) | None => {
exprs.push(predicate);
exprs
}
}
}
/// This function maps back requirement after ProjectionExec
/// to the Executor for its input.
// Specifically, `ProjectionExec` changes index of `Column`s in the schema of its input executor.
// This function changes requirement given according to ProjectionExec schema to the requirement
// according to schema of input executor to the ProjectionExec.
// For instance, Column{"a", 0} would turn to Column{"a", 1}. Please note that this function assumes that
// name of the Column is unique. If we have a requirement such that Column{"a", 0}, Column{"a", 1}.
// This function will produce incorrect result (It will only emit single Column as a result).
pub fn map_columns_before_projection(
parent_required: &[Arc<dyn PhysicalExpr>],
proj_exprs: &[(Arc<dyn PhysicalExpr>, String)],
) -> Vec<Arc<dyn PhysicalExpr>> {
if parent_required.is_empty() {
// No need to build mapping.
return vec![];
}
let column_mapping = proj_exprs
.iter()
.filter_map(|(expr, name)| {
expr.as_any()
.downcast_ref::<Column>()
.map(|column| (name.clone(), column.clone()))
})
.collect::<HashMap<_, _>>();
parent_required
.iter()
.filter_map(|r| {
r.as_any()
.downcast_ref::<Column>()
.and_then(|c| column_mapping.get(c.name()))
})
.map(|e| Arc::new(e.clone()) as _)
.collect()
}
/// This function returns all `Arc<dyn PhysicalExpr>`s inside the given
/// `PhysicalSortExpr` sequence.
pub fn convert_to_expr<T: Borrow<PhysicalSortExpr>>(
sequence: impl IntoIterator<Item = T>,
) -> Vec<Arc<dyn PhysicalExpr>> {
sequence
.into_iter()
.map(|elem| Arc::clone(&elem.borrow().expr))
.collect()
}
/// This function finds the indices of `targets` within `items` using strict
/// equality.
pub fn get_indices_of_exprs_strict<T: Borrow<Arc<dyn PhysicalExpr>>>(
targets: impl IntoIterator<Item = T>,
items: &[Arc<dyn PhysicalExpr>],
) -> Vec<usize> {
targets
.into_iter()
.filter_map(|target| items.iter().position(|e| e.eq(target.borrow())))
.collect()
}
pub type ExprTreeNode<T> = ExprContext<Option<T>>;
/// This struct is used to convert a [`PhysicalExpr`] tree into a DAEG (i.e. an expression
/// DAG) by collecting identical expressions in one node. Caller specifies the node type
/// in the DAEG via the `constructor` argument, which constructs nodes in the DAEG from
/// the [`ExprTreeNode`] ancillary object.
struct PhysicalExprDAEGBuilder<'a, T, F: Fn(&ExprTreeNode<NodeIndex>) -> Result<T>> {
// The resulting DAEG (expression DAG).
graph: StableGraph<T, usize>,
// A vector of visited expression nodes and their corresponding node indices.
visited_plans: Vec<(Arc<dyn PhysicalExpr>, NodeIndex)>,
// A function to convert an input expression node to T.
constructor: &'a F,
}
impl<T, F: Fn(&ExprTreeNode<NodeIndex>) -> Result<T>> PhysicalExprDAEGBuilder<'_, T, F> {
// This method mutates an expression node by transforming it to a physical expression
// and adding it to the graph. The method returns the mutated expression node.
fn mutate(
&mut self,
mut node: ExprTreeNode<NodeIndex>,
) -> Result<Transformed<ExprTreeNode<NodeIndex>>> {
// Get the expression associated with the input expression node.
let expr = &node.expr;
// Check if the expression has already been visited.
let node_idx = match self.visited_plans.iter().find(|(e, _)| expr.eq(e)) {
// If the expression has been visited, return the corresponding node index.
Some((_, idx)) => *idx,
// If the expression has not been visited, add a new node to the graph and
// add edges to its child nodes. Add the visited expression to the vector
// of visited expressions and return the newly created node index.
None => {
let node_idx = self.graph.add_node((self.constructor)(&node)?);
for expr_node in node.children.iter() {
self.graph.add_edge(node_idx, expr_node.data.unwrap(), 0);
}
self.visited_plans.push((Arc::clone(expr), node_idx));
node_idx
}
};
// Set the data field of the input expression node to the corresponding node index.
node.data = Some(node_idx);
// Return the mutated expression node.
Ok(Transformed::yes(node))
}
}
// A function that builds a directed acyclic graph of physical expression trees.
pub fn build_dag<T, F>(
expr: Arc<dyn PhysicalExpr>,
constructor: &F,
) -> Result<(NodeIndex, StableGraph<T, usize>)>
where
F: Fn(&ExprTreeNode<NodeIndex>) -> Result<T>,
{
// Create a new expression tree node from the input expression.
let init = ExprTreeNode::new_default(expr);
// Create a new `PhysicalExprDAEGBuilder` instance.
let mut builder = PhysicalExprDAEGBuilder {
graph: StableGraph::<T, usize>::new(),
visited_plans: Vec::<(Arc<dyn PhysicalExpr>, NodeIndex)>::new(),
constructor,
};
// Use the builder to transform the expression tree node into a DAG.
let root = init.transform_up(|node| builder.mutate(node)).data()?;
// Return a tuple containing the root node index and the DAG.
Ok((root.data.unwrap(), builder.graph))
}
/// Recursively extract referenced [`Column`]s within a [`PhysicalExpr`].
pub fn collect_columns(expr: &Arc<dyn PhysicalExpr>) -> HashSet<Column> {
let mut columns = HashSet::<Column>::new();
expr.apply(|expr| {
if let Some(column) = expr.as_any().downcast_ref::<Column>() {
columns.get_or_insert_owned(column);
}
Ok(TreeNodeRecursion::Continue)
})
// pre_visit always returns OK, so this will always too
.expect("no way to return error during recursion");
columns
}
/// Re-assign indices of [`Column`]s within the given [`PhysicalExpr`] according to
/// the provided [`Schema`].
///
/// This can be useful when attempting to map an expression onto a different schema.
///
/// # Errors
///
/// This function will return an error if any column in the expression cannot be found
/// in the provided schema.
pub fn reassign_expr_columns(
expr: Arc<dyn PhysicalExpr>,
schema: &Schema,
) -> Result<Arc<dyn PhysicalExpr>> {
expr.transform_down(|expr| {
if let Some(column) = expr.as_any().downcast_ref::<Column>() {
let index = schema.index_of(column.name())?;
return Ok(Transformed::yes(Arc::new(Column::new(
column.name(),
index,
))));
}
Ok(Transformed::no(expr))
})
.data()
}
#[cfg(test)]
pub(crate) mod tests {
use std::any::Any;
use std::fmt::{Display, Formatter};
use super::*;
use crate::expressions::{binary, cast, col, in_list, lit, Literal};
use arrow::array::{ArrayRef, Float32Array, Float64Array};
use arrow::datatypes::{DataType, Field, Schema};
use datafusion_common::{exec_err, internal_datafusion_err, ScalarValue};
use datafusion_expr::sort_properties::{ExprProperties, SortProperties};
use datafusion_expr::{
ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl, Signature, Volatility,
};
use petgraph::visit::Bfs;
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct TestScalarUDF {
pub(crate) signature: Signature,
}
impl TestScalarUDF {
pub fn new() -> Self {
use DataType::*;
Self {
signature: Signature::uniform(
1,
vec![Float64, Float32],
Volatility::Immutable,
),
}
}
}
impl ScalarUDFImpl for TestScalarUDF {
fn as_any(&self) -> &dyn Any {
self
}
fn name(&self) -> &str {
"test-scalar-udf"
}
fn signature(&self) -> &Signature {
&self.signature
}
fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
let arg_type = &arg_types[0];
match arg_type {
DataType::Float32 => Ok(DataType::Float32),
_ => Ok(DataType::Float64),
}
}
fn output_ordering(&self, input: &[ExprProperties]) -> Result<SortProperties> {
Ok(input[0].sort_properties)
}
fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
let args = ColumnarValue::values_to_arrays(&args.args)?;
let arr: ArrayRef = match args[0].data_type() {
DataType::Float64 => Arc::new({
let arg = &args[0]
.as_any()
.downcast_ref::<Float64Array>()
.ok_or_else(|| {
internal_datafusion_err!(
"could not cast {} to {}",
self.name(),
std::any::type_name::<Float64Array>()
)
})?;
arg.iter()
.map(|a| a.map(f64::floor))
.collect::<Float64Array>()
}),
DataType::Float32 => Arc::new({
let arg = &args[0]
.as_any()
.downcast_ref::<Float32Array>()
.ok_or_else(|| {
internal_datafusion_err!(
"could not cast {} to {}",
self.name(),
std::any::type_name::<Float32Array>()
)
})?;
arg.iter()
.map(|a| a.map(f32::floor))
.collect::<Float32Array>()
}),
other => {
return exec_err!(
"Unsupported data type {other:?} for function {}",
self.name()
);
}
};
Ok(ColumnarValue::Array(arr))
}
}
#[derive(Clone)]
struct DummyProperty {
expr_type: String,
}
/// This is a dummy node in the DAEG; it stores a reference to the actual
/// [PhysicalExpr] as well as a dummy property.
#[derive(Clone)]
struct PhysicalExprDummyNode {
pub expr: Arc<dyn PhysicalExpr>,
pub property: DummyProperty,
}
impl Display for PhysicalExprDummyNode {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.expr)
}
}
fn make_dummy_node(node: &ExprTreeNode<NodeIndex>) -> Result<PhysicalExprDummyNode> {
let expr = Arc::clone(&node.expr);
let dummy_property = if expr.as_any().is::<BinaryExpr>() {
"Binary"
} else if expr.as_any().is::<Column>() {
"Column"
} else if expr.as_any().is::<Literal>() {
"Literal"
} else {
"Other"
}
.to_owned();
Ok(PhysicalExprDummyNode {
expr,
property: DummyProperty {
expr_type: dummy_property,
},
})
}
#[test]
fn test_build_dag() -> Result<()> {
let schema = Schema::new(vec![
Field::new("0", DataType::Int32, true),
Field::new("1", DataType::Int32, true),
Field::new("2", DataType::Int32, true),
]);
let expr = binary(
cast(
binary(
col("0", &schema)?,
Operator::Plus,
col("1", &schema)?,
&schema,
)?,
&schema,
DataType::Int64,
)?,
Operator::Gt,
binary(
cast(col("2", &schema)?, &schema, DataType::Int64)?,
Operator::Plus,
lit(ScalarValue::Int64(Some(10))),
&schema,
)?,
&schema,
)?;
let mut vector_dummy_props = vec![];
let (root, graph) = build_dag(expr, &make_dummy_node)?;
let mut bfs = Bfs::new(&graph, root);
while let Some(node_index) = bfs.next(&graph) {
let node = &graph[node_index];
vector_dummy_props.push(node.property.clone());
}
assert_eq!(
vector_dummy_props
.iter()
.filter(|property| property.expr_type == "Binary")
.count(),
3
);
assert_eq!(
vector_dummy_props
.iter()
.filter(|property| property.expr_type == "Column")
.count(),
3
);
assert_eq!(
vector_dummy_props
.iter()
.filter(|property| property.expr_type == "Literal")
.count(),
1
);
assert_eq!(
vector_dummy_props
.iter()
.filter(|property| property.expr_type == "Other")
.count(),
2
);
Ok(())
}
#[test]
fn test_convert_to_expr() -> Result<()> {
let schema = Schema::new(vec![Field::new("a", DataType::UInt64, false)]);
let sort_expr = vec![PhysicalSortExpr {
expr: col("a", &schema)?,
options: Default::default(),
}];
assert!(convert_to_expr(&sort_expr)[0].eq(&sort_expr[0].expr));
Ok(())
}
#[test]
fn test_get_indices_of_exprs_strict() {
let list1: Vec<Arc<dyn PhysicalExpr>> = vec![
Arc::new(Column::new("a", 0)),
Arc::new(Column::new("b", 1)),
Arc::new(Column::new("c", 2)),
Arc::new(Column::new("d", 3)),
];
let list2: Vec<Arc<dyn PhysicalExpr>> = vec![
Arc::new(Column::new("b", 1)),
Arc::new(Column::new("c", 2)),
Arc::new(Column::new("a", 0)),
];
assert_eq!(get_indices_of_exprs_strict(&list1, &list2), vec![2, 0, 1]);
assert_eq!(get_indices_of_exprs_strict(&list2, &list1), vec![1, 2, 0]);
}
#[test]
fn test_reassign_expr_columns_in_list() {
let int_field = Field::new("should_not_matter", DataType::Int64, true);
let dict_field = Field::new(
"id",
DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
true,
);
let schema_small = Arc::new(Schema::new(vec![dict_field.clone()]));
let schema_big = Arc::new(Schema::new(vec![int_field, dict_field]));
let pred = in_list(
Arc::new(Column::new_with_schema("id", &schema_big).unwrap()),
vec![lit(ScalarValue::Dictionary(
Box::new(DataType::Int32),
Box::new(ScalarValue::from("2")),
))],
&false,
&schema_big,
)
.unwrap();
let actual = reassign_expr_columns(pred, &schema_small).unwrap();
let expected = in_list(
Arc::new(Column::new_with_schema("id", &schema_small).unwrap()),
vec![lit(ScalarValue::Dictionary(
Box::new(DataType::Int32),
Box::new(ScalarValue::from("2")),
))],
&false,
&schema_small,
)
.unwrap();
assert_eq!(actual.as_ref(), expected.as_ref());
}
#[test]
fn test_collect_columns() -> Result<()> {
let expr1 = Arc::new(Column::new("col1", 2)) as _;
let mut expected = HashSet::new();
expected.insert(Column::new("col1", 2));
assert_eq!(collect_columns(&expr1), expected);
let expr2 = Arc::new(Column::new("col2", 5)) as _;
let mut expected = HashSet::new();
expected.insert(Column::new("col2", 5));
assert_eq!(collect_columns(&expr2), expected);
let expr3 = Arc::new(BinaryExpr::new(expr1, Operator::Plus, expr2)) as _;
let mut expected = HashSet::new();
expected.insert(Column::new("col1", 2));
expected.insert(Column::new("col2", 5));
assert_eq!(collect_columns(&expr3), expected);
Ok(())
}
}