remove ToSqlString trait

This commit is contained in:
Levy A.
2025-07-05 03:05:13 -03:00
parent 6fe2505425
commit 714225b9f0
12 changed files with 8 additions and 2219 deletions

View File

@@ -1,428 +1,2 @@
use std::fmt::Display;
use crate::ast::{self, fmt::ToTokens, Expr};
use super::ToSqlString;
impl ToSqlString for Expr {
fn to_sql_string<C: super::ToSqlContext>(&self, context: &C) -> String {
let mut ret = String::new();
match self {
Expr::Between {
lhs,
not,
start,
end,
} => {
ret.push_str(&lhs.to_sql_string(context));
ret.push(' ');
if *not {
ret.push_str("NOT ");
}
ret.push_str("BETWEEN ");
ret.push_str(&start.to_sql_string(context));
ret.push_str(" AND ");
ret.push_str(&end.to_sql_string(context));
}
Expr::Binary(lhs, op, rhs) => {
ret.push_str(&lhs.to_sql_string(context));
ret.push(' ');
ret.push_str(&op.to_string());
ret.push(' ');
ret.push_str(&rhs.to_sql_string(context));
}
Expr::Case {
base,
when_then_pairs,
else_expr,
} => {
ret.push_str("CASE ");
if let Some(base) = base {
ret.push_str(&base.to_sql_string(context));
ret.push(' ');
}
for (when, then) in when_then_pairs {
ret.push_str("WHEN ");
ret.push_str(&when.to_sql_string(context));
ret.push_str(" THEN ");
ret.push_str(&then.to_sql_string(context));
}
if let Some(else_expr) = else_expr {
ret.push_str(" ELSE ");
ret.push_str(&else_expr.to_sql_string(context));
}
ret.push_str(" END");
}
Expr::Cast { expr, type_name } => {
ret.push_str("CAST");
ret.push('(');
ret.push_str(&expr.to_sql_string(context));
if let Some(type_name) = type_name {
ret.push_str(" AS ");
ret.push_str(&type_name.to_sql_string(context));
}
ret.push(')');
}
Expr::Collate(expr, name) => {
ret.push_str(&expr.to_sql_string(context));
ret.push_str(" COLLATE ");
ret.push_str(name);
}
Expr::DoublyQualified(name, name1, name2) => {
ret.push_str(&name.0);
ret.push('.');
ret.push_str(&name1.0);
ret.push('.');
ret.push_str(&name2.0);
}
Expr::Exists(select) => {
ret.push_str("EXISTS (");
ret.push_str(&select.to_sql_string(context));
ret.push(')');
}
Expr::FunctionCall {
name,
distinctness: _,
args,
order_by: _,
filter_over,
} => {
ret.push_str(&name.0);
// TODO: pretty sure there should be no ORDER_BY nor DISTINCT
ret.push('(');
if let Some(args) = args {
let joined_args = args
.iter()
.map(|arg| arg.to_sql_string(context))
.collect::<Vec<_>>()
.join(", ");
ret.push_str(&joined_args);
}
ret.push(')');
if let Some(filter_over) = filter_over {
if let Some(filter) = &filter_over.filter_clause {
ret.push_str(&format!(
" FILTER (WHERE {})",
filter.to_sql_string(context)
));
}
if let Some(over) = &filter_over.over_clause {
ret.push(' ');
ret.push_str(&over.to_sql_string(context));
}
}
}
Expr::FunctionCallStar { name, filter_over } => {
ret.push_str(&name.0);
ret.push_str("(*)");
if let Some(filter_over) = filter_over {
if let Some(filter) = &filter_over.filter_clause {
ret.push_str(&format!(
" FILTER (WHERE {})",
filter.to_sql_string(context)
));
}
if let Some(over) = &filter_over.over_clause {
ret.push(' ');
ret.push_str(&over.to_sql_string(context));
}
}
}
Expr::Id(id) => {
ret.push_str(&id.0);
}
Expr::Column {
database: _, // TODO: Ignore database for now
table,
column,
is_rowid_alias: _,
} => {
ret.push_str(context.get_table_name(*table));
ret.push('.');
ret.push_str(context.get_column_name(*table, *column));
}
Expr::RowId { database: _, table } => {
ret.push_str(&format!("{}.rowid", context.get_table_name(*table)))
}
Expr::InList { lhs, not, rhs } => {
ret.push_str(&format!(
"{} {}IN ({})",
lhs.to_sql_string(context),
if *not { "NOT " } else { "" },
if let Some(rhs) = rhs {
rhs.iter()
.map(|expr| expr.to_sql_string(context))
.collect::<Vec<_>>()
.join(", ")
} else {
"".to_string()
}
));
}
Expr::InSelect { lhs, not, rhs } => {
ret.push_str(&format!(
"{} {}IN ({})",
lhs.to_sql_string(context),
if *not { "NOT " } else { "" },
rhs.to_sql_string(context)
));
}
Expr::InTable {
lhs,
not,
rhs,
args,
} => {
ret.push_str(&lhs.to_sql_string(context));
ret.push(' ');
if *not {
ret.push_str("NOT ");
}
ret.push_str(&rhs.to_sql_string(context));
if let Some(args) = args {
ret.push('(');
let joined_args = args
.iter()
.map(|expr| expr.to_sql_string(context))
.collect::<Vec<_>>()
.join(", ");
ret.push_str(&joined_args);
ret.push(')');
}
}
Expr::IsNull(expr) => {
ret.push_str(&expr.to_sql_string(context));
ret.push_str(" ISNULL");
}
Expr::Like {
lhs,
not,
op,
rhs,
escape,
} => {
ret.push_str(&lhs.to_sql_string(context));
ret.push(' ');
if *not {
ret.push_str("NOT ");
}
ret.push_str(&op.to_string());
ret.push(' ');
ret.push_str(&rhs.to_sql_string(context));
if let Some(escape) = escape {
ret.push_str(" ESCAPE ");
ret.push_str(&escape.to_sql_string(context));
}
}
Expr::Literal(literal) => {
ret.push_str(&literal.to_string());
}
Expr::Name(name) => {
ret.push_str(&name.0);
}
Expr::NotNull(expr) => {
ret.push_str(&expr.to_sql_string(context));
ret.push_str(" NOT NULL");
}
Expr::Parenthesized(exprs) => {
ret.push('(');
let joined_args = exprs
.iter()
.map(|expr| expr.to_sql_string(context))
.collect::<Vec<_>>()
.join(", ");
ret.push_str(&joined_args);
ret.push(')');
}
Expr::Qualified(name, name1) => {
ret.push_str(&name.0);
ret.push('.');
ret.push_str(&name1.0);
}
Expr::Raise(resolve_type, expr) => {
ret.push_str("RAISE(");
ret.push_str(&resolve_type.to_string());
if let Some(expr) = expr {
ret.push_str(", ");
ret.push_str(&expr.to_sql_string(context));
}
ret.push(')');
}
Expr::Subquery(select) => {
ret.push('(');
ret.push_str(&select.to_sql_string(context));
ret.push(')');
}
Expr::Unary(unary_operator, expr) => {
ret.push_str(&unary_operator.to_string());
ret.push(' ');
ret.push_str(&expr.to_sql_string(context));
}
Expr::Variable(variable) => {
ret.push_str(variable);
}
};
ret
}
}
impl Display for ast::Operator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let value = match self {
Self::Add => "+",
Self::And => "AND",
Self::ArrowRight => "->",
Self::ArrowRightShift => "->>",
Self::BitwiseAnd => "&",
Self::BitwiseNot => "~",
Self::BitwiseOr => "|",
Self::Concat => "||",
Self::Divide => "/",
Self::Equals => "=",
Self::Greater => ">",
Self::GreaterEquals => ">=",
Self::Is => "IS",
Self::IsNot => "IS NOT",
Self::LeftShift => "<<",
Self::Less => "<",
Self::LessEquals => "<=",
Self::Modulus => "%",
Self::Multiply => "*",
Self::NotEquals => "!=",
Self::Or => "OR",
Self::RightShift => ">>",
Self::Subtract => "-",
};
write!(f, "{value}")
}
}
impl ToSqlString for ast::Type {
fn to_sql_string<C: super::ToSqlContext>(&self, context: &C) -> String {
let mut ret = self.name.clone();
if let Some(size) = &self.size {
ret.push(' ');
ret.push('(');
ret.push_str(&size.to_sql_string(context));
ret.push(')');
}
ret
}
}
impl ToSqlString for ast::TypeSize {
fn to_sql_string<C: super::ToSqlContext>(&self, context: &C) -> String {
let mut ret = String::new();
match self {
Self::MaxSize(e) => {
ret.push_str(&e.to_sql_string(context));
}
Self::TypeSize(lhs, rhs) => {
ret.push_str(&lhs.to_sql_string(context));
ret.push_str(", ");
ret.push_str(&rhs.to_sql_string(context));
}
};
ret
}
}
impl Display for ast::Distinctness {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::All => "ALL",
Self::Distinct => "DISTINCT",
}
)
}
}
// Can't impl Display here as it is already implemented for it
impl ToSqlString for ast::QualifiedName {
fn to_sql_string<C: super::ToSqlContext>(&self, _context: &C) -> String {
let mut ret = String::new();
if let Some(db_name) = &self.db_name {
ret.push_str(&db_name.0);
ret.push('.');
}
if let Some(alias) = &self.alias {
ret.push_str(&alias.0);
ret.push('.');
}
ret.push_str(&self.name.0);
ret
}
}
impl Display for ast::LikeOperator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.to_fmt(f)
}
}
impl Display for ast::Literal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Blob(b) => format!("x'{b}'"),
Self::CurrentDate => "CURRENT_DATE".to_string(),
Self::CurrentTime => "CURRENT_TIME".to_string(),
Self::CurrentTimestamp => "CURRENT_TIMESTAMP".to_string(),
Self::Keyword(keyword) => keyword.clone(),
Self::Null => "NULL".to_string(),
Self::Numeric(num) => num.clone(),
Self::String(s) => s.clone(),
}
)
}
}
impl Display for ast::ResolveType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.to_fmt(f)
}
}
impl Display for ast::UnaryOperator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::BitwiseNot => "~",
Self::Negative => "-",
Self::Not => "NOT",
Self::Positive => "+",
}
)
}
}
impl ToSqlString for ast::Over {
fn to_sql_string<C: super::ToSqlContext>(&self, context: &C) -> String {
let mut ret = vec!["OVER".to_string()];
match self {
Self::Name(name) => {
ret.push(name.0.clone());
}
Self::Window(window) => {
ret.push(window.to_sql_string(context));
}
}
ret.join(" ")
}
}
#[cfg(test)]
mod tests {}

View File

@@ -15,18 +15,6 @@ pub trait ToSqlContext {
fn get_column_name(&self, table_id: TableInternalId, col_idx: usize) -> &str;
}
/// Trait to convert an ast to a string
pub trait ToSqlString {
/// Convert the given value to String
fn to_sql_string<C: ToSqlContext>(&self, context: &C) -> String;
}
impl<T: ToSqlString> ToSqlString for Box<T> {
fn to_sql_string<C: ToSqlContext>(&self, context: &C) -> String {
T::to_sql_string(self, context)
}
}
#[cfg(test)]
mod tests {
use super::ToSqlContext;

View File

@@ -1,206 +1,3 @@
use std::fmt::Display;
use crate::{ast, to_sql_string::ToSqlString};
impl ToSqlString for ast::AlterTableBody {
fn to_sql_string<C: crate::to_sql_string::ToSqlContext>(&self, context: &C) -> String {
match self {
Self::AddColumn(col_def) => format!("ADD COLUMN {}", col_def.to_sql_string(context)),
Self::DropColumn(name) => format!("DROP COLUMN {}", name.0),
Self::RenameColumn { old, new } => format!("RENAME COLUMN {} TO {}", old.0, new.0),
Self::RenameTo(name) => format!("RENAME TO {}", name.0),
}
}
}
impl ToSqlString for ast::ColumnDefinition {
fn to_sql_string<C: crate::to_sql_string::ToSqlContext>(&self, context: &C) -> String {
format!(
"{}{}{}",
self.col_name.0,
if let Some(col_type) = &self.col_type {
format!(" {}", col_type.to_sql_string(context))
} else {
"".to_string()
},
if !self.constraints.is_empty() {
format!(
" {}",
self.constraints
.iter()
.map(|constraint| constraint.to_sql_string(context))
.collect::<Vec<_>>()
.join(" ")
)
} else {
"".to_string()
}
)
}
}
impl ToSqlString for ast::NamedColumnConstraint {
fn to_sql_string<C: crate::to_sql_string::ToSqlContext>(&self, context: &C) -> String {
let mut ret = Vec::new();
if let Some(name) = &self.name {
ret.push(format!("CONSTRAINT {}", name.0));
}
ret.push(self.constraint.to_sql_string(context));
ret.join(" ")
}
}
impl ToSqlString for ast::ColumnConstraint {
fn to_sql_string<C: crate::to_sql_string::ToSqlContext>(&self, context: &C) -> String {
match self {
Self::Check(expr) => format!("CHECK ({})", expr.to_sql_string(context)),
Self::Collate { collation_name } => format!("COLLATE {}", collation_name.0),
Self::Default(expr) => {
if matches!(expr, ast::Expr::Literal(..)) {
format!("DEFAULT {}", expr.to_sql_string(context))
} else {
format!("DEFAULT ({})", expr.to_sql_string(context))
}
}
Self::Defer(expr) => expr.to_string(),
Self::ForeignKey {
clause,
deref_clause,
} => format!(
"{}{}",
clause,
if let Some(deref) = deref_clause {
deref.to_string()
} else {
"".to_string()
}
),
Self::Generated { expr, typ } => {
// Don't need to add the generated part
format!(
"AS ({}){}",
expr.to_sql_string(context),
if let Some(typ) = typ {
format!(" {}", &typ.0)
} else {
"".to_string()
}
)
}
Self::NotNull {
nullable: _,
conflict_clause,
} => {
// nullable should always be true here
format!(
"NOT NULL{}",
conflict_clause.map_or("".to_string(), |conflict| format!(" {conflict}"))
)
}
Self::PrimaryKey {
order,
conflict_clause,
auto_increment,
} => {
format!(
"PRIMARY KEY{}{}{}",
order.map_or("".to_string(), |order| format!(" {order}")),
conflict_clause.map_or("".to_string(), |conflict| format!(" {conflict}")),
auto_increment.then_some(" AUTOINCREMENT").unwrap_or("")
)
}
Self::Unique(conflict_clause) => {
format!(
"UNIQUE{}",
conflict_clause.map_or("".to_string(), |conflict| format!(" {conflict}"))
)
}
}
}
}
impl Display for ast::ForeignKeyClause {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let value = format!(
"REFERENCES {}{}{}",
self.tbl_name.0,
if let Some(columns) = &self.columns {
format!(
"({})",
columns
.iter()
.map(|cols| cols.to_string())
.collect::<Vec<_>>()
.join(", ")
)
} else {
"".to_string()
},
if !self.args.is_empty() {
format!(
" {}",
self.args
.iter()
.map(|arg| arg.to_string())
.collect::<Vec<_>>()
.join(" ")
)
} else {
"".to_string()
}
);
write!(f, "{value}")
}
}
impl Display for ast::RefArg {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let value = match self {
Self::Match(name) => format!("MATCH {}", name.0),
Self::OnDelete(act) => format!("ON DELETE {act}"),
Self::OnUpdate(act) => format!("ON UPDATE {act}"),
Self::OnInsert(..) => unimplemented!(
"On Insert does not exist in SQLite: https://www.sqlite.org/lang_altertable.html"
),
};
write!(f, "{value}")
}
}
impl Display for ast::RefAct {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let value = match self {
Self::Cascade => "CASCADE",
Self::NoAction => "NO ACTION",
Self::Restrict => "RESTRICT",
Self::SetDefault => "SET DEFAULT",
Self::SetNull => "SET NULL",
};
write!(f, "{value}")
}
}
impl Display for ast::DeferSubclause {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let value = format!(
"{}{}",
if self.deferrable {
"NOT DEFERRABLE"
} else {
"DEFERRABLE"
},
if let Some(init_deffered) = &self.init_deferred {
match init_deffered {
ast::InitDeferredPred::InitiallyDeferred => " INITIALLY DEFERRED",
ast::InitDeferredPred::InitiallyImmediate => " INITIALLY IMMEDIATE",
}
} else {
""
}
);
write!(f, "{value}")
}
}
#[cfg(test)]
mod tests {
use crate::to_sql_string_test;

View File

@@ -1,122 +1,3 @@
use std::fmt::Display;
use crate::{ast, to_sql_string::ToSqlString};
impl ToSqlString for ast::CreateTableBody {
fn to_sql_string<C: crate::to_sql_string::ToSqlContext>(&self, context: &C) -> String {
match self {
Self::AsSelect(select) => format!("AS {}", select.to_sql_string(context)),
Self::ColumnsAndConstraints {
columns,
constraints,
options,
} => {
format!(
"({}{}){}",
columns
.iter()
.map(|(_, col)| col.to_sql_string(context))
.collect::<Vec<_>>()
.join(", "),
constraints
.as_ref()
.map_or("".to_string(), |constraints| format!(
", {}",
constraints
.iter()
.map(|constraint| constraint.to_sql_string(context))
.collect::<Vec<_>>()
.join(", ")
)),
options
)
}
}
}
}
impl ToSqlString for ast::NamedTableConstraint {
fn to_sql_string<C: crate::to_sql_string::ToSqlContext>(&self, context: &C) -> String {
if let Some(name) = &self.name {
format!(
"CONSTRAINT {} {}",
name.0,
self.constraint.to_sql_string(context)
)
} else {
self.constraint.to_sql_string(context).to_string()
}
}
}
impl ToSqlString for ast::TableConstraint {
fn to_sql_string<C: crate::to_sql_string::ToSqlContext>(&self, context: &C) -> String {
match self {
Self::Check(expr) => format!("CHECK ({})", expr.to_sql_string(context)),
Self::ForeignKey {
columns,
clause,
deref_clause,
} => format!(
"FOREIGN KEY ({}) {}{}",
columns
.iter()
.map(|col| col.to_string())
.collect::<Vec<_>>()
.join(", "),
clause,
if let Some(deref) = deref_clause {
deref.to_string()
} else {
"".to_string()
}
),
Self::PrimaryKey {
columns,
auto_increment,
conflict_clause,
} => format!(
"PRIMARY KEY ({}){}{}",
columns
.iter()
.map(|col| col.to_sql_string(context))
.collect::<Vec<_>>()
.join(", "),
conflict_clause.map_or("".to_string(), |conflict| format!(" {conflict}")),
auto_increment.then_some(" AUTOINCREMENT").unwrap_or("")
),
Self::Unique {
columns,
conflict_clause,
} => format!(
"UNIQUE ({}){}",
columns
.iter()
.map(|col| col.to_sql_string(context))
.collect::<Vec<_>>()
.join(", "),
conflict_clause.map_or("".to_string(), |conflict| format!(" {conflict}"))
),
}
}
}
impl Display for ast::TableOptions {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
if *self == Self::NONE {
""
} else if *self == Self::STRICT {
" STRICT"
} else {
" WITHOUT ROWID"
}
)
}
}
#[cfg(test)]
mod tests {
use crate::to_sql_string_test;

View File

@@ -1,248 +1,3 @@
use std::fmt::Display;
use crate::{
ast::{self, fmt::ToTokens},
to_sql_string::ToSqlString,
};
impl ToSqlString for ast::CreateTrigger {
fn to_sql_string<C: crate::to_sql_string::ToSqlContext>(&self, context: &C) -> String {
format!(
"CREATE{} TRIGGER {}{}{} {} ON {}{}{} BEGIN {} END;",
if self.temporary { " TEMP" } else { "" },
if self.if_not_exists {
"IF NOT EXISTS "
} else {
""
},
self.trigger_name.to_sql_string(context),
self.time.map_or("".to_string(), |time| format!(" {time}")),
self.event,
self.tbl_name.to_sql_string(context),
if self.for_each_row {
" FOR EACH ROW"
} else {
""
},
self.when_clause
.as_ref()
.map_or("".to_string(), |expr| format!(
" WHEN {}",
expr.to_sql_string(context)
)),
self.commands
.iter()
.map(|command| format!("{};", command.to_sql_string(context)))
.collect::<Vec<_>>()
.join(" ")
)
}
}
impl Display for ast::TriggerTime {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.to_fmt(f)
}
}
impl Display for ast::TriggerEvent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Delete => "DELETE".to_string(),
Self::Insert => "INSERT".to_string(),
Self::Update => "UPDATE".to_string(),
Self::UpdateOf(col_names) => format!(
"UPDATE OF {}",
col_names
.iter()
.map(|name| name.0.clone())
.collect::<Vec<_>>()
.join(", ")
),
}
)
}
}
impl ToSqlString for ast::TriggerCmd {
fn to_sql_string<C: crate::to_sql_string::ToSqlContext>(&self, context: &C) -> String {
match self {
Self::Delete(delete) => delete.to_sql_string(context),
Self::Insert(insert) => insert.to_sql_string(context),
Self::Select(select) => select.to_sql_string(context),
Self::Update(update) => update.to_sql_string(context),
}
}
}
impl ToSqlString for ast::TriggerCmdDelete {
fn to_sql_string<C: crate::to_sql_string::ToSqlContext>(&self, context: &C) -> String {
// https://sqlite.org/lang_createtrigger.html
// TODO: no CTEs and returning clause present in ast for delete
// Also for tbl_name it should be a qualified table name with indexed by clauses
format!(
"DELETE FROM {}{}",
self.tbl_name.0,
self.where_clause
.as_ref()
.map_or("".to_string(), |expr| format!(
" WHERE {}",
expr.to_sql_string(context)
))
)
}
}
impl ToSqlString for ast::TriggerCmdInsert {
fn to_sql_string<C: crate::to_sql_string::ToSqlContext>(&self, context: &C) -> String {
// https://sqlite.org/lang_createtrigger.html
// FOR TRIGGER SHOULD JUST USE REGULAR INSERT AST
// TODO: no ALIAS after table name
// TODO: no DEFAULT VALUES
format!(
"INSERT {}INTO {} {}{}{}{}",
self.or_conflict
.map_or("".to_string(), |conflict| format!("OR {conflict} ")),
self.tbl_name.0,
self.col_names
.as_ref()
.map_or("".to_string(), |col_names| format!(
"({}) ",
col_names
.iter()
.map(|name| name.0.clone())
.collect::<Vec<_>>()
.join(", ")
)),
self.select.to_sql_string(context),
self.upsert
.as_ref()
.map_or("".to_string(), |upsert| format!(
" {}",
upsert.to_sql_string(context)
)),
self.returning
.as_ref()
.map_or("".to_string(), |returning| format!(
" RETURNING {}",
returning
.iter()
.map(|col| col.to_sql_string(context))
.collect::<Vec<_>>()
.join(", ")
))
)
}
}
impl ToSqlString for ast::Upsert {
fn to_sql_string<C: crate::to_sql_string::ToSqlContext>(&self, context: &C) -> String {
format!(
"ON CONFLICT{}{}{}",
self.index.as_ref().map_or("".to_string(), |index| format!(
"{} ",
index.to_sql_string(context)
)),
self.do_clause.to_sql_string(context),
self.next.as_ref().map_or("".to_string(), |next| format!(
" {}",
next.to_sql_string(context)
))
)
}
}
impl ToSqlString for ast::UpsertIndex {
fn to_sql_string<C: crate::to_sql_string::ToSqlContext>(&self, context: &C) -> String {
format!(
"({}){}",
self.targets
.iter()
.map(|target| target.to_sql_string(context))
.collect::<Vec<_>>()
.join(", "),
self.where_clause
.as_ref()
.map_or("".to_string(), |expr| format!(
" WHERE {}",
expr.to_sql_string(context)
))
)
}
}
impl ToSqlString for ast::UpsertDo {
fn to_sql_string<C: crate::to_sql_string::ToSqlContext>(&self, context: &C) -> String {
match self {
Self::Nothing => "DO NOTHING".to_string(),
Self::Set { sets, where_clause } => {
format!(
"DO UPDATE SET {}{}",
sets.iter()
.map(|set| set.to_sql_string(context))
.collect::<Vec<_>>()
.join(", "),
where_clause.as_ref().map_or("".to_string(), |expr| format!(
" WHERE {}",
expr.to_sql_string(context)
))
)
}
}
}
}
impl ToSqlString for ast::Set {
fn to_sql_string<C: crate::to_sql_string::ToSqlContext>(&self, context: &C) -> String {
if self.col_names.len() == 1 {
format!(
"{} = {}",
&self.col_names[0],
self.expr.to_sql_string(context)
)
} else {
format!(
"({}) = {}",
self.col_names
.iter()
.map(|name| name.0.clone())
.collect::<Vec<_>>()
.join(", "),
self.expr.to_sql_string(context)
)
}
}
}
impl ToSqlString for ast::TriggerCmdUpdate {
fn to_sql_string<C: crate::to_sql_string::ToSqlContext>(&self, context: &C) -> String {
format!(
"UPDATE {}{} SET {}{}{}",
self.or_conflict
.map_or("".to_string(), |conflict| format!("OR {conflict}")),
self.tbl_name.0, // TODO: should be a qualified table name,
self.sets
.iter()
.map(|set| set.to_sql_string(context))
.collect::<Vec<_>>()
.join(", "),
self.from.as_ref().map_or("".to_string(), |from| format!(
" {}",
from.to_sql_string(context)
)),
self.where_clause
.as_ref()
.map_or("".to_string(), |expr| format!(
" WHERE {}",
expr.to_sql_string(context)
))
)
}
}
#[cfg(test)]
mod tests {
use crate::to_sql_string_test;

View File

@@ -1,23 +1,3 @@
use crate::{ast, to_sql_string::ToSqlString};
impl ToSqlString for ast::CreateVirtualTable {
fn to_sql_string<C: crate::to_sql_string::ToSqlContext>(&self, context: &C) -> String {
format!(
"CREATE VIRTUAL TABLE {}{} USING {}{};",
if self.if_not_exists {
"IF NOT EXISTS "
} else {
""
},
self.tbl_name.to_sql_string(context),
self.module_name.0,
self.args
.as_ref()
.map_or("".to_string(), |args| format!("({})", args.join(", ")))
)
}
}
#[cfg(test)]
mod tests {
use crate::to_sql_string_test;

View File

@@ -1,51 +1,3 @@
use crate::{ast, to_sql_string::ToSqlString};
impl ToSqlString for ast::Delete {
fn to_sql_string<C: crate::to_sql_string::ToSqlContext>(&self, context: &C) -> String {
format!(
"{}DELETE FROM {}{}{}{}{}{};",
self.with.as_ref().map_or("".to_string(), |with| format!(
"{} ",
with.to_sql_string(context)
)),
self.tbl_name.to_sql_string(context),
self.indexed
.as_ref()
.map_or("".to_string(), |indexed| format!(" {indexed}")),
self.where_clause
.as_ref()
.map_or("".to_string(), |expr| format!(
" WHERE {}",
expr.to_sql_string(context)
)),
self.returning
.as_ref()
.map_or("".to_string(), |returning| format!(
" RETURNING {}",
returning
.iter()
.map(|col| col.to_sql_string(context))
.collect::<Vec<_>>()
.join(", ")
)),
self.order_by
.as_ref()
.map_or("".to_string(), |order_by| format!(
" ORDER BY {}",
order_by
.iter()
.map(|col| col.to_sql_string(context))
.collect::<Vec<_>>()
.join(", ")
)),
self.limit.as_ref().map_or("".to_string(), |limit| format!(
" {}",
limit.to_sql_string(context)
))
)
}
}
#[cfg(test)]
mod tests {
use crate::to_sql_string_test;
@@ -54,10 +6,7 @@ mod tests {
to_sql_string_test!(test_delete_all, "DELETE FROM employees");
// DELETE with a simple WHERE clause
to_sql_string_test!(
test_delete_with_where,
"DELETE FROM employees WHERE id = 1"
);
to_sql_string_test!(test_delete_with_where, "DELETE FROM employees WHERE id = 1");
// DELETE with multiple WHERE conditions
to_sql_string_test!(

View File

@@ -1,57 +1,3 @@
use crate::{ast, to_sql_string::ToSqlString};
impl ToSqlString for ast::Insert {
fn to_sql_string<C: crate::to_sql_string::ToSqlContext>(&self, context: &C) -> String {
format!(
"{}INSERT {}INTO {} {}{}{}",
self.with.as_ref().map_or("".to_string(), |with| format!(
"{} ",
with.to_sql_string(context)
)),
self.or_conflict
.map_or("".to_string(), |conflict| format!("OR {conflict} ")),
self.tbl_name.to_sql_string(context),
self.columns
.as_ref()
.map_or("".to_string(), |col_names| format!(
"({}) ",
col_names
.iter()
.map(|name| name.0.clone())
.collect::<Vec<_>>()
.join(", ")
)),
self.body.to_sql_string(context),
self.returning
.as_ref()
.map_or("".to_string(), |returning| format!(
" RETURNING {}",
returning
.iter()
.map(|col| col.to_sql_string(context))
.collect::<Vec<_>>()
.join(", ")
))
)
}
}
impl ToSqlString for ast::InsertBody {
fn to_sql_string<C: crate::to_sql_string::ToSqlContext>(&self, context: &C) -> String {
match self {
Self::DefaultValues => "DEFAULT VALUES".to_string(),
Self::Select(select, upsert) => format!(
"{}{}",
select.to_sql_string(context),
upsert.as_ref().map_or("".to_string(), |upsert| format!(
" {}",
upsert.to_sql_string(context)
)),
),
}
}
}
#[cfg(test)]
mod tests {
use crate::to_sql_string_test;

View File

@@ -1,7 +1,3 @@
use crate::ast;
use super::ToSqlString;
mod alter_table;
mod create_table;
mod create_trigger;
@@ -11,196 +7,6 @@ mod insert;
mod select;
mod update;
impl ToSqlString for ast::Stmt {
fn to_sql_string<C: super::ToSqlContext>(&self, context: &C) -> String {
match self {
Self::AlterTable(alter_table) => {
let (name, body) = alter_table.as_ref();
format!(
"ALTER TABLE {} {};",
name.to_sql_string(context),
body.to_sql_string(context)
)
}
Self::Analyze(name) => {
if let Some(name) = name {
format!("ANALYZE {};", name.to_sql_string(context))
} else {
"ANALYZE;".to_string()
}
}
Self::Attach {
expr,
db_name,
key: _,
} => {
// TODO: what is `key` in the attach syntax?
format!(
"ATTACH {} AS {};",
expr.to_sql_string(context),
db_name.to_sql_string(context)
)
}
// TODO: not sure where name is applied here
// https://www.sqlite.org/lang_transaction.html
Self::Begin(transaction_type, _name) => {
let t_type = transaction_type.map_or("", |t_type| match t_type {
ast::TransactionType::Deferred => " DEFERRED",
ast::TransactionType::Exclusive => " EXCLUSIVE",
ast::TransactionType::Immediate => " IMMEDIATE",
});
format!("BEGIN{t_type};")
}
// END or COMMIT are equivalent here, so just defaulting to COMMIT
// TODO: again there are no names in the docs
Self::Commit(_name) => "COMMIT;".to_string(),
Self::CreateIndex {
unique,
if_not_exists,
idx_name,
tbl_name,
columns,
where_clause,
} => format!(
"CREATE {}INDEX {}{} ON {} ({}){};",
unique.then_some("UNIQUE ").unwrap_or(""),
if_not_exists.then_some("IF NOT EXISTS ").unwrap_or(""),
idx_name.to_sql_string(context),
tbl_name.0,
columns
.iter()
.map(|col| col.to_sql_string(context))
.collect::<Vec<_>>()
.join(", "),
where_clause
.as_ref()
.map_or("".to_string(), |where_clause| format!(
" WHERE {}",
where_clause.to_sql_string(context)
))
),
Self::CreateTable {
temporary,
if_not_exists,
tbl_name,
body,
} => format!(
"CREATE{} TABLE {}{} {};",
temporary.then_some(" TEMP").unwrap_or(""),
if_not_exists.then_some("IF NOT EXISTS ").unwrap_or(""),
tbl_name.to_sql_string(context),
body.to_sql_string(context)
),
Self::CreateTrigger(trigger) => trigger.to_sql_string(context),
Self::CreateView {
temporary,
if_not_exists,
view_name,
columns,
select,
} => {
format!(
"CREATE{} VIEW {}{}{} AS {};",
temporary.then_some(" TEMP").unwrap_or(""),
if_not_exists.then_some("IF NOT EXISTS ").unwrap_or(""),
view_name.to_sql_string(context),
columns.as_ref().map_or("".to_string(), |columns| format!(
" ({})",
columns
.iter()
.map(|col| col.to_string())
.collect::<Vec<_>>()
.join(", ")
)),
select.to_sql_string(context)
)
}
Self::CreateVirtualTable(create_virtual_table) => {
create_virtual_table.to_sql_string(context)
}
Self::Delete(delete) => delete.to_sql_string(context),
Self::Detach(name) => format!("DETACH {};", name.to_sql_string(context)),
Self::DropIndex {
if_exists,
idx_name,
} => format!(
"DROP INDEX{} {};",
if_exists.then_some("IF EXISTS ").unwrap_or(""),
idx_name.to_sql_string(context)
),
Self::DropTable {
if_exists,
tbl_name,
} => format!(
"DROP TABLE{} {};",
if_exists.then_some("IF EXISTS ").unwrap_or(""),
tbl_name.to_sql_string(context)
),
Self::DropTrigger {
if_exists,
trigger_name,
} => format!(
"DROP TRIGGER{} {};",
if_exists.then_some("IF EXISTS ").unwrap_or(""),
trigger_name.to_sql_string(context)
),
Self::DropView {
if_exists,
view_name,
} => format!(
"DROP VIEW{} {};",
if_exists.then_some("IF EXISTS ").unwrap_or(""),
view_name.to_sql_string(context)
),
Self::Insert(insert) => format!("{};", insert.to_sql_string(context)),
Self::Pragma(name, body) => format!(
"PRAGMA {}{};",
name.to_sql_string(context),
body.as_ref()
.map_or("".to_string(), |body| match body.as_ref() {
ast::PragmaBody::Equals(expr) =>
format!(" = {}", expr.to_sql_string(context)),
ast::PragmaBody::Call(expr) => format!("({})", expr.to_sql_string(context)),
})
),
// TODO: missing collation name
Self::Reindex { obj_name } => format!(
"REINDEX{};",
obj_name.as_ref().map_or("".to_string(), |name| format!(
" {}",
name.to_sql_string(context)
))
),
Self::Release(name) => format!("RELEASE {};", name.0),
Self::Rollback {
// TODO: there is no transaction name in SQLITE
// https://www.sqlite.org/lang_transaction.html
tx_name: _,
savepoint_name,
} => format!(
"ROLLBACK{};",
savepoint_name
.as_ref()
.map_or("".to_string(), |name| format!(" TO {}", name.0))
),
Self::Savepoint(name) => format!("SAVEPOINT {};", name.0),
Self::Select(select) => format!("{};", select.to_sql_string(context)),
Self::Update(update) => format!("{};", update.to_sql_string(context)),
Self::Vacuum(name, expr) => {
format!(
"VACUUM{}{};",
name.as_ref()
.map_or("".to_string(), |name| format!(" {}", name.0)),
expr.as_ref().map_or("".to_string(), |expr| format!(
" INTO {}",
expr.to_sql_string(context)
))
)
}
}
}
}
#[cfg(test)]
mod tests {
use crate::to_sql_string::ToSqlContext;
@@ -211,7 +17,7 @@ mod tests {
($test_name:ident, $input:expr) => {
#[test]
fn $test_name() {
use crate::parser::ast::fmt::ToTokens;
use $crate::parser::ast::fmt::ToTokens;
let context = $crate::to_sql_string::stmt::tests::TestContext;
let input = $input.split_whitespace().collect::<Vec<&str>>().join(" ");
let mut parser = $crate::lexer::sql::Parser::new(input.as_bytes());
@@ -228,7 +34,7 @@ mod tests {
#[test]
$(#[$attribute])*
fn $test_name() {
use crate::parser::ast::fmt::ToTokens;
use $crate::parser::ast::fmt::ToTokens;
let context = $crate::to_sql_string::stmt::tests::TestContext;
let input = $input.split_whitespace().collect::<Vec<&str>>().join(" ");
let mut parser = $crate::lexer::sql::Parser::new(input.as_bytes());

View File

@@ -1,533 +1,3 @@
use std::fmt::Display;
use crate::{
ast::{self, fmt::ToTokens},
to_sql_string::{ToSqlContext, ToSqlString},
};
impl ToSqlString for ast::Select {
fn to_sql_string<C: ToSqlContext>(&self, context: &C) -> String {
let mut ret = Vec::new();
if let Some(with) = &self.with {
ret.push(with.to_sql_string(context));
}
ret.push(self.body.to_sql_string(context));
if let Some(order_by) = &self.order_by {
// TODO: SortedColumn missing collation in ast
let joined_cols = order_by
.iter()
.map(|col| col.to_sql_string(context))
.collect::<Vec<_>>()
.join(", ");
ret.push(format!("ORDER BY {joined_cols}"));
}
if let Some(limit) = &self.limit {
ret.push(limit.to_sql_string(context));
}
ret.join(" ")
}
}
impl ToSqlString for ast::SelectBody {
fn to_sql_string<C: ToSqlContext>(&self, context: &C) -> String {
let mut ret = self.select.to_sql_string(context);
if let Some(compounds) = &self.compounds {
ret.push(' ');
let compound_selects = compounds
.iter()
.map(|compound_select| {
let mut curr = compound_select.operator.to_string();
curr.push(' ');
curr.push_str(&compound_select.select.to_sql_string(context));
curr
})
.collect::<Vec<_>>()
.join(" ");
ret.push_str(&compound_selects);
}
ret
}
}
impl ToSqlString for ast::OneSelect {
fn to_sql_string<C: ToSqlContext>(&self, context: &C) -> String {
match self {
ast::OneSelect::Select(select) => select.to_sql_string(context),
ast::OneSelect::Values(values) => {
let joined_values = values
.iter()
.map(|value| {
let joined_value = value
.iter()
.map(|e| e.to_sql_string(context))
.collect::<Vec<_>>()
.join(", ");
format!("({joined_value})")
})
.collect::<Vec<_>>()
.join(", ");
format!("VALUES {joined_values}")
}
}
}
}
impl ToSqlString for ast::SelectInner {
fn to_sql_string<C: ToSqlContext>(&self, context: &C) -> String {
// dbg!(&self);
let mut ret = Vec::with_capacity(2 + self.columns.len());
ret.push("SELECT".to_string());
if let Some(distinct) = self.distinctness {
ret.push(distinct.to_string());
}
let joined_cols = self
.columns
.iter()
.map(|col| col.to_sql_string(context))
.collect::<Vec<_>>()
.join(", ");
ret.push(joined_cols);
if let Some(from) = &self.from {
ret.push(from.to_sql_string(context));
}
if let Some(where_expr) = &self.where_clause {
ret.push("WHERE".to_string());
ret.push(where_expr.to_sql_string(context));
}
if let Some(group_by) = &self.group_by {
ret.push(group_by.to_sql_string(context));
}
if let Some(window_clause) = &self.window_clause {
ret.push("WINDOW".to_string());
let joined_window = window_clause
.iter()
.map(|window_def| window_def.to_sql_string(context))
.collect::<Vec<_>>()
.join(",");
ret.push(joined_window);
}
ret.join(" ")
}
}
impl ToSqlString for ast::FromClause {
fn to_sql_string<C: ToSqlContext>(&self, context: &C) -> String {
let mut ret = String::from("FROM");
if let Some(select_table) = &self.select {
ret.push(' ');
ret.push_str(&select_table.to_sql_string(context));
}
if let Some(joins) = &self.joins {
ret.push(' ');
let joined_joins = joins
.iter()
.map(|join| {
let mut curr = join.operator.to_string();
curr.push(' ');
curr.push_str(&join.table.to_sql_string(context));
if let Some(join_constraint) = &join.constraint {
curr.push(' ');
curr.push_str(&join_constraint.to_sql_string(context));
}
curr
})
.collect::<Vec<_>>()
.join(" ");
ret.push_str(&joined_joins);
}
ret
}
}
impl ToSqlString for ast::SelectTable {
fn to_sql_string<C: ToSqlContext>(&self, context: &C) -> String {
let mut ret = String::new();
match self {
Self::Table(name, alias, indexed) => {
ret.push_str(&name.to_sql_string(context));
if let Some(alias) = alias {
ret.push(' ');
ret.push_str(&alias.to_string());
}
if let Some(indexed) = indexed {
ret.push(' ');
ret.push_str(&indexed.to_string());
}
}
Self::TableCall(table_func, args, alias) => {
ret.push_str(&table_func.to_sql_string(context));
if let Some(args) = args {
ret.push(' ');
let joined_args = args
.iter()
.map(|arg| arg.to_sql_string(context))
.collect::<Vec<_>>()
.join(", ");
ret.push_str(&joined_args);
}
if let Some(alias) = alias {
ret.push(' ');
ret.push_str(&alias.to_string());
}
}
Self::Select(select, alias) => {
ret.push('(');
ret.push_str(&select.to_sql_string(context));
ret.push(')');
if let Some(alias) = alias {
ret.push(' ');
ret.push_str(&alias.to_string());
}
}
Self::Sub(from_clause, alias) => {
ret.push('(');
ret.push_str(&from_clause.to_sql_string(context));
ret.push(')');
if let Some(alias) = alias {
ret.push(' ');
ret.push_str(&alias.to_string());
}
}
}
ret
}
}
impl ToSqlString for ast::With {
fn to_sql_string<C: ToSqlContext>(&self, context: &C) -> String {
format!(
"WITH{} {}",
if self.recursive { " RECURSIVE " } else { "" },
self.ctes
.iter()
.map(|cte| cte.to_sql_string(context))
.collect::<Vec<_>>()
.join(", ")
)
}
}
impl ToSqlString for ast::Limit {
fn to_sql_string<C: ToSqlContext>(&self, context: &C) -> String {
format!(
"LIMIT {}{}",
self.expr.to_sql_string(context),
self.offset
.as_ref()
.map_or("".to_string(), |offset| format!(
" OFFSET {}",
offset.to_sql_string(context)
))
)
// TODO: missing , + expr in ast
}
}
impl ToSqlString for ast::CommonTableExpr {
fn to_sql_string<C: ToSqlContext>(&self, context: &C) -> String {
let mut ret = Vec::with_capacity(self.columns.as_ref().map_or(2, |cols| cols.len()));
ret.push(self.tbl_name.0.clone());
if let Some(cols) = &self.columns {
let joined_cols = cols
.iter()
.map(|col| col.to_string())
.collect::<Vec<_>>()
.join(", ");
ret.push(format!("({joined_cols})"));
}
ret.push(format!(
"AS {}({})",
{
let mut materialized = self.materialized.to_string();
if !materialized.is_empty() {
materialized.push(' ');
}
materialized
},
self.select.to_sql_string(context)
));
ret.join(" ")
}
}
impl Display for ast::IndexedColumn {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.col_name.0)
}
}
impl ToSqlString for ast::SortedColumn {
fn to_sql_string<C: ToSqlContext>(&self, context: &C) -> String {
let mut curr = self.expr.to_sql_string(context);
if let Some(sort_order) = self.order {
curr.push(' ');
curr.push_str(&sort_order.to_string());
}
if let Some(nulls_order) = self.nulls {
curr.push(' ');
curr.push_str(&nulls_order.to_string());
}
curr
}
}
impl Display for ast::SortOrder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.to_fmt(f)
}
}
impl Display for ast::NullsOrder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.to_fmt(f)
}
}
impl Display for ast::Materialized {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let value = match self {
Self::Any => "",
Self::No => "NOT MATERIALIZED",
Self::Yes => "MATERIALIZED",
};
write!(f, "{value}")
}
}
impl ToSqlString for ast::ResultColumn {
fn to_sql_string<C: ToSqlContext>(&self, context: &C) -> String {
let mut ret = String::new();
match self {
Self::Expr(expr, alias) => {
ret.push_str(&expr.to_sql_string(context));
if let Some(alias) = alias {
ret.push(' ');
ret.push_str(&alias.to_string());
}
}
Self::Star => {
ret.push('*');
}
Self::TableStar(name) => {
ret.push_str(&format!("{}.*", name.0));
}
}
ret
}
}
impl Display for ast::As {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::As(alias) => {
format!("AS {}", alias.0)
}
Self::Elided(alias) => alias.0.clone(),
}
)
}
}
impl Display for ast::Indexed {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::NotIndexed => "NOT INDEXED".to_string(),
Self::IndexedBy(name) => format!("INDEXED BY {}", name.0),
}
)
}
}
impl Display for ast::JoinOperator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Comma => ",".to_string(),
Self::TypedJoin(join) => {
let join_keyword = "JOIN";
if let Some(join) = join {
format!("{join} {join_keyword}")
} else {
join_keyword.to_string()
}
}
}
)
}
}
impl Display for ast::JoinType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let value = {
let mut modifiers = Vec::new();
if self.contains(Self::NATURAL) {
modifiers.push("NATURAL");
}
if self.contains(Self::LEFT) || self.contains(Self::RIGHT) {
// TODO: I think the parser incorrectly asigns outer to every LEFT and RIGHT query
if self.contains(Self::LEFT | Self::RIGHT) {
modifiers.push("FULL");
} else if self.contains(Self::LEFT) {
modifiers.push("LEFT");
} else if self.contains(Self::RIGHT) {
modifiers.push("RIGHT");
}
// FIXME: ignore outer joins as I think they are parsed incorrectly in the bitflags
// if self.contains(Self::OUTER) {
// modifiers.push("OUTER");
// }
}
if self.contains(Self::INNER) {
modifiers.push("INNER");
}
if self.contains(Self::CROSS) {
modifiers.push("CROSS");
}
modifiers.join(" ")
};
write!(f, "{value}")
}
}
impl ToSqlString for ast::JoinConstraint {
fn to_sql_string<C: ToSqlContext>(&self, context: &C) -> String {
match self {
Self::On(expr) => {
format!("ON {}", expr.to_sql_string(context))
}
Self::Using(col_names) => {
let joined_names = col_names
.iter()
.map(|col| col.0.clone())
.collect::<Vec<_>>()
.join(",");
format!("USING ({joined_names})")
}
}
}
}
impl ToSqlString for ast::GroupBy {
fn to_sql_string<C: ToSqlContext>(&self, context: &C) -> String {
let mut ret = String::from("GROUP BY ");
let curr = self
.exprs
.iter()
.map(|expr| expr.to_sql_string(context))
.collect::<Vec<_>>()
.join(",");
ret.push_str(&curr);
if let Some(having) = &self.having {
ret.push_str(&format!(" HAVING {}", having.to_sql_string(context)));
}
ret
}
}
impl ToSqlString for ast::WindowDef {
fn to_sql_string<C: ToSqlContext>(&self, context: &C) -> String {
format!("{} AS {}", self.name.0, self.window.to_sql_string(context))
}
}
impl ToSqlString for ast::Window {
fn to_sql_string<C: ToSqlContext>(&self, context: &C) -> String {
let mut ret = Vec::new();
if let Some(name) = &self.base {
ret.push(name.0.clone());
}
if let Some(partition) = &self.partition_by {
let joined_exprs = partition
.iter()
.map(|e| e.to_sql_string(context))
.collect::<Vec<_>>()
.join(",");
ret.push(format!("PARTITION BY {joined_exprs}"));
}
if let Some(order_by) = &self.order_by {
let joined_cols = order_by
.iter()
.map(|col| col.to_sql_string(context))
.collect::<Vec<_>>()
.join(", ");
ret.push(format!("ORDER BY {joined_cols}"));
}
if let Some(frame_claue) = &self.frame_clause {
ret.push(frame_claue.to_sql_string(context));
}
format!("({})", ret.join(" "))
}
}
impl ToSqlString for ast::FrameClause {
fn to_sql_string<C: ToSqlContext>(&self, context: &C) -> String {
let mut ret = Vec::new();
ret.push(self.mode.to_string());
let start_sql = self.start.to_sql_string(context);
if let Some(end) = &self.end {
ret.push(format!(
"BETWEEN {} AND {}",
start_sql,
end.to_sql_string(context)
));
} else {
ret.push(start_sql);
}
if let Some(exclude) = &self.exclude {
ret.push(exclude.to_string());
}
ret.join(" ")
}
}
impl Display for ast::FrameMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.to_fmt(f)
}
}
impl ToSqlString for ast::FrameBound {
fn to_sql_string<C: ToSqlContext>(&self, context: &C) -> String {
match self {
Self::CurrentRow => "CURRENT ROW".to_string(),
Self::Following(expr) => format!("{} FOLLOWING", expr.to_sql_string(context)),
Self::Preceding(expr) => format!("{} PRECEDING", expr.to_sql_string(context)),
Self::UnboundedFollowing => "UNBOUNDED FOLLOWING".to_string(),
Self::UnboundedPreceding => "UNBOUNDED PRECEDING".to_string(),
}
}
}
impl Display for ast::FrameExclude {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", {
let clause = match self {
Self::CurrentRow => "CURRENT ROW",
Self::Group => "GROUP",
Self::NoOthers => "NO OTHERS",
Self::Ties => "TIES",
};
format!("EXCLUDE {clause}")
})
}
}
#[cfg(test)]
mod tests {
use crate::to_sql_string_test;
@@ -551,17 +21,11 @@ mod tests {
"SELECT a FROM t WHERE b = 1 AND c > 2"
);
to_sql_string_test!(
test_select_with_order_by,
"SELECT a FROM t ORDER BY a DESC"
);
to_sql_string_test!(test_select_with_order_by, "SELECT a FROM t ORDER BY a DESC");
to_sql_string_test!(test_select_with_limit, "SELECT a FROM t LIMIT 10");
to_sql_string_test!(
test_select_with_offset,
"SELECT a FROM t LIMIT 10 OFFSET 5"
);
to_sql_string_test!(test_select_with_offset, "SELECT a FROM t LIMIT 10 OFFSET 5");
to_sql_string_test!(
test_select_with_join,

View File

@@ -1,48 +1,3 @@
use crate::{ast, to_sql_string::ToSqlString};
impl ToSqlString for ast::Update {
fn to_sql_string<C: crate::to_sql_string::ToSqlContext>(&self, context: &C) -> String {
format!(
"{}UPDATE {}{}{} SET {}{}{}{}",
self.with.as_ref().map_or("".to_string(), |with| format!(
"{} ",
with.to_sql_string(context)
)),
self.or_conflict
.map_or("".to_string(), |conflict| format!("OR {conflict} ")),
self.tbl_name.to_sql_string(context),
self.indexed
.as_ref()
.map_or("".to_string(), |indexed| format!(" {indexed}")),
self.sets
.iter()
.map(|set| set.to_sql_string(context))
.collect::<Vec<_>>()
.join(", "),
self.from.as_ref().map_or("".to_string(), |from| format!(
" {}",
from.to_sql_string(context)
)),
self.where_clause
.as_ref()
.map_or("".to_string(), |expr| format!(
" WHERE {}",
expr.to_sql_string(context)
)),
self.returning
.as_ref()
.map_or("".to_string(), |returning| format!(
" RETURNING {}",
returning
.iter()
.map(|col| col.to_sql_string(context))
.collect::<Vec<_>>()
.join(", ")
))
)
}
}
#[cfg(test)]
mod tests {
use crate::to_sql_string_test;