STET

agent_patch

runs/2026-02-28__18-27-22__gpt-5-3-codex/flux-pr-2185/flux-pr-2185.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/agent-logs/agent.patch

12014 bytes

Back to adjudication
diff --git a/tmp/agent-patch-flux-pr-2185.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/ast/query.rs b/app/src/ast/query.rs
index bb2d889..ae4e786 100644
--- a/tmp/agent-patch-flux-pr-2185.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/ast/query.rs
+++ b/app/src/ast/query.rs
@@ -501,7 +501,7 @@ pub struct Select {
     pub window_before_qualify: bool,
     /// BigQuery syntax: `SELECT AS VALUE | SELECT AS STRUCT`
     pub value_table_mode: Option<ValueTableMode>,
-    /// STARTING WITH .. CONNECT BY
+    /// START WITH .. CONNECT BY
     pub connect_by: Option<ConnectBy>,
     /// Was this a FROM-first query?
     pub flavor: SelectFlavor,
@@ -1209,19 +1209,23 @@ impl fmt::Display for TableWithJoins {
 #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
 pub struct ConnectBy {
     /// START WITH
-    pub condition: Expr,
+    pub start_with: Option<Expr>,
+    /// NOCYCLE
+    pub no_cycle: bool,
     /// CONNECT BY
     pub relationships: Vec<Expr>,
 }
 
 impl fmt::Display for ConnectBy {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(
-            f,
-            "START WITH {condition} CONNECT BY {relationships}",
-            condition = self.condition,
-            relationships = display_comma_separated(&self.relationships)
-        )
+        if let Some(start_with) = &self.start_with {
+            write!(f, "START WITH {start_with} ")?;
+        }
+        write!(f, "CONNECT BY ")?;
+        if self.no_cycle {
+            write!(f, "NOCYCLE ")?;
+        }
+        write!(f, "{}", display_comma_separated(&self.relationships))
     }
 }
 
diff --git a/tmp/agent-patch-flux-pr-2185.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/ast/spans.rs b/app/src/ast/spans.rs
index ffc96ed..3b1f768 100644
--- a/tmp/agent-patch-flux-pr-2185.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/ast/spans.rs
+++ b/app/src/ast/spans.rs
@@ -2284,12 +2284,16 @@ impl Spanned for Select {
 impl Spanned for ConnectBy {
     fn span(&self) -> Span {
         let ConnectBy {
-            condition,
+            start_with,
+            no_cycle: _,
             relationships,
         } = self;
 
         union_spans(
-            core::iter::once(condition.span()).chain(relationships.iter().map(|item| item.span())),
+            start_with
+                .iter()
+                .map(|item| item.span())
+                .chain(relationships.iter().map(|item| item.span())),
         )
     }
 }
diff --git a/tmp/agent-patch-flux-pr-2185.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/dialect/oracle.rs b/app/src/dialect/oracle.rs
index a72d5d7..0e53f10 100644
--- a/tmp/agent-patch-flux-pr-2185.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/dialect/oracle.rs
+++ b/app/src/dialect/oracle.rs
@@ -18,12 +18,15 @@
 use log::debug;
 
 use crate::{
+    keywords::Keyword,
     parser::{Parser, ParserError},
     tokenizer::Token,
 };
 
 use super::{Dialect, Precedence};
 
+const RESERVED_KEYWORDS_FOR_SELECT_ITEM_OPERATOR: [Keyword; 1] = [Keyword::CONNECT_BY_ROOT];
+
 /// A [`Dialect`] for [Oracle Databases](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/index.html)
 #[derive(Debug, Default)]
 pub struct OracleDialect;
@@ -103,4 +106,8 @@ impl Dialect for OracleDialect {
     fn supports_comment_optimizer_hint(&self) -> bool {
         true
     }
+
+    fn get_reserved_keywords_for_select_item_operator(&self) -> &[Keyword] {
+        &RESERVED_KEYWORDS_FOR_SELECT_ITEM_OPERATOR
+    }
 }
diff --git a/tmp/agent-patch-flux-pr-2185.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/keywords.rs b/app/src/keywords.rs
index 2e26bda..f84f4d2 100644
--- a/tmp/agent-patch-flux-pr-2185.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/keywords.rs
+++ b/app/src/keywords.rs
@@ -678,6 +678,7 @@ define_keywords!(
     NOCOMPRESS,
     NOCREATEDB,
     NOCREATEROLE,
+    NOCYCLE,
     NOINHERIT,
     NOLOGIN,
     NONE,
diff --git a/tmp/agent-patch-flux-pr-2185.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/parser/mod.rs b/app/src/parser/mod.rs
index bddafd1..9845c46 100644
--- a/tmp/agent-patch-flux-pr-2185.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/parser/mod.rs
+++ b/app/src/parser/mod.rs
@@ -14280,24 +14280,32 @@ impl<'a> Parser<'a> {
 
     /// Parse a `CONNECT BY` clause (Oracle-style hierarchical query support).
     pub fn parse_connect_by(&mut self) -> Result<ConnectBy, ParserError> {
-        let (condition, relationships) = if self.parse_keywords(&[Keyword::CONNECT, Keyword::BY]) {
-            let relationships = self.with_state(ParserState::ConnectBy, |parser| {
-                parser.parse_comma_separated(Parser::parse_expr)
-            })?;
-            self.expect_keywords(&[Keyword::START, Keyword::WITH])?;
-            let condition = self.parse_expr()?;
-            (condition, relationships)
-        } else {
-            self.expect_keywords(&[Keyword::START, Keyword::WITH])?;
-            let condition = self.parse_expr()?;
-            self.expect_keywords(&[Keyword::CONNECT, Keyword::BY])?;
-            let relationships = self.with_state(ParserState::ConnectBy, |parser| {
-                parser.parse_comma_separated(Parser::parse_expr)
-            })?;
-            (condition, relationships)
+        let mut start_with = None;
+        let mut relationships = None;
+        let mut no_cycle = false;
+
+        for _ in 0..2 {
+            if start_with.is_none() && self.parse_keywords(&[Keyword::START, Keyword::WITH]) {
+                start_with = Some(self.parse_expr()?);
+                continue;
+            }
+
+            if relationships.is_none() && self.parse_keywords(&[Keyword::CONNECT, Keyword::BY]) {
+                no_cycle = self.parse_keyword(Keyword::NOCYCLE);
+                relationships = Some(self.with_state(ParserState::ConnectBy, |parser| {
+                    parser.parse_comma_separated(Parser::parse_expr)
+                })?);
+                continue;
+            }
+        }
+
+        let Some(relationships) = relationships else {
+            return self.expected("CONNECT BY", self.peek_token());
         };
+
         Ok(ConnectBy {
-            condition,
+            start_with,
+            no_cycle,
             relationships,
         })
     }
diff --git a/tmp/agent-patch-flux-pr-2185.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_common.rs b/app/tests/sqlparser_common.rs
index b442ec4..29f44b7 100644
--- a/tmp/agent-patch-flux-pr-2185.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_common.rs
+++ b/app/tests/sqlparser_common.rs
@@ -12675,13 +12675,14 @@ fn parse_connect_by() {
         window_before_qualify: false,
         value_table_mode: None,
         connect_by: Some(ConnectBy {
-            condition: Expr::BinaryOp {
+            start_with: Some(Expr::BinaryOp {
                 left: Box::new(Expr::Identifier(Ident::new("title"))),
                 op: BinaryOperator::Eq,
                 right: Box::new(Expr::Value(
                     Value::SingleQuotedString("president".to_owned()).with_empty_span(),
                 )),
-            },
+            }),
+            no_cycle: false,
             relationships: vec![Expr::BinaryOp {
                 left: Box::new(Expr::Identifier(Ident::new("manager_id"))),
                 op: BinaryOperator::Eq,
@@ -12763,13 +12764,14 @@ fn parse_connect_by() {
             window_before_qualify: false,
             value_table_mode: None,
             connect_by: Some(ConnectBy {
-                condition: Expr::BinaryOp {
+                start_with: Some(Expr::BinaryOp {
                     left: Box::new(Expr::Identifier(Ident::new("title"))),
                     op: BinaryOperator::Eq,
                     right: Box::new(Expr::Value(
                         (Value::SingleQuotedString("president".to_owned(),)).with_empty_span()
                     )),
-                },
+                }),
+                no_cycle: false,
                 relationships: vec![Expr::BinaryOp {
                     left: Box::new(Expr::Identifier(Ident::new("manager_id"))),
                     op: BinaryOperator::Eq,
@@ -12793,6 +12795,58 @@ fn parse_connect_by() {
         .parse_sql_statements(connect_by_4)
         .expect_err("should have failed");
 
+    let connect_by_5 = concat!(
+        "SELECT employee_id, manager_id, title FROM employees ",
+        "CONNECT BY manager_id = PRIOR employee_id ",
+        "ORDER BY employee_id"
+    );
+    assert_eq!(
+        all_dialects_where(|d| d.supports_connect_by())
+            .verified_only_select_with_canonical(connect_by_5, connect_by_5)
+            .connect_by,
+        Some(ConnectBy {
+            start_with: None,
+            no_cycle: false,
+            relationships: vec![Expr::BinaryOp {
+                left: Box::new(Expr::Identifier(Ident::new("manager_id"))),
+                op: BinaryOperator::Eq,
+                right: Box::new(Expr::Prior(Box::new(Expr::Identifier(Ident::new(
+                    "employee_id",
+                ))))),
+            }],
+        })
+    );
+
+    let connect_by_6 = concat!(
+        "SELECT employee_id, manager_id, title FROM employees ",
+        "START WITH title = 'president' ",
+        "CONNECT BY NOCYCLE manager_id = PRIOR employee_id ",
+        "ORDER BY employee_id"
+    );
+    assert_eq!(
+        all_dialects_where(|d| d.supports_connect_by())
+            .verified_only_select(connect_by_6)
+            .connect_by
+            .unwrap(),
+        ConnectBy {
+            start_with: Some(Expr::BinaryOp {
+                left: Box::new(Expr::Identifier(Ident::new("title"))),
+                op: BinaryOperator::Eq,
+                right: Box::new(Expr::Value(
+                    Value::SingleQuotedString("president".to_owned()).with_empty_span(),
+                )),
+            }),
+            no_cycle: true,
+            relationships: vec![Expr::BinaryOp {
+                left: Box::new(Expr::Identifier(Ident::new("manager_id"))),
+                op: BinaryOperator::Eq,
+                right: Box::new(Expr::Prior(Box::new(Expr::Identifier(Ident::new(
+                    "employee_id",
+                ))))),
+            }],
+        }
+    );
+
     // PRIOR expressions are only valid within a CONNECT BY, and the the token
     // `prior` is valid as an identifier anywhere else.
     assert_eq!(
diff --git a/tmp/agent-patch-flux-pr-2185.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_oracle.rs b/app/tests/sqlparser_oracle.rs
index 1c12f86..7fa0d31 100644
--- a/tmp/agent-patch-flux-pr-2185.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_oracle.rs
+++ b/app/tests/sqlparser_oracle.rs
@@ -21,7 +21,7 @@
 use pretty_assertions::assert_eq;
 
 use sqlparser::{
-    ast::{BinaryOperator, Expr, Ident, QuoteDelimitedString, Value, ValueWithSpan},
+    ast::{BinaryOperator, Expr, Ident, QuoteDelimitedString, SelectItem, Value, ValueWithSpan},
     dialect::OracleDialect,
     parser::ParserError,
     tokenizer::Span,
@@ -388,3 +388,27 @@ fn test_optimizer_hints() {
                VALUES (ps.person_id, ps.first_name, ps.last_name, ps.title)",
     );
 }
+
+#[test]
+fn parse_connect_by_root_operator() {
+    let sql = "SELECT CONNECT_BY_ROOT name AS root_name FROM tbl1";
+    assert_eq!(
+        oracle().verified_only_select(sql).projection[0],
+        SelectItem::ExprWithAlias {
+            expr: Expr::Prefixed {
+                prefix: Ident::new("CONNECT_BY_ROOT"),
+                value: Box::new(Expr::Identifier(Ident::new("name"))),
+            },
+            alias: Ident::new("root_name"),
+        }
+    );
+
+    let sql = "SELECT CONNECT_BY_ROOT name FROM tbl2";
+    assert_eq!(
+        oracle().verified_only_select(sql).projection[0],
+        SelectItem::UnnamedExpr(Expr::Prefixed {
+            prefix: Ident::new("CONNECT_BY_ROOT"),
+            value: Box::new(Expr::Identifier(Ident::new("name"))),
+        })
+    );
+}