Part 2 · 2 chapters · ~12 min

Query Building

From fluent API to AST to SQL, dialect compilers and where abstractions leak, parameter binding and injection, relation loading and hydration (the expensive part nobody profiles), type mapping for dates, decimals, JSON, enums and arrays, and raw SQL escape hatches.

5

The pipeline

code
// Kysely: the AST is visible and the SQL is predictable
const q = db.selectFrom('transfers').innerJoin('accounts', 'accounts.id', 'transfers.account_id')
  .select(['transfers.id', 'transfers.amount_kobo', 'accounts.currency'])
  .where('accounts.owner_id', '=', ownerId).orderBy('transfers.created_at', 'desc').limit(50);
q.compile();   // { sql: 'select "transfers"."id", ... where "accounts"."owner_id" = $1 ... limit $2', parameters: [7, 50] }

// injection through an escape hatch: never interpolate user input into raw SQL
await prisma.$queryRawUnsafe(`SELECT * FROM accounts WHERE email = '${email}'`);   // vulnerable
await prisma.$queryRaw`SELECT * FROM accounts WHERE email = ${email}`;             // tagged template: parameterised
FROM A FLUENT CALL TO SQL
every ORM and query builder follows this pipeline
fluent APIdb.select().from(t).where(...)query ASTnodes: select, from, where, paramsdialect compilerPostgres / MySQL / SQLiteSQL + parameters$1, $2 or ?driverextended protocol, preparedhydrationrows → objects
swipe the figure sideways, or tap expand for full screen
1/4
the AST
Fluent calls build an abstract syntax tree of the query, not a string. That is what lets builders compose, inspect and type-check queries.
calls build a tree, not a stringcomposable and checkable
6

Type mapping

typepitfallpractice
bigintJS drivers return strings (or lose precision as Number)map to BigInt or string; never Number for ids above 2^53
numeric / decimalreturned as string or floata decimal library; minor units in bigint for money
timestamp vs timestamptztime zone shifts on readtimestamptz, UTC everywhere
JSON / JSONBuntyped blobsvalidate on read (zod, Pydantic)
enumsdatabase enums are hard to changetext with a check constraint, or lookup tables