use crate::parse::lexer;
use crate::error::ParseError;
use lalrpop_util as lpop;
use std::str::FromStr;
use partiql_ast::ast;
use partiql_common::syntax::location::{ByteOffset, BytePosition, Location, ToLocated};
use crate::parse::parse_util::{
strip_expr,
strip_query,
strip_query_set,
struct_to_lit,
bag_to_lit,
list_to_lit,
CallSite,
Attrs,
Synth
};
use crate::parse::parser_state::ParserState;
use partiql_common::node::NodeIdGenerator;
grammar<'input, 'state, Id>(input: &'input str, state: &'state mut ParserState<'input, Id>) where Id: NodeIdGenerator;
pub(crate) TopLevelQuery: ast::AstNode<ast::TopLevelQuery> = {
<lo:@L>
<with:WithClause?>
<query:Query>
<hi:@R> => {
state.node(ast::TopLevelQuery { with, query }, lo..hi)
}
}
Query: ast::AstNode<ast::Query> = {
<lo:@L>
<set:QuerySet>
<order_by:OrderByClause?>
<limit_offset:LimitOffsetClause>
<hi:@R> => {
state.node(ast::Query { set, order_by, limit_offset }, lo..hi)
}
}
// ------------------------------------------------------------------------------ //
// WITH //
// ------------------------------------------------------------------------------ //
WithClause: ast::AstNode<ast::WithClause> = {
<lo:@L> "WITH" <rec:"RECURSIVE"?> <withs:WithList> <hi:@R> => {
let recursive = matches!(rec, Some(_));
state.node(ast::WithClause {
recursive,
withs
}, lo..hi)
}
}
#[inline]
WithList: Vec<ast::AstNode<ast::WithElement>> = {
<CommaSepPlus<WithListElement>>
}
#[inline]
WithListElement: ast::AstNode<ast::WithElement> = {
<lo:@L> <query_name: SymbolPrimitive> <columns:WithColList?> "AS" <subquery:SubQueryAst> <how:WithSearchOrCycle?> <hi:@R> => {
state.node(ast::WithElement {
query_name,
columns,
subquery,
}, lo..hi)
}
}
#[inline]
WithColList: Vec<ast::SymbolPrimitive> = {
"(" <CommaSepPlus<SymbolPrimitive>> ")"
}
// TODO
#[inline]
WithSearchOrCycle: () = {
<WithSearchClause>,
<WithCycleClause>,
<WithSearchClause> <WithCycleClause>,
}
// TODO
WithSearchClause : () = {
"SEARCH"
}
// TODO
WithCycleClause : () = {
"CYCLE"
}
// ------------------------------------------------------------------------------ //
// //
// UNION/INTERSECT/EXCEPT //
// //
// ------------------------------------------------------------------------------ //
// Note: the Set operation rules are formulated in a precise way to assure 'natural'
// interpretation of set operations in an LR grammar while attempting
// to stay in accordance with SQL interpretations.
//
// In particular:
// - in order to assure operator precedence, the lowest precedence operators
// (i.e., `UNION` & `EXCEPT`) are at the top of the recursion tree, and the highest
// precedence are at the bottom (i.e., `INTERSECT`)
// - all set operations are left-associative and are thus expressed as left-self-recursive rules
QuerySet: ast::AstNode<ast::QuerySet> = {
<lo:@L> <lhs:Query> <bag_op:BagOp> <setq:SetQuantifierStrategy> <rhs:SingleQuery> <hi:@R> => {
let lhs = strip_query(lhs);
let rhs = strip_query_set(rhs, state, lo, hi);
let bag_expr = state.node(ast::BagOpExpr {
bag_op,
setq,
lhs: Box::new(lhs),
rhs: Box::new(rhs)
}, lo..hi);
state.node(ast::QuerySet::BagOp(Box::new( bag_expr )), lo..hi)
},
<SingleQuery>,
}
#[inline]
BagOp: ast::BagOperator = {
"UNION" => ast::BagOperator::Union,
"OUTER" "UNION" => ast::BagOperator::OuterUnion,
"EXCEPT" => ast::BagOperator::Except,
"OUTER" "EXCEPT" => ast::BagOperator::OuterExcept,
"INTERSECT" => ast::BagOperator::Intersect,
"OUTER" "INTERSECT" => ast::BagOperator::OuterIntersect,
}
#[inline]
SetQuantifier: ast::SetQuantifier = {
"DISTINCT"? => ast::SetQuantifier::Distinct,
"ALL" => ast::SetQuantifier::All,
}
// ------------------------------------------------------------------------------ //
// //
// ExprQuery or SFW Query //
// //
// ------------------------------------------------------------------------------ //
SingleQuery: ast::AstNode<ast::QuerySet> = {
<lo:@L> <expr:ExprQuery> <hi:@R> => {
match *expr {
ast::Expr::Query(ast::AstNode{ node: ast::Query{set, order_by:None, limit_offset:None} , .. }) => set,
_ => state.node(ast::QuerySet::Expr( expr ), lo..hi),
}
},
<lo:@L> <sfw:SfwQuery> <hi:@R> => state.node(ast::QuerySet::Select( Box::new(sfw)), lo..hi),
<lo:@L> <values:Values> <hi:@R> => values,
<lo:@L> <table:ExplicitTable> <hi:@R> => table,
}
Values: ast::AstNode<ast::QuerySet> = {
<lo:@L> "VALUES" <rows:CommaSepPlus<ValueRow>> <hi:@R> => state.node(ast::QuerySet::Values( rows ), lo..hi)
}
#[inline]
ValueRow: Box<ast::Expr> = {
"(" <e:ExprQuery> ")" => Box::new(*e),
<array:ExprTermCollection> => Box::new(array.data)
}
#[inline]
ExplicitTable: ast::AstNode<ast::QuerySet> = {
<lo:@L> "TABLE" <table_name:SymbolPrimitive> <hi:@R> => state.node(ast::QuerySet::Table( ast::QueryTable{table_name} ), lo..hi)
}
// ------------------------------------------------------------------------------ //
// //
// SFW Query //
// //
// ------------------------------------------------------------------------------ //
SfwQuery: ast::AstNode<ast::Select> = {
<SfwClauses>,
<FwsClauses>
}
// SQL-style where `Select` precedes `From`
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,
group_by,
having,
}, lo..hi)
}
}
// PartiQL-style where `Select` is last
FwsClauses: ast::AstNode<ast::Select> = {
<lo:@L>
<from:FromClause>
<where_clause:WhereClause?>
<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,
group_by,
having,
}, lo..hi)
}
}
// ------------------------------------------------------------------------------ //
// SELECT //
// ------------------------------------------------------------------------------ //
SelectClause: ast::AstNode<ast::Projection> = {
<lo:@L> "SELECT" <strategy: SetQuantifierStrategy> "*" <hi:@R> => state.node(ast::Projection {
kind: ast::ProjectionKind::ProjectStar,
setq: strategy,
}, lo..hi),
<lo:@L> "SELECT" <strategy: SetQuantifierStrategy> <project_items:CommaSepPlus<Projection>> <hi:@R> => state.node(ast::Projection {
kind: ast::ProjectionKind::ProjectList(project_items),
setq: strategy,
}, lo..hi),
<lo:@L> "SELECT" <strategy: SetQuantifierStrategy> "VALUE" <value:ExprQuery> <hi:@R> => state.node(ast::Projection {
kind: ast::ProjectionKind::ProjectValue(value),
setq: strategy,
}, lo..hi),
<lo:@L> "PIVOT" <value:ExprQuery> "AT" <key:ExprQuery> <hi:@R> => state.node(ast::Projection {
kind: ast::ProjectionKind::ProjectPivot(ast::ProjectPivot { key, value }),
setq: None,
}, lo..hi),
}
#[inline]
SetQuantifierStrategy: Option<ast::SetQuantifier> = {
"ALL" => Some(ast::SetQuantifier::All),
<distinct: "DISTINCT"?> => distinct.map(|_|ast::SetQuantifier::Distinct)
}
#[inline]
Projection: ast::AstNode<ast::ProjectItem> = {
<lo:@L> <expr:ExprQuery> <hi:@R>
=> state.node(ast::ProjectItem::ProjectExpr( ast::ProjectExpr{ expr, as_alias: None } ), lo..hi),
<lo:@L> <expr:ExprQuery> "AS"? <as_alias:SymbolPrimitive> <hi:@R> => {
state.node(ast::ProjectItem::ProjectExpr( ast::ProjectExpr{ expr, as_alias: Some(as_alias) } ), lo..hi)
},
}
// ------------------------------------------------------------------------------ //
// Exclude //
// ------------------------------------------------------------------------------ //
ExcludeClause: ast::AstNode<ast::Exclusion> = {
<lo:@L> "EXCLUDE" <items:CommaSepPlus<ExcludePath>> <hi:@R> => state.node(ast::Exclusion {items}, lo..hi),
}
// ------------------------------------------------------------------------------ //
// FROM //
// ------------------------------------------------------------------------------ //
FromClause: ast::AstNode<ast::FromClause> = {
<lo:@L> "FROM" <mut froms:(<TableReference> "," "LATERAL"?)*> <last:TableReference> <hi:@R> => {
let total: Location<BytePosition> = Location::from(lo.into()..hi.into());
// use `reduce` to process the comma-seperated `TableReference`s
// as left-associative `CROSS JOIN`s
froms.push(last);
let source = froms.into_iter()
.reduce(|lfrom, rfrom| {
let start_id = match &lfrom {
ast::FromSource::FromLet(node) => node.id,
ast::FromSource::Join(node) => node.id,
};
let end_id = match &rfrom {
ast::FromSource::FromLet(node) => node.id,
ast::FromSource::Join(node) => node.id,
};
let start = state.locations.get(&start_id).unwrap_or(&total).start.0.clone();
let end = state.locations.get(&end_id).unwrap_or(&total).end.0.clone();
let range = start..end;
let join = state.node(ast::Join {
kind: ast::JoinKind::Cross,
left: Box::new(lfrom),
right: Box::new(rfrom),
predicate: None
}, range);
ast::FromSource::Join( join )
})
.unwrap(); // safe, because we know there's at least 1 input
state.node(ast::FromClause{source}, lo..hi)
}
}
TableReference: ast::FromSource = {
<TableNonJoin>,
<TableJoined>,
}
TableNonJoin: ast::FromSource = {
<lo:@L> <t:TableBaseReference> <hi:@R> =>ast::FromSource::FromLet( t ),
<lo:@L> <t:TableUnpivot> <hi:@R> => ast::FromSource::FromLet( t ),
}
#[inline]
TableBaseReference: ast::AstNode<ast::FromLet> = {
<lo:@L> <e:ExprQuery> <as_alias:AsIdent?> <at_alias:AtIdent?> <by_alias:ByIdent?> <hi:@R> => {
state.node(ast::FromLet {
expr: e,
kind: ast::FromLetKind::Scan,
as_alias,
at_alias,
by_alias
}, lo..hi)
},
<GraphTable>,
}
// 7.1
GraphTable: ast::AstNode<ast::FromLet> = {
<lo:@L> "GRAPH_TABLE" <e:ParenGraphMatch> <hi:@R> => {
state.node(ast::FromLet {
expr: Box::new(e),
kind: ast::FromLetKind::GraphTable,
as_alias: None,
at_alias: None,
by_alias: None,
}, lo..hi)
},
}
ParenGraphMatch: ast::Expr = {
"(" <GraphMatch> ")",
}
GraphMatch: ast::Expr = {
<lo:@L> <expr:ExprQuery> "MATCH" <pattern:GraphPattern> <shape:GraphTableShape> <hi:@R>
=> ast::Expr::GraphMatch(Box::new(state.node(ast::GraphMatch{expr, pattern, shape}, lo..hi))),
}
GraphTableShape: ast::GraphTableShape = {
<rows: GraphTableRowsClause?> <cols: GraphTableColumnsClause?> <export: GraphTableExportClause?>
=> ast::GraphTableShape{rows, cols, export}
}
GraphTableRowsClause: ast::AstNode<ast::GraphTableRows> = {
<lo:@L> "ONE" "ROW" "PER" "MATCH" <hi:@R> => {
state.node(ast::GraphTableRows::OneRowPerMatch, lo..hi)
},
<lo:@L> "ONE" "ROW" "PER" <syn:GraphVertexSynonym>
"(" <v:GraphVertexVariable> ")"
<in_paths:GraphInPathsClause?> <hi:@R> => {
state.node(ast::GraphTableRows::OneRowPerVertex{v, in_paths}, lo..hi)
},
<lo:@L> "ONE" "ROW" "PER" "STEP"
"(" <v1:GraphVertexVariable> "," <e:GraphEdgeVariable> "," <v2:GraphVertexVariable> ")"
<in_paths:GraphInPathsClause?> <hi:@R> => {
state.node(ast::GraphTableRows::OneRowPerStep{v1, e, v2, in_paths}, lo..hi)
},
}
GraphInPathsClause: Vec<ast::SymbolPrimitive> = {
"IN" "(" <CommaSepPlus<GraphPathVariable>> ")"
}
GraphTableColumnsClause: ast::AstNode<ast::GraphTableColumns> = {
<lo:@L> "COLUMNS" "(" <columns:CommaSepPlus<GraphTableColumnDef>> ")" <hi:@R> => {
state.node(ast::GraphTableColumns{columns}, lo..hi)
}
}
GraphTableColumnDef: ast::GraphTableColumnDef = {
<v:ExprQuery> <as_ident:("AS" <SymbolPrimitive>)?> => ast::GraphTableColumnDef::Expr(v, as_ident),
//<var:GraphElementVariable> "." "*" => ast::GraphTableColumnDef::AllProperties(var),
}
GraphTableExportClause: ast::AstNode<ast::GraphTableExport> = {
<lo:@L> "EXPORT" "ALL" "SINGLETONS" <except:GraphTableExportExcept?> <hi:@R>
=> state.node(ast::GraphTableExport::AllSingletons{except}, lo..hi),
<lo:@L> "EXPORT" "SINGLETONS" "(" <exports:CommaSepPlus<GraphPatternVariable>> ")" <hi:@R>
=> state.node(ast::GraphTableExport::Singletons{exports}, lo..hi),
<lo:@L> "EXPORT" "NO" "SINGLETONS" <hi:@R> => state.node(ast::GraphTableExport::NoSingletons, lo..hi),
}
#[inline]
GraphTableExportExcept: Vec<ast::SymbolPrimitive> = {
"EXCEPT" "(" <CommaSepPlus<GraphPatternVariable>> ")",
}
#[inline]
GraphReference = <GraphName>;
// 10.4
GraphPattern: ast::AstNode<ast::GraphPattern> = {
<lo:@L> <match_mode:GraphMatchMode?> <patterns:CommaSepPlus<GraphPathPattern>> <keep:GraphKeepClause?> <where_clause:GraphPatternWhereClause?> <hi:@R> => {
state.node(ast::GraphPattern{match_mode, patterns, keep, where_clause}, lo..hi)
},
}
GraphMatchMode: ast::GraphMatchMode = {
"REPEATABLE" "ELEMENT" "BINDINGS"? => ast::GraphMatchMode::RepeatableElements,
"REPEATABLE" "ELEMENTS" => ast::GraphMatchMode::RepeatableElements,
"DIFFERENT" <GraphEdgeSynonym> "BINDINGS"? => ast::GraphMatchMode::DifferentEdges,
"DIFFERENT" <GraphEdgesSynonym> => ast::GraphMatchMode::DifferentEdges,
}
GraphPathPattern: ast::AstNode<ast::GraphPathPattern> = {
<lo:@L> <variable:GraphPathVariableDecl?> <prefix:GraphPathPatternPrefix?> <path:GraphPathPatternExpr> <hi:@R>
=> state.node(ast::GraphPathPattern{variable, prefix, path}, lo..hi),
}
GraphPathVariableDecl: ast::SymbolPrimitive = {
<GraphPathVariable> "="
}
GraphKeepClause: ast::GraphPathPrefix = {
"KEEP" <GraphPathPatternPrefix>,
}
GraphPatternWhereClause: Box<ast::Expr> = {
"WHERE" <ExprQuery>,
}
// 10.5
GraphPathPatternPrefix: ast::GraphPathPrefix = {
<lo:@L> <mode:GraphPathModePrefix> <hi:@R> => ast::GraphPathPrefix::Mode(mode),
<lo:@L> <search:GraphPathSearchPrefix> <hi:@R> => ast::GraphPathPrefix::Search(search.0, search.1),
}
GraphPathModePrefix: ast::GraphPathMode = {
<GraphPathMode>,
<GraphPathMode> "PATH",
<GraphPathMode> "PATHS",
}
#[inline]
GraphPathMode: ast::GraphPathMode = {
"WALK" => ast::GraphPathMode::Walk,
"TRAIL" => ast::GraphPathMode::Trail,
"SIMPLE" => ast::GraphPathMode::Simple,
"ACYCLIC" => ast::GraphPathMode::Acyclic,
}
#[inline]
GraphPathSearchPrefix: (ast::GraphPathSearchPrefix, Option<ast::GraphPathMode>) = {
// TODO handle invalid number parse
<lo:@L> "ALL" <mode:GraphPathModePrefix?> <hi:@R>
=> (ast::GraphPathSearchPrefix::All, mode),
<lo:@L> "ANY" <mode:GraphPathModePrefix?> <hi:@R>
=> (ast::GraphPathSearchPrefix::Any, mode),
<lo:@L> "ANY" <k:"Int"> <mode:GraphPathModePrefix?> <hi:@R>
=> (ast::GraphPathSearchPrefix::AnyK(std::num::NonZeroU32::new(k.parse().unwrap()).unwrap()), mode),
<lo:@L> "ALL" "SHORTEST" <mode:GraphPathModePrefix?> <hi:@R>
=> (ast::GraphPathSearchPrefix::AllShortest, mode),
<lo:@L> "ANY" "SHORTEST" <mode:GraphPathModePrefix?> <hi:@R>
=> (ast::GraphPathSearchPrefix::AnyShortest, mode),
<lo:@L> "SHORTEST" <k:"Int"> <mode:GraphPathModePrefix?> <hi:@R>
=> (ast::GraphPathSearchPrefix::ShortestK(std::num::NonZeroU32::new(k.parse().unwrap()).unwrap()), mode),
<lo:@L> "SHORTEST" <k:"Int"?> <mode:GraphPathModePrefix?> "GROUP" <hi:@R>
=> (ast::GraphPathSearchPrefix::ShortestKGroup(k.and_then(|n| std::num::NonZeroU32::new(n.parse().unwrap()))), mode),
<lo:@L> "SHORTEST" <k:"Int"?> <mode:GraphPathModePrefix?> "GROUPS" <hi:@R>
=> (ast::GraphPathSearchPrefix::ShortestKGroup(k.and_then(|n| std::num::NonZeroU32::new(n.parse().unwrap()))), mode),
}
// 10.6
GraphPathPatternExpr: ast::AstNode<ast::GraphMatchPathPattern> = {
<GraphPathTerm>,
<lo:@L> <terms: PuncSep2Plus<"|", GraphPathTerm>> <hi:@R>
=> state.node(ast::GraphMatchPathPattern::Union(terms), lo..hi),
<lo:@L> <terms: PuncSep2Plus<"|+|", GraphPathTerm>> <hi:@R>
=> state.node(ast::GraphMatchPathPattern::Multiset(terms), lo..hi),
}
GraphPathTerm: ast::AstNode<ast::GraphMatchPathPattern> = {
<GraphPathFactor>,
<lo:@L> <term:GraphPathTerm> <factor:GraphPathFactor> <hi:@R> => {
let path = if let ast::GraphMatchPathPattern::Path(mut path) = term.node {
path.push(factor);
path
} else {
vec![term, factor]
};
state.node(ast::GraphMatchPathPattern::Path(path), lo..hi)
}
}
GraphPathFactor: ast::AstNode<ast::GraphMatchPathPattern> = {
<GraphPathPrimary>,
<lo:@L> <p:GraphPathPrimary> <quant:GraphPatternQuantifier> <hi:@R>
=> state.node(ast::GraphMatchPathPattern::Quantified(ast::GraphMatchPathPatternQuantified{path: Box::new(p), quant}), lo..hi),
<lo:@L> <p:GraphPathPrimary> "?" <hi:@R>
=> state.node(ast::GraphMatchPathPattern::Questioned(Box::new(p)), lo..hi),
}
GraphPathPrimary: ast::AstNode<ast::GraphMatchPathPattern> = {
<lo:@L> <pat:GraphVertexPattern> <hi:@R> => state.node(ast::GraphMatchPathPattern::Node(pat), lo..hi),
<lo:@L> <pat:GraphEdgePattern> <hi:@R> => state.node(ast::GraphMatchPathPattern::Edge(pat), lo..hi),
<lo:@L> <pat:GraphParenthesizedPathPatternExpr> <hi:@R> => state.node(ast::GraphMatchPathPattern::Sub(Box::new(pat)), lo..hi),
<lo:@L> <pat:GraphSimplifiedPathPatternExpression> <hi:@R> => state.node(ast::GraphMatchPathPattern::Simplified(pat), lo..hi),
}
GraphVertexPattern: ast::AstNode<ast::GraphMatchNode> = {
<lo:@L> "(" <pat:GraphElementPatternFiller> ")" <hi:@R> => {
let ast::GraphMatchElement{variable, label, where_clause} = pat;
state.node(ast::GraphMatchNode {variable, label, where_clause}, lo..hi)
}
}
GraphElementPatternFiller: ast::GraphMatchElement = {
<lo:@L> <variable:GraphElementVariableDecl?>
<label:GraphIsLabelExpression?>
<where_clause:GraphElementPatternWhereClause?> <hi:@R> => {
ast::GraphMatchElement {variable, label, where_clause}
}
}
#[inline]
GraphElementVariableDecl = <GraphElementVariable>;
GraphIsLabelExpression: ast::AstNode<ast::GraphMatchLabel> = {
<lo:@L> ":" <e:GraphLabelExpr> <hi:@R> => state.node(e.node, lo..hi),
<lo:@L> "IS" <e:GraphLabelExpr> <hi:@R> => state.node(e.node, lo..hi),
}
GraphElementPatternWhereClause: Box<ast::Expr> = {
"WHERE" <ExprQuery>
}
GraphEdgePattern: ast::AstNode<ast::GraphMatchEdge> = {
<GraphFullEdgePattern>,
<GraphAbbreviatedEdgePattern>,
}
GraphFullEdgePattern: ast::AstNode<ast::GraphMatchEdge> = {
<lo:@L> "<-[" <pat:GraphEdgePatternFiller> "]-" <hi:@R>
=> state.node(ast::GraphMatchEdge {direction: ast::GraphMatchDirection::Left, ..pat}, lo..hi),
<lo:@L> "~[" <pat:GraphEdgePatternFiller> "]~" <hi:@R>
=> state.node(ast::GraphMatchEdge {direction: ast::GraphMatchDirection::Undirected, ..pat}, lo..hi),
<lo:@L> "-[" <pat:GraphEdgePatternFiller> "]->" <hi:@R>
=> state.node(ast::GraphMatchEdge {direction: ast::GraphMatchDirection::Right, ..pat}, lo..hi),
<lo:@L> "<~[" <pat:GraphEdgePatternFiller> "]~" <hi:@R>
=> state.node(ast::GraphMatchEdge {direction: ast::GraphMatchDirection::LeftOrUndirected, ..pat}, lo..hi),
<lo:@L> "~[" <pat:GraphEdgePatternFiller> "]~>" <hi:@R>
=> state.node(ast::GraphMatchEdge {direction: ast::GraphMatchDirection::UndirectedOrRight, ..pat}, lo..hi),
<lo:@L> "<-[" <pat:GraphEdgePatternFiller> "]->" <hi:@R>
=> state.node(ast::GraphMatchEdge {direction: ast::GraphMatchDirection::LeftOrRight, ..pat}, lo..hi),
<lo:@L> "-[" <pat:GraphEdgePatternFiller> "]-" <hi:@R>
=> state.node(ast::GraphMatchEdge {direction: ast::GraphMatchDirection::LeftOrUndirectedOrRight, ..pat}, lo..hi),
}
GraphEdgePatternFiller: ast::GraphMatchEdge = {
<pat:GraphElementPatternFiller> => {
let ast::GraphMatchElement{variable, label, where_clause} = pat;
ast::GraphMatchEdge {direction: ast::GraphMatchDirection::Undirected, variable, label, where_clause}
}
}
GraphAbbreviatedEdgePattern: ast::AstNode<ast::GraphMatchEdge> = {
<lo:@L> <direction:GraphAbbreviatedEdgePatternEdge> <hi:@R> => {
state.node(ast::GraphMatchEdge {
direction,
variable: None,
label: None,
where_clause: None,
}, lo..hi)
}
}
GraphAbbreviatedEdgePatternEdge: ast::GraphMatchDirection = {
"<-" => ast::GraphMatchDirection::Left,
"~" => ast::GraphMatchDirection::Undirected,
"->" => ast::GraphMatchDirection::Right,
"<~" => ast::GraphMatchDirection::LeftOrUndirected,
"~>" => ast::GraphMatchDirection::UndirectedOrRight,
"<->" => ast::GraphMatchDirection::LeftOrRight,
"-" => ast::GraphMatchDirection::LeftOrUndirectedOrRight,
}
GraphParenthesizedPathPatternExpr: ast::AstNode<ast::GraphPathSubPattern> = {
<lo:@L> "(" <variable:GraphSubPathVariableDecl?>
<mode:GraphPathModePrefix?>
<path:GraphPathPatternExpr>
<where_clause:GraphParenthesizedPathPatternWhereClause?>
")" <hi:@R> => {
state.node(ast::GraphPathSubPattern {
variable,
mode,
path,
where_clause,
}, lo..hi)
}
}
GraphSubPathVariableDecl: ast::SymbolPrimitive = {
<GraphSubPathVariable> "=",
}
GraphParenthesizedPathPatternWhereClause: Box<ast::Expr> = {
"WHERE" <ExprQuery>,
}
// 10.7
#[inline]
GraphPatternQuantifier: ast::AstNode<ast::GraphMatchQuantifier> = {
<lo:@L> "*" <hi:@R> => state.node(ast::GraphMatchQuantifier{ lower:0, upper:None }, lo..hi),
<lo:@L> "+" <hi:@R> => state.node(ast::GraphMatchQuantifier{ lower:1, upper:None }, lo..hi),
<lo:@L> "{" <k:"Int"> "}" <hi:@R> => {
// TODO error on invalid literal
let k = std::num::NonZeroU32::new(k.parse().unwrap()).unwrap();
state.node(ast::GraphMatchQuantifier{ lower: k.into(), upper: Some(k) }, lo..hi)
},
<lo:@L> "{" <l:"Int"?> "," <u:"Int"?> "}" <hi:@R> => {
// TODO error on invalid literal
let lower = if let Some(l) = l {
l.parse().unwrap()
} else {
0
};
let upper = if let Some(n) = u {
Some(std::num::NonZeroU32::new(n.parse().unwrap()).expect("non-zero upper bound"))
} else {
None
};
state.node(ast::GraphMatchQuantifier{ lower, upper }, lo..hi)
},
}
// 10.8
GraphLabelExpr: ast::AstNode<ast::GraphMatchLabel> = {
<GraphLabelTerm>,
<GraphLabelDisjunction>,
}
GraphLabelDisjunction: ast::AstNode<ast::GraphMatchLabel> = {
<lo:@L> <l:GraphLabelExpr> "|" <r:GraphLabelTerm> <hi:@R> => {
let d = if let ast::GraphMatchLabel::Disjunction(mut d) = l.node {
d.push(r);
d
} else {
vec![l,r]
};
state.node(ast::GraphMatchLabel::Disjunction(d), lo..hi)
},
}
GraphLabelTerm: ast::AstNode<ast::GraphMatchLabel> = {
<GraphLabelFactor>,
<GraphLabelConjunction>,
}
GraphLabelConjunction: ast::AstNode<ast::GraphMatchLabel> = {
<lo:@L> <l:GraphLabelTerm> "&" <r:GraphLabelFactor> <hi:@R> => {
let c = if let ast::GraphMatchLabel::Conjunction(mut c) = l.node {
c.push(r);
c
} else {
vec![l,r]
};
state.node(ast::GraphMatchLabel::Conjunction(c), lo..hi)
},
}
GraphLabelFactor: ast::AstNode<ast::GraphMatchLabel> = {
<GraphLabelPrimary>,
<lo:@L> "!" <l:GraphLabelPrimary> <hi:@R> => state.node(ast::GraphMatchLabel::Negated(Box::new(l)), lo..hi),
}
#[inline]
GraphLabelPrimary: ast::AstNode<ast::GraphMatchLabel> = {
<lo:@L> <l:GraphLabelName> <hi:@R> => state.node(ast::GraphMatchLabel::Name(l), lo..hi),
<lo:@L> "%" <hi:@R> => state.node(ast::GraphMatchLabel::Wildcard, lo..hi),
<lo:@L> "(" <e:GraphLabelExpr> ")" <hi:@R> => state.node(e.node, lo..hi),
}
// 10.9
GraphSimplifiedPathPatternExpression: ast::AstNode<ast::GraphMatchSimplified> = {
<lo:@L> "<-/" <pattern:GraphSimplifiedContents> "/-" <hi:@R> => state.node(ast::GraphMatchSimplified{dir: ast::GraphMatchDirection::Left, pattern}, lo..hi),
<lo:@L> "~/" <pattern:GraphSimplifiedContents> "/~" <hi:@R> => state.node(ast::GraphMatchSimplified{dir: ast::GraphMatchDirection::Undirected, pattern}, lo..hi),
<lo:@L> "-/" <pattern:GraphSimplifiedContents> "/->" <hi:@R> => state.node(ast::GraphMatchSimplified{dir: ast::GraphMatchDirection::Right, pattern}, lo..hi),
<lo:@L> "<~/" <pattern:GraphSimplifiedContents> "/~" <hi:@R> => state.node(ast::GraphMatchSimplified{dir: ast::GraphMatchDirection::LeftOrUndirected, pattern}, lo..hi),
<lo:@L> "~/" <pattern:GraphSimplifiedContents> "/~>" <hi:@R> => state.node(ast::GraphMatchSimplified{dir: ast::GraphMatchDirection::UndirectedOrRight, pattern}, lo..hi),
<lo:@L> "<-/" <pattern:GraphSimplifiedContents> "/->" <hi:@R> => state.node(ast::GraphMatchSimplified{dir: ast::GraphMatchDirection::LeftOrRight, pattern}, lo..hi),
<lo:@L> "-/" <pattern:GraphSimplifiedContents> "/-" <hi:@R> => state.node(ast::GraphMatchSimplified{dir: ast::GraphMatchDirection::LeftOrUndirectedOrRight, pattern}, lo..hi),
}
GraphSimplifiedContents: ast::AstNode<ast::GraphMatchSimplifiedPattern> = {
<GraphSimplifiedTerm>,
<lo:@L> <terms: PuncSep2Plus<"|", GraphSimplifiedTerm>> <hi:@R> => state.node(ast::GraphMatchSimplifiedPattern::Union(terms), lo..hi),
<lo:@L> <terms: PuncSep2Plus<"|+|", GraphSimplifiedTerm>> <hi:@R> => state.node(ast::GraphMatchSimplifiedPattern::Multiset(terms), lo..hi),
}
GraphSimplifiedTerm: ast::AstNode<ast::GraphMatchSimplifiedPattern> = {
<lo:@L> <t:GraphSimplifiedTerm?> <f:GraphSimplifiedFactorLow> <hi:@R> => {
if t.is_none() {
return f;
}
let t = t.unwrap();
let path = if let ast::GraphMatchSimplifiedPattern::Path(mut v) = t.node {
v.push(f);
v
} else {
vec![t, f]
};
state.node(ast::GraphMatchSimplifiedPattern::Path(path), lo..hi)
}
}
GraphSimplifiedFactorLow: ast::AstNode<ast::GraphMatchSimplifiedPattern> = {
<GraphSimplifiedFactorHigh>,
<GraphSimplifiedConjunction>,
}
#[inline]
GraphSimplifiedConjunction: ast::AstNode<ast::GraphMatchSimplifiedPattern> = {
<lo:@L> <l:GraphSimplifiedFactorLow> "&" <r:GraphSimplifiedFactorHigh> <hi:@R> => {
let conj = if let ast::GraphMatchSimplifiedPattern::Conjunction(mut conj) = l.node {
conj.push(r);
conj
} else {
vec![l,r]
};
state.node(ast::GraphMatchSimplifiedPattern::Conjunction(conj), lo..hi)
}
}
GraphSimplifiedFactorHigh: ast::AstNode<ast::GraphMatchSimplifiedPattern> = {
<GraphSimplifiedTertiary>,
<lo:@L> <s:GraphSimplifiedTertiary> <quant:GraphPatternQuantifier> <hi:@R> =>
state.node(ast::GraphMatchSimplifiedPattern::Quantified(ast::GraphMatchSimplifiedPatternQuantified{path: Box::new(s), quant}), lo..hi),
<lo:@L> <s:GraphSimplifiedTertiary> "?" <hi:@R> => state.node(ast::GraphMatchSimplifiedPattern::Questioned(Box::new(s)), lo..hi),
}
GraphSimplifiedTertiary: ast::AstNode<ast::GraphMatchSimplifiedPattern> = {
<GraphSimplifiedSecondary>,
<lo:@L> "<" <s:GraphSimplifiedSecondary> <hi:@R>
=> state.node(ast::GraphMatchSimplifiedPattern::Direction(ast::GraphMatchSimplifiedPatternDirected{dir: ast::GraphMatchDirection::Left, path: Box::new(s)}), lo..hi),
<lo:@L> "~" <s:GraphSimplifiedSecondary> <hi:@R>
=> state.node(ast::GraphMatchSimplifiedPattern::Direction(ast::GraphMatchSimplifiedPatternDirected{dir: ast::GraphMatchDirection::Undirected, path: Box::new(s)}), lo..hi),
<lo:@L> <s:GraphSimplifiedSecondary> ">" <hi:@R>
=> state.node(ast::GraphMatchSimplifiedPattern::Direction(ast::GraphMatchSimplifiedPatternDirected{dir: ast::GraphMatchDirection::Right, path: Box::new(s)}), lo..hi),
<lo:@L> "<~" <s:GraphSimplifiedSecondary> <hi:@R>
=> state.node(ast::GraphMatchSimplifiedPattern::Direction(ast::GraphMatchSimplifiedPatternDirected{dir: ast::GraphMatchDirection::LeftOrUndirected, path: Box::new(s)}), lo..hi),
<lo:@L> "~" <s:GraphSimplifiedSecondary> ">" <hi:@R>
=> state.node(ast::GraphMatchSimplifiedPattern::Direction(ast::GraphMatchSimplifiedPatternDirected{dir: ast::GraphMatchDirection::UndirectedOrRight, path: Box::new(s)}), lo..hi),
<lo:@L> "<" <s:GraphSimplifiedSecondary> ">" <hi:@R>
=> state.node(ast::GraphMatchSimplifiedPattern::Direction(ast::GraphMatchSimplifiedPatternDirected{dir: ast::GraphMatchDirection::LeftOrRight, path: Box::new(s)}), lo..hi),
<lo:@L> "-" <s:GraphSimplifiedSecondary> <hi:@R>
=> state.node(ast::GraphMatchSimplifiedPattern::Direction(ast::GraphMatchSimplifiedPatternDirected{dir: ast::GraphMatchDirection::LeftOrUndirectedOrRight, path: Box::new(s)}), lo..hi),
}
GraphSimplifiedSecondary: ast::AstNode<ast::GraphMatchSimplifiedPattern> = {
<lo:@L> <negated:"!"?> <simp:GraphSimplifiedPrimary> <hi:@R> => {
if negated.is_some() {
state.node(ast::GraphMatchSimplifiedPattern::Negated(Box::new(simp)), lo..hi)
} else {
simp
}
}
}
#[inline]
GraphSimplifiedPrimary: ast::AstNode<ast::GraphMatchSimplifiedPattern> = {
<lo:@L> <l:GraphLabelName> <hi:@R> => state.node(ast::GraphMatchSimplifiedPattern::Label(l), lo..hi),
<lo:@L> "(" <path:GraphSimplifiedContents> ")" <hi:@R> => state.node(ast::GraphMatchSimplifiedPattern::Sub(Box::new(path)), lo..hi),
}
// 10.10
#[inline]
GraphElementReference = <GraphElementVariable>;
// 10.11
#[inline]
GraphPathReference = <GraphPathVariable>;
// 5.3
#[inline]
GraphName = <Identifier>;
#[inline]
GraphVertexVariable = <GraphElementVariable>;
#[inline]
GraphEdgeVariable = <GraphElementVariable>;
#[inline]
GraphElementVariable = <Identifier>;
#[inline]
GraphLabelName = <Identifier>;
#[inline]
GraphPathOrSubPathVariable = <SymbolPrimitive>;
#[inline]
GraphPathVariable = <SymbolPrimitive>;
#[inline]
GraphSubPathVariable = <SymbolPrimitive>;
#[inline]
GraphPatternVariable = <Identifier>;
#[inline]
TableUnpivot: ast::AstNode<ast::FromLet> = {
<lo:@L> "UNPIVOT" <e:ExprQuery> <as_ident:AsIdent?> <at_ident:AtIdent?> <hi:@R> => {
state.node(ast::FromLet {
expr: e,
kind: ast::FromLetKind::Unpivot,
as_alias: as_ident,
at_alias: at_ident,
by_alias: None,
}, lo..hi)
}
}
TableJoined: ast::FromSource = {
<TableCrossJoin>,
<TableQualifiedJoin>,
"(" <TableJoined> ")",
}
#[inline]
TableCrossJoin: ast::FromSource = {
// Note the `TableReference` on the lhs and the `JoinRhs` on the rhs of the `JOIN`.
// This is to prevent ambiguity in the grammar and effectively treats `JOIN` like
// a left-associative operator
<lo:@L> <ltable:TableReference> <j:JoinType?> "CROSS" KW_JOIN <rtable:JoinRhs> <hi:@R> => {
let kind = match j {
Some(j) => j,
None => ast::JoinKind::Cross
};
let join = state.node(ast::Join {
kind,
left: Box::new(ltable),
right: Box::new(rtable),
predicate: None
}, lo..hi);
ast::FromSource::Join( join )
}
}
#[inline]
TableQualifiedJoin: ast::FromSource = {
// Note the `TableReference` on the lhs and the `JoinRhs` on the rhs of the `JOIN`.
// This is to prevent ambiguity in the grammar and effectively treats `JOIN` like
// a left-associative operator
<lo:@L> <ltable:TableReference> <j:JoinType?> KW_JOIN <rtable:JoinRhs> <on:JoinSpec> <hi:@R> => {
let join = state.node(ast::Join {
kind: j.unwrap_or(ast::JoinKind::Inner),
left: Box::new(ltable),
right: Box::new(rtable),
predicate: Some(on),
}, lo..hi);
ast::FromSource::Join( join )
},
// Note the `TableReference` on the lhs and the `JoinRhs` on the rhs of the `JOIN`.
// This is to prevent ambiguity in the grammar and effectively treats `JOIN` like
// a left-associative operator
<lo:@L> <ltable:TableReference> <spec:JoinSpecNatural> <j:JoinType?> KW_JOIN <rtable:JoinRhs> <hi:@R> => {
let join = state.node(ast::Join {
kind: j.unwrap_or(ast::JoinKind::Inner),
left: Box::new(ltable),
right: Box::new(rtable),
predicate: Some(spec)
}, lo..hi);
ast::FromSource::Join( join )
},
}
#[inline]
KW_JOIN: () = {
"JOIN",
"LATERAL",
"JOIN" "LATERAL",
}
#[inline]
JoinRhs: ast::FromSource = {
<TableNonJoin>,
"(" <TableJoined> ")"
}
#[inline]
JoinSpecNatural: ast::AstNode<ast::JoinSpec> = {
<lo:@L> "NATURAL" <hi:@R> => state.node(ast::JoinSpec::Natural, lo..hi)
}
#[inline]
JoinType: ast::JoinKind = {
"INNER" => ast::JoinKind::Inner,
"LEFT" "OUTER"? => ast::JoinKind::Left,
"RIGHT" "OUTER"? => ast::JoinKind::Right,
"FULL" "OUTER"? => ast::JoinKind::Full,
}
#[inline]
JoinSpec: ast::AstNode<ast::JoinSpec> = {
<lo:@L> "ON" <e:ExprQuery> <hi:@R> => state.node(ast::JoinSpec::On(e), lo..hi),
<lo:@L> "USING" "(" <paths:CommaSepPlus<PathExpr>> ")" <hi:@R> => state.node(ast::JoinSpec::Using( paths ), lo..hi),
}
// ------------------------------------------------------------------------------ //
// WHERE //
// ------------------------------------------------------------------------------ //
WhereClause: Box<ast::AstNode<ast::WhereClause>> = {
<lo:@L> "WHERE" <expr:ExprQuery> <hi:@R> => {
Box::new(state.node(ast::WhereClause{expr}, lo..hi))
}
}
// ------------------------------------------------------------------------------ //
// GROUP BY //
// ------------------------------------------------------------------------------ //
GroupClause: Box<ast::AstNode<ast::GroupByExpr>> = {
<lo:@L> "GROUP" <strategy: GroupStrategy> <keys:GroupByKeys?> <group_as_alias:GroupAlias?> <hi:@R> => {
let keys = keys.unwrap_or_default();
Box::new(state.node(ast::GroupByExpr{
strategy,
keys,
group_as_alias,
}, lo..hi))
}
}
#[inline]
GroupStrategy: Option<ast::GroupingStrategy> = {
"ALL" => Some(ast::GroupingStrategy::GroupFull),
<partial:"PARTIAL"?> => partial.map(|_| ast::GroupingStrategy::GroupPartial),
}
#[inline]
GroupByKeys: Vec<ast::AstNode<ast::GroupKey>> = {
"BY" <CommaSepPlus<GroupKey>>
}
#[inline]
GroupKey: ast::AstNode<ast::GroupKey> = {
<lo:@L> <expr:ExprQuery> <hi:@R>
=> state.node(ast::GroupKey{ expr, as_alias: None }, lo..hi),
<lo:@L> <expr:ExprQuery> "AS" <as_alias:SymbolPrimitive> <hi:@R>
=> state.node(ast::GroupKey{ expr, as_alias: Some(as_alias) }, lo..hi),
}
#[inline]
GroupAlias: ast::SymbolPrimitive = {
"GROUP" "AS" <SymbolPrimitive>
}
// ------------------------------------------------------------------------------ //
// HAVING //
// ------------------------------------------------------------------------------ //
HavingClause: Box<ast::AstNode<ast::HavingClause>> = {
<lo:@L> "HAVING" <expr:ExprQuery> <hi:@R> => {
Box::new(state.node(ast::HavingClause{expr}, lo..hi))
}
}
// ------------------------------------------------------------------------------ //
// ORDER BY //
// ------------------------------------------------------------------------------ //
OrderByClause: Box<ast::AstNode<ast::OrderByExpr>> = {
<lo:@L> "ORDER" "BY" "PRESERVE" <hi:@R> => Box::new( state.node(ast::OrderByExpr{ sort_specs: vec![] }, lo..hi) ),
<lo:@L> "ORDER" "BY" <sort_specs: CommaSepPlus<OrderSortSpec>> <hi:@R> => Box::new( state.node(ast::OrderByExpr{ sort_specs }, lo..hi) ),
}
#[inline]
OrderSortSpec: ast::AstNode<ast::SortSpec> = {
<lo:@L> <expr:ExprQuery> <ordering_spec:BySpec?> <null_ordering_spec:ByNullSpec?> <hi:@R>
=> state.node(ast::SortSpec { expr, ordering_spec, null_ordering_spec }, lo..hi)
}
#[inline]
BySpec: ast::OrderingSpec = {
"ASC" => ast::OrderingSpec::Asc,
"DESC" => ast::OrderingSpec::Desc,
}
#[inline]
ByNullSpec: ast::NullOrderingSpec = {
"NULLS" "FIRST" => ast::NullOrderingSpec::First,
"NULLS" "LAST" => ast::NullOrderingSpec::Last,
}
// ------------------------------------------------------------------------------ //
// LIMIT / OFFSET //
// ------------------------------------------------------------------------------ //
LimitOffsetClause: Option<Box<ast::AstNode<ast::LimitOffsetClause>>> = {
<lo:@L> <limit:LimitClause?> <offset:OffsetByClause?> <hi:@R> => {
if limit.is_none() && offset.is_none() {
None
} else {
Some(Box::new(state.node(ast::LimitOffsetClause { limit, offset }, lo..hi)))
}
}
}
LimitClause: Box<ast::Expr> = { "LIMIT" <ExprQuery> }
OffsetByClause: Box<ast::Expr> = { "OFFSET" <ExprQuery> }
// ------------------------------------------------------------------------------ //
// //
// Expr Query //
// //
// ------------------------------------------------------------------------------ //
// Note: the `Expr` rules are formulated in a precise way to assure 'natural'
// interpretation of mathematical expressions in an LR grammar while attempting
// to stay in accordance with SQL interpretations.
//
// In particular:
// - in order to assure operator precedence, the lowest precedence operators
// (i.e., `OR`) are at the top of the recursion tree, and the highest
// precedence are at the bottom (i.e., `<PathExpr>`)
// - left-associative operators (i.e., '+', '-', '*', '/') are expressed as
// left-self-recursive rules, while right-associative operators
// (i.e., unary '-', '^) are expressed as right-self-recursive rules
//
// In the parsing tutorials and academic literature, you will often see the above
// structure in the 'classical expression grammar':
// Expr -> Expr '+' Factor | Expr '-' Factor | Factor
// Factor -> Factor '*' Term | Factor '/' Term | Term
// Term -> number
//
// Instead of trying to come up with other synonyms to Expr/Factor/Term, the rules
// here are named directly for their numeric level in the precedence table
// in the form ExprPrecedence<NN>
//
// PartiQL's precedence levels:
// |-------+-------------------+---------------+------------------------------------|
// | Level | Operator | Associativity | Description |
// |-------+-------------------+---------------+------------------------------------|
// | 1 | <Path Expression> | left | e.g., `field`, `binding.field[2]` |
// | 2 | <Function call> | left | e.g., `upper(field_reference)` |
// | 3 | + - | right | unary plus, unary minus |
// | 4 | ^ | left | exponentiation |
// | 5 | * / % | left | multiplication, division, modulo |
// | 6 | + - | left | addition, subtraction |
// | 7 | <other> | left | other operators, e.g., `||`
// | 8 | BETWEEN IN LIKE | | range/set/pattern compare |
// | 9 | < > <= >= | | comparison operators |
// | 10 | = <> != | | equality operators |
// | 11 | IS | | IS [NOT] NULL |
// | 12 | NOT | right | logical negate |
// | 13 | AND | left | logical conjunct |
// | 14 | OR | left | logical disjunct |
// |-------+-------------------+---------------+------------------------------------|
//
// See https://en.wikipedia.org/wiki/Order_of_operations#Programming_languages
// See https://en.wikipedia.org/wiki/Order_of_operations#Special_cases
ExprQuery: Box<ast::Expr> = {
<e:ExprQuerySynth> => e.data,
}
ExprQuerySynth: Synth<Box<ast::Expr>> = {
<e:ExprPrecedence15> => {
e.map_data(|e| Box::new(e))
}
}
ExprPrecedence15: Synth<ast::Expr> = {
<lo:@L> <l:ExprPrecedence15> "OR" <r:ExprPrecedence14> <hi:@R> =>
Synth::empty(ast::Expr::BinOp(
state.node(ast::BinOp {
kind: ast::BinOpKind::Or,
lhs: Box::new(l.data),
rhs: Box::new(r.data),
}, lo..hi)
)),
<ExprPrecedence14>,
}
ExprPrecedence14: Synth<ast::Expr> = {
<lo:@L> <l:ExprPrecedence14> "AND" <r:ExprPrecedence13> <hi:@R> =>
Synth::empty(ast::Expr::BinOp(
state.node(ast::BinOp {
kind: ast::BinOpKind::And,
lhs: Box::new(l.data),
rhs: Box::new(r.data),
}, lo..hi)
)),
<ExprPrecedence13>,
}
ExprPrecedence13: Synth<ast::Expr> = {
<lo:@L> "NOT" <r:ExprPrecedence13> <hi:@R> =>
Synth::empty(ast::Expr::UniOp(
state.node(ast::UniOp {
kind: ast::UniOpKind::Not,
expr: Box::new(r.data),
}, lo..hi)
)),
<ExprPrecedence12>,
}
ExprPrecedence12: Synth<ast::Expr> = {
<lo:@L> <l:ExprPrecedence12> "IS" <r:ExprPrecedence11> <hi:@R> =>
Synth::empty(ast::Expr::BinOp(
state.node(ast::BinOp {
kind: ast::BinOpKind::Is,
lhs: Box::new(l.data),
rhs: Box::new(r.data),
}, lo..hi)
)),
<lo:@L> <l:ExprPrecedence12> "IS" "NOT" <r:ExprPrecedence11> <hi:@R> => {
let is = ast::Expr::BinOp(
state.node(ast::BinOp {
kind: ast::BinOpKind::Is,
lhs: Box::new(l.data),
rhs: Box::new(r.data),
}, lo..hi)
);
Synth::empty(ast::Expr::UniOp(
state.node(ast::UniOp {
kind: ast::UniOpKind::Not,
expr: Box::new(is),
}, lo..hi)
))
},
<ExprPrecedence11>
}
ExprPrecedence11: Synth<ast::Expr> = {
<lo:@L> <l:ExprPrecedence11> "=" <r:ExprPrecedence10> <hi:@R> =>
Synth::empty(ast::Expr::BinOp(
state.node(ast::BinOp {
kind: ast::BinOpKind::Eq,
lhs: Box::new(l.data),
rhs: Box::new(r.data),
}, lo..hi)
)),
<lo:@L> <l:ExprPrecedence11> "!=" <r:ExprPrecedence10> <hi:@R> =>
Synth::empty(ast::Expr::BinOp(
state.node(ast::BinOp {
kind: ast::BinOpKind::Ne,
lhs: Box::new(l.data),
rhs: Box::new(r.data),
}, lo..hi)
)),
<lo:@L> <l:ExprPrecedence11> "<>" <r:ExprPrecedence10> <hi:@R> =>
Synth::empty(ast::Expr::BinOp(
state.node(ast::BinOp {
kind: ast::BinOpKind::Ne,
lhs: Box::new(l.data),
rhs: Box::new(r.data),
}, lo..hi)
)),
<ExprPrecedence10>,
}
ExprPrecedence10: Synth<ast::Expr> = {
<lo:@L> <l:ExprPrecedence09> "<" <r:ExprPrecedence09> <hi:@R> =>
Synth::empty(ast::Expr::BinOp(
state.node(ast::BinOp {
kind: ast::BinOpKind::Lt,
lhs: Box::new(l.data),
rhs: Box::new(r.data),
}, lo..hi)
)),
<lo:@L> <l:ExprPrecedence09> ">" <r:ExprPrecedence09> <hi:@R> =>
Synth::empty(ast::Expr::BinOp(
state.node(ast::BinOp {
kind: ast::BinOpKind::Gt,
lhs: Box::new(l.data),
rhs: Box::new(r.data),
}, lo..hi)
)),
<lo:@L> <l:ExprPrecedence09> "<=" <r:ExprPrecedence09> <hi:@R> =>
Synth::empty(ast::Expr::BinOp(
state.node(ast::BinOp {
kind: ast::BinOpKind::Lte,
lhs: Box::new(l.data),
rhs: Box::new(r.data),
}, lo..hi)
)),
<lo:@L> <l:ExprPrecedence09> ">=" <r:ExprPrecedence09> <hi:@R> =>
Synth::empty(ast::Expr::BinOp(
state.node(ast::BinOp {
kind: ast::BinOpKind::Gte,
lhs: Box::new(l.data),
rhs: Box::new(r.data),
}, lo..hi)
)),
<ExprPrecedence09>,
}
ExprPrecedence09: Synth<ast::Expr> = {
<lo:@L> <value:ExprPrecedence09> "BETWEEN" <from:ExprPrecedence08> "AND" <to:ExprPrecedence08> <hi:@R> =>
Synth::empty(ast::Expr::Between( state.node(ast::Between{ value: Box::new(value.data), from: Box::new(from.data), to: Box::new(to.data) }, lo..hi) )),
<lo:@L> <value:ExprPrecedence09> "NOT" "BETWEEN" <from:ExprPrecedence08> "AND" <to:ExprPrecedence08> <hi:@R> => {
let between = ast::Expr::Between( state.node(ast::Between{ value: Box::new(value.data), from: Box::new(from.data), to: Box::new(to.data) }, lo..hi) );
Synth::empty(ast::Expr::UniOp(
state.node(ast::UniOp {
kind: ast::UniOpKind::Not,
expr: Box::new(between),
}, lo..hi)
))
},
<lo:@L> <value:ExprPrecedence09> "LIKE" <pattern:ExprPrecedence08> <escape:LikeEscape?> <hi:@R> =>
Synth::empty(ast::Expr::Like( state.node(ast::Like{ value: Box::new(value.data), pattern: Box::new(pattern.data), escape }, lo..hi) )),
<lo:@L> <value:ExprPrecedence09> "NOT" "LIKE" <pattern:ExprPrecedence08> <escape:LikeEscape?> <hi:@R> => {
let like = ast::Expr::Like( state.node(ast::Like{ value: Box::new(value.data), pattern: Box::new(pattern.data), escape }, lo..hi) );
Synth::empty(ast::Expr::UniOp(
state.node(ast::UniOp {
kind: ast::UniOpKind::Not,
expr: Box::new(like),
}, lo..hi)
))
},
<lo:@L> <l:ExprPrecedence09> "IN" <r:ExprPrecedence08> <hi:@R> =>
Synth::empty(ast::Expr::In( state.node(ast::In{ lhs: Box::new(l.data), rhs: Box::new(r.data) }, lo..hi) )),
<lo:@L> <l:ExprPrecedence09> "NOT" "IN" <r:ExprPrecedence08> <hi:@R> => {
let in_expr = ast::Expr::In( state.node(ast::In{ lhs: Box::new(l.data), rhs: Box::new(r.data) }, lo..hi) );
Synth::empty(ast::Expr::UniOp(
state.node(ast::UniOp {
kind: ast::UniOpKind::Not,
expr: Box::new(in_expr),
}, lo..hi)
))
},
// PartiQL extension to treat `(<g>MATCH <patt>)` as an expression.
<gm:ParenGraphMatch> => Synth::empty(gm),
<ExprPrecedence08>,
}
#[inline]
LikeEscape: Box<ast::Expr> = {
"ESCAPE" <e:ExprPrecedence07> => Box::new(e.data)
}
ExprPrecedence08: Synth<ast::Expr> = {
<lo:@L> <l:ExprPrecedence08> "||" <r:ExprPrecedence07> <hi:@R> =>
Synth::empty(ast::Expr::BinOp(
state.node(ast::BinOp {
kind: ast::BinOpKind::Concat,
lhs: Box::new(l.data),
rhs: Box::new(r.data),
}, lo..hi)
)),
<ExprPrecedence07>,
}
ExprPrecedence07: Synth<ast::Expr> = {
<lo:@L> <l:ExprPrecedence07> "+" <r:ExprPrecedence06> <hi:@R> =>
Synth::empty(ast::Expr::BinOp(
state.node(ast::BinOp {
kind: ast::BinOpKind::Add,
lhs: Box::new(l.data),
rhs: Box::new(r.data),
}, lo..hi)
)),
<lo:@L> <l:ExprPrecedence07> "-" <r:ExprPrecedence06> <hi:@R> =>
Synth::empty(ast::Expr::BinOp(
state.node(ast::BinOp {
kind: ast::BinOpKind::Sub,
lhs: Box::new(l.data),
rhs: Box::new(r.data),
}, lo..hi)
)),
<ExprPrecedence06>,
}
ExprPrecedence06: Synth<ast::Expr> = {
<lo:@L> <l:ExprPrecedence06> "*" <r:ExprPrecedence05> <hi:@R> =>
Synth::empty(ast::Expr::BinOp(
state.node(ast::BinOp {
kind: ast::BinOpKind::Mul,
lhs: Box::new(l.data),
rhs: Box::new(r.data),
}, lo..hi)
)),
<lo:@L> <l:ExprPrecedence06> "/" <r:ExprPrecedence05> <hi:@R> =>
Synth::empty(ast::Expr::BinOp(
state.node(ast::BinOp {
kind: ast::BinOpKind::Div,
lhs: Box::new(l.data),
rhs: Box::new(r.data),
}, lo..hi)
)),
<lo:@L> <l:ExprPrecedence06> "%" <r:ExprPrecedence05> <hi:@R> =>
Synth::empty(ast::Expr::BinOp(
state.node(ast::BinOp {
kind: ast::BinOpKind::Mod,
lhs: Box::new(l.data),
rhs: Box::new(r.data),
}, lo..hi)
)),
<ExprPrecedence05>,
}
ExprPrecedence05: Synth<ast::Expr> = {
<lo:@L> <l:ExprPrecedence05> "^" <r:ExprPrecedence04> <hi:@R> =>
Synth::empty(ast::Expr::BinOp(
state.node(ast::BinOp {
kind: ast::BinOpKind::Exp,
lhs: Box::new(l.data),
rhs: Box::new(r.data),
}, lo..hi)
)),
<ExprPrecedence04>,
}
ExprPrecedence04: Synth<ast::Expr> = {
<lo:@L> "+" <r:ExprPrecedence04> <hi:@R> =>
Synth::empty(ast::Expr::UniOp(
state.node(ast::UniOp {
kind: ast::UniOpKind::Pos,
expr: Box::new(r.data),
}, lo..hi)
)),
<lo:@L> "-" <r:ExprPrecedence04> <hi:@R> =>
Synth::empty(ast::Expr::UniOp(
state.node(ast::UniOp {
kind: ast::UniOpKind::Neg,
expr: Box::new(r.data),
}, lo..hi)
)),
<ExprPrecedence03>,
}
#[inline]
ExprPrecedence03: Synth<ast::Expr> = {
<casexpr:CaseExpr> => Synth::empty(ast::Expr::Case(casexpr)),
<ExprPrecedence02>,
}
ExprPrecedence02: Synth<ast::Expr> = {
<lo:@L> <expr:PathExpr> <hi:@R> => Synth::empty(ast::Expr::Path( state.node(expr, lo..hi) )),
<ExprPrecedence01>,
}
PathExpr: ast::Path = {
<l:ExprPrecedence01> <s:PathSteps> => {
ast::Path {
root: Box::new(l.data),
steps: s
}
},
}
#[inline]
ExprPrecedence01: Synth<ast::Expr> = {
<lo:@L> <call:FunctionCall> <hi:@R> => {
let call = match call {
CallSite::Call(call) => ast::Expr::Call( state.node(call, lo..hi) ),
CallSite::CallAgg(call_agg) => ast::Expr::CallAgg( state.node(call_agg, lo..hi) ),
};
Synth::empty(call)
},
<ExprTerm>,
}
ExprTerm: Synth<ast::Expr> = {
<s:SubQuery> => Synth::empty(s),
<lo:@L> <lit:Literal> <hi:@R> => Synth::lit(ast::Expr::Lit( state.node(lit, lo..hi) )),
<v:VarRefExpr> => Synth::empty(v),
<lo:@L> <c:ExprTermCollection> <hi:@R> => {
if c.attrs.contains(Attrs::LIT) {
match c.data {
ast::Expr::List(l) => {
match list_to_lit(l.node) {
Ok(list_lit) => {
let list_lit = state.node(list_lit, lo..hi);
let lit = state.node(ast::Lit::ListLit(list_lit), lo..hi);
Synth::lit(ast::Expr::Lit( lit ))
},
Err(e) => {
let err = lpop::ErrorRecovery{error: e.into(), dropped_tokens: Default::default()};
state.errors.push(err);
Synth::empty(ast::Expr::Error)
}
}
},
ast::Expr::Bag(b) => {
match bag_to_lit(b.node) {
Ok(bag_lit) => {
let bag_lit = state.node(bag_lit, lo..hi);
let lit = state.node(ast::Lit::BagLit(bag_lit), lo..hi);
Synth::lit(ast::Expr::Lit( lit ))
},
Err(e) => {
let err = lpop::ErrorRecovery{error: e.into(), dropped_tokens: Default::default()};
state.errors.push(err);
Synth::empty(ast::Expr::Error)
}
}
},
_ => unreachable!(),
}
} else {
c
}
},
<lo:@L> <t:ExprTermTuple> <hi:@R> => {
if t.attrs.contains(Attrs::LIT) {
match t.data {
ast::Expr::Struct(s) => {
match struct_to_lit(s.node) {
Ok(struct_lit) => {
let struct_lit = state.node(struct_lit, lo..hi);
let lit = state.node(ast::Lit::StructLit(struct_lit), lo..hi);
Synth::lit(ast::Expr::Lit( lit ))
},
Err(e) => {
let err = lpop::ErrorRecovery{error: e.into(), dropped_tokens: Default::default()};
state.errors.push(err);
Synth::empty(ast::Expr::Error)
}
}
},
_ => unreachable!(),
}
} else {
t
}
},
! => { state.errors.push(<>); Synth::empty(ast::Expr::Error)},
}
SubQuery: ast::Expr = {
"(" <q:Query> ")" => *strip_expr(q),
}
SubQueryAst: ast::AstNode<ast::Expr> = {
<lo:@L> <subq:SubQuery> <hi:@R> => state.node(subq, lo..hi),
}
// ------------------------------------------------------------------------------ //
// //
// GPML Expression //
// //
// ------------------------------------------------------------------------------ //
// ------------------------------------------------------------------------------ //
// //
// Case Expression //
// //
// ------------------------------------------------------------------------------ //
// Implements parsing for CASE expressions.
//
// Searched Case Example:
// CASE WHEN titanId IS 1 THEN 2 WHEN titanId IS 2 THEN 3 ELSE 1 END
//
// Simple Case Example:
// CASE hello WHEN titanId IS NOT NULL THEN (SELECT * FROM data) ELSE 1 END
//
// The following is not allowed:
// CASE hello WHEN titanId IS NOT NULL THEN SELECT * FROM data ELSE 1 END
//
// This becaue as per SQL-92 standard THEN <result> ultimately leads to
// the following:
// <subquery> ::= <left paren> <query expression> <right paren>
CaseExpr: ast::AstNode<ast::Case> = {
<lo:@L> "CASE" <expr:ExprQuery?> <cases:ExprPairWhenThen+> <elsexpr:ElseClause?> "END" <hi:@R> => {
match expr {
None => state.node(ast::Case::SearchedCase(
ast::SearchedCase { cases, default: elsexpr }
), lo..hi),
Some(expr) => state.node(ast::Case::SimpleCase(
ast::SimpleCase { expr, cases, default: elsexpr }
), lo..hi)
}
}
}
ElseClause: Box<ast::Expr> = {
"ELSE" <e:ExprQuery> => Box::new(*e)
}
ExprPairWhenThen: ast::ExprPair = {
<lo:@L> "WHEN" <first:ExprQuery> "THEN" <second:ExprQuery> <hi:@R> => ast::ExprPair { first, second },
}
#[inline]
ExprTermCollection: Synth<ast::Expr> = {
<ExprTermArray>,
<ExprTermBag>,
}
#[inline]
ExprTermArray: Synth<ast::Expr> = {
<ExprTermArrayBrackets>,
<ExprTermArrayParens>
}
#[inline]
ExprTermArrayBrackets: Synth<ast::Expr> = {
<lo:@L> "[" <values:(<ExprQuerySynth> ",")*> <value:ExprQuerySynth?> "]" <hi:@R> => {
let values = match value {
None => values.into_iter().collect(),
Some(v) => values.into_iter().chain( std::iter::once(v) ).collect(),
};
let Synth{data, attrs} = values;
let data = ast::Expr::List( state.node(ast::List{values:data}, lo..hi) );
Synth{data, attrs}
}
}
#[inline]
ExprTermArrayParens: Synth<ast::Expr> = {
<lo:@L> "(" <values:(<ExprQuerySynth> ",")+> <value:ExprQuerySynth?> ")" <hi:@R> => {
let values = match value {
None => values.into_iter().collect(),
Some(v) => values.into_iter().chain( std::iter::once(v) ).collect(),
};
let Synth{data, attrs} = values;
let data = ast::Expr::List( state.node(ast::List{values:data}, lo..hi) );
Synth{data, attrs}
}
}
#[inline]
ExprTermBag: Synth<ast::Expr> = {
<lo:@L> "<<" <values:(<ExprQuerySynth> ",")*> <value:ExprQuerySynth?> ">>" <hi:@R> => {
let values = match value {
None => values.into_iter().collect(),
Some(v) => values.into_iter().chain( std::iter::once(v) ).collect(),
};
let Synth{data, attrs} = values;
let data = ast::Expr::Bag( state.node(ast::Bag{values:data}, lo..hi) );
Synth{data, attrs}
}
}
#[inline]
ExprTermTuple: Synth<ast::Expr> = {
<lo:@L> "{" <fields:(<ExprPair> ",")*> <field:ExprPair?> "}" <hi:@R> => {
let fields = match field {
None => fields.into_iter().collect(),
Some(f) => fields.into_iter().chain( std::iter::once(f) ).collect(),
};
let Synth{data, attrs} = fields;
let data = ast::Expr::Struct( state.node(ast::Struct{fields:data}, lo..hi) );
Synth{data, attrs}
}
}
ExprPair: Synth<ast::ExprPair> = {
<lo:@L> <first:ExprQuerySynth> ":" <second:ExprQuerySynth> <hi:@R> => {
let Synth{data:first, attrs: fattrs} = first;
let Synth{data:second, attrs: sattrs} = second;
Synth{ data: ast::ExprPair{ first, second }, attrs: fattrs.synthesize(sattrs) }
}
}
#[inline]
FunctionCall: CallSite = {
<func_name:FunctionName> "(" <args:FunctionCallArgs> ")" => {
if state.is_agg_fn(&func_name) {
CallSite::CallAgg(ast::CallAgg{ func_name, args })
} else {
CallSite::Call(ast::Call{ func_name, args })
}
},
}
#[inline]
FunctionName: ast::SymbolPrimitive = {
<SymbolPrimitive>,
<FnNonReservedKeyword>,
}
#[inline]
FunctionCallArgs: Vec<ast::AstNode<ast::CallArg>> = {
CommaSepStar<FunctionCallArg>,
// Special case subquery when it is the only sub-expression of a function call (e.g., `SELECT AVG(SELECT VALUE price FROM g AS v))... `)
<lo:@L> <subq:SfwQuery> <hi:@R> => {
let qset = state.node(ast::QuerySet::Select(Box::new(subq)), lo..hi);
let query = state.node(ast::Query{ set: qset, order_by: None, limit_offset:None }, lo..hi);
vec![state.node(ast::CallArg::Positional(Box::new(ast::Expr::Query(query))), lo..hi)]
},
}
#[inline]
FunctionCallArg: ast::AstNode<ast::CallArg> = {
<FunctionArgPositional>,
<FunctionArgNamed>,
}
#[inline]
FunctionArgPositional: ast::AstNode<ast::CallArg> = {
<lo:@L> "*" <hi:@R> => state.node(ast::CallArg::Star(), lo..hi),
<lo:@L> <expr:ExprQuery> <hi:@R> => state.node(ast::CallArg::Positional(expr), lo..hi),
<lo:@L> <ty:TypeName> <hi:@R> => state.node(ast::CallArg::PositionalType(ty), lo..hi),
}
#[inline]
FunctionArgNamed: ast::AstNode<ast::CallArg> = {
<lo:@L> <name:FunctionArgName> ":" <value:ExprQuery> <hi:@R> => state.node(ast::CallArg::Named(ast::CallArgNamed{name, value}), lo..hi),
<lo:@L> <name:FunctionArgName> ":" <ty:TypeName> <hi:@R> => state.node(ast::CallArg::NamedType(ast::CallArgNamedType{name, ty}), lo..hi)
}
#[inline]
FunctionArgName: ast::SymbolPrimitive = {
<name:"QuotedIdent"> => {
ast::SymbolPrimitive {
value: name.to_owned(),
case: ast::CaseSensitivity::CaseSensitive
}
},
<name:"UnquotedIdent"> => {
ast::SymbolPrimitive {
value: name.to_owned(),
case: ast::CaseSensitivity::CaseInsensitive
}
}
}
// ------------------------------------------------------------------------------ //
// //
// Path Navigation //
// //
// ------------------------------------------------------------------------------ //
// Implementation of Path Navigation as specified by PartiQL Specification Section 4:
// https://partiql.org/assets/PartiQL-Specification.pdf
//
// Examples:
// a.b
// a.*
// a[*]
// a[*][*]
// a.b.c
// a[*].b[*].c
// "a".b
// "a"."b"
// { 'a': 1, 'b': 2 }.a
// [1, 2][1]
// foo(x, y).a
// (SELECT a from en).b
//
// See the path expression conformance tests under `partiql-tests` or parser unit-tests for more examples.
PathSteps: Vec<ast::PathStep> = {
<path:PathSteps> <step:PathStep> => {
let mut steps = path;
steps.push(step);
steps
},
<step:PathStep> => {
vec![step]
},
}
PathStep: ast::PathStep = {
"." <v:PathExprVarRef> => {
ast::PathStep::PathProject( ast::PathExpr{ index: Box::new(v) })
},
"[" "*" "]" => {
ast::PathStep::PathForEach
},
"." "*" => {
ast::PathStep::PathUnpivot
},
"[" <expr:ExprQuery> "]" => {
ast::PathStep::PathIndex( ast::PathExpr{ index: Box::new(*expr) })
},
}
PathExprVarRef: ast::Expr = {
<lo:@L> <s:"String"> <hi:@R> => ast::Expr::VarRef(state.node(ast::VarRef {
name: ast::SymbolPrimitive { value: s.to_owned(), case: ast::CaseSensitivity::CaseInsensitive },
qualifier: ast::ScopeQualifier::Unqualified
}, lo..hi)),
<VarRefExpr>,
}
VarRefExpr: ast::Expr = {
<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> => 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> => 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> => state.node(ast::VarRef {
name: ast::SymbolPrimitive { value: ident.to_owned(), case: ast::CaseSensitivity::CaseSensitive },
qualifier: ast::ScopeQualifier::Unqualified
},lo..hi),
<lo:@L> <ident:VarNonReservedKeyword> <hi:@R> => state.node(ast::VarRef {
name: ident,
qualifier: ast::ScopeQualifier::Unqualified
},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) )
},
}
// ------------------------------------------------------------------------------ //
// //
// Literal Values //
// //
// ------------------------------------------------------------------------------ //
Literal: ast::Lit = {
<LiteralAbsent>,
<LiteralScalar>,
<LiteralEmbeddedDoc>,
<TypedLiteral>,
}
#[inline]
LiteralAbsent: ast::Lit = {
"NULL" => ast::Lit::Null,
"MISSING" => ast::Lit::Missing,
}
#[inline]
LiteralScalar: ast::Lit = {
<LiteralString>,
<LiteralBool>,
<LiteralNumber>
}
#[inline]
LiteralString: ast::Lit = {
<s:"String"> => ast::Lit::CharStringLit(s.to_owned()),
}
#[inline]
LiteralBool: ast::Lit = {
"TRUE" => ast::Lit::BoolLit(true),
"FALSE" => ast::Lit::BoolLit(false),
}
#[inline]
LiteralNumber: ast::Lit = {
// TODO check bounds before parsing?
<lo:@L> <n:"Int"> <hi:@R> =>? {
n.parse()
.map(ast::Lit::Int64Lit)
.map_err(|_| lpop::ParseError::User{
error: ParseError::SyntaxError(
"invalid literal".to_string().to_located(BytePosition::from(lo)..BytePosition::from(hi)))
})
},
<lo:@L> <r:"Real"> <hi:@R> =>? {
rust_decimal::Decimal::from_str(r)
.map(ast::Lit::DecimalLit)
.map_err(|_| lpop::ParseError::User{
error: ParseError::SyntaxError(
"invalid literal".to_string().to_located(BytePosition::from(lo)..BytePosition::from(hi)))
})
},
<lo:@L> <r:"ExpReal"> <hi:@R> =>? {
rust_decimal::Decimal::from_scientific(r)
.map(ast::Lit::DecimalLit)
.map_err(|_| lpop::ParseError::User{
error: ParseError::SyntaxError(
"invalid literal".to_string().to_located(BytePosition::from(lo)..BytePosition::from(hi)))
})
},
}
#[inline]
LiteralEmbeddedDoc: ast::Lit = {
<ion:"EmbeddedDoc"> => {
let ion_typ = ast::Type::CustomType(ast::CustomType {
parts: vec![ast::CustomTypePart::Name(ast::SymbolPrimitive {
value: "Ion".to_string(),
case: ast::CaseSensitivity::CaseInsensitive,
})],
});
ast::Lit::EmbeddedDocLit(ion.to_owned(), ion_typ)
}
}
#[inline]
TypeKeywordStr: &'static str = {
"DATE" => "DATE",
"TIME" => "TIME",
"TIMESTAMP" => "TIMESTAMP",
"WITH" => "WITH",
"WITHOUT" => "WITHOUT",
"ZONE" => "ZONE",
}
#[inline]
TypeKeyword: ast::SymbolPrimitive = {
<s:TypeKeywordStr> => ast::SymbolPrimitive { value: s.to_string(), case: ast::CaseSensitivity::CaseInsensitive, },
}
#[inline]
TypeParam: ast::CustomTypeParam = {
<lo:@L> <ty:TypeName> <hi:@R> => ast::CustomTypeParam::Type(ty),
<lo:@L> <lit:LiteralScalar> <hi:@R> => ast::CustomTypeParam::Lit(lit),
}
TypeNamePart: ast::CustomTypePart = {
<id:TypeKeyword> => ast::CustomTypePart::Name( id ),
<id:TypeKeyword> "(" <args:CommaSepPlus<TypeParam>> ")" => ast::CustomTypePart::Parameterized( id, args ),
}
#[inline]
TypeName: ast::Type = {
<parts:TypeNamePart+> => ast::Type::CustomType( ast::CustomType{ parts } ),
}
#[inline]
TypedLiteral: ast::Lit = {
<ty:TypeName> <s:"String"> => ast::Lit::TypedLit(s.to_owned(), ty),
// TODO we could support postgres-style literals with the following:
//<s:"String"> "::" <ty:TypeName> => ast::Lit::TypedLit(s.to_owned(), ty),
}
// ------------------------------------------------------------------------------ //
// //
// Utilities //
// //
// ------------------------------------------------------------------------------ //
// Comma as terminator (i.e. "<T>, <T>, <T>," where final comma is optional); may be empty
// This is a macro; see http://lalrpop.github.io/lalrpop/tutorial/006_macros.html
CommaTermStar<T>: Vec<T> = {
<mut v:(<T> ",")*> <e:T?> => match e {
None => v,
Some(e) => {
v.push(e);
v
}
}
}
// Comma as terminator (i.e. "<T>, <T>, <T>," where final comma is optional); at least 1 arg
// This is a macro; see http://lalrpop.github.io/lalrpop/tutorial/006_macros.html
CommaTermPlus<T>: Vec<T> = {
<mut v:(<T> ",")+> <e:T?> => match e {
None => v,
Some(e) => {
v.push(e);
v
}
}
}
// Comma as separator (i.e. "<T>, <T>, <T>"); may be empty
// This is a macro; see http://lalrpop.github.io/lalrpop/tutorial/006_macros.html
CommaSepStar<T>: Vec<T> = {
<e:T?> <mut v:("," <T>)*> => match e {
None => vec![],
Some(e) => {
v.insert(0,e);
v
}
}
}
// Comma as separator (i.e. "<T>, <T>, <T>"); at least 1 arg
// This is a macro; see http://lalrpop.github.io/lalrpop/tutorial/006_macros.html
CommaSepPlus<T>: Vec<T> = {
<mut v:(<T> ",")*> <e:T> => {
v.push(e);
v
}
}
// Comma as separator (i.e. "<T>, <T>, <T>"); at least 1 arg
// This is a macro; see http://lalrpop.github.io/lalrpop/tutorial/006_macros.html
MultiSetSepPlus<T>: Vec<T> = {
<e:T> <mut v:("|+|" <T>)*> => {
v.push(e);
v
}
}
// This is a macro; see http://lalrpop.github.io/lalrpop/tutorial/006_macros.html
PuncSepStar<P, T>: Vec<T> = {
<e:T?> <mut v:(<P> <T>)*> => match e {
None => vec![],
Some(e) => {
v.insert(0,e);
v
}
}
}
// This is a macro; see http://lalrpop.github.io/lalrpop/tutorial/006_macros.html
PuncSepPlus<P, T>: Vec<T> = {
<v:(<T> <P>)*> <e:T> => {
v.into_iter().map(|t|t.0).chain(std::iter::once(e)).collect()
}
}
// This is a macro; see http://lalrpop.github.io/lalrpop/tutorial/006_macros.html
PuncSep2Plus<P, T>: Vec<T> = {
<v:(<T> <P>)+> <e:T> => {
v.into_iter().map(|t|t.0).chain(std::iter::once(e)).collect()
}
}
// TODO rename to Identifier?
#[inline]
SymbolPrimitive: ast::SymbolPrimitive = {
<ident:"UnquotedIdent"> => ast::SymbolPrimitive {
value: ident.to_owned(),
case: ast::CaseSensitivity::CaseInsensitive,
},
<ident:"QuotedIdent"> => ast::SymbolPrimitive {
value: ident.to_owned(),
case: ast::CaseSensitivity::CaseSensitive,
},
/*
<ident:VarNonReservedKeyword> => ast::SymbolPrimitive {
value: ident.to_owned(),
case: ast::CaseSensitivity::CaseInsensitive
},
*/
}
#[inline]
Identifier: ast::SymbolPrimitive = {
<ident:"UnquotedIdent"> => ast::SymbolPrimitive {
value: ident.to_owned(),
case: ast::CaseSensitivity::CaseInsensitive,
},
<ident:"QuotedIdent"> => ast::SymbolPrimitive {
value: ident.to_owned(),
case: ast::CaseSensitivity::CaseSensitive,
},
<VarNonReservedKeyword>
}
AsIdent: ast::SymbolPrimitive = {
"AS"? <SymbolPrimitive>
}
AtIdent: ast::SymbolPrimitive = {
"AT" <SymbolPrimitive>
}
ByIdent: ast::SymbolPrimitive = {
"BY" <SymbolPrimitive>
}
// ------------------------------------------------------------------------------ //
// //
// Lexer //
// //
// ------------------------------------------------------------------------------ //
#[inline]
VarNonReservedKeyword: ast::SymbolPrimitive = {
<ident:NonReservedKeyword> => ast::SymbolPrimitive {
value: ident.to_owned(),
case: ast::CaseSensitivity::CaseInsensitive
},
}
#[inline]
FnNonReservedKeyword: ast::SymbolPrimitive = {
<ident:NonReservedKeyword> => ast::SymbolPrimitive {
value: ident.to_owned(),
case: ast::CaseSensitivity::CaseInsensitive
},
}
NonReservedKeyword: &'input str = {
"ANY",
"SIMPLE",
<GraphNonReservedKeyword>
}
//C.f. SQL '23, section 16, 5.2
#[inline]
GraphNonReservedKeyword: &'input str = {
"ACYCLIC",
"BINDINGS",
"BOUND",
"DESTINATION",
"DIFFERENT",
"DIRECTED",
"EDGE",
"EDGES",
"ELEMENTS",
"LABEL",
"LABELED",
"NODE",
"PATHS",
"PROPERTIES",
"PROPERTY",
"PROPERTY_GRAPH_CATALOG",
"PROPERTY_GRAPH_NAME",
"PROPERTY_GRAPH_SCHEMA",
"RELATIONSHIP",
"RELATIONSHIPS",
"SHORTEST",
"SINGLETONS",
"STEP",
"TABLES",
"TRAIL",
"VERTEX",
"WALK",
}
#[inline]
GraphEdgeSynonym = {
"EDGE",
"RELATIONSHIP",
}
#[inline]
GraphEdgesSynonym = {
"EDGES",
"RELATIONSHIPS",
}
#[inline]
GraphVertexSynonym = {
"NODE",
"VERTEX",
}
// The lexer is external; See [`lexer.rs`] for its definition.
//
// See also http://lalrpop.github.io/lalrpop/lexer_tutorial/002_writing_custom_lexer.html
extern {
type Location = ByteOffset;
type Error = ParseError<'input, BytePosition>;
enum lexer::Token<'input> {
// Brackets
"[" => lexer::Token::OpenSquare,
"]" => lexer::Token::CloseSquare,
"{" => lexer::Token::OpenCurly,
"}" => lexer::Token::CloseCurly,
"(" => lexer::Token::OpenParen,
")" => lexer::Token::CloseParen,
"<<" => lexer::Token::OpenDblAngle,
">>" => lexer::Token::CloseDblAngle,
// Punc
"," => lexer::Token::Comma,
":" => lexer::Token::Colon,
";" => lexer::Token::Semicolon,
"." => lexer::Token::Period,
"-" => lexer::Token::Minus,
"+" => lexer::Token::Plus,
"*" => lexer::Token::Star,
"/" => lexer::Token::Slash,
"%" => lexer::Token::Percent,
"^" => lexer::Token::Caret,
"||" => lexer::Token::DblPipe,
"|" => lexer::Token::Pipe,
"&" => lexer::Token::Ampersand,
"!" => lexer::Token::Bang,
"?" => lexer::Token::QuestionMark,
"=" => lexer::Token::Equal,
"==" => lexer::Token::EqualEqual,
"!=" => lexer::Token::BangEqual,
"<>" => lexer::Token::LessGreater,
"~" => lexer::Token::Tilde,
"<" => lexer::Token::LessThan,
">" => lexer::Token::GreaterThan,
"<=" => lexer::Token::LessEqual,
">=" => lexer::Token::GreaterEqual,
// Graph
"|+|" => lexer::Token::PipePlusPipe,
"<-" => lexer::Token::LeftArrow,
"~" => lexer::Token::Tilde,
"->" => lexer::Token::RightArrow,
"<~" => lexer::Token::LeftArrowTilde,
"~>" => lexer::Token::TildeRightArrow,
"<-[" => lexer::Token::LeftArrowBracket,
"]-" => lexer::Token::RightBracketMinus,
"~[" => lexer::Token::TildeLeftBracket,
"]~" => lexer::Token::RightBracketTilde,
"-[" => lexer::Token::MinusLeftBracket,
"]->" => lexer::Token::BracketRightArrow,
"<~[" => lexer::Token::LeftArrowTildeBracket,
"]~>" => lexer::Token::BracketTildeRightArrow,
"<->" => lexer::Token::LeftMinusRight,
"<-/" => lexer::Token::LeftArrowSlash,
"/-" => lexer::Token::RightSlashMinus,
"~/" => lexer::Token::TildeLeftSlash,
"/~" => lexer::Token::RightSlashTilde,
"-/" => lexer::Token::MinusLeftSlash,
"/->" => lexer::Token::SlashRightArrow,
"<~/" => lexer::Token::LeftArrowTildeSlash,
"/~>" => lexer::Token::SlashTildeRightArrow,
// Types
"UnquotedIdent" => lexer::Token::UnquotedIdent(<&'input str>),
"QuotedIdent" => lexer::Token::QuotedIdent(<&'input str>),
"UnquotedAtIdentifier" => lexer::Token::UnquotedAtIdentifier(<&'input str>),
"QuotedAtIdentifier" => lexer::Token::QuotedAtIdentifier(<&'input str>),
"Int" => lexer::Token::Int(<&'input str>),
"Real" => lexer::Token::Real(<&'input str>),
"ExpReal" => lexer::Token::ExpReal(<&'input str>),
"String" => lexer::Token::String(<&'input str>),
"EmbeddedDoc" => lexer::Token::EmbeddedDoc(<&'input str>),
// Keywords
"ALL" => lexer::Token::All,
"ASC" => lexer::Token::Asc,
"AND" => lexer::Token::And,
"ANY" => lexer::Token::Any(<&'input str>),
"AS" => lexer::Token::As,
"AT" => lexer::Token::At,
"BETWEEN" => lexer::Token::Between,
"BY" => lexer::Token::By,
"CASE" => lexer::Token::Case,
"COLUMNS" => lexer::Token::Columns,
"CROSS" => lexer::Token::Cross,
"CYCLE" => lexer::Token::Cycle,
"DATE" => lexer::Token::Date,
"DESC" => lexer::Token::Desc,
"DISTINCT" => lexer::Token::Distinct,
"ELEMENT" => lexer::Token::Element,
"EXCLUDE" => lexer::Token::Exclude,
"ELSE" => lexer::Token::Else,
"END" => lexer::Token::End,
"ESCAPE" => lexer::Token::Escape,
"EXCEPT" => lexer::Token::Except,
"EXPORT" => lexer::Token::Export,
"FALSE" => lexer::Token::False,
"FIRST" => lexer::Token::First,
"FOR" => lexer::Token::For,
"FULL" => lexer::Token::Full,
"FROM" => lexer::Token::From,
"GROUP" => lexer::Token::Group,
"GROUPS" => lexer::Token::Groups,
"HAVING" => lexer::Token::Having,
"IN" => lexer::Token::In,
"INNER" => lexer::Token::Inner,
"INTERSECT" => lexer::Token::Intersect,
"IS" => lexer::Token::Is,
"JOIN" => lexer::Token::Join,
"KEEP" => lexer::Token::Keep,
"LAST" => lexer::Token::Last,
"LATERAL" => lexer::Token::Lateral,
"LEFT" => lexer::Token::Left,
"LIKE" => lexer::Token::Like,
"LIMIT" => lexer::Token::Limit,
"MATCH" => lexer::Token::Match,
"MISSING" => lexer::Token::Missing,
"NATURAL" => lexer::Token::Natural,
"NO" => lexer::Token::No,
"NOT" => lexer::Token::Not,
"NULL" => lexer::Token::Null,
"NULLS" => lexer::Token::Nulls,
"OFFSET" => lexer::Token::Offset,
"ON" => lexer::Token::On,
"ONE" => lexer::Token::One,
"OR" => lexer::Token::Or,
"ORDER" => lexer::Token::Order,
"OUTER" => lexer::Token::Outer,
"PATH" => lexer::Token::Path,
"PARTIAL" => lexer::Token::Partial,
"PIVOT" => lexer::Token::Pivot,
"PER" => lexer::Token::Per,
"PRESERVE" => lexer::Token::Preserve,
"RECURSIVE" => lexer::Token::Recursive,
"REPEATABLE" => lexer::Token::Repeatable,
"RIGHT" => lexer::Token::Right,
"ROW" => lexer::Token::Row,
"SELECT" => lexer::Token::Select,
"SEARCH" => lexer::Token::Search,
"TABLE" => lexer::Token::Table,
"TIME" => lexer::Token::Time,
"TIMESTAMP" => lexer::Token::Timestamp,
"SIMPLE" => lexer::Token::Simple(<&'input str>),
"THEN" => lexer::Token::Then,
"TRUE" => lexer::Token::True,
"UNION" => lexer::Token::Union,
"UNPIVOT" => lexer::Token::Unpivot,
"USING" => lexer::Token::Using,
"VALUE" => lexer::Token::Value,
"VALUES" => lexer::Token::Values,
"WHEN" => lexer::Token::When,
"WHERE" => lexer::Token::Where,
"WITH" => lexer::Token::With,
"WITHOUT" => lexer::Token::Without,
"ZONE" => lexer::Token::Zone,
// Graph Keywords; reserved
"ALL_DIFFERENT" => lexer::Token::AllDifferent,
"BINDING_COUNT" => lexer::Token::BindingCount,
"ELEMENT_ID" => lexer::Token::ElementId,
"ELEMENT_NUMBER" => lexer::Token::ElementNumber,
"GRAPH" => lexer::Token::Graph,
"GRAPH_TABLE" => lexer::Token::GraphTable,
"MATCHNUM" => lexer::Token::MatchNum,
"PATH_LENGTH" => lexer::Token::PathLength,
"PATH_NAME" => lexer::Token::PathName,
"PROPERTY_EXISTS" => lexer::Token::PropertyExists,
"SAME" => lexer::Token::Same,
// Graph Keywords; non-reserved
"ACYCLIC" => lexer::Token::Acyclic(<&'input str>),
"BINDINGS" => lexer::Token::Bindings(<&'input str>),
"BOUND" => lexer::Token::Bound(<&'input str>),
"DESTINATION" => lexer::Token::Destination(<&'input str>),
"DIFFERENT" => lexer::Token::Different(<&'input str>),
"DIRECTED" => lexer::Token::Directed(<&'input str>),
"EDGE" => lexer::Token::Edge(<&'input str>),
"EDGES" => lexer::Token::Edges(<&'input str>),
"ELEMENTS" => lexer::Token::Elements(<&'input str>),
"LABEL" => lexer::Token::Label(<&'input str>),
"LABELED" => lexer::Token::Labeled(<&'input str>),
"NODE" => lexer::Token::Node(<&'input str>),
"PATHS" => lexer::Token::Paths(<&'input str>),
"PROPERTIES" => lexer::Token::Properties(<&'input str>),
"PROPERTY" => lexer::Token::Property(<&'input str>),
"PROPERTY_GRAPH_CATALOG" => lexer::Token::PropertyGraphCatalog(<&'input str>),
"PROPERTY_GRAPH_NAME" => lexer::Token::PropertyGraphName(<&'input str>),
"PROPERTY_GRAPH_SCHEMA" => lexer::Token::PropertyGraphSchema(<&'input str>),
"RELATIONSHIP" => lexer::Token::Relationship(<&'input str>),
"RELATIONSHIPS" => lexer::Token::Relationships(<&'input str>),
"SHORTEST" => lexer::Token::Shortest(<&'input str>),
"SINGLETONS" => lexer::Token::Singletons(<&'input str>),
"STEP" => lexer::Token::Step(<&'input str>),
"TABLES" => lexer::Token::Tables(<&'input str>),
"TRAIL" => lexer::Token::Trail(<&'input str>),
"VERTEX" => lexer::Token::Vertex(<&'input str>),
"WALK" => lexer::Token::Walk(<&'input str>),
}
}