STET

agent_patch

runs/2026-02-28__18-27-22__gpt-5-4/flux-pr-1435/flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-4/agent-logs/agent.patch

130361 bytes

Back to adjudication
diff --git a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/mod.rs b/app/src/ast/mod.rs
index 9185c9d..cd08b7d 100644
--- a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/mod.rs
+++ b/app/src/ast/mod.rs
@@ -25,6 +25,8 @@ use alloc::{
 };
 
 use core::fmt::{self, Display};
+use core::hash;
+use core::cmp::Ordering;
 use core::ops::Deref;
 
 #[cfg(feature = "serde")]
@@ -73,6 +75,8 @@ pub use self::value::{
     escape_double_quote_string, escape_quoted_string, DateTimeField, DollarQuotedString,
     TrimWhereField, Value,
 };
+pub use self::spans::Spanned;
+pub use crate::tokenizer::Span;
 
 use crate::ast::helpers::stmt_data_loading::{
     DataLoadingOptions, StageLoadSelectItem, StageParamsObject,
@@ -87,6 +91,7 @@ mod dml;
 pub mod helpers;
 mod operator;
 mod query;
+mod spans;
 mod trigger;
 mod value;
 
@@ -131,7 +136,7 @@ where
 }
 
 /// An identifier, decomposed into its value or character data and the quote style.
-#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[derive(Debug, Clone)]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
 pub struct Ident {
@@ -140,6 +145,37 @@ pub struct Ident {
     /// The starting quote if any. Valid quote characters are the single quote,
     /// double quote, backtick, and opening square bracket.
     pub quote_style: Option<char>,
+    /// The span of the identifier in the original SQL string.
+    pub span: Span,
+}
+
+impl PartialEq for Ident {
+    fn eq(&self, other: &Self) -> bool {
+        self.value == other.value && self.quote_style == other.quote_style
+    }
+}
+
+impl Eq for Ident {}
+
+impl PartialOrd for Ident {
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Ord for Ident {
+    fn cmp(&self, other: &Self) -> Ordering {
+        self.value
+            .cmp(&other.value)
+            .then_with(|| self.quote_style.cmp(&other.quote_style))
+    }
+}
+
+impl hash::Hash for Ident {
+    fn hash<H: hash::Hasher>(&self, state: &mut H) {
+        self.value.hash(state);
+        self.quote_style.hash(state);
+    }
 }
 
 impl Ident {
@@ -151,6 +187,7 @@ impl Ident {
         Ident {
             value: value.into(),
             quote_style: None,
+            span: Span::empty(),
         }
     }
 
@@ -164,6 +201,32 @@ impl Ident {
         Ident {
             value: value.into(),
             quote_style: Some(quote),
+            span: Span::empty(),
+        }
+    }
+
+    /// Create a new identifier with an explicit span.
+    pub fn with_span<S>(span: Span, value: S) -> Self
+    where
+        S: Into<String>,
+    {
+        Ident {
+            value: value.into(),
+            quote_style: None,
+            span,
+        }
+    }
+
+    /// Create a quoted identifier with an explicit span.
+    pub fn with_quote_and_span<S>(quote: char, span: Span, value: S) -> Self
+    where
+        S: Into<String>,
+    {
+        assert!(quote == '\'' || quote == '"' || quote == '`' || quote == '[');
+        Ident {
+            value: value.into(),
+            quote_style: Some(quote),
+            span,
         }
     }
 }
@@ -173,6 +236,7 @@ impl From<&str> for Ident {
         Ident {
             value: value.to_string(),
             quote_style: None,
+            span: Span::empty(),
         }
     }
 }
diff --git a/app/src/ast/spans.rs b/app/src/ast/spans.rs
new file mode 100644
index 0000000..286a233
--- /dev/null
+++ b/app/src/ast/spans.rs
@@ -0,0 +1,664 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use crate::ast::{
+    AlterIndexOperation, AlterPolicyOperation, Array, Assignment, AssignmentTarget, Cte,
+    ColumnDef, Expr, Fetch, FromTable, Function, FunctionArg, FunctionArgExpr, FunctionArguments,
+    GroupByExpr, HiveSetLocation, Ident, Join, JoinConstraint, JoinOperator,
+    NamedWindowDefinition, ObjectName, Offset, OrderBy, OrderByExpr, Query, SchemaName, Select,
+    SelectInto, SelectItem, SetExpr, SqlOption, Statement, TableAlias, TableFactor,
+    TableFunctionArgs, TableWithJoins, Top, TopQuantity, Use, Value, Values, With,
+};
+use crate::tokenizer::{Span, TokenWithLocation};
+
+fn union_spans<I: IntoIterator<Item = Span>>(iter: I) -> Span {
+    Span::union_iter(iter)
+}
+
+pub trait Spanned {
+    fn span(&self) -> Span;
+}
+
+impl<T: Spanned> Spanned for Box<T> {
+    fn span(&self) -> Span {
+        self.as_ref().span()
+    }
+}
+
+impl<T: Spanned> Spanned for Option<T> {
+    fn span(&self) -> Span {
+        self.as_ref().map_or_else(Span::empty, Spanned::span)
+    }
+}
+
+impl<T: Spanned> Spanned for Vec<T> {
+    fn span(&self) -> Span {
+        union_spans(self.iter().map(Spanned::span))
+    }
+}
+
+impl Spanned for TokenWithLocation {
+    fn span(&self) -> Span {
+        self.span
+    }
+}
+
+impl Spanned for Ident {
+    fn span(&self) -> Span {
+        self.span
+    }
+}
+
+impl Spanned for ObjectName {
+    fn span(&self) -> Span {
+        self.0.span()
+    }
+}
+
+impl Spanned for Value {
+    fn span(&self) -> Span {
+        Span::empty()
+    }
+}
+
+impl Spanned for Query {
+    fn span(&self) -> Span {
+        union_spans([
+            self.with.span(),
+            self.body.span(),
+            self.order_by.span(),
+            self.limit.span(),
+            self.limit_by.span(),
+            self.offset.span(),
+            self.fetch.span(),
+        ])
+    }
+}
+
+impl Spanned for With {
+    fn span(&self) -> Span {
+        self.cte_tables.span()
+    }
+}
+
+impl Spanned for Cte {
+    fn span(&self) -> Span {
+        union_spans([self.alias.span(), self.query.span(), self.from.span()])
+    }
+}
+
+impl Spanned for SetExpr {
+    fn span(&self) -> Span {
+        match self {
+            SetExpr::Select(select) => select.span(),
+            SetExpr::Query(query) => query.span(),
+            SetExpr::SetOperation { left, right, .. } => left.span().union(&right.span()),
+            SetExpr::Values(values) => values.span(),
+            SetExpr::Insert(statement) | SetExpr::Update(statement) => statement.span(),
+            SetExpr::Table(_) => Span::empty(),
+        }
+    }
+}
+
+impl Spanned for Select {
+    fn span(&self) -> Span {
+        union_spans([
+            self.projection.span(),
+            self.into.span(),
+            self.from.span(),
+            self.selection.span(),
+            self.group_by.span(),
+            self.having.span(),
+            self.cluster_by.span(),
+            self.distribute_by.span(),
+            self.sort_by.span(),
+            self.named_window.span(),
+            self.qualify.span(),
+            self.top.span(),
+        ])
+    }
+}
+
+impl Spanned for Values {
+    fn span(&self) -> Span {
+        union_spans(
+            self.rows
+                .iter()
+                .map(|row| union_spans(row.iter().map(Spanned::span))),
+        )
+    }
+}
+
+impl Spanned for OrderBy {
+    fn span(&self) -> Span {
+        self.exprs.span()
+    }
+}
+
+impl Spanned for OrderByExpr {
+    fn span(&self) -> Span {
+        self.expr.span()
+    }
+}
+
+impl Spanned for Offset {
+    fn span(&self) -> Span {
+        self.value.span()
+    }
+}
+
+impl Spanned for Fetch {
+    fn span(&self) -> Span {
+        self.quantity.span()
+    }
+}
+
+impl Spanned for TableWithJoins {
+    fn span(&self) -> Span {
+        self.relation.span().union(&self.joins.span())
+    }
+}
+
+impl Spanned for Join {
+    fn span(&self) -> Span {
+        self.relation
+            .span()
+            .union(&match &self.join_operator {
+                JoinOperator::Inner(constraint)
+                | JoinOperator::LeftOuter(constraint)
+                | JoinOperator::RightOuter(constraint)
+                | JoinOperator::FullOuter(constraint)
+                | JoinOperator::Semi(constraint)
+                | JoinOperator::LeftSemi(constraint)
+                | JoinOperator::RightSemi(constraint)
+                | JoinOperator::Anti(constraint)
+                | JoinOperator::LeftAnti(constraint)
+                | JoinOperator::RightAnti(constraint) => constraint.span(),
+                JoinOperator::AsOf {
+                    match_condition,
+                    constraint,
+                } => match_condition.span().union(&constraint.span()),
+                JoinOperator::CrossJoin
+                | JoinOperator::CrossApply
+                | JoinOperator::OuterApply => Span::empty(),
+            })
+    }
+}
+
+impl Spanned for JoinConstraint {
+    fn span(&self) -> Span {
+        match self {
+            JoinConstraint::On(expr) => expr.span(),
+            JoinConstraint::Using(attrs) => attrs.span(),
+            JoinConstraint::Natural | JoinConstraint::None => Span::empty(),
+        }
+    }
+}
+
+impl Spanned for TableFactor {
+    fn span(&self) -> Span {
+        match self {
+            TableFactor::Table { name, alias, args, .. } => {
+                union_spans([name.span(), alias.span(), args.span()])
+            }
+            TableFactor::Derived {
+                subquery, alias, ..
+            } => subquery.span().union(&alias.span()),
+            TableFactor::TableFunction { expr, alias } => expr.span().union(&alias.span()),
+            TableFactor::Function {
+                name, args, alias, ..
+            } => union_spans([name.span(), args.span(), alias.span()]),
+            TableFactor::UNNEST {
+                array_exprs, alias, ..
+            } => array_exprs.span().union(&alias.span()),
+            TableFactor::NestedJoin {
+                table_with_joins,
+                alias,
+            } => table_with_joins.span().union(&alias.span()),
+            _ => Span::empty(),
+        }
+    }
+}
+
+impl Spanned for TableAlias {
+    fn span(&self) -> Span {
+        self.name.span().union(&self.columns.span())
+    }
+}
+
+impl Spanned for SelectItem {
+    fn span(&self) -> Span {
+        match self {
+            SelectItem::UnnamedExpr(expr) => expr.span(),
+            SelectItem::ExprWithAlias { expr, alias } => expr.span().union(&alias.span()),
+            SelectItem::QualifiedWildcard(name, _) => name.span(),
+            SelectItem::Wildcard(_) => Span::empty(),
+        }
+    }
+}
+
+impl Spanned for Function {
+    fn span(&self) -> Span {
+        union_spans([
+            self.name.span(),
+            self.parameters.span(),
+            self.args.span(),
+            self.filter.span(),
+            self.within_group.span(),
+        ])
+    }
+}
+
+impl Spanned for FunctionArguments {
+    fn span(&self) -> Span {
+        match self {
+            FunctionArguments::None => Span::empty(),
+            FunctionArguments::Subquery(query) => query.span(),
+            FunctionArguments::List(list) => list.args.span(),
+        }
+    }
+}
+
+impl Spanned for TableFunctionArgs {
+    fn span(&self) -> Span {
+        self.args.span()
+    }
+}
+
+impl Spanned for SelectInto {
+    fn span(&self) -> Span {
+        self.name.span()
+    }
+}
+
+impl Spanned for Top {
+    fn span(&self) -> Span {
+        self.quantity.span()
+    }
+}
+
+impl Spanned for TopQuantity {
+    fn span(&self) -> Span {
+        match self {
+            TopQuantity::Expr(expr) => expr.span(),
+            TopQuantity::Constant(_) => Span::empty(),
+        }
+    }
+}
+
+impl Spanned for NamedWindowDefinition {
+    fn span(&self) -> Span {
+        self.0.span()
+    }
+}
+
+impl Spanned for FunctionArg {
+    fn span(&self) -> Span {
+        match self {
+            FunctionArg::Named { name, arg, .. } => name.span().union(&arg.span()),
+            FunctionArg::ExprNamed { name, arg, .. } => name.span().union(&arg.span()),
+            FunctionArg::Unnamed(arg) => arg.span(),
+        }
+    }
+}
+
+impl Spanned for FunctionArgExpr {
+    fn span(&self) -> Span {
+        match self {
+            FunctionArgExpr::Expr(expr) => expr.span(),
+            FunctionArgExpr::QualifiedWildcard(name) => name.span(),
+            FunctionArgExpr::Wildcard => Span::empty(),
+        }
+    }
+}
+
+impl Spanned for GroupByExpr {
+    fn span(&self) -> Span {
+        match self {
+            GroupByExpr::All(_) => Span::empty(),
+            GroupByExpr::Expressions(exprs, _) => exprs.span(),
+        }
+    }
+}
+
+impl Spanned for Array {
+    fn span(&self) -> Span {
+        self.elem.span()
+    }
+}
+
+impl Spanned for AssignmentTarget {
+    fn span(&self) -> Span {
+        match self {
+            AssignmentTarget::ColumnName(name) => name.span(),
+            AssignmentTarget::Tuple(names) => names.span(),
+        }
+    }
+}
+
+impl Spanned for Assignment {
+    fn span(&self) -> Span {
+        self.target.span().union(&self.value.span())
+    }
+}
+
+impl Spanned for ColumnDef {
+    fn span(&self) -> Span {
+        self.name.span().union(&self.collation.span())
+    }
+}
+
+impl Spanned for crate::ast::MapAccessKey {
+    fn span(&self) -> Span {
+        self.key.span()
+    }
+}
+
+impl Spanned for SqlOption {
+    fn span(&self) -> Span {
+        match self {
+            SqlOption::Clustered(_) => Span::empty(),
+            SqlOption::Ident(ident) => ident.span(),
+            SqlOption::KeyValue { key, value } => key.span().union(&value.span()),
+            SqlOption::Partition {
+                column_name,
+                for_values,
+                ..
+            } => column_name.span().union(&for_values.span()),
+        }
+    }
+}
+
+impl Spanned for FromTable {
+    fn span(&self) -> Span {
+        match self {
+            FromTable::WithFromKeyword(items) | FromTable::WithoutKeyword(items) => items.span(),
+        }
+    }
+}
+
+impl Spanned for HiveSetLocation {
+    fn span(&self) -> Span {
+        self.location.span()
+    }
+}
+
+impl Spanned for AlterIndexOperation {
+    fn span(&self) -> Span {
+        match self {
+            AlterIndexOperation::RenameIndex { index_name } => index_name.span(),
+        }
+    }
+}
+
+impl Spanned for AlterPolicyOperation {
+    fn span(&self) -> Span {
+        match self {
+            AlterPolicyOperation::Rename { new_name } => new_name.span(),
+            AlterPolicyOperation::Apply {
+                using, with_check, ..
+            } => using.span().union(&with_check.span()),
+        }
+    }
+}
+
+impl Spanned for Use {
+    fn span(&self) -> Span {
+        match self {
+            Use::Catalog(name)
+            | Use::Schema(name)
+            | Use::Database(name)
+            | Use::Warehouse(name)
+            | Use::Object(name) => name.span(),
+            Use::Default => Span::empty(),
+        }
+    }
+}
+
+impl Spanned for SchemaName {
+    fn span(&self) -> Span {
+        match self {
+            SchemaName::Simple(name) => name.span(),
+            SchemaName::UnnamedAuthorization(ident) => ident.span(),
+            SchemaName::NamedAuthorization(name, ident) => name.span().union(&ident.span()),
+        }
+    }
+}
+
+impl Spanned for Expr {
+    fn span(&self) -> Span {
+        match self {
+            Expr::Identifier(ident) => ident.span(),
+            Expr::CompoundIdentifier(idents) => idents.span(),
+            Expr::BinaryOp { left, right, .. } => left.span().union(&right.span()),
+            Expr::UnaryOp { expr, .. } => expr.span(),
+            Expr::Nested(expr) => expr.span(),
+            Expr::Value(value) => value.span(),
+            Expr::TypedString { .. } => Span::empty(),
+            Expr::MapAccess { column, keys } => column.span().union(&keys.span()),
+            Expr::Function(function) => function.span(),
+            Expr::Exists { subquery, .. } => subquery.span(),
+            Expr::Subquery(query) => query.span(),
+            Expr::Substring {
+                expr,
+                substring_from,
+                substring_for,
+                ..
+            } => union_spans([expr.span(), substring_from.span(), substring_for.span()]),
+            Expr::Trim {
+                expr,
+                trim_what,
+                trim_characters,
+                ..
+            } => union_spans([expr.span(), trim_what.span(), trim_characters.span()]),
+            Expr::Collate { expr, collation } => expr.span().union(&collation.span()),
+            Expr::InList { expr, list, .. } => expr.span().union(&list.span()),
+            Expr::InSubquery { expr, subquery, .. } => expr.span().union(&subquery.span()),
+            Expr::Between {
+                expr, low, high, ..
+            } => union_spans([expr.span(), low.span(), high.span()]),
+            Expr::Case {
+                operand,
+                conditions,
+                results,
+                else_result,
+            } => union_spans([
+                operand.span(),
+                conditions.span(),
+                results.span(),
+                else_result.span(),
+            ]),
+            Expr::Cast { expr, .. }
+            | Expr::AtTimeZone { timestamp: expr, .. }
+            | Expr::Extract { expr, .. }
+            | Expr::Ceil { expr, .. }
+            | Expr::Floor { expr, .. }
+            | Expr::Position { expr, .. }
+            | Expr::Overlay { expr, .. }
+            | Expr::OuterJoin(expr)
+            | Expr::Prior(expr) => expr.span(),
+            Expr::Array(array) => array.span(),
+            Expr::Interval(interval) => interval.value.span(),
+            Expr::Tuple(exprs) => exprs.span(),
+            Expr::GroupingSets(exprs) | Expr::Cube(exprs) | Expr::Rollup(exprs) => union_spans(
+                exprs.iter().map(|items| union_spans(items.iter().map(Spanned::span))),
+            ),
+            Expr::Struct { values, .. } => values.span(),
+            Expr::Named { expr, name } => expr.span().union(&name.span()),
+            Expr::Dictionary(fields) => union_spans(
+                fields
+                    .iter()
+                    .map(|field| field.key.span().union(&field.value.span())),
+            ),
+            Expr::Map(map) => union_spans(
+                map.entries
+                    .iter()
+                    .map(|entry| entry.key.span().union(&entry.value.span())),
+            ),
+            Expr::Subscript { expr, subscript } => expr.span().union(&subscript.span()),
+            Expr::IntroducedString { .. }
+            | Expr::MatchAgainst { .. }
+            | Expr::Wildcard
+            | Expr::QualifiedWildcard(_)
+            | Expr::Lambda(_) => Span::empty(),
+            _ => Span::empty(),
+        }
+    }
+}
+
+impl Spanned for crate::ast::Subscript {
+    fn span(&self) -> Span {
+        match self {
+            crate::ast::Subscript::Index { index } => index.span(),
+            crate::ast::Subscript::Slice {
+                lower_bound,
+                upper_bound,
+                stride,
+            } => union_spans([lower_bound.span(), upper_bound.span(), stride.span()]),
+        }
+    }
+}
+
+impl<T: Spanned> Spanned for crate::ast::OneOrManyWithParens<T> {
+    fn span(&self) -> Span {
+        union_spans(self.iter().map(Spanned::span))
+    }
+}
+
+impl Spanned for Statement {
+    fn span(&self) -> Span {
+        match self {
+            Statement::Query(query) => query.span(),
+            Statement::Insert(insert) => insert.table_name.span().union(&insert.source.span()),
+            Statement::Update {
+                table,
+                assignments,
+                from,
+                selection,
+                ..
+            } => union_spans([table.span(), assignments.span(), from.span(), selection.span()]),
+            Statement::Delete(delete) => {
+                union_spans([delete.from.span(), delete.using.span(), delete.selection.span()])
+            }
+            Statement::CreateView { name, query, .. } => name.span().union(&query.span()),
+            Statement::CreateTable(create) => create.name.span().union(&create.columns.span()),
+            Statement::Drop { names, .. } => names.span(),
+            Statement::AlterTable {
+                name,
+                location,
+                on_cluster,
+                ..
+            } => union_spans([name.span(), location.span(), on_cluster.span()]),
+            Statement::AlterIndex { name, operation } => name.span().union(&operation.span()),
+            Statement::AlterView {
+                name,
+                columns,
+                query,
+                with_options,
+            } => union_spans([name.span(), columns.span(), query.span(), with_options.span()]),
+            Statement::CreateSchema { schema_name, .. } => schema_name.span(),
+            Statement::CreateDatabase { db_name, .. } => db_name.span(),
+            Statement::CreateFunction { name, .. } => name.span(),
+            Statement::CreateRole { names, .. } => names.span(),
+            Statement::CreateSecret {
+                name,
+                storage_specifier,
+                secret_type,
+                ..
+            } => union_spans([name.span(), storage_specifier.span(), secret_type.span()]),
+            Statement::CreatePolicy {
+                name,
+                table_name,
+                using,
+                with_check,
+                ..
+            } => union_spans([name.span(), table_name.span(), using.span(), with_check.span()]),
+            Statement::AlterRole { name, .. } => name.span(),
+            Statement::AlterPolicy {
+                name,
+                table_name,
+                operation,
+            } => union_spans([name.span(), table_name.span(), operation.span()]),
+            Statement::AttachDatabase {
+                schema_name,
+                database_file_name,
+                ..
+            } => schema_name.span().union(&database_file_name.span()),
+            Statement::AttachDuckDBDatabase {
+                database_path,
+                database_alias,
+                ..
+            } => union_spans([database_path.span(), database_alias.span()]),
+            Statement::DetachDuckDBDatabase { database_alias, .. } => database_alias.span(),
+            Statement::DropSecret {
+                name,
+                storage_specifier,
+                ..
+            } => name.span().union(&storage_specifier.span()),
+            Statement::DropPolicy {
+                name, table_name, ..
+            } => name.span().union(&table_name.span()),
+            Statement::Fetch { name, into, .. } => name.span().union(&into.span()),
+            Statement::Flush { tables, .. } => tables.span(),
+            Statement::SetRole { role_name, .. } => role_name.span(),
+            Statement::SetVariable {
+                variables, value, ..
+            } => variables.span().union(&value.span()),
+            Statement::SetTimeZone { value, .. } => value.span(),
+            Statement::ShowVariable { variable } => variable.span(),
+            Statement::ShowCreate { obj_name, .. } => obj_name.span(),
+            Statement::Use(use_stmt) => use_stmt.span(),
+            Statement::Rollback { savepoint, .. } => savepoint.span(),
+            Statement::Comment { object_name, .. } => object_name.span(),
+            Statement::CreateTrigger {
+                name,
+                table_name,
+                referenced_table_name,
+                condition,
+                ..
+            } => union_spans([
+                name.span(),
+                table_name.span(),
+                referenced_table_name.span(),
+                condition.span(),
+            ]),
+            Statement::DropTrigger {
+                trigger_name,
+                table_name,
+                ..
+            } => trigger_name.span().union(&table_name.span()),
+            Statement::CreateProcedure { name, body, .. } => name.span().union(&body.span()),
+            Statement::CreateMacro { name, .. } => name.span(),
+            Statement::CreateStage { name, .. } => name.span(),
+            Statement::Assert {
+                condition, message, ..
+            } => condition.span().union(&message.span()),
+            Statement::Grant {
+                grantees,
+                granted_by,
+                ..
+            } => grantees.span().union(&granted_by.span()),
+            Statement::Revoke {
+                grantees,
+                granted_by,
+                ..
+            } => grantees.span().union(&granted_by.span()),
+            Statement::Deallocate { name, .. } => name.span(),
+            _ => Span::empty(),
+        }
+    }
+}
diff --git a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/parser/mod.rs b/app/src/parser/mod.rs
index 6767f35..76da619 100644
--- a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/parser/mod.rs
+++ b/app/src/parser/mod.rs
@@ -372,6 +372,7 @@ impl<'a> Parser<'a> {
             .map(|token| TokenWithLocation {
                 token,
                 location: Location { line: 0, column: 0 },
+                span: crate::tokenizer::Span::empty(),
             })
             .collect();
         self.with_tokens_with_locations(tokens_with_locations)
@@ -914,18 +915,20 @@ impl<'a> Parser<'a> {
             t @ (Token::Word(_) | Token::SingleQuotedString(_)) => {
                 if self.peek_token().token == Token::Period {
                     let mut id_parts: Vec<Ident> = vec![match t {
-                        Token::Word(w) => w.to_ident(),
-                        Token::SingleQuotedString(s) => Ident::with_quote('\'', s),
+                        Token::Word(w) => w.to_ident_with_span(next_token.span),
+                        Token::SingleQuotedString(s) => {
+                            Ident::with_quote_and_span('\'', next_token.span, s)
+                        }
                         _ => unreachable!(), // We matched above
                     }];
 
                     while self.consume_token(&Token::Period) {
                         let next_token = self.next_token();
                         match next_token.token {
-                            Token::Word(w) => id_parts.push(w.to_ident()),
+                            Token::Word(w) => id_parts.push(w.to_ident_with_span(next_token.span)),
                             Token::SingleQuotedString(s) => {
                                 // SQLite has single-quoted identifiers
-                                id_parts.push(Ident::with_quote('\'', s))
+                                id_parts.push(Ident::with_quote_and_span('\'', next_token.span, s))
                             }
                             Token::Mul => {
                                 return Ok(Expr::QualifiedWildcard(ObjectName(id_parts)));
@@ -1030,6 +1033,7 @@ impl<'a> Parser<'a> {
     fn parse_expr_prefix_by_reserved_word(
         &mut self,
         w: &Word,
+        span: crate::tokenizer::Span,
     ) -> Result<Option<Expr>, ParserError> {
         match w.keyword {
             Keyword::TRUE | Keyword::FALSE if self.dialect.supports_boolean_literals() => {
@@ -1047,7 +1051,7 @@ impl<'a> Parser<'a> {
                 if dialect_of!(self is PostgreSqlDialect | GenericDialect) =>
             {
                 Ok(Some(Expr::Function(Function {
-                    name: ObjectName(vec![w.to_ident()]),
+                    name: ObjectName(vec![w.to_ident_with_span(span)]),
                     parameters: FunctionArguments::None,
                     args: FunctionArguments::None,
                     null_treatment: None,
@@ -1061,7 +1065,7 @@ impl<'a> Parser<'a> {
             | Keyword::CURRENT_DATE
             | Keyword::LOCALTIME
             | Keyword::LOCALTIMESTAMP => {
-                Ok(Some(self.parse_time_functions(ObjectName(vec![w.to_ident()]))?))
+                Ok(Some(self.parse_time_functions(ObjectName(vec![w.to_ident_with_span(span)]))?))
             }
             Keyword::CASE => Ok(Some(self.parse_case_expr()?)),
             Keyword::CONVERT => Ok(Some(self.parse_convert_expr(false)?)),
@@ -1086,7 +1090,7 @@ impl<'a> Parser<'a> {
             Keyword::CEIL => Ok(Some(self.parse_ceil_floor_expr(true)?)),
             Keyword::FLOOR => Ok(Some(self.parse_ceil_floor_expr(false)?)),
             Keyword::POSITION if self.peek_token().token == Token::LParen => {
-                Ok(Some(self.parse_position_expr(w.to_ident())?))
+                Ok(Some(self.parse_position_expr(w.to_ident_with_span(span))?))
             }
             Keyword::SUBSTRING => Ok(Some(self.parse_substring_expr()?)),
             Keyword::OVERLAY => Ok(Some(self.parse_overlay_expr()?)),
@@ -1105,7 +1109,7 @@ impl<'a> Parser<'a> {
                 let query = self.parse_query()?;
                 self.expect_token(&Token::RParen)?;
                 Ok(Some(Expr::Function(Function {
-                    name: ObjectName(vec![w.to_ident()]),
+                    name: ObjectName(vec![w.to_ident_with_span(span)]),
                     parameters: FunctionArguments::None,
                     args: FunctionArguments::Subquery(query),
                     filter: None,
@@ -1134,15 +1138,19 @@ impl<'a> Parser<'a> {
     }
 
     // Tries to parse an expression by a word that is not known to have a special meaning in the dialect.
-    fn parse_expr_prefix_by_unreserved_word(&mut self, w: &Word) -> Result<Expr, ParserError> {
+    fn parse_expr_prefix_by_unreserved_word(
+        &mut self,
+        w: &Word,
+        span: crate::tokenizer::Span,
+    ) -> Result<Expr, ParserError> {
         match self.peek_token().token {
             Token::LParen | Token::Period => {
-                let mut id_parts: Vec<Ident> = vec![w.to_ident()];
+                let mut id_parts: Vec<Ident> = vec![w.to_ident_with_span(span)];
                 let mut ends_with_wildcard = false;
                 while self.consume_token(&Token::Period) {
                     let next_token = self.next_token();
                     match next_token.token {
-                        Token::Word(w) => id_parts.push(w.to_ident()),
+                        Token::Word(w) => id_parts.push(w.to_ident_with_span(next_token.span)),
                         Token::Mul => {
                             // Postgres explicitly allows funcnm(tablenm.*) and the
                             // function array_agg traverses this control flow
@@ -1153,7 +1161,9 @@ impl<'a> Parser<'a> {
                                 return self.expected("an identifier after '.'", next_token);
                             }
                         }
-                        Token::SingleQuotedString(s) => id_parts.push(Ident::with_quote('\'', s)),
+                        Token::SingleQuotedString(s) => {
+                            id_parts.push(Ident::with_quote_and_span('\'', next_token.span, s))
+                        }
                         _ => {
                             return self.expected("an identifier or a '*' after '.'", next_token);
                         }
@@ -1194,11 +1204,11 @@ impl<'a> Parser<'a> {
             Token::Arrow if self.dialect.supports_lambda_functions() => {
                 self.expect_token(&Token::Arrow)?;
                 Ok(Expr::Lambda(LambdaFunction {
-                    params: OneOrManyWithParens::One(w.to_ident()),
+                    params: OneOrManyWithParens::One(w.to_ident_with_span(span)),
                     body: Box::new(self.parse_expr()?),
                 }))
             }
-            _ => Ok(Expr::Identifier(w.to_ident())),
+            _ => Ok(Expr::Identifier(w.to_ident_with_span(span))),
         }
     }
 
@@ -1251,6 +1261,7 @@ impl<'a> Parser<'a> {
         let next_token = self.next_token();
         let expr = match next_token.token {
             Token::Word(w) => {
+                let span = next_token.span;
                 // The word we consumed may fall into one of two cases: it has a special meaning, or not.
                 // For example, in Snowflake, the word `interval` may have two meanings depending on the context:
                 // `SELECT CURRENT_DATE() + INTERVAL '1 DAY', MAX(interval) FROM tbl;`
@@ -1259,12 +1270,12 @@ impl<'a> Parser<'a> {
                 //
                 // We first try to parse the word and following tokens as a special expression, and if that fails,
                 // we rollback and try to parse it as an identifier.
-                match self.try_parse(|parser| parser.parse_expr_prefix_by_reserved_word(&w)) {
+                match self.try_parse(|parser| parser.parse_expr_prefix_by_reserved_word(&w, span)) {
                     // This word indicated an expression prefix and parsing was successful
                     Ok(Some(expr)) => Ok(expr),
 
                     // No expression prefix associated with this word
-                    Ok(None) => Ok(self.parse_expr_prefix_by_unreserved_word(&w)?),
+                    Ok(None) => Ok(self.parse_expr_prefix_by_unreserved_word(&w, span)?),
 
                     // If parsing of the word as a special expression failed, we are facing two options:
                     // 1. The statement is malformed, e.g. `SELECT INTERVAL '1 DAI` (`DAI` instead of `DAY`)
@@ -1275,7 +1286,7 @@ impl<'a> Parser<'a> {
                     Err(e) => {
                         if !self.dialect.is_reserved_for_identifier(w.keyword) {
                             if let Ok(Some(expr)) = self.maybe_parse(|parser| {
-                                parser.parse_expr_prefix_by_unreserved_word(&w)
+                                parser.parse_expr_prefix_by_unreserved_word(&w, span)
                             }) {
                                 return Ok(expr);
                             }
@@ -1377,7 +1388,7 @@ impl<'a> Parser<'a> {
                 } else {
                     let tok = self.next_token();
                     let key = match tok.token {
-                        Token::Word(word) => word.to_ident(),
+                        Token::Word(word) => word.to_ident_with_span(tok.span),
                         _ => {
                             return parser_err!(
                                 format!("Expected identifier, found: {tok}"),
@@ -1471,7 +1482,7 @@ impl<'a> Parser<'a> {
             while p.consume_token(&Token::Period) {
                 let tok = p.next_token();
                 let name = match tok.token {
-                    Token::Word(word) => word.to_ident(),
+                    Token::Word(word) => word.to_ident_with_span(tok.span),
                     _ => return p.expected("identifier", tok),
                 };
                 let func = match p.parse_function(ObjectName(vec![name]))? {
@@ -3299,6 +3310,7 @@ impl<'a> Parser<'a> {
             if let Some(TokenWithLocation {
                 token: Token::Whitespace(_),
                 location: _,
+                span: _,
             }) = token
             {
                 continue;
@@ -3306,6 +3318,7 @@ impl<'a> Parser<'a> {
             break token.cloned().unwrap_or(TokenWithLocation {
                 token: Token::EOF,
                 location: Location { line: 0, column: 0 },
+                span: crate::tokenizer::Span::empty(),
             });
         })
     }
@@ -3319,12 +3332,14 @@ impl<'a> Parser<'a> {
                 Some(TokenWithLocation {
                     token: Token::Whitespace(_),
                     location: _,
+                    span: _,
                 }) => continue,
                 non_whitespace => {
                     if n == 0 {
                         return non_whitespace.cloned().unwrap_or(TokenWithLocation {
                             token: Token::EOF,
                             location: Location { line: 0, column: 0 },
+                            span: crate::tokenizer::Span::empty(),
                         });
                     }
                     n -= 1;
@@ -3347,6 +3362,7 @@ impl<'a> Parser<'a> {
             .unwrap_or(TokenWithLocation {
                 token: Token::EOF,
                 location: Location { line: 0, column: 0 },
+                span: crate::tokenizer::Span::empty(),
             })
     }
 
@@ -3376,6 +3392,7 @@ impl<'a> Parser<'a> {
                 Some(TokenWithLocation {
                     token: Token::Whitespace(_),
                     location: _,
+                    span: _,
                 }) => continue,
                 token => {
                     return token
@@ -3402,6 +3419,7 @@ impl<'a> Parser<'a> {
             if let Some(TokenWithLocation {
                 token: Token::Whitespace(_),
                 location: _,
+                span: _,
             }) = self.tokens.get(self.index)
             {
                 continue;
@@ -6819,6 +6837,7 @@ impl<'a> Parser<'a> {
                         TokenWithLocation {
                             token: Token::make_keyword(&name.to_string()),
                             location: next_token.location,
+                            span: next_token.span,
                         },
                     );
                 }
@@ -7795,6 +7814,7 @@ impl<'a> Parser<'a> {
                         TokenWithLocation {
                             token: Token::Word(w),
                             location,
+                            span: next_token.span,
                         },
                     )?,
                 },
@@ -7803,6 +7823,7 @@ impl<'a> Parser<'a> {
                     TokenWithLocation {
                         token: Token::Word(w),
                         location,
+                        span: next_token.span,
                     },
                 ),
             },
@@ -7853,8 +7874,8 @@ impl<'a> Parser<'a> {
                 // This because snowflake allows numbers as placeholders
                 let next_token = self.next_token();
                 let ident = match next_token.token {
-                    Token::Word(w) => Ok(w.to_ident()),
-                    Token::Number(w, false) => Ok(Ident::new(w)),
+                    Token::Word(w) => Ok(w.to_ident_with_span(next_token.span)),
+                    Token::Number(w, false) => Ok(Ident::with_span(next_token.span, w)),
                     _ => self.expected("placeholder", next_token),
                 }?;
                 let placeholder = tok.to_string() + &ident.value;
@@ -7865,6 +7886,7 @@ impl<'a> Parser<'a> {
                 TokenWithLocation {
                     token: unexpected,
                     location,
+                    span: location.span_to(location),
                 },
             ),
         }
@@ -7914,6 +7936,7 @@ impl<'a> Parser<'a> {
                 TokenWithLocation {
                     token: unexpected,
                     location,
+                    span: location.span_to(location),
                 },
             ),
         }
@@ -8322,7 +8345,7 @@ impl<'a> Parser<'a> {
             // (For example, in `FROM t1 JOIN` the `JOIN` will always be parsed as a keyword,
             // not an alias.)
             Token::Word(w) if after_as || !reserved_kwds.contains(&w.keyword) => {
-                Ok(Some(w.to_ident()))
+                Ok(Some(w.to_ident_with_span(next_token.span)))
             }
             // MSSQL supports single-quoted strings as aliases for columns
             // We accept them as table aliases too, although MSSQL does not.
@@ -8336,7 +8359,9 @@ impl<'a> Parser<'a> {
             //    character. When it sees such a <literal>, your DBMS will
             //    ignore the <separator> and treat the multiple strings as
             //    a single <literal>."
-            Token::SingleQuotedString(s) => Ok(Some(Ident::with_quote('\'', s))),
+            Token::SingleQuotedString(s) => {
+                Ok(Some(Ident::with_quote_and_span('\'', next_token.span, s)))
+            }
             // Support for MySql dialect double-quoted string, `AS "HOUR"` for example
             Token::DoubleQuotedString(s) => Ok(Some(Ident::with_quote('\"', s))),
             _ => {
@@ -8455,6 +8480,7 @@ impl<'a> Parser<'a> {
                         .map(|value| Ident {
                             value: value.into(),
                             quote_style: ident.quote_style,
+                            span: ident.span,
                         })
                         .collect::<Vec<_>>()
                 })
@@ -8468,14 +8494,19 @@ impl<'a> Parser<'a> {
     pub fn parse_identifiers(&mut self) -> Result<Vec<Ident>, ParserError> {
         let mut idents = vec![];
         loop {
-            match self.peek_token().token {
-                Token::Word(w) => {
-                    idents.push(w.to_ident());
+            let next = self.peek_token();
+            match next.token {
+                Token::Word(_) => {
+                    let next = self.next_token();
+                    if let Token::Word(w) = next.token {
+                        idents.push(w.to_ident_with_span(next.span));
+                    }
                 }
                 Token::EOF | Token::Eq => break,
-                _ => {}
+                _ => {
+                    self.next_token();
+                }
             }
-            self.next_token();
         }
         Ok(idents)
     }
@@ -8523,8 +8554,9 @@ impl<'a> Parser<'a> {
         let mut idents = vec![];
 
         // expecting at least one word for identifier
-        match self.next_token().token {
-            Token::Word(w) => idents.push(w.to_ident()),
+        let next = self.next_token();
+        match next.token {
+            Token::Word(w) => idents.push(w.to_ident_with_span(next.span)),
             Token::EOF => {
                 return Err(ParserError::ParserError(
                     "Empty input when parsing identifier".to_string(),
@@ -8541,8 +8573,12 @@ impl<'a> Parser<'a> {
         loop {
             match self.next_token().token {
                 // ensure that optional period is succeeded by another identifier
-                Token::Period => match self.next_token().token {
-                    Token::Word(w) => idents.push(w.to_ident()),
+                Token::Period => match self.next_token() {
+                    TokenWithLocation {
+                        token: Token::Word(w),
+                        span,
+                        ..
+                    } => idents.push(w.to_ident_with_span(span)),
                     Token::EOF => {
                         return Err(ParserError::ParserError(
                             "Trailing period in identifier".to_string(),
@@ -8575,7 +8611,7 @@ impl<'a> Parser<'a> {
         let next_token = self.next_token();
         match next_token.token {
             Token::Word(w) => {
-                let mut ident = w.to_ident();
+                let mut ident = w.to_ident_with_span(next_token.span);
 
                 // On BigQuery, hyphens are permitted in unquoted identifiers inside of a FROM or
                 // TABLE clause [0].
@@ -8601,10 +8637,12 @@ impl<'a> Parser<'a> {
                         requires_whitespace = match token.token {
                             Token::Word(next_word) if next_word.quote_style.is_none() => {
                                 ident.value.push_str(&next_word.value);
+                                ident.span = ident.span.union(&token.span);
                                 false
                             }
                             Token::Number(s, false) if s.chars().all(|c| c.is_ascii_digit()) => {
                                 ident.value.push_str(&s);
+                                ident.span = ident.span.union(&token.span);
                                 true
                             }
                             _ => {
@@ -8626,8 +8664,8 @@ impl<'a> Parser<'a> {
                 }
                 Ok(ident)
             }
-            Token::SingleQuotedString(s) => Ok(Ident::with_quote('\'', s)),
-            Token::DoubleQuotedString(s) => Ok(Ident::with_quote('\"', s)),
+            Token::SingleQuotedString(s) => Ok(Ident::with_quote_and_span('\'', next_token.span, s)),
+            Token::DoubleQuotedString(s) => Ok(Ident::with_quote_and_span('\"', next_token.span, s)),
             _ => self.expected("identifier", next_token),
         }
     }
@@ -12816,13 +12854,25 @@ impl Word {
         Ident {
             value: self.value.clone(),
             quote_style: self.quote_style,
+            span: crate::tokenizer::Span::empty(),
+        }
+    }
+
+    pub fn to_ident_with_span(&self, span: crate::tokenizer::Span) -> Ident {
+        Ident {
+            value: self.value.clone(),
+            quote_style: self.quote_style,
+            span,
         }
     }
 }
 
 #[cfg(test)]
 mod tests {
+    use crate::ast::Spanned;
+    use crate::dialect::GenericDialect;
     use crate::test_utils::{all_dialects, TestedDialects};
+    use crate::tokenizer::{Span, Tokenizer};
 
     use super::*;
 
@@ -12847,6 +12897,25 @@ mod tests {
         });
     }
 
+    #[test]
+    fn test_tokenizer_spans() {
+        let dialect = GenericDialect {};
+        let mut tokenizer = Tokenizer::new(&dialect, "SELECT a");
+        let tokens = tokenizer.tokenize_with_location().unwrap();
+        assert_eq!(tokens[0].span, Span::new((1, 1).into(), (1, 7).into()));
+        assert_eq!(tokens[2].span, Span::new((1, 8).into(), (1, 9).into()));
+    }
+
+    #[test]
+    fn test_statement_span_from_identifiers() {
+        let dialect = GenericDialect {};
+        let mut parser = Parser::new(&dialect)
+            .try_with_sql("SELECT a FROM table_1")
+            .unwrap();
+        let statement = parser.parse_statement().unwrap();
+        assert_eq!(statement.span(), Span::new((1, 8).into(), (1, 22).into()));
+    }
+
     #[test]
     fn test_peek_tokens() {
         all_dialects().run_parser_method("SELECT foo AS bar FROM baz", |parser| {
@@ -13389,14 +13458,17 @@ mod tests {
             Ident {
                 value: "CATALOG".to_string(),
                 quote_style: None,
+                span: Span::empty(),
             },
             Ident {
                 value: "F(o)o. \"bar".to_string(),
                 quote_style: Some('"'),
+                span: Span::empty(),
             },
             Ident {
                 value: "table".to_string(),
                 quote_style: None,
+                span: Span::empty(),
             },
         ];
         dialect.run_parser_method(r#"CATALOG."F(o)o. ""bar".table"#, |parser| {
@@ -13409,10 +13481,12 @@ mod tests {
             Ident {
                 value: "CATALOG".to_string(),
                 quote_style: None,
+                span: Span::empty(),
             },
             Ident {
                 value: "table".to_string(),
                 quote_style: None,
+                span: Span::empty(),
             },
         ];
         dialect.run_parser_method("CATALOG . table", |parser| {
diff --git a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/tokenizer.rs b/app/src/tokenizer.rs
index 05aaf1e..b8cb5fd 100644
--- a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/tokenizer.rs
+++ b/app/src/tokenizer.rs
@@ -29,6 +29,7 @@ use alloc::{
     vec,
     vec::Vec,
 };
+use core::cmp;
 use core::fmt;
 use core::iter::Peekable;
 use core::num::NonZeroU8;
@@ -444,11 +445,93 @@ impl fmt::Display for Location {
     }
 }
 
-/// A [Token] with [Location] attached to it
-#[derive(Debug, Eq, PartialEq, Clone)]
+impl Location {
+    /// Return an "empty" / unknown location.
+    pub const fn empty() -> Self {
+        Self { line: 0, column: 0 }
+    }
+
+    /// Create a new location for a given line and column.
+    pub const fn new(line: u64, column: u64) -> Self {
+        Self { line, column }
+    }
+
+    /// Combine self and `end` into a new span.
+    pub const fn span_to(self, end: Self) -> Span {
+        Span { start: self, end }
+    }
+}
+
+impl From<(u64, u64)> for Location {
+    fn from((line, column): (u64, u64)) -> Self {
+        Self { line, column }
+    }
+}
+
+/// A span represents a linear portion of the input string.
+#[derive(Eq, PartialEq, Hash, Clone, PartialOrd, Ord, Copy)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct Span {
+    /// Start location (inclusive).
+    pub start: Location,
+    /// End location (exclusive).
+    pub end: Location,
+}
+
+impl fmt::Debug for Span {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "Span({:?}..{:?})", self.start, self.end)
+    }
+}
+
+impl Span {
+    const EMPTY: Span = Self::empty();
+
+    /// Create a new span from a start and end location.
+    pub const fn new(start: Location, end: Location) -> Span {
+        Span { start, end }
+    }
+
+    /// Return an empty span.
+    pub const fn empty() -> Span {
+        Span {
+            start: Location::empty(),
+            end: Location::empty(),
+        }
+    }
+
+    /// Return the smallest span that contains both `self` and `other`.
+    pub fn union(&self, other: &Span) -> Span {
+        match (self, other) {
+            (&Span::EMPTY, _) => *other,
+            (_, &Span::EMPTY) => *self,
+            _ => Span {
+                start: cmp::min(self.start, other.start),
+                end: cmp::max(self.end, other.end),
+            },
+        }
+    }
+
+    /// Return the union of all spans in the iterator.
+    pub fn union_iter<I: IntoIterator<Item = Span>>(iter: I) -> Span {
+        iter.into_iter()
+            .reduce(|acc, item| acc.union(&item))
+            .unwrap_or_else(Span::empty)
+    }
+}
+
+/// A backwards-compatible alias for a token carrying source span information.
+pub type TokenWithSpan = TokenWithLocation;
+
+/// A [Token] with [Location] and [Span] attached to it
+#[derive(Debug, Eq, PartialEq, Clone, Hash, Ord, PartialOrd)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
 pub struct TokenWithLocation {
     pub token: Token,
     pub location: Location,
+    pub span: Span,
 }
 
 impl TokenWithLocation {
@@ -456,12 +539,21 @@ impl TokenWithLocation {
         TokenWithLocation {
             token,
             location: Location { line, column },
+            span: Span::new(Location { line, column }, Location { line, column }),
         }
     }
 
     pub fn wrap(token: Token) -> TokenWithLocation {
         TokenWithLocation::new(token, 0, 0)
     }
+
+    pub fn with_span(token: Token, span: Span) -> TokenWithLocation {
+        TokenWithLocation {
+            token,
+            location: span.start,
+            span,
+        }
+    }
 }
 
 impl PartialEq<Token> for TokenWithLocation {
@@ -656,7 +748,12 @@ impl<'a> Tokenizer<'a> {
 
         let mut location = state.location();
         while let Some(token) = self.next_token(&mut state)? {
-            buf.push(TokenWithLocation { token, location });
+            let span = Span::new(location, state.location());
+            buf.push(TokenWithLocation {
+                token,
+                location,
+                span,
+            });
 
             location = state.location();
         }
diff --git a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_bigquery.rs b/app/tests/sqlparser_bigquery.rs
index d4c178b..bc118d0 100644
--- a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_bigquery.rs
+++ b/app/tests/sqlparser_bigquery.rs
@@ -678,10 +678,14 @@ fn parse_typed_struct_syntax_bigquery() {
                     Ident {
                         value: "t".into(),
                         quote_style: None,
+                   span: Span::empty(),
+                    span: Span::empty(),
                     },
                     Ident {
                         value: "str_col".into(),
                         quote_style: None,
+                   span: Span::empty(),
+                    span: Span::empty(),
                     },
                 ]),
             ],
@@ -690,6 +694,8 @@ fn parse_typed_struct_syntax_bigquery() {
                     field_name: Some(Ident {
                         value: "x".into(),
                         quote_style: None,
+                   span: Span::empty(),
+                    span: Span::empty(),
                     }),
                     field_type: DataType::Int64
                 },
@@ -697,6 +703,8 @@ fn parse_typed_struct_syntax_bigquery() {
                     field_name: Some(Ident {
                         value: "y".into(),
                         quote_style: None,
+                   span: Span::empty(),
+                    span: Span::empty(),
                     }),
                     field_type: DataType::String(None)
                 },
@@ -709,6 +717,8 @@ fn parse_typed_struct_syntax_bigquery() {
             values: vec![Expr::Identifier(Ident {
                 value: "nested_col".into(),
                 quote_style: None,
+           span: Span::empty(),
+            span: Span::empty(),
             }),],
             fields: vec![
                 StructField {
@@ -740,6 +750,8 @@ fn parse_typed_struct_syntax_bigquery() {
             values: vec![Expr::Identifier(Ident {
                 value: "nested_col".into(),
                 quote_style: None,
+           span: Span::empty(),
+            span: Span::empty(),
             }),],
             fields: vec![
                 StructField {
@@ -987,10 +999,14 @@ fn parse_typed_struct_syntax_bigquery_and_generic() {
                     Ident {
                         value: "t".into(),
                         quote_style: None,
+                   span: Span::empty(),
+                    span: Span::empty(),
                     },
                     Ident {
                         value: "str_col".into(),
                         quote_style: None,
+                   span: Span::empty(),
+                    span: Span::empty(),
                     },
                 ]),
             ],
@@ -999,6 +1015,8 @@ fn parse_typed_struct_syntax_bigquery_and_generic() {
                     field_name: Some(Ident {
                         value: "x".into(),
                         quote_style: None,
+                   span: Span::empty(),
+                    span: Span::empty(),
                     }),
                     field_type: DataType::Int64
                 },
@@ -1006,6 +1024,8 @@ fn parse_typed_struct_syntax_bigquery_and_generic() {
                     field_name: Some(Ident {
                         value: "y".into(),
                         quote_style: None,
+                   span: Span::empty(),
+                    span: Span::empty(),
                     }),
                     field_type: DataType::String(None)
                 },
@@ -1018,6 +1038,8 @@ fn parse_typed_struct_syntax_bigquery_and_generic() {
             values: vec![Expr::Identifier(Ident {
                 value: "nested_col".into(),
                 quote_style: None,
+           span: Span::empty(),
+            span: Span::empty(),
             }),],
             fields: vec![
                 StructField {
@@ -1049,6 +1071,8 @@ fn parse_typed_struct_syntax_bigquery_and_generic() {
             values: vec![Expr::Identifier(Ident {
                 value: "nested_col".into(),
                 quote_style: None,
+           span: Span::empty(),
+            span: Span::empty(),
             }),],
             fields: vec![
                 StructField {
diff --git a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_clickhouse.rs b/app/tests/sqlparser_clickhouse.rs
index 90af12a..a9b0ea2 100644
--- a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_clickhouse.rs
+++ b/app/tests/sqlparser_clickhouse.rs
@@ -45,6 +45,8 @@ fn parse_map_access_expr() {
                 column: Box::new(Identifier(Ident {
                     value: "string_values".to_string(),
                     quote_style: None,
+               span: Span::empty(),
+                span: Span::empty(),
                 })),
                 keys: vec![MapAccessKey {
                     key: call(
@@ -903,7 +905,8 @@ fn parse_create_view_with_fields_data_types() {
                         data_type: Some(DataType::Custom(
                             ObjectName(vec![Ident {
                                 value: "int".into(),
-                                quote_style: Some('"')
+                                quote_style: Some('"'),
+                            span: Span::empty(),
                             }]),
                             vec![]
                         )),
@@ -914,7 +917,8 @@ fn parse_create_view_with_fields_data_types() {
                         data_type: Some(DataType::Custom(
                             ObjectName(vec![Ident {
                                 value: "String".into(),
-                                quote_style: Some('"')
+                                quote_style: Some('"'),
+                            span: Span::empty(),
                             }]),
                             vec![]
                         )),
diff --git a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_common.rs b/app/tests/sqlparser_common.rs
index e22877d..efc25cf 100644
--- a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_common.rs
+++ b/app/tests/sqlparser_common.rs
@@ -1271,6 +1271,8 @@ fn parse_select_with_date_column_name() {
         &Expr::Identifier(Ident {
             value: "date".into(),
             quote_style: None,
+       span: Span::empty(),
+        span: Span::empty(),
         }),
         expr_from_projection(only(&select.projection)),
     );
@@ -1789,6 +1791,8 @@ fn parse_null_like() {
             alias: Ident {
                 value: "col_null".to_owned(),
                 quote_style: None,
+           span: Span::empty(),
+            span: Span::empty(),
             },
         },
         select.projection[0]
@@ -1805,6 +1809,8 @@ fn parse_null_like() {
             alias: Ident {
                 value: "null_col".to_owned(),
                 quote_style: None,
+           span: Span::empty(),
+            span: Span::empty(),
             },
         },
         select.projection[1]
@@ -2823,6 +2829,8 @@ fn parse_listagg() {
                     expr: Expr::Identifier(Ident {
                         value: "id".to_string(),
                         quote_style: None,
+                   span: Span::empty(),
+                    span: Span::empty(),
                     }),
                     asc: None,
                     nulls_first: None,
@@ -2832,6 +2840,8 @@ fn parse_listagg() {
                     expr: Expr::Identifier(Ident {
                         value: "username".to_string(),
                         quote_style: None,
+                   span: Span::empty(),
+                    span: Span::empty(),
                     }),
                     asc: None,
                     nulls_first: None,
@@ -4038,7 +4048,8 @@ fn parse_alter_table() {
                 [SqlOption::KeyValue {
                     key: Ident {
                         value: "classification".to_string(),
-                        quote_style: Some('\'')
+                        quote_style: Some('\''),
+                    span: Span::empty(),
                     },
                     value: Expr::Value(Value::SingleQuotedString("parquet".to_string())),
                 }],
@@ -4833,6 +4844,8 @@ fn test_parse_named_window() {
                     name: ObjectName(vec![Ident {
                         value: "MIN".to_string(),
                         quote_style: None,
+                   span: Span::empty(),
+                    span: Span::empty(),
                     }]),
                     parameters: FunctionArguments::None,
                     args: FunctionArguments::List(FunctionArgumentList {
@@ -4841,6 +4854,8 @@ fn test_parse_named_window() {
                             Expr::Identifier(Ident {
                                 value: "c12".to_string(),
                                 quote_style: None,
+                           span: Span::empty(),
+                            span: Span::empty(),
                             }),
                         ))],
                         clauses: vec![],
@@ -4850,12 +4865,16 @@ fn test_parse_named_window() {
                     over: Some(WindowType::NamedWindow(Ident {
                         value: "window1".to_string(),
                         quote_style: None,
+                   span: Span::empty(),
+                    span: Span::empty(),
                     })),
                     within_group: vec![],
                 }),
                 alias: Ident {
                     value: "min1".to_string(),
                     quote_style: None,
+               span: Span::empty(),
+                span: Span::empty(),
                 },
             },
             SelectItem::ExprWithAlias {
@@ -4863,6 +4882,8 @@ fn test_parse_named_window() {
                     name: ObjectName(vec![Ident {
                         value: "MAX".to_string(),
                         quote_style: None,
+                   span: Span::empty(),
+                    span: Span::empty(),
                     }]),
                     parameters: FunctionArguments::None,
                     args: FunctionArguments::List(FunctionArgumentList {
@@ -4871,6 +4892,8 @@ fn test_parse_named_window() {
                             Expr::Identifier(Ident {
                                 value: "c12".to_string(),
                                 quote_style: None,
+                           span: Span::empty(),
+                            span: Span::empty(),
                             }),
                         ))],
                         clauses: vec![],
@@ -4880,12 +4903,16 @@ fn test_parse_named_window() {
                     over: Some(WindowType::NamedWindow(Ident {
                         value: "window2".to_string(),
                         quote_style: None,
+                   span: Span::empty(),
+                    span: Span::empty(),
                     })),
                     within_group: vec![],
                 }),
                 alias: Ident {
                     value: "max1".to_string(),
                     quote_style: None,
+               span: Span::empty(),
+                span: Span::empty(),
                 },
             },
         ],
@@ -4895,6 +4922,8 @@ fn test_parse_named_window() {
                 name: ObjectName(vec![Ident {
                     value: "aggregate_test_100".to_string(),
                     quote_style: None,
+               span: Span::empty(),
+                span: Span::empty(),
                 }]),
                 alias: None,
                 args: None,
@@ -4919,6 +4948,8 @@ fn test_parse_named_window() {
                 Ident {
                     value: "window1".to_string(),
                     quote_style: None,
+               span: Span::empty(),
+                span: Span::empty(),
                 },
                 NamedWindowExpr::WindowSpec(WindowSpec {
                     window_name: None,
@@ -4927,6 +4958,8 @@ fn test_parse_named_window() {
                         expr: Expr::Identifier(Ident {
                             value: "C12".to_string(),
                             quote_style: None,
+                       span: Span::empty(),
+                        span: Span::empty(),
                         }),
                         asc: None,
                         nulls_first: None,
@@ -4939,12 +4972,16 @@ fn test_parse_named_window() {
                 Ident {
                     value: "window2".to_string(),
                     quote_style: None,
+               span: Span::empty(),
+                span: Span::empty(),
                 },
                 NamedWindowExpr::WindowSpec(WindowSpec {
                     window_name: None,
                     partition_by: vec![Expr::Identifier(Ident {
                         value: "C11".to_string(),
                         quote_style: None,
+                   span: Span::empty(),
+                    span: Span::empty(),
                     })],
                     order_by: vec![],
                     window_frame: None,
@@ -5425,6 +5462,8 @@ fn interval_disallow_interval_expr_gt() {
             right: Box::new(Expr::Identifier(Ident {
                 value: "x".to_string(),
                 quote_style: None,
+           span: Span::empty(),
+            span: Span::empty(),
             })),
         }
     )
@@ -5471,6 +5510,8 @@ fn parse_interval_and_or_xor() {
             projection: vec![UnnamedExpr(Expr::Identifier(Ident {
                 value: "col".to_string(),
                 quote_style: None,
+           span: Span::empty(),
+            span: Span::empty(),
             }))],
             into: None,
             from: vec![TableWithJoins {
@@ -5478,6 +5519,8 @@ fn parse_interval_and_or_xor() {
                     name: ObjectName(vec![Ident {
                         value: "test".to_string(),
                         quote_style: None,
+                   span: Span::empty(),
+                    span: Span::empty(),
                     }]),
                     alias: None,
                     args: None,
@@ -5496,12 +5539,16 @@ fn parse_interval_and_or_xor() {
                     left: Box::new(Expr::Identifier(Ident {
                         value: "d3_date".to_string(),
                         quote_style: None,
+                   span: Span::empty(),
+                    span: Span::empty(),
                     })),
                     op: BinaryOperator::Gt,
                     right: Box::new(Expr::BinaryOp {
                         left: Box::new(Expr::Identifier(Ident {
                             value: "d1_date".to_string(),
                             quote_style: None,
+                       span: Span::empty(),
+                        span: Span::empty(),
                         })),
                         op: BinaryOperator::Plus,
                         right: Box::new(Expr::Interval(Interval {
@@ -5520,12 +5567,16 @@ fn parse_interval_and_or_xor() {
                     left: Box::new(Expr::Identifier(Ident {
                         value: "d2_date".to_string(),
                         quote_style: None,
+                   span: Span::empty(),
+                    span: Span::empty(),
                     })),
                     op: BinaryOperator::Gt,
                     right: Box::new(Expr::BinaryOp {
                         left: Box::new(Expr::Identifier(Ident {
                             value: "d1_date".to_string(),
                             quote_style: None,
+                       span: Span::empty(),
+                        span: Span::empty(),
                         })),
                         op: BinaryOperator::Plus,
                         right: Box::new(Expr::Interval(Interval {
@@ -5617,6 +5668,8 @@ fn parse_at_timezone() {
             alias: Ident {
                 value: "hour".to_string(),
                 quote_style: Some('"'),
+           span: Span::empty(),
+            span: Span::empty(),
             },
         },
         only(&select.projection),
@@ -6637,6 +6690,8 @@ fn parse_recursive_cte() {
             name: Ident {
                 value: "nums".to_string(),
                 quote_style: None,
+           span: Span::empty(),
+            span: Span::empty(),
             },
             columns: vec![TableAliasColumnDef::from_name("val")],
         },
@@ -7632,6 +7687,8 @@ fn lateral_function() {
                 name: ObjectName(vec![Ident {
                     value: "customer".to_string(),
                     quote_style: None,
+               span: Span::empty(),
+                span: Span::empty(),
                 }]),
                 alias: None,
                 args: None,
@@ -8270,10 +8327,14 @@ fn parse_grant() {
                                 Ident {
                                     value: "shape".into(),
                                     quote_style: None,
+                               span: Span::empty(),
+                                span: Span::empty(),
                                 },
                                 Ident {
                                     value: "size".into(),
                                     quote_style: None,
+                               span: Span::empty(),
+                                span: Span::empty(),
                                 },
                             ])
                         },
@@ -8515,6 +8576,8 @@ fn parse_merge() {
                         name: Ident {
                             value: "stg".to_string(),
                             quote_style: None,
+                       span: Span::empty(),
+                        span: Span::empty(),
                         },
                         columns: vec![],
                     }),
@@ -8714,7 +8777,8 @@ fn test_lock_table() {
         lock.of.unwrap().0,
         vec![Ident {
             value: "school".to_string(),
-            quote_style: None
+            quote_style: None,
+        span: Span::empty(),
         }]
     );
     assert!(lock.nonblock.is_none());
@@ -8728,7 +8792,8 @@ fn test_lock_table() {
         lock.of.unwrap().0,
         vec![Ident {
             value: "school".to_string(),
-            quote_style: None
+            quote_style: None,
+        span: Span::empty(),
         }]
     );
     assert!(lock.nonblock.is_none());
@@ -8742,7 +8807,8 @@ fn test_lock_table() {
         lock.of.unwrap().0,
         vec![Ident {
             value: "school".to_string(),
-            quote_style: None
+            quote_style: None,
+        span: Span::empty(),
         }]
     );
     assert!(lock.nonblock.is_none());
@@ -8752,7 +8818,8 @@ fn test_lock_table() {
         lock.of.unwrap().0,
         vec![Ident {
             value: "student".to_string(),
-            quote_style: None
+            quote_style: None,
+        span: Span::empty(),
         }]
     );
     assert!(lock.nonblock.is_none());
@@ -8769,7 +8836,8 @@ fn test_lock_nonblock() {
         lock.of.unwrap().0,
         vec![Ident {
             value: "school".to_string(),
-            quote_style: None
+            quote_style: None,
+        span: Span::empty(),
         }]
     );
     assert_eq!(lock.nonblock.unwrap(), NonBlock::SkipLocked);
@@ -8783,7 +8851,8 @@ fn test_lock_nonblock() {
         lock.of.unwrap().0,
         vec![Ident {
             value: "school".to_string(),
-            quote_style: None
+            quote_style: None,
+        span: Span::empty(),
         }]
     );
     assert_eq!(lock.nonblock.unwrap(), NonBlock::Nowait);
@@ -9584,7 +9653,8 @@ fn parse_pivot_table() {
             alias: Some(TableAlias {
                 name: Ident {
                     value: "p".to_string(),
-                    quote_style: None
+                    quote_style: None,
+                span: Span::empty(),
                 },
                 columns: vec![
                     TableAliasColumnDef::from_name("c"),
@@ -9636,12 +9706,14 @@ fn parse_unpivot_table() {
             }),
             value: Ident {
                 value: "quantity".to_string(),
-                quote_style: None
+                quote_style: None,
+            span: Span::empty(),
             },
 
             name: Ident {
                 value: "quarter".to_string(),
-                quote_style: None
+                quote_style: None,
+            span: Span::empty(),
             },
             columns: ["Q1", "Q2", "Q3", "Q4"]
                 .into_iter()
@@ -9704,12 +9776,14 @@ fn parse_pivot_unpivot_table() {
                 }),
                 value: Ident {
                     value: "population".to_string(),
-                    quote_style: None
+                    quote_style: None,
+                span: Span::empty(),
                 },
 
                 name: Ident {
                     value: "year".to_string(),
-                    quote_style: None
+                    quote_style: None,
+                span: Span::empty(),
                 },
                 columns: ["population_2000", "population_2010"]
                     .into_iter()
@@ -9999,10 +10073,14 @@ fn parse_execute_stored_procedure() {
             Ident {
                 value: "my_schema".to_string(),
                 quote_style: None,
+           span: Span::empty(),
+            span: Span::empty(),
             },
             Ident {
                 value: "my_stored_procedure".to_string(),
                 quote_style: None,
+           span: Span::empty(),
+            span: Span::empty(),
             },
         ]),
         parameters: vec![
@@ -10143,12 +10221,14 @@ fn parse_unload() {
             }),
             to: Ident {
                 value: "s3://...".to_string(),
-                quote_style: Some('\'')
+                quote_style: Some('\''),
+            span: Span::empty(),
             },
             with: vec![SqlOption::KeyValue {
                 key: Ident {
                     value: "format".to_string(),
-                    quote_style: None
+                    quote_style: None,
+                span: Span::empty(),
                 },
                 value: Expr::Value(Value::SingleQuotedString("AVRO".to_string()))
             }]
@@ -11206,6 +11286,8 @@ fn test_extract_seconds_ok() {
             field: DateTimeField::Custom(Ident {
                 value: "seconds".to_string(),
                 quote_style: None,
+           span: Span::empty(),
+            span: Span::empty(),
             }),
             syntax: ExtractSyntax::From,
             expr: Box::new(Expr::Cast {
@@ -11231,6 +11313,8 @@ fn test_extract_seconds_single_quote_ok() {
             field: DateTimeField::Custom(Ident {
                 value: "seconds".to_string(),
                 quote_style: Some('\''),
+           span: Span::empty(),
+            span: Span::empty(),
             }),
             syntax: ExtractSyntax::From,
             expr: Box::new(Expr::Cast {
@@ -12130,7 +12214,8 @@ fn test_load_extension() {
             assert_eq!(
                 Ident {
                     value: "filename".to_string(),
-                    quote_style: Some('\'')
+                    quote_style: Some('\''),
+                span: Span::empty(),
                 },
                 extension_name
             );
diff --git a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_duckdb.rs b/app/tests/sqlparser_duckdb.rs
index 73b0f66..364fce1 100644
--- a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_duckdb.rs
+++ b/app/tests/sqlparser_duckdb.rs
@@ -275,6 +275,8 @@ fn test_select_union_by_name() {
                         name: ObjectName(vec![Ident {
                             value: "capitals".to_string(),
                             quote_style: None,
+                       span: Span::empty(),
+                        span: Span::empty(),
                         }]),
                         alias: None,
                         args: None,
@@ -317,6 +319,8 @@ fn test_select_union_by_name() {
                         name: ObjectName(vec![Ident {
                             value: "weather".to_string(),
                             quote_style: None,
+                       span: Span::empty(),
+                        span: Span::empty(),
                         }]),
                         alias: None,
                         args: None,
@@ -355,7 +359,8 @@ fn test_duckdb_install() {
         Statement::Install {
             extension_name: Ident {
                 value: "tpch".to_string(),
-                quote_style: None
+                quote_style: None,
+            span: Span::empty(),
             }
         }
     );
diff --git a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_mssql.rs b/app/tests/sqlparser_mssql.rs
index d1d8d12..ca016db 100644
--- a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_mssql.rs
+++ b/app/tests/sqlparser_mssql.rs
@@ -138,14 +138,16 @@ fn parse_create_procedure() {
                 ProcedureParam {
                     name: Ident {
                         value: "@foo".into(),
-                        quote_style: None
+                        quote_style: None,
+                    span: Span::empty(),
                     },
                     data_type: DataType::Int(None)
                 },
                 ProcedureParam {
                     name: Ident {
                         value: "@bar".into(),
-                        quote_style: None
+                        quote_style: None,
+                    span: Span::empty(),
                     },
                     data_type: DataType::Varchar(Some(CharacterLength::IntegerLength {
                         length: 256,
@@ -155,7 +157,8 @@ fn parse_create_procedure() {
             ]),
             name: ObjectName(vec![Ident {
                 value: "test".into(),
-                quote_style: None
+                quote_style: None,
+            span: Span::empty(),
             }])
         }
     )
@@ -207,11 +210,14 @@ fn parse_mssql_openjson() {
                 name: ObjectName(vec![Ident {
                     value: "t_test_table".into(),
                     quote_style: None,
+               span: Span::empty(),
+                span: Span::empty(),
                 },]),
                 alias: Some(TableAlias {
                     name: Ident {
                         value: "A".into(),
-                        quote_style: None
+                        quote_style: None,
+                    span: Span::empty(),
                     },
                     columns: vec![]
                 }),
@@ -228,10 +234,14 @@ fn parse_mssql_openjson() {
                         Ident {
                             value: "A".into(),
                             quote_style: None,
+                       span: Span::empty(),
+                        span: Span::empty(),
                         },
                         Ident {
                             value: "param".into(),
                             quote_style: None,
+                       span: Span::empty(),
+                        span: Span::empty(),
                         }
                     ]),
                     json_path: Some(Value::SingleQuotedString("$.config".into())),
@@ -240,6 +250,8 @@ fn parse_mssql_openjson() {
                             name: Ident {
                                 value: "kind".into(),
                                 quote_style: None,
+                           span: Span::empty(),
+                            span: Span::empty(),
                             },
                             r#type: DataType::Varchar(Some(CharacterLength::IntegerLength {
                                 length: 20,
@@ -252,6 +264,8 @@ fn parse_mssql_openjson() {
                             name: Ident {
                                 value: "id_list".into(),
                                 quote_style: Some('['),
+                           span: Span::empty(),
+                            span: Span::empty(),
                             },
                             r#type: DataType::Nvarchar(Some(CharacterLength::Max)),
                             path: Some("$.id_list".into()),
@@ -261,7 +275,8 @@ fn parse_mssql_openjson() {
                     alias: Some(TableAlias {
                         name: Ident {
                             value: "B".into(),
-                            quote_style: None
+                            quote_style: None,
+                        span: Span::empty(),
                         },
                         columns: vec![]
                     })
@@ -283,11 +298,14 @@ fn parse_mssql_openjson() {
                 name: ObjectName(vec![Ident {
                     value: "t_test_table".into(),
                     quote_style: None,
+               span: Span::empty(),
+                span: Span::empty(),
                 },]),
                 alias: Some(TableAlias {
                     name: Ident {
                         value: "A".into(),
-                        quote_style: None
+                        quote_style: None,
+                    span: Span::empty(),
                     },
                     columns: vec![]
                 }),
@@ -304,10 +322,14 @@ fn parse_mssql_openjson() {
                         Ident {
                             value: "A".into(),
                             quote_style: None,
+                       span: Span::empty(),
+                        span: Span::empty(),
                         },
                         Ident {
                             value: "param".into(),
                             quote_style: None,
+                       span: Span::empty(),
+                        span: Span::empty(),
                         }
                     ]),
                     json_path: None,
@@ -316,6 +338,8 @@ fn parse_mssql_openjson() {
                             name: Ident {
                                 value: "kind".into(),
                                 quote_style: None,
+                           span: Span::empty(),
+                            span: Span::empty(),
                             },
                             r#type: DataType::Varchar(Some(CharacterLength::IntegerLength {
                                 length: 20,
@@ -328,6 +352,8 @@ fn parse_mssql_openjson() {
                             name: Ident {
                                 value: "id_list".into(),
                                 quote_style: Some('['),
+                           span: Span::empty(),
+                            span: Span::empty(),
                             },
                             r#type: DataType::Nvarchar(Some(CharacterLength::Max)),
                             path: Some("$.id_list".into()),
@@ -337,7 +363,8 @@ fn parse_mssql_openjson() {
                     alias: Some(TableAlias {
                         name: Ident {
                             value: "B".into(),
-                            quote_style: None
+                            quote_style: None,
+                        span: Span::empty(),
                         },
                         columns: vec![]
                     })
@@ -359,11 +386,14 @@ fn parse_mssql_openjson() {
                 name: ObjectName(vec![Ident {
                     value: "t_test_table".into(),
                     quote_style: None,
+               span: Span::empty(),
+                span: Span::empty(),
                 },]),
                 alias: Some(TableAlias {
                     name: Ident {
                         value: "A".into(),
-                        quote_style: None
+                        quote_style: None,
+                    span: Span::empty(),
                     },
                     columns: vec![]
                 }),
@@ -380,10 +410,14 @@ fn parse_mssql_openjson() {
                         Ident {
                             value: "A".into(),
                             quote_style: None,
+                       span: Span::empty(),
+                        span: Span::empty(),
                         },
                         Ident {
                             value: "param".into(),
                             quote_style: None,
+                       span: Span::empty(),
+                        span: Span::empty(),
                         }
                     ]),
                     json_path: None,
@@ -392,6 +426,8 @@ fn parse_mssql_openjson() {
                             name: Ident {
                                 value: "kind".into(),
                                 quote_style: None,
+                           span: Span::empty(),
+                            span: Span::empty(),
                             },
                             r#type: DataType::Varchar(Some(CharacterLength::IntegerLength {
                                 length: 20,
@@ -404,6 +440,8 @@ fn parse_mssql_openjson() {
                             name: Ident {
                                 value: "id_list".into(),
                                 quote_style: Some('['),
+                           span: Span::empty(),
+                            span: Span::empty(),
                             },
                             r#type: DataType::Nvarchar(Some(CharacterLength::Max)),
                             path: None,
@@ -413,7 +451,8 @@ fn parse_mssql_openjson() {
                     alias: Some(TableAlias {
                         name: Ident {
                             value: "B".into(),
-                            quote_style: None
+                            quote_style: None,
+                        span: Span::empty(),
                         },
                         columns: vec![]
                     })
@@ -435,11 +474,14 @@ fn parse_mssql_openjson() {
                 name: ObjectName(vec![Ident {
                     value: "t_test_table".into(),
                     quote_style: None,
+               span: Span::empty(),
+                span: Span::empty(),
                 },]),
                 alias: Some(TableAlias {
                     name: Ident {
                         value: "A".into(),
-                        quote_style: None
+                        quote_style: None,
+                    span: Span::empty(),
                     },
                     columns: vec![]
                 }),
@@ -456,10 +498,14 @@ fn parse_mssql_openjson() {
                         Ident {
                             value: "A".into(),
                             quote_style: None,
+                       span: Span::empty(),
+                        span: Span::empty(),
                         },
                         Ident {
                             value: "param".into(),
                             quote_style: None,
+                       span: Span::empty(),
+                        span: Span::empty(),
                         }
                     ]),
                     json_path: Some(Value::SingleQuotedString("$.config".into())),
@@ -467,7 +513,8 @@ fn parse_mssql_openjson() {
                     alias: Some(TableAlias {
                         name: Ident {
                             value: "B".into(),
-                            quote_style: None
+                            quote_style: None,
+                        span: Span::empty(),
                         },
                         columns: vec![]
                     })
@@ -489,11 +536,14 @@ fn parse_mssql_openjson() {
                 name: ObjectName(vec![Ident {
                     value: "t_test_table".into(),
                     quote_style: None,
+               span: Span::empty(),
+                span: Span::empty(),
                 },]),
                 alias: Some(TableAlias {
                     name: Ident {
                         value: "A".into(),
-                        quote_style: None
+                        quote_style: None,
+                    span: Span::empty(),
                     },
                     columns: vec![]
                 }),
@@ -510,10 +560,14 @@ fn parse_mssql_openjson() {
                         Ident {
                             value: "A".into(),
                             quote_style: None,
+                       span: Span::empty(),
+                        span: Span::empty(),
                         },
                         Ident {
                             value: "param".into(),
                             quote_style: None,
+                       span: Span::empty(),
+                        span: Span::empty(),
                         }
                     ]),
                     json_path: None,
@@ -521,7 +575,8 @@ fn parse_mssql_openjson() {
                     alias: Some(TableAlias {
                         name: Ident {
                             value: "B".into(),
-                            quote_style: None
+                            quote_style: None,
+                        span: Span::empty(),
                         },
                         columns: vec![]
                     })
@@ -607,7 +662,8 @@ fn parse_mssql_create_role() {
                 authorization_owner,
                 Some(ObjectName(vec![Ident {
                     value: "helena".into(),
-                    quote_style: None
+                    quote_style: None,
+                span: Span::empty(),
                 }]))
             );
         }
@@ -623,12 +679,14 @@ fn parse_alter_role() {
         [Statement::AlterRole {
             name: Ident {
                 value: "old_name".into(),
-                quote_style: None
+                quote_style: None,
+            span: Span::empty(),
             },
             operation: AlterRoleOperation::RenameRole {
                 role_name: Ident {
                     value: "new_name".into(),
-                    quote_style: None
+                    quote_style: None,
+                span: Span::empty(),
                 }
             },
         }]
@@ -640,12 +698,14 @@ fn parse_alter_role() {
         Statement::AlterRole {
             name: Ident {
                 value: "role_name".into(),
-                quote_style: None
+                quote_style: None,
+            span: Span::empty(),
             },
             operation: AlterRoleOperation::AddMember {
                 member_name: Ident {
                     value: "new_member".into(),
-                    quote_style: None
+                    quote_style: None,
+                span: Span::empty(),
                 }
             },
         }
@@ -657,12 +717,14 @@ fn parse_alter_role() {
         Statement::AlterRole {
             name: Ident {
                 value: "role_name".into(),
-                quote_style: None
+                quote_style: None,
+            span: Span::empty(),
             },
             operation: AlterRoleOperation::DropMember {
                 member_name: Ident {
                     value: "old_member".into(),
-                    quote_style: None
+                    quote_style: None,
+                span: Span::empty(),
                 }
             },
         }
@@ -1143,7 +1205,8 @@ fn parse_substring_in_select() {
                         projection: vec![SelectItem::UnnamedExpr(Expr::Substring {
                             expr: Box::new(Expr::Identifier(Ident {
                                 value: "description".to_string(),
-                                quote_style: None
+                                quote_style: None,
+                            span: Span::empty(),
                             })),
                             substring_from: Some(Box::new(Expr::Value(number("0")))),
                             substring_for: Some(Box::new(Expr::Value(number("1")))),
@@ -1154,7 +1217,8 @@ fn parse_substring_in_select() {
                             relation: TableFactor::Table {
                                 name: ObjectName(vec![Ident {
                                     value: "test".to_string(),
-                                    quote_style: None
+                                    quote_style: None,
+                                span: Span::empty(),
                                 }]),
                                 alias: None,
                                 args: None,
@@ -1208,7 +1272,8 @@ fn parse_mssql_declare() {
                 Declare {
                     names: vec![Ident {
                         value: "@foo".to_string(),
-                        quote_style: None
+                        quote_style: None,
+                    span: Span::empty(),
                     }],
                     data_type: None,
                     assignment: None,
@@ -1222,7 +1287,8 @@ fn parse_mssql_declare() {
                 Declare {
                     names: vec![Ident {
                         value: "@bar".to_string(),
-                        quote_style: None
+                        quote_style: None,
+                    span: Span::empty(),
                     }],
                     data_type: Some(Int(None)),
                     assignment: None,
@@ -1236,7 +1302,8 @@ fn parse_mssql_declare() {
                 Declare {
                     names: vec![Ident {
                         value: "@baz".to_string(),
-                        quote_style: None
+                        quote_style: None,
+                    span: Span::empty(),
                     }],
                     data_type: Some(Text),
                     assignment: Some(MsSqlAssignment(Box::new(Expr::Value(SingleQuotedString(
@@ -1262,7 +1329,8 @@ fn parse_mssql_declare() {
                 stmts: vec![Declare {
                     names: vec![Ident {
                         value: "@bar".to_string(),
-                        quote_style: None
+                        quote_style: None,
+                    span: Span::empty(),
                     }],
                     data_type: Some(Int(None)),
                     assignment: None,
@@ -1364,10 +1432,14 @@ fn parse_create_table_with_valid_options() {
                     key: Ident {
                         value: "DISTRIBUTION".to_string(),
                         quote_style: None,
+                   span: Span::empty(),
+                    span: Span::empty(),
                     },
                     value: Expr::Identifier(Ident {
                         value: "ROUND_ROBIN".to_string(),
                         quote_style: None,
+                   span: Span::empty(),
+                    span: Span::empty(),
                     })
                 },
                 SqlOption::Partition {
@@ -1411,6 +1483,8 @@ fn parse_create_table_with_valid_options() {
                             name: Ident {
                                 value: "column_a".to_string(),
                                 quote_style: None,
+                           span: Span::empty(),
+                            span: Span::empty(),
                             },
                             asc: Some(true),
                         },
@@ -1418,6 +1492,8 @@ fn parse_create_table_with_valid_options() {
                             name: Ident {
                                 value: "column_b".to_string(),
                                 quote_style: None,
+                           span: Span::empty(),
+                            span: Span::empty(),
                             },
                             asc: Some(false),
                         },
@@ -1425,6 +1501,8 @@ fn parse_create_table_with_valid_options() {
                             name: Ident {
                                 value: "column_c".to_string(),
                                 quote_style: None,
+                           span: Span::empty(),
+                            span: Span::empty(),
                             },
                             asc: None,
                         },
@@ -1438,6 +1516,8 @@ fn parse_create_table_with_valid_options() {
                     key: Ident {
                         value: "DISTRIBUTION".to_string(),
                         quote_style: None,
+                   span: Span::empty(),
+                    span: Span::empty(),
                     },
                     value: Expr::Function(
                         Function {
@@ -1446,6 +1526,8 @@ fn parse_create_table_with_valid_options() {
                                     Ident {
                                         value: "HASH".to_string(),
                                         quote_style: None,
+                                   span: Span::empty(),
+                                    span: Span::empty(),
                                     },
                                 ],
                             ),
@@ -1460,6 +1542,8 @@ fn parse_create_table_with_valid_options() {
                                                     Ident {
                                                         value: "column_a".to_string(),
                                                         quote_style: None,
+                                                   span: Span::empty(),
+                                                    span: Span::empty(),
                                                     },
                                                 ),
                                             ),
@@ -1470,6 +1554,8 @@ fn parse_create_table_with_valid_options() {
                                                     Ident {
                                                         value: "column_b".to_string(),
                                                         quote_style: None,
+                                                   span: Span::empty(),
+                                                    span: Span::empty(),
                                                     },
                                                 ),
                                             ),
@@ -1504,12 +1590,16 @@ fn parse_create_table_with_valid_options() {
                 name: ObjectName(vec![Ident {
                     value: "mytable".to_string(),
                     quote_style: None,
+               span: Span::empty(),
+                span: Span::empty(),
                 },],),
                 columns: vec![
                     ColumnDef {
                         name: Ident {
                             value: "column_a".to_string(),
                             quote_style: None,
+                       span: Span::empty(),
+                        span: Span::empty(),
                         },
                         data_type: Int(None,),
                         collation: None,
@@ -1519,6 +1609,8 @@ fn parse_create_table_with_valid_options() {
                         name: Ident {
                             value: "column_b".to_string(),
                             quote_style: None,
+                       span: Span::empty(),
+                        span: Span::empty(),
                         },
                         data_type: Int(None,),
                         collation: None,
@@ -1528,6 +1620,8 @@ fn parse_create_table_with_valid_options() {
                         name: Ident {
                             value: "column_c".to_string(),
                             quote_style: None,
+                       span: Span::empty(),
+                        span: Span::empty(),
                         },
                         data_type: Int(None,),
                         collation: None,
@@ -1669,11 +1763,15 @@ fn parse_create_table_with_identity_column() {
                 name: ObjectName(vec![Ident {
                     value: "mytable".to_string(),
                     quote_style: None,
+               span: Span::empty(),
+                span: Span::empty(),
                 },],),
                 columns: vec![ColumnDef {
                     name: Ident {
                         value: "columnA".to_string(),
                         quote_style: None,
+                   span: Span::empty(),
+                    span: Span::empty(),
                     },
                     data_type: Int(None,),
                     collation: None,
diff --git a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_mysql.rs b/app/tests/sqlparser_mysql.rs
index 3d8b086..c2718eb 100644
--- a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_mysql.rs
+++ b/app/tests/sqlparser_mysql.rs
@@ -142,16 +142,19 @@ fn parse_flush() {
                 ObjectName(vec![
                     Ident {
                         value: "mek".to_string(),
-                        quote_style: Some('`')
+                        quote_style: Some('`'),
+                    span: Span::empty(),
                     },
                     Ident {
                         value: "table1".to_string(),
-                        quote_style: Some('`')
+                        quote_style: Some('`'),
+                    span: Span::empty(),
                     }
                 ]),
                 ObjectName(vec![Ident {
                     value: "table2".to_string(),
-                    quote_style: None
+                    quote_style: None,
+                span: Span::empty(),
                 }])
             ]
         }
@@ -179,16 +182,19 @@ fn parse_flush() {
                 ObjectName(vec![
                     Ident {
                         value: "mek".to_string(),
-                        quote_style: Some('`')
+                        quote_style: Some('`'),
+                    span: Span::empty(),
                     },
                     Ident {
                         value: "table1".to_string(),
-                        quote_style: Some('`')
+                        quote_style: Some('`'),
+                    span: Span::empty(),
                     }
                 ]),
                 ObjectName(vec![Ident {
                     value: "table2".to_string(),
-                    quote_style: None
+                    quote_style: None,
+                span: Span::empty(),
                 }])
             ]
         }
@@ -205,16 +211,19 @@ fn parse_flush() {
                 ObjectName(vec![
                     Ident {
                         value: "mek".to_string(),
-                        quote_style: Some('`')
+                        quote_style: Some('`'),
+                    span: Span::empty(),
                     },
                     Ident {
                         value: "table1".to_string(),
-                        quote_style: Some('`')
+                        quote_style: Some('`'),
+                    span: Span::empty(),
                     }
                 ]),
                 ObjectName(vec![Ident {
                     value: "table2".to_string(),
-                    quote_style: None
+                    quote_style: None,
+                span: Span::empty(),
                 }])
             ]
         }
@@ -1064,6 +1073,8 @@ fn parse_escaped_quote_identifiers_with_escape() {
                 projection: vec![SelectItem::UnnamedExpr(Expr::Identifier(Ident {
                     value: "quoted ` identifier".into(),
                     quote_style: Some('`'),
+               span: Span::empty(),
+                span: Span::empty(),
                 }))],
                 into: None,
                 from: vec![],
@@ -1115,6 +1126,8 @@ fn parse_escaped_quote_identifiers_with_no_escape() {
                 projection: vec![SelectItem::UnnamedExpr(Expr::Identifier(Ident {
                     value: "quoted `` identifier".into(),
                     quote_style: Some('`'),
+               span: Span::empty(),
+                span: Span::empty(),
                 }))],
                 into: None,
                 from: vec![],
@@ -1159,6 +1172,8 @@ fn parse_escaped_backticks_with_escape() {
                 projection: vec![SelectItem::UnnamedExpr(Expr::Identifier(Ident {
                     value: "`quoted identifier`".into(),
                     quote_style: Some('`'),
+               span: Span::empty(),
+                span: Span::empty(),
                 }))],
                 into: None,
                 from: vec![],
@@ -1207,6 +1222,8 @@ fn parse_escaped_backticks_with_no_escape() {
                 projection: vec![SelectItem::UnnamedExpr(Expr::Identifier(Ident {
                     value: "``quoted identifier``".into(),
                     quote_style: Some('`'),
+               span: Span::empty(),
+                span: Span::empty(),
                 }))],
                 into: None,
                 from: vec![],
@@ -2055,7 +2072,8 @@ fn parse_delete_with_order_by() {
                 vec![OrderByExpr {
                     expr: Expr::Identifier(Ident {
                         value: "id".to_owned(),
-                        quote_style: None
+                        quote_style: None,
+                    span: Span::empty(),
                     }),
                     asc: Some(false),
                     nulls_first: None,
@@ -2136,7 +2154,8 @@ fn parse_alter_table_add_column() {
                     },
                     column_position: Some(MySQLColumnPosition::After(Ident {
                         value: String::from("foo"),
-                        quote_style: None
+                        quote_style: None,
+                    span: Span::empty(),
                     })),
                 },]
             );
@@ -2187,6 +2206,8 @@ fn parse_alter_table_add_columns() {
                         column_position: Some(MySQLColumnPosition::After(Ident {
                             value: String::from("foo"),
                             quote_style: None,
+                       span: Span::empty(),
+                        span: Span::empty(),
                         })),
                     },
                 ]
@@ -2247,6 +2268,8 @@ fn parse_alter_table_change_column() {
         column_position: Some(MySQLColumnPosition::After(Ident {
             value: String::from("foo"),
             quote_style: None,
+       span: Span::empty(),
+        span: Span::empty(),
         })),
     };
     let sql4 = "ALTER TABLE orders CHANGE COLUMN description desc TEXT NOT NULL AFTER foo";
@@ -2286,6 +2309,8 @@ fn parse_alter_table_change_column_with_column_position() {
         column_position: Some(MySQLColumnPosition::After(Ident {
             value: String::from("total_count"),
             quote_style: None,
+       span: Span::empty(),
+        span: Span::empty(),
         })),
     };
 
@@ -2342,6 +2367,8 @@ fn parse_alter_table_modify_column() {
         column_position: Some(MySQLColumnPosition::After(Ident {
             value: String::from("foo"),
             quote_style: None,
+       span: Span::empty(),
+        span: Span::empty(),
         })),
     };
     let sql4 = "ALTER TABLE orders MODIFY COLUMN description TEXT NOT NULL AFTER foo";
@@ -2379,6 +2406,8 @@ fn parse_alter_table_modify_column_with_column_position() {
         column_position: Some(MySQLColumnPosition::After(Ident {
             value: String::from("total_count"),
             quote_style: None,
+       span: Span::empty(),
+        span: Span::empty(),
         })),
     };
 
@@ -2413,7 +2442,8 @@ fn parse_substring_in_select() {
                         projection: vec![SelectItem::UnnamedExpr(Expr::Substring {
                             expr: Box::new(Expr::Identifier(Ident {
                                 value: "description".to_string(),
-                                quote_style: None
+                                quote_style: None,
+                            span: Span::empty(),
                             })),
                             substring_from: Some(Box::new(Expr::Value(number("0")))),
                             substring_for: Some(Box::new(Expr::Value(number("1")))),
@@ -2424,7 +2454,8 @@ fn parse_substring_in_select() {
                             relation: TableFactor::Table {
                                 name: ObjectName(vec![Ident {
                                     value: "test".to_string(),
-                                    quote_style: None
+                                    quote_style: None,
+                                span: Span::empty(),
                                 }]),
                                 alias: None,
                                 args: None,
diff --git a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_postgres.rs b/app/tests/sqlparser_postgres.rs
index d27569e..6a27817 100644
--- a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_postgres.rs
+++ b/app/tests/sqlparser_postgres.rs
@@ -1172,6 +1172,8 @@ fn parse_copy_to() {
                             alias: Ident {
                                 value: "a".into(),
                                 quote_style: None,
+                           span: Span::empty(),
+                            span: Span::empty(),
                             },
                         },
                         SelectItem::ExprWithAlias {
@@ -1179,6 +1181,8 @@ fn parse_copy_to() {
                             alias: Ident {
                                 value: "b".into(),
                                 quote_style: None,
+                           span: Span::empty(),
+                            span: Span::empty(),
                             },
                         }
                     ],
@@ -1318,7 +1322,8 @@ fn parse_set() {
             variables: OneOrManyWithParens::One(ObjectName(vec![Ident::new("a")])),
             value: vec![Expr::Identifier(Ident {
                 value: "b".into(),
-                quote_style: None
+                quote_style: None,
+            span: Span::empty(),
             })],
         }
     );
@@ -1380,7 +1385,8 @@ fn parse_set() {
             ])),
             value: vec![Expr::Identifier(Ident {
                 value: "b".into(),
-                quote_style: None
+                quote_style: None,
+            span: Span::empty(),
             })],
         }
     );
@@ -1452,6 +1458,8 @@ fn parse_set_role() {
             role_name: Some(Ident {
                 value: "rolename".to_string(),
                 quote_style: Some('\"'),
+           span: Span::empty(),
+            span: Span::empty(),
             }),
         }
     );
@@ -1466,6 +1474,8 @@ fn parse_set_role() {
             role_name: Some(Ident {
                 value: "rolename".to_string(),
                 quote_style: Some('\''),
+           span: Span::empty(),
+            span: Span::empty(),
             }),
         }
     );
@@ -1765,7 +1775,8 @@ fn parse_pg_on_conflict() {
                     selection: Some(Expr::BinaryOp {
                         left: Box::new(Expr::Identifier(Ident {
                             value: "dsize".to_string(),
-                            quote_style: None
+                            quote_style: None,
+                        span: Span::empty(),
                         })),
                         op: BinaryOperator::Gt,
                         right: Box::new(Expr::Value(Value::Placeholder("$2".to_string())))
@@ -1802,7 +1813,8 @@ fn parse_pg_on_conflict() {
                     selection: Some(Expr::BinaryOp {
                         left: Box::new(Expr::Identifier(Ident {
                             value: "dsize".to_string(),
-                            quote_style: None
+                            quote_style: None,
+                        span: Span::empty(),
                         })),
                         op: BinaryOperator::Gt,
                         right: Box::new(Expr::Value(Value::Placeholder("$2".to_string())))
@@ -2105,14 +2117,16 @@ fn parse_array_index_expr() {
                 subscript: Box::new(Subscript::Index {
                     index: Expr::Identifier(Ident {
                         value: "baz".to_string(),
-                        quote_style: Some('"')
+                        quote_style: Some('"'),
+                    span: Span::empty(),
                     })
                 })
             }),
             subscript: Box::new(Subscript::Index {
                 index: Expr::Identifier(Ident {
                     value: "fooz".to_string(),
-                    quote_style: Some('"')
+                    quote_style: Some('"'),
+                span: Span::empty(),
                 })
             })
         },
@@ -3123,6 +3137,8 @@ fn parse_custom_operator() {
             left: Box::new(Expr::Identifier(Ident {
                 value: "relname".into(),
                 quote_style: None,
+           span: Span::empty(),
+            span: Span::empty(),
             })),
             op: BinaryOperator::PGCustomBinaryOperator(vec![
                 "database".into(),
@@ -3142,6 +3158,8 @@ fn parse_custom_operator() {
             left: Box::new(Expr::Identifier(Ident {
                 value: "relname".into(),
                 quote_style: None,
+           span: Span::empty(),
+            span: Span::empty(),
             })),
             op: BinaryOperator::PGCustomBinaryOperator(vec!["pg_catalog".into(), "~".into()]),
             right: Box::new(Expr::Value(Value::SingleQuotedString("^(table)$".into())))
@@ -3157,6 +3175,8 @@ fn parse_custom_operator() {
             left: Box::new(Expr::Identifier(Ident {
                 value: "relname".into(),
                 quote_style: None,
+           span: Span::empty(),
+            span: Span::empty(),
             })),
             op: BinaryOperator::PGCustomBinaryOperator(vec!["~".into()]),
             right: Box::new(Expr::Value(Value::SingleQuotedString("^(table)$".into())))
@@ -3307,12 +3327,14 @@ fn parse_alter_role() {
         Statement::AlterRole {
             name: Ident {
                 value: "old_name".into(),
-                quote_style: None
+                quote_style: None,
+            span: Span::empty(),
             },
             operation: AlterRoleOperation::RenameRole {
                 role_name: Ident {
                     value: "new_name".into(),
-                    quote_style: None
+                    quote_style: None,
+                span: Span::empty(),
                 }
             },
         }
@@ -3324,7 +3346,8 @@ fn parse_alter_role() {
         Statement::AlterRole {
             name: Ident {
                 value: "role_name".into(),
-                quote_style: None
+                quote_style: None,
+            span: Span::empty(),
             },
             operation: AlterRoleOperation::WithOptions {
                 options: vec![
@@ -3353,7 +3376,8 @@ fn parse_alter_role() {
         Statement::AlterRole {
             name: Ident {
                 value: "role_name".into(),
-                quote_style: None
+                quote_style: None,
+            span: Span::empty(),
             },
             operation: AlterRoleOperation::WithOptions {
                 options: vec![
@@ -3376,12 +3400,14 @@ fn parse_alter_role() {
         Statement::AlterRole {
             name: Ident {
                 value: "role_name".into(),
-                quote_style: None
+                quote_style: None,
+            span: Span::empty(),
             },
             operation: AlterRoleOperation::Set {
                 config_name: ObjectName(vec![Ident {
                     value: "maintenance_work_mem".into(),
-                    quote_style: None
+                    quote_style: None,
+                span: Span::empty(),
                 }]),
                 config_value: SetConfigValue::FromCurrent,
                 in_database: None
@@ -3395,17 +3421,20 @@ fn parse_alter_role() {
         [Statement::AlterRole {
             name: Ident {
                 value: "role_name".into(),
-                quote_style: None
+                quote_style: None,
+            span: Span::empty(),
             },
             operation: AlterRoleOperation::Set {
                 config_name: ObjectName(vec![Ident {
                     value: "maintenance_work_mem".into(),
-                    quote_style: None
+                    quote_style: None,
+                span: Span::empty(),
                 }]),
                 config_value: SetConfigValue::Value(Expr::Value(number("100000"))),
                 in_database: Some(ObjectName(vec![Ident {
                     value: "database_name".into(),
-                    quote_style: None
+                    quote_style: None,
+                span: Span::empty(),
                 }]))
             },
         }]
@@ -3417,17 +3446,20 @@ fn parse_alter_role() {
         Statement::AlterRole {
             name: Ident {
                 value: "role_name".into(),
-                quote_style: None
+                quote_style: None,
+            span: Span::empty(),
             },
             operation: AlterRoleOperation::Set {
                 config_name: ObjectName(vec![Ident {
                     value: "maintenance_work_mem".into(),
-                    quote_style: None
+                    quote_style: None,
+                span: Span::empty(),
                 }]),
                 config_value: SetConfigValue::Value(Expr::Value(number("100000"))),
                 in_database: Some(ObjectName(vec![Ident {
                     value: "database_name".into(),
-                    quote_style: None
+                    quote_style: None,
+                span: Span::empty(),
                 }]))
             },
         }
@@ -3439,17 +3471,20 @@ fn parse_alter_role() {
         Statement::AlterRole {
             name: Ident {
                 value: "role_name".into(),
-                quote_style: None
+                quote_style: None,
+            span: Span::empty(),
             },
             operation: AlterRoleOperation::Set {
                 config_name: ObjectName(vec![Ident {
                     value: "maintenance_work_mem".into(),
-                    quote_style: None
+                    quote_style: None,
+                span: Span::empty(),
                 }]),
                 config_value: SetConfigValue::Default,
                 in_database: Some(ObjectName(vec![Ident {
                     value: "database_name".into(),
-                    quote_style: None
+                    quote_style: None,
+                span: Span::empty(),
                 }]))
             },
         }
@@ -3461,7 +3496,8 @@ fn parse_alter_role() {
         Statement::AlterRole {
             name: Ident {
                 value: "role_name".into(),
-                quote_style: None
+                quote_style: None,
+            span: Span::empty(),
             },
             operation: AlterRoleOperation::Reset {
                 config_name: ResetConfig::ALL,
@@ -3476,16 +3512,19 @@ fn parse_alter_role() {
         Statement::AlterRole {
             name: Ident {
                 value: "role_name".into(),
-                quote_style: None
+                quote_style: None,
+            span: Span::empty(),
             },
             operation: AlterRoleOperation::Reset {
                 config_name: ResetConfig::ConfigName(ObjectName(vec![Ident {
                     value: "maintenance_work_mem".into(),
-                    quote_style: None
+                    quote_style: None,
+                span: Span::empty(),
                 }])),
                 in_database: Some(ObjectName(vec![Ident {
                     value: "database_name".into(),
-                    quote_style: None
+                    quote_style: None,
+                span: Span::empty(),
                 }]))
             },
         }
@@ -3630,7 +3669,8 @@ fn parse_drop_function() {
             func_desc: vec![FunctionDesc {
                 name: ObjectName(vec![Ident {
                     value: "test_func".to_string(),
-                    quote_style: None
+                    quote_style: None,
+                span: Span::empty(),
                 }]),
                 args: None
             }],
@@ -3646,7 +3686,8 @@ fn parse_drop_function() {
             func_desc: vec![FunctionDesc {
                 name: ObjectName(vec![Ident {
                     value: "test_func".to_string(),
-                    quote_style: None
+                    quote_style: None,
+                span: Span::empty(),
                 }]),
                 args: Some(vec![
                     OperateFunctionArg::with_name("a", DataType::Integer(None)),
@@ -3671,7 +3712,8 @@ fn parse_drop_function() {
                 FunctionDesc {
                     name: ObjectName(vec![Ident {
                         value: "test_func1".to_string(),
-                        quote_style: None
+                        quote_style: None,
+                    span: Span::empty(),
                     }]),
                     args: Some(vec![
                         OperateFunctionArg::with_name("a", DataType::Integer(None)),
@@ -3689,7 +3731,8 @@ fn parse_drop_function() {
                 FunctionDesc {
                     name: ObjectName(vec![Ident {
                         value: "test_func2".to_string(),
-                        quote_style: None
+                        quote_style: None,
+                    span: Span::empty(),
                     }]),
                     args: Some(vec![
                         OperateFunctionArg::with_name("a", DataType::Varchar(None)),
@@ -3720,7 +3763,8 @@ fn parse_drop_procedure() {
             proc_desc: vec![FunctionDesc {
                 name: ObjectName(vec![Ident {
                     value: "test_proc".to_string(),
-                    quote_style: None
+                    quote_style: None,
+                span: Span::empty(),
                 }]),
                 args: None
             }],
@@ -3736,7 +3780,8 @@ fn parse_drop_procedure() {
             proc_desc: vec![FunctionDesc {
                 name: ObjectName(vec![Ident {
                     value: "test_proc".to_string(),
-                    quote_style: None
+                    quote_style: None,
+                span: Span::empty(),
                 }]),
                 args: Some(vec![
                     OperateFunctionArg::with_name("a", DataType::Integer(None)),
@@ -3761,7 +3806,8 @@ fn parse_drop_procedure() {
                 FunctionDesc {
                     name: ObjectName(vec![Ident {
                         value: "test_proc1".to_string(),
-                        quote_style: None
+                        quote_style: None,
+                    span: Span::empty(),
                     }]),
                     args: Some(vec![
                         OperateFunctionArg::with_name("a", DataType::Integer(None)),
@@ -3779,7 +3825,8 @@ fn parse_drop_procedure() {
                 FunctionDesc {
                     name: ObjectName(vec![Ident {
                         value: "test_proc2".to_string(),
-                        quote_style: None
+                        quote_style: None,
+                    span: Span::empty(),
                     }]),
                     args: Some(vec![
                         OperateFunctionArg::with_name("a", DataType::Varchar(None)),
@@ -3860,6 +3907,8 @@ fn parse_dollar_quoted_string() {
             alias: Ident {
                 value: "col_name".into(),
                 quote_style: None,
+           span: Span::empty(),
+            span: Span::empty(),
             },
         }
     );
@@ -4204,20 +4253,24 @@ fn test_simple_postgres_insert_with_alias() {
             into: true,
             table_name: ObjectName(vec![Ident {
                 value: "test_tables".to_string(),
-                quote_style: None
+                quote_style: None,
+            span: Span::empty(),
             }]),
             table_alias: Some(Ident {
                 value: "test_table".to_string(),
-                quote_style: None
+                quote_style: None,
+            span: Span::empty(),
             }),
             columns: vec![
                 Ident {
                     value: "id".to_string(),
-                    quote_style: None
+                    quote_style: None,
+                span: Span::empty(),
                 },
                 Ident {
                     value: "a".to_string(),
-                    quote_style: None
+                    quote_style: None,
+                span: Span::empty(),
                 }
             ],
             overwrite: false,
@@ -4267,20 +4320,24 @@ fn test_simple_postgres_insert_with_alias() {
             into: true,
             table_name: ObjectName(vec![Ident {
                 value: "test_tables".to_string(),
-                quote_style: None
+                quote_style: None,
+            span: Span::empty(),
             }]),
             table_alias: Some(Ident {
                 value: "test_table".to_string(),
-                quote_style: None
+                quote_style: None,
+            span: Span::empty(),
             }),
             columns: vec![
                 Ident {
                     value: "id".to_string(),
-                    quote_style: None
+                    quote_style: None,
+                span: Span::empty(),
                 },
                 Ident {
                     value: "a".to_string(),
-                    quote_style: None
+                    quote_style: None,
+                span: Span::empty(),
                 }
             ],
             overwrite: false,
@@ -4332,20 +4389,24 @@ fn test_simple_insert_with_quoted_alias() {
             into: true,
             table_name: ObjectName(vec![Ident {
                 value: "test_tables".to_string(),
-                quote_style: None
+                quote_style: None,
+            span: Span::empty(),
             }]),
             table_alias: Some(Ident {
                 value: "Test_Table".to_string(),
-                quote_style: Some('"')
+                quote_style: Some('"'),
+            span: Span::empty(),
             }),
             columns: vec![
                 Ident {
                     value: "id".to_string(),
-                    quote_style: None
+                    quote_style: None,
+                span: Span::empty(),
                 },
                 Ident {
                     value: "a".to_string(),
-                    quote_style: None
+                    quote_style: None,
+                span: Span::empty(),
                 }
             ],
             overwrite: false,
@@ -5017,6 +5078,8 @@ fn check_arrow_precedence(sql: &str, arrow_operator: BinaryOperator) {
                 left: Box::new(Expr::Identifier(Ident {
                     value: "foo".to_string(),
                     quote_style: None,
+               span: Span::empty(),
+                span: Span::empty(),
                 })),
                 op: arrow_operator,
                 right: Box::new(Expr::Value(Value::SingleQuotedString("bar".to_string()))),
@@ -5047,6 +5110,8 @@ fn arrow_cast_precedence() {
             left: Box::new(Expr::Identifier(Ident {
                 value: "foo".to_string(),
                 quote_style: None,
+           span: Span::empty(),
+            span: Span::empty(),
             })),
             op: BinaryOperator::Arrow,
             right: Box::new(Expr::Cast {
diff --git a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_redshift.rs b/app/tests/sqlparser_redshift.rs
index 0a084b3..b1285a3 100644
--- a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_redshift.rs
+++ b/app/tests/sqlparser_redshift.rs
@@ -31,7 +31,8 @@ fn test_square_brackets_over_db_schema_table_name() {
         select.projection[0],
         SelectItem::UnnamedExpr(Expr::Identifier(Ident {
             value: "col1".to_string(),
-            quote_style: Some('[')
+            quote_style: Some('['),
+        span: Span::empty(),
         })),
     );
     assert_eq!(
@@ -41,11 +42,13 @@ fn test_square_brackets_over_db_schema_table_name() {
                 name: ObjectName(vec![
                     Ident {
                         value: "test_schema".to_string(),
-                        quote_style: Some('[')
+                        quote_style: Some('['),
+                    span: Span::empty(),
                     },
                     Ident {
                         value: "test_table".to_string(),
-                        quote_style: Some('[')
+                        quote_style: Some('['),
+                    span: Span::empty(),
                     }
                 ]),
                 alias: None,
@@ -79,7 +82,8 @@ fn test_double_quotes_over_db_schema_table_name() {
         select.projection[0],
         SelectItem::UnnamedExpr(Expr::Identifier(Ident {
             value: "col1".to_string(),
-            quote_style: Some('"')
+            quote_style: Some('"'),
+        span: Span::empty(),
         })),
     );
     assert_eq!(
@@ -89,11 +93,13 @@ fn test_double_quotes_over_db_schema_table_name() {
                 name: ObjectName(vec![
                     Ident {
                         value: "test_schema".to_string(),
-                        quote_style: Some('"')
+                        quote_style: Some('"'),
+                    span: Span::empty(),
                     },
                     Ident {
                         value: "test_table".to_string(),
-                        quote_style: Some('"')
+                        quote_style: Some('"'),
+                    span: Span::empty(),
                     }
                 ]),
                 alias: None,