#[macro_use]
mod fmt;
use fmt::ToSql;
mod column;
use column::ColumnAlias;
mod cte;
mod delim;
use delim::{Comma, Delimited, Period};
mod flavor;
use flavor::Flavor;
mod ident;
use ident::Ident;
mod params;
pub use params::{Params, Placeholder, TypedValue};
mod column_def;
mod expr;
mod name;
mod statement;
mod ty;
mod value;
use crate::stmt::Statement;
use toasty_core::{
driver::operation::{IsolationLevel, Transaction},
schema::db::{self, Index, Table},
};
#[derive(Debug, Clone)]
pub struct InsertContext {
pub table_id: db::TableId,
pub columns: Vec<db::ColumnId>,
}
#[derive(Debug)]
pub struct Serializer<'a> {
schema: &'a db::Schema,
flavor: Flavor,
}
struct Formatter<'a, T> {
serializer: &'a Serializer<'a>,
dst: &'a mut String,
params: &'a mut T,
depth: usize,
alias: bool,
insert_context: Option<InsertContext>,
}
pub type ExprContext<'a> = toasty_core::stmt::ExprContext<'a, db::Schema>;
impl<'a> Serializer<'a> {
pub fn serialize(&self, stmt: &Statement, params: &mut impl Params) -> String {
let mut ret = String::new();
let mut fmt = Formatter {
serializer: self,
dst: &mut ret,
params,
depth: 0,
alias: false,
insert_context: None,
};
let cx = ExprContext::new(self.schema);
stmt.to_sql(&cx, &mut fmt);
ret.push(';');
ret
}
pub fn serialize_transaction(&self, op: &Transaction) -> String {
let mut ret = String::new();
let mut f = Formatter {
serializer: self,
dst: &mut ret,
params: &mut Vec::<TypedValue>::new(),
depth: 0,
alias: false,
insert_context: None,
};
let cx = ExprContext::new(self.schema);
match op {
Transaction::Start {
isolation,
read_only,
} => fmt!(
&cx,
&mut f,
self.serialize_transaction_start(*isolation, *read_only)
),
Transaction::Commit => fmt!(&cx, &mut f, "COMMIT"),
Transaction::Rollback => fmt!(&cx, &mut f, "ROLLBACK"),
Transaction::Savepoint(name) => {
fmt!(&cx, &mut f, "SAVEPOINT " Ident(name))
}
Transaction::ReleaseSavepoint(name) => {
fmt!(&cx, &mut f, "RELEASE SAVEPOINT " Ident(name))
}
Transaction::RollbackToSavepoint(name) => {
fmt!(&cx, &mut f, "ROLLBACK TO SAVEPOINT " Ident(name))
}
};
ret.push(';');
ret
}
fn serialize_transaction_start(
&self,
isolation: Option<IsolationLevel>,
read_only: bool,
) -> String {
fn isolation_level_str(level: IsolationLevel) -> &'static str {
match level {
IsolationLevel::ReadUncommitted => "READ UNCOMMITTED",
IsolationLevel::ReadCommitted => "READ COMMITTED",
IsolationLevel::RepeatableRead => "REPEATABLE READ",
IsolationLevel::Serializable => "SERIALIZABLE",
}
}
match self.flavor {
Flavor::Mysql => {
let mut sql = String::new();
if let Some(level) = isolation {
sql.push_str("SET TRANSACTION ISOLATION LEVEL ");
sql.push_str(isolation_level_str(level));
sql.push_str("; ");
}
sql.push_str("START TRANSACTION");
if read_only {
sql.push_str(" READ ONLY");
}
sql
}
Flavor::Postgresql => {
let mut sql = String::from("BEGIN");
if let Some(level) = isolation {
sql.push_str(" ISOLATION LEVEL ");
sql.push_str(isolation_level_str(level));
}
if read_only {
sql.push_str(" READ ONLY");
}
sql
}
Flavor::Sqlite => {
"BEGIN".to_string()
}
}
}
fn table(&self, id: impl Into<db::TableId>) -> &'a Table {
self.schema.table(id.into())
}
fn index(&self, id: impl Into<db::IndexId>) -> &'a Index {
self.schema.index(id.into())
}
fn table_name(&self, id: impl Into<db::TableId>) -> Ident<&str> {
let table = self.schema.table(id.into());
Ident(&table.name)
}
fn column_name(&self, id: impl Into<db::ColumnId>) -> Ident<&str> {
let column = self.schema.column(id.into());
Ident(&column.name)
}
}