use std::sync::Arc;
use datafusion::{
common::Result as DataFusionResult,
logical_expr::planner::{RelationPlanner, RelationPlannerContext, RelationPlanning},
sql::sqlparser::ast::TableFactor,
};
use super::{
GPU_DESCRIBE_FUNCTION_FUNCTION, GPU_LIST_FUNCTIONS_FUNCTION, GPU_VALIDATE_CALL_FUNCTION, GpuFunctionCatalog,
dense_relation::table_function_name,
};
use crate::{
error::{Error, ErrorCode},
name_suggestion::closest_name,
};
const GPU_FUNCTION_PREFIXES: [&str; 4] = ["cugraph_", "cuml_", "cuvs_", "gpu_"];
const METADATA_FUNCTIONS: [&str; 3] =
[GPU_LIST_FUNCTIONS_FUNCTION, GPU_DESCRIBE_FUNCTION_FUNCTION, GPU_VALIDATE_CALL_FUNCTION];
#[derive(Debug)]
pub(crate) struct UnknownGpuFunctionPlanner {
catalog: Arc<GpuFunctionCatalog>,
}
impl UnknownGpuFunctionPlanner {
pub(crate) fn new(catalog: Arc<GpuFunctionCatalog>) -> Self {
Self { catalog }
}
}
impl RelationPlanner for UnknownGpuFunctionPlanner {
fn plan_relation(
&self,
relation: TableFactor,
context: &mut dyn RelationPlannerContext,
) -> DataFusionResult<RelationPlanning> {
match table_function_name(&relation, context) {
Some(name)
if GPU_FUNCTION_PREFIXES.iter().any(|prefix| name.starts_with(prefix))
&& self.catalog.get(&name).is_none()
&& !METADATA_FUNCTIONS.contains(&name.as_str()) =>
{
Err(unknown_gpu_function_error(&self.catalog, &name).into())
},
_ => Ok(RelationPlanning::Original(Box::new(relation))),
}
}
}
pub(crate) fn unknown_gpu_function_error(catalog: &GpuFunctionCatalog, name: &str) -> Error {
Error::new(
ErrorCode::ObjectNotFound,
"algeon_datafusion.gpu_functions.unknown_function",
unknown_gpu_function_message(catalog, name),
)
.with_log_safe_fact("reason", "unknown_gpu_function")
}
pub(crate) fn unknown_gpu_function_message(catalog: &GpuFunctionCatalog, name: &str) -> String {
let prefix = GPU_FUNCTION_PREFIXES.iter().copied().find(|prefix| name.starts_with(prefix)).unwrap_or_default();
let registered = catalog
.entries()
.iter()
.map(|entry| entry.descriptor.function_name.as_str())
.chain(METADATA_FUNCTIONS)
.collect::<Vec<_>>();
let family_installed = registered.iter().any(|candidate| candidate.starts_with(prefix));
match closest_name(name, prefix, registered.iter().copied()) {
Some(closest) => format!(
"unknown GPU function {name}; closest registered name: {closest}; {GPU_LIST_FUNCTIONS_FUNCTION}() lists every installed function"
),
None if !family_installed => format!(
"unknown GPU function {name}; no {prefix}* functions are installed in this session; {GPU_LIST_FUNCTIONS_FUNCTION}() lists every installed function"
),
None => format!("unknown GPU function {name}; {GPU_LIST_FUNCTIONS_FUNCTION}() lists every installed function"),
}
}