From 9fcf4c9419a78626479f7b05177315832578f678 Mon Sep 17 00:00:00 2001 From: Tobie Morgan Hitchcock Date: Tue, 21 Feb 2023 14:15:02 +0000 Subject: [PATCH] Add a new SQL `Block` type for multi-line statements and expressions Closes #225 Closes #1319 Related to #247 --- lib/src/sql/block.rs | 274 +++++++++++++++++++++++++++++++++++++ lib/src/sql/fmt.rs | 51 ++++--- lib/src/sql/mod.rs | 2 + lib/src/sql/statement.rs | 4 +- lib/src/sql/value/value.rs | 33 +++-- 5 files changed, 335 insertions(+), 29 deletions(-) create mode 100644 lib/src/sql/block.rs diff --git a/lib/src/sql/block.rs b/lib/src/sql/block.rs new file mode 100644 index 00000000..800de2d9 --- /dev/null +++ b/lib/src/sql/block.rs @@ -0,0 +1,274 @@ +use crate::cnf::PROTECTED_PARAM_NAMES; +use crate::ctx::Context; +use crate::dbs::Options; +use crate::dbs::Transaction; +use crate::err::Error; +use crate::sql::comment::{comment, mightbespace}; +use crate::sql::common::colons; +use crate::sql::error::IResult; +use crate::sql::fmt::{is_pretty, pretty_indent, Fmt, Pretty}; +use crate::sql::statements::create::{create, CreateStatement}; +use crate::sql::statements::delete::{delete, DeleteStatement}; +use crate::sql::statements::ifelse::{ifelse, IfelseStatement}; +use crate::sql::statements::insert::{insert, InsertStatement}; +use crate::sql::statements::output::{output, OutputStatement}; +use crate::sql::statements::relate::{relate, RelateStatement}; +use crate::sql::statements::select::{select, SelectStatement}; +use crate::sql::statements::set::{set, SetStatement}; +use crate::sql::statements::update::{update, UpdateStatement}; +use crate::sql::value::{value, Value}; +use nom::branch::alt; +use nom::character::complete::char; +use nom::combinator::map; +use nom::multi::many0; +use nom::multi::separated_list1; +use nom::sequence::delimited; +use serde::{Deserialize, Serialize}; +use std::cmp::Ordering; +use std::fmt::{self, Display, Formatter, Write}; +use std::ops::Deref; + +#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)] +pub struct Block(pub Vec); + +impl Deref for Block { + type Target = Vec; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From for Block { + fn from(v: Value) -> Self { + Block(vec![Entry::Value(v)]) + } +} + +impl Block { + pub(crate) fn writeable(&self) -> bool { + self.iter().any(Entry::writeable) + } + + pub(crate) async fn compute( + &self, + ctx: &Context<'_>, + opt: &Options, + txn: &Transaction, + doc: Option<&Value>, + ) -> Result { + // Duplicate context + let mut ctx = Context::new(ctx); + // Loop over the statements + for v in self.iter() { + match v { + Entry::Set(v) => { + // Check if the variable is a protected variable + let val = match PROTECTED_PARAM_NAMES.contains(&v.name.as_str()) { + // The variable isn't protected and can be stored + false => v.compute(&ctx, opt, txn, None).await, + // The user tried to set a protected variable + true => { + return Err(Error::InvalidParam { + name: v.name.to_owned(), + }) + } + }?; + // Set the parameter + ctx.add_value(v.name.to_owned(), val); + } + Entry::Ifelse(v) => { + v.compute(&ctx, opt, txn, doc).await?; + } + Entry::Select(v) => { + v.compute(&ctx, opt, txn, doc).await?; + } + Entry::Create(v) => { + v.compute(&ctx, opt, txn, doc).await?; + } + Entry::Update(v) => { + v.compute(&ctx, opt, txn, doc).await?; + } + Entry::Delete(v) => { + v.compute(&ctx, opt, txn, doc).await?; + } + Entry::Relate(v) => { + v.compute(&ctx, opt, txn, doc).await?; + } + Entry::Insert(v) => { + v.compute(&ctx, opt, txn, doc).await?; + } + Entry::Output(v) => { + return v.compute(&ctx, opt, txn, doc).await; + } + Entry::Value(v) => { + return v.compute(&ctx, opt, txn, doc).await; + } + } + } + // Return nothing + Ok(Value::None) + } +} + +impl Display for Block { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let mut f = Pretty::from(f); + match (self.len(), self.first()) { + (1, Some(Entry::Value(v))) => { + write!(f, "{{ {v} }}") + } + (l, _) => { + f.write_char('{')?; + if is_pretty() && l > 1 { + f.write_char('\n')?; + } + let indent = pretty_indent(); + write!( + f, + "{}", + &Fmt::two_line_separated( + self.0.iter().map(|args| Fmt::new(args, |v, f| write!(f, "{};", v))), + ) + )?; + drop(indent); + if is_pretty() && l > 1 { + f.write_char('\n')?; + } + f.write_char('}') + } + } + } +} + +pub fn block(i: &str) -> IResult<&str, Block> { + let (i, _) = char('{')(i)?; + let (i, _) = mightbespace(i)?; + let (i, v) = separated_list1(colons, entry)(i)?; + let (i, _) = many0(alt((colons, comment)))(i)?; + let (i, _) = mightbespace(i)?; + let (i, _) = char('}')(i)?; + Ok((i, Block(v))) +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)] +pub enum Entry { + Value(Value), + Set(SetStatement), + Ifelse(IfelseStatement), + Select(SelectStatement), + Create(CreateStatement), + Update(UpdateStatement), + Delete(DeleteStatement), + Relate(RelateStatement), + Insert(InsertStatement), + Output(OutputStatement), +} + +impl PartialOrd for Entry { + #[inline] + fn partial_cmp(&self, _: &Self) -> Option { + None + } +} + +impl Entry { + pub(crate) fn writeable(&self) -> bool { + match self { + Self::Set(v) => v.writeable(), + Self::Value(v) => v.writeable(), + Self::Ifelse(v) => v.writeable(), + Self::Select(v) => v.writeable(), + Self::Create(v) => v.writeable(), + Self::Update(v) => v.writeable(), + Self::Delete(v) => v.writeable(), + Self::Relate(v) => v.writeable(), + Self::Insert(v) => v.writeable(), + Self::Output(v) => v.writeable(), + } + } +} + +impl Display for Entry { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Self::Set(v) => write!(f, "{v}"), + Self::Value(v) => Display::fmt(v, f), + Self::Ifelse(v) => write!(f, "{v}"), + Self::Select(v) => write!(f, "{v}"), + Self::Create(v) => write!(f, "{v}"), + Self::Update(v) => write!(f, "{v}"), + Self::Delete(v) => write!(f, "{v}"), + Self::Relate(v) => write!(f, "{v}"), + Self::Insert(v) => write!(f, "{v}"), + Self::Output(v) => write!(f, "{v}"), + } + } +} + +pub fn entry(i: &str) -> IResult<&str, Entry> { + delimited( + mightbespace, + alt(( + map(set, Entry::Set), + map(output, Entry::Output), + map(ifelse, Entry::Ifelse), + map(select, Entry::Select), + map(create, Entry::Create), + map(update, Entry::Update), + map(relate, Entry::Relate), + map(delete, Entry::Delete), + map(insert, Entry::Insert), + map(value, Entry::Value), + )), + mightbespace, + )(i) +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn block_value() { + let sql = "{ 80 }"; + let res = block(sql); + assert!(res.is_ok()); + let out = res.unwrap().1; + assert_eq!(sql, format!("{}", out)) + } + + #[test] + fn block_ifelse() { + let sql = "{ + RETURN IF true THEN + 50 + ELSE + 40 + END; +}"; + let res = block(sql); + assert!(res.is_ok()); + let out = res.unwrap().1; + assert_eq!(sql, format!("{:#}", out)) + } + + #[test] + fn block_multiple() { + let sql = r#"{ + + LET $person = (SELECT * FROM person WHERE first = $first AND last = $last AND birthday = $birthday); + + RETURN IF $person[0].id THEN + $person[0] + ELSE + (CREATE person SET first = $first, last = $last, birthday = $birthday) + END; + +}"#; + let res = block(sql); + assert!(res.is_ok()); + let out = res.unwrap().1; + assert_eq!(sql, format!("{:#}", out)) + } +} diff --git a/lib/src/sql/fmt.rs b/lib/src/sql/fmt.rs index 21fe0348..283f2381 100644 --- a/lib/src/sql/fmt.rs +++ b/lib/src/sql/fmt.rs @@ -34,19 +34,17 @@ impl, T: Display> Fmt fmt: /// 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 - }, - ) + Self::new(into_iter, fmt_pretty_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) + pub(crate) fn one_line_separated(into_iter: I) -> Self { + Self::new(into_iter, fmt_one_line_separated) + } + + /// Formats values with a new line separating them. + pub(crate) fn two_line_separated(into_iter: I) -> Self { + Self::new(into_iter, fmt_two_line_separated) } } @@ -56,7 +54,6 @@ 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)?; @@ -70,23 +67,24 @@ fn fmt_pretty_comma_separated( ) -> 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(); + if is_pretty() { + f.write_char(',')?; + pretty_sequence_item(); + } else { + f.write_str(", ")?; + } } Display::fmt(&v, f)?; } Ok(()) } -fn fmt_new_line_separated( +fn fmt_one_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 { @@ -98,6 +96,25 @@ fn fmt_new_line_separated( Ok(()) } +fn fmt_two_line_separated( + into_iter: impl IntoIterator, + f: &mut Formatter, +) -> fmt::Result { + for (i, v) in into_iter.into_iter().enumerate() { + if i > 0 { + if is_pretty() { + f.write_char('\n')?; + pretty_sequence_item(); + } else { + f.write_char('\n')?; + 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, diff --git a/lib/src/sql/mod.rs b/lib/src/sql/mod.rs index 1ec6d61c..79fa3cd2 100644 --- a/lib/src/sql/mod.rs +++ b/lib/src/sql/mod.rs @@ -3,6 +3,7 @@ pub(crate) mod algorithm; pub(crate) mod array; pub(crate) mod base; +pub(crate) mod block; pub(crate) mod comment; pub(crate) mod common; pub(crate) mod cond; @@ -70,6 +71,7 @@ pub use self::parser::*; pub use self::algorithm::Algorithm; pub use self::array::Array; pub use self::base::Base; +pub use self::block::Block; pub use self::cond::Cond; pub use self::data::Data; pub use self::datetime::Datetime; diff --git a/lib/src/sql/statement.rs b/lib/src/sql/statement.rs index f06f3f19..9a4b937d 100644 --- a/lib/src/sql/statement.rs +++ b/lib/src/sql/statement.rs @@ -50,9 +50,7 @@ impl Deref for Statements { impl fmt::Display for Statements { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { Display::fmt( - &Fmt::pretty_new_line_separated( - self.0.iter().map(|v| Fmt::new(v, |v, f| write!(f, "{v};"))), - ), + &Fmt::one_line_separated(self.0.iter().map(|v| Fmt::new(v, |v, f| write!(f, "{v};")))), f, ) } diff --git a/lib/src/sql/value/value.rs b/lib/src/sql/value/value.rs index d47a56b5..ae0b61d4 100644 --- a/lib/src/sql/value/value.rs +++ b/lib/src/sql/value/value.rs @@ -5,6 +5,7 @@ use crate::dbs::Options; use crate::dbs::Transaction; use crate::err::Error; use crate::sql::array::{array, Array}; +use crate::sql::block::{block, Block}; use crate::sql::common::commas; use crate::sql::constant::{constant, Constant}; use crate::sql::datetime::{datetime, Datetime}; @@ -116,6 +117,7 @@ pub enum Value { Thing(Thing), Model(Model), Regex(Regex), + Block(Box), Range(Box), Edges(Box), Future(Box), @@ -239,6 +241,12 @@ impl From for Value { } } +impl From for Value { + fn from(v: Block) -> Self { + Value::Block(Box::new(v)) + } +} + impl From for Value { fn from(v: Range) -> Self { Value::Range(Box::new(v)) @@ -1314,6 +1322,7 @@ impl fmt::Display for Value { Value::Thing(v) => write!(f, "{v}"), Value::Model(v) => write!(f, "{v}"), Value::Regex(v) => write!(f, "{v}"), + Value::Block(v) => write!(f, "{v}"), Value::Range(v) => write!(f, "{v}"), Value::Edges(v) => write!(f, "{v}"), Value::Future(v) => write!(f, "{v}"), @@ -1328,9 +1337,10 @@ impl fmt::Display for Value { impl Value { pub(crate) fn writeable(&self) -> bool { match self { - Value::Array(v) => v.iter().any(|v| v.writeable()), + Value::Block(v) => v.writeable(), + Value::Array(v) => v.iter().any(Value::writeable), Value::Object(v) => v.iter().any(|(_, v)| v.writeable()), - Value::Function(v) => v.args().iter().any(|v| v.writeable()), + Value::Function(v) => v.args().iter().any(Value::writeable), Value::Subquery(v) => v.writeable(), Value::Expression(v) => v.l.writeable() || v.r.writeable(), _ => false, @@ -1352,6 +1362,7 @@ impl Value { Value::True => Ok(Value::True), Value::False => Ok(Value::False), Value::Thing(v) => v.compute(ctx, opt, txn, doc).await, + Value::Block(v) => v.compute(ctx, opt, txn, doc).await, Value::Range(v) => v.compute(ctx, opt, txn, doc).await, Value::Param(v) => v.compute(ctx, opt, txn, doc).await, Value::Idiom(v) => v.compute(ctx, opt, txn, doc).await, @@ -1392,13 +1403,14 @@ impl Serialize for Value { Value::Thing(v) => s.serialize_newtype_variant("Value", 15, "Thing", v), Value::Model(v) => s.serialize_newtype_variant("Value", 16, "Model", v), Value::Regex(v) => s.serialize_newtype_variant("Value", 17, "Regex", v), - Value::Range(v) => s.serialize_newtype_variant("Value", 18, "Range", v), - Value::Edges(v) => s.serialize_newtype_variant("Value", 19, "Edges", v), - Value::Future(v) => s.serialize_newtype_variant("Value", 20, "Future", v), - Value::Constant(v) => s.serialize_newtype_variant("Value", 21, "Constant", v), - Value::Function(v) => s.serialize_newtype_variant("Value", 22, "Function", v), - Value::Subquery(v) => s.serialize_newtype_variant("Value", 23, "Subquery", v), - Value::Expression(v) => s.serialize_newtype_variant("Value", 24, "Expression", v), + Value::Block(v) => s.serialize_newtype_variant("Value", 18, "Block", v), + Value::Range(v) => s.serialize_newtype_variant("Value", 19, "Range", v), + Value::Edges(v) => s.serialize_newtype_variant("Value", 20, "Edges", v), + Value::Future(v) => s.serialize_newtype_variant("Value", 21, "Future", v), + Value::Constant(v) => s.serialize_newtype_variant("Value", 22, "Constant", v), + Value::Function(v) => s.serialize_newtype_variant("Value", 23, "Function", v), + Value::Subquery(v) => s.serialize_newtype_variant("Value", 24, "Subquery", v), + Value::Expression(v) => s.serialize_newtype_variant("Value", 25, "Expression", v), } } else { match self { @@ -1498,6 +1510,7 @@ pub fn single(i: &str) -> IResult<&str, Value> { map(number, Value::from), map(object, Value::from), map(array, Value::from), + map(block, Value::from), map(param, Value::from), map(regex, Value::from), map(model, Value::from), @@ -1530,6 +1543,7 @@ pub fn select(i: &str) -> IResult<&str, Value> { map(number, Value::from), map(object, Value::from), map(array, Value::from), + map(block, Value::from), map(param, Value::from), map(regex, Value::from), map(model, Value::from), @@ -1548,6 +1562,7 @@ pub fn what(i: &str) -> IResult<&str, Value> { map(function, Value::from), map(constant, Value::from), map(future, Value::from), + map(block, Value::from), map(param, Value::from), map(model, Value::from), map(edges, Value::from),