Skip to content

Commit

Permalink
Add pretty-printing for Value
Browse files Browse the repository at this point in the history
  • Loading branch information
jpschorr committed Oct 7, 2024
1 parent 76d35ac commit bf6e730
Show file tree
Hide file tree
Showing 6 changed files with 526 additions and 0 deletions.
2 changes: 2 additions & 0 deletions partiql-value/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ edition.workspace = true
bench = false

[dependencies]
partiql-common = { path = "../partiql-common", version = "0.11.*" }
ordered-float = "4"
itertools = "0.13"
unicase = "2.7"
rust_decimal = { version = "1.36.0", default-features = false, features = ["std"] }
rust_decimal_macros = "1.36"

time = { version = "0.3", features = ["macros"] }
pretty = "0.12"

serde = { version = "1", features = ["derive"], optional = true }

Expand Down
2 changes: 2 additions & 0 deletions partiql-value/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ use rust_decimal::{Decimal as RustDecimal, Decimal};
mod bag;
mod datetime;
mod list;
mod pretty;
mod tuple;

pub use bag::*;
pub use datetime::*;
pub use list::*;
pub use pretty::*;
pub use tuple::*;

#[cfg(feature = "serde")]
Expand Down
130 changes: 130 additions & 0 deletions partiql-value/src/pretty.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
use crate::{Bag, DateTime, List, Tuple, Value};
use partiql_common::pretty::{
pretty_prefixed_doc, pretty_seq, pretty_surrounded, PrettyDoc, ToPretty, ToPrettyResult,
PRETTY_INDENT_MINOR_NEST,
};
use pretty::{Arena, DocAllocator, DocBuilder, Pretty};
use std::io::Write;

impl PrettyDoc for Value {
#[inline]
fn pretty_doc<'b, D, A>(&'b self, arena: &'b D) -> DocBuilder<'b, D, A>
where
D: DocAllocator<'b, A>,
D::Doc: Clone,
A: Clone,
{
match self {
Value::Null => arena.text("NULL"),
Value::Missing => arena.text("MISSING"),
Value::Boolean(inner) => arena.text(inner.to_string()),
Value::Integer(inner) => arena.text(inner.to_string()),
Value::Real(inner) => arena.text(inner.0.to_string()),
Value::Decimal(inner) => inner.pretty_doc(arena),
Value::String(inner) => pretty_string(inner, arena),
Value::Blob(inner) => pretty_string(inner, arena),
Value::DateTime(inner) => inner.pretty_doc(arena),
Value::List(inner) => inner.pretty_doc(arena),
Value::Bag(inner) => inner.pretty_doc(arena),
Value::Tuple(inner) => inner.pretty_doc(arena),
}
}
}

impl PrettyDoc for DateTime {
#[inline]
fn pretty_doc<'b, D, A>(&'b self, arena: &'b D) -> DocBuilder<'b, D, A>
where
D: DocAllocator<'b, A>,
D::Doc: Clone,
A: Clone,
{
match self {
DateTime::Date(d) => pretty_prefixed_doc("DATE", format!("'{d:?}'"), arena),

DateTime::Time(t) => pretty_prefixed_doc("TIME", format!("'{t:?}'"), arena),
DateTime::TimeWithTz(t, tz) => {
pretty_prefixed_doc("TIME WITH TIME ZONE", format!("'{t:?} {tz:?}'"), arena)
}
DateTime::Timestamp(dt) => pretty_prefixed_doc("TIMESTAMP", format!("'{dt:?}'"), arena),
DateTime::TimestampWithTz(dt) => {
pretty_prefixed_doc("TIMESTAMP WITH TIME ZONE", format!("'{dt:?}'"), arena)
}
}
}
}

impl PrettyDoc for List {
#[inline]
fn pretty_doc<'b, D, A>(&'b self, arena: &'b D) -> DocBuilder<'b, D, A>
where
D: DocAllocator<'b, A>,
D::Doc: Clone,
A: Clone,
{
pretty_seq(self.iter(), "[", "]", ",", PRETTY_INDENT_MINOR_NEST, arena)
}
}

impl PrettyDoc for Bag {
#[inline]
fn pretty_doc<'b, D, A>(&'b self, arena: &'b D) -> DocBuilder<'b, D, A>
where
D: DocAllocator<'b, A>,
D::Doc: Clone,
A: Clone,
{
pretty_seq(
self.iter(),
"<<",
">>",
",",
PRETTY_INDENT_MINOR_NEST,
arena,
)
}
}

impl PrettyDoc for Tuple {
#[inline]
fn pretty_doc<'b, D, A>(&'b self, arena: &'b D) -> DocBuilder<'b, D, A>
where
D: DocAllocator<'b, A>,
D::Doc: Clone,
A: Clone,
{
let wrapped = self.pairs().map(|p| unsafe {
let x: &'b StructValuePair<'b> = std::mem::transmute(&p);
x
});
pretty_seq(wrapped, "{", "}", ",", PRETTY_INDENT_MINOR_NEST, arena)
}
}

pub struct StructValuePair<'a>((&'a String, &'a Value));

impl<'a> PrettyDoc for StructValuePair<'a> {
fn pretty_doc<'b, D, A>(&'b self, arena: &'b D) -> DocBuilder<'b, D, A>
where
D: DocAllocator<'b, A>,
D::Doc: Clone,
A: Clone,
{
let (k, v) = self.0;
let k = k.pretty_doc(arena);
let v = v.pretty_doc(arena);
let sep = arena.text(": ");

k.append(sep).group().append(v).group()
}
}

fn pretty_string<'b, P, D, A>(contents: &'b P, arena: &'b D) -> DocBuilder<'b, D, A>
where
P: PrettyDoc + 'b,
D: DocAllocator<'b, A>,
D::Doc: Clone,
A: Clone,
{
pretty_surrounded(contents, "'", "'", arena)
}
3 changes: 3 additions & 0 deletions partiql/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ insta = "1.40.0"
thiserror = "1.0"

itertools = "0.13"
rust_decimal = { version = "1.25.0", default-features = false, features = ["std"] }
time = { version = "0.3", features = ["macros"] }

criterion = "0.5"
rand = "0.8"

Expand Down
99 changes: 99 additions & 0 deletions partiql/tests/pretty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ use itertools::Itertools;
use partiql_ast::ast::{AstNode, TopLevelQuery};
use partiql_common::pretty::ToPretty;
use partiql_parser::ParserResult;
use partiql_value::Value::Decimal;
use partiql_value::{bag, list, tuple, DateTime, Value};
use rust_decimal::prelude::FromPrimitive;
use time::macros::{date, datetime, offset, time};

#[track_caller]
#[inline]
Expand Down Expand Up @@ -51,6 +55,101 @@ fn pretty_print_roundtrip_test(statement_ast: &AstNode<TopLevelQuery>) {
assert_eq!(pretty, pretty2);
}

#[track_caller]
#[inline]
fn pretty_print_value_test(name: &str, value: &Value) {
pretty_print_value_output_test(name, value);
pretty_print_value_roundtrip_test(value);
}

#[track_caller]
fn pretty_print_value_output_test(name: &str, value: &Value) {
// TODO https://github.com/partiql/partiql-lang-rust/issues/473
let doc = [180, 120, 80, 40, 30, 20, 10]
.into_iter()
.map(|w| {
let header = format!("{:-<w$}", "");
let ast = format!("{}\n", value.to_pretty_string(w).unwrap());
format!("{header}\n{ast}")
})
.join("\n");

let w = 200;
let header = format!("{:=<w$}", "");
let doc = format!("{header}\n\n{doc}");

insta::assert_snapshot!(name, doc)
}

#[track_caller]
fn pretty_print_value_roundtrip_test(value: &Value) {
let pretty = value.to_pretty_string(40).unwrap();

let reparsed = parse(pretty.as_str());
assert!(reparsed.is_ok());

let pretty2 = reparsed.unwrap().ast.to_pretty_string(40).unwrap();

assert_eq!(pretty, pretty2);
}

#[test]
fn pretty_val() {
let dec = Value::Decimal(Box::new(
rust_decimal::Decimal::from_f64(2.998e8).expect("deciaml"),
));
let dt_d = Value::DateTime(Box::new(DateTime::Date(date!(2020 - 01 - 01))));
let dt_t = Value::DateTime(Box::new(DateTime::Time(time!(1:02:03.004_005_006))));
let dt_ttz = Value::DateTime(Box::new(DateTime::TimeWithTz(
time!(1:02:03.004_005_006),
offset!(UTC),
)));
let dt_ts = Value::DateTime(Box::new(DateTime::Timestamp(datetime!(2020-01-01 0:00 ))));
let dt_tstz = Value::DateTime(Box::new(DateTime::TimestampWithTz(
datetime!(2020-01-01 0:00 UTC),
)));
let blob = Value::Blob(Box::new("abcdef".as_bytes().into()));
let l_val = list!(
1,
2,
999.876,
dec,
dt_d,
dt_t,
dt_ttz,
dt_ts,
dt_tstz,
blob,
Value::Missing
);
let short_l_val = list!(1, 2, "skip a few", 99, 100);
let b_val = bag!(
tuple!(("n", 1)),
tuple!(("n", 2)),
tuple!(("n", 3)),
tuple!(("n", 4)),
tuple!(("n", 5)),
tuple!(("n", 6)),
tuple!(("n", 7)),
tuple!(("n", 8)),
tuple!(("n", 9)),
tuple!(("n", 10))
);
let t_val = tuple!(
("foo", true),
("-foo", false),
("bar", 42),
("baz", 3.14),
("qux", "string"),
("thud", Value::Null),
("plugh", l_val),
("xyzzy", b_val),
("waldo", short_l_val)
)
.into();
pretty_print_value_test("pretty_val", &t_val);
}

#[test]
fn pretty() {
pretty_print_test(
Expand Down
Loading

0 comments on commit bf6e730

Please sign in to comment.