STET

agent_patch

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

14613 bytes

Back to adjudication
diff --git a/tmp/agent-patch-flux-pr-1984.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/src/ast/data_type.rs b/app/src/ast/data_type.rs
index 0897f2d..6dba96b 100644
--- a/tmp/agent-patch-flux-pr-1984.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/src/ast/data_type.rs
+++ b/app/src/ast/data_type.rs
@@ -27,7 +27,8 @@ use sqlparser_derive::{Visit, VisitMut};
 
 use crate::ast::{display_comma_separated, Expr, ObjectName, StructField, UnionField};
 
-use super::{value::escape_single_quote_string, ColumnDef};
+use super::{value::{escape_single_quote_string, DateTimeField}, ColumnDef};
+
 
 #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -40,6 +41,55 @@ pub enum EnumMember {
     NamedValue(String, Expr),
 }
 
+/// Describes the leading/last fields and precision options for PostgreSQL-style
+/// `INTERVAL` qualifiers.
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct IntervalFieldSpec {
+    pub leading_field: DateTimeField,
+    pub leading_precision: Option<u64>,
+    pub last_field: Option<DateTimeField>,
+    pub fractional_seconds_precision: Option<u64>,
+}
+
+impl fmt::Display for IntervalFieldSpec {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        if self.leading_field == DateTimeField::Second {
+            match (self.leading_precision, self.fractional_seconds_precision) {
+                (Some(leading_precision), Some(fractional_precision)) => {
+                    write!(
+                        f,
+                        "{} ({}, {})",
+                        self.leading_field, leading_precision, fractional_precision
+                    )
+                }
+                (Some(leading_precision), None) => {
+                    write!(f, "{} ({})", self.leading_field, leading_precision)
+                }
+                (None, Some(fractional_precision)) => {
+                    write!(f, "{} ({})", self.leading_field, fractional_precision)
+                }
+                (None, None) => write!(f, "{}", self.leading_field),
+            }
+        } else {
+            write!(f, "{}", self.leading_field)?;
+            if let Some(precision) = self.leading_precision {
+                write!(f, " ({precision})")?;
+            }
+            if let Some(last_field) = &self.last_field {
+                write!(f, " TO {last_field}")?;
+                if *last_field == DateTimeField::Second {
+                    if let Some(fractional_precision) = self.fractional_seconds_precision {
+                        write!(f, " ({fractional_precision})")?;
+                    }
+                }
+            }
+            Ok(())
+        }
+    }
+}
+
 /// SQL data types
 #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -345,8 +395,8 @@ pub enum DataType {
     ///
     /// [1]: https://docs.databricks.com/aws/en/sql/language-manual/data-types/timestamp-ntz-type
     TimestampNtz,
-    /// Interval type.
-    Interval,
+    /// Interval type with optional PostgreSQL-style field qualifiers.
+    Interval(Option<IntervalFieldSpec>),
     /// JSON type.
     JSON,
     /// Binary JSON type.
@@ -635,7 +685,13 @@ impl fmt::Display for DataType {
                     timezone,
                 )
             }
-            DataType::Interval => write!(f, "INTERVAL"),
+            DataType::Interval(spec) => {
+                write!(f, "INTERVAL")?;
+                if let Some(spec) = spec {
+                    write!(f, " {spec}")?;
+                }
+                Ok(())
+            }
             DataType::JSON => write!(f, "JSON"),
             DataType::JSONB => write!(f, "JSONB"),
             DataType::Regclass => write!(f, "REGCLASS"),
diff --git a/tmp/agent-patch-flux-pr-1984.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/src/ast/mod.rs b/app/src/ast/mod.rs
index 34112ac..7272ca5 100644
--- a/tmp/agent-patch-flux-pr-1984.1-of-1.2026-02-21__03-36-16__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, IntervalFieldSpec, StructBracketKind, TimezoneInfo,
 };
 pub use self::dcl::{
     AlterRoleOperation, ResetConfig, RoleOption, SecondaryRoles, SetConfigValue, Use,
diff --git a/tmp/agent-patch-flux-pr-1984.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/src/dialect/generic.rs b/app/src/dialect/generic.rs
index de83d50..3b07be1 100644
--- a/tmp/agent-patch-flux-pr-1984.1-of-1.2026-02-21__03-36-16__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_qualifiers(&self) -> bool {
+        true
+    }
 }
diff --git a/tmp/agent-patch-flux-pr-1984.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/src/dialect/mod.rs b/app/src/dialect/mod.rs
index 9003220..b58a8d8 100644
--- a/tmp/agent-patch-flux-pr-1984.1-of-1.2026-02-21__03-36-16__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
     }
 
+    /// Whether the dialect supports PostgreSQL-style interval qualifiers on
+    /// the `INTERVAL` data type (e.g. `INTERVAL YEAR TO MONTH`).
+    fn supports_interval_data_type_qualifiers(&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-21__03-36-16__gpt-5-1-codex-mini/app/src/dialect/postgresql.rs b/app/src/dialect/postgresql.rs
index b12abaa..aee6410 100644
--- a/tmp/agent-patch-flux-pr-1984.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/src/dialect/postgresql.rs
+++ b/app/src/dialect/postgresql.rs
@@ -144,6 +144,10 @@ impl Dialect for PostgreSqlDialect {
         true
     }
 
+    fn supports_interval_data_type_qualifiers(&self) -> bool {
+        true
+    }
+
     fn prec_value(&self, prec: Precedence) -> u8 {
         match prec {
             Precedence::Period => PERIOD_PREC,
diff --git a/tmp/agent-patch-flux-pr-1984.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 455e0ca..26a517d 100644
--- a/tmp/agent-patch-flux-pr-1984.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
@@ -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),
@@ -2977,6 +2977,49 @@ impl<'a> Parser<'a> {
         }))
     }
 
+    pub fn parse_optional_interval_field_spec(
+        &mut self,
+    ) -> Result<Option<IntervalFieldSpec>, ParserError> {
+        if self.next_token_is_temporal_unit() {
+            Ok(Some(self.parse_interval_field_spec()?))
+        } else {
+            Ok(None)
+        }
+    }
+
+    pub fn parse_interval_field_spec(&mut self) -> Result<IntervalFieldSpec, ParserError> {
+        let leading_field = self.parse_date_time_field()?;
+        if leading_field == DateTimeField::Second {
+            let (leading_precision, fractional_precision) = self.parse_optional_precision_scale()?;
+            Ok(IntervalFieldSpec {
+                leading_field,
+                leading_precision,
+                last_field: None,
+                fractional_seconds_precision: fractional_precision,
+            })
+        } else {
+            let leading_precision = self.parse_optional_precision()?;
+            let (last_field, fractional_seconds_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
+                };
+                (last_field, fractional_seconds_precision)
+            } else {
+                (None, None)
+            };
+
+            Ok(IntervalFieldSpec {
+                leading_field,
+                leading_precision,
+                last_field,
+                fractional_seconds_precision,
+            })
+        }
+    }
+
     /// 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 +10112,14 @@ 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 spec = if dialect.supports_interval_data_type_qualifiers() {
+                        self.parse_optional_interval_field_spec()?
+                    } else {
+                        None
+                    };
+                    Ok(DataType::Interval(spec))
+                }
                 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-21__03-36-16__gpt-5-1-codex-mini/app/tests/sqlparser_bigquery.rs b/app/tests/sqlparser_bigquery.rs
index 839ea6b..382daee 100644
--- a/tmp/agent-patch-flux-pr-1984.1-of-1.2026-02-21__03-36-16__gpt-5-1-codex-mini/app/tests/sqlparser_bigquery.rs
+++ b/app/tests/sqlparser_bigquery.rs
@@ -959,11 +959,11 @@ fn parse_typed_struct_syntax_bigquery() {
                 last_field: None,
                 fractional_seconds_precision: None
             })],
-            fields: vec![StructField {
-                field_name: None,
-                field_type: DataType::Interval,
-                options: None,
-            }]
+                fields: vec![StructField {
+                    field_name: None,
+                    field_type: DataType::Interval(None),
+                    options: None,
+                }]
         },
         expr_from_projection(&select.projection[0])
     );
@@ -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(None),
                 options: None,
             }]
         },
diff --git a/tmp/agent-patch-flux-pr-1984.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 5e389ae..0f67e52 100644
--- a/tmp/agent-patch-flux-pr-1984.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
@@ -6316,6 +6316,46 @@ fn parse_interval_disallow_interval_expr() {
     );
 }
 
+#[test]
+fn parse_interval_data_type_qualifiers() {
+    let dialects = all_dialects_where(|d| d.supports_interval_data_type_qualifiers());
+
+    let stmt = dialects.verified_stmt("CREATE TABLE t (i INTERVAL YEAR TO MONTH)");
+    match stmt {
+        Statement::CreateTable(create_table) => {
+            assert_eq!(1, create_table.columns.len());
+            assert_eq!(
+                DataType::Interval(Some(IntervalFieldSpec {
+                    leading_field: DateTimeField::Year,
+                    leading_precision: None,
+                    last_field: Some(DateTimeField::Month),
+                    fractional_seconds_precision: None,
+                })),
+                create_table.columns[0].data_type
+            );
+        }
+        _ => panic!("Expected CREATE TABLE statement"),
+    }
+
+    let expr = dialects.verified_expr("'1 second'::INTERVAL HOUR TO SECOND(3)");
+    assert_eq!(
+        Expr::Cast {
+            kind: CastKind::DoubleColon,
+            expr: Box::new(Expr::Value(
+                (Value::SingleQuotedString("1 second".to_string())).with_empty_span()
+            )),
+            data_type: DataType::Interval(Some(IntervalFieldSpec {
+                leading_field: DateTimeField::Hour,
+                leading_precision: None,
+                last_field: Some(DateTimeField::Second),
+                fractional_seconds_precision: Some(3),
+            })),
+            format: None,
+        },
+        expr
+    );
+}
+
 #[test]
 fn interval_disallow_interval_expr_gt() {
     let dialects = all_dialects_except(|d| d.require_interval_qualifier());
@@ -12955,7 +12995,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(None),
                 format: None,
             }),
         }
@@ -12980,7 +13020,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(None),
                     format: None,
                 }),
             })],
@@ -13034,7 +13074,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(None),
                 format: None,
             }),
         }