Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Parsing of EXCLUDE #480

Merged
merged 2 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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.
jpschorr marked this conversation as resolved.
Show resolved Hide resolved
#[derive(Visit, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ExcludePathStep {
Copy link
Member

Choose a reason for hiding this comment

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

I like that the naming of the enum variants are the same as PathStep

#[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
Loading