Skip to content

Commit

Permalink
Improvements to pretty-printing for AND/OR/CASE/PIVOT (#482)
Browse files Browse the repository at this point in the history
  • Loading branch information
jpschorr authored Jul 31, 2024
1 parent 2da9cdb commit 6dc088c
Show file tree
Hide file tree
Showing 18 changed files with 485 additions and 257 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]
### Changed

- partiql-ast: fixed pretty-printing of `PIVOT`
- partiql-ast: improved pretty-printing of `CASE` and various clauses
-
### Added

### Fixed
Expand Down
175 changes: 101 additions & 74 deletions partiql-ast/src/pretty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,14 +213,24 @@ impl PrettyDoc for Select {
D::Doc: Clone,
A: Clone,
{
fn format<'b, C, D, A>(child: &'b C, arena: &'b D) -> DocBuilder<'b, D, A>
where
D: DocAllocator<'b, A>,
D::Doc: Clone,
A: Clone,
C: PrettyDoc,
{
child.pretty_doc(arena).group()
}

fn delegate<'b, C, D, A>(child: &'b Option<C>, arena: &'b D) -> Option<DocBuilder<'b, D, A>>
where
D: DocAllocator<'b, A>,
D::Doc: Clone,
A: Clone,
C: PrettyDoc,
{
child.as_ref().map(|inner| inner.pretty_doc(arena).group())
child.as_ref().map(|inner| format(inner, arena))
}

let Select {
Expand All @@ -232,25 +242,32 @@ impl PrettyDoc for Select {
group_by,
having,
} = self;
let clauses = [
Some(project.pretty_doc(arena).group()),
let mut clauses = [
Some(format(project, arena)),
delegate(exclude, arena),
from.as_ref().map(|inner| inner.pretty_doc(arena).group()),
from_let
.as_ref()
.map(|inner| inner.pretty_doc(arena).group()),
where_clause
.as_ref()
.map(|inner| inner.pretty_doc(arena).group()),
group_by
.as_ref()
.map(|inner| inner.pretty_doc(arena).group()),
having.as_ref().map(|inner| inner.pretty_doc(arena).group()),
delegate(from, arena),
delegate(from_let, arena),
delegate(where_clause, arena),
delegate(group_by, arena),
delegate(having, arena),
]
.into_iter()
.flatten();

arena.intersperse(clauses, arena.softline()).group()
let mut result = arena.nil();
let separator = arena.line();
if let Some(first) = clauses.next() {
let mut curr = first;

for clause in clauses {
result = result.append(curr.append(separator.clone()).group());
curr = clause;
}

result = result.append(curr);
}

result
}
}

Expand Down Expand Up @@ -282,9 +299,9 @@ impl PrettyDoc for ProjectionKind {
}
ProjectionKind::ProjectPivot(ProjectPivot { key, value }) => {
let parts = [
key.pretty_doc(arena),
arena.text("AT"),
value.pretty_doc(arena),
arena.text("AT"),
key.pretty_doc(arena),
];
let decl = arena.intersperse(parts, arena.space()).group();
pretty_annotated_doc("PIVOT", decl, arena)
Expand Down Expand Up @@ -401,6 +418,7 @@ impl PrettyDoc for Expr {
unreachable!();
}
}
.group()
}
}

Expand Down Expand Up @@ -570,10 +588,13 @@ impl PrettyDoc for BinOp {
let op = arena.text(sym);
let lhs = lhs.pretty_doc(arena).nest(nest);
let rhs = rhs.pretty_doc(arena).nest(nest);
let sep = arena.space();
let sep = if nest == 0 {
arena.space()
} else {
arena.softline()
};
let expr = arena.intersperse([lhs, op, rhs], sep).group();
let paren_expr = [arena.text("("), expr, arena.text(")")];
arena.concat(paren_expr).group()
pretty_parenthesized_doc(expr, arena).group()
}
}

Expand Down Expand Up @@ -698,30 +719,9 @@ impl PrettyDoc for SimpleCase {
default,
} = self;

let kw_case = arena.text("CASE");
let search = expr.pretty_doc(arena);
let branches = cases.iter().map(|ExprPair { first, second }| {
let kw_when = arena.text("WHEN");
let test = first.pretty_doc(arena);
let kw_then = arena.text("THEN");
let then = second.pretty_doc(arena);
arena
.intersperse([kw_when, test, kw_then, then], arena.space())
.group()
});
let branches = arena
.intersperse(branches, arena.softline())
.group()
.nest(MINOR_NEST_INDENT);
let default = default
.as_ref()
.map(|d| arena.text("ELSE ").append(d.pretty_doc(arena)));

if let Some(default) = default {
arena.intersperse([kw_case, search, branches, default], arena.softline())
} else {
arena.intersperse([kw_case, search, branches], arena.softline())
}
let branches = case_branches(arena, cases, default);
pretty_seq_doc(branches, "CASE", Some(search), "END", " ", arena)
}
}

Expand All @@ -734,30 +734,37 @@ impl PrettyDoc for SearchedCase {
{
let SearchedCase { cases, default } = self;

let kw_case = arena.text("CASE");
let branches = cases.iter().map(|ExprPair { first, second }| {
let branches = case_branches(arena, cases, default);
pretty_seq_doc(branches, "CASE", None, "END", " ", arena)
}
}

fn case_branches<'b, D, A>(
arena: &'b D,
cases: &'b [ExprPair],
default: &'b Option<Box<Expr>>,
) -> impl Iterator<Item = DocBuilder<'b, D, A>>
where
D: DocAllocator<'b, A>,
D::Doc: Clone,
A: Clone + 'b,
{
cases
.iter()
.map(|ExprPair { first, second }| {
let kw_when = arena.text("WHEN");
let test = first.pretty_doc(arena);
let kw_then = arena.text("THEN");
let then = second.pretty_doc(arena);
arena
.intersperse([kw_when, test, kw_then, then], arena.space())
.group()
});
let branches = arena
.intersperse(branches, arena.softline())
.group()
.nest(MINOR_NEST_INDENT);
let default = default
.as_ref()
.map(|d| arena.text("ELSE ").append(d.pretty_doc(arena)));

if let Some(default) = default {
arena.intersperse([kw_case, branches, default], arena.softline())
} else {
arena.intersperse([kw_case, branches], arena.softline())
}
}
})
.chain(
default
.iter()
.map(|d| arena.text("ELSE ").append(d.pretty_doc(arena)).group()),
)
}

impl PrettyDoc for Struct {
Expand Down Expand Up @@ -1258,41 +1265,61 @@ where
D::Doc: Clone,
A: Clone,
{
arena
.text("(")
.append(arena.space())
.append(doc)
.append(arena.space())
.append(arena.text(")"))
.group()
arena.text("(").append(doc).append(arena.text(")")).group()
}

fn pretty_seq<'i, 'b, I, P, D, A>(
list: I,
fn pretty_seq_doc<'i, 'b, I, E, D, A>(
seq: I,
start: &'static str,
qualifier: Option<E>,
end: &'static str,
sep: &'static str,
arena: &'b D,
) -> DocBuilder<'b, D, A>
where
I: IntoIterator<Item = &'b P>,
P: PrettyDoc + 'b,
E: Pretty<'b, D, A>,
I: IntoIterator<Item = E>,
D: DocAllocator<'b, A>,
D::Doc: Clone,
A: Clone,
{
let start = arena.text(start);
let end = arena.text(end);
let sep = arena.text(sep).append(arena.line());
let seq = list.into_iter().map(|l| l.pretty_doc(arena));
let body = arena.line().append(arena.intersperse(seq, sep)).group();
let start = if let Some(qual) = qualifier {
start.append(arena.space()).append(qual)
} else {
start
};
let body = arena
.line()
.append(arena.intersperse(seq, sep))
.append(arena.line())
.group();
start
.append(body.nest(MINOR_NEST_INDENT))
.append(arena.line())
.append(end)
.group()
}

fn pretty_seq<'i, 'b, I, P, D, A>(
list: I,
start: &'static str,
end: &'static str,
sep: &'static str,
arena: &'b D,
) -> DocBuilder<'b, D, A>
where
I: IntoIterator<Item = &'b P>,
P: PrettyDoc + 'b,
D: DocAllocator<'b, A>,
D::Doc: Clone,
A: Clone,
{
let seq = list.into_iter().map(|l| l.pretty_doc(arena));
pretty_seq_doc(seq, start, None, end, sep, arena)
}

fn pretty_list<'b, I, P, D, A>(list: I, nest: isize, arena: &'b D) -> DocBuilder<'b, D, A>
where
I: IntoIterator<Item = &'b P>,
Expand Down
34 changes: 32 additions & 2 deletions partiql/tests/pretty.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use itertools::Itertools;
use partiql_ast::ast::{AstNode, TopLevelQuery};
use partiql_ast::pretty::ToPretty;
use partiql_parser::ParserResult;

Expand All @@ -15,13 +16,18 @@ fn pretty_print_test(name: &str, statement: &str) {
assert!(res.is_ok());
let res = res.unwrap();

// TODO https://github.com/partiql/partiql-lang-rust/issues/473
pretty_print_output_test(name, statement, &res.ast);
pretty_print_roundtrip_test(&res.ast);
}

#[track_caller]
fn pretty_print_output_test(name: &str, statement: &str, statement_ast: &AstNode<TopLevelQuery>) {
// 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", res.ast.to_pretty_string(w).unwrap());
let ast = format!("{}\n", statement_ast.to_pretty_string(w).unwrap());
format!("{header}\n{ast}")
})
.join("\n");
Expand All @@ -33,6 +39,18 @@ fn pretty_print_test(name: &str, statement: &str) {
insta::assert_snapshot!(name, doc)
}

#[track_caller]
fn pretty_print_roundtrip_test(statement_ast: &AstNode<TopLevelQuery>) {
let pretty = statement_ast.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() {
pretty_print_test(
Expand Down Expand Up @@ -169,6 +187,18 @@ fn pretty_pivot() {
);
}

#[test]
fn pretty_ands_and_ors() {
pretty_print_test(
"pretty_ands_and_ors",
"
SELECT *
FROM test_data AS test_data
WHERE ((((test_data.country_code <> 'Distinctio.') AND ((test_data.* < false) AND (NOT (test_data.description LIKE 'Esse solam.') AND NOT (test_data.transaction_id LIKE 'Esset accusata.')))) OR (test_data.test_address <> 'Potest. Sed.')) AND (test_data.* > -28.146858383543243))
",
);
}

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

1 comment on commit 6dc088c

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PartiQL (rust) Benchmark

Benchmark suite Current: 6dc088c Previous: 2da9cdb Ratio
arith_agg-avg 769347 ns/iter (± 8086) 761913 ns/iter (± 14385) 1.01
arith_agg-avg_distinct 864631 ns/iter (± 3111) 853598 ns/iter (± 26650) 1.01
arith_agg-count 817735 ns/iter (± 44170) 809269 ns/iter (± 13784) 1.01
arith_agg-count_distinct 859871 ns/iter (± 16448) 849768 ns/iter (± 6574) 1.01
arith_agg-min 823541 ns/iter (± 16473) 820144 ns/iter (± 9836) 1.00
arith_agg-min_distinct 860789 ns/iter (± 3429) 850831 ns/iter (± 2157) 1.01
arith_agg-max 828686 ns/iter (± 1915) 825705 ns/iter (± 3804) 1.00
arith_agg-max_distinct 867876 ns/iter (± 32406) 863395 ns/iter (± 2866) 1.01
arith_agg-sum 824516 ns/iter (± 3833) 816547 ns/iter (± 1833) 1.01
arith_agg-sum_distinct 868788 ns/iter (± 2851) 856852 ns/iter (± 3790) 1.01
arith_agg-avg-count-min-max-sum 965437 ns/iter (± 13827) 959703 ns/iter (± 4902) 1.01
arith_agg-avg-count-min-max-sum-group_by 1203160 ns/iter (± 24930) 1200958 ns/iter (± 8200) 1.00
arith_agg-avg-count-min-max-sum-group_by-group_as 1805354 ns/iter (± 41273) 1799692 ns/iter (± 10981) 1.00
arith_agg-avg_distinct-count_distinct-min_distinct-max_distinct-sum_distinct 1264656 ns/iter (± 12802) 1267278 ns/iter (± 25309) 1.00
arith_agg-avg_distinct-count_distinct-min_distinct-max_distinct-sum_distinct-group_by 1550278 ns/iter (± 24933) 1509818 ns/iter (± 14136) 1.03
arith_agg-avg_distinct-count_distinct-min_distinct-max_distinct-sum_distinct-group_by-group_as 2145855 ns/iter (± 41224) 2112653 ns/iter (± 9660) 1.02
parse-1 5378 ns/iter (± 84) 5677 ns/iter (± 25) 0.95
parse-15 46773 ns/iter (± 120) 48290 ns/iter (± 123) 0.97
parse-30 91519 ns/iter (± 268) 93389 ns/iter (± 314) 0.98
compile-1 4321 ns/iter (± 23) 4414 ns/iter (± 18) 0.98
compile-15 33227 ns/iter (± 112) 33339 ns/iter (± 199) 1.00
compile-30 69079 ns/iter (± 1950) 70444 ns/iter (± 198) 0.98
plan-1 67288 ns/iter (± 1696) 66887 ns/iter (± 373) 1.01
plan-15 1050445 ns/iter (± 41700) 1042690 ns/iter (± 9301) 1.01
plan-30 2102844 ns/iter (± 19876) 2093184 ns/iter (± 7870) 1.00
eval-1 13304642 ns/iter (± 223004) 12671873 ns/iter (± 212003) 1.05
eval-15 86429143 ns/iter (± 2425132) 85566685 ns/iter (± 739891) 1.01
eval-30 166575250 ns/iter (± 2418779) 164700563 ns/iter (± 807391) 1.01
join 9850 ns/iter (± 102) 9804 ns/iter (± 473) 1.00
simple 2502 ns/iter (± 9) 2537 ns/iter (± 10) 0.99
simple-no 423 ns/iter (± 2) 434 ns/iter (± 0) 0.97
numbers 57 ns/iter (± 0) 57 ns/iter (± 0) 1
parse-simple 775 ns/iter (± 9) 728 ns/iter (± 5) 1.06
parse-ion 2398 ns/iter (± 5) 2281 ns/iter (± 9) 1.05
parse-group 6958 ns/iter (± 23) 7258 ns/iter (± 36) 0.96
parse-complex 18549 ns/iter (± 418) 18954 ns/iter (± 89) 0.98
parse-complex-fexpr 25695 ns/iter (± 131) 27188 ns/iter (± 134) 0.95

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.