STET

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

Back to adjudication
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!(