agent_patch
runs/2026-02-21__03-36-16__gpt-5-1-codex-mini/flux-pr-1759/flux-pr-1759.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/agent-logs/agent.patch
10840 bytes
diff --git a/tmp/agent-patch-flux-pr-1759.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/src/ast/mod.rs b/app/src/ast/mod.rs
index d658898..cad5a29 100644
--- a/tmp/agent-patch-flux-pr-1759.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/src/ast/mod.rs
+++ b/app/src/ast/mod.rs
@@ -73,6 +73,7 @@ pub use self::query::{
LateralView, LimitClause, LockClause, LockType, MatchRecognizePattern, MatchRecognizeSymbol,
Measure, NamedWindowDefinition, NamedWindowExpr, NonBlock, Offset, OffsetRows,
OpenJsonTableColumn, OrderBy, OrderByExpr, OrderByKind, OrderByOptions, PivotValueSource,
+ PipeOperator,
ProjectionSelect, Query, RenameSelectItem, RepetitionQuantifier, ReplaceSelectElement,
ReplaceSelectItem, RowsPerMatch, Select, SelectFlavor, SelectInto, SelectItem,
SelectItemQualifiedWildcardKind, SetExpr, SetOperator, SetQuantifier, Setting,
diff --git a/tmp/agent-patch-flux-pr-1759.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/src/ast/query.rs b/app/src/ast/query.rs
index 982985e..171fde7 100644
--- a/tmp/agent-patch-flux-pr-1759.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/src/ast/query.rs
+++ b/app/src/ast/query.rs
@@ -27,7 +27,7 @@ use sqlparser_derive::{Visit, VisitMut};
use crate::{
ast::*,
- tokenizer::{Token, TokenWithSpan},
+ tokenizer::{Span, Token, TokenWithSpan},
};
/// The most complete variant of a `SELECT` query expression, optionally
@@ -123,6 +123,28 @@ impl fmt::Display for ProjectionSelect {
}
}
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct PipeOperator {
+ pub tokens: Vec<TokenWithSpan>,
+ pub span: Span,
+}
+
+impl fmt::Display for PipeOperator {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let mut first = true;
+ for token in &self.tokens {
+ if !first {
+ f.write_str(" ")?;
+ }
+ first = false;
+ write!(f, "{}", token.token)?;
+ }
+ Ok(())
+ }
+}
+
/// A node in a tree, representing a "query body" expression, roughly:
/// `SELECT ... [ {UNION|EXCEPT|INTERSECT} SELECT ...]`
#[allow(clippy::large_enum_variant)]
@@ -147,6 +169,10 @@ pub enum SetExpr {
Update(Statement),
Delete(Statement),
Table(Box<Table>),
+ Pipe {
+ input: Box<SetExpr>,
+ operators: Vec<PipeOperator>,
+ },
}
impl SetExpr {
@@ -170,6 +196,13 @@ impl fmt::Display for SetExpr {
SetExpr::Update(v) => write!(f, "{v}"),
SetExpr::Delete(v) => write!(f, "{v}"),
SetExpr::Table(t) => write!(f, "{t}"),
+ SetExpr::Pipe { input, operators } => {
+ write!(f, "{input}")?;
+ for op in operators {
+ write!(f, " |> {op}")?;
+ }
+ Ok(())
+ }
SetExpr::SetOperation {
left,
right,
diff --git a/tmp/agent-patch-flux-pr-1759.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/src/ast/spans.rs b/app/src/ast/spans.rs
index 28d479f..febe0c5 100644
--- a/tmp/agent-patch-flux-pr-1759.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/src/ast/spans.rs
+++ b/app/src/ast/spans.rs
@@ -207,6 +207,9 @@ impl Spanned for SetExpr {
SetExpr::Values(values) => values.span(),
SetExpr::Insert(statement) => statement.span(),
SetExpr::Table(_) => Span::empty(),
+ SetExpr::Pipe { input, operators } => union_spans(
+ iter::once(input.span()).chain(operators.iter().map(|op| op.span)),
+ ),
SetExpr::Update(statement) => statement.span(),
SetExpr::Delete(statement) => statement.span(),
}
diff --git a/tmp/agent-patch-flux-pr-1759.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/src/dialect/bigquery.rs b/app/src/dialect/bigquery.rs
index 49fb24f..9318628 100644
--- a/tmp/agent-patch-flux-pr-1759.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/src/dialect/bigquery.rs
+++ b/app/src/dialect/bigquery.rs
@@ -118,6 +118,14 @@ impl Dialect for BigQueryDialect {
true
}
+ fn supports_from_first_select(&self) -> bool {
+ true
+ }
+
+ fn supports_pipe_syntax(&self) -> bool {
+ true
+ }
+
/// See <https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#execute_immediate>
fn supports_execute_immediate(&self) -> bool {
true
diff --git a/tmp/agent-patch-flux-pr-1759.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/src/dialect/mod.rs b/app/src/dialect/mod.rs
index b2dff06..570ebd3 100644
--- a/tmp/agent-patch-flux-pr-1759.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/src/dialect/mod.rs
+++ b/app/src/dialect/mod.rs
@@ -518,6 +518,11 @@ pub trait Dialect: Debug + Any {
false
}
+ /// Return true if the dialect supports pipe syntax (BigQuery style `|>` operators).
+ fn supports_pipe_syntax(&self) -> bool {
+ false
+ }
+
/// Does the dialect support MySQL-style `'user'@'host'` grantee syntax?
fn supports_user_host_grantee(&self) -> bool {
false
diff --git a/tmp/agent-patch-flux-pr-1759.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/src/parser/mod.rs b/app/src/parser/mod.rs
index 03ea91f..3ffaaf4 100644
--- a/tmp/agent-patch-flux-pr-1759.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/src/parser/mod.rs
+++ b/app/src/parser/mod.rs
@@ -10863,7 +10863,8 @@ impl<'a> Parser<'a> {
);
};
- self.parse_remaining_set_exprs(expr, precedence)
+ let expr = self.parse_remaining_set_exprs(expr, precedence)?;
+ self.parse_pipe_operators(expr)
}
/// Parse any extra set expressions that may be present in a query body
@@ -10903,6 +10904,71 @@ impl<'a> Parser<'a> {
Ok(expr.into())
}
+ fn parse_pipe_operators(
+ &mut self,
+ expr: Box<SetExpr>,
+ ) -> Result<Box<SetExpr>, ParserError> {
+ if !self.dialect.supports_pipe_syntax() {
+ return Ok(expr);
+ }
+
+ let mut operators = Vec::new();
+ while self.consume_token(&Token::PipeGt) {
+ operators.push(self.parse_pipe_operator()?);
+ }
+
+ if operators.is_empty() {
+ Ok(expr)
+ } else {
+ Ok(SetExpr::Pipe {
+ input: expr,
+ operators,
+ }
+ .into())
+ }
+ }
+
+ fn parse_pipe_operator(&mut self) -> Result<PipeOperator, ParserError> {
+ let mut tokens = Vec::new();
+ let mut parens = 0;
+ let mut brackets = 0;
+ let mut braces = 0;
+
+ loop {
+ let next_token = self.peek_token().clone();
+ if matches!(next_token.token, Token::Whitespace(_)) {
+ self.next_token();
+ continue;
+ }
+
+ if parens == 0 && brackets == 0 && braces == 0 {
+ match next_token.token {
+ Token::PipeGt | Token::SemiColon | Token::EOF => break,
+ _ => {}
+ }
+ }
+
+ match next_token.token {
+ Token::LParen => parens += 1,
+ Token::RParen if parens > 0 => parens -= 1,
+ Token::LBracket => brackets += 1,
+ Token::RBracket if brackets > 0 => brackets -= 1,
+ Token::LBrace => braces += 1,
+ Token::RBrace if braces > 0 => braces -= 1,
+ _ => {}
+ }
+
+ tokens.push(self.next_token().clone());
+ }
+
+ if tokens.is_empty() {
+ return self.expected("pipe operator body", self.peek_token());
+ }
+
+ let span = Span::union_iter(tokens.iter().map(|t| t.span));
+ Ok(PipeOperator { tokens, span })
+ }
+
pub fn parse_set_operator(&mut self, token: &Token) -> Option<SetOperator> {
match token {
Token::Word(w) if w.keyword == Keyword::UNION => Some(SetOperator::Union),
diff --git a/tmp/agent-patch-flux-pr-1759.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/src/tokenizer.rs b/app/src/tokenizer.rs
index 13bce0c..613a96c 100644
--- a/tmp/agent-patch-flux-pr-1759.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/src/tokenizer.rs
+++ b/app/src/tokenizer.rs
@@ -162,6 +162,8 @@ pub enum Token {
Ampersand,
/// Pipe `|`
Pipe,
+ /// Pipe followed by greater than `|>` (BigQuery pipe syntax)
+ PipeGt,
/// Caret `^`
Caret,
/// Left brace `{`
@@ -331,6 +333,7 @@ impl fmt::Display for Token {
Token::Ampersand => f.write_str("&"),
Token::Caret => f.write_str("^"),
Token::Pipe => f.write_str("|"),
+ Token::PipeGt => f.write_str("|>"),
Token::LBrace => f.write_str("{"),
Token::RBrace => f.write_str("}"),
Token::RArrow => f.write_str("=>"),
@@ -1392,6 +1395,10 @@ impl<'a> Tokenizer<'a> {
_ => self.start_binop_opt(chars, "|&", None),
}
}
+ Some('>') if self.dialect.supports_pipe_syntax() => {
+ chars.next();
+ return Ok(Some(Token::PipeGt));
+ }
Some('>') if self.dialect.supports_geometric_types() => {
chars.next(); // consume
match chars.peek() {
diff --git a/tmp/agent-patch-flux-pr-1759.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/tests/sqlparser_bigquery.rs b/app/tests/sqlparser_bigquery.rs
index 416d2e4..2d9563f 100644
--- a/tmp/agent-patch-flux-pr-1759.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/tests/sqlparser_bigquery.rs
+++ b/app/tests/sqlparser_bigquery.rs
@@ -115,6 +115,27 @@ fn parse_literal_string() {
);
}
+#[test]
+fn parse_bigquery_pipe_syntax() {
+ let sql = concat!(
+ "FROM `project.dataset.sales` ",
+ "|> SELECT product, amount ",
+ "|> WHERE amount > 100 ",
+ "|> ORDER BY amount DESC",
+ );
+
+ let query = bigquery().verified_query(sql);
+ if let SetExpr::Pipe { input, operators } = *query.body {
+ assert!(matches!(*input, SetExpr::Select(_)));
+ assert_eq!(operators.len(), 3);
+ assert_eq!(operators[0].to_string(), "SELECT product , amount");
+ assert_eq!(operators[1].to_string(), "WHERE amount > 100");
+ assert_eq!(operators[2].to_string(), "ORDER BY amount DESC");
+ } else {
+ panic!("expected pipe query");
+ }
+}
+
#[test]
fn parse_byte_literal() {
let sql = concat!(