NormQueryBuilder class
The fluent builder behind every query: where_*, joins, aggregations, scopes, pagination, and bulk writes.
Auto-generated
This page is generated from the source annotations by scripts/gen-api.mjs. Edit the LuaCATS doc comments in the Norm sources, not here.
| Member | Returns | Description |
|---|---|---|
all | NormRecordListPromise (async) | Execute the query and resolve with all matching records. |
avg | NormNumberPromise (async) | AVG of a column over the current filter. |
count | NormNumberPromise (async) | Resolve with the COUNT(*) for the current conditions. |
decrement | NormNumberPromise (async) | Atomically subtract amount (default 1) from a column on every matching row. |
delete | NormNumberPromise (async) | Bulk-delete every matching row. |
first | NormRecordOrNilPromise (async) | Execute the query with LIMIT 1 and resolve with the first record (or nil). |
force_delete | NormNumberPromise (async) | Bulk physical-DELETE every matching row, even on a soft-delete model. |
group_by | self: NormQueryBuilder | Add GROUP BY columns (call again, or pass several, to group by more). |
having | self: NormQueryBuilder | Add a HAVING condition (ANDed) on a RAW aggregate expression. |
include | self: NormQueryBuilder | Eager-load relations with the result (one batched query per relation level — |
increment | NormNumberPromise (async) | Atomically add amount (default 1) to a column on every matching row, in one |
join | self: NormQueryBuilder | INNER JOIN another table. |
left_join | self: NormQueryBuilder | LEFT JOIN another table (same argument forms as :join). |
limit | self: NormQueryBuilder | Limit the number of rows (pair with :offset() for pagination). |
max | NormNumberPromise (async) | MAX of a column over the current filter. |
min | NormNumberPromise (async) | MIN of a column over the current filter. |
model | — | |
offset | self: NormQueryBuilder | Skip n rows (use with :limit()). |
omit | self: NormQueryBuilder | Inverse of select: select every column of the model EXCEPT the given ones |
only_trashed | self: NormQueryBuilder | Return ONLY soft-deleted rows. |
or_where | self: NormQueryBuilder | OR variant of where. |
or_where_between | self: NormQueryBuilder | |
or_where_in | self: NormQueryBuilder | |
or_where_like | self: NormQueryBuilder | |
or_where_not | self: NormQueryBuilder | |
or_where_not_between | self: NormQueryBuilder | |
or_where_not_in | self: NormQueryBuilder | |
or_where_not_like | self: NormQueryBuilder | |
or_where_not_null | self: NormQueryBuilder | |
or_where_null | self: NormQueryBuilder | |
order | self: NormQueryBuilder | Add an ORDER BY clause (call again for secondary orderings). |
paginate | NormPromise (async) | Paginate the current query. |
rows | NormRowsPromise (async) | Execute the query and resolve with the RAW rows (no record wrapping). |
scope | self: NormQueryBuilder | Apply a named scope (a reusable query fragment registered on the model with |
select | self: NormQueryBuilder | Restrict selected columns (the inverse is :omit). |
select_raw | self: NormQueryBuilder | Add a RAW (unquoted) select expression — for aggregates/computed columns that |
sum | NormNumberPromise (async) | SUM of a column over the current filter. |
update | NormNumberPromise (async) | Bulk-update every matching row in one statement (no records loaded). |
where | self: NormQueryBuilder | Add an AND condition. |
where_between | self: NormQueryBuilder | column [NOT] BETWEEN min AND max (inclusive). |
where_doesnt_have | self: NormQueryBuilder | Inverse of where_has: keep only rows with NO matching related row |
where_has | self: NormQueryBuilder | Keep only rows that HAVE at least one related row for name (optionally |
where_in | self: NormQueryBuilder | column IN (...) (and its OR / negated variants). |
where_like | self: NormQueryBuilder | column [NOT] LIKE pattern (use % / _ wildcards). |
where_not | self: NormQueryBuilder | column != value (and OR variant). |
where_not_between | self: NormQueryBuilder | |
where_not_in | self: NormQueryBuilder | |
where_not_like | self: NormQueryBuilder | |
where_not_null | self: NormQueryBuilder | |
where_null | self: NormQueryBuilder | column IS [NOT] NULL (and OR variants). |
with_count | self: NormQueryBuilder | Add a <name>_count field to each returned record: the number of related rows, |
with_trashed | self: NormQueryBuilder | Include soft-deleted rows in the result (disables the default exclusion). |
all async method
NormQueryBuilder:all()
-> promise: NormRecordListPromiseExecute the query and resolve with all matching records.
local users = User:query():where("admin", true):all():await()avg async method
NormQueryBuilder:avg(column: string)
-> promise: NormNumberPromiseAVG of a column over the current filter. Resolves with a number.
count async method
NormQueryBuilder:count()
-> promise: NormNumberPromiseResolve with the COUNT(*) for the current conditions.
local admins = User:query():where("admin", true):count():await()decrement async method
NormQueryBuilder:decrement(column: string, amount?: number)
-> promise: NormNumberPromiseAtomically subtract amount (default 1) from a column on every matching row.
delete async method
NormQueryBuilder:delete()
-> promise: NormNumberPromiseBulk-delete every matching row. On a soft-delete model this marks the rows (sets deleted_at) rather than removing them; use force_delete to remove. Resolves with the affected row count.
local n = User:query():where("coins", 0):delete():await()first async method
NormQueryBuilder:first()
-> promise: NormRecordOrNilPromiseExecute the query with LIMIT 1 and resolve with the first record (or nil).
local newest = User:query():order("id", "DESC"):first():await()force_delete async method
NormQueryBuilder:force_delete()
-> promise: NormNumberPromiseBulk physical-DELETE every matching row, even on a soft-delete model. Resolves with the affected row count.
group_by method
NormQueryBuilder:group_by(...string)
-> self: NormQueryBuilderAdd GROUP BY columns (call again, or pass several, to group by more).
Player:select_raw("faction, COUNT(*) AS n"):group_by("faction"):rows():await()having method
NormQueryBuilder:having(expr: string, op?: string, value?: any)
-> self: NormQueryBuilderAdd a HAVING condition (ANDed) on a RAW aggregate expression. Forms: having(expr, value) or having(expr, op, value). The expression is emitted verbatim (so you can reference COUNT(*), SUM(\coins`)`, …); the value is bound.
Player:select_raw("faction, COUNT(*) AS n"):group_by("faction")
:having("COUNT(*)", ">", 10):rows():await()include method
NormQueryBuilder:include(...string|fun(q: NormQueryBuilder))
-> self: NormQueryBuilderEager-load relations with the result (one batched query per relation level — no N+1), attaching them to each returned record. Three forms:
include("posts", "profile")— simple relation names.include("posts.comments")— nested via a dotted path (shared prefixes load once).include("posts", function(q) ... end)— with per-relation options: callwhere/order/limit/offset(and nestedinclude) onq. Thelimitis applied PER PARENT (e.g. 5 latest posts for each user).
local users = User:query():include("posts.comments"):all():await()
print(#users[1].posts[1].comments)
local u = User:query():include("posts", function(q)
q:where("published", true):order("created_at", "DESC"):limit(5)
:include("comments", function(c) c:order("created_at", "ASC") end)
end):all():await()increment async method
NormQueryBuilder:increment(column: string, amount?: number)
-> promise: NormNumberPromiseAtomically add amount (default 1) to a column on every matching row, in one SET col = col + ? statement (no read-modify-write, race-free). Resolves with the affected row count.
Player:where("id", id):increment("coins", 50):await()join method
NormQueryBuilder:join(table_name: string, first: string, op: string, second?: string)
-> self: NormQueryBuilderINNER JOIN another table. Use qualified table.column refs. Forms: join(table, first, second) (defaults =) or join(table, first, op, second). Joins are for FILTERING/SORTING by a related table — combine with qualified where/order. Since joined rows mix columns from both tables, restrict the projection with :select_raw("main.*") if you still want :all() to wrap the main model, or read the flattened rows with :rows().
Post:join("users", "users.id", "posts.user_id")
:where("users.admin", true):select_raw("`posts`.*"):all():await()left_join method
NormQueryBuilder:left_join(table_name: string, first: string, op: string, second?: string)
-> self: NormQueryBuilderLEFT JOIN another table (same argument forms as :join). Keeps main rows even when there is no match on the joined side.
limit method
NormQueryBuilder:limit(n: number)
-> self: NormQueryBuilderLimit the number of rows (pair with :offset() for pagination).
local page = User:query():order("id"):limit(10):offset(20):all():await()max async method
NormQueryBuilder:max(column: string)
-> promise: NormNumberPromiseMAX of a column over the current filter. Resolves with the raw value.
local top = Player:max("score"):await()min async method
NormQueryBuilder:min(column: string)
-> promise: NormNumberPromiseMIN of a column over the current filter. Resolves with the raw value.
model field
NormModeloffset method
NormQueryBuilder:offset(n: number)
-> self: NormQueryBuilderSkip n rows (use with :limit()).
omit method
NormQueryBuilder:omit(...string|string[])
-> self: NormQueryBuilderInverse of select: select every column of the model EXCEPT the given ones (e.g. to drop a password / large blob without listing all the others). The omitted columns are simply absent from the returned records.
local u = User:omit("password"):find(1):await()only_trashed method
NormQueryBuilder:only_trashed()
-> self: NormQueryBuilderReturn ONLY soft-deleted rows.
or_where method
NormQueryBuilder:or_where(column: string|table<string, any>, op?: string, value?: any)
-> self: NormQueryBuilderOR variant of where.
User:query():where("admin", true):or_where("coins", ">", 1000):all():await()or_where_between method
NormQueryBuilder:or_where_between(column: string, min: any, max: any)
-> self: NormQueryBuilderor_where_in method
NormQueryBuilder:or_where_in(column: string, list: any[])
-> self: NormQueryBuilderor_where_like method
NormQueryBuilder:or_where_like(column: string, pattern: string)
-> self: NormQueryBuilderor_where_not method
NormQueryBuilder:or_where_not(column: string, value: any)
-> self: NormQueryBuilderor_where_not_between method
NormQueryBuilder:or_where_not_between(column: string, min: any, max: any)
-> self: NormQueryBuilderor_where_not_in method
NormQueryBuilder:or_where_not_in(column: string, list: any[])
-> self: NormQueryBuilderor_where_not_like method
NormQueryBuilder:or_where_not_like(column: string, pattern: string)
-> self: NormQueryBuilderor_where_not_null method
NormQueryBuilder:or_where_not_null(column: string)
-> self: NormQueryBuilderor_where_null method
NormQueryBuilder:or_where_null(column: string)
-> self: NormQueryBuilderorder method
NormQueryBuilder:order(column: string, dir?: "ASC"|"DESC")
-> self: NormQueryBuilderAdd an ORDER BY clause (call again for secondary orderings).
User:query():order("coins", "DESC"):order("name"):all():await()dir:
| "ASC"
| "DESC"paginate async method
NormQueryBuilder:paginate(page?: number, per_page?: number)
-> promise: NormPromisePaginate the current query. Runs a COUNT(*) over the filter plus a LIMIT/OFFSET page query, resolving with { data, total, page, per_page, last_page, from, to }. Honours where, order, soft-delete scope, and with_count.
local p = User:where("admin", true):order("name"):paginate(2, 20):await()
print(p.page, p.last_page, #p.data, p.total)rows async method
NormQueryBuilder:rows()
-> promise: NormRowsPromiseExecute the query and resolve with the RAW rows (no record wrapping). Use this for grouped aggregates built with :select_raw / :group_by / :having.
local stats = Player:select_raw("faction, COUNT(*) AS n, SUM(`coins`) AS total")
:group_by("faction"):having("COUNT(*)", ">", 10):rows():await()scope method
NormQueryBuilder:scope(name: string, ...any)
-> self: NormQueryBuilderApply a named scope (a reusable query fragment registered on the model with Model:scope(name, fn)), passing it any extra args. Chainable.
User:active():scope("older_than", 18):all():await()select method
NormQueryBuilder:select(...string|string[])
-> self: NormQueryBuilderRestrict selected columns (the inverse is :omit).
User:query():select("id", "name"):all():await()select_raw method
NormQueryBuilder:select_raw(expr: string)
-> self: NormQueryBuilderAdd a RAW (unquoted) select expression — for aggregates/computed columns that the column-quoting select can't express. Pair with :group_by and :rows().
User:select_raw("faction, COUNT(*) AS n"):group_by("faction"):rows():await()sum async method
NormQueryBuilder:sum(column: string)
-> promise: NormNumberPromiseSUM of a column over the current filter. Resolves with a number (0 if empty).
local bank = User:where("admin", false):sum("coins"):await()update async method
NormQueryBuilder:update(data: table<string, any>)
-> promise: NormNumberPromiseBulk-update every matching row in one statement (no records loaded). Resolves with the affected row count.
local n = User:query():where("admin", true):update({ coins = 0 }):await()where method
NormQueryBuilder:where(column: string|table<string, any>, op?: string, value?: any)
-> self: NormQueryBuilderAdd an AND condition. Forms: where(col, value), where(col, op, value) or where({ col = value, ... }). Chainable with the other where_* helpers.
User:query():where("coins", ">", 100):where("admin", true):all():await()where_between method
NormQueryBuilder:where_between(column: string, min: any, max: any)
-> self: NormQueryBuildercolumn [NOT] BETWEEN min AND max (inclusive). With OR / negated variants.
Player:query():where_between("level", 10, 20):all():await()where_doesnt_have method
NormQueryBuilder:where_doesnt_have(name: string, configure?: fun(q: NormQueryBuilder))
-> self: NormQueryBuilderInverse of where_has: keep only rows with NO matching related row (NOT EXISTS (...)).
where_has method
NormQueryBuilder:where_has(name: string, configure?: fun(q: NormQueryBuilder))
-> self: NormQueryBuilderKeep only rows that HAVE at least one related row for name (optionally matching the configure conditions). Compiles to EXISTS (correlated subquery).
User:where_has("posts"):all():await() -- users with any post
User:where_has("posts", function(q) q:where("published", true) end):all():await()where_in method
NormQueryBuilder:where_in(column: string, list: any[])
-> self: NormQueryBuildercolumn IN (...) (and its OR / negated variants).
User:query():where_in("id", { 1, 2, 3 }):all():await()where_like method
NormQueryBuilder:where_like(column: string, pattern: string)
-> self: NormQueryBuildercolumn [NOT] LIKE pattern (use % / _ wildcards). With OR / negated variants.
User:query():where_like("name", "John%"):all():await()where_not method
NormQueryBuilder:where_not(column: string, value: any)
-> self: NormQueryBuildercolumn != value (and OR variant).
where_not_between method
NormQueryBuilder:where_not_between(column: string, min: any, max: any)
-> self: NormQueryBuilderwhere_not_in method
NormQueryBuilder:where_not_in(column: string, list: any[])
-> self: NormQueryBuilderwhere_not_like method
NormQueryBuilder:where_not_like(column: string, pattern: string)
-> self: NormQueryBuilderwhere_not_null method
NormQueryBuilder:where_not_null(column: string)
-> self: NormQueryBuilderwhere_null method
NormQueryBuilder:where_null(column: string)
-> self: NormQueryBuildercolumn IS [NOT] NULL (and OR variants).
with_count method
NormQueryBuilder:with_count(...string)
-> self: NormQueryBuilderAdd a <name>_count field to each returned record: the number of related rows, without loading them (a correlated COUNT(*) subquery). Soft-deleted related rows aren't counted.
local users = User:with_count("posts"):all():await()
print(users[1].posts_count)with_trashed method
NormQueryBuilder:with_trashed()
-> self: NormQueryBuilderInclude soft-deleted rows in the result (disables the default exclusion).