Skip to content

Commit

Permalink
Add Parsing of EXCLUDE
Browse files Browse the repository at this point in the history
  • Loading branch information
jpschorr committed Jul 25, 2024
1 parent 3ca67b7 commit 3c07a7e
Show file tree
Hide file tree
Showing 13 changed files with 832 additions and 11 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]
### Changed
- *BREAKING:* partiql-ast: added modeling of `EXCLUDE`
- *BREAKING:* partiql-ast: added pretty-printing of `EXCLUDE`

### Added
- *BREAKING:* partiql-parser: added parsing of `EXCLUDE`

### Fixed

Expand Down
28 changes: 28 additions & 0 deletions partiql-ast/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ pub enum SetQuantifier {
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Select {
pub project: AstNode<Projection>,
pub exclude: Option<AstNode<Exclusion>>,
pub from: Option<AstNode<FromClause>>,
pub from_let: Option<AstNode<Let>>,
pub where_clause: Option<Box<AstNode<WhereClause>>>,
Expand Down Expand Up @@ -375,6 +376,12 @@ pub struct ProjectExpr {
pub as_alias: Option<SymbolPrimitive>,
}

#[derive(Visit, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Exclusion {
pub items: Vec<AstNode<ExcludePath>>,
}

/// The expressions that can result in values.
#[derive(Visit, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
Expand Down Expand Up @@ -690,6 +697,27 @@ pub struct PathExpr {
pub index: Box<Expr>,
}

#[derive(Visit, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ExcludePath {
pub root: AstNode<VarRef>,
pub steps: Vec<ExcludePathStep>,
}

/// A "step" within a path expression; that is the components of the expression following the root.
#[derive(Visit, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ExcludePathStep {
#[visit(skip)]
PathProject(AstNode<SymbolPrimitive>),
#[visit(skip)]
PathIndex(AstNode<Lit>),
#[visit(skip)]
PathForEach,
#[visit(skip)]
PathUnpivot,
}

#[derive(Visit, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Let {
Expand Down
52 changes: 52 additions & 0 deletions partiql-ast/src/pretty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,19 @@ impl PrettyDoc for Select {
D::Doc: Clone,
A: Clone,
{
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())
}

let Select {
project,
exclude,
from,
from_let,
where_clause,
Expand All @@ -223,6 +234,7 @@ impl PrettyDoc for Select {
} = self;
let clauses = [
Some(project.pretty_doc(arena).group()),
delegate(exclude, arena),
from.as_ref().map(|inner| inner.pretty_doc(arena).group()),
from_let
.as_ref()
Expand Down Expand Up @@ -313,6 +325,46 @@ impl PrettyDoc for ProjectExpr {
}
}

impl PrettyDoc for Exclusion {
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_annotated_doc(
"EXCLUDE",
pretty_list(&self.items, MINOR_NEST_INDENT, arena),
arena,
)
}
}

impl PrettyDoc for ExcludePath {
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 ExcludePath { root, steps } = self;
let mut path = root.pretty_doc(arena);
for step in steps {
path = path.append(match step {
ExcludePathStep::PathProject(e) => arena.text(".").append(e.pretty_doc(arena)),
ExcludePathStep::PathIndex(e) => arena
.text("[")
.append(e.pretty_doc(arena))
.append(arena.text("]")),
ExcludePathStep::PathForEach => arena.text("[*]"),
ExcludePathStep::PathUnpivot => arena.text(".*"),
});
}

path
}
}

impl PrettyDoc for Expr {
fn pretty_doc<'b, D, A>(&'b self, arena: &'b D) -> DocBuilder<'b, D, A>
where
Expand Down
18 changes: 18 additions & 0 deletions partiql-ast/src/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,24 @@ pub trait Visitor<'ast> {
fn exit_project_expr(&mut self, _project_expr: &'ast ast::ProjectExpr) -> Traverse {
Traverse::Continue
}
fn enter_exclusion(&mut self, _exclusion: &'ast ast::Exclusion) -> Traverse {
Traverse::Continue
}
fn exit_exclusion(&mut self, _exclusion: &'ast ast::Exclusion) -> Traverse {
Traverse::Continue
}
fn enter_exclude_path(&mut self, _path: &'ast ast::ExcludePath) -> Traverse {
Traverse::Continue
}
fn exit_exclude_path(&mut self, _path: &'ast ast::ExcludePath) -> Traverse {
Traverse::Continue
}
fn enter_exclude_path_step(&mut self, _step: &'ast ast::ExcludePathStep) -> Traverse {
Traverse::Continue
}
fn exit_exclude_path_step(&mut self, _step: &'ast ast::ExcludePathStep) -> Traverse {
Traverse::Continue
}
fn enter_expr(&mut self, _expr: &'ast ast::Expr) -> Traverse {
Traverse::Continue
}
Expand Down
6 changes: 5 additions & 1 deletion partiql-logical-planner/src/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use partiql_ast::ast;
use partiql_ast::ast::{
Assignment, Bag, BagOpExpr, BagOperator, Between, BinOp, BinOpKind, Call, CallAgg, CallArg,
CallArgNamed, CaseSensitivity, CreateIndex, CreateTable, Ddl, DdlOp, Delete, Dml, DmlOp,
DropIndex, DropTable, Expr, FromClause, FromLet, FromLetKind, GroupByExpr, GroupKey,
DropIndex, DropTable, Exclusion, Expr, FromClause, FromLet, FromLetKind, GroupByExpr, GroupKey,
GroupingStrategy, Insert, InsertValue, Item, Join, JoinKind, JoinSpec, Like, List, Lit, NodeId,
NullOrderingSpec, OnConflict, OrderByExpr, OrderingSpec, Path, PathStep, ProjectExpr,
Projection, ProjectionKind, Query, QuerySet, Remove, SearchedCase, Select, Set, SetQuantifier,
Expand Down Expand Up @@ -810,6 +810,10 @@ impl<'a, 'ast> Visitor<'ast> for AstToLogical<'a> {
Traverse::Continue
}

fn enter_exclusion(&mut self, _exclusion: &'ast Exclusion) -> Traverse {
not_yet_implemented_fault!(self, "EXCLUDE");
}

fn enter_select(&mut self, select: &'ast Select) -> Traverse {
if select.having.is_some() && select.group_by.is_none() {
self.errors.push(AstTransformError::HavingWithoutGroupBy);
Expand Down
3 changes: 3 additions & 0 deletions partiql-parser/src/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,8 @@ pub enum Token<'input> {
Escape,
#[regex("(?i:Except)")]
Except,
#[regex("(?i:Exclude)")]
Exclude,
#[regex("(?i:False)")]
False,
#[regex("(?i:First)")]
Expand Down Expand Up @@ -776,6 +778,7 @@ impl<'input> fmt::Display for Token<'input> {
| Token::End
| Token::Escape
| Token::Except
| Token::Exclude
| Token::False
| Token::First
| Token::For
Expand Down
66 changes: 58 additions & 8 deletions partiql-parser/src/parse/partiql.lalrpop
Original file line number Diff line number Diff line change
Expand Up @@ -182,13 +182,15 @@ SfwQuery: ast::AstNode<ast::Select> = {
SfwClauses: ast::AstNode<ast::Select> = {
<lo:@L>
<project:SelectClause>
<exclude:ExcludeClause?>
<from:FromClause?>
<where_clause:WhereClause?>
<group_by:GroupClause?>
<having:HavingClause?>
<hi:@R> => {
state.node(ast::Select {
project,
exclude,
from,
from_let: None,
where_clause,
Expand All @@ -206,9 +208,11 @@ FwsClauses: ast::AstNode<ast::Select> = {
<group_by:GroupClause?>
<having:HavingClause?>
<project:SelectClause>
<exclude:ExcludeClause?>
<hi:@R> => {
state.node(ast::Select {
project,
exclude,
from: Some(from),
from_let: None,
where_clause,
Expand Down Expand Up @@ -255,6 +259,13 @@ Projection: ast::AstNode<ast::ProjectItem> = {
},
}

// ------------------------------------------------------------------------------ //
// Exclude //
// ------------------------------------------------------------------------------ //
ExcludeClause: ast::AstNode<ast::Exclusion> = {
<lo:@L> "EXCLUDE" <items:CommaSepPlus<ExcludePath>> <hi:@R> => state.node(ast::Exclusion {items}, lo..hi),
}

// ------------------------------------------------------------------------------ //
// FROM //
// ------------------------------------------------------------------------------ //
Expand Down Expand Up @@ -1121,22 +1132,60 @@ PathExprVarRef: ast::Expr = {
}

VarRefExpr: ast::Expr = {
<lo:@L> <ident:"UnquotedIdent"> <hi:@R> => ast::Expr::VarRef(state.node(ast::VarRef {
<varref:VarRef> => ast::Expr::VarRef(varref),
}

VarRef: ast::AstNode<ast::VarRef> = {
<lo:@L> <ident:"UnquotedIdent"> <hi:@R> => state.node(ast::VarRef {
name: ast::SymbolPrimitive { value: ident.to_owned(), case: ast::CaseSensitivity::CaseInsensitive },
qualifier: ast::ScopeQualifier::Unqualified
}, lo..hi)),
<lo:@L> <ident:"QuotedIdent"> <hi:@R> => ast::Expr::VarRef(state.node(ast::VarRef {
}, lo..hi),
<lo:@L> <ident:"QuotedIdent"> <hi:@R> => state.node(ast::VarRef {
name: ast::SymbolPrimitive { value: ident.to_owned(), case: ast::CaseSensitivity::CaseSensitive },
qualifier: ast::ScopeQualifier::Unqualified
}, lo..hi)),
<lo:@L> <ident:"UnquotedAtIdentifier"> <hi:@R> => ast::Expr::VarRef(state.node(ast::VarRef {
}, lo..hi),
<lo:@L> <ident:"UnquotedAtIdentifier"> <hi:@R> => state.node(ast::VarRef {
name: ast::SymbolPrimitive { value: ident.to_owned(), case: ast::CaseSensitivity::CaseInsensitive },
qualifier: ast::ScopeQualifier::Unqualified
}, lo..hi)),
<lo:@L> <ident:"QuotedAtIdentifier"> <hi:@R> => ast::Expr::VarRef(state.node(ast::VarRef {
}, lo..hi),
<lo:@L> <ident:"QuotedAtIdentifier"> <hi:@R> => state.node(ast::VarRef {
name: ast::SymbolPrimitive { value: ident.to_owned(), case: ast::CaseSensitivity::CaseSensitive },
qualifier: ast::ScopeQualifier::Unqualified
},lo..hi)),
},lo..hi),
}

ExcludePath: ast::AstNode<ast::ExcludePath> = {
<lo:@L> <root:VarRef> <steps:ExcludePathSteps> <hi:@R> => state.node(ast::ExcludePath { root, steps },lo..hi),
}

ExcludePathSteps: Vec<ast::ExcludePathStep> = {
<path:ExcludePathSteps> <step:ExcludePathStep> => {
let mut steps = path;
steps.push(step);
steps
},
<step:ExcludePathStep> => {
vec![step]
},
}

ExcludePathStep: ast::ExcludePathStep = {
"." <lo:@L> <s:SymbolPrimitive> <hi:@R> => {
ast::ExcludePathStep::PathProject( state.node(s, lo..hi) )
},
"[" "*" "]" => {
ast::ExcludePathStep::PathForEach
},
"." "*" => {
ast::ExcludePathStep::PathUnpivot
},
"[" <lo:@L> <l:LiteralNumber> <hi:@R> "]" => {
ast::ExcludePathStep::PathIndex( state.node(l, lo..hi) )
},
"[" <lo:@L> <s:"String"> <hi:@R> "]" => {
let sym = ast::SymbolPrimitive { value: s.to_string(), case: ast::CaseSensitivity::CaseSensitive };
ast::ExcludePathStep::PathProject( state.node(sym, lo..hi) )
},
}

// ------------------------------------------------------------------------------ //
Expand Down Expand Up @@ -1392,6 +1441,7 @@ extern {
"DATE" => lexer::Token::Date,
"DESC" => lexer::Token::Desc,
"DISTINCT" => lexer::Token::Distinct,
"EXCLUDE" => lexer::Token::Exclude,
"ELSE" => lexer::Token::Else,
"END" => lexer::Token::End,
"ESCAPE" => lexer::Token::Escape,
Expand Down
4 changes: 2 additions & 2 deletions partiql-parser/src/preprocessor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -489,9 +489,9 @@ where
///
/// * `tok` - The current [`Token`] being considered for matching.
/// * `is_nested` - Whether the preprocessor is considering [`Tokens`] inside a nested expression
/// (i.e., inside parens).
/// (i.e., inside parens).
/// * `is_init_arg` - Whether this is the first argument being considered for the function expression's
/// parameters.
/// parameters.
/// * `matchers` - A slice of the remaining arguments for a single pattern for the function expression.
#[allow(clippy::only_used_in_recursion)]
fn match_arg(
Expand Down
Loading

0 comments on commit 3c07a7e

Please sign in to comment.