Skip to content

Commit 1b3198e

Browse files
committed
support some of pipe operators
Part of #1758 Still missing - join - union|intersect|except - call - tablesample - pivot - unpivot
1 parent 6ec5223 commit 1b3198e

File tree

12 files changed

+341
-13
lines changed

12 files changed

+341
-13
lines changed

src/ast/mod.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,16 @@ pub use self::query::{
6868
JsonTableColumn, JsonTableColumnErrorHandling, JsonTableNamedColumn, JsonTableNestedColumn,
6969
LateralView, LockClause, LockType, MatchRecognizePattern, MatchRecognizeSymbol, Measure,
7070
NamedWindowDefinition, NamedWindowExpr, NonBlock, Offset, OffsetRows, OpenJsonTableColumn,
71-
OrderBy, OrderByExpr, OrderByKind, OrderByOptions, PivotValueSource, ProjectionSelect, Query,
72-
RenameSelectItem, RepetitionQuantifier, ReplaceSelectElement, ReplaceSelectItem, RowsPerMatch,
73-
Select, SelectFlavor, SelectInto, SelectItem, SelectItemQualifiedWildcardKind, SetExpr,
74-
SetOperator, SetQuantifier, Setting, SymbolDefinition, Table, TableAlias, TableAliasColumnDef,
75-
TableFactor, TableFunctionArgs, TableIndexHintForClause, TableIndexHintType, TableIndexHints,
76-
TableIndexType, TableSample, TableSampleBucket, TableSampleKind, TableSampleMethod,
77-
TableSampleModifier, TableSampleQuantity, TableSampleSeed, TableSampleSeedModifier,
78-
TableSampleUnit, TableVersion, TableWithJoins, Top, TopQuantity, UpdateTableFromKind,
79-
ValueTableMode, Values, WildcardAdditionalOptions, With, WithFill,
71+
OrderBy, OrderByExpr, OrderByKind, OrderByOptions, PipeOperator, PivotValueSource,
72+
ProjectionSelect, Query, RenameSelectItem, RepetitionQuantifier, ReplaceSelectElement,
73+
ReplaceSelectItem, RowsPerMatch, Select, SelectFlavor, SelectInto, SelectItem,
74+
SelectItemQualifiedWildcardKind, SetExpr, SetOperator, SetQuantifier, Setting,
75+
SymbolDefinition, Table, TableAlias, TableAliasColumnDef, TableFactor, TableFunctionArgs,
76+
TableIndexHintForClause, TableIndexHintType, TableIndexHints, TableIndexType, TableSample,
77+
TableSampleBucket, TableSampleKind, TableSampleMethod, TableSampleModifier,
78+
TableSampleQuantity, TableSampleSeed, TableSampleSeedModifier, TableSampleUnit, TableVersion,
79+
TableWithJoins, Top, TopQuantity, UpdateTableFromKind, ValueTableMode, Values,
80+
WildcardAdditionalOptions, With, WithFill,
8081
};
8182

8283
pub use self::trigger::{

src/ast/query.rs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ pub struct Query {
6868
/// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/select/format)
6969
/// (ClickHouse-specific)
7070
pub format_clause: Option<FormatClause>,
71+
72+
/// Pipe operator
73+
pub pipe_operators: Vec<PipeOperator>,
7174
}
7275

7376
impl fmt::Display for Query {
@@ -103,6 +106,9 @@ impl fmt::Display for Query {
103106
if let Some(ref format) = self.format_clause {
104107
write!(f, " {}", format)?;
105108
}
109+
for pipe_operator in &self.pipe_operators {
110+
write!(f, " |> {}", pipe_operator)?;
111+
}
106112
Ok(())
107113
}
108114
}
@@ -2407,6 +2413,98 @@ impl fmt::Display for OffsetRows {
24072413
}
24082414
}
24092415

2416+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
2417+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2418+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
2419+
pub enum PipeOperator {
2420+
Limit {
2421+
expr: Expr,
2422+
offset: Option<Expr>,
2423+
},
2424+
Where {
2425+
expr: Expr,
2426+
},
2427+
OrderBy {
2428+
exprs: Vec<OrderByExpr>,
2429+
},
2430+
Select {
2431+
exprs: Vec<SelectItem>,
2432+
},
2433+
Extend {
2434+
exprs: Vec<SelectItem>,
2435+
},
2436+
Set {
2437+
assignments: Vec<Assignment>,
2438+
},
2439+
Drop {
2440+
columns: Vec<Ident>,
2441+
},
2442+
Alias {
2443+
alias: Ident,
2444+
},
2445+
Aggregate {
2446+
full_table_exprs: Vec<ExprWithAlias>,
2447+
group_by_exprs: Vec<ExprWithAlias>,
2448+
},
2449+
}
2450+
2451+
impl fmt::Display for PipeOperator {
2452+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2453+
match self {
2454+
PipeOperator::Select { exprs } => {
2455+
write!(f, "SELECT {}", display_comma_separated(exprs.as_slice()))
2456+
}
2457+
PipeOperator::Extend { exprs } => {
2458+
write!(f, "EXTEND {}", display_comma_separated(exprs.as_slice()))
2459+
}
2460+
PipeOperator::Set { assignments } => {
2461+
write!(f, "SET {}", display_comma_separated(assignments.as_slice()))
2462+
}
2463+
PipeOperator::Drop { columns } => {
2464+
write!(f, "DROP {}", display_comma_separated(columns.as_slice()))
2465+
}
2466+
PipeOperator::Alias { alias } => {
2467+
write!(f, "AS {}", alias)
2468+
}
2469+
PipeOperator::Limit { expr, offset } => {
2470+
write!(f, "LIMIT {}", expr)?;
2471+
if let Some(offset) = offset {
2472+
write!(f, " OFFSET {}", offset)?;
2473+
}
2474+
Ok(())
2475+
}
2476+
PipeOperator::Aggregate {
2477+
full_table_exprs,
2478+
group_by_exprs,
2479+
} => {
2480+
write!(f, "AGGREGATE")?;
2481+
if !full_table_exprs.is_empty() {
2482+
write!(
2483+
f,
2484+
" {}",
2485+
display_comma_separated(full_table_exprs.as_slice())
2486+
)?;
2487+
}
2488+
if !group_by_exprs.is_empty() {
2489+
write!(
2490+
f,
2491+
" GROUP BY {}",
2492+
display_comma_separated(group_by_exprs.as_slice())
2493+
)?;
2494+
}
2495+
Ok(())
2496+
}
2497+
2498+
PipeOperator::Where { expr } => {
2499+
write!(f, "WHERE {}", expr)
2500+
}
2501+
PipeOperator::OrderBy { exprs } => {
2502+
write!(f, "ORDER BY {}", display_comma_separated(exprs.as_slice()))
2503+
}
2504+
}
2505+
}
2506+
}
2507+
24102508
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
24112509
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
24122510
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]

src/ast/spans.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,11 @@ impl Spanned for Query {
9898
limit_by,
9999
offset,
100100
fetch,
101-
locks: _, // todo
102-
for_clause: _, // todo, mssql specific
103-
settings: _, // todo, clickhouse specific
104-
format_clause: _, // todo, clickhouse specific
101+
locks: _, // todo
102+
for_clause: _, // todo, mssql specific
103+
settings: _, // todo, clickhouse specific
104+
format_clause: _, // todo, clickhouse specific
105+
pipe_operators: _, // todo bigquery specific
105106
} = self;
106107

107108
union_spans(

src/dialect/bigquery.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@ impl Dialect for BigQueryDialect {
136136
fn is_column_alias(&self, kw: &Keyword, _parser: &mut Parser) -> bool {
137137
!RESERVED_FOR_COLUMN_ALIAS.contains(kw)
138138
}
139+
140+
fn supports_pipe_operator(&self) -> bool {
141+
true
142+
}
139143
}
140144

141145
impl BigQueryDialect {

src/dialect/mod.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,20 @@ pub trait Dialect: Debug + Any {
508508
false
509509
}
510510

511+
/// Return true if the dialect supports pipe operator.
512+
///
513+
/// Example:
514+
/// ```sql
515+
/// SELECT *
516+
/// FROM table
517+
/// |> limit 1
518+
/// ```
519+
///
520+
/// See "SQL Has Problems. We Can Fix Them: Pipe Syntax In SQL" https://research.google/pubs/sql-has-problems-we-can-fix-them-pipe-syntax-in-sql/
521+
fn supports_pipe_operator(&self) -> bool {
522+
false
523+
}
524+
511525
/// Does the dialect support MySQL-style `'user'@'host'` grantee syntax?
512526
fn supports_user_host_grantee(&self) -> bool {
513527
false

src/keywords.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ define_keywords!(
8383
ADMIN,
8484
AFTER,
8585
AGAINST,
86+
AGGREGATE,
8687
AGGREGATION,
8788
ALERT,
8889
ALGORITHM,
@@ -335,6 +336,7 @@ define_keywords!(
335336
EXPLAIN,
336337
EXPLICIT,
337338
EXPORT,
339+
EXTEND,
338340
EXTENDED,
339341
EXTENSION,
340342
EXTERNAL,

src/parser/mod.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10231,6 +10231,7 @@ impl<'a> Parser<'a> {
1023110231
for_clause: None,
1023210232
settings: None,
1023310233
format_clause: None,
10234+
pipe_operators: vec![],
1023410235
}
1023510236
.into())
1023610237
} else if self.parse_keyword(Keyword::UPDATE) {
@@ -10246,6 +10247,7 @@ impl<'a> Parser<'a> {
1024610247
for_clause: None,
1024710248
settings: None,
1024810249
format_clause: None,
10250+
pipe_operators: vec![],
1024910251
}
1025010252
.into())
1025110253
} else {
@@ -10319,6 +10321,104 @@ impl<'a> Parser<'a> {
1031910321
None
1032010322
};
1032110323

10324+
let mut pipe_operators = Vec::new();
10325+
10326+
// Syntax from "SQL Has Problems. We Can Fix Them: Pipe Syntax In SQL"
10327+
// https://storage.googleapis.com/gweb-research2023-media/pubtools/1004848.pdf
10328+
while self.consume_token(&Token::VerticalBarRightAngleBracket) {
10329+
let kw = self.expect_one_of_keywords(&[
10330+
Keyword::SELECT,
10331+
Keyword::EXTEND,
10332+
Keyword::SET,
10333+
Keyword::DROP,
10334+
Keyword::AS,
10335+
Keyword::WHERE,
10336+
Keyword::LIMIT,
10337+
Keyword::AGGREGATE,
10338+
Keyword::ORDER,
10339+
])?;
10340+
match kw {
10341+
// SELECT <expr> [[AS] alias], ...
10342+
Keyword::SELECT => {
10343+
let exprs = self.parse_comma_separated(Parser::parse_select_item)?;
10344+
pipe_operators.push(PipeOperator::Select { exprs })
10345+
}
10346+
// EXTEND <expr> [[AS] alias], ...
10347+
Keyword::EXTEND => {
10348+
let exprs = self.parse_comma_separated(Parser::parse_select_item)?;
10349+
pipe_operators.push(PipeOperator::Extend { exprs })
10350+
}
10351+
// SET <column> = <expression>, ...
10352+
Keyword::SET => {
10353+
let assignments = self.parse_comma_separated(Parser::parse_assignment)?;
10354+
pipe_operators.push(PipeOperator::Set { assignments })
10355+
}
10356+
// DROP <column>, ...
10357+
Keyword::DROP => {
10358+
let columns = self.parse_identifiers()?;
10359+
pipe_operators.push(PipeOperator::Drop { columns })
10360+
}
10361+
// AS <alias>
10362+
Keyword::AS => {
10363+
let alias = self.parse_identifier()?;
10364+
pipe_operators.push(PipeOperator::Alias { alias })
10365+
}
10366+
// WHERE <condition>
10367+
Keyword::WHERE => {
10368+
let expr = self.parse_expr()?;
10369+
pipe_operators.push(PipeOperator::Where { expr })
10370+
}
10371+
// LIMIT <n> [OFFSET <m>]
10372+
Keyword::LIMIT => {
10373+
let expr = self.parse_expr()?;
10374+
let offset = if self.parse_keyword(Keyword::OFFSET) {
10375+
Some(self.parse_expr()?)
10376+
} else {
10377+
None
10378+
};
10379+
pipe_operators.push(PipeOperator::Limit { expr, offset })
10380+
}
10381+
// AGGREGATE <agg_expr> [[AS] alias], ...
10382+
//
10383+
// and
10384+
//
10385+
// AGGREGATE [<agg_expr> [[AS] alias], ...]
10386+
// GROUP BY <grouping_expr> [AS alias], ...
10387+
Keyword::AGGREGATE => {
10388+
let full_table_exprs = self.parse_comma_separated0(
10389+
|parser| {
10390+
let expr = parser.parse_expr()?;
10391+
let alias = parser.maybe_parse_select_item_alias()?;
10392+
Ok(ExprWithAlias { expr, alias })
10393+
},
10394+
Token::make_keyword(keywords::GROUP),
10395+
)?;
10396+
10397+
let group_by_exprs = if self.parse_keywords(&[Keyword::GROUP, Keyword::BY])
10398+
{
10399+
self.parse_comma_separated(|parser| {
10400+
let expr = parser.parse_expr()?;
10401+
let alias = parser.maybe_parse_select_item_alias()?;
10402+
Ok(ExprWithAlias { expr, alias })
10403+
})?
10404+
} else {
10405+
vec![]
10406+
};
10407+
pipe_operators.push(PipeOperator::Aggregate {
10408+
full_table_exprs,
10409+
group_by_exprs,
10410+
})
10411+
}
10412+
// ORDER BY <expr> [ASC|DESC], ...
10413+
Keyword::ORDER => {
10414+
self.expect_one_of_keywords(&[Keyword::BY])?;
10415+
let exprs = self.parse_comma_separated(Parser::parse_order_by_expr)?;
10416+
pipe_operators.push(PipeOperator::OrderBy { exprs })
10417+
}
10418+
_ => {}
10419+
}
10420+
}
10421+
1032210422
Ok(Query {
1032310423
with,
1032410424
body,
@@ -10331,6 +10431,7 @@ impl<'a> Parser<'a> {
1033110431
for_clause,
1033210432
settings,
1033310433
format_clause,
10434+
pipe_operators,
1033410435
}
1033510436
.into())
1033610437
}
@@ -11688,6 +11789,7 @@ impl<'a> Parser<'a> {
1168811789
for_clause: None,
1168911790
settings: None,
1169011791
format_clause: None,
11792+
pipe_operators: vec![],
1169111793
}),
1169211794
alias,
1169311795
})

src/tokenizer.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,8 @@ pub enum Token {
246246
ShiftLeftVerticalBar,
247247
/// `|>> PostgreSQL/Redshift geometrical binary operator (Is strictly above?)
248248
VerticalBarShiftRight,
249+
/// `|> BigQuery pipe operator
250+
VerticalBarRightAngleBracket,
249251
/// `#>>`, extracts JSON sub-object at the specified path as text
250252
HashLongArrow,
251253
/// jsonb @> jsonb -> boolean: Test whether left json contains the right json
@@ -359,6 +361,7 @@ impl fmt::Display for Token {
359361
Token::AmpersandRightAngleBracket => f.write_str("&>"),
360362
Token::AmpersandLeftAngleBracketVerticalBar => f.write_str("&<|"),
361363
Token::VerticalBarAmpersandRightAngleBracket => f.write_str("|&>"),
364+
Token::VerticalBarRightAngleBracket => f.write_str("|>"),
362365
Token::TwoWayArrow => f.write_str("<->"),
363366
Token::LeftAngleBracketCaret => f.write_str("<^"),
364367
Token::RightAngleBracketCaret => f.write_str(">^"),
@@ -1378,6 +1381,9 @@ impl<'a> Tokenizer<'a> {
13781381
_ => self.start_binop_opt(chars, "|>", None),
13791382
}
13801383
}
1384+
Some('>') if self.dialect.supports_pipe_operator() => {
1385+
self.consume_for_binop(chars, "|>", Token::VerticalBarRightAngleBracket)
1386+
}
13811387
// Bitshift '|' operator
13821388
_ => self.start_binop(chars, "|", Token::Pipe),
13831389
}

0 commit comments

Comments
 (0)