1- <!-- docs: sync from coderbuzz/codex@d28b4e9 -->
1+ <!-- docs: sync from coderbuzz/codex@b8d6f33 -->
22
33# @coderbuzz/sql — AI Expert Knowledge Reference
44
@@ -302,8 +302,29 @@ for (const stmt of stmts) {
302302}
303303```
304304
305- ` applyDiff() ` supports: ADD COLUMN (all dialects), DROP COLUMN (PG/MySQL/MSSQL),
306- ALTER COLUMN (PG/MySQL/MSSQL). SQLite skips DROP/ALTER with a ` console.warn ` .
305+ ` applyDiff() ` supports: RENAME COLUMN (all dialects), ADD COLUMN (all dialects),
306+ DROP COLUMN (PG/MySQL/MSSQL), ALTER COLUMN (PG/MySQL/MSSQL). SQLite skips
307+ DROP/ALTER with a ` console.warn ` .
308+
309+ ** Renaming a column.** ` diff() ` cannot infer a rename — ` memo ` disappearing and
310+ ` keterangan ` appearing is indistinguishable from a genuine drop-and-add, and
311+ guessing means sometimes emitting ` ALTER ... RENAME ` for a column that should
312+ have been dropped, keeping data that was meant to go under a name that now means
313+ something else. Declare it on the schema:
314+
315+ ``` ts
316+ new SqlTable (' journal' , { keterangan: pg .varchar (255 ).renamedFrom (' memo' ) })
317+ ```
318+
319+ ` diff() ` then fills ` TableDiff.renameColumns ` instead of producing an add plus a
320+ drop, and ` applyDiff() ` emits
321+ ` ALTER TABLE journal RENAME COLUMN memo TO keterangan ` ** before** any
322+ ` ADD COLUMN ` , so the data moves with the name. A rename that also changes the
323+ column's type emits both statements. The annotation is inert once the old name
324+ is gone from the database, so it is safe to leave in place until every
325+ environment has migrated. ` renameColumns ` is optional on ` TableDiff ` , so a
326+ hand-built diff still compiles. The annotation survives the other column
327+ modifiers (` .notNull() ` , ` .index() ` , …).
307328
308329---
309330
@@ -351,6 +372,8 @@ const rows = await db.select("u.id", "u.name", "p.title")
351372
352373### Joins
353374
375+ ** Untyped** — raw strings, validated by the identifier check:
376+
354377``` ts
355378db .select (" u.id" , " p.title" )
356379 .from (" users u" )
@@ -360,6 +383,50 @@ db.select("u.id", "p.title")
360383// .full_join() — NOT supported by SQLite, MySQL, ClickHouse
361384```
362385
386+ ** Typed** — from a ` SqlTable ` , stated as column pairs:
387+
388+ ``` ts
389+ journalLines .from (db )
390+ .join (accounts , { left: ' account_id' , right: ' id' }) // INNER
391+ .leftJoin (entries , { left: ' entry_id' , right: ' id' }) // LEFT
392+ .rightJoin (table , on ) // RIGHT
393+ .fullJoin (table , on ) // FULL
394+ ```
395+
396+ ``` ts
397+ type JoinOn <L , R > =
398+ | { left: keyof L & string ; right: keyof R & string }
399+ | ReadonlyArray <{ left: keyof L & string ; right: keyof R & string }>
400+ ` ` `
401+
402+ ` left ` is a column of the query so far — the base table or anything already
403+ joined — ` right ` a column of the table being joined. Both are checked against
404+ their schemas: a typo is a compile error, and there is no string left for
405+ anything else to end up inside. An array of pairs joins them with ` AND ` .
406+
407+ **Result types track outer-join nullability**, which is the part worth having:
408+
409+ | | Result |
410+ |---|---|
411+ | ` join ` | ` TResult & InferRow <S2 >` |
412+ | ` leftJoin ` | ` TResult & Nullable <InferRow <S2 >>` |
413+ | ` rightJoin ` | ` Nullable <TResult > & InferRow <S2 >` |
414+ | ` fullJoin ` | ` Nullable <TResult > & Nullable <InferRow <S2 >>` |
415+
416+ ` order_by ()` and ` group_by ()` on a typed query accept columns of the base table
417+ **and** of everything joined; ` order_by ` also takes ` [column , ' ASC' | ' DESC' ]` . A
418+ raw string still works — the validator still runs on it — so nothing existing
419+ breaks.
420+
421+ Why this exists: ` SqlTable .from ()` gave a typed query, but ` TypedSelectQuery `
422+ inherited ` left_join ` / ` order_by ` / ` group_by ` from ` SelectQuery ` unchanged, so the
423+ first join dropped you back to raw strings and took the joined table's column
424+ types with it. The identifier validator had already closed the injection surface;
425+ this makes the safe path the convenient one.
426+
427+ Dialect limits still apply: SQLite, MySQL and ClickHouse reject ` fullJoin ` at
428+ compile time, the same as ` .full_join ()` .
429+
363430### CTE
364431
365432` ` ` ts
0 commit comments