Builder (Query Builder)
Le Query Builder te permet de construire des requetes complexes en chainant des methodes. Tu l'obtiens avec Model.query().
local builder = User.query()
Methodes chainables
Toutes ces methodes retournent le builder, ce qui permet de les chainer.
:where(conditions)
Ajoute une condition WHERE (reliee par AND).
4 facons de l'utiliser :
-- 1. Egalite simple (table)
:where({ job = "police" })
-- 2. Raccourci egalite (2 arguments, = implicite)
:where("job", "police")
-- 3. Avec operateur (3 arguments)
:where("money", ">", 1000)
-- 4. Operateur dans la table
:where({ money = { ">", 1000 } })
:where({ job = { "LIKE", "%poli%" } })
:where({ id = { "IN", { 1, 2, 3 } } })
:where({ money = { "BETWEEN", { 500, 5000 } } })
:where({ status = { "!=", "banned" } })
:where({ job = { "IS", "NULL" } })
:where({ job = { "IS NOT", "NULL" } })
:::tip Raccourci 2 arguments
:where("job", "police") est equivalent a :where({ job = "police" }). C'est plus court quand tu n'as qu'une seule condition.
:::
:orWhere(conditions)
Ajoute une condition reliee par OR.
:where({ job = "police" })
:orWhere({ job = "medic" })
-- SQL: WHERE job = ? OR job = ?
:orderBy(column, direction?)
Trie les resultats.
| Parametre | Type | Defaut | Description |
|---|---|---|---|
column | string | — | Colonne de tri |
direction | "ASC" | "DESC" | "ASC" | Sens du tri |
:orderBy("money", "DESC")
:orderBy("name") -- ASC par defaut
:orderByDesc(column)
Raccourci pour ORDER BY ... DESC.
:orderByDesc("money")
:orderByAsc(column)
Raccourci pour ORDER BY ... ASC.
:orderByAsc("name")
:latest(column?)
Trie par la colonne donnee en DESC. Defaut : "created_at".
:latest() -- ORDER BY created_at DESC
:latest("updated_at") -- ORDER BY updated_at DESC
:oldest(column?)
Trie par la colonne donnee en ASC. Defaut : "created_at".
:oldest() -- ORDER BY created_at ASC
:limit(n)
Limite le nombre de resultats.
:limit(10)
:offset(n)
Saute les N premiers resultats (pour la pagination).
:offset(20)
:select(columns)
Specifie les colonnes a selectionner (defaut: "*").
:select("identifier, money")
:select("users.*, profiles.bio")
:leftJoin(table, col1, col2)
Ajoute un LEFT JOIN.
:leftJoin("profiles", "profiles.user_id", "users.identifier")
:innerJoin(table, col1, col2)
Ajoute un INNER JOIN.
:innerJoin("vehicles", "vehicles.owner_id", "users.identifier")
:rightJoin(table, col1, col2)
Ajoute un RIGHT JOIN.
:rightJoin("users", "users.identifier", "vehicles.owner_id")
:groupBy(columns)
Regroupe les resultats.
:groupBy("job")
:groupBy("job, department")
:having(clause, ...params)
Filtre les groupes (utilise avec groupBy).
:having("COUNT(*) > ?", 5)
:having("SUM(money) > ?", 10000)
:distinct()
Active SELECT DISTINCT sur la requete.
:distinct()
-- SQL: SELECT DISTINCT * FROM users ...
:whereNull(column)
Filtre les lignes ou la colonne est NULL.
:whereNull("deleted_at")
-- SQL: WHERE deleted_at IS NULL
:whereNotNull(column)
Filtre les lignes ou la colonne n'est pas NULL.
:whereNotNull("email")
-- SQL: WHERE email IS NOT NULL
:whereIn(column, valuesOrSubSQL, params?)
Filtre avec une liste de valeurs OU une sous-requete IN.
-- Avec un tableau de valeurs
:whereIn("job", { "police", "medic", "fire" })
-- SQL: WHERE job IN (?, ?, ?)
-- Avec une sous-requete
:whereIn("id", "SELECT user_id FROM active_users WHERE last_login > ?", { "2024-01-01" })
-- SQL: WHERE id IN (SELECT user_id FROM active_users WHERE last_login > ?)
:whereNotIn(column, valuesOrSubSQL, params?)
Filtre avec NOT IN (tableau ou sous-requete).
:whereNotIn("id", { 1, 2, 3 })
-- SQL: WHERE id NOT IN (?, ?, ?)
:whereNotIn("id", "SELECT user_id FROM banned_users")
-- SQL: WHERE id NOT IN (SELECT user_id FROM banned_users)
:whereBetween(column, min, max)
Raccourci pour BETWEEN.
:whereBetween("money", 500, 5000)
-- SQL: WHERE money BETWEEN ? AND ?
:whereNotBetween(column, min, max)
Raccourci pour NOT BETWEEN.
:whereNotBetween("money", 0, 100)
-- SQL: WHERE money NOT BETWEEN ? AND ?
:whereLike(column, pattern)
Raccourci pour LIKE.
:whereLike("name", "%john%")
-- SQL: WHERE name LIKE ?
:whereExists(subSQL, params?)
Filtre avec WHERE EXISTS (sous-requete).
:whereExists("SELECT 1 FROM vehicles WHERE vehicles.owner_id = users.id")
-- SQL: WHERE EXISTS (SELECT 1 FROM vehicles WHERE vehicles.owner_id = users.id)
:whereRaw(clause, params?)
Ajoute une clause SQL libre, entouree de parentheses et combinee en AND avec les autres conditions.
:whereRaw("money > ? OR job = ?", { 1000, "police" })
-- SQL: WHERE (money > ? OR job = ?)
:whereRaw("LOWER(name) = ?", { "john" })
-- SQL: WHERE (LOWER(name) = ?)
:::caution Injection SQL
Passe toujours les valeurs via params, jamais par concatenation de chaine. :whereRaw("name = '" .. input .. "'") est une porte ouverte a l'injection SQL.
:::
:after(column, value, direction?)
Pagination par curseur : recupere les lignes apres une valeur donnee.
-- Recuperer les 10 joueurs apres l'ID 50
User.query():after("id", 50):limit(10):get()
-- SQL: WHERE id > 50 ORDER BY id ASC LIMIT 10
:before(column, value, direction?)
Pagination par curseur : recupere les lignes avant une valeur donnee.
User.query():before("id", 50):limit(10):get()
-- SQL: WHERE id < 50 ORDER BY id ASC LIMIT 10
Methodes terminales
Ces methodes executent la requete et retournent un resultat.
:get() → OrvexInstance[]
Execute la requete et retourne toutes les lignes.
local results = User.query()
:where({ job = "police" })
:orderBy("money", "DESC")
:get()
:first() → OrvexInstance | nil
Execute la requete et retourne la premiere ligne (ajoute automatiquement LIMIT 1).
local richest = User.query()
:orderBy("money", "DESC")
:first()
:count() → integer
Compte les lignes.
local nb = User.query()
:where({ job = "police" })
:count()
:sum(column) → number
Additionne les valeurs d'une colonne.
local total = User.query()
:where({ job = "police" })
:sum("money")
:avg(column) → number
Calcule la moyenne.
local avg = User.query():avg("money")
:min(column) → any
Retourne la valeur minimale.
local min = User.query():min("money")
:max(column) → any
Retourne la valeur maximale.
local max = User.query():max("money")
:pluck(column) → any[]
Retourne un tableau des valeurs d'une seule colonne.
local jobs = User.query():pluck("job")
-- { "police", "medic", "firefighter" }
:exists() → boolean
Verifie si au moins une ligne correspond. Ne charge pas les donnees.
local hasCops = User.query():where("job", "police"):exists()
:chunk(size, callback)
Traite les resultats par lots pour eviter de tout charger en memoire.
User.query():chunk(100, function(rows, chunkIndex)
-- traiter le lot...
return false -- retourner false pour arreter
end)
:paginate(page, perPage?) → table
Pagine les resultats automatiquement. Retourne un objet avec les donnees et les metadonnees de pagination.
| Parametre | Type | Defaut | Description |
|---|---|---|---|
page | integer | — | Numero de la page (commence a 1) |
perPage | integer | 15 | Nombre d'elements par page |
local page = User.query()
:where({ job = "police" })
:orderBy("money", "DESC")
:paginate(2, 10)
-- page.data → OrvexInstance[] (les resultats)
-- page.total → nombre total d'enregistrements
-- page.page → 2
-- page.perPage → 10
-- page.lastPage → ceil(total / perPage)
:toSQL() → string, table
Retourne le SQL genere et les parametres sans executer la requete.
local sql, params = User.query()
:where({ job = "police" })
:where("money", ">", 500)
:toSQL()
print(sql) -- SELECT * FROM users WHERE job = ? AND money > ?
print(params) -- { "police", 500 }
Exemples complets
Classement avec pagination
-- Page 2 du top des joueurs (10 par page)
local page2 = User.query()
:where("money", ">", 0)
:orderBy("money", "DESC")
:limit(10)
:offset(10)
:get()
Statistiques par job
local stats = User.query()
:select("job, COUNT(*) as nb, AVG(money) as avg_money")
:groupBy("job")
:having("COUNT(*) >= ?", 2)
:orderBy("avg_money", "DESC")
:get()
Joueurs avec profil (jointure)
local players = User.query()
:select("users.*, profiles.bio, profiles.avatar")
:leftJoin("profiles", "profiles.user_id", "users.identifier")
:where({ job = { "IN", { "police", "medic" } } })
:where({ money = { "BETWEEN", { 1000, 50000 } } })
:orderBy("money", "DESC")
:limit(20)
:get()