SQL Injection Protection
SQL injection occurs when an external value is concatenated into the query
string before reaching the database. Using sql"...", Zolo never concatenates:
every interpolation {expr} becomes a positional ? and the value is sent
separately by the driver. The database receives the query structure on one channel
and the data on another — SQL metacharacters in the value are treated as text,
not as code.
The classic payload '; DROP TABLE users; -- is inserted as a variable in a
sql"..." query. The table remains intact and no rows are found.
// Feature: `sql"..."` interpolations are *bound parameters*, not concatenated
// Syntax: `sql"SELECT ... WHERE x = {evil}"` — at compile time the
// `{evil}` becomes `?` and the value is sent as a parameter. SQL
// metacharacters in the value are literal, not parsed.
// When to use: ALWAYS. Never build queries with `+` or interpolation
// outside `sql"..."`. This file demonstrates why.
use std::Database
let db = Database.open("sqlite://:memory:")?
defer db.close()
db.execute(sql"CREATE TABLE users (name TEXT)")?
db.execute(sql"INSERT INTO users VALUES ('alice')")?
db.execute(sql"INSERT INTO users VALUES ('bob')")?
// The classic injection payload — in a string-concat builder it
// would terminate the query, drop the table, and comment out the rest.
// Inside `sql"..."` it's just a string parameter that doesn't match.
let evil = "'; DROP TABLE users; --"
let rows = sql"SELECT name FROM users WHERE name = {evil}".query(db)?
var count = 0
for _ in rows { count = count + 1 }
print("matched: {count}")
// expected: matched: 0
// Proof: the table is still there, both rows intact.
let total = sql"SELECT COUNT(*) FROM users".scalar(db)?
print("total: {total}")
// expected: total: 2
db.query("SELECT * FROM users") ?> .each(|u| print(u.name))
Requires the Zolo CLI/host — open in the playground or run locally.
The practical rule is simple:
- Use
sql"..."(or?+ bindings) always when a value comes from outside the source code. - Never build queries with
+or with interpolation outside thesql"..."literal.
Challenge
Try building the same query without sql"..." using string concatenation and
observe what happens to the table. What error message does Zolo emit?