|
| 1 | +# postgres |
| 2 | + |
| 3 | +PostgreSQL client via `libpq`. Connect to a Postgres database, run queries, and read results as native strings. Imported as a module: |
| 4 | + |
| 5 | +```typescript |
| 6 | +import { Pool } from "chadscript/postgres"; |
| 7 | +``` |
| 8 | + |
| 9 | +`libpq` is required at build time. On macOS: `brew install libpq`. On Debian/Ubuntu: `apt install libpq-dev`. |
| 10 | + |
| 11 | +## `new Pool(conninfo)` |
| 12 | + |
| 13 | +Create a connection pool. The connection string follows [libpq's format](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING) — either key/value pairs or a URI. |
| 14 | + |
| 15 | +```typescript |
| 16 | +const pool = new Pool("host=127.0.0.1 port=5432 user=postgres password=secret dbname=mydb"); |
| 17 | +// or |
| 18 | +const pool2 = new Pool("postgresql://postgres:secret@127.0.0.1:5432/mydb"); |
| 19 | +``` |
| 20 | + |
| 21 | +`Pool` is the recommended entry point for application code. It connects lazily on first query — the constructor does not block. |
| 22 | + |
| 23 | +## `pool.query(sql)` |
| 24 | + |
| 25 | +Execute a SQL statement and return a [`QueryResult`](#queryresult). Throws on SQL errors. Parameterized queries are coming in a follow-up; for now, build the SQL string yourself — **escape any user input** or you will ship an SQL injection. |
| 26 | + |
| 27 | +```typescript |
| 28 | +pool.query("CREATE TABLE users (id INT, name TEXT)"); |
| 29 | +pool.query("INSERT INTO users VALUES (1, 'alice')"); |
| 30 | + |
| 31 | +const res = pool.query("SELECT id, name FROM users ORDER BY id"); |
| 32 | +console.log(res.rowCount); // 1 |
| 33 | +console.log(res.getValue(0, "name")); // "alice" |
| 34 | +``` |
| 35 | + |
| 36 | +## `pool.end()` |
| 37 | + |
| 38 | +Close the pool's underlying connection. Safe to call multiple times. |
| 39 | + |
| 40 | +```typescript |
| 41 | +pool.end(); |
| 42 | +``` |
| 43 | + |
| 44 | +## `Client` (low-level) |
| 45 | + |
| 46 | +For cases where you need explicit connection lifecycle, use `Client` directly: |
| 47 | + |
| 48 | +```typescript |
| 49 | +import { Client } from "chadscript/postgres"; |
| 50 | + |
| 51 | +const c = new Client("postgresql://postgres:secret@127.0.0.1:5432/mydb"); |
| 52 | +c.connect(); |
| 53 | +const res = c.query("SELECT 1"); |
| 54 | +c.end(); |
| 55 | +``` |
| 56 | + |
| 57 | +Most application code should prefer `Pool`. `Client` requires you to call `connect()` before any queries — `Pool` handles this automatically. |
| 58 | + |
| 59 | +## `QueryResult` |
| 60 | + |
| 61 | +Returned by `client.query()`. All values are currently strings — type coercion (integer, boolean, date) is a follow-up. |
| 62 | + |
| 63 | +| Field | Type | Description | |
| 64 | +|-------|------|-------------| |
| 65 | +| `rowCount` | `number` | Affected rows (INSERT/UPDATE/DELETE) or number of rows returned (SELECT) | |
| 66 | +| `numRows` | `number` | Number of rows in the result set (SELECT) | |
| 67 | +| `numCols` | `number` | Number of columns in the result set | |
| 68 | +| `fields` | `string[]` | Column names in order | |
| 69 | + |
| 70 | +### `result.getValue(row, col)` |
| 71 | + |
| 72 | +Return the value at a given row index and column name, as a string. |
| 73 | + |
| 74 | +```typescript |
| 75 | +const res = c.query("SELECT id, name, city FROM users ORDER BY id"); |
| 76 | +for (let i = 0; i < res.numRows; i++) { |
| 77 | + const name = res.getValue(i, "name"); |
| 78 | + const city = res.getValue(i, "city"); |
| 79 | + console.log(name + " in " + city); |
| 80 | +} |
| 81 | +``` |
| 82 | + |
| 83 | +Column lookup by name is linear in the number of columns. If you're reading millions of rows in a tight loop, cache the column index: |
| 84 | + |
| 85 | +```typescript |
| 86 | +const nameIdx = res.fields.indexOf("name"); |
| 87 | +for (let i = 0; i < res.numRows; i++) { |
| 88 | + const r = res.getRow(i); |
| 89 | + console.log(r.getAt(nameIdx)); |
| 90 | +} |
| 91 | +``` |
| 92 | + |
| 93 | +### `result.getRow(index)` |
| 94 | + |
| 95 | +Return a lightweight `Row` view into the result set. The returned `Row` holds a reference to the parent result's data — it does not copy. |
| 96 | + |
| 97 | +```typescript |
| 98 | +const r = res.getRow(0); |
| 99 | +const name = r.get("name"); // by column name |
| 100 | +const first = r.getAt(0); // by column index |
| 101 | +``` |
| 102 | + |
| 103 | +## Example — CRUD |
| 104 | + |
| 105 | +```typescript |
| 106 | +import { Pool } from "chadscript/postgres"; |
| 107 | + |
| 108 | +const pool = new Pool("postgresql://postgres:secret@127.0.0.1:5432/mydb"); |
| 109 | + |
| 110 | +pool.query("DROP TABLE IF EXISTS users"); |
| 111 | +pool.query("CREATE TABLE users (id INT, name TEXT, city TEXT)"); |
| 112 | + |
| 113 | +pool.query("INSERT INTO users VALUES (1, 'alice', 'nyc'), (2, 'bob', 'sf')"); |
| 114 | + |
| 115 | +const res = pool.query("SELECT id, name, city FROM users ORDER BY id"); |
| 116 | +console.log("rows: " + res.numRows); |
| 117 | +for (let i = 0; i < res.numRows; i++) { |
| 118 | + console.log(res.getValue(i, "id") + " " + res.getValue(i, "name") + " " + res.getValue(i, "city")); |
| 119 | +} |
| 120 | + |
| 121 | +const upd = pool.query("UPDATE users SET city = 'LA' WHERE id = 1"); |
| 122 | +console.log("updated " + upd.rowCount); |
| 123 | + |
| 124 | +pool.end(); |
| 125 | +``` |
| 126 | + |
| 127 | +## Current Limitations |
| 128 | + |
| 129 | +This module is under active development. Known gaps: |
| 130 | + |
| 131 | +- **No parameterized queries yet** — every query is literal SQL. Build strings carefully and never concatenate user input. Coming soon. |
| 132 | +- **All values are strings** — integers come back as `"42"`, booleans as `"t"`/`"f"`, dates as ISO-ish strings. Type coercion is a follow-up. |
| 133 | +- **`Pool` is a thin wrapper over a single `Client`** — no real connection reuse yet. Calls are sequential. Real pooling (multiple connections, queuing, limits) needs async, which is a follow-up. |
| 134 | +- **Synchronous under the hood** — `libpq` is called synchronously. Calls block the event loop. Real async (libuv integration) is a follow-up. |
| 135 | +- **No `LISTEN`/`NOTIFY`, no `COPY`, no streaming** — the basics only. |
| 136 | + |
| 137 | +## Native Implementation |
| 138 | + |
| 139 | +| API | Maps to | |
| 140 | +|-----|---------| |
| 141 | +| `new Client()` / `connect()` | `PQconnectdb()` + `PQstatus()` | |
| 142 | +| `client.query()` | `PQexec()` + `PQresultStatus()` + `PQgetvalue()` loop | |
| 143 | +| `QueryResult.fields` | `PQfname()` per column | |
| 144 | +| `QueryResult.numRows` / `numCols` | `PQntuples()` / `PQnfields()` | |
| 145 | +| `client.end()` | `PQfinish()` | |
| 146 | + |
| 147 | +All string values returned from `libpq` are copied into GC-managed memory before the underlying `PGresult` is cleared, so values remain valid after the next query. |
0 commit comments