#[derive(Debug, Clone)]
pub(crate) struct GraphDef {
pub edge_table: String,
pub src_col: String,
pub dst_col: String,
pub node_table: String,
pub id_col: String,
}
#[derive(Debug, Default)]
pub(crate) struct QueryState {
pub table: String,
pub select: Vec<String>,
pub exclude: Vec<String>,
pub extend: Vec<(String, String)>,
pub where_clauses: Vec<String>,
pub group_by: Vec<String>,
pub aggregates: Vec<String>,
pub order_by: Vec<(String, bool)>,
pub limit: Option<u64>,
pub distinct: bool,
pub graph_def: Option<GraphDef>,
pub ctes: Vec<(String, String, bool)>,
}
impl QueryState {
pub(crate) fn new(table: impl Into<String>) -> Self {
Self {
table: table.into(),
..Default::default()
}
}
pub(crate) fn to_sql(&self) -> String {
let prelude = self.render_cte_prelude();
let select_clause = if !self.aggregates.is_empty() {
let mut items: Vec<String> = self.group_by.iter().cloned().collect();
items.extend(self.aggregates.iter().cloned());
items.join(", ")
} else if self.distinct {
let mut items: Vec<String> = Vec::new();
if self.select.is_empty() {
if self.extend.is_empty() {
items.push("*".to_string());
} else {
for (name, expr) in &self.extend {
items.push(format!("({expr}) AS {name}"));
}
}
} else {
for col in &self.select {
if let Some((_, expr)) = self.extend.iter().find(|(n, _)| n == col) {
items.push(format!("({expr}) AS {col}"));
} else {
items.push(col.clone());
}
}
}
items.join(", ")
} else {
let mut items = Vec::new();
if self.select.is_empty() && self.exclude.is_empty() {
items.push("*".to_string());
} else if !self.select.is_empty() {
items.extend(self.select.iter().cloned());
} else {
items.push("*".to_string());
}
for (name, expr) in &self.extend {
items.push(format!("({expr}) AS {name}"));
}
items.join(", ")
};
let distinct = if self.distinct && self.aggregates.is_empty() {
"DISTINCT "
} else {
""
};
let mut sql = format!(
"{prelude}SELECT {distinct}{select_clause} FROM {}",
self.table
);
if !self.where_clauses.is_empty() {
sql.push_str(" WHERE ");
sql.push_str(&self.where_clauses.join(" AND "));
}
if !self.group_by.is_empty() {
sql.push_str(" GROUP BY ");
sql.push_str(&self.group_by.join(", "));
}
if !self.order_by.is_empty() {
sql.push_str(" ORDER BY ");
let parts: Vec<String> = self
.order_by
.iter()
.map(|(c, desc)| {
if *desc {
format!("{c} DESC")
} else {
format!("{c} ASC")
}
})
.collect();
sql.push_str(&parts.join(", "));
}
if let Some(n) = self.limit {
sql.push_str(&format!(" LIMIT {n}"));
}
sql
}
fn render_cte_prelude(&self) -> String {
if self.ctes.is_empty() {
return String::new();
}
let any_recursive = self.ctes.iter().any(|(_, _, r)| *r);
let keyword = if any_recursive {
"WITH RECURSIVE "
} else {
"WITH "
};
let parts: Vec<String> = self
.ctes
.iter()
.map(|(name, body, _)| format!("{name} AS ({body})"))
.collect();
format!("{keyword}{} ", parts.join(", "))
}
}