From a1b9201bbd813536321270d1403d4df5172659df Mon Sep 17 00:00:00 2001 From: Finn Bear Date: Thu, 19 Jan 2023 01:53:33 -0800 Subject: [PATCH] Add pretty printing for SQL `Query`, `Statement`, `Value`, `Array`, and `Object` types (#1420) --- lib/src/sql/array.rs | 11 +- lib/src/sql/fmt.rs | 243 ++++++++++++++++++++++++++++++- lib/src/sql/idiom.rs | 23 ++- lib/src/sql/object.rs | 36 +++-- lib/src/sql/permission.rs | 71 +++++++-- lib/src/sql/query.rs | 4 +- lib/src/sql/statement.rs | 46 +++--- lib/src/sql/statements/define.rs | 26 ++-- lib/src/sql/statements/ifelse.rs | 20 +-- lib/src/sql/value/value.rs | 5 +- 10 files changed, 405 insertions(+), 80 deletions(-) diff --git a/lib/src/sql/array.rs b/lib/src/sql/array.rs index a8aa6dc8..73a4ae42 100644 --- a/lib/src/sql/array.rs +++ b/lib/src/sql/array.rs @@ -5,7 +5,7 @@ use crate::err::Error; use crate::sql::comment::mightbespace; use crate::sql::common::commas; use crate::sql::error::IResult; -use crate::sql::fmt::Fmt; +use crate::sql::fmt::{pretty_indent, Fmt, Pretty}; use crate::sql::number::Number; use crate::sql::operation::Operation; use crate::sql::serde::is_internal_serialization; @@ -16,7 +16,7 @@ use nom::combinator::opt; use nom::multi::separated_list0; use serde::{Deserialize, Serialize}; use std::collections::HashSet; -use std::fmt::{self, Display, Formatter}; +use std::fmt::{self, Display, Formatter, Write}; use std::ops; use std::ops::Deref; use std::ops::DerefMut; @@ -148,7 +148,12 @@ impl Array { impl Display for Array { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "[{}]", Fmt::comma_separated(self.as_slice())) + let mut f = Pretty::from(f); + f.write_char('[')?; + let indent = pretty_indent(); + write!(f, "{}", Fmt::pretty_comma_separated(self.as_slice()))?; + drop(indent); + f.write_char(']') } } diff --git a/lib/src/sql/fmt.rs b/lib/src/sql/fmt.rs index fddeed9f..21fe0348 100644 --- a/lib/src/sql/fmt.rs +++ b/lib/src/sql/fmt.rs @@ -1,5 +1,6 @@ use std::cell::Cell; -use std::fmt::{self, Display, Formatter}; +use std::fmt::{self, Display, Formatter, Write}; +use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; /// Implements fmt::Display by calling formatter on contents. pub(crate) struct Fmt { @@ -29,6 +30,24 @@ impl, T: Display> Fmt fmt: pub(crate) fn comma_separated(into_iter: I) -> Self { Self::new(into_iter, fmt_comma_separated) } + + /// Formats values with a comma and a space separating them or, if pretty printing is in + /// effect, a comma, a newline, and indentation. + pub(crate) fn pretty_comma_separated(into_iter: I) -> Self { + Self::new( + into_iter, + if is_pretty() { + fmt_pretty_comma_separated + } else { + fmt_comma_separated + }, + ) + } + + /// Formats values with a new line separating them. + pub(crate) fn pretty_new_line_separated(into_iter: I) -> Self { + Self::new(into_iter, fmt_new_line_separated) + } } fn fmt_comma_separated( @@ -37,9 +56,231 @@ fn fmt_comma_separated( ) -> fmt::Result { for (i, v) in into_iter.into_iter().enumerate() { if i > 0 { + // This comma goes after the item formatted in the last iteration. f.write_str(", ")?; } Display::fmt(&v, f)?; } Ok(()) } + +fn fmt_pretty_comma_separated( + into_iter: impl IntoIterator, + f: &mut Formatter, +) -> fmt::Result { + for (i, v) in into_iter.into_iter().enumerate() { + if i > 0 { + // We don't need a space after the comma if we are going to have a newline. + f.write_char(',')?; + pretty_sequence_item(); + } + Display::fmt(&v, f)?; + } + Ok(()) +} + +fn fmt_new_line_separated( + into_iter: impl IntoIterator, + f: &mut Formatter, +) -> fmt::Result { + for (i, v) in into_iter.into_iter().enumerate() { + if i > 0 { + // One of the few cases where the raw string data depends on is pretty i.e. we don't + // need a space after the comma if we are going to have a newline. + if is_pretty() { + pretty_sequence_item(); + } else { + f.write_char('\n')?; + } + } + Display::fmt(&v, f)?; + } + Ok(()) +} + +/// Creates a formatting function that joins iterators with an arbitrary separator. +pub fn fmt_separated_by>( + separator: impl Display, +) -> impl Fn(II, &mut Formatter) -> fmt::Result { + move |into_iter: II, f: &mut Formatter| { + for (i, v) in into_iter.into_iter().enumerate() { + if i > 0 { + // This separator goes after the item formatted in the last iteration. + Display::fmt(&separator, f)?; + } + Display::fmt(&v, f)?; + } + Ok(()) + } +} + +thread_local! { + // Avoid `RefCell`/`UnsafeCell` by using atomic types. Access is synchronized due to + // `thread_local!` so all accesses can use `Ordering::Relaxed`. + + /// Whether pretty-printing. + static PRETTY: AtomicBool = AtomicBool::new(false); + /// The current level of indentation, in units of tabs. + static INDENT: AtomicU32 = AtomicU32::new(0); + /// Whether the next formatting action should be preceded by a newline and indentation. + static NEW_LINE: AtomicBool = AtomicBool::new(false); +} + +/// An adapter that, if enabled, adds pretty print formatting. +pub(crate) struct Pretty { + inner: W, + /// This is the active pretty printer, responsible for injecting formatting. + active: bool, +} + +impl Pretty { + #[allow(unused)] + pub fn new(inner: W) -> Self { + Self::conditional(inner, true) + } + + pub fn conditional(inner: W, enable: bool) -> Self { + let pretty_started_here = enable + && PRETTY.with(|pretty| { + // Evaluates to true if PRETTY was false and is now true. + pretty.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed).is_ok() + }); + if pretty_started_here { + // Clean slate. + NEW_LINE.with(|new_line| new_line.store(false, Ordering::Relaxed)); + INDENT.with(|indent| indent.store(0, Ordering::Relaxed)); + } + Self { + inner, + // Don't want multiple active pretty printers, although they wouldn't necessarily misbehave. + active: pretty_started_here, + } + } +} + +impl<'a, 'b> From<&'a mut Formatter<'b>> for Pretty<&'a mut Formatter<'b>> { + fn from(f: &'a mut Formatter<'b>) -> Self { + Self::conditional(f, f.alternate()) + } +} + +impl Drop for Pretty { + fn drop(&mut self) { + if self.active { + PRETTY.with(|pretty| { + debug_assert!(pretty.load(Ordering::Relaxed), "pretty status changed unexpectedly"); + pretty.store(false, Ordering::Relaxed); + }); + } + } +} + +/// Returns whether pretty printing is in effect. +pub(crate) fn is_pretty() -> bool { + PRETTY.with(|pretty| pretty.load(Ordering::Relaxed)) +} + +/// If pretty printing is in effect, increments the indentation level (until the return value +/// is dropped). +#[must_use = "hold for the span of the indent, then drop"] +pub(crate) fn pretty_indent() -> PrettyGuard { + PrettyGuard::new(1) +} + +/// Marks the end of an item in the sequence, after which indentation will follow if pretty printing +/// is in effect. +pub(crate) fn pretty_sequence_item() { + // List items need a new line, but no additional indentation. + NEW_LINE.with(|new_line| new_line.store(true, Ordering::Relaxed)); +} + +/// When dropped, applies the opposite increment to the current indentation level. +pub(crate) struct PrettyGuard { + increment: i8, +} + +impl PrettyGuard { + fn new(increment: i8) -> Self { + Self::raw(increment); + PrettyGuard { + increment, + } + } + + fn raw(increment: i8) { + INDENT.with(|indent| { + // Equivalent to `indent += increment` if signed numbers could be added to unsigned + // numbers in stable, atomic Rust. + if increment >= 0 { + indent.fetch_add(increment as u32, Ordering::Relaxed); + } else { + indent.fetch_sub(increment.unsigned_abs() as u32, Ordering::Relaxed); + } + }); + NEW_LINE.with(|new_line| new_line.store(true, Ordering::Relaxed)); + } +} + +impl Drop for PrettyGuard { + fn drop(&mut self) { + Self::raw(-self.increment) + } +} + +impl std::fmt::Write for Pretty { + fn write_str(&mut self, s: &str) -> std::fmt::Result { + if self.active && NEW_LINE.with(|new_line| new_line.swap(false, Ordering::Relaxed)) { + // Newline. + self.inner.write_char('\n')?; + for _ in 0..INDENT.with(|indent| indent.load(Ordering::Relaxed)) { + // One level of indentation. + self.inner.write_char('\t')?; + } + } + // What we were asked to write. + self.inner.write_str(s) + } +} + +#[cfg(test)] +mod tests { + use crate::sql::{array::array, object::object, parse, value::value}; + + #[test] + fn pretty_query() { + let query = parse("SELECT * FROM {foo: [1, 2, 3]};").unwrap(); + assert_eq!(format!("{}", query), "SELECT * FROM { foo: [1, 2, 3] };"); + assert_eq!( + format!("{:#}", query), + "SELECT * FROM {\n\tfoo: [\n\t\t1,\n\t\t2,\n\t\t3\n\t]\n};" + ); + } + + #[test] + fn pretty_define_query() { + let query = parse("DEFINE TABLE test SCHEMAFULL PERMISSIONS FOR create, update, delete NONE FOR select WHERE public = true;").unwrap(); + assert_eq!(format!("{}", query), "DEFINE TABLE test SCHEMAFULL PERMISSIONS FOR select WHERE public = true, FOR create, update, delete NONE;"); + assert_eq!(format!("{:#}", query), "DEFINE TABLE test SCHEMAFULL\n\tPERMISSIONS\n\t\tFOR select\n\t\t\tWHERE public = true\n\t\tFOR create, update, delete NONE\n;"); + } + + #[test] + fn pretty_value() { + let value = value("{foo: [1, 2, 3]};").unwrap().1; + assert_eq!(format!("{}", value), "{ foo: [1, 2, 3] }"); + assert_eq!(format!("{:#}", value), "{\n\tfoo: [\n\t\t1,\n\t\t2,\n\t\t3\n\t]\n}"); + } + + #[test] + fn pretty_object() { + let object = object("{foo: [1, 2, 3]};").unwrap().1; + assert_eq!(format!("{}", object), "{ foo: [1, 2, 3] }"); + assert_eq!(format!("{:#}", object), "{\n\tfoo: [\n\t\t1,\n\t\t2,\n\t\t3\n\t]\n}"); + } + + #[test] + fn pretty_array() { + let array = array("[1, 2, 3];").unwrap().1; + assert_eq!(format!("{}", array), "[1, 2, 3]"); + assert_eq!(format!("{:#}", array), "[\n\t1,\n\t2,\n\t3\n]"); + } +} diff --git a/lib/src/sql/idiom.rs b/lib/src/sql/idiom.rs index 8f91d6f5..dbbb02e6 100644 --- a/lib/src/sql/idiom.rs +++ b/lib/src/sql/idiom.rs @@ -4,7 +4,7 @@ use crate::dbs::Transaction; use crate::err::Error; use crate::sql::common::commas; use crate::sql::error::IResult; -use crate::sql::fmt::Fmt; +use crate::sql::fmt::{fmt_separated_by, Fmt}; use crate::sql::part::Next; use crate::sql::part::{all, field, first, graph, index, last, part, thing, Part}; use crate::sql::paths::{ID, IN, OUT}; @@ -147,18 +147,17 @@ impl Idiom { impl fmt::Display for Idiom { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( + Display::fmt( + &Fmt::new( + self.0.iter().enumerate().map(|args| { + Fmt::new(args, |(i, p), f| match (i, p) { + (0, Part::Field(v)) => Display::fmt(v, f), + _ => Display::fmt(p, f), + }) + }), + fmt_separated_by(""), + ), f, - "{}", - self.0 - .iter() - .enumerate() - .map(|(i, p)| match (i, p) { - (0, Part::Field(v)) => format!("{}", v), - _ => format!("{}", p), - }) - .collect::>() - .join("") ) } } diff --git a/lib/src/sql/object.rs b/lib/src/sql/object.rs index d570914e..070c9052 100644 --- a/lib/src/sql/object.rs +++ b/lib/src/sql/object.rs @@ -6,7 +6,7 @@ use crate::sql::comment::mightbespace; use crate::sql::common::{commas, val_char}; use crate::sql::error::IResult; use crate::sql::escape::escape_key; -use crate::sql::fmt::Fmt; +use crate::sql::fmt::{is_pretty, pretty_indent, Fmt, Pretty}; use crate::sql::operation::{Op, Operation}; use crate::sql::serde::is_internal_serialization; use crate::sql::thing::Thing; @@ -22,7 +22,7 @@ use serde::ser::SerializeMap; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use std::collections::HashMap; -use std::fmt; +use std::fmt::{self, Display, Formatter, Write}; use std::ops::Deref; use std::ops::DerefMut; @@ -134,17 +134,33 @@ impl Object { } } -impl fmt::Display for Object { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +impl Display for Object { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let mut f = Pretty::from(f); + if is_pretty() { + f.write_char('{')?; + } else { + f.write_str("{ ")?; + } + let indent = pretty_indent(); write!( f, - "{{ {} }}", - Fmt::comma_separated( - self.0.iter().map(|args| Fmt::new(args, |(k, v), f| { - write!(f, "{}: {}", escape_key(k), v) - })) + "{}", + Fmt::pretty_comma_separated( + self.0.iter().map(|args| Fmt::new(args, |(k, v), f| write!( + f, + "{}: {}", + escape_key(k), + v + ))), ) - ) + )?; + drop(indent); + if is_pretty() { + f.write_char('}') + } else { + f.write_str(" }") + } } } diff --git a/lib/src/sql/permission.rs b/lib/src/sql/permission.rs index 708fa89a..7300f580 100644 --- a/lib/src/sql/permission.rs +++ b/lib/src/sql/permission.rs @@ -2,13 +2,17 @@ use crate::sql::comment::shouldbespace; use crate::sql::common::commas; use crate::sql::common::commasorspace; use crate::sql::error::IResult; +use crate::sql::fmt::is_pretty; +use crate::sql::fmt::pretty_indent; +use crate::sql::fmt::pretty_sequence_item; use crate::sql::value::{value, Value}; use nom::branch::alt; use nom::bytes::complete::tag_no_case; use nom::combinator::map; use nom::{multi::separated_list0, sequence::tuple}; use serde::{Deserialize, Serialize}; -use std::fmt; +use std::fmt::Write; +use std::fmt::{self, Display, Formatter}; use std::str; #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Hash)] @@ -53,8 +57,8 @@ impl Permissions { } } -impl fmt::Display for Permissions { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +impl Display for Permissions { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "PERMISSIONS")?; if self.is_none() { return write!(f, " NONE"); @@ -62,11 +66,56 @@ impl fmt::Display for Permissions { if self.is_full() { return write!(f, " FULL"); } - write!( - f, - " FOR select {}, FOR create {}, FOR update {}, FOR delete {}", - self.select, self.create, self.update, self.delete - ) + let mut lines = Vec::<(Vec, &Permission)>::new(); + for (c, permission) in ['s', 'c', 'u', 'd'].into_iter().zip([ + &self.select, + &self.create, + &self.update, + &self.delete, + ]) { + if let Some((existing, _)) = lines.iter_mut().find(|(_, p)| *p == permission) { + existing.push(c); + } else { + lines.push((vec![c], permission)); + } + } + let indent = if is_pretty() { + Some(pretty_indent()) + } else { + f.write_char(' ')?; + None + }; + for (i, (kinds, permission)) in lines.into_iter().enumerate() { + if i > 0 { + if is_pretty() { + pretty_sequence_item(); + } else { + f.write_str(", ")?; + } + } + write!(f, "FOR ")?; + for (i, kind) in kinds.into_iter().enumerate() { + if i > 0 { + f.write_str(", ")?; + } + f.write_str(match kind { + 's' => "select", + 'c' => "create", + 'u' => "update", + 'd' => "delete", + _ => unreachable!(), + })?; + } + match permission { + Permission::Specific(_) if is_pretty() => { + let _indent = pretty_indent(); + Display::fmt(permission, f)?; + } + _ => write!(f, " {}", permission)?, + } + } + drop(indent); + Ok(()) } } @@ -144,8 +193,8 @@ impl Default for Permission { } } -impl fmt::Display for Permission { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +impl Display for Permission { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { Self::None => f.write_str("NONE"), Self::Full => f.write_str("FULL"), @@ -212,7 +261,7 @@ mod tests { assert!(res.is_ok()); let out = res.unwrap().1; assert_eq!( - "PERMISSIONS FOR select FULL, FOR create WHERE public = true, FOR update WHERE public = true, FOR delete NONE", + "PERMISSIONS FOR select FULL, FOR create, update WHERE public = true, FOR delete NONE", format!("{}", out) ); assert_eq!( diff --git a/lib/src/sql/query.rs b/lib/src/sql/query.rs index 84c0e1e9..2c3c6b7b 100644 --- a/lib/src/sql/query.rs +++ b/lib/src/sql/query.rs @@ -1,8 +1,10 @@ use crate::sql::error::IResult; +use crate::sql::fmt::Pretty; use crate::sql::statement::{statements, Statement, Statements}; use derive::Store; use nom::combinator::all_consuming; use serde::{Deserialize, Serialize}; +use std::fmt::Write; use std::fmt::{self, Display, Formatter}; use std::ops::Deref; use std::str; @@ -19,7 +21,7 @@ impl Deref for Query { impl Display for Query { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - Display::fmt(&self.0, f) + write!(Pretty::from(f), "{}", &self.0) } } diff --git a/lib/src/sql/statement.rs b/lib/src/sql/statement.rs index 9ece2092..83e8238e 100644 --- a/lib/src/sql/statement.rs +++ b/lib/src/sql/statement.rs @@ -5,6 +5,8 @@ use crate::err::Error; use crate::sql::comment::{comment, mightbespace}; use crate::sql::common::colons; use crate::sql::error::IResult; +use crate::sql::fmt::Fmt; +use crate::sql::fmt::Pretty; use crate::sql::statements::begin::{begin, BeginStatement}; use crate::sql::statements::cancel::{cancel, CancelStatement}; use crate::sql::statements::commit::{commit, CommitStatement}; @@ -31,7 +33,7 @@ use nom::multi::many0; use nom::multi::separated_list1; use nom::sequence::delimited; use serde::{Deserialize, Serialize}; -use std::fmt::{self, Display, Formatter}; +use std::fmt::{self, Display, Formatter, Write}; use std::ops::Deref; use std::time::Duration; @@ -48,7 +50,9 @@ impl Deref for Statements { impl fmt::Display for Statements { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { Display::fmt( - &self.0.iter().map(|ref v| format!("{};", v)).collect::>().join("\n"), + &Fmt::pretty_new_line_separated( + self.0.iter().map(|v| Fmt::new(v, |v, f| write!(f, "{};", v))), + ), f, ) } @@ -148,25 +152,25 @@ impl Statement { impl Display for Statement { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { - Self::Use(v) => Display::fmt(v, f), - Self::Set(v) => Display::fmt(v, f), - Self::Info(v) => Display::fmt(v, f), - Self::Live(v) => Display::fmt(v, f), - Self::Kill(v) => Display::fmt(v, f), - Self::Begin(v) => Display::fmt(v, f), - Self::Cancel(v) => Display::fmt(v, f), - Self::Commit(v) => Display::fmt(v, f), - Self::Output(v) => Display::fmt(v, f), - Self::Ifelse(v) => Display::fmt(v, f), - Self::Select(v) => Display::fmt(v, f), - Self::Create(v) => Display::fmt(v, f), - Self::Update(v) => Display::fmt(v, f), - Self::Relate(v) => Display::fmt(v, f), - Self::Delete(v) => Display::fmt(v, f), - Self::Insert(v) => Display::fmt(v, f), - Self::Define(v) => Display::fmt(v, f), - Self::Remove(v) => Display::fmt(v, f), - Self::Option(v) => Display::fmt(v, f), + Self::Use(v) => write!(Pretty::from(f), "{}", v), + Self::Set(v) => write!(Pretty::from(f), "{}", v), + Self::Info(v) => write!(Pretty::from(f), "{}", v), + Self::Live(v) => write!(Pretty::from(f), "{}", v), + Self::Kill(v) => write!(Pretty::from(f), "{}", v), + Self::Begin(v) => write!(Pretty::from(f), "{}", v), + Self::Cancel(v) => write!(Pretty::from(f), "{}", v), + Self::Commit(v) => write!(Pretty::from(f), "{}", v), + Self::Output(v) => write!(Pretty::from(f), "{}", v), + Self::Ifelse(v) => write!(Pretty::from(f), "{}", v), + Self::Select(v) => write!(Pretty::from(f), "{}", v), + Self::Create(v) => write!(Pretty::from(f), "{}", v), + Self::Update(v) => write!(Pretty::from(f), "{}", v), + Self::Relate(v) => write!(Pretty::from(f), "{}", v), + Self::Delete(v) => write!(Pretty::from(f), "{}", v), + Self::Insert(v) => write!(Pretty::from(f), "{}", v), + Self::Define(v) => write!(Pretty::from(f), "{}", v), + Self::Remove(v) => write!(Pretty::from(f), "{}", v), + Self::Option(v) => write!(Pretty::from(f), "{}", v), } } } diff --git a/lib/src/sql/statements/define.rs b/lib/src/sql/statements/define.rs index 4fdd5d00..267289ab 100644 --- a/lib/src/sql/statements/define.rs +++ b/lib/src/sql/statements/define.rs @@ -9,6 +9,8 @@ use crate::sql::comment::shouldbespace; use crate::sql::duration::{duration, Duration}; use crate::sql::error::IResult; use crate::sql::escape::escape_str; +use crate::sql::fmt::is_pretty; +use crate::sql::fmt::pretty_indent; use crate::sql::ident::{ident, Ident}; use crate::sql::idiom; use crate::sql::idiom::{Idiom, Idioms}; @@ -31,8 +33,7 @@ use rand::distributions::Alphanumeric; use rand::rngs::OsRng; use rand::Rng; use serde::{Deserialize, Serialize}; -use std::fmt; -use std::fmt::Display; +use std::fmt::{self, Display, Write}; #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] pub enum DefineStatement { @@ -709,19 +710,24 @@ impl fmt::Display for DefineTableStatement { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "DEFINE TABLE {}", self.name)?; if self.drop { - write!(f, " DROP")? - } - if self.full { - write!(f, " SCHEMAFULL")? - } - if !self.full { - write!(f, " SCHEMALESS")? + f.write_str(" DROP")?; } + f.write_str(if self.full { + " SCHEMAFULL" + } else { + " SCHEMALESS" + })?; if let Some(ref v) = self.view { write!(f, " {}", v)? } if !self.permissions.is_full() { - write!(f, " {}", self.permissions)?; + let _indent = if is_pretty() { + Some(pretty_indent()) + } else { + f.write_char(' ')?; + None + }; + write!(f, "{}", self.permissions)?; } Ok(()) } diff --git a/lib/src/sql/statements/ifelse.rs b/lib/src/sql/statements/ifelse.rs index 19e2f066..3029af65 100644 --- a/lib/src/sql/statements/ifelse.rs +++ b/lib/src/sql/statements/ifelse.rs @@ -4,6 +4,7 @@ use crate::dbs::Transaction; use crate::err::Error; use crate::sql::comment::shouldbespace; use crate::sql::error::IResult; +use crate::sql::fmt::{fmt_separated_by, Fmt}; use crate::sql::value::{value, Value}; use derive::Store; use nom::bytes::complete::tag_no_case; @@ -11,6 +12,7 @@ use nom::combinator::opt; use nom::multi::separated_list0; use serde::{Deserialize, Serialize}; use std::fmt; +use std::fmt::Display; #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] pub struct IfelseStatement { @@ -48,21 +50,21 @@ impl IfelseStatement { } } -impl fmt::Display for IfelseStatement { +impl Display for IfelseStatement { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( + Display::fmt( + &Fmt::new( + self.exprs.iter().map(|args| { + Fmt::new(args, |(cond, then), f| write!(f, "IF {} THEN {}", cond, then)) + }), + fmt_separated_by(" ELSE "), + ), f, - "{}", - self.exprs - .iter() - .map(|(ref cond, ref then)| format!("IF {} THEN {}", cond, then)) - .collect::>() - .join(" ELSE ") )?; if let Some(ref v) = self.close { write!(f, " ELSE {}", v)? } - write!(f, " END")?; + f.write_str(" END")?; Ok(()) } } diff --git a/lib/src/sql/value/value.rs b/lib/src/sql/value/value.rs index a773c10d..11532d53 100644 --- a/lib/src/sql/value/value.rs +++ b/lib/src/sql/value/value.rs @@ -12,7 +12,7 @@ use crate::sql::duration::{duration, Duration}; use crate::sql::edges::{edges, Edges}; use crate::sql::error::IResult; use crate::sql::expression::{expression, Expression}; -use crate::sql::fmt::Fmt; +use crate::sql::fmt::{Fmt, Pretty}; use crate::sql::function::{function, Function}; use crate::sql::future::{future, Future}; use crate::sql::geometry::{geometry, Geometry}; @@ -49,7 +49,7 @@ use serde::{Deserialize, Serialize}; use std::cmp::Ordering; use std::collections::BTreeMap; use std::collections::HashMap; -use std::fmt::{self, Display, Formatter}; +use std::fmt::{self, Display, Formatter, Write}; use std::ops; use std::ops::Deref; use std::str::FromStr; @@ -1284,6 +1284,7 @@ impl Value { impl fmt::Display for Value { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut f = Pretty::from(f); match self { Value::None => write!(f, "NONE"), Value::Null => write!(f, "NULL"),