use crate::storage::query::ast::{
CompareOp, FieldRef, Filter, InsertQuery, Projection, QueryExpr, QueueCommand, TableQuery,
};
use crate::storage::schema::Value;
pub fn render(expr: &QueryExpr) -> String {
match expr {
QueryExpr::Table(tq) => render_table(tq),
QueryExpr::Insert(iq) => render_insert(iq),
QueryExpr::QueueCommand(qc) => render_queue_command(qc),
_ => String::new(),
}
}
fn render_table(tq: &TableQuery) -> String {
let cols = if tq.columns.is_empty() {
"*".to_string()
} else {
tq.columns
.iter()
.map(render_projection)
.collect::<Vec<_>>()
.join(", ")
};
let mut sql = format!("SELECT {} FROM {}", cols, tq.table);
if let Some(filter) = &tq.filter {
sql.push_str(" WHERE ");
sql.push_str(&render_filter(filter));
}
sql
}
fn render_insert(iq: &InsertQuery) -> String {
let cols = iq.columns.join(", ");
let rows: Vec<String> = iq
.values
.iter()
.map(|row| {
let vals = row
.iter()
.map(render_value_sql)
.collect::<Vec<_>>()
.join(", ");
format!("({})", vals)
})
.collect();
format!(
"INSERT INTO {} ({}) VALUES {}",
iq.table,
cols,
rows.join(", ")
)
}
fn render_queue_command(qc: &QueueCommand) -> String {
match qc {
QueueCommand::Push { queue, value, .. } => {
format!("QUEUE PUSH {} {}", queue, render_value_sql(value))
}
_ => String::new(),
}
}
fn render_projection(p: &Projection) -> String {
match p {
Projection::All => "*".to_string(),
Projection::Column(col) => col.clone(),
Projection::Alias(col, alias) => format!("{} AS {}", col, alias),
Projection::Field(field, alias) => {
let col = render_field_ref(field);
match alias {
Some(a) => format!("{} AS {}", col, a),
None => col,
}
}
_ => "*".to_string(),
}
}
pub(crate) fn render_field_ref(f: &FieldRef) -> String {
match f {
FieldRef::TableColumn { table, column } if table.is_empty() => column.clone(),
FieldRef::TableColumn { table, column } => format!("{}.{}", table, column),
_ => "field".to_string(),
}
}
fn render_filter(filter: &Filter) -> String {
match filter {
Filter::Compare { field, op, value } => {
format!(
"{} {} {}",
render_field_ref(field),
op,
render_value_sql(value)
)
}
Filter::And(a, b) => format!("({}) AND ({})", render_filter(a), render_filter(b)),
Filter::Or(a, b) => format!("({}) OR ({})", render_filter(a), render_filter(b)),
_ => "1=1".to_string(),
}
}
pub(crate) fn render_value_sql(v: &Value) -> String {
match v {
Value::Null => "NULL".to_string(),
Value::Integer(i) => i.to_string(),
Value::UnsignedInteger(u) => u.to_string(),
Value::Float(f) => {
if f.fract() == 0.0 {
format!("{:.1}", f)
} else {
format!("{}", f)
}
}
Value::Boolean(b) => {
if *b {
"true".to_string()
} else {
"false".to_string()
}
}
Value::Text(s) => format!("'{}'", s.replace('\'', "''")),
Value::Json(bytes) => String::from_utf8_lossy(bytes).to_string(),
_ => "NULL".to_string(),
}
}