@@ -20,11 +20,11 @@ pub async fn query_table_info(
2020 // Check if table exists and get WITHOUT ROWID status
2121 let without_rowid = is_without_rowid ( conn, table_name) . await ?;
2222
23- // Get primary key columns using PRAGMA table_info
23+ // Get primary key columns using pragma_table_info()
2424 let pk_columns = query_pk_columns ( conn, table_name) . await ?;
2525
2626 // Determine if table exists:
27- // - If pk_columns is None, PRAGMA table_info returned no rows (table doesn't exist)
27+ // - If pk_columns is None, pragma_table_info returned no rows (table doesn't exist)
2828 // - If without_rowid is true, the table must exist (we found it in sqlite_master)
2929 // - A table with no explicit PK returns Some([]), not None
3030 if pk_columns. is_none ( ) && !without_rowid {
@@ -78,15 +78,20 @@ fn has_without_rowid_clause(create_sql: &str) -> bool {
7878/// Returns column indices in the order they appear in the PRIMARY KEY definition.
7979/// For composite primary keys, the `pk` column in PRAGMA table_info indicates
8080/// the position (1-indexed) within the PK.
81+ ///
82+ /// Uses the `pragma_table_info()` table-valued function (available since SQLite
83+ /// 3.16.0) so the table name can be bound as a parameter instead of interpolated
84+ /// into the SQL string.
8185async fn query_pk_columns (
8286 conn : & mut SqliteConnection ,
8387 table_name : & str ,
8488) -> crate :: Result < Option < Vec < usize > > > {
85- // PRAGMA table_info returns: cid, name, type, notnull, dflt_value, pk
89+ // pragma_table_info returns: cid, name, type, notnull, dflt_value, pk
8690 // pk is 0 for non-PK columns, or 1-indexed position for PK columns
87- let pragma = format ! ( "PRAGMA table_info({})" , quote_identifier ( table_name ) ) ;
91+ let sql = "SELECT cid, name, type, \" notnull \" , dflt_value, pk FROM pragma_table_info(?1)" ;
8892
89- let rows = sqlx:: query ( & pragma)
93+ let rows = sqlx:: query ( sql)
94+ . bind ( table_name)
9095 . fetch_all ( & mut * conn)
9196 . await
9297 . map_err ( crate :: Error :: Sqlx ) ?;
@@ -116,23 +121,10 @@ async fn query_pk_columns(
116121 Ok ( Some ( pk_columns. into_iter ( ) . map ( |( cid, _) | cid) . collect ( ) ) )
117122}
118123
119- /// Quotes a SQLite identifier to prevent SQL injection.
120- fn quote_identifier ( name : & str ) -> String {
121- // Double any existing double quotes and wrap in double quotes
122- format ! ( "\" {}\" " , name. replace( '"' , "\" \" " ) )
123- }
124-
125124#[ cfg( test) ]
126125mod tests {
127126 use super :: * ;
128127
129- #[ test]
130- fn test_quote_identifier ( ) {
131- assert_eq ! ( quote_identifier( "users" ) , "\" users\" " ) ;
132- assert_eq ! ( quote_identifier( "my table" ) , "\" my table\" " ) ;
133- assert_eq ! ( quote_identifier( "foo\" bar" ) , "\" foo\" \" bar\" " ) ;
134- }
135-
136128 #[ test]
137129 fn test_has_without_rowid_clause ( ) {
138130 // Positive cases
0 commit comments