diff --git a/src/ast/data_type.rs b/src/ast/data_type.rs index 551622729..cfbc2147d 100644 --- a/src/ast/data_type.rs +++ b/src/ast/data_type.rs @@ -11,6 +11,7 @@ // limitations under the License. use super::ObjectName; +use std::fmt; /// SQL data types #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -65,47 +66,53 @@ pub enum DataType { Array(Box), } -impl ToString for DataType { - fn to_string(&self) -> String { +impl fmt::Display for DataType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - DataType::Char(size) => format_type_with_optional_length("char", size), - DataType::Varchar(size) => format_type_with_optional_length("character varying", size), - DataType::Uuid => "uuid".to_string(), - DataType::Clob(size) => format!("clob({})", size), - DataType::Binary(size) => format!("binary({})", size), - DataType::Varbinary(size) => format!("varbinary({})", size), - DataType::Blob(size) => format!("blob({})", size), + DataType::Char(size) => format_type_with_optional_length(f, "char", size), + DataType::Varchar(size) => { + format_type_with_optional_length(f, "character varying", size) + } + DataType::Uuid => write!(f, "uuid"), + DataType::Clob(size) => write!(f, "clob({})", size), + DataType::Binary(size) => write!(f, "binary({})", size), + DataType::Varbinary(size) => write!(f, "varbinary({})", size), + DataType::Blob(size) => write!(f, "blob({})", size), DataType::Decimal(precision, scale) => { if let Some(scale) = scale { - format!("numeric({},{})", precision.unwrap(), scale) + write!(f, "numeric({},{})", precision.unwrap(), scale) } else { - format_type_with_optional_length("numeric", precision) + format_type_with_optional_length(f, "numeric", precision) } } - DataType::Float(size) => format_type_with_optional_length("float", size), - DataType::SmallInt => "smallint".to_string(), - DataType::Int => "int".to_string(), - DataType::BigInt => "bigint".to_string(), - DataType::Real => "real".to_string(), - DataType::Double => "double".to_string(), - DataType::Boolean => "boolean".to_string(), - DataType::Date => "date".to_string(), - DataType::Time => "time".to_string(), - DataType::Timestamp => "timestamp".to_string(), - DataType::Interval => "interval".to_string(), - DataType::Regclass => "regclass".to_string(), - DataType::Text => "text".to_string(), - DataType::Bytea => "bytea".to_string(), - DataType::Array(ty) => format!("{}[]", ty.to_string()), - DataType::Custom(ty) => ty.to_string(), + DataType::Float(size) => format_type_with_optional_length(f, "float", size), + DataType::SmallInt => write!(f, "smallint"), + DataType::Int => write!(f, "int"), + DataType::BigInt => write!(f, "bigint"), + DataType::Real => write!(f, "real"), + DataType::Double => write!(f, "double"), + DataType::Boolean => write!(f, "boolean"), + DataType::Date => write!(f, "date"), + DataType::Time => write!(f, "time"), + DataType::Timestamp => write!(f, "timestamp"), + DataType::Interval => write!(f, "interval"), + DataType::Regclass => write!(f, "regclass"), + DataType::Text => write!(f, "text"), + DataType::Bytea => write!(f, "bytea"), + DataType::Array(ty) => write!(f, "{}[]", ty), + DataType::Custom(ty) => write!(f, "{}", ty), } } } -fn format_type_with_optional_length(sql_type: &str, len: &Option) -> String { - let mut s = sql_type.to_string(); +fn format_type_with_optional_length( + f: &mut fmt::Formatter, + sql_type: &'static str, + len: &Option, +) -> fmt::Result { + write!(f, "{}", sql_type)?; if let Some(len) = len { - s += &format!("({})", len); + write!(f, "({})", len)?; } - s + Ok(()) } diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index a42f02583..f7ed6afa7 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -1,6 +1,7 @@ //! AST types specific to CREATE/ALTER variants of `SQLStatement` //! (commonly referred to as Data Definition Language, or DDL) -use super::{DataType, Expr, Ident, ObjectName}; +use super::{display_comma_separated, DataType, Expr, Ident, ObjectName}; +use std::fmt; /// An `ALTER TABLE` (`SQLStatement::SQLAlterTable`) operation #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -11,11 +12,11 @@ pub enum AlterTableOperation { DropConstraint { name: Ident }, } -impl ToString for AlterTableOperation { - fn to_string(&self) -> String { +impl fmt::Display for AlterTableOperation { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - AlterTableOperation::AddConstraint(c) => format!("ADD {}", c.to_string()), - AlterTableOperation::DropConstraint { name } => format!("DROP CONSTRAINT {}", name), + AlterTableOperation::AddConstraint(c) => write!(f, "ADD {}", c), + AlterTableOperation::DropConstraint { name } => write!(f, "DROP CONSTRAINT {}", name), } } } @@ -46,36 +47,36 @@ pub enum TableConstraint { }, } -impl ToString for TableConstraint { - fn to_string(&self) -> String { +impl fmt::Display for TableConstraint { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { TableConstraint::Unique { name, columns, is_primary, - } => format!( + } => write!( + f, "{}{} ({})", - format_constraint_name(name), + display_constraint_name(name), if *is_primary { "PRIMARY KEY" } else { "UNIQUE" }, - columns.join(", ") + display_comma_separated(columns) ), TableConstraint::ForeignKey { name, columns, foreign_table, referred_columns, - } => format!( + } => write!( + f, "{}FOREIGN KEY ({}) REFERENCES {}({})", - format_constraint_name(name), - columns.join(", "), - foreign_table.to_string(), - referred_columns.join(", ") - ), - TableConstraint::Check { name, expr } => format!( - "{}CHECK ({})", - format_constraint_name(name), - expr.to_string() + display_constraint_name(name), + display_comma_separated(columns), + foreign_table, + display_comma_separated(referred_columns) ), + TableConstraint::Check { name, expr } => { + write!(f, "{}CHECK ({})", display_constraint_name(name), expr) + } } } } @@ -89,18 +90,13 @@ pub struct ColumnDef { pub options: Vec, } -impl ToString for ColumnDef { - fn to_string(&self) -> String { - format!( - "{} {}{}", - self.name, - self.data_type.to_string(), - self.options - .iter() - .map(|c| format!(" {}", c.to_string())) - .collect::>() - .join("") - ) +impl fmt::Display for ColumnDef { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} {}", self.name, self.data_type)?; + for option in &self.options { + write!(f, " {}", option)?; + } + Ok(()) } } @@ -126,13 +122,9 @@ pub struct ColumnOptionDef { pub option: ColumnOption, } -impl ToString for ColumnOptionDef { - fn to_string(&self) -> String { - format!( - "{}{}", - format_constraint_name(&self.name), - self.option.to_string() - ) +impl fmt::Display for ColumnOptionDef { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}{}", display_constraint_name(&self.name), self.option) } } @@ -160,35 +152,39 @@ pub enum ColumnOption { Check(Expr), } -impl ToString for ColumnOption { - fn to_string(&self) -> String { +impl fmt::Display for ColumnOption { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use ColumnOption::*; match self { - Null => "NULL".to_string(), - NotNull => "NOT NULL".to_string(), - Default(expr) => format!("DEFAULT {}", expr.to_string()), + Null => write!(f, "NULL"), + NotNull => write!(f, "NOT NULL"), + Default(expr) => write!(f, "DEFAULT {}", expr), Unique { is_primary } => { - if *is_primary { - "PRIMARY KEY".to_string() - } else { - "UNIQUE".to_string() - } + write!(f, "{}", if *is_primary { "PRIMARY KEY" } else { "UNIQUE" }) } ForeignKey { foreign_table, referred_columns, - } => format!( + } => write!( + f, "REFERENCES {} ({})", - foreign_table.to_string(), - referred_columns.join(", ") + foreign_table, + display_comma_separated(referred_columns) ), - Check(expr) => format!("CHECK ({})", expr.to_string(),), + Check(expr) => write!(f, "CHECK ({})", expr), } } } -fn format_constraint_name(name: &Option) -> String { - name.as_ref() - .map(|name| format!("CONSTRAINT {} ", name)) - .unwrap_or_default() +fn display_constraint_name<'a>(name: &'a Option) -> impl fmt::Display + 'a { + struct ConstraintName<'a>(&'a Option); + impl<'a> fmt::Display for ConstraintName<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Some(name) = self.0 { + write!(f, "CONSTRAINT {} ", name)?; + } + Ok(()) + } + } + ConstraintName(name) } diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 66dfeaa07..e6587de39 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -18,7 +18,7 @@ mod operator; mod query; mod value; -use std::ops::Deref; +use std::fmt; pub use self::data_type::DataType; pub use self::ddl::{ @@ -31,17 +31,41 @@ pub use self::query::{ }; pub use self::value::{DateTimeField, Value}; -/// Like `vec.join(", ")`, but for any types implementing ToString. -fn comma_separated_string(iter: I) -> String +struct DisplaySeparated<'a, T> where - I: IntoIterator, - I::Item: Deref, - ::Target: ToString, + T: fmt::Display, { - iter.into_iter() - .map(|t| t.deref().to_string()) - .collect::>() - .join(", ") + slice: &'a [T], + sep: &'static str, +} + +impl<'a, T> fmt::Display for DisplaySeparated<'a, T> +where + T: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut delim = ""; + for t in self.slice { + write!(f, "{}", delim)?; + delim = self.sep; + write!(f, "{}", t)?; + } + Ok(()) + } +} + +fn display_separated<'a, T>(slice: &'a [T], sep: &'static str) -> DisplaySeparated<'a, T> +where + T: fmt::Display, +{ + DisplaySeparated { slice, sep } +} + +fn display_comma_separated(slice: &[T]) -> DisplaySeparated<'_, T> +where + T: fmt::Display, +{ + DisplaySeparated { slice, sep: ", " } } /// Identifier name, in the originally quoted form (e.g. `"id"`) @@ -138,95 +162,82 @@ pub enum Expr { Subquery(Box), } -impl ToString for Expr { - fn to_string(&self) -> String { +impl fmt::Display for Expr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Expr::Identifier(s) => s.to_string(), - Expr::Wildcard => "*".to_string(), - Expr::QualifiedWildcard(q) => q.join(".") + ".*", - Expr::CompoundIdentifier(s) => s.join("."), - Expr::IsNull(ast) => format!("{} IS NULL", ast.as_ref().to_string()), - Expr::IsNotNull(ast) => format!("{} IS NOT NULL", ast.as_ref().to_string()), + Expr::Identifier(s) => write!(f, "{}", s), + Expr::Wildcard => f.write_str("*"), + Expr::QualifiedWildcard(q) => { + write!(f, "{}", display_separated(q, "."))?; + f.write_str(".*") + } + Expr::CompoundIdentifier(s) => write!(f, "{}", display_separated(s, ".")), + Expr::IsNull(ast) => write!(f, "{} IS NULL", ast), + Expr::IsNotNull(ast) => write!(f, "{} IS NOT NULL", ast), Expr::InList { expr, list, negated, - } => format!( + } => write!( + f, "{} {}IN ({})", - expr.as_ref().to_string(), + expr, if *negated { "NOT " } else { "" }, - comma_separated_string(list) + display_comma_separated(list) ), Expr::InSubquery { expr, subquery, negated, - } => format!( + } => write!( + f, "{} {}IN ({})", - expr.as_ref().to_string(), + expr, if *negated { "NOT " } else { "" }, - subquery.to_string() + subquery ), Expr::Between { expr, negated, low, high, - } => format!( + } => write!( + f, "{} {}BETWEEN {} AND {}", - expr.to_string(), + expr, if *negated { "NOT " } else { "" }, - low.to_string(), - high.to_string() - ), - Expr::BinaryOp { left, op, right } => format!( - "{} {} {}", - left.as_ref().to_string(), - op.to_string(), - right.as_ref().to_string() - ), - Expr::UnaryOp { op, expr } => { - format!("{} {}", op.to_string(), expr.as_ref().to_string()) - } - Expr::Cast { expr, data_type } => format!( - "CAST({} AS {})", - expr.as_ref().to_string(), - data_type.to_string() - ), - Expr::Extract { field, expr } => { - format!("EXTRACT({} FROM {})", field.to_string(), expr.to_string()) - } - Expr::Collate { expr, collation } => format!( - "{} COLLATE {}", - expr.as_ref().to_string(), - collation.to_string() + low, + high ), - Expr::Nested(ast) => format!("({})", ast.as_ref().to_string()), - Expr::Value(v) => v.to_string(), - Expr::Function(f) => f.to_string(), + Expr::BinaryOp { left, op, right } => write!(f, "{} {} {}", left, op, right), + Expr::UnaryOp { op, expr } => write!(f, "{} {}", op, expr), + Expr::Cast { expr, data_type } => write!(f, "CAST({} AS {})", expr, data_type), + Expr::Extract { field, expr } => write!(f, "EXTRACT({} FROM {})", field, expr), + Expr::Collate { expr, collation } => write!(f, "{} COLLATE {}", expr, collation), + Expr::Nested(ast) => write!(f, "({})", ast), + Expr::Value(v) => write!(f, "{}", v), + Expr::Function(fun) => write!(f, "{}", fun), Expr::Case { operand, conditions, results, else_result, } => { - let mut s = "CASE".to_string(); + f.write_str("CASE")?; if let Some(operand) = operand { - s += &format!(" {}", operand.to_string()); + write!(f, " {}", operand)?; } - s += &conditions - .iter() - .zip(results) - .map(|(c, r)| format!(" WHEN {} THEN {}", c.to_string(), r.to_string())) - .collect::>() - .join(""); + for (c, r) in conditions.iter().zip(results) { + write!(f, " WHEN {} THEN {}", c, r)?; + } + if let Some(else_result) = else_result { - s += &format!(" ELSE {}", else_result.to_string()) + write!(f, " ELSE {}", else_result)?; } - s + " END" + f.write_str(" END") } - Expr::Exists(s) => format!("EXISTS ({})", s.to_string()), - Expr::Subquery(s) => format!("({})", s.to_string()), + Expr::Exists(s) => write!(f, "EXISTS ({})", s), + Expr::Subquery(s) => write!(f, "({})", s), } } } @@ -239,38 +250,36 @@ pub struct WindowSpec { pub window_frame: Option, } -impl ToString for WindowSpec { - fn to_string(&self) -> String { - let mut clauses = vec![]; +impl fmt::Display for WindowSpec { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut delim = ""; if !self.partition_by.is_empty() { - clauses.push(format!( + delim = " "; + write!( + f, "PARTITION BY {}", - comma_separated_string(&self.partition_by) - )) - }; + display_comma_separated(&self.partition_by) + )?; + } if !self.order_by.is_empty() { - clauses.push(format!( - "ORDER BY {}", - comma_separated_string(&self.order_by) - )) - }; + f.write_str(delim)?; + delim = " "; + write!(f, "ORDER BY {}", display_comma_separated(&self.order_by))?; + } if let Some(window_frame) = &self.window_frame { if let Some(end_bound) = &window_frame.end_bound { - clauses.push(format!( + f.write_str(delim)?; + write!( + f, "{} BETWEEN {} AND {}", - window_frame.units.to_string(), - window_frame.start_bound.to_string(), - end_bound.to_string() - )); + window_frame.units, window_frame.start_bound, end_bound + )?; } else { - clauses.push(format!( - "{} {}", - window_frame.units.to_string(), - window_frame.start_bound.to_string() - )); + f.write_str(delim)?; + write!(f, "{} {}", window_frame.units, window_frame.start_bound)?; } } - clauses.join(" ") + Ok(()) } } @@ -292,13 +301,13 @@ pub enum WindowFrameUnits { Groups, } -impl ToString for WindowFrameUnits { - fn to_string(&self) -> String { - match self { - WindowFrameUnits::Rows => "ROWS".to_string(), - WindowFrameUnits::Range => "RANGE".to_string(), - WindowFrameUnits::Groups => "GROUPS".to_string(), - } +impl fmt::Display for WindowFrameUnits { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(match self { + WindowFrameUnits::Rows => "ROWS", + WindowFrameUnits::Range => "RANGE", + WindowFrameUnits::Groups => "GROUPS", + }) } } @@ -329,14 +338,14 @@ pub enum WindowFrameBound { Following(Option), } -impl ToString for WindowFrameBound { - fn to_string(&self) -> String { +impl fmt::Display for WindowFrameBound { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - WindowFrameBound::CurrentRow => "CURRENT ROW".to_string(), - WindowFrameBound::Preceding(None) => "UNBOUNDED PRECEDING".to_string(), - WindowFrameBound::Following(None) => "UNBOUNDED FOLLOWING".to_string(), - WindowFrameBound::Preceding(Some(n)) => format!("{} PRECEDING", n), - WindowFrameBound::Following(Some(n)) => format!("{} FOLLOWING", n), + WindowFrameBound::CurrentRow => f.write_str("CURRENT ROW"), + WindowFrameBound::Preceding(None) => f.write_str("UNBOUNDED PRECEDING"), + WindowFrameBound::Following(None) => f.write_str("UNBOUNDED FOLLOWING"), + WindowFrameBound::Preceding(Some(n)) => write!(f, "{} PRECEDING", n), + WindowFrameBound::Following(Some(n)) => write!(f, "{} FOLLOWING", n), } } } @@ -424,69 +433,70 @@ pub enum Statement { Rollback { chain: bool }, } -impl ToString for Statement { - fn to_string(&self) -> String { +impl fmt::Display for Statement { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Statement::Query(s) => s.to_string(), + Statement::Query(s) => write!(f, "{}", s), Statement::Insert { table_name, columns, source, } => { - let mut s = format!("INSERT INTO {} ", table_name.to_string()); + write!(f, "INSERT INTO {} ", table_name)?; if !columns.is_empty() { - s += &format!("({}) ", columns.join(", ")); + write!(f, "({}) ", display_comma_separated(columns))?; } - s += &source.to_string(); - s + write!(f, "{}", source) } Statement::Copy { table_name, columns, values, } => { - let mut s = format!("COPY {}", table_name.to_string()); + write!(f, "COPY {}", table_name)?; if !columns.is_empty() { - s += &format!(" ({})", comma_separated_string(columns)); + write!(f, " ({})", display_comma_separated(columns))?; } - s += " FROM stdin; "; + write!(f, " FROM stdin; ")?; if !values.is_empty() { - s += &format!( - "\n{}", - values - .iter() - .map(|v| v.clone().unwrap_or_else(|| "\\N".to_string())) - .collect::>() - .join("\t") - ); + writeln!(f)?; + let mut delim = ""; + for v in values { + write!(f, "{}", delim)?; + delim = "\t"; + if let Some(v) = v { + write!(f, "{}", v)?; + } else { + write!(f, "\\N")?; + } + } } - s += "\n\\."; - s + write!(f, "\n\\.") } Statement::Update { table_name, assignments, selection, } => { - let mut s = format!("UPDATE {}", table_name.to_string()); + write!(f, "UPDATE {}", table_name)?; if !assignments.is_empty() { - s += " SET "; - s += &comma_separated_string(assignments); + write!(f, " SET ")?; + write!(f, "{}", display_comma_separated(assignments))?; } if let Some(selection) = selection { - s += &format!(" WHERE {}", selection.to_string()); + write!(f, " WHERE {}", selection)?; } - s + Ok(()) } Statement::Delete { table_name, selection, } => { - let mut s = format!("DELETE FROM {}", table_name.to_string()); + write!(f, "DELETE FROM {}", table_name)?; if let Some(selection) = selection { - s += &format!(" WHERE {}", selection.to_string()); + write!(f, " WHERE {}", selection)?; } - s + Ok(()) } Statement::CreateView { name, @@ -495,25 +505,22 @@ impl ToString for Statement { materialized, with_options, } => { - let modifier = if *materialized { " MATERIALIZED" } else { "" }; - let with_options = if !with_options.is_empty() { - format!(" WITH ({})", comma_separated_string(with_options)) - } else { - "".into() - }; - let columns = if !columns.is_empty() { - format!(" ({})", comma_separated_string(columns)) - } else { - "".into() - }; - format!( - "CREATE{} VIEW {}{}{} AS {}", - modifier, - name.to_string(), - with_options, - columns, - query.to_string(), - ) + write!(f, "CREATE")?; + if *materialized { + write!(f, " MATERIALIZED")?; + } + + write!(f, " VIEW {}", name)?; + + if !with_options.is_empty() { + write!(f, " WITH ({})", display_comma_separated(with_options))?; + } + + if !columns.is_empty() { + write!(f, " ({})", display_comma_separated(columns))?; + } + + write!(f, " AS {}", query) } Statement::CreateTable { name, @@ -524,64 +531,66 @@ impl ToString for Statement { file_format, location, } => { - let mut s = format!( + write!( + f, "CREATE {}TABLE {} ({}", if *external { "EXTERNAL " } else { "" }, - name.to_string(), - comma_separated_string(columns) - ); + name, + display_comma_separated(columns) + )?; if !constraints.is_empty() { - s += &format!(", {}", comma_separated_string(constraints)); + write!(f, ", {}", display_comma_separated(constraints))?; } - s += ")"; + write!(f, ")")?; + if *external { - s += &format!( + write!( + f, " STORED AS {} LOCATION '{}'", - file_format.as_ref().unwrap().to_string(), + file_format.as_ref().unwrap(), location.as_ref().unwrap() - ); + )?; } if !with_options.is_empty() { - s += &format!(" WITH ({})", comma_separated_string(with_options)); + write!(f, " WITH ({})", display_comma_separated(with_options))?; } - s + Ok(()) } Statement::AlterTable { name, operation } => { - format!("ALTER TABLE {} {}", name.to_string(), operation.to_string()) + write!(f, "ALTER TABLE {} {}", name, operation) } Statement::Drop { object_type, if_exists, names, cascade, - } => format!( + } => write!( + f, "DROP {}{} {}{}", - object_type.to_string(), + object_type, if *if_exists { " IF EXISTS" } else { "" }, - comma_separated_string(names), + display_comma_separated(names), if *cascade { " CASCADE" } else { "" }, ), - Statement::StartTransaction { modes } => format!( - "START TRANSACTION{}", - if modes.is_empty() { - "".into() - } else { - format!(" {}", comma_separated_string(modes)) + Statement::StartTransaction { modes } => { + write!(f, "START TRANSACTION")?; + if !modes.is_empty() { + write!(f, " {}", display_comma_separated(modes))?; } - ), - Statement::SetTransaction { modes } => format!( - "SET TRANSACTION{}", - if modes.is_empty() { - "".into() - } else { - format!(" {}", comma_separated_string(modes)) + Ok(()) + } + Statement::SetTransaction { modes } => { + write!(f, "SET TRANSACTION")?; + if !modes.is_empty() { + write!(f, " {}", display_comma_separated(modes))?; } - ), + Ok(()) + } Statement::Commit { chain } => { - format!("COMMIT{}", if *chain { " AND CHAIN" } else { "" },) + write!(f, "COMMIT{}", if *chain { " AND CHAIN" } else { "" },) } Statement::Rollback { chain } => { - format!("ROLLBACK{}", if *chain { " AND CHAIN" } else { "" },) + write!(f, "ROLLBACK{}", if *chain { " AND CHAIN" } else { "" },) } } } @@ -591,9 +600,9 @@ impl ToString for Statement { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ObjectName(pub Vec); -impl ToString for ObjectName { - fn to_string(&self) -> String { - self.0.join(".") +impl fmt::Display for ObjectName { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", display_separated(&self.0, ".")) } } @@ -604,9 +613,9 @@ pub struct Assignment { pub value: Expr, } -impl ToString for Assignment { - fn to_string(&self) -> String { - format!("{} = {}", self.id, self.value.to_string()) +impl fmt::Display for Assignment { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} = {}", self.id, self.value) } } @@ -620,18 +629,19 @@ pub struct Function { pub distinct: bool, } -impl ToString for Function { - fn to_string(&self) -> String { - let mut s = format!( +impl fmt::Display for Function { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, "{}({}{})", - self.name.to_string(), + self.name, if self.distinct { "DISTINCT " } else { "" }, - comma_separated_string(&self.args), - ); + display_comma_separated(&self.args), + )?; if let Some(o) = &self.over { - s += &format!(" OVER ({})", o.to_string()) + write!(f, " OVER ({})", o)?; } - s + Ok(()) } } @@ -647,18 +657,22 @@ pub enum FileFormat { JSONFILE, } -impl ToString for FileFormat { - fn to_string(&self) -> String { +impl fmt::Display for FileFormat { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use self::FileFormat::*; - match self { - TEXTFILE => "TEXTFILE".to_string(), - SEQUENCEFILE => "SEQUENCEFILE".to_string(), - ORC => "ORC".to_string(), - PARQUET => "PARQUET".to_string(), - AVRO => "AVRO".to_string(), - RCFILE => "RCFILE".to_string(), - JSONFILE => "TEXTFILE".to_string(), - } + write!( + f, + "{}", + match self { + TEXTFILE => "TEXTFILE", + SEQUENCEFILE => "SEQUENCEFILE", + ORC => "ORC", + PARQUET => "PARQUET", + AVRO => "AVRO", + RCFILE => "RCFILE", + JSONFILE => "TEXTFILE", + } + ) } } @@ -691,12 +705,16 @@ pub enum ObjectType { View, } -impl ObjectType { - fn to_string(&self) -> String { - match self { - ObjectType::Table => "TABLE".into(), - ObjectType::View => "VIEW".into(), - } +impl fmt::Display for ObjectType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{}", + match self { + ObjectType::Table => "TABLE", + ObjectType::View => "VIEW", + } + ) } } @@ -706,9 +724,9 @@ pub struct SqlOption { pub value: Value, } -impl ToString for SqlOption { - fn to_string(&self) -> String { - format!("{} = {}", self.name.to_string(), self.value.to_string()) +impl fmt::Display for SqlOption { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} = {}", self.name, self.value) } } @@ -718,12 +736,12 @@ pub enum TransactionMode { IsolationLevel(TransactionIsolationLevel), } -impl ToString for TransactionMode { - fn to_string(&self) -> String { +impl fmt::Display for TransactionMode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use TransactionMode::*; match self { - AccessMode(access_mode) => access_mode.to_string(), - IsolationLevel(iso_level) => format!("ISOLATION LEVEL {}", iso_level.to_string()), + AccessMode(access_mode) => write!(f, "{}", access_mode.to_string()), + IsolationLevel(iso_level) => write!(f, "ISOLATION LEVEL {}", iso_level), } } } @@ -734,13 +752,17 @@ pub enum TransactionAccessMode { ReadWrite, } -impl ToString for TransactionAccessMode { - fn to_string(&self) -> String { +impl fmt::Display for TransactionAccessMode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use TransactionAccessMode::*; - match self { - ReadOnly => "READ ONLY".into(), - ReadWrite => "READ WRITE".into(), - } + write!( + f, + "{}", + match self { + ReadOnly => "READ ONLY", + ReadWrite => "READ WRITE", + } + ) } } @@ -752,14 +774,18 @@ pub enum TransactionIsolationLevel { Serializable, } -impl ToString for TransactionIsolationLevel { - fn to_string(&self) -> String { +impl fmt::Display for TransactionIsolationLevel { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use TransactionIsolationLevel::*; - match self { - ReadUncommitted => "READ UNCOMMITTED".into(), - ReadCommitted => "READ COMMITTED".into(), - RepeatableRead => "REPEATABLE READ".into(), - Serializable => "SERIALIZABLE".into(), - } + write!( + f, + "{}", + match self { + ReadUncommitted => "READ UNCOMMITTED", + ReadCommitted => "READ COMMITTED", + RepeatableRead => "REPEATABLE READ", + Serializable => "SERIALIZABLE", + } + ) } } diff --git a/src/ast/operator.rs b/src/ast/operator.rs index 2d311c674..747cfd9ef 100644 --- a/src/ast/operator.rs +++ b/src/ast/operator.rs @@ -10,6 +10,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::fmt; + /// Unary operators #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum UnaryOperator { @@ -18,13 +20,17 @@ pub enum UnaryOperator { Not, } -impl ToString for UnaryOperator { - fn to_string(&self) -> String { - match self { - UnaryOperator::Plus => "+".to_string(), - UnaryOperator::Minus => "-".to_string(), - UnaryOperator::Not => "NOT".to_string(), - } +impl fmt::Display for UnaryOperator { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{}", + match self { + UnaryOperator::Plus => "+", + UnaryOperator::Minus => "-", + UnaryOperator::Not => "NOT", + } + ) } } @@ -48,24 +54,28 @@ pub enum BinaryOperator { NotLike, } -impl ToString for BinaryOperator { - fn to_string(&self) -> String { - match self { - BinaryOperator::Plus => "+".to_string(), - BinaryOperator::Minus => "-".to_string(), - BinaryOperator::Multiply => "*".to_string(), - BinaryOperator::Divide => "/".to_string(), - BinaryOperator::Modulus => "%".to_string(), - BinaryOperator::Gt => ">".to_string(), - BinaryOperator::Lt => "<".to_string(), - BinaryOperator::GtEq => ">=".to_string(), - BinaryOperator::LtEq => "<=".to_string(), - BinaryOperator::Eq => "=".to_string(), - BinaryOperator::NotEq => "<>".to_string(), - BinaryOperator::And => "AND".to_string(), - BinaryOperator::Or => "OR".to_string(), - BinaryOperator::Like => "LIKE".to_string(), - BinaryOperator::NotLike => "NOT LIKE".to_string(), - } +impl fmt::Display for BinaryOperator { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{}", + match self { + BinaryOperator::Plus => "+", + BinaryOperator::Minus => "-", + BinaryOperator::Multiply => "*", + BinaryOperator::Divide => "/", + BinaryOperator::Modulus => "%", + BinaryOperator::Gt => ">", + BinaryOperator::Lt => "<", + BinaryOperator::GtEq => ">=", + BinaryOperator::LtEq => "<=", + BinaryOperator::Eq => "=", + BinaryOperator::NotEq => "<>", + BinaryOperator::And => "AND", + BinaryOperator::Or => "OR", + BinaryOperator::Like => "LIKE", + BinaryOperator::NotLike => "NOT LIKE", + } + ) } } diff --git a/src/ast/query.rs b/src/ast/query.rs index c95226c7b..e1f2637d7 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -30,27 +30,25 @@ pub struct Query { pub fetch: Option, } -impl ToString for Query { - fn to_string(&self) -> String { - let mut s = String::new(); +impl fmt::Display for Query { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if !self.ctes.is_empty() { - s += &format!("WITH {} ", comma_separated_string(&self.ctes)) + write!(f, "WITH {} ", display_comma_separated(&self.ctes))?; } - s += &self.body.to_string(); + write!(f, "{}", self.body)?; if !self.order_by.is_empty() { - s += &format!(" ORDER BY {}", comma_separated_string(&self.order_by)); + write!(f, " ORDER BY {}", display_comma_separated(&self.order_by))?; } if let Some(ref limit) = self.limit { - s += &format!(" LIMIT {}", limit.to_string()); + write!(f, " LIMIT {}", limit)?; } if let Some(ref offset) = self.offset { - s += &format!(" OFFSET {} ROWS", offset.to_string()); + write!(f, " OFFSET {} ROWS", offset)?; } if let Some(ref fetch) = self.fetch { - s.push(' '); - s += &fetch.to_string(); + write!(f, " {}", fetch)?; } - s + Ok(()) } } @@ -74,12 +72,12 @@ pub enum SetExpr { // TODO: ANSI SQL supports `TABLE` here. } -impl ToString for SetExpr { - fn to_string(&self) -> String { +impl fmt::Display for SetExpr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - SetExpr::Select(s) => s.to_string(), - SetExpr::Query(q) => format!("({})", q.to_string()), - SetExpr::Values(v) => v.to_string(), + SetExpr::Select(s) => write!(f, "{}", s), + SetExpr::Query(q) => write!(f, "({})", q), + SetExpr::Values(v) => write!(f, "{}", v), SetExpr::SetOperation { left, right, @@ -87,13 +85,7 @@ impl ToString for SetExpr { all, } => { let all_str = if *all { " ALL" } else { "" }; - format!( - "{} {}{} {}", - left.to_string(), - op.to_string(), - all_str, - right.to_string() - ) + write!(f, "{} {}{} {}", left, op, all_str, right) } } } @@ -106,13 +98,17 @@ pub enum SetOperator { Intersect, } -impl ToString for SetOperator { - fn to_string(&self) -> String { - match self { - SetOperator::Union => "UNION".to_string(), - SetOperator::Except => "EXCEPT".to_string(), - SetOperator::Intersect => "INTERSECT".to_string(), - } +impl fmt::Display for SetOperator { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{}", + match self { + SetOperator::Union => "UNION", + SetOperator::Except => "EXCEPT", + SetOperator::Intersect => "INTERSECT", + } + ) } } @@ -134,26 +130,27 @@ pub struct Select { pub having: Option, } -impl ToString for Select { - fn to_string(&self) -> String { - let mut s = format!( +impl fmt::Display for Select { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, "SELECT{} {}", if self.distinct { " DISTINCT" } else { "" }, - comma_separated_string(&self.projection) - ); + display_comma_separated(&self.projection) + )?; if !self.from.is_empty() { - s += &format!(" FROM {}", comma_separated_string(&self.from)); + write!(f, " FROM {}", display_comma_separated(&self.from))?; } if let Some(ref selection) = self.selection { - s += &format!(" WHERE {}", selection.to_string()); + write!(f, " WHERE {}", selection)?; } if !self.group_by.is_empty() { - s += &format!(" GROUP BY {}", comma_separated_string(&self.group_by)); + write!(f, " GROUP BY {}", display_comma_separated(&self.group_by))?; } if let Some(ref having) = self.having { - s += &format!(" HAVING {}", having.to_string()); + write!(f, " HAVING {}", having)?; } - s + Ok(()) } } @@ -167,9 +164,9 @@ pub struct Cte { pub query: Query, } -impl ToString for Cte { - fn to_string(&self) -> String { - format!("{} AS ({})", self.alias.to_string(), self.query.to_string()) +impl fmt::Display for Cte { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} AS ({})", self.alias, self.query) } } @@ -186,15 +183,13 @@ pub enum SelectItem { Wildcard, } -impl ToString for SelectItem { - fn to_string(&self) -> String { +impl fmt::Display for SelectItem { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match &self { - SelectItem::UnnamedExpr(expr) => expr.to_string(), - SelectItem::ExprWithAlias { expr, alias } => { - format!("{} AS {}", expr.to_string(), alias) - } - SelectItem::QualifiedWildcard(prefix) => format!("{}.*", prefix.to_string()), - SelectItem::Wildcard => "*".to_string(), + SelectItem::UnnamedExpr(expr) => write!(f, "{}", expr), + SelectItem::ExprWithAlias { expr, alias } => write!(f, "{} AS {}", expr, alias), + SelectItem::QualifiedWildcard(prefix) => write!(f, "{}.*", prefix), + SelectItem::Wildcard => write!(f, "*"), } } } @@ -205,13 +200,13 @@ pub struct TableWithJoins { pub joins: Vec, } -impl ToString for TableWithJoins { - fn to_string(&self) -> String { - let mut s = self.relation.to_string(); +impl fmt::Display for TableWithJoins { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.relation)?; for join in &self.joins { - s += &join.to_string(); + write!(f, "{}", join)?; } - s + Ok(()) } } @@ -240,8 +235,8 @@ pub enum TableFactor { NestedJoin(Box), } -impl ToString for TableFactor { - fn to_string(&self) -> String { +impl fmt::Display for TableFactor { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { TableFactor::Table { name, @@ -249,36 +244,33 @@ impl ToString for TableFactor { args, with_hints, } => { - let mut s = name.to_string(); + write!(f, "{}", name)?; if !args.is_empty() { - s += &format!("({})", comma_separated_string(args)) - }; + write!(f, "({})", display_comma_separated(args))?; + } if let Some(alias) = alias { - s += &format!(" AS {}", alias.to_string()); + write!(f, " AS {}", alias)?; } if !with_hints.is_empty() { - s += &format!(" WITH ({})", comma_separated_string(with_hints)); + write!(f, " WITH ({})", display_comma_separated(with_hints))?; } - s + Ok(()) } TableFactor::Derived { lateral, subquery, alias, } => { - let mut s = String::new(); if *lateral { - s += "LATERAL "; + write!(f, "LATERAL ")?; } - s += &format!("({})", subquery.to_string()); + write!(f, "({})", subquery)?; if let Some(alias) = alias { - s += &format!(" AS {}", alias.to_string()); + write!(f, " AS {}", alias)?; } - s - } - TableFactor::NestedJoin(table_reference) => { - format!("({})", table_reference.to_string()) + Ok(()) } + TableFactor::NestedJoin(table_reference) => write!(f, "({})", table_reference), } } } @@ -289,13 +281,13 @@ pub struct TableAlias { pub columns: Vec, } -impl ToString for TableAlias { - fn to_string(&self) -> String { - let mut s = self.name.clone(); +impl fmt::Display for TableAlias { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.name)?; if !self.columns.is_empty() { - s += &format!(" ({})", comma_separated_string(&self.columns)); + write!(f, " ({})", display_comma_separated(&self.columns))?; } - s + Ok(()) } } @@ -305,49 +297,61 @@ pub struct Join { pub join_operator: JoinOperator, } -impl ToString for Join { - fn to_string(&self) -> String { - fn prefix(constraint: &JoinConstraint) -> String { +impl fmt::Display for Join { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn prefix(constraint: &JoinConstraint) -> &'static str { match constraint { - JoinConstraint::Natural => "NATURAL ".to_string(), - _ => "".to_string(), + JoinConstraint::Natural => "NATURAL ", + _ => "", } } - fn suffix(constraint: &JoinConstraint) -> String { - match constraint { - JoinConstraint::On(expr) => format!(" ON {}", expr.to_string()), - JoinConstraint::Using(attrs) => format!(" USING({})", attrs.join(", ")), - _ => "".to_string(), + fn suffix<'a>(constraint: &'a JoinConstraint) -> impl fmt::Display + 'a { + struct Suffix<'a>(&'a JoinConstraint); + impl<'a> fmt::Display for Suffix<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.0 { + JoinConstraint::On(expr) => write!(f, " ON {}", expr), + JoinConstraint::Using(attrs) => { + write!(f, " USING({})", display_comma_separated(attrs)) + } + _ => Ok(()), + } + } } + Suffix(constraint) } match &self.join_operator { - JoinOperator::Inner(constraint) => format!( + JoinOperator::Inner(constraint) => write!( + f, " {}JOIN {}{}", prefix(constraint), - self.relation.to_string(), + self.relation, suffix(constraint) ), - JoinOperator::LeftOuter(constraint) => format!( + JoinOperator::LeftOuter(constraint) => write!( + f, " {}LEFT JOIN {}{}", prefix(constraint), - self.relation.to_string(), + self.relation, suffix(constraint) ), - JoinOperator::RightOuter(constraint) => format!( + JoinOperator::RightOuter(constraint) => write!( + f, " {}RIGHT JOIN {}{}", prefix(constraint), - self.relation.to_string(), + self.relation, suffix(constraint) ), - JoinOperator::FullOuter(constraint) => format!( + JoinOperator::FullOuter(constraint) => write!( + f, " {}FULL JOIN {}{}", prefix(constraint), - self.relation.to_string(), + self.relation, suffix(constraint) ), - JoinOperator::CrossJoin => format!(" CROSS JOIN {}", self.relation.to_string()), - JoinOperator::CrossApply => format!(" CROSS APPLY {}", self.relation.to_string()), - JoinOperator::OuterApply => format!(" OUTER APPLY {}", self.relation.to_string()), + JoinOperator::CrossJoin => write!(f, " CROSS JOIN {}", self.relation), + JoinOperator::CrossApply => write!(f, " CROSS APPLY {}", self.relation), + JoinOperator::OuterApply => write!(f, " OUTER APPLY {}", self.relation), } } } @@ -379,12 +383,12 @@ pub struct OrderByExpr { pub asc: Option, } -impl ToString for OrderByExpr { - fn to_string(&self) -> String { +impl fmt::Display for OrderByExpr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.asc { - Some(true) => format!("{} ASC", self.expr.to_string()), - Some(false) => format!("{} DESC", self.expr.to_string()), - None => self.expr.to_string(), + Some(true) => write!(f, "{} ASC", self.expr), + Some(false) => write!(f, "{} DESC", self.expr), + None => write!(f, "{}", self.expr), } } } @@ -396,19 +400,14 @@ pub struct Fetch { pub quantity: Option, } -impl ToString for Fetch { - fn to_string(&self) -> String { +impl fmt::Display for Fetch { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let extension = if self.with_ties { "WITH TIES" } else { "ONLY" }; if let Some(ref quantity) = self.quantity { let percent = if self.percent { " PERCENT" } else { "" }; - format!( - "FETCH FIRST {}{} ROWS {}", - quantity.to_string(), - percent, - extension - ) + write!(f, "FETCH FIRST {}{} ROWS {}", quantity, percent, extension) } else { - format!("FETCH FIRST ROWS {}", extension) + write!(f, "FETCH FIRST ROWS {}", extension) } } } @@ -416,12 +415,15 @@ impl ToString for Fetch { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Values(pub Vec>); -impl ToString for Values { - fn to_string(&self) -> String { - let rows = self - .0 - .iter() - .map(|row| format!("({})", comma_separated_string(row))); - format!("VALUES {}", comma_separated_string(rows)) +impl fmt::Display for Values { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "VALUES ")?; + let mut delim = ""; + for row in &self.0 { + write!(f, "{}", delim)?; + delim = ", "; + write!(f, "({})", display_comma_separated(row))?; + } + Ok(()) } } diff --git a/src/ast/value.rs b/src/ast/value.rs index ff5a4df4c..f0e418a25 100644 --- a/src/ast/value.rs +++ b/src/ast/value.rs @@ -11,6 +11,7 @@ // limitations under the License. use ordered_float::OrderedFloat; +use std::fmt; /// Primitive SQL values such as number and string #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -56,18 +57,18 @@ pub enum Value { Null, } -impl ToString for Value { - fn to_string(&self) -> String { +impl fmt::Display for Value { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Value::Long(v) => v.to_string(), - Value::Double(v) => v.to_string(), - Value::SingleQuotedString(v) => format!("'{}'", escape_single_quote_string(v)), - Value::NationalStringLiteral(v) => format!("N'{}'", v), - Value::HexStringLiteral(v) => format!("X'{}'", v), - Value::Boolean(v) => v.to_string(), - Value::Date(v) => format!("DATE '{}'", escape_single_quote_string(v)), - Value::Time(v) => format!("TIME '{}'", escape_single_quote_string(v)), - Value::Timestamp(v) => format!("TIMESTAMP '{}'", escape_single_quote_string(v)), + Value::Long(v) => write!(f, "{}", v), + Value::Double(v) => write!(f, "{}", v), + Value::SingleQuotedString(v) => write!(f, "'{}'", escape_single_quote_string(v)), + Value::NationalStringLiteral(v) => write!(f, "N'{}'", v), + Value::HexStringLiteral(v) => write!(f, "X'{}'", v), + Value::Boolean(v) => write!(f, "{}", v), + Value::Date(v) => write!(f, "DATE '{}'", escape_single_quote_string(v)), + Value::Time(v) => write!(f, "TIME '{}'", escape_single_quote_string(v)), + Value::Timestamp(v) => write!(f, "TIMESTAMP '{}'", escape_single_quote_string(v)), Value::Interval { value, leading_field: DateTimeField::Second, @@ -78,7 +79,8 @@ impl ToString for Value { // When the leading field is SECOND, the parser guarantees that // the last field is None. assert!(last_field.is_none()); - format!( + write!( + f, "INTERVAL '{}' SECOND ({}, {})", escape_single_quote_string(value), leading_precision, @@ -92,23 +94,24 @@ impl ToString for Value { last_field, fractional_seconds_precision, } => { - let mut s = format!( + write!( + f, "INTERVAL '{}' {}", escape_single_quote_string(value), - leading_field.to_string() - ); + leading_field + )?; if let Some(leading_precision) = leading_precision { - s += &format!(" ({})", leading_precision); + write!(f, " ({})", leading_precision)?; } if let Some(last_field) = last_field { - s += &format!(" TO {}", last_field.to_string()); + write!(f, " TO {}", last_field)?; } if let Some(fractional_seconds_precision) = fractional_seconds_precision { - s += &format!(" ({})", fractional_seconds_precision); + write!(f, " ({})", fractional_seconds_precision)?; } - s + Ok(()) } - Value::Null => "NULL".to_string(), + Value::Null => write!(f, "NULL"), } } } @@ -123,27 +126,36 @@ pub enum DateTimeField { Second, } -impl ToString for DateTimeField { - fn to_string(&self) -> String { - match self { - DateTimeField::Year => "YEAR".to_string(), - DateTimeField::Month => "MONTH".to_string(), - DateTimeField::Day => "DAY".to_string(), - DateTimeField::Hour => "HOUR".to_string(), - DateTimeField::Minute => "MINUTE".to_string(), - DateTimeField::Second => "SECOND".to_string(), - } +impl fmt::Display for DateTimeField { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{}", + match self { + DateTimeField::Year => "YEAR", + DateTimeField::Month => "MONTH", + DateTimeField::Day => "DAY", + DateTimeField::Hour => "HOUR", + DateTimeField::Minute => "MINUTE", + DateTimeField::Second => "SECOND", + } + ) } } -fn escape_single_quote_string(s: &str) -> String { - let mut escaped = String::new(); - for c in s.chars() { - if c == '\'' { - escaped.push_str("\'\'"); - } else { - escaped.push(c); +struct EscapeSingleQuoteString<'a>(&'a str); +impl<'a> fmt::Display for EscapeSingleQuoteString<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for c in self.0.chars() { + if c == '\'' { + write!(f, "\'\'")?; + } else { + write!(f, "{}", c)?; + } } + Ok(()) } - escaped +} +fn escape_single_quote_string(s: &str) -> EscapeSingleQuoteString<'_> { + EscapeSingleQuoteString(s) } diff --git a/src/parser.rs b/src/parser.rs index 2338cc754..de879c834 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -19,6 +19,7 @@ use super::dialect::keywords; use super::dialect::Dialect; use super::tokenizer::*; use std::error::Error; +use std::fmt; #[derive(Debug, Clone, PartialEq)] pub enum ParserError { @@ -52,8 +53,8 @@ impl From for ParserError { } } -impl std::fmt::Display for ParserError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { +impl fmt::Display for ParserError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, "sql parser error: {}", @@ -728,7 +729,7 @@ impl Parser { parser_err!(format!( "Expected {}, found: {}", expected, - found.map_or("EOF".to_string(), |t| t.to_string()) + found.map_or_else(|| "EOF".to_string(), |t| format!("{}", t)) )) } diff --git a/src/tokenizer.rs b/src/tokenizer.rs index d68390d93..bdb8e55c3 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -21,6 +21,7 @@ use std::str::Chars; use super::dialect::keywords::ALL_KEYWORDS; use super::dialect::Dialect; +use std::fmt; /// SQL Token enumeration #[derive(Debug, Clone, PartialEq)] @@ -89,40 +90,40 @@ pub enum Token { RBrace, } -impl ToString for Token { - fn to_string(&self) -> String { +impl fmt::Display for Token { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Token::Word(ref w) => w.to_string(), - Token::Number(ref n) => n.to_string(), - Token::Char(ref c) => c.to_string(), - Token::SingleQuotedString(ref s) => format!("'{}'", s), - Token::NationalStringLiteral(ref s) => format!("N'{}'", s), - Token::HexStringLiteral(ref s) => format!("X'{}'", s), - Token::Comma => ",".to_string(), - Token::Whitespace(ws) => ws.to_string(), - Token::Eq => "=".to_string(), - Token::Neq => "<>".to_string(), - Token::Lt => "<".to_string(), - Token::Gt => ">".to_string(), - Token::LtEq => "<=".to_string(), - Token::GtEq => ">=".to_string(), - Token::Plus => "+".to_string(), - Token::Minus => "-".to_string(), - Token::Mult => "*".to_string(), - Token::Div => "/".to_string(), - Token::Mod => "%".to_string(), - Token::LParen => "(".to_string(), - Token::RParen => ")".to_string(), - Token::Period => ".".to_string(), - Token::Colon => ":".to_string(), - Token::DoubleColon => "::".to_string(), - Token::SemiColon => ";".to_string(), - Token::Backslash => "\\".to_string(), - Token::LBracket => "[".to_string(), - Token::RBracket => "]".to_string(), - Token::Ampersand => "&".to_string(), - Token::LBrace => "{".to_string(), - Token::RBrace => "}".to_string(), + Token::Word(ref w) => write!(f, "{}", w), + Token::Number(ref n) => f.write_str(n), + Token::Char(ref c) => write!(f, "{}", c), + Token::SingleQuotedString(ref s) => write!(f, "'{}'", s), + Token::NationalStringLiteral(ref s) => write!(f, "N'{}'", s), + Token::HexStringLiteral(ref s) => write!(f, "X'{}'", s), + Token::Comma => f.write_str(","), + Token::Whitespace(ws) => write!(f, "{}", ws), + Token::Eq => f.write_str("="), + Token::Neq => f.write_str("<>"), + Token::Lt => f.write_str("<"), + Token::Gt => f.write_str(">"), + Token::LtEq => f.write_str("<="), + Token::GtEq => f.write_str(">="), + Token::Plus => f.write_str("+"), + Token::Minus => f.write_str("-"), + Token::Mult => f.write_str("*"), + Token::Div => f.write_str("/"), + Token::Mod => f.write_str("%"), + Token::LParen => f.write_str("("), + Token::RParen => f.write_str(")"), + Token::Period => f.write_str("."), + Token::Colon => f.write_str(":"), + Token::DoubleColon => f.write_str("::"), + Token::SemiColon => f.write_str(";"), + Token::Backslash => f.write_str("\\"), + Token::LBracket => f.write_str("["), + Token::RBracket => f.write_str("]"), + Token::Ampersand => f.write_str("&"), + Token::LBrace => f.write_str("{"), + Token::RBrace => f.write_str("}"), } } } @@ -164,13 +165,13 @@ pub struct Word { pub keyword: String, } -impl ToString for Word { - fn to_string(&self) -> String { +impl fmt::Display for Word { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.quote_style { Some(s) if s == '"' || s == '[' || s == '`' => { - format!("{}{}{}", s, self.value, Word::matching_end_quote(s)) + write!(f, "{}{}{}", s, self.value, Word::matching_end_quote(s)) } - None => self.value.clone(), + None => f.write_str(&self.value), _ => panic!("Unexpected quote_style!"), } } @@ -195,14 +196,14 @@ pub enum Whitespace { MultiLineComment(String), } -impl ToString for Whitespace { - fn to_string(&self) -> String { +impl fmt::Display for Whitespace { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Whitespace::Space => " ".to_string(), - Whitespace::Newline => "\n".to_string(), - Whitespace::Tab => "\t".to_string(), - Whitespace::SingleLineComment(s) => format!("--{}", s), - Whitespace::MultiLineComment(s) => format!("/*{}*/", s), + Whitespace::Space => f.write_str(" "), + Whitespace::Newline => f.write_str("\n"), + Whitespace::Tab => f.write_str("\t"), + Whitespace::SingleLineComment(s) => write!(f, "--{}", s), + Whitespace::MultiLineComment(s) => write!(f, "/*{}*/", s), } } }