Skip to content

sql"…" — Safe Template and Scalar

The sql"..." literal is a more expressive way to build dynamic queries. Any {value} inside the template is compiled as ? and bound automatically — you write the query as if it were an interpolated string, but with full protection against injection.

The result is a TaggedSqlQuery with four methods:

Method What it does
.query(db) Executes SELECT, returns a list of maps
.execute(db) Executes DDL/DML, returns affected rows
.scalar(db) Returns the first value of the first row
.one(db) Returns the first row as a map

Filters books by year with {min_year} and uses .scalar to count the total.

06-tagged-sql.zolo
// Feature: Database — `sql"..."` tagged template with auto-parametrization

// Syntax: `sql"SELECT ... WHERE x = {value}"` — interpolations become `?`

// and the values are bound. Returns a TaggedSqlQuery with methods

// `:query(db)`, `:execute(db)`, `:one(db)`, `:scalar(db)`.

// When to use: dynamic, safe queries without string concatenation.


use std::Database

let db = Database.open("sqlite://:memory:").unwrap()
defer db.close()

db.execute(sql"CREATE TABLE books (id INTEGER PRIMARY KEY, title TEXT, year INTEGER)").unwrap()
db.execute(sql"INSERT INTO books VALUES (1, 'Zolo Guide', 2025)").unwrap()
db.execute(sql"INSERT INTO books VALUES (2, 'Lua in 24h', 2010)").unwrap()
db.execute(sql"INSERT INTO books VALUES (3, 'Rust Book',  2020)").unwrap()

const min_year = 2020

// `{min_year}` compiles to `?` + binding — safe against injection.

let q = sql"SELECT title, year FROM books WHERE year >= {min_year} ORDER BY year"

let rows = q.query(db).unwrap()
for row in rows {
  print("  {row.title} ({row.year})")
}

// expected:

//   Rust Book (2020)

//   Zolo Guide (2025)


// `:scalar` returns the first value of the first column.

let total = sql#"SELECT COUNT(*) "total" FROM books"#.scalar(db).unwrap()
print("total: {total}")
// expected: total: 3

Requires the Zolo CLI/host — open in the playground or run locally.

.scalar is the ideal shortcut for aggregations (COUNT, MAX, SUM) and queries that return exactly one cell:

Counts SKUs by value with sql"...{target}".scalar(db){target} is bound, not concatenated.

07-scalar.zolo
// Feature: `sql"...".scalar(db)` — first column of first row, as a value

// Syntax: returns the value directly (number/string/bool/nil) rather

// than a row object.

// When to use: COUNT(*), MAX/MIN/SUM aggregates, SELECT 1, single

// scalar lookups by primary key — anywhere the result is exactly

// one cell.


use std::Database

let db = Database.open("sqlite://:memory:").unwrap()
defer db.close()

db.execute(sql"CREATE TABLE items (id INTEGER PRIMARY KEY, sku TEXT)").unwrap()
db.execute(sql"INSERT INTO items VALUES (1, 'a')").unwrap()
db.execute(sql"INSERT INTO items VALUES (2, 'b')").unwrap()
db.execute(sql"INSERT INTO items VALUES (3, 'a')").unwrap()

// Interpolated value is bound, not concatenated — safe.

let target = "a"
let n = sql"SELECT COUNT(*) FROM items WHERE sku = {target}".scalar(db).unwrap()
print("count: {n}")

// expected: count: 2


// `scalar` also works without interpolation.

let total = sql"SELECT COUNT(*) FROM items".scalar(db).unwrap()
print("total: {total}")
// expected: total: 3

Requires the Zolo CLI/host — open in the playground or run locally.

Challenge

Use sql"..." to calculate the average price with AVG(price) and display the result formatted to two decimal places.

enespt-br