agent_patch
runs/2026-02-28__18-27-22__gpt-5-4/flux-pr-1576/flux-pr-1576.1-of-1.2026-02-28__18-27-22__gpt-5-4/agent-logs/agent.patch
9250 bytes
diff --git a/tmp/agent-patch-flux-pr-1576.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/dialect/mod.rs b/app/src/dialect/mod.rs
index 8cce6a3..9e04183 100644
--- a/tmp/agent-patch-flux-pr-1576.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/dialect/mod.rs
+++ b/app/src/dialect/mod.rs
@@ -138,6 +138,23 @@ pub trait Dialect: Debug + Any {
true
}
+ /// Determine if a character starts a nested delimited identifier.
+ ///
+ /// A nested delimited identifier uses an outer delimiter, such as `[`,
+ /// and may optionally include an inner delimiter, such as `["foo"]`.
+ /// This is separate from standard delimited identifiers so dialects can
+ /// disambiguate it from other syntax like array or JSON access.
+ fn is_nested_delimited_identifier_start(&self, _ch: char) -> bool {
+ false
+ }
+
+ /// Determine if the upcoming token should be treated as a nested delimited
+ /// identifier rather than another construct that starts with the same
+ /// character.
+ fn peek_nested_delimited_identifier(&self, _chars: Peekable<Chars<'_>>) -> bool {
+ false
+ }
+
/// Determine if a character is a valid start character for an unquoted identifier
fn is_identifier_start(&self, ch: char) -> bool;
@@ -884,6 +901,17 @@ mod tests {
self.0.is_proper_identifier_inside_quotes(chars)
}
+ fn is_nested_delimited_identifier_start(&self, ch: char) -> bool {
+ self.0.is_nested_delimited_identifier_start(ch)
+ }
+
+ fn peek_nested_delimited_identifier(
+ &self,
+ chars: std::iter::Peekable<std::str::Chars<'_>>,
+ ) -> bool {
+ self.0.peek_nested_delimited_identifier(chars)
+ }
+
fn supports_filter_during_aggregation(&self) -> bool {
self.0.supports_filter_during_aggregation()
}
diff --git a/tmp/agent-patch-flux-pr-1576.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/dialect/redshift.rs b/app/src/dialect/redshift.rs
index 48eb00a..3fa8a7a 100644
--- a/tmp/agent-patch-flux-pr-1576.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/dialect/redshift.rs
+++ b/app/src/dialect/redshift.rs
@@ -33,17 +33,21 @@ pub struct RedshiftSqlDialect {}
// be a json path
impl Dialect for RedshiftSqlDialect {
fn is_delimited_identifier_start(&self, ch: char) -> bool {
- ch == '"' || ch == '['
+ ch == '"'
}
- /// Determine if quoted characters are proper for identifier
- /// It's needed to distinguish treating square brackets as quotes from
- /// treating them as json path. If there is identifier then we assume
- /// there is no json path.
- fn is_proper_identifier_inside_quotes(&self, mut chars: Peekable<Chars<'_>>) -> bool {
+ fn is_nested_delimited_identifier_start(&self, ch: char) -> bool {
+ ch == '['
+ }
+
+ /// Determine if `[` starts a bracket-delimited identifier or PartiQL/JSON access.
+ fn peek_nested_delimited_identifier(&self, mut chars: Peekable<Chars<'_>>) -> bool {
chars.next();
let mut not_white_chars = chars.skip_while(|ch| ch.is_whitespace()).peekable();
if let Some(&ch) = not_white_chars.peek() {
+ if ch == '"' {
+ return true;
+ }
return self.is_identifier_start(ch);
}
false
diff --git a/tmp/agent-patch-flux-pr-1576.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/tokenizer.rs b/app/src/tokenizer.rs
index aacfc16..94e118a 100644
--- a/tmp/agent-patch-flux-pr-1576.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/tokenizer.rs
+++ b/app/src/tokenizer.rs
@@ -1095,6 +1095,24 @@ impl<'a> Tokenizer<'a> {
)
}
}
+ nested_quote_start
+ if self.dialect.is_nested_delimited_identifier_start(ch)
+ && self
+ .dialect
+ .peek_nested_delimited_identifier(chars.peekable.clone()) =>
+ {
+ let error_loc = chars.location();
+ match self.tokenize_nested_delimited_identifier(chars)? {
+ Some(s) => Ok(Some(Token::make_word(&s, Some(nested_quote_start)))),
+ None => self.tokenizer_error(
+ error_loc,
+ format!(
+ "Expected close delimiter '{}' before EOF.",
+ Word::matching_end_quote(nested_quote_start)
+ ),
+ ),
+ }
+ }
// numbers and period
'0'..='9' | '.' => {
let mut s = peeking_take_while(chars, |ch| ch.is_ascii_digit());
@@ -1843,6 +1861,46 @@ impl<'a> Tokenizer<'a> {
(s, last_char)
}
+ fn tokenize_nested_delimited_identifier(
+ &self,
+ chars: &mut State,
+ ) -> Result<Option<String>, TokenizerError> {
+ let error_loc = chars.location();
+ if chars.next() != Some('[') {
+ return self.tokenizer_error(error_loc, "invalid nested delimited identifier opening");
+ }
+
+ let leading_ws = peeking_take_while(chars, |ch| ch.is_whitespace());
+ let Some(&next) = chars.peek() else {
+ return Ok(None);
+ };
+
+ let value = if next == '"' {
+ let quoted = self.tokenize_quoted_string(
+ chars,
+ TokenizeQuotedStringSettings {
+ quote_style: '"',
+ num_quote_chars: NumStringQuoteChars::One,
+ num_opening_quotes_to_consume: 1,
+ backslash_escape: false,
+ },
+ )?;
+
+ peeking_take_while(chars, |ch| ch.is_whitespace());
+ quoted
+ } else {
+ let mut value = leading_ws;
+ value.push_str(&peeking_take_while(chars, |ch| ch != ']'));
+ value
+ };
+
+ if chars.next() == Some(']') {
+ Ok(Some(value))
+ } else {
+ Ok(None)
+ }
+ }
+
#[allow(clippy::unnecessary_wraps)]
fn consume_and_return(
&self,
@@ -2081,6 +2139,7 @@ mod tests {
use super::*;
use crate::dialect::{
BigQueryDialect, ClickHouseDialect, HiveDialect, MsSqlDialect, MySqlDialect,
+ RedshiftSqlDialect,
};
use core::fmt::Debug;
@@ -2855,6 +2914,24 @@ mod tests {
compare(expected, tokens);
}
+ #[test]
+ fn tokenize_redshift_nested_delimited_identifier() {
+ let sql = r#"["0"] payload[0] [foo]"#;
+ let dialect = RedshiftSqlDialect {};
+ let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap();
+ let expected = vec![
+ Token::make_word("0", Some('[')),
+ Token::Whitespace(Whitespace::Space),
+ Token::make_word("payload", None),
+ Token::LBracket,
+ Token::Number("0".to_string(), false),
+ Token::RBracket,
+ Token::Whitespace(Whitespace::Space),
+ Token::make_word("foo", Some('[')),
+ ];
+ compare(expected, tokens);
+ }
+
#[test]
fn tokenize_with_location() {
let sql = "SELECT a,\n b";
diff --git a/tmp/agent-patch-flux-pr-1576.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_redshift.rs b/app/tests/sqlparser_redshift.rs
index 9492946..10b57e1 100644
--- a/tmp/agent-patch-flux-pr-1576.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_redshift.rs
+++ b/app/tests/sqlparser_redshift.rs
@@ -262,6 +262,47 @@ fn test_redshift_json_path() {
);
}
+#[test]
+fn test_redshift_bracketed_identifier_vs_json_index() {
+ let select = redshift().verified_only_select(r#"SELECT ["0"] FROM t"#);
+ assert_eq!(
+ expr_from_projection(only(&select.projection)),
+ &Expr::Identifier(Ident::with_quote('[', "0"))
+ );
+
+ let select = redshift().verified_only_select("SELECT payload[0] FROM t");
+ assert_eq!(
+ expr_from_projection(only(&select.projection)),
+ &Expr::JsonAccess {
+ value: Box::new(Expr::Identifier(Ident::new("payload"))),
+ path: JsonPath {
+ path: vec![JsonPathElem::Bracket {
+ key: Expr::Value(Value::Number("0".parse().unwrap(), false))
+ }]
+ }
+ }
+ );
+}
+
+#[test]
+fn test_redshift_nested_bracketed_identifier() {
+ let select = redshift().verified_only_select(r#"SELECT ["foo"] FROM ["schema"].["table"]"#);
+ assert_eq!(
+ select.projection[0],
+ SelectItem::UnnamedExpr(Expr::Identifier(Ident::with_quote('[', "foo")))
+ );
+ assert_eq!(
+ select.from[0],
+ TableWithJoins {
+ relation: table_from_name(ObjectName(vec![
+ Ident::with_quote('[', "schema"),
+ Ident::with_quote('[', "table"),
+ ])),
+ joins: vec![],
+ }
+ );
+}
+
#[test]
fn test_parse_json_path_from() {
let dialects = all_dialects_where(|d| d.supports_partiql());