agent_patch
runs/2026-02-28__18-27-22__gpt-5-3-codex/flux-pr-1435/flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/agent-logs/agent.patch
82806 bytes
diff --git a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/ast/mod.rs b/app/src/ast/mod.rs
index 9185c9d..8588068 100644
--- a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/ast/mod.rs
+++ b/app/src/ast/mod.rs
@@ -33,6 +33,8 @@ use serde::{Deserialize, Serialize};
#[cfg(feature = "visitor")]
use sqlparser_derive::{Visit, VisitMut};
+use crate::tokenizer::Span;
+
pub use self::data_type::{
ArrayElemTypeDef, CharLengthUnits, CharacterLength, DataType, ExactNumberInfo,
StructBracketKind, TimezoneInfo,
@@ -87,11 +89,13 @@ mod dml;
pub mod helpers;
mod operator;
mod query;
+mod spans;
mod trigger;
mod value;
#[cfg(feature = "visitor")]
mod visitor;
+pub use spans::Spanned;
pub struct DisplaySeparated<'a, T>
where
@@ -131,7 +135,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 +144,8 @@ 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 location span of this identifier in the source SQL.
+ pub span: Span,
}
impl Ident {
@@ -151,6 +157,7 @@ impl Ident {
Ident {
value: value.into(),
quote_style: None,
+ span: Span::empty(),
}
}
@@ -164,6 +171,32 @@ impl Ident {
Ident {
value: value.into(),
quote_style: Some(quote),
+ span: Span::empty(),
+ }
+ }
+
+ /// Create a new identifier with a specific source span and no quotes.
+ pub fn with_span<S>(span: Span, value: S) -> Self
+ where
+ S: Into<String>,
+ {
+ Ident {
+ value: value.into(),
+ quote_style: None,
+ span,
+ }
+ }
+
+ /// Create a new quoted identifier with a specific source 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 +206,7 @@ impl From<&str> for Ident {
Ident {
value: value.to_string(),
quote_style: None,
+ span: Span::empty(),
}
}
}
@@ -191,6 +225,33 @@ impl fmt::Display for Ident {
}
}
+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<core::cmp::Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+impl Ord for Ident {
+ fn cmp(&self, other: &Self) -> core::cmp::Ordering {
+ (&self.value, &self.quote_style).cmp(&(&other.value, &other.quote_style))
+ }
+}
+
+impl core::hash::Hash for Ident {
+ fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
+ core::hash::Hash::hash(&self.value, state);
+ core::hash::Hash::hash(&self.quote_style, state);
+ }
+}
+
/// A name of a table, view, custom type, etc., possibly multi-part, i.e. db.schema.obj
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
diff --git a/app/src/ast/spans.rs b/app/src/ast/spans.rs
new file mode 100644
index 0000000..24817ee
--- /dev/null
+++ b/app/src/ast/spans.rs
@@ -0,0 +1,562 @@
+// 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.
+
+#[cfg(not(feature = "std"))]
+use alloc::vec::Vec;
+
+use crate::tokenizer::{Span, TokenWithLocation};
+
+use super::{
+ Cte, ExceptSelectItem, ExcludeSelectItem, Expr, Fetch, Function, FunctionArg, FunctionArgExpr,
+ FunctionArgumentList, FunctionArguments, GroupByExpr, Ident, Join, JoinConstraint,
+ JoinOperator, ObjectName, Offset, OrderBy, OrderByExpr, Query, RenameSelectItem,
+ ReplaceSelectElement, ReplaceSelectItem, Select, SelectItem, SetExpr, Statement, TableAlias,
+ TableFactor, TableWithJoins, Value, Values, WildcardAdditionalOptions, With, Subscript,
+};
+
+/// Trait for AST nodes that can return a source span.
+pub trait Spanned {
+ fn span(&self) -> Span;
+}
+
+fn union_spans<I: Iterator<Item = Span>>(iter: I) -> Span {
+ Span::union_iter(iter)
+}
+
+impl Spanned for Span {
+ fn span(&self) -> Span {
+ *self
+ }
+}
+
+impl<T: Spanned> Spanned for Option<T> {
+ fn span(&self) -> Span {
+ self.as_ref().map_or(Span::empty(), Spanned::span)
+ }
+}
+
+impl<T: Spanned> Spanned for Box<T> {
+ fn span(&self) -> Span {
+ self.as_ref().span()
+ }
+}
+
+impl<T: Spanned> Spanned for Vec<T> {
+ fn span(&self) -> Span {
+ Span::union_iter(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 {
+ union_spans(self.0.iter().map(Spanned::span))
+ }
+}
+
+impl Spanned for TableAlias {
+ fn span(&self) -> Span {
+ union_spans(
+ core::iter::once(self.name.span()).chain(self.columns.iter().map(Spanned::span)),
+ )
+ }
+}
+
+impl Spanned for Statement {
+ fn span(&self) -> Span {
+ match self {
+ Statement::Query(query) => query.span(),
+ _ => Span::empty(),
+ }
+ }
+}
+
+impl Spanned for Query {
+ fn span(&self) -> Span {
+ union_spans(
+ self.with
+ .iter()
+ .map(Spanned::span)
+ .chain(core::iter::once(self.body.span()))
+ .chain(self.order_by.iter().map(Spanned::span))
+ .chain(self.limit.iter().map(Spanned::span))
+ .chain(self.limit_by.iter().map(Spanned::span))
+ .chain(self.offset.iter().map(Spanned::span))
+ .chain(self.fetch.iter().map(Spanned::span)),
+ )
+ }
+}
+
+impl Spanned for With {
+ fn span(&self) -> Span {
+ union_spans(self.cte_tables.iter().map(Spanned::span))
+ }
+}
+
+impl Spanned for Cte {
+ fn span(&self) -> Span {
+ union_spans(
+ core::iter::once(self.alias.span())
+ .chain(core::iter::once(self.query.span()))
+ .chain(self.from.iter().map(Spanned::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 Values {
+ fn span(&self) -> Span {
+ union_spans(
+ self.rows
+ .iter()
+ .flat_map(|row| row.iter())
+ .map(Spanned::span),
+ )
+ }
+}
+
+impl Spanned for Select {
+ fn span(&self) -> Span {
+ let mut spans = Vec::new();
+ spans.extend(self.projection.iter().map(Spanned::span));
+ spans.extend(self.from.iter().map(Spanned::span));
+ spans.extend(self.prewhere.iter().map(Spanned::span));
+ spans.extend(self.selection.iter().map(Spanned::span));
+ if let GroupByExpr::Expressions(exprs, _) = &self.group_by {
+ spans.extend(exprs.iter().map(Spanned::span));
+ }
+ spans.extend(self.cluster_by.iter().map(Spanned::span));
+ spans.extend(self.distribute_by.iter().map(Spanned::span));
+ spans.extend(self.sort_by.iter().map(Spanned::span));
+ spans.extend(self.having.iter().map(Spanned::span));
+ spans.extend(self.qualify.iter().map(Spanned::span));
+ if let Some(connect_by) = &self.connect_by {
+ spans.push(connect_by.condition.span());
+ spans.extend(connect_by.relationships.iter().map(Spanned::span));
+ }
+ Span::union_iter(spans.into_iter())
+ }
+}
+
+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, options) => name.span().union(&options.span()),
+ SelectItem::Wildcard(options) => options.span(),
+ }
+ }
+}
+
+impl Spanned for WildcardAdditionalOptions {
+ fn span(&self) -> Span {
+ union_spans(
+ self.opt_exclude
+ .iter()
+ .map(Spanned::span)
+ .chain(self.opt_except.iter().map(Spanned::span))
+ .chain(self.opt_replace.iter().map(Spanned::span))
+ .chain(self.opt_rename.iter().map(Spanned::span)),
+ )
+ }
+}
+
+impl Spanned for ExcludeSelectItem {
+ fn span(&self) -> Span {
+ match self {
+ ExcludeSelectItem::Single(ident) => ident.span(),
+ ExcludeSelectItem::Multiple(idents) => union_spans(idents.iter().map(Spanned::span)),
+ }
+ }
+}
+
+impl Spanned for ExceptSelectItem {
+ fn span(&self) -> Span {
+ union_spans(
+ core::iter::once(self.first_element.span())
+ .chain(self.additional_elements.iter().map(Spanned::span)),
+ )
+ }
+}
+
+impl Spanned for RenameSelectItem {
+ fn span(&self) -> Span {
+ match self {
+ RenameSelectItem::Single(item) => item.ident.span().union(&item.alias.span()),
+ RenameSelectItem::Multiple(items) => union_spans(
+ items
+ .iter()
+ .map(|item| item.ident.span().union(&item.alias.span())),
+ ),
+ }
+ }
+}
+
+impl Spanned for ReplaceSelectItem {
+ fn span(&self) -> Span {
+ union_spans(self.items.iter().map(Spanned::span))
+ }
+}
+
+impl Spanned for ReplaceSelectElement {
+ fn span(&self) -> Span {
+ self.expr.span().union(&self.column_name.span())
+ }
+}
+
+impl Spanned for TableWithJoins {
+ fn span(&self) -> Span {
+ union_spans(
+ core::iter::once(self.relation.span()).chain(self.joins.iter().map(Spanned::span)),
+ )
+ }
+}
+
+impl Spanned for TableFactor {
+ fn span(&self) -> Span {
+ match self {
+ TableFactor::Table {
+ name,
+ alias,
+ args: _,
+ with_hints: _,
+ version: _,
+ with_ordinality: _,
+ partitions: _,
+ json_path: _,
+ } => name.span().union(&alias.span()),
+ TableFactor::Derived {
+ subquery, alias, ..
+ } => subquery.span().union(&alias.span()),
+ TableFactor::TableFunction { expr, alias } => expr.span().union(&alias.span()),
+ TableFactor::UNNEST {
+ alias, array_exprs, ..
+ } => union_spans(
+ array_exprs
+ .iter()
+ .map(Spanned::span)
+ .chain(alias.iter().map(Spanned::span)),
+ ),
+ TableFactor::NestedJoin {
+ table_with_joins,
+ alias,
+ } => table_with_joins.span().union(&alias.span()),
+ _ => Span::empty(),
+ }
+ }
+}
+
+impl Spanned for Join {
+ fn span(&self) -> Span {
+ self.relation.span().union(&self.join_operator.span())
+ }
+}
+
+impl Spanned for JoinOperator {
+ fn span(&self) -> Span {
+ match self {
+ JoinOperator::Inner(c)
+ | JoinOperator::LeftOuter(c)
+ | JoinOperator::RightOuter(c)
+ | JoinOperator::FullOuter(c)
+ | JoinOperator::Semi(c)
+ | JoinOperator::LeftSemi(c)
+ | JoinOperator::RightSemi(c)
+ | JoinOperator::Anti(c)
+ | JoinOperator::LeftAnti(c)
+ | JoinOperator::RightAnti(c) => c.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(idents) => union_spans(idents.iter().map(Spanned::span)),
+ JoinConstraint::Natural | JoinConstraint::None => Span::empty(),
+ }
+ }
+}
+
+impl Spanned for OrderBy {
+ fn span(&self) -> Span {
+ union_spans(self.exprs.iter().map(Spanned::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 Function {
+ fn span(&self) -> Span {
+ self.name.span().union(&self.args.span())
+ }
+}
+
+impl Spanned for FunctionArguments {
+ fn span(&self) -> Span {
+ match self {
+ FunctionArguments::None => Span::empty(),
+ FunctionArguments::Subquery(query) => query.span(),
+ FunctionArguments::List(list) => list.span(),
+ }
+ }
+}
+
+impl Spanned for FunctionArgumentList {
+ fn span(&self) -> Span {
+ union_spans(
+ self.args
+ .iter()
+ .map(Spanned::span)
+ .chain(self.clauses.iter().map(|_| Span::empty())),
+ )
+ }
+}
+
+impl Spanned for FunctionArg {
+ fn span(&self) -> Span {
+ match self {
+ FunctionArg::Named { 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 Value {
+ fn span(&self) -> Span {
+ Span::empty()
+ }
+}
+
+impl Spanned for Subscript {
+ fn span(&self) -> Span {
+ match self {
+ Subscript::Index { index } => index.span(),
+ Subscript::Slice {
+ lower_bound,
+ upper_bound,
+ stride,
+ } => union_spans(
+ lower_bound
+ .iter()
+ .map(Spanned::span)
+ .chain(upper_bound.iter().map(Spanned::span))
+ .chain(stride.iter().map(Spanned::span)),
+ ),
+ }
+ }
+}
+
+impl Spanned for Expr {
+ fn span(&self) -> Span {
+ match self {
+ Expr::Identifier(ident) => ident.span(),
+ Expr::CompoundIdentifier(idents) => union_spans(idents.iter().map(Spanned::span)),
+ Expr::JsonAccess { value, .. } => value.span(),
+ Expr::CompositeAccess { expr, key } => expr.span().union(&key.span()),
+ Expr::IsFalse(expr)
+ | Expr::IsNotFalse(expr)
+ | Expr::IsTrue(expr)
+ | Expr::IsNotTrue(expr)
+ | Expr::IsNull(expr)
+ | Expr::IsNotNull(expr)
+ | Expr::IsUnknown(expr)
+ | Expr::IsNotUnknown(expr)
+ | Expr::Nested(expr)
+ | Expr::UnaryOp { expr, .. }
+ | Expr::Prior(expr)
+ | Expr::OuterJoin(expr) => expr.span(),
+ Expr::AnyOp { left, right, .. } | Expr::AllOp { left, right, .. } => {
+ left.span().union(&right.span())
+ }
+ Expr::IsDistinctFrom(lhs, rhs)
+ | Expr::IsNotDistinctFrom(lhs, rhs)
+ | Expr::BinaryOp {
+ left: lhs,
+ right: rhs,
+ ..
+ } => lhs.span().union(&rhs.span()),
+ Expr::InList { expr, list, .. } => {
+ expr.span().union(&union_spans(list.iter().map(Spanned::span)))
+ }
+ Expr::InSubquery { expr, subquery, .. } => expr.span().union(&subquery.span()),
+ Expr::InUnnest {
+ expr, array_expr, ..
+ } => expr.span().union(&array_expr.span()),
+ Expr::Between {
+ expr, low, high, ..
+ } => expr.span().union(&low.span()).union(&high.span()),
+ Expr::Like { expr, pattern, .. }
+ | Expr::ILike { expr, pattern, .. }
+ | Expr::SimilarTo { expr, pattern, .. } => expr.span().union(&pattern.span()),
+ Expr::Cast { expr, .. }
+ | Expr::Convert { expr, .. }
+ | Expr::AtTimeZone {
+ timestamp: expr, ..
+ } => expr.span(),
+ Expr::Extract { expr, .. } => expr.span(),
+ Expr::Ceil { expr, .. } | Expr::Floor { expr, .. } => expr.span(),
+ Expr::Position { expr, r#in } => expr.span().union(&r#in.span()),
+ Expr::Substring {
+ expr,
+ substring_from,
+ substring_for,
+ } => union_spans(
+ core::iter::once(expr.span())
+ .chain(substring_from.iter().map(Spanned::span))
+ .chain(substring_for.iter().map(Spanned::span)),
+ ),
+ Expr::Trim {
+ expr,
+ trim_what,
+ trim_characters,
+ ..
+ } => union_spans(
+ core::iter::once(expr.span())
+ .chain(trim_what.iter().map(Spanned::span))
+ .chain(trim_characters.iter().flat_map(|items| items.iter().map(Spanned::span))),
+ ),
+ Expr::Overlay {
+ expr,
+ overlay_what,
+ overlay_from,
+ overlay_for,
+ } => union_spans(
+ core::iter::once(expr.span())
+ .chain(core::iter::once(overlay_what.span()))
+ .chain(core::iter::once(overlay_from.span()))
+ .chain(overlay_for.iter().map(Spanned::span)),
+ ),
+ Expr::Collate { expr, collation } => expr.span().union(&collation.span()),
+ Expr::Value(value) => value.span(),
+ Expr::IntroducedString { value, .. } => value.span(),
+ Expr::TypedString { .. } => Span::empty(),
+ Expr::MapAccess { column, keys } => {
+ column
+ .span()
+ .union(&union_spans(keys.iter().map(|k| k.key.span())))
+ }
+ Expr::Function(function) => function.span(),
+ Expr::Method(method) => {
+ method
+ .expr
+ .span()
+ .union(&union_spans(method.method_chain.iter().map(Spanned::span)))
+ }
+ Expr::Case {
+ operand,
+ conditions,
+ results,
+ else_result,
+ } => union_spans(
+ operand
+ .iter()
+ .map(Spanned::span)
+ .chain(conditions.iter().map(Spanned::span))
+ .chain(results.iter().map(Spanned::span))
+ .chain(else_result.iter().map(Spanned::span)),
+ ),
+ Expr::Exists { subquery, .. } => subquery.span(),
+ Expr::Subquery(query) => query.span(),
+ Expr::GroupingSets(v) | Expr::Cube(v) | Expr::Rollup(v) => union_spans(
+ v.iter()
+ .flat_map(|items| items.iter())
+ .map(Spanned::span),
+ ),
+ Expr::Tuple(v) => union_spans(v.iter().map(Spanned::span)),
+ Expr::Struct { values, .. } => union_spans(values.iter().map(Spanned::span)),
+ Expr::Named { expr, name } => expr.span().union(&name.span()),
+ Expr::Dictionary(v) => union_spans(v.iter().map(|item| item.value.span())),
+ Expr::Map(map) => union_spans(
+ map.entries
+ .iter()
+ .flat_map(|entry| [entry.key.span(), entry.value.span()]),
+ ),
+ Expr::Subscript { expr, subscript } => expr.span().union(&subscript.span()),
+ Expr::Array(array) => union_spans(array.elem.iter().map(Spanned::span)),
+ Expr::Interval(interval) => interval.value.span(),
+ Expr::MatchAgainst { columns, .. } => union_spans(columns.iter().map(Spanned::span)),
+ Expr::Wildcard => Span::empty(),
+ Expr::QualifiedWildcard(name) => name.span(),
+ Expr::Lambda(lambda) => {
+ lambda
+ .body
+ .span()
+ .union(&union_spans(lambda.params.iter().map(Spanned::span)))
+ }
+ _ => Span::empty(),
+ }
+ }
+}
diff --git a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/parser/mod.rs b/app/src/parser/mod.rs
index 6767f35..b1f8ed1 100644
--- a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/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: Span::empty(),
})
.collect();
self.with_tokens_with_locations(tokens_with_locations)
@@ -3298,7 +3299,7 @@ impl<'a> Parser<'a> {
index += 1;
if let Some(TokenWithLocation {
token: Token::Whitespace(_),
- location: _,
+ ..
}) = token
{
continue;
@@ -3306,6 +3307,7 @@ impl<'a> Parser<'a> {
break token.cloned().unwrap_or(TokenWithLocation {
token: Token::EOF,
location: Location { line: 0, column: 0 },
+ span: Span::empty(),
});
})
}
@@ -3318,13 +3320,14 @@ impl<'a> Parser<'a> {
match self.tokens.get(index - 1) {
Some(TokenWithLocation {
token: Token::Whitespace(_),
- location: _,
+ ..
}) => continue,
non_whitespace => {
if n == 0 {
return non_whitespace.cloned().unwrap_or(TokenWithLocation {
token: Token::EOF,
location: Location { line: 0, column: 0 },
+ span: Span::empty(),
});
}
n -= 1;
@@ -3347,6 +3350,7 @@ impl<'a> Parser<'a> {
.unwrap_or(TokenWithLocation {
token: Token::EOF,
location: Location { line: 0, column: 0 },
+ span: Span::empty(),
})
}
@@ -3375,7 +3379,7 @@ impl<'a> Parser<'a> {
match self.tokens.get(self.index - 1) {
Some(TokenWithLocation {
token: Token::Whitespace(_),
- location: _,
+ ..
}) => continue,
token => {
return token
@@ -3401,7 +3405,7 @@ impl<'a> Parser<'a> {
self.index -= 1;
if let Some(TokenWithLocation {
token: Token::Whitespace(_),
- location: _,
+ ..
}) = self.tokens.get(self.index)
{
continue;
@@ -6819,6 +6823,7 @@ impl<'a> Parser<'a> {
TokenWithLocation {
token: Token::make_keyword(&name.to_string()),
location: next_token.location,
+ span: next_token.span,
},
);
}
@@ -7795,6 +7800,7 @@ impl<'a> Parser<'a> {
TokenWithLocation {
token: Token::Word(w),
location,
+ span: next_token.span,
},
)?,
},
@@ -7803,6 +7809,7 @@ impl<'a> Parser<'a> {
TokenWithLocation {
token: Token::Word(w),
location,
+ span: next_token.span,
},
),
},
@@ -7853,7 +7860,7 @@ 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::Word(w) => Ok(w.to_ident_with_span(next_token.span)),
Token::Number(w, false) => Ok(Ident::new(w)),
_ => self.expected("placeholder", next_token),
}?;
@@ -7865,6 +7872,7 @@ impl<'a> Parser<'a> {
TokenWithLocation {
token: unexpected,
location,
+ span: next_token.span,
},
),
}
@@ -7914,6 +7922,7 @@ impl<'a> Parser<'a> {
TokenWithLocation {
token: unexpected,
location,
+ span: next_token.span,
},
),
}
@@ -8455,6 +8464,7 @@ impl<'a> Parser<'a> {
.map(|value| Ident {
value: value.into(),
quote_style: ident.quote_style,
+ span: ident.span,
})
.collect::<Vec<_>>()
})
@@ -8468,9 +8478,10 @@ impl<'a> Parser<'a> {
pub fn parse_identifiers(&mut self) -> Result<Vec<Ident>, ParserError> {
let mut idents = vec![];
loop {
- match self.peek_token().token {
+ let token = self.peek_token();
+ match token.token {
Token::Word(w) => {
- idents.push(w.to_ident());
+ idents.push(w.to_ident_with_span(token.span));
}
Token::EOF | Token::Eq => break,
_ => {}
@@ -8523,8 +8534,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 token = self.next_token();
+ match token.token {
+ Token::Word(w) => idents.push(w.to_ident_with_span(token.span)),
Token::EOF => {
return Err(ParserError::ParserError(
"Empty input when parsing identifier".to_string(),
@@ -8541,8 +8553,10 @@ 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 => {
+ let token = self.next_token();
+ match token.token {
+ Token::Word(w) => idents.push(w.to_ident_with_span(token.span)),
Token::EOF => {
return Err(ParserError::ParserError(
"Trailing period in identifier".to_string(),
@@ -8553,7 +8567,8 @@ impl<'a> Parser<'a> {
"Unexpected token following period in identifier: {token}"
)))?
}
- },
+ }
+ }
Token::EOF => break,
token => {
return Err(ParserError::ParserError(format!(
@@ -8575,7 +8590,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].
@@ -8626,8 +8641,12 @@ 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,6 +12835,15 @@ impl Word {
Ident {
value: self.value.clone(),
quote_style: self.quote_style,
+ span: Span::empty(),
+ }
+ }
+
+ pub fn to_ident_with_span(&self, span: Span) -> Ident {
+ Ident {
+ value: self.value.clone(),
+ quote_style: self.quote_style,
+ span,
}
}
}
@@ -13389,14 +13417,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 +13440,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-3-codex/app/src/tokenizer.rs b/app/src/tokenizer.rs
index 05aaf1e..3ca1901 100644
--- a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/tokenizer.rs
+++ b/app/src/tokenizer.rs
@@ -422,7 +422,7 @@ impl fmt::Display for Whitespace {
}
/// Location in input string
-#[derive(Debug, Eq, PartialEq, Clone, Copy)]
+#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash, PartialOrd, Ord)]
pub struct Location {
/// Line number, starting from 1
pub line: u64,
@@ -430,6 +430,22 @@ pub struct Location {
pub column: u64,
}
+impl Location {
+ pub fn new(line: u64, column: u64) -> Self {
+ Self { line, column }
+ }
+
+ pub fn span_to(self, end: Self) -> Span {
+ Span::new(self, end)
+ }
+}
+
+impl From<(u64, u64)> for Location {
+ fn from((line, column): (u64, u64)) -> Self {
+ Self::new(line, column)
+ }
+}
+
impl fmt::Display for Location {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.line == 0 {
@@ -444,23 +460,94 @@ impl fmt::Display for Location {
}
}
+/// A span in the input string, inclusive start and exclusive end.
+#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash, PartialOrd, Ord)]
+pub struct Span {
+ pub start: Location,
+ pub end: Location,
+}
+
+impl Span {
+ pub fn new(start: Location, end: Location) -> Self {
+ Self { start, end }
+ }
+
+ pub fn empty() -> Self {
+ Self {
+ start: Location::new(0, 0),
+ end: Location::new(0, 0),
+ }
+ }
+
+ pub fn is_empty(&self) -> bool {
+ *self == Self::empty()
+ }
+
+ pub fn union(&self, other: &Self) -> Self {
+ if self.is_empty() {
+ return *other;
+ }
+ if other.is_empty() {
+ return *self;
+ }
+
+ let start = if self.start <= other.start {
+ self.start
+ } else {
+ other.start
+ };
+ let end = if self.end >= other.end {
+ self.end
+ } else {
+ other.end
+ };
+ Self::new(start, end)
+ }
+
+ pub fn union_opt(&self, other: &Option<Self>) -> Self {
+ match other {
+ Some(other) => self.union(other),
+ None => *self,
+ }
+ }
+
+ pub fn union_iter<I: Iterator<Item = Span>>(iter: I) -> Span {
+ iter.fold(Span::empty(), |acc, item| acc.union(&item))
+ }
+}
+
/// A [Token] with [Location] attached to it
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct TokenWithLocation {
pub token: Token,
pub location: Location,
+ pub span: Span,
}
impl TokenWithLocation {
pub fn new(token: Token, line: u64, column: u64) -> TokenWithLocation {
+ let location = Location { line, column };
TokenWithLocation {
token,
- location: Location { line, column },
+ location,
+ span: Span::new(location, location),
}
}
+ pub fn with_span(token: Token, span: Span) -> TokenWithLocation {
+ TokenWithLocation {
+ token,
+ location: span.start,
+ span,
+ }
+ }
+
+ pub fn at(token: Token, start: Location, end: Location) -> TokenWithLocation {
+ TokenWithLocation::with_span(token, Span::new(start, end))
+ }
+
pub fn wrap(token: Token) -> TokenWithLocation {
- TokenWithLocation::new(token, 0, 0)
+ TokenWithLocation::with_span(token, Span::empty())
}
}
@@ -656,7 +743,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 = location.span_to(state.location());
+ buf.push(TokenWithLocation {
+ token,
+ location,
+ span,
+ });
location = state.location();
}
@@ -2669,13 +2761,25 @@ mod tests {
.tokenize_with_location()
.unwrap();
let expected = vec![
- TokenWithLocation::new(Token::make_keyword("SELECT"), 1, 1),
- TokenWithLocation::new(Token::Whitespace(Whitespace::Space), 1, 7),
- TokenWithLocation::new(Token::make_word("a", None), 1, 8),
- TokenWithLocation::new(Token::Comma, 1, 9),
- TokenWithLocation::new(Token::Whitespace(Whitespace::Newline), 1, 10),
- TokenWithLocation::new(Token::Whitespace(Whitespace::Space), 2, 1),
- TokenWithLocation::new(Token::make_word("b", None), 2, 2),
+ TokenWithLocation::at(Token::make_keyword("SELECT"), (1, 1).into(), (1, 7).into()),
+ TokenWithLocation::at(
+ Token::Whitespace(Whitespace::Space),
+ (1, 7).into(),
+ (1, 8).into(),
+ ),
+ TokenWithLocation::at(Token::make_word("a", None), (1, 8).into(), (1, 9).into()),
+ TokenWithLocation::at(Token::Comma, (1, 9).into(), (1, 10).into()),
+ TokenWithLocation::at(
+ Token::Whitespace(Whitespace::Newline),
+ (1, 10).into(),
+ (2, 1).into(),
+ ),
+ TokenWithLocation::at(
+ Token::Whitespace(Whitespace::Space),
+ (2, 1).into(),
+ (2, 2).into(),
+ ),
+ TokenWithLocation::at(Token::make_word("b", None), (2, 2).into(), (2, 3).into()),
];
compare(expected, tokens);
}
diff --git a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_bigquery.rs b/app/tests/sqlparser_bigquery.rs
index d4c178b..6cf08dc 100644
--- a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_bigquery.rs
+++ b/app/tests/sqlparser_bigquery.rs
@@ -677,11 +677,11 @@ fn parse_typed_struct_syntax_bigquery() {
Expr::CompoundIdentifier(vec![
Ident {
value: "t".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
Ident {
value: "str_col".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
]),
],
@@ -689,14 +689,14 @@ fn parse_typed_struct_syntax_bigquery() {
StructField {
field_name: Some(Ident {
value: "x".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
}),
field_type: DataType::Int64
},
StructField {
field_name: Some(Ident {
value: "y".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
}),
field_type: DataType::String(None)
},
@@ -708,7 +708,7 @@ fn parse_typed_struct_syntax_bigquery() {
&Expr::Struct {
values: vec![Expr::Identifier(Ident {
value: "nested_col".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
}),],
fields: vec![
StructField {
@@ -739,7 +739,7 @@ fn parse_typed_struct_syntax_bigquery() {
&Expr::Struct {
values: vec![Expr::Identifier(Ident {
value: "nested_col".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
}),],
fields: vec![
StructField {
@@ -986,11 +986,11 @@ fn parse_typed_struct_syntax_bigquery_and_generic() {
Expr::CompoundIdentifier(vec![
Ident {
value: "t".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
Ident {
value: "str_col".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
]),
],
@@ -998,14 +998,14 @@ fn parse_typed_struct_syntax_bigquery_and_generic() {
StructField {
field_name: Some(Ident {
value: "x".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
}),
field_type: DataType::Int64
},
StructField {
field_name: Some(Ident {
value: "y".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
}),
field_type: DataType::String(None)
},
@@ -1017,7 +1017,7 @@ fn parse_typed_struct_syntax_bigquery_and_generic() {
&Expr::Struct {
values: vec![Expr::Identifier(Ident {
value: "nested_col".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
}),],
fields: vec![
StructField {
@@ -1048,7 +1048,7 @@ fn parse_typed_struct_syntax_bigquery_and_generic() {
&Expr::Struct {
values: vec![Expr::Identifier(Ident {
value: "nested_col".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::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-3-codex/app/tests/sqlparser_clickhouse.rs b/app/tests/sqlparser_clickhouse.rs
index 90af12a..b752915 100644
--- a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_clickhouse.rs
+++ b/app/tests/sqlparser_clickhouse.rs
@@ -44,7 +44,7 @@ fn parse_map_access_expr() {
projection: vec![UnnamedExpr(MapAccess {
column: Box::new(Identifier(Ident {
value: "string_values".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
})),
keys: vec![MapAccessKey {
key: call(
diff --git a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_common.rs b/app/tests/sqlparser_common.rs
index e22877d..0229b9c 100644
--- a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_common.rs
+++ b/app/tests/sqlparser_common.rs
@@ -1270,7 +1270,7 @@ fn parse_select_with_date_column_name() {
assert_eq!(
&Expr::Identifier(Ident {
value: "date".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
}),
expr_from_projection(only(&select.projection)),
);
@@ -1788,7 +1788,7 @@ fn parse_null_like() {
},
alias: Ident {
value: "col_null".to_owned(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
},
select.projection[0]
@@ -1804,7 +1804,7 @@ fn parse_null_like() {
},
alias: Ident {
value: "null_col".to_owned(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
},
select.projection[1]
@@ -2822,7 +2822,7 @@ fn parse_listagg() {
OrderByExpr {
expr: Expr::Identifier(Ident {
value: "id".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
}),
asc: None,
nulls_first: None,
@@ -2831,7 +2831,7 @@ fn parse_listagg() {
OrderByExpr {
expr: Expr::Identifier(Ident {
value: "username".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
}),
asc: None,
nulls_first: None,
@@ -4832,7 +4832,7 @@ fn test_parse_named_window() {
expr: Expr::Function(Function {
name: ObjectName(vec![Ident {
value: "MIN".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
}]),
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
@@ -4840,7 +4840,7 @@ fn test_parse_named_window() {
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
Expr::Identifier(Ident {
value: "c12".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
}),
))],
clauses: vec![],
@@ -4849,20 +4849,20 @@ fn test_parse_named_window() {
filter: None,
over: Some(WindowType::NamedWindow(Ident {
value: "window1".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
})),
within_group: vec![],
}),
alias: Ident {
value: "min1".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
},
SelectItem::ExprWithAlias {
expr: Expr::Function(Function {
name: ObjectName(vec![Ident {
value: "MAX".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
}]),
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
@@ -4870,7 +4870,7 @@ fn test_parse_named_window() {
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
Expr::Identifier(Ident {
value: "c12".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
}),
))],
clauses: vec![],
@@ -4879,13 +4879,13 @@ fn test_parse_named_window() {
filter: None,
over: Some(WindowType::NamedWindow(Ident {
value: "window2".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
})),
within_group: vec![],
}),
alias: Ident {
value: "max1".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
},
],
@@ -4894,7 +4894,7 @@ fn test_parse_named_window() {
relation: TableFactor::Table {
name: ObjectName(vec![Ident {
value: "aggregate_test_100".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
}]),
alias: None,
args: None,
@@ -4918,7 +4918,7 @@ fn test_parse_named_window() {
NamedWindowDefinition(
Ident {
value: "window1".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
NamedWindowExpr::WindowSpec(WindowSpec {
window_name: None,
@@ -4926,7 +4926,7 @@ fn test_parse_named_window() {
order_by: vec![OrderByExpr {
expr: Expr::Identifier(Ident {
value: "C12".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
}),
asc: None,
nulls_first: None,
@@ -4938,13 +4938,13 @@ fn test_parse_named_window() {
NamedWindowDefinition(
Ident {
value: "window2".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
NamedWindowExpr::WindowSpec(WindowSpec {
window_name: None,
partition_by: vec![Expr::Identifier(Ident {
value: "C11".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
})],
order_by: vec![],
window_frame: None,
@@ -5424,7 +5424,7 @@ fn interval_disallow_interval_expr_gt() {
op: BinaryOperator::Gt,
right: Box::new(Expr::Identifier(Ident {
value: "x".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
})),
}
)
@@ -5470,14 +5470,14 @@ fn parse_interval_and_or_xor() {
top_before_distinct: false,
projection: vec![UnnamedExpr(Expr::Identifier(Ident {
value: "col".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
}))],
into: None,
from: vec![TableWithJoins {
relation: TableFactor::Table {
name: ObjectName(vec![Ident {
value: "test".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
}]),
alias: None,
args: None,
@@ -5495,13 +5495,13 @@ fn parse_interval_and_or_xor() {
left: Box::new(Expr::BinaryOp {
left: Box::new(Expr::Identifier(Ident {
value: "d3_date".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
})),
op: BinaryOperator::Gt,
right: Box::new(Expr::BinaryOp {
left: Box::new(Expr::Identifier(Ident {
value: "d1_date".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
})),
op: BinaryOperator::Plus,
right: Box::new(Expr::Interval(Interval {
@@ -5519,13 +5519,13 @@ fn parse_interval_and_or_xor() {
right: Box::new(Expr::BinaryOp {
left: Box::new(Expr::Identifier(Ident {
value: "d2_date".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
})),
op: BinaryOperator::Gt,
right: Box::new(Expr::BinaryOp {
left: Box::new(Expr::Identifier(Ident {
value: "d1_date".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
})),
op: BinaryOperator::Plus,
right: Box::new(Expr::Interval(Interval {
@@ -5616,7 +5616,7 @@ fn parse_at_timezone() {
),
alias: Ident {
value: "hour".to_string(),
- quote_style: Some('"'),
+ quote_style: Some('"'), span: sqlparser::tokenizer::Span::empty(),
},
},
only(&select.projection),
@@ -6636,7 +6636,7 @@ fn parse_recursive_cte() {
alias: TableAlias {
name: Ident {
value: "nums".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
columns: vec![TableAliasColumnDef::from_name("val")],
},
@@ -7631,7 +7631,7 @@ fn lateral_function() {
relation: TableFactor::Table {
name: ObjectName(vec![Ident {
value: "customer".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
}]),
alias: None,
args: None,
@@ -8269,11 +8269,11 @@ fn parse_grant() {
columns: Some(vec![
Ident {
value: "shape".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
Ident {
value: "size".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
])
},
@@ -8514,7 +8514,7 @@ fn parse_merge() {
alias: Some(TableAlias {
name: Ident {
value: "stg".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
columns: vec![],
}),
@@ -9998,11 +9998,11 @@ fn parse_execute_stored_procedure() {
name: ObjectName(vec![
Ident {
value: "my_schema".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
Ident {
value: "my_stored_procedure".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
]),
parameters: vec![
@@ -11205,7 +11205,7 @@ fn test_extract_seconds_ok() {
Expr::Extract {
field: DateTimeField::Custom(Ident {
value: "seconds".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
}),
syntax: ExtractSyntax::From,
expr: Box::new(Expr::Cast {
@@ -11230,7 +11230,7 @@ fn test_extract_seconds_single_quote_ok() {
Expr::Extract {
field: DateTimeField::Custom(Ident {
value: "seconds".to_string(),
- quote_style: Some('\''),
+ quote_style: Some('\''), span: sqlparser::tokenizer::Span::empty(),
}),
syntax: ExtractSyntax::From,
expr: Box::new(Expr::Cast {
diff --git a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_duckdb.rs b/app/tests/sqlparser_duckdb.rs
index 73b0f66..109ed6b 100644
--- a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_duckdb.rs
+++ b/app/tests/sqlparser_duckdb.rs
@@ -274,7 +274,7 @@ fn test_select_union_by_name() {
relation: TableFactor::Table {
name: ObjectName(vec![Ident {
value: "capitals".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
}]),
alias: None,
args: None,
@@ -316,7 +316,7 @@ fn test_select_union_by_name() {
relation: TableFactor::Table {
name: ObjectName(vec![Ident {
value: "weather".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::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-3-codex/app/tests/sqlparser_mssql.rs b/app/tests/sqlparser_mssql.rs
index d1d8d12..2429683 100644
--- a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_mssql.rs
+++ b/app/tests/sqlparser_mssql.rs
@@ -206,7 +206,7 @@ fn parse_mssql_openjson() {
relation: TableFactor::Table {
name: ObjectName(vec![Ident {
value: "t_test_table".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},]),
alias: Some(TableAlias {
name: Ident {
@@ -227,11 +227,11 @@ fn parse_mssql_openjson() {
json_expr: Expr::CompoundIdentifier(vec![
Ident {
value: "A".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
Ident {
value: "param".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
}
]),
json_path: Some(Value::SingleQuotedString("$.config".into())),
@@ -239,7 +239,7 @@ fn parse_mssql_openjson() {
OpenJsonTableColumn {
name: Ident {
value: "kind".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
r#type: DataType::Varchar(Some(CharacterLength::IntegerLength {
length: 20,
@@ -251,7 +251,7 @@ fn parse_mssql_openjson() {
OpenJsonTableColumn {
name: Ident {
value: "id_list".into(),
- quote_style: Some('['),
+ quote_style: Some('['), span: sqlparser::tokenizer::Span::empty(),
},
r#type: DataType::Nvarchar(Some(CharacterLength::Max)),
path: Some("$.id_list".into()),
@@ -282,7 +282,7 @@ fn parse_mssql_openjson() {
relation: TableFactor::Table {
name: ObjectName(vec![Ident {
value: "t_test_table".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},]),
alias: Some(TableAlias {
name: Ident {
@@ -303,11 +303,11 @@ fn parse_mssql_openjson() {
json_expr: Expr::CompoundIdentifier(vec![
Ident {
value: "A".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
Ident {
value: "param".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
}
]),
json_path: None,
@@ -315,7 +315,7 @@ fn parse_mssql_openjson() {
OpenJsonTableColumn {
name: Ident {
value: "kind".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
r#type: DataType::Varchar(Some(CharacterLength::IntegerLength {
length: 20,
@@ -327,7 +327,7 @@ fn parse_mssql_openjson() {
OpenJsonTableColumn {
name: Ident {
value: "id_list".into(),
- quote_style: Some('['),
+ quote_style: Some('['), span: sqlparser::tokenizer::Span::empty(),
},
r#type: DataType::Nvarchar(Some(CharacterLength::Max)),
path: Some("$.id_list".into()),
@@ -358,7 +358,7 @@ fn parse_mssql_openjson() {
relation: TableFactor::Table {
name: ObjectName(vec![Ident {
value: "t_test_table".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},]),
alias: Some(TableAlias {
name: Ident {
@@ -379,11 +379,11 @@ fn parse_mssql_openjson() {
json_expr: Expr::CompoundIdentifier(vec![
Ident {
value: "A".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
Ident {
value: "param".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
}
]),
json_path: None,
@@ -391,7 +391,7 @@ fn parse_mssql_openjson() {
OpenJsonTableColumn {
name: Ident {
value: "kind".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
r#type: DataType::Varchar(Some(CharacterLength::IntegerLength {
length: 20,
@@ -403,7 +403,7 @@ fn parse_mssql_openjson() {
OpenJsonTableColumn {
name: Ident {
value: "id_list".into(),
- quote_style: Some('['),
+ quote_style: Some('['), span: sqlparser::tokenizer::Span::empty(),
},
r#type: DataType::Nvarchar(Some(CharacterLength::Max)),
path: None,
@@ -434,7 +434,7 @@ fn parse_mssql_openjson() {
relation: TableFactor::Table {
name: ObjectName(vec![Ident {
value: "t_test_table".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},]),
alias: Some(TableAlias {
name: Ident {
@@ -455,11 +455,11 @@ fn parse_mssql_openjson() {
json_expr: Expr::CompoundIdentifier(vec![
Ident {
value: "A".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
Ident {
value: "param".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
}
]),
json_path: Some(Value::SingleQuotedString("$.config".into())),
@@ -488,7 +488,7 @@ fn parse_mssql_openjson() {
relation: TableFactor::Table {
name: ObjectName(vec![Ident {
value: "t_test_table".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},]),
alias: Some(TableAlias {
name: Ident {
@@ -509,11 +509,11 @@ fn parse_mssql_openjson() {
json_expr: Expr::CompoundIdentifier(vec![
Ident {
value: "A".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
Ident {
value: "param".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
}
]),
json_path: None,
@@ -1363,11 +1363,11 @@ fn parse_create_table_with_valid_options() {
SqlOption::KeyValue {
key: Ident {
value: "DISTRIBUTION".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
value: Expr::Identifier(Ident {
value: "ROUND_ROBIN".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
})
},
SqlOption::Partition {
@@ -1410,21 +1410,21 @@ fn parse_create_table_with_valid_options() {
ClusteredIndex {
name: Ident {
value: "column_a".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
asc: Some(true),
},
ClusteredIndex {
name: Ident {
value: "column_b".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
asc: Some(false),
},
ClusteredIndex {
name: Ident {
value: "column_c".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
asc: None,
},
@@ -1437,7 +1437,7 @@ fn parse_create_table_with_valid_options() {
SqlOption::KeyValue {
key: Ident {
value: "DISTRIBUTION".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
value: Expr::Function(
Function {
@@ -1445,7 +1445,7 @@ fn parse_create_table_with_valid_options() {
vec![
Ident {
value: "HASH".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
],
),
@@ -1459,7 +1459,7 @@ fn parse_create_table_with_valid_options() {
Expr::Identifier(
Ident {
value: "column_a".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
),
),
@@ -1469,7 +1469,7 @@ fn parse_create_table_with_valid_options() {
Expr::Identifier(
Ident {
value: "column_b".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
),
),
@@ -1503,13 +1503,13 @@ fn parse_create_table_with_valid_options() {
volatile: false,
name: ObjectName(vec![Ident {
value: "mytable".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},],),
columns: vec![
ColumnDef {
name: Ident {
value: "column_a".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
data_type: Int(None,),
collation: None,
@@ -1518,7 +1518,7 @@ fn parse_create_table_with_valid_options() {
ColumnDef {
name: Ident {
value: "column_b".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
data_type: Int(None,),
collation: None,
@@ -1527,7 +1527,7 @@ fn parse_create_table_with_valid_options() {
ColumnDef {
name: Ident {
value: "column_c".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
data_type: Int(None,),
collation: None,
@@ -1668,12 +1668,12 @@ fn parse_create_table_with_identity_column() {
volatile: false,
name: ObjectName(vec![Ident {
value: "mytable".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},],),
columns: vec![ColumnDef {
name: Ident {
value: "columnA".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::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-3-codex/app/tests/sqlparser_mysql.rs b/app/tests/sqlparser_mysql.rs
index 3d8b086..3544b6c 100644
--- a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_mysql.rs
+++ b/app/tests/sqlparser_mysql.rs
@@ -1063,7 +1063,7 @@ fn parse_escaped_quote_identifiers_with_escape() {
top_before_distinct: false,
projection: vec![SelectItem::UnnamedExpr(Expr::Identifier(Ident {
value: "quoted ` identifier".into(),
- quote_style: Some('`'),
+ quote_style: Some('`'), span: sqlparser::tokenizer::Span::empty(),
}))],
into: None,
from: vec![],
@@ -1114,7 +1114,7 @@ fn parse_escaped_quote_identifiers_with_no_escape() {
top_before_distinct: false,
projection: vec![SelectItem::UnnamedExpr(Expr::Identifier(Ident {
value: "quoted `` identifier".into(),
- quote_style: Some('`'),
+ quote_style: Some('`'), span: sqlparser::tokenizer::Span::empty(),
}))],
into: None,
from: vec![],
@@ -1158,7 +1158,7 @@ fn parse_escaped_backticks_with_escape() {
top_before_distinct: false,
projection: vec![SelectItem::UnnamedExpr(Expr::Identifier(Ident {
value: "`quoted identifier`".into(),
- quote_style: Some('`'),
+ quote_style: Some('`'), span: sqlparser::tokenizer::Span::empty(),
}))],
into: None,
from: vec![],
@@ -1206,7 +1206,7 @@ fn parse_escaped_backticks_with_no_escape() {
top_before_distinct: false,
projection: vec![SelectItem::UnnamedExpr(Expr::Identifier(Ident {
value: "``quoted identifier``".into(),
- quote_style: Some('`'),
+ quote_style: Some('`'), span: sqlparser::tokenizer::Span::empty(),
}))],
into: None,
from: vec![],
@@ -2186,7 +2186,7 @@ fn parse_alter_table_add_columns() {
},
column_position: Some(MySQLColumnPosition::After(Ident {
value: String::from("foo"),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
})),
},
]
@@ -2246,7 +2246,7 @@ fn parse_alter_table_change_column() {
options: vec![ColumnOption::NotNull],
column_position: Some(MySQLColumnPosition::After(Ident {
value: String::from("foo"),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
})),
};
let sql4 = "ALTER TABLE orders CHANGE COLUMN description desc TEXT NOT NULL AFTER foo";
@@ -2285,7 +2285,7 @@ fn parse_alter_table_change_column_with_column_position() {
options: vec![ColumnOption::NotNull],
column_position: Some(MySQLColumnPosition::After(Ident {
value: String::from("total_count"),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
})),
};
@@ -2341,7 +2341,7 @@ fn parse_alter_table_modify_column() {
options: vec![ColumnOption::NotNull],
column_position: Some(MySQLColumnPosition::After(Ident {
value: String::from("foo"),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
})),
};
let sql4 = "ALTER TABLE orders MODIFY COLUMN description TEXT NOT NULL AFTER foo";
@@ -2378,7 +2378,7 @@ fn parse_alter_table_modify_column_with_column_position() {
options: vec![ColumnOption::NotNull],
column_position: Some(MySQLColumnPosition::After(Ident {
value: String::from("total_count"),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
})),
};
diff --git a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_postgres.rs b/app/tests/sqlparser_postgres.rs
index d27569e..fde5c15 100644
--- a/tmp/agent-patch-flux-pr-1435.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_postgres.rs
+++ b/app/tests/sqlparser_postgres.rs
@@ -1171,14 +1171,14 @@ fn parse_copy_to() {
expr: Expr::Value(number("42")),
alias: Ident {
value: "a".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
},
SelectItem::ExprWithAlias {
expr: Expr::Value(Value::SingleQuotedString("hello".into())),
alias: Ident {
value: "b".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
}
],
@@ -1451,7 +1451,7 @@ fn parse_set_role() {
context_modifier: ContextModifier::Local,
role_name: Some(Ident {
value: "rolename".to_string(),
- quote_style: Some('\"'),
+ quote_style: Some('\"'), span: sqlparser::tokenizer::Span::empty(),
}),
}
);
@@ -1465,7 +1465,7 @@ fn parse_set_role() {
context_modifier: ContextModifier::None,
role_name: Some(Ident {
value: "rolename".to_string(),
- quote_style: Some('\''),
+ quote_style: Some('\''), span: sqlparser::tokenizer::Span::empty(),
}),
}
);
@@ -3122,7 +3122,7 @@ fn parse_custom_operator() {
Some(Expr::BinaryOp {
left: Box::new(Expr::Identifier(Ident {
value: "relname".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
})),
op: BinaryOperator::PGCustomBinaryOperator(vec![
"database".into(),
@@ -3141,7 +3141,7 @@ fn parse_custom_operator() {
Some(Expr::BinaryOp {
left: Box::new(Expr::Identifier(Ident {
value: "relname".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
})),
op: BinaryOperator::PGCustomBinaryOperator(vec!["pg_catalog".into(), "~".into()]),
right: Box::new(Expr::Value(Value::SingleQuotedString("^(table)$".into())))
@@ -3156,7 +3156,7 @@ fn parse_custom_operator() {
Some(Expr::BinaryOp {
left: Box::new(Expr::Identifier(Ident {
value: "relname".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
})),
op: BinaryOperator::PGCustomBinaryOperator(vec!["~".into()]),
right: Box::new(Expr::Value(Value::SingleQuotedString("^(table)$".into())))
@@ -3859,7 +3859,7 @@ fn parse_dollar_quoted_string() {
})),
alias: Ident {
value: "col_name".into(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
},
}
);
@@ -5016,7 +5016,7 @@ fn check_arrow_precedence(sql: &str, arrow_operator: BinaryOperator) {
left: Box::new(Expr::BinaryOp {
left: Box::new(Expr::Identifier(Ident {
value: "foo".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
})),
op: arrow_operator,
right: Box::new(Expr::Value(Value::SingleQuotedString("bar".to_string()))),
@@ -5046,7 +5046,7 @@ fn arrow_cast_precedence() {
Expr::BinaryOp {
left: Box::new(Expr::Identifier(Ident {
value: "foo".to_string(),
- quote_style: None,
+ quote_style: None, span: sqlparser::tokenizer::Span::empty(),
})),
op: BinaryOperator::Arrow,
right: Box::new(Expr::Cast {