Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Functions:
## 10 @ 1: add

=== Plan
Project[$0, $1, add($0, $1):i32?]
Project[_ => $0, $1, add($0, $1):i32?]
Read[table1 => col1:i32?, col2:i32?]
"#;

Expand All @@ -59,7 +59,7 @@ use substrait_explain::{parse, format_with_options, OutputOptions, Visibility};

let plan = parse(r#"
=== Plan
Project[$0, 42, 54:i16]
Project[_ => $0, 42, 54:i16]
Read[data => name:string?, num:i64]
"#).unwrap();

Expand Down Expand Up @@ -244,7 +244,7 @@ Functions:
=== Plan
Root[result]
Aggregate[$0 => $0, sum($1):i32?, count($1):i64]
Project[$0, add($1, $2):i32?]
Project[_ => $0, add($1, $2):i32?]
Read[table1 => category:string, col1:i32?, col2:i32?]
```

Expand Down
33 changes: 20 additions & 13 deletions GRAMMAR.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ Functions:

=== Plan
Root[result]
Project[$0, $1, add($0, $1):i64]
Project[_ => $0, $1, add($0, $1):i64]
Read[orders => quantity:i32?, price:i64]
# "#;
#
Expand Down Expand Up @@ -178,7 +178,7 @@ Functions:

=== Plan
Root[result] // Level 0 (no indentation)
Project[$0, $1] // Level 1 (2 spaces)
Project[_ => $0, $1] // Level 1 (2 spaces)
Filter[gt($0, 10):boolean => $0] // Level 2 (4 spaces)
Read[data => a:i64] // Level 3 (6 spaces)
# "#;
Expand Down Expand Up @@ -298,7 +298,7 @@ From [official Substrait grammar](https://raw.githubusercontent.com/substrait-io
let plan_text = r#"
=== Plan
Root[result]
Project[$0, $1, $2, $3]
Project[_ => $0, $1, $2, $3]
Read[data => int_field:i64, string_field:string?, created_at:timestamp?, user_id:uuid]
"#;
#
Expand Down Expand Up @@ -360,7 +360,7 @@ use substrait_explain::Parser;
let plan_text = r#"
=== Plan
Root[result]
Project[$0, $1, $2]
Project[_ => $0, $1, $2]
Read[data => list_field:list<i64>, map_field:map<string, i64>, struct_field:struct<i64, string?>]
"#;

Expand Down Expand Up @@ -400,7 +400,7 @@ Functions:

=== Plan
Root[result]
Project[$0, $1, $2]
Project[_ => $0, $1, $2]
Read[data => point_field:point#8@1?<i8>, custom_field:custom_type#9, prefixed_field:u!custom_type]
# "#;
#
Expand Down Expand Up @@ -437,7 +437,7 @@ Currently, only references to fields in the Relations' input are supported.
# let plan_text = r#"
=== Plan
Root[result]
Project[$0, $1, $42]
Project[_ => $0, $1, $42]
Read[data => field0:i64, field1:string, field42:boolean]
# "#;
#
Expand Down Expand Up @@ -523,7 +523,7 @@ An IfThen expression is a conditional function or logical operator that evaluate
=== Plan
Root[status]
Fetch[limit=10, offset=0 => $0]
Project[if_then(true -> $0, false -> $1, _ -> $2)]
Project[_ => if_then(true -> $0, false -> $1, _ -> $2)]
Read[events.logs => status:string?]
# "#;
#
Expand Down Expand Up @@ -689,7 +689,7 @@ form a 1-element tuple: `(x,)`. For 2+ elements the trailing comma is optional:
# let plan_text = r#"
=== Plan
Root[c, d] // root with output columns c and d
Project[$0, $1]
Project[_ => $0, $1]
Read[data => a:i64, b:string]
# "#;
#
Expand Down Expand Up @@ -984,7 +984,7 @@ Functions:
=== Plan
Root[result]
Filter[gt($2, 100):boolean => $0, $1, $2]
Project[$0, $1, $2]
Project[_ => $0, $1, $2]
Read[data => a:i64, b:string, c:i32]
# "#;
#
Expand All @@ -996,11 +996,18 @@ Root[result]

#### Syntax

`"Project" "[" (expression ("," expression)*)? "]"`
```text
project_relation := "Project" "[" "_" project_output? "]"
project_output := "=>" project_values
/ "+>" project_additions ("|>" emit_mapping)?
```

#### Components

- `expression` - field reference, function call, or literal (see Expressions section)
- The leading `_` is Project's required empty parameter list.
- `project_values` is a comma-separated list of input references and expressions. In compact `=>` syntax, it describes Project's final output.
- `project_additions` is a comma-separated list of expressions appended to Project's inherited input fields. Use `_` when it is empty.
- `emit_mapping` references the complete direct output: inherited input fields first, followed by `project_additions`.

#### Example

Expand All @@ -1010,7 +1017,7 @@ Root[result]
# let plan_text = r#"
=== Plan
Root[result]
Project[$1, 42] // project field 1 and literal 42
Project[_ => $1, 42] // project field 1 and literal 42
Read[data => a:i64, b:string]
# "#;
#
Expand Down Expand Up @@ -1440,7 +1447,7 @@ Functions:
Root[customer_revenue]
Aggregate[$0, $1 => $0, $1, sum($3):i64]
Filter[gt($3, 100):boolean => $0, $1, $2, $3]
Project[$0, $1, $2, multiply($4, $5):i64]
Project[_ => $0, $1, $2, multiply($4, $5):i64]
Join[&Inner, eq($0, $3):boolean => $0, $1, $2, $3, $4, $5]
Read[users => id:i64, name:string, region:string]
Read[orders => user_id:i64, quantity:i32, price:i64]
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ Functions:
=== Plan
Root[revenue]
Filter[gt($2, 100) => $0, $1]
Project[$0, $1, multiply($0, $1)]
Project[_ => $0, $1, multiply($0, $1)]
Read[orders => quantity:i32?, price:fp64?]
```

Expand Down Expand Up @@ -157,7 +157,7 @@ Functions:
=== Plan
Root[revenue]
Filter[gt($2, 100) => $0, $1]
Project[$0, $1, multiply($0, $1)]
Project[_ => $0, $1, multiply($0, $1)]
Read[orders => quantity:i32?, price:fp64?]
"#;

Expand Down
2 changes: 1 addition & 1 deletion example-plans/basic.substrait
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
=== Plan
Root[result]
Project[$0, $1]
Project[_ => $0, $1]
Read[data => a:i64, b:string]
6 changes: 3 additions & 3 deletions example-plans/simple.substrait
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ Functions:
=== Plan
Root[c, d]
Filter[eq($0, 0):boolean => $0, $1]
Project[$1, abs($0):fp64?]
Project[_ => $1, abs($0):fp64?]
Read[schema.table => a:fp64?, b:i64]

Root[c, d, e]
Aggregate[$0 => $0, sum($1):fp64?, count($1):i64]
Read[schema.table => name:string?, num:fp64?, id:i64]

Root[name, num]
Project[$1, coalesce($1, $2):fp64?]
Project[_ => $1, coalesce($1, $2):fp64?]
Read[schema.table => name:string?, num:fp64?, other_num:fp64?, id:i64]

Root[name, parent, sum, count]
Expand All @@ -35,7 +35,7 @@ Root[name, num, id]
Read[schema.table => name:string?, num:fp64?, id:i64]

Root[result]
Project[$0, $1, $2]
Project[_ => $0, $1, $2]
Filter[eq($2, 100):boolean => $0, $1, $2]
Read[orders => customer:string, amount:fp64, order_id:i64]

Expand Down
2 changes: 1 addition & 1 deletion examples/advanced_usage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Functions:
=== Plan
Root[revenue]
Filter[gt($2, 100):boolean => $0, $1]
Project[$0, $1, multiply($0, $1):fp64?]
Project[_ => $0, $1, multiply($0, $1):fp64?]
Read[orders => quantity:i32?, price:fp64?]
"#;

Expand Down
2 changes: 1 addition & 1 deletion examples/basic_usage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Functions:
=== Plan
Root[revenue]
Filter[gt($2, 100):boolean => $0, $1]
Project[$0, $1, multiply($0, $1):fp64?]
Project[_ => $0, $1, multiply($0, $1):fp64?]
Read[orders => quantity:i32?, price:fp64?]
"#;

Expand Down
8 changes: 4 additions & 4 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@ mod tests {

const BASIC_PLAN: &str = r#"=== Plan
Root[result]
Project[$0, $1]
Project[_ => $0, $1]
Read[data => a:i64, b:string]
"#;

Expand All @@ -497,7 +497,7 @@ Functions:
=== Plan
Root[result]
Filter[gt($2, 100):boolean => $0, $1, $2]
Project[$0, $1, $2]
Project[_ => $0, $1, $2]
Read[data => a:i64, b:string, c:i32]
"#;

Expand All @@ -523,7 +523,7 @@ Root[result]
let output_content = String::from_utf8(output).unwrap();
assert!(output_content.contains("=== Plan"));
assert!(output_content.contains("Root[result]"));
assert!(output_content.contains("Project[$0, $1]"));
assert!(output_content.contains("Project[_]"));
assert!(output_content.contains("Read[data => a:i64, b:string]"));
}

Expand Down Expand Up @@ -644,7 +644,7 @@ Root[result]
let output_content = String::from_utf8(output).unwrap();
assert!(output_content.contains("=== Plan"));
assert!(output_content.contains("Root[result]"));
assert!(output_content.contains("Project[$0, $1]"));
assert!(output_content.contains("Project[_]"));
assert!(output_content.contains("Read[data => a:i64, b:string]"));
}

Expand Down
6 changes: 3 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ use textify::plan::PlanWriter;
/// let plan_text = r#"
/// === Plan
/// Root[c, d]
/// Project[$1, 42]
/// Project[_ => $1, 42]
/// Read[schema.table => a:i64, b:string?]
/// "#;
///
Expand Down Expand Up @@ -108,7 +108,7 @@ pub fn parse_with_registry(
/// let plan: Plan = parse(r#"
/// === Plan
/// Root[result]
/// Project[$0, $1]
/// Project[_ => $0, $1]
/// Read[data => a:i64, b:string]
/// "#).unwrap();
///
Expand Down Expand Up @@ -142,7 +142,7 @@ pub fn format(plan: &Plan) -> (String, Vec<FormatError>) {
/// let plan = parse(r#"
/// === Plan
/// Root[result]
/// Project[$0, 42]
/// Project[_ => $0, 42]
/// Read[data => a:i64]
/// "#).unwrap();
///
Expand Down
14 changes: 11 additions & 3 deletions src/parser/expression_grammar.pest
Original file line number Diff line number Diff line change
Expand Up @@ -300,9 +300,17 @@ virtual_row = { "(" ~ sp ~ expression_list? ~ sp ~ ")" }

filter_relation = { "Filter" ~ "[" ~ expression ~ (sp ~ reference_output)? ~ "]" }

project_relation = { "Project" ~ "[" ~ project_argument_list ~ "]" }
project_argument_list = { (project_argument ~ (sp ~ "," ~ sp ~ project_argument)*)? }
project_argument = { reference | expression }
// Project has no relation parameters, so `_` marks the empty parameter list.
// Compact output combines input references and added expressions; explicit
// output declares only the expressions appended to the inherited input fields.
project_relation = { "Project" ~ "[" ~ empty ~ (sp ~ project_output)? ~ "]" }
project_output = { implicit_project_output | explicit_project_output }
implicit_project_output = { "=>" ~ sp ~ project_argument_list }
explicit_project_output = { ("+>" ~ sp ~ project_additions ~ (sp ~ explicit_emit)?) | explicit_emit }
project_argument_list = { empty | (project_argument ~ (sp ~ "," ~ sp ~ project_argument)*) }
project_argument = { reference | expression }
project_additions = { project_expression_list }
project_expression_list = { empty | expression_list }

// Aggregate relation: groups by specified fields and applies aggregate functions
// Format: Aggregate[group_by_fields => output_items]
Expand Down
2 changes: 1 addition & 1 deletion src/parser/extensions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -737,7 +737,7 @@ Types:
# 11 @ 1: u!point
=== Plan
Root[result]
Project[$0]
Project[_ => $0]
Read[data => p:point#11]";
let plan = Parser::parse(plan_text).unwrap();
let (text, errors) = format(&plan);
Expand Down
Loading