Builder State
Last updated
This page explains where the query builder keeps its state and what that means for how you call it.
State Lives in Static Fields
MySQLPool has no instances: table(), where(), and the other builder methods modify static fields on the class and return the class itself (typeof MySQLPool) for chaining (src/MySQLPool.ts:20-30).
| Field | Written by |
|---|---|
tableName, currentTarget |
table() |
selectAry |
select() |
joinAry |
innerJoin(), leftJoin(), rightJoin() |
whereAry, bindingAry |
where() |
orderAry |
orderBy() |
queryLimit, queryOffset |
limit(), offset() |
withTotal |
total() |
setAry |
increase(), update() |
Every Chain Starts with table()
Only table() calls reset() to clear these fields (src/MySQLPool.ts:117-137). Running get(), update(), or another execution method does not clear them, so:
await MySQLPool.table("users").where("id", 1).get();
// Wrong: reuses the previous table name and WHERE, so it queries id = 1 again
await MySQLPool.where("status", "active").get();
// Right
await MySQLPool.table("users").where("status", "active").get();
currentTarget persists too: calling query(sql) without a target after table("x", "write") uses the write pool.
Concurrent Queries
get(), insert(), update(), and upsert() assemble the SQL and bindings synchronously before their first await, so chains written synchronously in a single expression can run concurrently:
const [users, orders] = await Promise.all([
MySQLPool.table("users").where("status", "active").get(),
MySQLPool.table("orders").where("paid", 1).get(),
]);
If you split a chain with an await in the middle, a table() call from another async flow can reset the state halfway:
// Unsafe: another request may call table() during the await
const q = MySQLPool.table("users");
const role = await loadRole();
const rows = await q.where("role", role).get();
When a condition value needs an await, resolve it first and then write the whole chain in one go.