Parameter Binding
Parameter Binding
Stoolap supports parameterized queries using positional parameters ($1, $2, etc.) to prevent SQL injection and improve performance through query plan reuse.
Syntax
Parameters use the $N syntax where N is the 1-based position:
-- Single parameter
SELECT * FROM users WHERE id = $1;
-- Multiple parameters
SELECT * FROM users WHERE age > $1 AND department = $2;
-- Parameters in INSERT
INSERT INTO users (id, name, age) VALUES ($1, $2, $3);
-- Parameters in UPDATE
UPDATE products SET price = $1 WHERE id = $2;
Using Parameters in Rust
Basic Usage
use stoolap::Database;
let db = Database::open("memory://")?;
// Create a table
db.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)", ())?;
// Insert with tuple parameters
db.execute("INSERT INTO users VALUES ($1, $2, $3)", (1, "Alice", 30))?;
// Query with parameters
for row in db.query("SELECT * FROM users WHERE age > $1", (25,))? {
let row = row?;
let name: String = row.get("name")?;
println!("Name: {}", name);
}
Using the params! Macro
For more complex cases, use the params! macro:
use stoolap::{Database, params};
let db = Database::open("memory://")?;
// Using params! macro
db.execute("INSERT INTO users VALUES ($1, $2, $3)", params![2, "Bob", 25])?;
// With variables
let name = "Charlie";
let age = 35;
db.execute("INSERT INTO users VALUES ($1, $2, $3)", params![3, name, age])?;
Named Parameters
Stoolap also supports named parameters with the :name syntax:
use stoolap::{Database, named_params};
let db = Database::open("memory://")?;
// Using named_params! macro
db.execute_named(
"INSERT INTO users VALUES (:id, :name, :age)",
named_params!{ id: 1, name: "Alice", age: 30 }
)?;
// Query with named parameters
for row in db.query_named(
"SELECT * FROM users WHERE age > :min_age",
named_params!{ min_age: 25 }
)? {
// Process rows...
}
Supported Data Types
| Rust Type | SQL Type |
|---|---|
i64, i32, i16, i8 |
INTEGER |
f64, f32 |
FLOAT |
String, &str |
TEXT |
bool |
BOOLEAN |
Option<T> |
NULL or T |
With Transactions
use stoolap::Database;
let db = Database::open("memory://")?;
db.execute("BEGIN", ())?;
db.execute("INSERT INTO accounts VALUES ($1, $2)", (1, 1000))?;
db.execute("UPDATE accounts SET balance = balance - $1 WHERE id = $2", (100, 1))?;
db.execute("COMMIT", ())?;
Query Methods
query() - Multiple Rows
for row in db.query("SELECT * FROM users WHERE age > $1", (18,))? {
let row = row?;
let id: i64 = row.get("id")?;
let name: String = row.get("name")?;
}
query_one() - Single Value
let count: i64 = db.query_one("SELECT COUNT(*) FROM users WHERE active = $1", (true,))?;
let name: String = db.query_one("SELECT name FROM users WHERE id = $1", (1,))?;
query_opt() - Optional Value
let name: Option<String> = db.query_opt("SELECT name FROM users WHERE id = $1", (999,))?;
match name {
Some(n) => println!("Found: {}", n),
None => println!("Not found"),
}
Prepared Statements
For repeated queries, use prepared statements:
let stmt = db.prepare("SELECT * FROM users WHERE id = $1")?;
for id in 1..=100 {
for row in stmt.query((id,))? {
let row = row?;
// Process row...
}
}
Benefits of Parameters
- SQL Injection Prevention: Parameters are never interpolated into SQL strings
- Query Plan Caching: Same query structure allows plan reuse
- Type Safety: Rust type system ensures correct parameter types
- Performance: Reduced parsing overhead for repeated queries
Best Practices
- Always use parameters for user input - Never concatenate user data into SQL
- Match parameter count - Number of
$Nplaceholders must match parameters provided - Use correct types - Match Rust types to expected SQL column types
- Prefer prepared statements - For queries executed multiple times
Common Patterns
Bulk Insert
db.execute("BEGIN", ())?;
for (id, name, age) in data {
db.execute("INSERT INTO users VALUES ($1, $2, $3)", (id, name, age))?;
}
db.execute("COMMIT", ())?;
Dynamic Queries
// Build query based on optional filters
let mut conditions = vec!["1=1".to_string()];
let mut params: Vec<Value> = vec![];
if let Some(min_age) = filter.min_age {
conditions.push(format!("age > ${}", params.len() + 1));
params.push(Value::Integer(min_age));
}
let query = format!("SELECT * FROM users WHERE {}", conditions.join(" AND "));
// Execute with collected parameters