use crate::result::QueryError;
use powdb_storage::catalog::Catalog;
use powdb_storage::types::*;
use crate::executor::compiled::*;
use super::*;
pub(crate) fn predicate_column_indices_json(expr: &Expr, columns: &[String]) -> Vec<usize> {
let mut indices = predicate_column_indices(expr, columns);
collect_json_path_base_indices(expr, columns, &mut indices);
indices.sort_unstable();
indices.dedup();
indices
}
fn collect_json_path_base_indices(expr: &Expr, columns: &[String], out: &mut Vec<usize>) {
match expr {
Expr::JsonPath { base, .. } => {
let name = match base.as_ref() {
Expr::Field(n) => n.clone(),
Expr::QualifiedField { qualifier, field } => format!("{qualifier}.{field}"),
other => {
collect_json_path_base_indices(other, columns, out);
return;
}
};
if let Some(idx) = columns.iter().position(|c| *c == name) {
out.push(idx);
}
}
Expr::BinaryOp(l, _, r) | Expr::Coalesce(l, r) => {
collect_json_path_base_indices(l, columns, out);
collect_json_path_base_indices(r, columns, out);
}
Expr::UnaryOp(_, i) | Expr::FunctionCall(_, i, _) | Expr::Cast(i, _) => {
collect_json_path_base_indices(i, columns, out);
}
Expr::ScalarFunc(_, args) => {
for a in args {
collect_json_path_base_indices(a, columns, out);
}
}
Expr::InList { expr, list, .. } => {
collect_json_path_base_indices(expr, columns, out);
for item in list {
collect_json_path_base_indices(item, columns, out);
}
}
Expr::InSubquery { expr, .. } => collect_json_path_base_indices(expr, columns, out),
Expr::Case { whens, else_expr } => {
for (c, r) in whens {
collect_json_path_base_indices(c, columns, out);
collect_json_path_base_indices(r, columns, out);
}
if let Some(e) = else_expr {
collect_json_path_base_indices(e, columns, out);
}
}
_ => {}
}
}
pub(crate) fn validate_json_path_types(
catalog: &Catalog,
plan: &PlanNode,
) -> Result<(), QueryError> {
let mut scope: Vec<(String, TypeId)> = Vec::new();
collect_scan_columns(catalog, plan, &mut scope);
let mut shadowed: std::collections::HashSet<String> = std::collections::HashSet::new();
collect_projected_names(plan, &mut shadowed);
check_plan_json_paths(plan, &scope, &shadowed)
}
fn collect_scan_columns(catalog: &Catalog, plan: &PlanNode, out: &mut Vec<(String, TypeId)>) {
match plan {
PlanNode::SeqScan { table }
| PlanNode::IndexScan { table, .. }
| PlanNode::RangeScan { table, .. } => {
if let Some(schema) = catalog.schema(table) {
for c in &schema.columns {
out.push((c.name.clone(), c.type_id));
}
}
}
PlanNode::AliasScan { table, alias } => {
if let Some(schema) = catalog.schema(table) {
for c in &schema.columns {
out.push((format!("{alias}.{}", c.name), c.type_id));
}
}
}
PlanNode::Filter { input, .. }
| PlanNode::Project { input, .. }
| PlanNode::Sort { input, .. }
| PlanNode::Limit { input, .. }
| PlanNode::Offset { input, .. }
| PlanNode::Aggregate { input, .. }
| PlanNode::Distinct { input }
| PlanNode::GroupBy { input, .. }
| PlanNode::Window { input, .. }
| PlanNode::Update { input, .. }
| PlanNode::Delete { input, .. }
| PlanNode::Explain { input } => collect_scan_columns(catalog, input, out),
PlanNode::NestedLoopJoin { left, right, .. } | PlanNode::Union { left, right, .. } => {
collect_scan_columns(catalog, left, out);
collect_scan_columns(catalog, right, out);
}
_ => {}
}
}
fn collect_projected_names(plan: &PlanNode, out: &mut std::collections::HashSet<String>) {
if let PlanNode::Project { fields, .. } = plan {
for f in fields {
if let Some(a) = &f.alias {
out.insert(a.clone());
} else {
match &f.expr {
Expr::Field(n) => {
out.insert(n.clone());
}
Expr::QualifiedField { qualifier, field } => {
out.insert(format!("{qualifier}.{field}"));
}
_ => {}
}
}
}
}
match plan {
PlanNode::Filter { input, .. }
| PlanNode::Project { input, .. }
| PlanNode::Sort { input, .. }
| PlanNode::Limit { input, .. }
| PlanNode::Offset { input, .. }
| PlanNode::Aggregate { input, .. }
| PlanNode::Distinct { input }
| PlanNode::GroupBy { input, .. }
| PlanNode::Window { input, .. }
| PlanNode::Update { input, .. }
| PlanNode::Delete { input, .. }
| PlanNode::Explain { input } => collect_projected_names(input, out),
PlanNode::NestedLoopJoin { left, right, .. } | PlanNode::Union { left, right, .. } => {
collect_projected_names(left, out);
collect_projected_names(right, out);
}
_ => {}
}
}
fn resolve_scan_type(name: &str, scope: &[(String, TypeId)]) -> Option<TypeId> {
let mut found: Option<TypeId> = None;
for (n, t) in scope {
if n == name {
match found {
None => found = Some(*t),
Some(prev) if prev == *t => {}
Some(_) => return None, }
}
}
found
}
fn json_path_base_error(
base: &Expr,
scope: &[(String, TypeId)],
shadowed: &std::collections::HashSet<String>,
) -> Option<String> {
let name = match base {
Expr::Field(n) => n.clone(),
Expr::QualifiedField { qualifier, field } => format!("{qualifier}.{field}"),
_ => return None,
};
if shadowed.contains(&name) {
return None;
}
match resolve_scan_type(&name, scope) {
Some(TypeId::Json) | None => None,
Some(other) => Some(format!(
"'{}' is a {} column, not json: the '->' path operator requires a json column",
name,
type_id_to_name(other)
)),
}
}
fn check_expr_json_paths(
expr: &Expr,
scope: &[(String, TypeId)],
shadowed: &std::collections::HashSet<String>,
) -> Result<(), QueryError> {
match expr {
Expr::JsonPath { base, .. } => {
if let Some(msg) = json_path_base_error(base, scope, shadowed) {
return Err(QueryError::TypeError(msg));
}
check_expr_json_paths(base, scope, shadowed)
}
Expr::BinaryOp(l, _, r) | Expr::Coalesce(l, r) => {
check_expr_json_paths(l, scope, shadowed)?;
check_expr_json_paths(r, scope, shadowed)
}
Expr::UnaryOp(_, inner) | Expr::FunctionCall(_, inner, _) | Expr::Cast(inner, _) => {
check_expr_json_paths(inner, scope, shadowed)
}
Expr::ScalarFunc(_, args) => {
for a in args {
check_expr_json_paths(a, scope, shadowed)?;
}
Ok(())
}
Expr::Window {
args,
partition_by,
order_by,
..
} => {
for expr in args.iter().chain(partition_by) {
check_expr_json_paths(expr, scope, shadowed)?;
}
for key in order_by {
check_expr_json_paths(&key.expr, scope, shadowed)?;
}
Ok(())
}
Expr::InList { expr, list, .. } => {
check_expr_json_paths(expr, scope, shadowed)?;
for item in list {
check_expr_json_paths(item, scope, shadowed)?;
}
Ok(())
}
Expr::Case { whens, else_expr } => {
for (c, r) in whens {
check_expr_json_paths(c, scope, shadowed)?;
check_expr_json_paths(r, scope, shadowed)?;
}
if let Some(e) = else_expr {
check_expr_json_paths(e, scope, shadowed)?;
}
Ok(())
}
Expr::InSubquery { expr, .. } => check_expr_json_paths(expr, scope, shadowed),
_ => Ok(()),
}
}
fn check_plan_json_paths(
plan: &PlanNode,
scope: &[(String, TypeId)],
shadowed: &std::collections::HashSet<String>,
) -> Result<(), QueryError> {
match plan {
PlanNode::Filter { input, predicate } => {
check_expr_json_paths(predicate, scope, shadowed)?;
check_plan_json_paths(input, scope, shadowed)
}
PlanNode::Project { input, fields } => {
for f in fields {
check_expr_json_paths(&f.expr, scope, shadowed)?;
}
check_plan_json_paths(input, scope, shadowed)
}
PlanNode::GroupBy {
input,
keys,
aggregates,
having,
} => {
for key in keys {
check_expr_json_paths(&key.expr, scope, shadowed)?;
}
for aggregate in aggregates {
check_expr_json_paths(&aggregate.argument, scope, shadowed)?;
}
if let Some(h) = having {
check_expr_json_paths(h, scope, shadowed)?;
}
check_plan_json_paths(input, scope, shadowed)
}
PlanNode::NestedLoopJoin {
left, right, on, ..
} => {
if let Some(on) = on {
check_expr_json_paths(on, scope, shadowed)?;
}
check_plan_json_paths(left, scope, shadowed)?;
check_plan_json_paths(right, scope, shadowed)
}
PlanNode::Union { left, right, .. } => {
check_plan_json_paths(left, scope, shadowed)?;
check_plan_json_paths(right, scope, shadowed)
}
PlanNode::Sort { input, keys } => {
for key in keys {
check_expr_json_paths(&key.expr, scope, shadowed)?;
}
check_plan_json_paths(input, scope, shadowed)
}
PlanNode::Aggregate {
input, argument, ..
} => {
if let Some(argument) = argument {
check_expr_json_paths(argument, scope, shadowed)?;
}
check_plan_json_paths(input, scope, shadowed)
}
PlanNode::Window { input, windows } => {
for window in windows {
for expr in window.args.iter().chain(&window.partition_by) {
check_expr_json_paths(expr, scope, shadowed)?;
}
for key in &window.order_by {
check_expr_json_paths(&key.expr, scope, shadowed)?;
}
}
check_plan_json_paths(input, scope, shadowed)
}
PlanNode::Limit { input, .. }
| PlanNode::Offset { input, .. }
| PlanNode::Distinct { input }
| PlanNode::Update { input, .. }
| PlanNode::Delete { input, .. }
| PlanNode::Explain { input } => check_plan_json_paths(input, scope, shadowed),
_ => Ok(()),
}
}
pub(crate) fn validate_no_stray_aggregates(plan: &PlanNode) -> Result<(), QueryError> {
match plan {
PlanNode::Project { input, fields } => {
for f in fields {
check_expr_no_aggregate(&f.expr)?;
}
validate_no_stray_aggregates(input)?;
}
PlanNode::Filter { input, predicate } => {
check_expr_no_aggregate(predicate)?;
validate_no_stray_aggregates(input)?;
}
PlanNode::GroupBy {
input,
keys,
aggregates,
having,
} => {
for key in keys {
check_expr_no_aggregate(&key.expr)?;
}
for aggregate in aggregates {
check_expr_no_aggregate(&aggregate.argument)?;
}
if let Some(h) = having {
check_expr_no_aggregate(h)?;
}
validate_no_stray_aggregates(input)?;
}
PlanNode::NestedLoopJoin {
left, right, on, ..
} => {
if let Some(on) = on {
check_expr_no_aggregate(on)?;
}
validate_no_stray_aggregates(left)?;
validate_no_stray_aggregates(right)?;
}
PlanNode::Union { left, right, .. } => {
validate_no_stray_aggregates(left)?;
validate_no_stray_aggregates(right)?;
}
PlanNode::Sort { input, keys } => {
for key in keys {
check_expr_no_aggregate(&key.expr)?;
}
validate_no_stray_aggregates(input)?;
}
PlanNode::Aggregate {
input, argument, ..
} => {
if let Some(argument) = argument {
check_expr_no_aggregate(argument)?;
}
validate_no_stray_aggregates(input)?;
}
PlanNode::Window { input, windows } => {
for window in windows {
for expr in window.args.iter().chain(&window.partition_by) {
check_expr_no_aggregate(expr)?;
}
for key in &window.order_by {
check_expr_no_aggregate(&key.expr)?;
}
}
validate_no_stray_aggregates(input)?;
}
PlanNode::Limit { input, .. }
| PlanNode::Offset { input, .. }
| PlanNode::Distinct { input }
| PlanNode::Update { input, .. }
| PlanNode::Delete { input, .. }
| PlanNode::Explain { input } => {
validate_no_stray_aggregates(input)?;
}
_ => {}
}
Ok(())
}
fn check_expr_no_aggregate(expr: &Expr) -> Result<(), QueryError> {
match expr {
Expr::FunctionCall(..) => Err(QueryError::Execution(
"invalid query: aggregate function in an unsupported position".to_string(),
)),
Expr::BinaryOp(l, _, r) | Expr::Coalesce(l, r) => {
check_expr_no_aggregate(l)?;
check_expr_no_aggregate(r)
}
Expr::UnaryOp(_, inner) | Expr::Cast(inner, _) | Expr::JsonPath { base: inner, .. } => {
check_expr_no_aggregate(inner)
}
Expr::ScalarFunc(_, args) => {
for a in args {
check_expr_no_aggregate(a)?;
}
Ok(())
}
Expr::InList { expr: e, list, .. } => {
check_expr_no_aggregate(e)?;
for item in list {
check_expr_no_aggregate(item)?;
}
Ok(())
}
Expr::InSubquery { expr: e, .. } => check_expr_no_aggregate(e),
Expr::Case { whens, else_expr } => {
for (c, r) in whens {
check_expr_no_aggregate(c)?;
check_expr_no_aggregate(r)?;
}
if let Some(e) = else_expr {
check_expr_no_aggregate(e)?;
}
Ok(())
}
Expr::Window {
args,
partition_by,
order_by,
..
} => {
for expr in args.iter().chain(partition_by) {
check_expr_no_aggregate(expr)?;
}
for key in order_by {
check_expr_no_aggregate(&key.expr)?;
}
Ok(())
}
_ => Ok(()),
}
}