use nodedb_sql::types::{AggregateExpr, SqlExpr, SqlPlan};
use crate::bridge::envelope::PhysicalPlan;
use crate::types::TenantId;
use nodedb_physical::physical_plan::*;
use super::super::convert::{ConvertContext, convert_one, db_qualified};
use super::super::expr::sql_expr_to_bridge_expr;
pub(in crate::control::planner::sql_plan_convert) fn inline_join_side(
plan: &SqlPlan,
tenant_id: TenantId,
ctx: &ConvertContext,
) -> crate::Result<Option<Box<PhysicalPlan>>> {
if matches!(plan, SqlPlan::PointGet { .. }) {
return Ok(None);
}
if let SqlPlan::Scan { collection, .. } = plan
&& !scan_is_catalog(collection)
{
return Ok(None);
}
let mut tasks = convert_one(plan, tenant_id, ctx)?;
if tasks.len() > 1 {
return Err(crate::Error::PlanError {
detail: format!(
"inline join side must produce exactly 1 task, got {}",
tasks.len()
),
});
}
Ok(tasks.pop().map(|t| {
let p = t.plan;
if p.is_sharded_source() {
Box::new(PhysicalPlan::Query(QueryOp::Exchange(ExchangeOp {
child: Box::new(p),
mode: ExchangeMode::Broadcast,
})))
} else {
Box::new(p)
}
}))
}
fn scan_is_catalog(collection: &str) -> bool {
crate::control::server::pgwire::catalog::schema::catalog_collection_info(collection).is_some()
}
pub(in crate::control::planner::sql_plan_convert) fn join_side_collection(
plan: &SqlPlan,
database_id: crate::types::DatabaseId,
) -> String {
let raw = extract_collection_name(plan);
if scan_is_catalog(&raw) {
String::new()
} else {
db_qualified(database_id, &raw)
}
}
pub(in crate::control::planner::sql_plan_convert) fn extract_collection_name(
plan: &SqlPlan,
) -> String {
match plan {
SqlPlan::Scan { collection, .. } => collection.clone(),
SqlPlan::PointGet { collection, .. } => collection.clone(),
SqlPlan::Join { left, .. } => extract_collection_name(left),
SqlPlan::Aggregate { input, .. } => extract_collection_name(input),
_ => String::new(),
}
}
pub(in crate::control::planner::sql_plan_convert) fn extract_scan_alias(
plan: &SqlPlan,
) -> Option<String> {
match plan {
SqlPlan::Scan { alias, .. } => alias.clone(),
SqlPlan::PointGet { alias, .. } => alias.clone(),
SqlPlan::Join { left, .. } => extract_scan_alias(left),
SqlPlan::Aggregate { input, .. } => extract_scan_alias(input),
_ => None,
}
}
pub(super) fn agg_expr_to_spec(a: &AggregateExpr) -> AggregateSpec {
if a.function == "grouping" {
let idx = a.grouping_col_index.unwrap_or(0);
let field = idx.to_string();
let canonical = format!("grouping({field})");
let user_alias = if a.alias.eq_ignore_ascii_case(&canonical) {
None
} else {
Some(a.alias.clone())
};
return AggregateSpec {
function: "grouping".into(),
alias: canonical,
user_alias,
field,
expr: None,
};
}
let (field, expr) = a
.args
.first()
.map(|arg| match arg {
SqlExpr::Column { name, .. } => (name.clone(), None),
SqlExpr::Wildcard => ("*".into(), None),
_ => ("*".into(), Some(sql_expr_to_bridge_expr(arg))),
})
.unwrap_or_else(|| ("*".into(), None));
let function = aggregate_function_name(a);
let canonical = nodedb_query::agg_key::canonical_agg_key(&function, &field);
let user_alias = if a.alias.eq_ignore_ascii_case(&canonical) {
None
} else {
Some(a.alias.clone())
};
AggregateSpec {
function,
alias: canonical,
user_alias,
field,
expr,
}
}
pub(in crate::control::planner::sql_plan_convert) fn agg_expr_to_pair(
a: &AggregateExpr,
) -> (String, String) {
let field = a
.args
.first()
.map(|arg| match arg {
SqlExpr::Column { name, .. } => name.clone(),
SqlExpr::Wildcard => "*".into(),
_ => format!("{arg:?}"),
})
.unwrap_or_else(|| "*".into());
(aggregate_function_name(a), field)
}
fn aggregate_function_name(a: &AggregateExpr) -> String {
if a.distinct {
match a.function.as_str() {
"count" => "count_distinct".into(),
"array_agg" => "array_agg_distinct".into(),
"sum" => "sum_distinct".into(),
"avg" => "avg_distinct".into(),
_ => a.function.clone(),
}
} else {
a.function.clone()
}
}
pub(super) fn group_by_to_strings(exprs: &[SqlExpr]) -> Vec<String> {
exprs
.iter()
.filter_map(|e| match e {
SqlExpr::Column { name, .. } => Some(name.clone()),
_ => None,
})
.collect()
}
pub(super) fn group_by_to_specs(exprs: &[SqlExpr]) -> Vec<GroupKeySpec> {
use super::super::group_key_name::group_key_output_name;
exprs
.iter()
.enumerate()
.map(|(index, e)| match e {
SqlExpr::Column { name, .. } => GroupKeySpec::column(name.clone()),
_ => GroupKeySpec {
output_name: group_key_output_name(e, index),
field: None,
expr: Some(sql_expr_to_bridge_expr(e)),
},
})
.collect()
}