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!(