1- <!-- docs: sync from coderbuzz/codex@b8d6f33 -->
1+ <!-- docs: sync from coderbuzz/codex@200be78 -->
22
3- # @coderbuzz/sql — AI Expert Knowledge Reference
3+ # @coderbuzz/sql : AI Expert Knowledge Reference
44
55** Package:** ` @coderbuzz/sql ` v0.1.3\
66** Purpose:** Comprehensive reference for AI agents generating application code
@@ -54,7 +54,7 @@ import { snowflake } from "@coderbuzz/sql/snowflake";
5454import { databricks } from " @coderbuzz/sql/databricks" ;
5555```
5656
57- ### 2.2 Root package — shared helpers and types
57+ ### 2.2 Root package: shared helpers and types
5858
5959``` ts
6060import {
@@ -120,7 +120,7 @@ import * as databricksTypes from "@coderbuzz/sql/databricks-types";
120120
121121## 3. Connecting to a Database
122122
123- Each dialect namespace has a ` connect() ` factory. Always call ` connect() ` — do
123+ Each dialect namespace has a ` connect() ` factory. Always call ` connect() ` . Do
124124not instantiate engine classes directly unless needed for testing.
125125
126126``` ts
@@ -227,7 +227,7 @@ const users = pg.table("users", {
227227| ` .defaultNow() ` | DEFAULT NOW() in DDL |
228228
229229Legacy uppercase aliases: ` .PRIMARY() ` , ` .NOT_NULL() ` , ` .ALLOW_NULL() ` ,
230- ` .INDEX() ` , ` .DEFAULT(v) ` — still work, prefer camelCase in new code.
230+ ` .INDEX() ` , ` .DEFAULT(v) ` still work. Prefer camelCase in new code.
231231
232232### ClickHouse Table Options (third arg)
233233
@@ -306,7 +306,7 @@ for (const stmt of stmts) {
306306DROP COLUMN (PG/MySQL/MSSQL), ALTER COLUMN (PG/MySQL/MSSQL). SQLite skips
307307DROP/ALTER with a ` console.warn ` .
308308
309- ** Renaming a column.** ` diff() ` cannot infer a rename — ` memo ` disappearing and
309+ ** Renaming a column.** ` diff() ` cannot infer a rename: ` memo ` disappearing and
310310` keterangan ` appearing is indistinguishable from a genuine drop-and-add, and
311311guessing means sometimes emitting ` ALTER ... RENAME ` for a column that should
312312have been dropped, keeping data that was meant to go under a name that now means
@@ -333,7 +333,7 @@ modifiers (`.notNull()`, `.index()`, …).
333333### Pattern 1: Table-bound typed query
334334
335335``` ts
336- // .from(engine) returns TypedSelectQuery — use .fields() for type-narrowing
336+ // .from(engine) returns TypedSelectQuery: use .fields() for type-narrowing
337337const rows = await users .from (db )
338338 .fields (" id" , " email" , " name" ) // typed: { id, email, name }[]
339339 .where ({ active: true })
@@ -357,13 +357,13 @@ const rows = await db.select("u.id", "u.name", "p.title")
357357### Fields variants in .fields()
358358
359359``` ts
360- // 1. Plain column name — type: InferRow<S>[col]
360+ // 1. Plain column name ( type: InferRow<S>[col])
361361.fields (" id" , " name" )
362362
363- // 2. Column with alias — type: { userEmail: string }
363+ // 2. Column with alias ( type: { userEmail: string })
364364.fields ([" email" , " userEmail" ])
365365
366- // 3. Computed expression — type: { upperName: string }
366+ // 3. Computed expression ( type: { upperName: string })
367367.fields (expr <string >(" UPPER(name)" , " upperName" ))
368368
369369// 4. Aggregate helpers
@@ -372,18 +372,18 @@ const rows = await db.select("u.id", "u.name", "p.title")
372372
373373### Joins
374374
375- ** Untyped** — raw strings, validated by the identifier check:
375+ ** Untyped** : raw strings, validated by the identifier check:
376376
377377``` ts
378378db .select (" u.id" , " p.title" )
379379 .from (" users u" )
380380 .left_join (" posts p" , " p.user_id = u.id" ) // LEFT JOIN
381381 .inner_join (" tags t" , " t.post_id = p.id" ) // INNER JOIN
382382 .right_join (" authors a" , " a.id = p.author" ); // RIGHT JOIN
383- // .full_join() — NOT supported by SQLite, MySQL, ClickHouse
383+ // .full_join(): NOT supported by SQLite, MySQL, ClickHouse
384384```
385385
386- ** Typed** — from a ` SqlTable ` , stated as column pairs:
386+ ** Typed** : from a ` SqlTable ` , stated as column pairs:
387387
388388``` ts
389389journalLines .from (db )
@@ -399,8 +399,8 @@ type JoinOn<L, R> =
399399 | ReadonlyArray <{ left: keyof L & string ; right: keyof R & string }>
400400` ` `
401401
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
402+ ` left ` is a column of the query so far ( the base table or anything already
403+ joined), and ` right ` a column of the table being joined. Both are checked against
404404their schemas: a typo is a compile error, and there is no string left for
405405anything else to end up inside. An array of pairs joins them with ` AND ` .
406406
@@ -415,7 +415,7 @@ anything else to end up inside. An array of pairs joins them with `AND`.
415415
416416` order_by ()` and ` group_by ()` on a typed query accept columns of the base table
417417**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
418+ raw string still works ( the validator still runs on it), so nothing existing
419419breaks.
420420
421421Why this exists: ` SqlTable .from ()` gave a typed query, but ` TypedSelectQuery `
@@ -486,7 +486,7 @@ const { sql } = users.from(db).where({ id: 1 }).explain();
486486
487487---
488488
489- ## 8. WHERE Conditions — Complete Reference
489+ ## 8. WHERE Conditions: Complete Reference
490490
491491### Object form (parameterised)
492492
@@ -504,7 +504,7 @@ const { sql } = users.from(db).where({ id: 1 }).explain();
504504.where ({ deleted_at: " IS NULL" }) // WHERE deleted_at IS NULL
505505.where ({ status: " IS NOT NULL" }) // WHERE status IS NOT NULL
506506
507- // Multiple fields — joined with AND
507+ // Multiple fields: joined with AND
508508.where ({ active: true , role: " admin" })
509509// WHERE active = ? AND role = ?
510510```
@@ -577,12 +577,12 @@ const [row] = await users.insert(db)
577577 .returning (" id" , " email" )
578578 .execute () as { id: number ; email: string }[];
579579
580- // Upsert — do nothing on conflict
580+ // Upsert: do nothing on conflict
581581await db .insert_into (" users" , {
582582 onConflict: { type: " do_nothing" , columns: [" email" ] },
583583}).values ([{ email: " a@b.com" , name: " Alice" }]).execute ();
584584
585- // Upsert — update on conflict
585+ // Upsert: update on conflict
586586await db .insert_into (" users" , {
587587 onConflict: { type: " do_update" , columns: [" email" ], set: { name: " Alice Updated" } },
588588}).values ([{ email: " a@b.com" , name: " Alice" }]).execute ();
@@ -628,10 +628,10 @@ const batcher = db.batchInsert("events", {
628628 timeout: 2_000 , // force flush after this many ms from first write
629629 maxInflight: 4 , // concurrent flushes allowed before write() waits
630630 settings: { async_insert: " 1" }, // engine-specific (ClickHouse)
631- onError : (err , rows ) => { /* REQUIRED — retry or dead-letter these rows */ },
631+ onError : (err , rows ) => { /* REQUIRED: retry or dead-letter these rows */ },
632632});
633633
634- // Write rows — write() returns a promise; awaiting it applies backpressure
634+ // Write rows: write() returns a promise; awaiting it applies backpressure
635635await batcher .write ({ id: 1 , val: " a" });
636636await batcher .write ([{ id: 2 , val: " b" }, { id: 3 , val: " c" }]);
637637
@@ -650,13 +650,13 @@ await batcher.close(); // flush + drain + seal (throws if write() called after)
650650- ` onError ` is REQUIRED. The constructor throws without it. Rows leave the
651651 pending queue before the insert runs, so a failed auto-flush has no other way
652652 to be observed.
653- - Always ` await batcher.close() ` at the end — never fire-and-forget.
653+ - Always ` await batcher.close() ` at the end: never fire-and-forget.
654654- After ` close() ` , calling ` write() ` rejects.
655- - Every row in a batch must have the SAME keys — a batched INSERT has one
655+ - Every row in a batch must have the SAME keys: a batched INSERT has one
656656 column list. A differing row rejects. Pass ` heterogeneousRows: "union" ` to
657657 insert the union of all columns with NULL for absent ones.
658658- ` await ` each ` write() ` in a bulk import; that is what keeps memory flat.
659- - Auto-flushes are fire-and-forget internally but tracked — ` drain() ` waits for
659+ - Auto-flushes are fire-and-forget internally but tracked: ` drain() ` waits for
660660 them.
661661
662662---
@@ -691,14 +691,14 @@ await db.delete_from("users")
691691 .where (lt (" created_at" , new Date (" 2024-01-01" )))
692692 .execute ();
693693
694- // WITHOUT .where() deletes ALL rows — guard with middleware
694+ // WITHOUT .where() deletes ALL rows: guard with middleware
695695```
696696
697697---
698698
699699## 13. Raw SQL (Tagged Template)
700700
701- Values are ** always** bound parameters — never inlined into SQL text.
701+ Values are ** always** bound parameters: never inlined into SQL text.
702702
703703``` ts
704704const email = " ada@example.com" ;
@@ -732,7 +732,7 @@ Placeholder styles per dialect:
732732## 14. Transactions
733733
734734` transaction() ` holds ONE connection for the whole callback. Use ` tx ` for every
735- statement inside — ` db ` is a pool and would run the statement on a different
735+ statement inside: ` db ` is a pool and would run the statement on a different
736736connection, outside the transaction.
737737
738738``` ts
@@ -756,7 +756,7 @@ await db.transaction(fn, {
756756});
757757```
758758
759- ` setup ` is where ` SET LOCAL ` belongs — it is the mechanism PostgreSQL
759+ ` setup ` is where ` SET LOCAL ` belongs: it is the mechanism PostgreSQL
760760row-level security depends on, and it is correct only inside a
761761single-connection transaction.
762762
@@ -787,14 +787,14 @@ PostgreSQL / MySQL / Oracle only. SQLite, MSSQL and ClickHouse throw.
787787
788788If the callback fails AND the ` ROLLBACK ` also fails, a
789789` TransactionRollbackError ` is thrown carrying both ` cause ` and ` rollbackError ` .
790- The transaction's outcome is undetermined — reconcile, do not just retry.
790+ The transaction's outcome is undetermined: reconcile, do not just retry.
791791
792792---
793793
794794## 15. Middleware
795795
796796``` ts
797- // Register in order — each calls next() to pass through
797+ // Register in order: each calls next() to pass through
798798db .use (async (query , next ) => {
799799 console .log (" [sql]" , query .sql , query .params );
800800 return next ();
@@ -853,7 +853,7 @@ max<number>("score", "topScore"); // MAX(score) AS topScore
853853
854854> ** TypeScript type of exact numerics.** ` decimal(p,s) ` , ` numeric(p,s) ` ,
855855> ` bigint() ` , ` bigserial() ` and MSSQL ` money() ` infer as ** ` string ` ** , not
856- > ` number ` — float64 cannot represent them exactly, and ` pg ` /` mysql2 ` return
856+ > ` number ` : float64 cannot represent them exactly, and ` pg ` /` mysql2 ` return
857857> them as strings anyway. ` integer ` , ` smallint ` , ` int ` , ` serial ` , ` float ` ,
858858> ` real ` and ` doublePrecision ` remain ` number ` .
859859
@@ -1016,9 +1016,9 @@ const result: ClickHouseDataset = await db.select(
10161016 .group_by (" tenant_id" )
10171017 .execute ();
10181018
1019- result .data ; // Record<string, unknown>[] — actual rows
1020- result .meta ; // { name: string; type: string }[] — column metadata
1021- result .rows ; // number — row count
1019+ result .data ; // Record<string, unknown>[] : actual rows
1020+ result .meta ; // { name: string; type: string }[] : column metadata
1021+ result .rows ; // number : row count
10221022result .statistics ?.read_rows ; // optional stats
10231023` ` `
10241024
@@ -1045,30 +1045,30 @@ result.statistics?.read_rows; // optional stats
10451045**DO NOT** construct queries by string concatenation:
10461046
10471047` ` ` ts
1048- // WRONG — SQL injection risk
1048+ // WRONG: SQL injection risk
10491049const rows = await db .execute (` SELECT * FROM users WHERE name = '${name }' ` );
10501050
1051- // CORRECT — use parameterised query or tagged template
1051+ // CORRECT: use parameterised query or tagged template
10521052const rows = await db .sql ` SELECT * FROM users WHERE name = ${name } ` .execute ();
10531053` ` `
10541054
10551055**DO NOT** call ` .stream ()` or ` .prepare ()` on MySQL, MSSQL, Oracle, Snowflake,
1056- Databricks, or ClickHouse engines — they throw.
1056+ Databricks, or ClickHouse engines: they throw.
10571057
10581058**DO NOT** use ` .returning ()` on MySQL, MSSQL, Oracle, Snowflake, Databricks, or
1059- ClickHouse — throws ` " RETURNING is not supported by this dialect" ` .
1059+ ClickHouse: throws ` " RETURNING is not supported by this dialect" ` .
10601060
1061- **DO NOT** use ` .full_join ()` with SQLite, MySQL, or ClickHouse — throws at
1061+ **DO NOT** use ` .full_join ()` with SQLite, MySQL, or ClickHouse: throws at
10621062compile time.
10631063
1064- **DO NOT** call ` batcher .write ()` after ` batcher .close ()` — rejects.
1064+ **DO NOT** call ` batcher .write ()` after ` batcher .close ()` : rejects.
10651065
1066- **DO NOT** forget ` await batcher .close ()` — rows may be left unwritten.
1066+ **DO NOT** forget ` await batcher .close ()` : rows may be left unwritten.
10671067
10681068**DO NOT** use the pooled engine inside a transaction callback. Use ` tx ` :
10691069
10701070` ` ` ts
1071- // WRONG — this INSERT runs on a different connection, outside the transaction
1071+ // WRONG: this INSERT runs on a different connection, outside the transaction
10721072await db .transaction (async (tx ) => { await db .execute (insertSql ); });
10731073
10741074// CORRECT
@@ -1078,7 +1078,7 @@ await db.transaction(async (tx) => { await tx.execute(insertSql); });
10781078**DO NOT** pass user input to ` .order_by ()` , ` .group_by ()` , ` .select ()` ,
10791079` .from ()` , or a join ` ON ` condition. These cannot be bound parameters, so they
10801080are interpolated. They are validated and will throw ` UnsafeIdentifierError ` on
1081- anything dangerous, but that is a guard, not a licence — map a sort parameter
1081+ anything dangerous, but that is a guard, not a licence: map a sort parameter
10821082through a fixed allow-list of column names:
10831083
10841084` ` ` ts
@@ -1095,16 +1095,16 @@ They are typed `string` because float64 cannot hold them exactly. `Number(x)`
10951095on a money column loses cents:
10961096
10971097` ` ` ts
1098- // WRONG — reintroduces the precision loss the string type exists to prevent
1098+ // WRONG: reintroduces the precision loss the string type exists to prevent
10991099const total = rows .reduce ((a , r ) => a + Number (r .debit ), 0 );
11001100
1101- // CORRECT — sum in SQL, or use a decimal library
1101+ // CORRECT: sum in SQL, or use a decimal library
11021102const [{ total }] = await db .sql ` SELECT SUM(debit)::text AS total FROM jurnal ` .execute ();
11031103` ` `
11041104
11051105**DO validate incoming amounts with ` decimal ()` from ` @coderbuzz / veta ` **, not
11061106` number ()` . It takes and returns the same normalized string this package uses,
1107- so there is no conversion at the HTTP boundary — and conversions are where
1107+ so there is no conversion at the HTTP boundary, and conversions are where
11081108precision goes:
11091109
11101110` ` ` ts
@@ -1121,7 +1121,7 @@ const postJournal = object({
11211121**DO NOT** use ` DELETE ` or ` UPDATE ` without ` .where ()` unless you intend to
11221122affect all rows. Add a middleware guard in production code.
11231123
1124- **DO NOT** use ` .unique ()` on ClickHouse table columns — throws.
1124+ **DO NOT** use ` .unique ()` on ClickHouse table columns: throws.
11251125
11261126---
11271127
@@ -1149,7 +1149,7 @@ await users.insert(db).values([{
11491149 active: true ,
11501150}]).execute ();
11511151
1152- // Read — typed result
1152+ // Read: typed result
11531153const all = await users .from (db ).fields (" id" , " name" ).where ({ active: true })
11541154 .execute ();
11551155
@@ -1270,7 +1270,7 @@ Version: 0.1.3
12701270License : MIT
12711271Type : ESM only (type : " module" )
12721272Peer deps (all optional ): pg , mysql2 , mssql , better - sqlite3 , @db / sqlite , oracledb , snowflake - sdk , @databricks / sql
1273- Runtime dep : @coderbuzz / veta (internal — schema coercion )
1273+ Runtime dep : @coderbuzz / veta (internal , schema coercion )
12741274` ` `
12751275
12761276**Export map summary:**
0 commit comments