use std::collections::HashMap;
use std::sync::Arc;
use datafusion::arrow::datatypes::DataType;
use datafusion::common::tree_node::{TreeNode, TreeNodeRecursion};
use datafusion::common::DFSchema;
use datafusion::config::ConfigOptions;
use datafusion::error::{DataFusionError, Result};
use datafusion::execution::SessionStateDefaults;
use datafusion::logical_expr::planner::ExprPlanner;
use datafusion::logical_expr::{
AggregateUDF, Expr, HigherOrderUDF, ScalarUDF, TableSource, WindowUDF,
};
use datafusion::sql::planner::{ContextProvider, PlannerContext, SqlToRel};
use datafusion::sql::TableReference;
use ddx_core::sqlparser::ast as sql_ast;
#[derive(Debug)]
pub(crate) struct ExprContext {
functions: HashMap<String, Arc<ScalarUDF>>,
higher_order: HashMap<String, Arc<HigherOrderUDF>>,
expr_planners: Vec<Arc<dyn ExprPlanner>>,
}
impl ExprContext {
pub(crate) fn new(extra: impl IntoIterator<Item = Arc<ScalarUDF>>) -> Self {
let functions = SessionStateDefaults::default_scalar_functions()
.into_iter()
.chain(extra)
.map(|f| (f.name().to_ascii_lowercase(), f))
.collect();
let higher_order = SessionStateDefaults::default_higher_order_functions()
.into_iter()
.map(|f| (f.name().to_ascii_lowercase(), f))
.collect();
ExprContext {
functions,
higher_order,
expr_planners: SessionStateDefaults::default_expr_planners(),
}
}
pub(crate) fn scoped<'a>(
&'a self,
options: &'a ConfigOptions,
local: HashMap<String, Arc<ScalarUDF>>,
) -> ScopedExprContext<'a> {
ScopedExprContext {
inner: self,
options,
local,
}
}
}
pub(crate) struct ScopedExprContext<'a> {
inner: &'a ExprContext,
options: &'a ConfigOptions,
local: HashMap<String, Arc<ScalarUDF>>,
}
impl ContextProvider for ScopedExprContext<'_> {
fn get_table_source(&self, name: TableReference) -> Result<Arc<dyn TableSource>> {
Err(DataFusionError::Internal(format!(
"ddx: re-planning a derivative expression tried to resolve the table `{name}`. \
A differentiated scalar expression must not contain table references — \
please report this with the query that triggered it."
)))
}
fn get_function_meta(&self, name: &str) -> Option<Arc<ScalarUDF>> {
let name = name.to_ascii_lowercase();
self.local
.get(&name)
.or_else(|| self.inner.functions.get(&name))
.cloned()
}
fn get_higher_order_meta(&self, name: &str) -> Option<Arc<HigherOrderUDF>> {
self.inner
.higher_order
.get(&name.to_ascii_lowercase())
.cloned()
}
fn get_aggregate_meta(&self, _name: &str) -> Option<Arc<AggregateUDF>> {
None
}
fn get_window_meta(&self, _name: &str) -> Option<Arc<WindowUDF>> {
None
}
fn get_variable_type(&self, _variable_names: &[String]) -> Option<DataType> {
None
}
fn get_expr_planners(&self) -> &[Arc<dyn ExprPlanner>] {
&self.inner.expr_planners
}
fn options(&self) -> &ConfigOptions {
self.options
}
fn udf_names(&self) -> Vec<String> {
self.inner
.functions
.keys()
.chain(self.local.keys())
.cloned()
.collect()
}
fn higher_order_function_names(&self) -> Vec<String> {
self.inner.higher_order.keys().cloned().collect()
}
fn udaf_names(&self) -> Vec<String> {
Vec::new()
}
fn udwf_names(&self) -> Vec<String> {
Vec::new()
}
}
pub(crate) fn replan(
ctx: &ExprContext,
options: &ConfigOptions,
local: HashMap<String, Arc<ScalarUDF>>,
expr: sql_ast::Expr,
schema: &DFSchema,
) -> Result<Expr> {
SqlToRel::new(&ctx.scoped(options, local)).sql_to_expr(expr, schema, &mut PlannerContext::new())
}
pub(crate) fn functions_in(exprs: &[Expr]) -> HashMap<String, Arc<ScalarUDF>> {
let mut found = HashMap::new();
for e in exprs {
let _ = e.apply(|node| {
if let Expr::ScalarFunction(call) = node {
found.insert(
call.func.name().to_ascii_lowercase(),
Arc::clone(&call.func),
);
}
Ok(TreeNodeRecursion::Continue)
});
}
found
}