It occurred to me that we can provide absolute compile time SQL-injection safety by hiding prepare()
and execute()
behind syntax extensions.
Basically, instead of
let s = conn.prepare("SELECT id, name, time_created, data FROM person where id = $1").unwrap();
s.query([123])
conn.execute("INSERT INTO person (name, time_created, data) VALUES ($1, $2, $3)",
&[&me.name, &me.time_created, &me.data])
we have
let s = prepare!(conn, "SELECT id, name, time_created, data FROM person where id = $1").unwrap();
s.query([123]);
execute!(conn, "INSERT INTO person (name, time_created, data VALUES ($1, $2, $3)",
&me.name, &me.time_created, &me.data)
Both prepare!
and execute!
will ensure at compile time that their first argument is a string literal (like format!
, and similarly execute!
will ensure that the number of arguments fits the number of parameters provided.
We can optionally extend the number of arguments protection to query
as well, by making it a macro too: give the result of prepare!
the type PostgresStatement<(String, String, String)>
if the prepared query has three parameters, and ensure that query!(stmt, 1 , 2 , 3)
indeed has three arguments after the first argument.
This sort of system would ensure that the programmer never uses string concatenation* for SQL, leaving sanitization to the library. As a bonus, no more sloppy array slices, macros have varargs!
*I have seen string concatenation being used in PDO prepared statements in PHP; you can drag a horse to the water but you can't make it drink ;) Rust seems to follow the "don't trust the programmer" principle, and this design seems to nicely follow it