🔗SQL Queries
Once you have defined your models, you can use them to interact with your database through the Ozark ORM. The query builder generates SQL at compile time, so table names, column names and value types are validated before your application compiles.
All queries run inside a database context: either withDB (one connection) or withDBPool (a connection from the pool):
withDB do:
# execute queries here
withDBPool do:
# execute queries hereThe examples below use the User model from Models:
newModel User:
id {.pk.}: Serial
email {.unique, notnull.}: Varchar(255)
password {.notnull.}: Text
created_at: TimestampTz🔗Create and drop tables
Tables are created automatically from the model definition:
withDB do:
Models.table(User).prepareTable().exec()
Models.table(User).dropTable(cascade = true).exec()🔗Insert data
The insert macro takes a table constructor of column: value pairs. Use execGet to get the id of the inserted row, or exec to ignore it:
withDBPool do:
let id = Models.table(User).insert({
email: "test@example.com",
password: "some-hash",
created_at: $now(),
}).execGet() # returns the id of the inserted row
Models.table(User).insert({
email: "another@example.com",
password: "some-hash",
}).exec()🔗Select data
Select specific columns with select, or every column with selectAll:
withDBPool do:
let res = Models.table(User)
.select(["email", "created_at"])
.getAll()
let res = Models.table(User).selectAll().getAll()🔗Filter with where
The where macro follows a select/selectAll clause. Chain conditions with whereNot, orWhere and orWhereNot:
withDBPool do:
let res = Models.table(User)
.selectAll()
.where("email", "test@example.com")
.getAll()
# NOT equal
let res = Models.table(User)
.select("email")
.whereNot("email", "test@example.com")
.get()
# OR conditions
let res = Models.table(User)
.select("email")
.where("email", "a@example.com")
.orWhere("email", "b@example.com")
.get()
let res = Models.table(User)
.select("email")
.whereNot("email", "a@example.com")
.orWhereNot("email", "b@example.com")
.get()🔗Filter with LIKE
Ozark provides pattern-matching helpers for partial matches. whereStartsLike and whereEndsLike add the wildcard on the correct side automatically:
withDBPool do:
# any position: %val%
let res = Models.table(User)
.select("email")
.whereLike("email", "test")
.get()
# prefix: val%
let res = Models.table(User)
.select("email")
.whereStartsLike("email", "test")
.get()
# suffix: %val
let res = Models.table(User)
.select("email")
.whereEndsLike("email", ".com")
.get()
# negative variants
let res = Models.table(User)
.select("email")
.whereNotLike("email", "test")
.get()🔗Filter with IN
Use whereIn and whereNotIn to match a list of values:
withDBPool do:
let res = Models.table(User)
.select("email")
.whereIn("email", ["a@example.com", "b@example.com"])
.get()
let res = Models.table(User)
.select("email")
.whereNotIn("email", ["a@example.com"])
.get()🔗Ordering and limiting
Order the results with orderDescBy, and limit the number of rows with limit:
withDBPool do:
let res = Models.table(User)
.selectAll()
.orderDescBy(["created_at"])
.getAll()
let res = Models.table(User)
.selectAll()
.orderDescBy(["created_at"])
.limit(10)
.getAll()
let res = Models.table(User)
.selectAll()
.where("email", "test@example.com")
.limit(1)
.get()🔗Update data
The update macro sets columns, then where selects the rows to update:
withDBPool do:
Models.table(User).update({
email: "new@example.com",
}).where("id", "1").exec()🔗Delete data
removeRow builds a DELETE FROM statement. Combine it with where to delete a specific row; without a where clause it removes every row in the table:
withDBPool do:
Models.table(User).removeRow().where("id", "1").exec()🔗Raw SQL
When the query builder is not enough, rawSQL runs raw SQL with parameter binding to prevent SQL injection, while keeping compile-time validation. Use getWith to map results to a model:
withDBPool do:
let res = Models.rawSQL(
"SELECT * FROM users WHERE email = $1",
"test@example.com"
).getWith(User)
# raw SQL with subqueries
let res = Models.rawSQL("""
SELECT
users.*,
(SELECT COUNT(*) FROM subscriptions WHERE subscriptions.user_id = users.id) AS subscriptions_count
FROM users ORDER BY users.id DESC LIMIT 20 OFFSET 0;
""").getWith(User)🔗Result: Collection[T]
Query results are always a Collection[T], where T is the model type, even when selecting a single row. This keeps the API consistent:
withDBPool do:
let res = Models.table(User)
.selectAll()
.where("email", "test@example.com")
.get()
assert not res.isEmpty
assert res.len == 1
assert res.get(0).email == "test@example.com"
assert res.entries[0].email == "test@example.com"
for user in res:
echo user.email