From 05a9acf8c50dc03c6ddd462764414c167017c763 Mon Sep 17 00:00:00 2001 From: Nils Koch Date: Wed, 16 Jul 2025 20:47:01 +0100 Subject: [PATCH 1/2] wrap special column names with [] in BTreeTable to_sql --- core/schema.rs | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/core/schema.rs b/core/schema.rs index 3085d4bb3..750a67966 100644 --- a/core/schema.rs +++ b/core/schema.rs @@ -435,7 +435,16 @@ impl BTreeTable { if i > 0 { sql.push_str(", "); } - sql.push_str(column.name.as_ref().expect("column name is None")); + + // we need to wrap the column name in square brackets if it contains special characters + let column_name = column.name.as_ref().expect("column name is None"); + if identifier_contains_special_chars(column_name) { + sql.push('['); + sql.push_str(column_name); + sql.push(']'); + } else { + sql.push_str(column_name); + } if !column.ty_str.is_empty() { sql.push(' '); @@ -464,6 +473,10 @@ impl BTreeTable { } } +fn identifier_contains_special_chars(name: &str) -> bool { + name.chars().any(|c| !c.is_ascii_alphanumeric() && c != '_') +} + #[derive(Debug, Default, Clone, Copy)] pub struct PseudoCursorType { pub column_count: usize, @@ -1650,6 +1663,25 @@ mod tests { assert_eq!(expected, actual); } + #[test] + pub fn test_special_column_names() -> Result<()> { + let tests = [ + ("foobar", "CREATE TABLE t (foobar TEXT)"), + ("_table_name3", "CREATE TABLE t (_table_name3 TEXT)"), + ("special name", "CREATE TABLE t ([special name] TEXT)"), + ("foo&bar", "CREATE TABLE t ([foo&bar] TEXT)"), + (" name", "CREATE TABLE t ([ name] TEXT)"), + ]; + + for (input_column_name, expected_sql) in tests { + let sql = format!("CREATE TABLE t ([{input_column_name}] TEXT)"); + let actual = BTreeTable::from_sql(&sql, 0)?.to_sql(); + assert_eq!(expected_sql, actual); + } + + Ok(()) + } + #[test] #[should_panic] fn test_automatic_index_single_column() { From ed8600db489791e7134aff673b95df9be17aa84d Mon Sep 17 00:00:00 2001 From: Nils Koch Date: Sun, 20 Jul 2025 15:34:43 +0100 Subject: [PATCH 2/2] add test for dropping special column names --- testing/alter_table.test | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/testing/alter_table.test b/testing/alter_table.test index 3d3b56053..188eb76e4 100755 --- a/testing/alter_table.test +++ b/testing/alter_table.test @@ -95,6 +95,18 @@ do_execsql_test_on_specific_db {:memory:} alter-table-drop-column { "3" } +do_execsql_test_on_specific_db {:memory:} alter-table-drop-column-special-name { + CREATE TABLE t(a, b, [c c]); + INSERT INTO t VALUES (1, 2, 3); + ALTER TABLE t DROP COLUMN b; + + SELECT "c c" FROM t; + SELECT sql FROM sqlite_schema; +} { + 3 + "CREATE TABLE t (a, [c c])" +} + do_execsql_test_in_memory_any_error fail-alter-table-drop-unique-column { CREATE TABLE t(a, b UNIQUE); ALTER TABLE t DROP b;