use uqa_core::Value;
use uqa_sql::ast::FunctionBinding;
use uqa_sql::ir::ScalarExpr;
use uqa_sql::plan::{QueryBlockPlan, QueryPlan, RelationalPlan, SourcePlan};
use super::{query_columns, Column, Deparser, RelationLookupMode, SQLError, Scope};
use crate::catalog::{CatalogReadView, RelationNameResolution};
#[derive(Debug, Clone, Default)]
pub struct QueryReferences {
pub relations: Vec<String>,
pub columns: Vec<(String, String)>,
pub types: Vec<String>,
pub routines: Vec<FunctionBinding>,
pub constants: Vec<(String, i64)>,
}
pub fn query_references(
catalog: &CatalogReadView,
resolution: &RelationNameResolution,
query: &QueryPlan,
) -> Result<QueryReferences, SQLError> {
let mut dynamic = resolution.clone();
dynamic.set_lookup_mode(RelationLookupMode::Dynamic);
let mut bound = resolution.clone();
bound.set_lookup_mode(RelationLookupMode::Bound);
let deparser = Deparser {
output: None,
catalog,
dynamic,
bound,
pretty: false,
wrap: 0,
standalone: false,
indent: true,
routine: None,
aliases: std::cell::OnceCell::new(),
};
let mut references = QueryReferences::default();
deparser.reference_query(query, &Scope::default(), &mut references)?;
Ok(references)
}
impl Deparser<'_> {
fn reference_query(
&self,
query: &QueryPlan,
parent: &Scope,
references: &mut QueryReferences,
) -> Result<(), SQLError> {
let mut scope = parent.clone();
for cte in &query.ctes {
let mut names = query_columns(super::query::view_cte_query(cte)?);
for (name, alias) in names.iter_mut().zip(&cte.columns) {
name.clone_from(alias);
}
scope.ctes.insert(cte.name.clone(), names);
}
for cte in &query.ctes {
self.reference_query(
super::query::view_cte_query(cte)?,
&scope.child(),
references,
)?;
}
match &query.root {
RelationalPlan::QueryBlock(block) => self.reference_block(block, &scope, references),
RelationalPlan::SetOp {
left,
right,
subqueries,
..
} => {
self.reference_query(left, &scope, references)?;
self.reference_query(right, &scope, references)?;
for subquery in subqueries {
self.reference_query(subquery, &scope.child(), references)?;
}
Ok(())
}
RelationalPlan::Values { rows, subqueries } => {
for expression in rows.iter().flatten() {
reference_expression(expression, &scope, references);
}
for subquery in subqueries {
self.reference_query(subquery, &scope.child(), references)?;
}
Ok(())
}
}
}
fn reference_block(
&self,
block: &QueryBlockPlan,
parent: &Scope,
references: &mut QueryReferences,
) -> Result<(), SQLError> {
let mut scope = parent.clone();
scope.columns = block
.from
.as_ref()
.map(|source| self.source_columns(source, parent))
.transpose()?
.unwrap_or_default();
for projection in &block.projections {
reference_expression(&projection.expr, &scope, references);
}
for expression in block
.r#where
.iter()
.chain(&block.having)
.chain(&block.limit)
.chain(&block.offset)
.chain(&block.group_by)
.chain(&block.distinct_on)
.chain(block.grouping_sets.iter().flatten())
{
reference_expression(expression, &scope, references);
}
for definition in &block.windows {
for expression in definition.spec.expressions() {
reference_expression(expression, &scope, references);
}
}
for order in &block.order_by {
let is_output = matches!(&order.expr, ScalarExpr::Column(name) if block.projections.iter().any(|projection| projection.alias.as_ref() == Some(name)));
if !is_output {
reference_expression(&order.expr, &scope, references);
}
}
if let Some(source) = &block.from {
self.reference_source(source, &scope, references)?;
}
for subquery in &block.subqueries {
self.reference_query(subquery, &scope.child(), references)?;
}
Ok(())
}
fn reference_source(
&self,
source: &SourcePlan,
scope: &Scope,
references: &mut QueryReferences,
) -> Result<(), SQLError> {
match source {
SourcePlan::Table { name, .. } => {
if super::sources::cte_source_columns(scope, name).is_none() {
references.relations.push(name.clone());
}
}
SourcePlan::Subquery { body, .. } => {
self.reference_query(body, &scope.child(), references)?;
}
SourcePlan::Values { rows, .. } => {
for expression in rows.iter().flatten() {
reference_expression(expression, scope, references);
}
}
SourcePlan::Function {
binding,
args,
relations,
..
} => {
references.routines.extend(binding.iter().cloned());
if let Some(relations) = relations {
references.relations.push(relations.left.clone());
references.relations.push(relations.right.clone());
}
for expression in args {
reference_expression(expression, scope, references);
}
}
SourcePlan::FunctionGroup { functions, .. } => {
for function in functions {
references.routines.extend(function.binding.iter().cloned());
if let Some(relations) = &function.relations {
references.relations.push(relations.left.clone());
references.relations.push(relations.right.clone());
}
for expression in &function.args {
reference_expression(expression, scope, references);
}
}
}
SourcePlan::Join {
left,
right,
on,
using,
natural,
..
} => {
let left_columns = self.source_columns(left, scope)?;
let right_columns = self.source_columns(right, scope)?;
let merged = using.as_ref().map_or_else(
|| {
if *natural {
left_columns
.iter()
.filter(|column| {
right_columns.iter().any(|other| other.name == column.name)
})
.map(|column| column.name.clone())
.collect()
} else {
Vec::new()
}
},
|using| using.columns.clone(),
);
for name in &merged {
for columns in [&left_columns, &right_columns] {
if let Some(column) = columns.iter().find(|column| column.name == *name) {
reference_column(column, references);
}
}
}
if let Some(on) = on {
reference_expression(on, scope, references);
}
self.reference_source(left, scope, references)?;
self.reference_source(right, scope, references)?;
}
}
Ok(())
}
}
fn reference_column(column: &Column, references: &mut QueryReferences) {
if let Some((relation, name)) = &column.base {
references.columns.push((relation.clone(), name.clone()));
}
}
fn reference_expression(expression: &ScalarExpr, scope: &Scope, references: &mut QueryReferences) {
expression.visit(&mut |node| match node {
ScalarExpr::Column(name) => {
if let Some(column) = scope_column(scope, None, name) {
reference_column(column, references);
}
}
ScalarExpr::QualifiedColumn { qualifier, column } => {
if let Some(column) = scope_column(scope, Some(qualifier), column) {
reference_column(column, references);
}
}
ScalarExpr::Star => {
for column in &scope.columns {
reference_column(column, references);
}
}
ScalarExpr::QualifiedStar(qualifier) => {
for column in scope
.columns
.iter()
.chain(&scope.outer)
.filter(|column| column.qualifier == *qualifier)
{
reference_column(column, references);
}
}
ScalarExpr::TypedLiteral { value, ty, .. } => {
references.types.push(ty.clone());
if let Value::Int(oid) = value {
references.constants.push((ty.clone(), *oid));
}
}
ScalarExpr::Cast { ty, .. }
| ScalarExpr::CompositeRow {
binding: uqa_sql::ast::CompositeRowBinding { ty, .. },
..
} => references.types.push(ty.clone()),
ScalarExpr::Func {
name,
binding,
args,
..
} => {
references.routines.extend(binding.iter().cloned());
if let Some(sequence) = sequence_argument(name, args) {
references.relations.push(sequence.to_string());
}
}
_ => {}
});
}
fn sequence_argument<'a>(name: &str, args: &'a [ScalarExpr]) -> Option<&'a str> {
let lower = name.to_ascii_lowercase();
let local = lower.strip_prefix("pg_catalog.").unwrap_or(&lower);
if !matches!(local, "nextval" | "currval" | "setval") {
return None;
}
let mut argument = args.first()?;
while let ScalarExpr::Cast { expr, ty, .. } = argument {
if !ty.eq_ignore_ascii_case("regclass") && !ty.eq_ignore_ascii_case("pg_catalog.regclass") {
return None;
}
argument = expr;
}
match argument {
ScalarExpr::Literal(Value::Str(sequence)) => Some(sequence),
_ => None,
}
}
fn scope_column<'a>(scope: &'a Scope, qualifier: Option<&str>, name: &str) -> Option<&'a Column> {
scope.columns.iter().chain(&scope.outer).find(|column| {
column.name == name && qualifier.is_none_or(|qualifier| qualifier == column.qualifier)
})
}