From dddb9714a7f98ca7f355f90614b321fbee9ab877 Mon Sep 17 00:00:00 2001 From: Haim Dimer Date: Thu, 30 Jul 2026 13:47:23 -0700 Subject: [PATCH] Escape closing brackets in bracket-quoted identifiers A bracket-quoted identifier whose value contains ] (e.g. [a]]b], value a]b) serialized back to [a]b], which no longer re-parses. Double each ] on display, mirroring the tokenizer folding ]] into ]. Redshift nested quoted identifiers (["a]b"]) store the value as a complete double-quoted string whose inner ] is literal, so those are left unchanged. Fixes #2409 Signed-off-by: Haim Dimer --- src/ast/mod.rs | 14 +++++++++++++- tests/sqlparser_mssql.rs | 12 ++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 8a9a67a74..b1b088a79 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -385,7 +385,19 @@ impl fmt::Display for Ident { let escaped = value::escape_quoted_string(&self.value, q); write!(f, "{q}{escaped}{q}") } - Some('[') => write!(f, "[{}]", self.value), + Some('[') => { + // Redshift nested quoted identifiers (e.g. `["a]b"]`) store the + // value as a complete double-quoted string whose inner `]` is + // literal, so leave those unchanged. Otherwise double each `]`, + // mirroring the tokenizer folding `]]` into `]`, so the + // identifier round-trips (#2409). + let v = &self.value; + if v.len() >= 2 && v.starts_with('"') && v.ends_with('"') { + write!(f, "[{v}]") + } else { + write!(f, "[{}]", v.replace(']', "]]")) + } + } None => f.write_str(&self.value), _ => panic!("unexpected quote style"), } diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index 3faf56f0d..07bfe52fe 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -937,6 +937,18 @@ fn parse_table_name_in_square_brackets() { ); } +#[test] +fn parse_bracket_identifier_with_escaped_closing_bracket() { + // A bracket-quoted identifier whose value contains `]` must serialize + // with the bracket doubled so it round-trips. See #2409. + let select = ms().verified_only_select("SELECT [a]]b]"); + assert_eq!( + &Expr::Identifier(Ident::with_quote('[', "a]b")), + expr_from_projection(&select.projection[0]), + ); + ms().verified_stmt("SELECT [a]]b] FROM [c]]d]"); +} + #[test] fn parse_for_clause() { ms_and_generic().verified_stmt("SELECT a FROM t FOR JSON PATH");