STET

agent_patch

runs/2026-02-28__18-27-22__gpt-5-1-codex-mini/flux-pr-1984/flux-pr-1984.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/agent-logs/agent.patch

17834 bytes

Back to adjudication
diff --git a/tmp/agent-patch-flux-pr-1984.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/src/ast/data_type.rs b/app/src/ast/data_type.rs
index 0897f2d..5c37d41 100644
--- a/tmp/agent-patch-flux-pr-1984.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/src/ast/data_type.rs
+++ b/app/src/ast/data_type.rs
@@ -25,7 +25,7 @@ use serde::{Deserialize, Serialize};
 #[cfg(feature = "visitor")]
 use sqlparser_derive::{Visit, VisitMut};
 
-use crate::ast::{display_comma_separated, Expr, ObjectName, StructField, UnionField};
+use crate::ast::{display_comma_separated, DateTimeField, Expr, ObjectName, StructField, UnionField};
 
 use super::{value::escape_single_quote_string, ColumnDef};
 
@@ -456,6 +456,27 @@ pub enum DataType {
     TsQuery,
 }
 
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct IntervalDataType {
+    pub leading_field: Option<DateTimeField>,
+    pub leading_precision: Option<u64>,
+    pub last_field: Option<DateTimeField>,
+    pub fractional_seconds_precision: Option<u64>,
+}
+
+impl Default for IntervalDataType {
+    fn default() -> Self {
+        Self {
+            leading_field: None,
+            leading_precision: None,
+            last_field: None,
+            fractional_seconds_precision: None,
+        }
+    }
+}
+
 impl fmt::Display for DataType {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         match self {
@@ -635,7 +656,16 @@ impl fmt::Display for DataType {
                     timezone,
                 )
             }
-            DataType::Interval => write!(f, "INTERVAL"),
+            DataType::Interval(interval) => {
+                write!(f, "INTERVAL")?;
+                fmt_interval_qualifier(
+                    f,
+                    interval.leading_field.as_ref(),
+                    interval.leading_precision,
+                    interval.last_field.as_ref(),
+                    interval.fractional_seconds_precision,
+                )
+            }
             DataType::JSON => write!(f, "JSON"),
             DataType::JSONB => write!(f, "JSONB"),
             DataType::Regclass => write!(f, "REGCLASS"),
@@ -1055,3 +1085,40 @@ impl fmt::Display for GeometricTypeKind {
         }
     }
 }
+
+pub(crate) fn fmt_interval_qualifier(
+    f: &mut fmt::Formatter<'_>,
+    leading_field: Option<&DateTimeField>,
+    leading_precision: Option<u64>,
+    last_field: Option<&DateTimeField>,
+    fractional_seconds_precision: Option<u64>,
+) -> fmt::Result {
+    let Some(field) = leading_field else {
+        return Ok(());
+    };
+
+    if matches!(field, DateTimeField::Second)
+        && last_field.is_none()
+        && leading_precision.is_some()
+        && fractional_seconds_precision.is_some()
+    {
+        write!(
+            f,
+            " {field} ({}, {})",
+            leading_precision.unwrap(),
+            fractional_seconds_precision.unwrap()
+        )
+    } else {
+        write!(f, " {field}")?;
+        if let Some(leading_precision) = leading_precision {
+            write!(f, " ({leading_precision})")?;
+        }
+        if let Some(last_field) = last_field {
+            write!(f, " TO {last_field}")?;
+        }
+        if let Some(fractional_seconds_precision) = fractional_seconds_precision {
+            write!(f, " ({fractional_seconds_precision})")?;
+        }
+        Ok(())
+    }
+}
diff --git a/tmp/agent-patch-flux-pr-1984.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/src/ast/mod.rs b/app/src/ast/mod.rs
index 34112ac..0661855 100644
--- a/tmp/agent-patch-flux-pr-1984.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/src/ast/mod.rs
+++ b/app/src/ast/mod.rs
@@ -52,7 +52,7 @@ use crate::{
 
 pub use self::data_type::{
     ArrayElemTypeDef, BinaryLength, CharLengthUnits, CharacterLength, DataType, EnumMember,
-    ExactNumberInfo, StructBracketKind, TimezoneInfo,
+    ExactNumberInfo, IntervalDataType, StructBracketKind, TimezoneInfo,
 };
 pub use self::dcl::{
     AlterRoleOperation, ResetConfig, RoleOption, SecondaryRoles, SetConfigValue, Use,
@@ -434,41 +434,14 @@ pub struct Interval {
 impl fmt::Display for Interval {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         let value = self.value.as_ref();
-        match (
-            &self.leading_field,
+        write!(f, "INTERVAL {value}")?;
+        crate::ast::data_type::fmt_interval_qualifier(
+            f,
+            self.leading_field.as_ref(),
             self.leading_precision,
+            self.last_field.as_ref(),
             self.fractional_seconds_precision,
-        ) {
-            (
-                Some(DateTimeField::Second),
-                Some(leading_precision),
-                Some(fractional_seconds_precision),
-            ) => {
-                // When the leading field is SECOND, the parser guarantees that
-                // the last field is None.
-                assert!(self.last_field.is_none());
-                write!(
-                    f,
-                    "INTERVAL {value} SECOND ({leading_precision}, {fractional_seconds_precision})"
-                )
-            }
-            _ => {
-                write!(f, "INTERVAL {value}")?;
-                if let Some(leading_field) = &self.leading_field {
-                    write!(f, " {leading_field}")?;
-                }
-                if let Some(leading_precision) = self.leading_precision {
-                    write!(f, " ({leading_precision})")?;
-                }
-                if let Some(last_field) = &self.last_field {
-                    write!(f, " TO {last_field}")?;
-                }
-                if let Some(fractional_seconds_precision) = self.fractional_seconds_precision {
-                    write!(f, " ({fractional_seconds_precision})")?;
-                }
-                Ok(())
-            }
-        }
+        )
     }
 }
 
diff --git a/tmp/agent-patch-flux-pr-1984.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/src/dialect/generic.rs b/app/src/dialect/generic.rs
index de83d50..3a65dab 100644
--- a/tmp/agent-patch-flux-pr-1984.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/src/dialect/generic.rs
+++ b/app/src/dialect/generic.rs
@@ -187,4 +187,8 @@ impl Dialect for GenericDialect {
     fn supports_data_type_signed_suffix(&self) -> bool {
         true
     }
+
+    fn supports_interval_data_type_qualifier(&self) -> bool {
+        true
+    }
 }
diff --git a/tmp/agent-patch-flux-pr-1984.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/src/dialect/mod.rs b/app/src/dialect/mod.rs
index 9003220..7c944c1 100644
--- a/tmp/agent-patch-flux-pr-1984.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/src/dialect/mod.rs
+++ b/app/src/dialect/mod.rs
@@ -868,6 +868,12 @@ pub trait Dialect: Debug + Any {
         false
     }
 
+    /// Returns true if this dialect supports PostgreSQL-style `INTERVAL` qualifiers
+    /// on data types (e.g., `INTERVAL YEAR TO MONTH` or `INTERVAL SECOND(3)`).
+    fn supports_interval_data_type_qualifier(&self) -> bool {
+        false
+    }
+
     fn supports_explain_with_utility_options(&self) -> bool {
         false
     }
diff --git a/tmp/agent-patch-flux-pr-1984.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/src/dialect/postgresql.rs b/app/src/dialect/postgresql.rs
index b12abaa..0aeaa00 100644
--- a/tmp/agent-patch-flux-pr-1984.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/src/dialect/postgresql.rs
+++ b/app/src/dialect/postgresql.rs
@@ -140,6 +140,10 @@ impl Dialect for PostgreSqlDialect {
         true
     }
 
+    fn supports_interval_data_type_qualifier(&self) -> bool {
+        true
+    }
+
     fn supports_group_by_expr(&self) -> bool {
         true
     }
diff --git a/tmp/agent-patch-flux-pr-1984.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/src/parser/mod.rs b/app/src/parser/mod.rs
index 455e0ca..7e4a403 100644
--- a/tmp/agent-patch-flux-pr-1984.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/src/parser/mod.rs
+++ b/app/src/parser/mod.rs
@@ -1534,7 +1534,7 @@ impl<'a> Parser<'a> {
         let loc = self.peek_token_ref().span.start;
         let opt_expr = self.maybe_parse(|parser| {
             match parser.parse_data_type()? {
-                DataType::Interval => parser.parse_interval(),
+                DataType::Interval(_) => parser.parse_interval(),
                 // PostgreSQL allows almost any identifier to be used as custom data type name,
                 // and we support that in `parse_data_type()`. But unlike Postgres we don't
                 // have a list of globally reserved keywords (since they vary across dialects),
@@ -2944,29 +2944,7 @@ impl<'a> Parser<'a> {
         };
 
         let (leading_precision, last_field, fsec_precision) =
-            if leading_field == Some(DateTimeField::Second) {
-                // SQL mandates special syntax for `SECOND TO SECOND` literals.
-                // Instead of
-                //     `SECOND [(<leading precision>)] TO SECOND[(<fractional seconds precision>)]`
-                // one must use the special format:
-                //     `SECOND [( <leading precision> [ , <fractional seconds precision>] )]`
-                let last_field = None;
-                let (leading_precision, fsec_precision) = self.parse_optional_precision_scale()?;
-                (leading_precision, last_field, fsec_precision)
-            } else {
-                let leading_precision = self.parse_optional_precision()?;
-                if self.parse_keyword(Keyword::TO) {
-                    let last_field = Some(self.parse_date_time_field()?);
-                    let fsec_precision = if last_field == Some(DateTimeField::Second) {
-                        self.parse_optional_precision()?
-                    } else {
-                        None
-                    };
-                    (leading_precision, last_field, fsec_precision)
-                } else {
-                    (leading_precision, None, None)
-                }
-            };
+            self.parse_interval_precision_and_fields(leading_field.as_ref())?;
 
         Ok(Expr::Interval(Interval {
             value: Box::new(value),
@@ -2977,6 +2955,32 @@ impl<'a> Parser<'a> {
         }))
     }
 
+    fn parse_interval_precision_and_fields(
+        &mut self,
+        leading_field: Option<&DateTimeField>,
+    ) -> Result<(Option<u64>, Option<DateTimeField>, Option<u64>), ParserError> {
+        if leading_field == Some(&DateTimeField::Second) {
+            let (leading_precision, fractional_seconds_precision) =
+                self.parse_optional_precision_scale()?;
+            Ok((leading_precision, None, fractional_seconds_precision))
+        } else if leading_field.is_some() {
+            let leading_precision = self.parse_optional_precision()?;
+            if self.parse_keyword(Keyword::TO) {
+                let last_field = Some(self.parse_date_time_field()?);
+                let fractional_seconds_precision = if last_field == Some(DateTimeField::Second) {
+                    self.parse_optional_precision()?
+                } else {
+                    None
+                };
+                Ok((leading_precision, last_field, fractional_seconds_precision))
+            } else {
+                Ok((leading_precision, None, None))
+            }
+        } else {
+            Ok((None, None, None))
+        }
+    }
+
     /// Peek at the next token and determine if it is a temporal unit
     /// like `second`.
     pub fn next_token_is_temporal_unit(&mut self) -> bool {
@@ -10069,7 +10073,24 @@ impl<'a> Parser<'a> {
                 // Interval types can be followed by a complicated interval
                 // qualifier that we don't currently support. See
                 // parse_interval for a taste.
-                Keyword::INTERVAL => Ok(DataType::Interval),
+                Keyword::INTERVAL => {
+                    let interval_data_type = if dialect.supports_interval_data_type_qualifier()
+                        && self.next_token_is_temporal_unit()
+                    {
+                        let leading_field = Some(self.parse_date_time_field()?);
+                        let (leading_precision, last_field, fractional_seconds_precision) =
+                            self.parse_interval_precision_and_fields(leading_field.as_ref())?;
+                        IntervalDataType {
+                            leading_field,
+                            leading_precision,
+                            last_field,
+                            fractional_seconds_precision,
+                        }
+                    } else {
+                        IntervalDataType::default()
+                    };
+                    Ok(DataType::Interval(interval_data_type))
+                }
                 Keyword::JSON => Ok(DataType::JSON),
                 Keyword::JSONB => Ok(DataType::JSONB),
                 Keyword::REGCLASS => Ok(DataType::Regclass),
diff --git a/tmp/agent-patch-flux-pr-1984.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/tests/sqlparser_bigquery.rs b/app/tests/sqlparser_bigquery.rs
index 839ea6b..bff466f 100644
--- a/tmp/agent-patch-flux-pr-1984.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/tests/sqlparser_bigquery.rs
+++ b/app/tests/sqlparser_bigquery.rs
@@ -961,7 +961,7 @@ fn parse_typed_struct_syntax_bigquery() {
             })],
             fields: vec![StructField {
                 field_name: None,
-                field_type: DataType::Interval,
+                field_type: DataType::Interval(IntervalDataType::default()),
                 options: None,
             }]
         },
@@ -1300,7 +1300,7 @@ fn parse_typed_struct_syntax_bigquery_and_generic() {
             })],
             fields: vec![StructField {
                 field_name: None,
-                field_type: DataType::Interval,
+                field_type: DataType::Interval(IntervalDataType::default()),
                 options: None,
             }]
         },
diff --git a/tmp/agent-patch-flux-pr-1984.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/tests/sqlparser_common.rs b/app/tests/sqlparser_common.rs
index 5e389ae..89d80dc 100644
--- a/tmp/agent-patch-flux-pr-1984.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/tests/sqlparser_common.rs
+++ b/app/tests/sqlparser_common.rs
@@ -12955,7 +12955,7 @@ fn test_extract_seconds_ok() {
                 expr: Box::new(Expr::Value(
                     (Value::SingleQuotedString("2 seconds".to_string())).with_empty_span()
                 )),
-                data_type: DataType::Interval,
+                data_type: DataType::Interval(IntervalDataType::default()),
                 format: None,
             }),
         }
@@ -12980,7 +12980,7 @@ fn test_extract_seconds_ok() {
                     expr: Box::new(Expr::Value(
                         (Value::SingleQuotedString("2 seconds".to_string())).with_empty_span(),
                     )),
-                    data_type: DataType::Interval,
+                    data_type: DataType::Interval(IntervalDataType::default()),
                     format: None,
                 }),
             })],
@@ -13034,7 +13034,7 @@ fn test_extract_seconds_single_quote_ok() {
                 expr: Box::new(Expr::Value(
                     (Value::SingleQuotedString("2 seconds".to_string())).with_empty_span()
                 )),
-                data_type: DataType::Interval,
+                data_type: DataType::Interval(IntervalDataType::default()),
                 format: None,
             }),
         }
diff --git a/tmp/agent-patch-flux-pr-1984.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/tests/sqlparser_postgres.rs b/app/tests/sqlparser_postgres.rs
index 6ce4a48..1d65939 100644
--- a/tmp/agent-patch-flux-pr-1984.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/tests/sqlparser_postgres.rs
+++ b/app/tests/sqlparser_postgres.rs
@@ -6218,6 +6218,50 @@ fn parse_varbit_datatype() {
     }
 }
 
+#[test]
+fn parse_interval_datatype_qualifiers() {
+    match pg_and_generic().verified_stmt("CREATE TABLE foo (i INTERVAL HOUR TO SECOND(3))") {
+        Statement::CreateTable(CreateTable { columns, .. }) => {
+            assert_eq!(
+                columns,
+                vec![ColumnDef {
+                    name: "i".into(),
+                    data_type: DataType::Interval(IntervalDataType {
+                        leading_field: Some(DateTimeField::Hour),
+                        leading_precision: None,
+                        last_field: Some(DateTimeField::Second),
+                        fractional_seconds_precision: Some(3),
+                    }),
+                    options: vec![],
+                },]
+            );
+        }
+        _ => unreachable!(),
+    }
+}
+
+#[test]
+fn parse_interval_datatype_second_precision() {
+    match pg_and_generic().verified_stmt("CREATE TABLE foo (i INTERVAL SECOND(3))") {
+        Statement::CreateTable(CreateTable { columns, .. }) => {
+            assert_eq!(
+                columns,
+                vec![ColumnDef {
+                    name: "i".into(),
+                    data_type: DataType::Interval(IntervalDataType {
+                        leading_field: Some(DateTimeField::Second),
+                        leading_precision: Some(3),
+                        last_field: None,
+                        fractional_seconds_precision: None,
+                    }),
+                    options: vec![],
+                },]
+            );
+        }
+        _ => unreachable!(),
+    }
+}
+
 #[test]
 fn parse_alter_table_replica_identity() {
     match pg_and_generic().verified_stmt("ALTER TABLE foo REPLICA IDENTITY FULL") {