use crate::{
ast,
expr::Identifier,
hierarchy::Path,
sql::visitor::{TableWithJoins, Visitor},
visitor::Visited,
};
use colored::Colorize;
use itertools::Itertools;
use std::{
collections::BTreeMap,
fmt,
iter::Iterator,
ops::{Deref, DerefMut},
};
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct QueryNames<'a>(BTreeMap<(&'a ast::Query, ast::ObjectName), Option<&'a ast::Query>>);
impl<'a> QueryNames<'a> {
pub fn new() -> Self {
QueryNames(BTreeMap::new())
}
pub fn set(&mut self, name: ast::ObjectName, referred: &'a ast::Query) -> &mut Self {
for ((_, n), r) in self.iter_mut() {
if (*n == name) && r.is_none() {
*r = Some(referred);
}
}
self
}
pub fn name_referred(
&self,
query: &'a ast::Query,
) -> impl Iterator<Item = (&ast::ObjectName, &'a ast::Query)> {
self.iter()
.filter_map(move |((q, n), r)| if *q == query { Some((n, (*r)?)) } else { None })
}
}
impl<'a> Deref for QueryNames<'a> {
type Target = BTreeMap<(&'a ast::Query, ast::ObjectName), Option<&'a ast::Query>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'a> DerefMut for QueryNames<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<'a> IntoIterator for QueryNames<'a> {
type Item =
<BTreeMap<(&'a ast::Query, ast::ObjectName), Option<&'a ast::Query>> as IntoIterator>::Item;
type IntoIter = <BTreeMap<(&'a ast::Query, ast::ObjectName), Option<&'a ast::Query>> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<'a> fmt::Display for QueryNames<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Query Names\n{}",
self.0
.iter()
.map(|((q, n), r)| match r {
Some(r) => format!(
"{} | {} -> {}",
format!("{q}").blue(),
format!("{n}").red(),
format!("{r}").green()
),
None => format!(
"{} | {} -> {}",
format!("{q}").blue(),
format!("{n}").red(),
format!("?").bold().green()
),
})
.join(",\n")
)
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub struct IntoQueryNamesVisitor;
fn names_from_set_expr<'a>(set_expr: &'a ast::SetExpr) -> Vec<&'a ast::ObjectName> {
match set_expr {
ast::SetExpr::Select(select) => select
.from
.iter()
.flat_map(|table_with_joins| TableWithJoins::new(table_with_joins).names())
.collect(),
ast::SetExpr::SetOperation { left, right, .. } => names_from_set_expr(left.as_ref())
.into_iter()
.chain(names_from_set_expr(right.as_ref()))
.collect(),
_ => todo!(),
}
}
impl<'a> Visitor<'a, QueryNames<'a>> for IntoQueryNamesVisitor {
fn query(
&self,
query: &'a ast::Query,
dependencies: Visited<'a, ast::Query, QueryNames<'a>>,
) -> QueryNames<'a> {
let mut query_names = QueryNames::new();
for (_, dependency) in dependencies {
query_names.extend(dependency);
}
for name in names_from_set_expr(query.body.as_ref()) {
query_names.insert((query, name.clone()), None);
}
if let Some(with) = &query.with {
for cte in &with.cte_tables {
query_names.set(
ast::ObjectName(vec![cte.alias.name.clone()]),
cte.query.as_ref(),
);
}
}
query_names
}
}
impl Path for ast::ObjectName {
fn path(self) -> Vec<String> {
self.0.path()
}
}
impl From<&ast::ObjectName> for Identifier {
fn from(value: &ast::ObjectName) -> Self {
value.0.iter().map(|i| i.value.clone()).collect()
}
}
#[cfg(test)]
mod tests {
use colored::Colorize;
use super::*;
use crate::{sql::relation, visitor::Acceptor as _};
#[test]
fn test_query_names() {
let query_1 = relation::parse("select * from table_1").unwrap();
let query_2 = relation::parse("select * from table_2").unwrap();
let query_3 = relation::parse("select * from table_3").unwrap();
let query_4 = relation::parse("select * from table_4").unwrap();
let name_1 = ast::ObjectName(vec!["name_1".into()]);
let name_2 = ast::ObjectName(vec!["name_2".into()]);
let name_3 = ast::ObjectName(vec!["name_3".into()]);
println!("query_1 = {}", query_1.to_string().blue());
let mut query_names_1 = QueryNames::new();
let mut query_names_2 = QueryNames::new();
let mut query_names_3 = QueryNames::new();
query_names_2.insert((&query_1, name_1), None);
query_names_2.insert((&query_1, name_2), Some(&query_2));
query_names_3.insert((&query_1, name_3.clone()), Some(&query_3));
query_names_3.insert((&query_2, name_3.clone()), None);
query_names_3.insert((&query_3, name_3.clone()), None);
query_names_1.extend(query_names_2);
query_names_1.extend(query_names_3);
println!("BEFORE: {query_names_1}");
query_names_1.set(name_3.clone(), &query_4);
println!("AFTER: {query_names_1}");
}
const COMPLEX_QUERY: &str = "
with view_1 as (select * from schema.table_1),
view_2 as (select * from view_1)
select 2*a, b+1 from (select view_2.c as a, right.d as b from (select * from view_2) LEFT OUTER JOIN schema.table_2 as right ON view_1.id = table_2.id);
select * from table_1;
";
#[test]
fn test_query_names_visitor() {
let query = relation::parse(COMPLEX_QUERY).unwrap();
println!("Query = {}", query.to_string().blue());
let visitor = IntoQueryNamesVisitor;
let query_names = query.accept(visitor);
println!("{}", query_names);
}
}