STET

agent_patch

runs/2026-02-21__03-36-16__gpt-5-1-codex-mini/flux-pr-2185/flux-pr-2185.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/agent-logs/agent.patch

12889 bytes

Back to adjudication
diff --git a/tmp/agent-patch-flux-pr-2185.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/src/ast/query.rs b/app/src/ast/query.rs
index bb2d889..236928c 100644
--- a/tmp/agent-patch-flux-pr-2185.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/src/ast/query.rs
+++ b/app/src/ast/query.rs
@@ -1208,18 +1208,26 @@ impl fmt::Display for TableWithJoins {
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
 pub struct ConnectBy {
-    /// START WITH
-    pub condition: Expr,
-    /// CONNECT BY
+    /// Optional START WITH predicate that seeds the traversal.
+    pub start_with: Option<Expr>,
+    /// CONNECT BY expressions describing parent-child relationships.
     pub relationships: Vec<Expr>,
+    /// NOCYCLE modifier presence, see Oracle documentation.
+    pub nocycle: bool,
 }
 
 impl fmt::Display for ConnectBy {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        if let Some(condition) = &self.start_with {
+            write!(f, "START WITH {condition} ")?;
+        }
+        write!(f, "CONNECT BY")?;
+        if self.nocycle {
+            write!(f, " NOCYCLE")?;
+        }
         write!(
             f,
-            "START WITH {condition} CONNECT BY {relationships}",
-            condition = self.condition,
+            " {relationships}",
             relationships = display_comma_separated(&self.relationships)
         )
     }
diff --git a/tmp/agent-patch-flux-pr-2185.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/src/ast/spans.rs b/app/src/ast/spans.rs
index ffc96ed..7638d79 100644
--- a/tmp/agent-patch-flux-pr-2185.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/src/ast/spans.rs
+++ b/app/src/ast/spans.rs
@@ -2283,13 +2283,11 @@ impl Spanned for Select {
 
 impl Spanned for ConnectBy {
     fn span(&self) -> Span {
-        let ConnectBy {
-            condition,
-            relationships,
-        } = self;
-
         union_spans(
-            core::iter::once(condition.span()).chain(relationships.iter().map(|item| item.span())),
+            self.start_with
+                .iter()
+                .map(|expr| expr.span())
+                .chain(self.relationships.iter().map(|item| item.span())),
         )
     }
 }
diff --git a/tmp/agent-patch-flux-pr-2185.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/src/dialect/oracle.rs b/app/src/dialect/oracle.rs
index a72d5d7..eb65b5c 100644
--- a/tmp/agent-patch-flux-pr-2185.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/src/dialect/oracle.rs
+++ b/app/src/dialect/oracle.rs
@@ -18,6 +18,8 @@
 use log::debug;
 
 use crate::{
+    ast::{Expr, Ident},
+    keywords::Keyword,
     parser::{Parser, ParserError},
     tokenizer::Token,
 };
@@ -103,4 +105,14 @@ impl Dialect for OracleDialect {
     fn supports_comment_optimizer_hint(&self) -> bool {
         true
     }
+
+    fn parse_prefix(&self, parser: &mut Parser) -> Option<Result<Expr, ParserError>> {
+        if parser.parse_keyword(Keyword::CONNECT_BY_ROOT) {
+            return Some(parser.parse_expr().map(|expr| Expr::Prefixed {
+                prefix: Ident::new("CONNECT_BY_ROOT"),
+                value: Box::new(expr),
+            }));
+        }
+        None
+    }
 }
diff --git a/tmp/agent-patch-flux-pr-2185.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/src/parser/mod.rs b/app/src/parser/mod.rs
index bddafd1..cf71628 100644
--- a/tmp/agent-patch-flux-pr-2185.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/src/parser/mod.rs
+++ b/app/src/parser/mod.rs
@@ -14280,25 +14280,47 @@ 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 nocycle = false;
+
+        loop {
+            if self.parse_keywords(&[Keyword::START, Keyword::WITH]) {
+                if start_with.is_some() {
+                    return parser_err!(
+                        "Multiple START WITH clauses",
+                        self.peek_token().span.start
+                    );
+                }
+                start_with = Some(self.parse_expr()?);
+                continue;
+            }
+
+            if self.parse_keywords(&[Keyword::CONNECT, Keyword::BY]) {
+                if relationships.is_some() {
+                    return parser_err!(
+                        "Multiple CONNECT BY clauses",
+                        self.peek_token().span.start
+                    );
+                }
+                nocycle = self.parse_keyword(Keyword::NOCYCLE);
+                relationships = Some(self.with_state(ParserState::ConnectBy, |parser| {
+                    parser.parse_comma_separated(Parser::parse_expr)
+                })?);
+                continue;
+            }
+
+            break;
+        }
+
+        let relationships = relationships.ok_or_else(|| {
+            parser_err!("Expected CONNECT BY clause", self.peek_token().span.start)
+        })?;
+
         Ok(ConnectBy {
-            condition,
+            start_with,
             relationships,
+            nocycle,
         })
     }
 
diff --git a/tmp/agent-patch-flux-pr-2185.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/tests/sqlparser_common.rs b/app/tests/sqlparser_common.rs
index b442ec4..112aecf 100644
--- a/tmp/agent-patch-flux-pr-2185.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/tests/sqlparser_common.rs
+++ b/app/tests/sqlparser_common.rs
@@ -12675,13 +12675,13 @@ 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(),
                 )),
-            },
+            }),
             relationships: vec![Expr::BinaryOp {
                 left: Box::new(Expr::Identifier(Ident::new("manager_id"))),
                 op: BinaryOperator::Eq,
@@ -12689,6 +12689,7 @@ fn parse_connect_by() {
                     "employee_id",
                 ))))),
             }],
+            nocycle: false,
         }),
         flavor: SelectFlavor::Standard,
     };
@@ -12763,13 +12764,13 @@ 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()
                     )),
-                },
+                }),
                 relationships: vec![Expr::BinaryOp {
                     left: Box::new(Expr::Identifier(Ident::new("manager_id"))),
                     op: BinaryOperator::Eq,
@@ -12777,6 +12778,7 @@ fn parse_connect_by() {
                         "employee_id",
                     ))))),
                 }],
+                nocycle: false,
             }),
             flavor: SelectFlavor::Standard,
         }
@@ -12805,6 +12807,64 @@ fn parse_connect_by() {
     );
 }
 
+#[test]
+fn parse_connect_by_without_start() {
+    let sql = concat!(
+        "SELECT employee_id, manager_id, title FROM employees ",
+        "CONNECT BY manager_id = PRIOR employee_id ",
+        "ORDER BY employee_id"
+    );
+
+    let select = all_dialects_where(|d| d.supports_connect_by()).verified_only_select(sql);
+    let connect_by = select.connect_by.expect("expected CONNECT BY clause");
+    assert!(connect_by.start_with.is_none());
+    assert!(!connect_by.nocycle);
+
+    let expected_relationship = 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"))))),
+    };
+    assert_eq!(connect_by.relationships, vec![expected_relationship]);
+}
+
+#[test]
+fn parse_connect_by_nocycle() {
+    let sql = 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"
+    );
+    let canonical = 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"
+    );
+
+    let select = all_dialects_where(|d| d.supports_connect_by())
+        .verified_only_select_with_canonical(sql, canonical);
+    let connect_by = select.connect_by.expect("expected CONNECT BY clause");
+    assert!(connect_by.nocycle);
+
+    let expected_start = 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(),
+        )),
+    };
+    assert_eq!(connect_by.start_with, Some(expected_start.clone()));
+
+    let expected_relationship = 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"))))),
+    };
+    assert_eq!(connect_by.relationships, vec![expected_relationship]);
+}
+
 #[test]
 fn test_selective_aggregation() {
     let testing_dialects = all_dialects_where(|d| d.supports_filter_during_aggregation());
diff --git a/tmp/agent-patch-flux-pr-2185.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/tests/sqlparser_oracle.rs b/app/tests/sqlparser_oracle.rs
index 1c12f86..98cc14c 100644
--- a/tmp/agent-patch-flux-pr-2185.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/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,39 @@ fn test_optimizer_hints() {
                VALUES (ps.person_id, ps.first_name, ps.last_name, ps.title)",
     );
 }
+
+#[test]
+fn parse_connect_by_root_operator_oracle() {
+    let sql = "SELECT CONNECT_BY_ROOT name AS root_name FROM dual";
+    let select = oracle().verified_only_select(sql);
+    match &select.projection[0] {
+        SelectItem::ExprWithAlias { expr, alias } => {
+            assert_eq!(
+                expr,
+                &Expr::Prefixed {
+                    prefix: Ident::new("CONNECT_BY_ROOT"),
+                    value: Box::new(Expr::Identifier(Ident::new("name")))
+                }
+            );
+            assert_eq!(alias, &Ident::new("root_name"));
+        }
+        _ => panic!("expected ExprWithAlias"),
+    }
+
+    let sql = "SELECT CONNECT_BY_ROOT name FROM dual";
+    let select = oracle().verified_only_select(sql);
+    assert_eq!(
+        select.projection[0],
+        SelectItem::UnnamedExpr(Expr::Prefixed {
+            prefix: Ident::new("CONNECT_BY_ROOT"),
+            value: Box::new(Expr::Identifier(Ident::new("name")))
+        })
+    );
+
+    let sql = "SELECT CONNECT_BY_ROOT FROM dual";
+    let err = oracle().parse_sql_statements(sql);
+    assert_eq!(
+        err.unwrap_err().to_string(),
+        "sql parser error: Expected an expression, found: FROM"
+    );
+}