From 7cd038a612ac1286d5cd7da58590221a31a0eaf3 Mon Sep 17 00:00:00 2001 From: nikita42 Date: Wed, 8 Apr 2026 00:21:01 +0300 Subject: [PATCH] fix(sqlite): escape identifiers in PRAGMA queries --- src/loader/sqlite.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/loader/sqlite.rs b/src/loader/sqlite.rs index 0e9fc98..2a6438d 100644 --- a/src/loader/sqlite.rs +++ b/src/loader/sqlite.rs @@ -50,7 +50,7 @@ fn get_table_names(conn: &Connection) -> Result, DbDiffError> { fn load_columns(conn: &Connection, table_name: &str, table: &mut Table) -> Result<(), DbDiffError> { // PRAGMA table_info returns: cid, name, type, notnull, dflt_value, pk - let mut stmt = conn.prepare(&format!("PRAGMA table_info(\"{}\")", table_name))?; + let mut stmt = conn.prepare(&format!("PRAGMA table_info({})", quote_identifier(table_name)))?; let columns = stmt.query_map([], |row| { let name: String = row.get(1)?; @@ -76,7 +76,7 @@ fn load_columns(conn: &Connection, table_name: &str, table: &mut Table) -> Resul fn load_indexes(conn: &Connection, table_name: &str, table: &mut Table) -> Result<(), DbDiffError> { // PRAGMA index_list returns: seq, name, unique, origin, partial - let mut stmt = conn.prepare(&format!("PRAGMA index_list(\"{}\")", table_name))?; + let mut stmt = conn.prepare(&format!("PRAGMA index_list({})", quote_identifier(table_name)))?; let indexes: Vec<(String, bool, String)> = stmt .query_map([], |row| { @@ -100,7 +100,8 @@ fn load_indexes(conn: &Connection, table_name: &str, table: &mut Table) -> Resul // PRAGMA index_info returns: seqno, cid, name // For expression indexes, name is NULL — skip those columns - let mut col_stmt = conn.prepare(&format!("PRAGMA index_info(\"{}\")", index_name))?; + let mut col_stmt = + conn.prepare(&format!("PRAGMA index_info({})", quote_identifier(&index_name)))?; let columns: Vec = col_stmt .query_map([], |row| row.get::<_, Option>(2))? .filter_map(|r| r.ok().flatten()) @@ -125,6 +126,10 @@ fn load_indexes(conn: &Connection, table_name: &str, table: &mut Table) -> Resul Ok(()) } +fn quote_identifier(identifier: &str) -> String { + format!("\"{}\"", identifier.replace('"', "\"\"")) +} + /// Normalize SQLite type names. /// /// SQLite has flexible typing. We normalize common aliases to canonical forms. @@ -177,6 +182,12 @@ mod tests { assert_eq!(normalize_default("CURRENT_TIMESTAMP"), "CURRENT_TIMESTAMP"); } + #[test] + fn test_quote_identifier() { + assert_eq!(quote_identifier("users"), "\"users\""); + assert_eq!(quote_identifier("foo\"bar"), "\"foo\"\"bar\""); + } + #[test] fn load_fails_when_sqlite_file_is_missing() { let missing_path = format!(