chadscript/postgres: native postgres client via libpq#512
Merged
Conversation
Contributor
Benchmark Results (Linux x86-64)
CLI Tool Benchmarks
|
adds c_bridges/pg-bridge.c with thin wrappers around libpq for connect, exec, parameterized exec, and result introspection. build-vendor.sh detects libpq headers (keg-only brew install on darwin, system paths on linux) and gracefully skips when absent. no codegen or ts wiring yet — this commit is just the foundation for chadscript/postgres.
…tep 2/10) adds chadscript/postgres module. lib/postgres.ts uses declare function bindings to cs_pg_* externs — compiler auto-detects the cs_pg_ prefix in llvm-generator.ts, flips a new usesPostgres flag, and the link step picks up -lpq plus homebrew keg-only libpq lib path. pg-bridge.o is included conditionally. Client class exposes connect()/end() — query comes in step 3. verified end-to-end: compiled a fixture that connects to a local podman postgres and cleanly disconnects. the cs_pg_connect extern is called directly from the TS Client.connect method, no codegen special-casing required. pattern matches cs_llvm_*/cs_lld_*. workaround: added trailing return in parser-native transformExpression for the known checkMissingReturns false-positive (memory/native-missing-returns-false-positive.md) — the usesPostgres field addition shifted AST layouts and tripped the bug.
adds Client.query(sql) that returns QueryResult with rowCount for INSERT/UPDATE/DELETE. test fixture covers single insert, multi-row insert, update, and delete. abi fix: c functions called from chadscript as returning 'number' must return 'double', not 'int' — chadscript maps 'number' to llvm double, which lives in d0 on arm64 while c int returns come back in w0, completely different registers. previous step 2 passed by luck because CONNECTION_OK happens to be 0 and d0 was zero-initialized. fixed the whole bridge preemptively: all int-returning cs_pg_* now return double, all int parameters (row, col, nparams) are now double with internal cast. matches the pattern cs_lld_available uses.
adds nrows/ncols/fname/getvalue/getisnull extern bindings and SELECT
row access. QueryResult now holds flat { fields, values } plus numRows
/numCols — getValue(row, col) walks the fields[] to resolve a column
name to its offset and returns the string. getRow(idx) constructs a
lightweight Row view for iteration.
flat-storage design avoids chadscript ObjectArray type-loss: storing
Row[] and accessing rows[i].get() inside lib/postgres.ts triggered
compile errors because element access on an object array discards the
concrete type. keeping values as a string[] sidesteps the problem.
test covers 3-row SELECT with column-name lookup, field ordering,
empty result set, and rowCount.
c bridge now gc_strdups all string returns (getvalue, fname, error
messages, cmdtuples) so they survive past cs_pg_clear — previously
those pointers dangled into the freed PGresult.
…+6/10) Pool: thin wrapper over Client that lazy-connects on first query. v1 is sequential — real pooling (multiple conns, queuing) needs async and is a follow-up. matches node-pg's Pool entry point so copy-paste from tutorials works. Pool is now the documented primary API; Client is the low-level escape hatch. Parameterized queries: pool.query(sql, params) uses libpq's PQexecParams via a stateful param-builder pattern (cs_pg_params_new / _add / _exec_params_with). avoids marshaling a chadscript string[] across the ffi boundary — the %StringArray struct layout doesn't match libpq's const char**. TS passes values one at a time; c side grows its own dynamic array. placeholders are $1, $2, $3 (postgres native syntax). libpq handles escaping so user input with quotes or semicolons is safe. Typed accessors on QueryResult: getInt, getFloat, getBool delegate to parseInt/parseFloat/string compare — saves users from writing boiler- plate parsing on every access. no OID-based auto-typing — users know their schema, they call the right getter. avoids type-coercion surprises. Docs: new docs/stdlib/postgres.md with Pool-first API, documented limitations (sync, no null detection, string-only params, no real pooling). chadscript/postgres added to stdlib module index. test coverage: postgres-pool (Pool lazy-connect + CRUD), postgres-params-types (getInt/Float/Bool + parameterized queries + sql-injection safety with quoted param values).
step 2 added a usesPostgres field to LLVMGenerator plus a cs_pg_ prefix check in generateExternDeclaration plus a trailing-return workaround in parser-native/transformer.ts. any of those shifted enough state that the native stage 1 compiler began miscompiling generateGlobalVariableDeclarations — storing a pointer into a double slot for isClassInstance. --quick skipped stage 2 so it passed local verify but failed CI. fix: do the -lpq/pg-bridge.o detection entirely in compiler.ts and native-compiler-lib.ts by walking generator.declaredExternFunctions for anything starting with cs_pg_. no changes to llvm-generator.ts, generator-context.ts, or parser-native/transformer.ts. feature is functionally identical end-to-end (verified against local postgres). npm run verify (full, including stage 2) is green on this commit.
cs01
added a commit
that referenced
this pull request
Apr 15, 2026
…ative compiler the postgres fixtures shipped in #512 were @test-skip — ci never exercised them, so any regression in lib/postgres.ts or pg-bridge.c would land silently. this gates them behind a new `@test-requires-env: PG_TESTS_ENABLED` annotation and wires up postgres in both ci jobs: - linux: services: postgres:16 container, libpq-dev installed, PG_TESTS_ENABLED=1 - macos: brew install libpq postgresql@16, brew services start, provision postgres user/password/chadtest db, PG_TESTS_ENABLED=1 also fixes a separate gap in #512: src/chad-native.ts never called registerStdlib for postgres.ts, so .build/chad rejected `import { Pool } from "chadscript/postgres"` with 'stdlib module not found'. all my prior verification went through node dist/chad-node.js which reads lib/*.ts from disk — the embedded-stdlib path was untested. fixed by adding the registerStdlib call alongside the other lib/ modules. new test-discovery annotation: `@test-requires-env: VAR` skips a fixture unless the env var is set and non-empty. enables conditional integration tests without a separate test runner. extra docs added to the annotation header in test-discovery.ts. pg-bridge.o now appears in the verify-vendor loop, the package release artifact cp commands (linux + macos), and scripts/build-target-sdk.sh, matching the pattern used by every other bridge. verified locally: full `PG_TESTS_ENABLED=1 npm run verify` green against podman postgres:16 — all 5 fixtures pass, stage 0/1/2 self-hosting clean.
4 tasks
cs01
added a commit
that referenced
this pull request
Apr 15, 2026
* ci: run postgres fixtures on linux + macos, register postgres.ts in native compiler the postgres fixtures shipped in #512 were @test-skip — ci never exercised them, so any regression in lib/postgres.ts or pg-bridge.c would land silently. this gates them behind a new `@test-requires-env: PG_TESTS_ENABLED` annotation and wires up postgres in both ci jobs: - linux: services: postgres:16 container, libpq-dev installed, PG_TESTS_ENABLED=1 - macos: brew install libpq postgresql@16, brew services start, provision postgres user/password/chadtest db, PG_TESTS_ENABLED=1 also fixes a separate gap in #512: src/chad-native.ts never called registerStdlib for postgres.ts, so .build/chad rejected `import { Pool } from "chadscript/postgres"` with 'stdlib module not found'. all my prior verification went through node dist/chad-node.js which reads lib/*.ts from disk — the embedded-stdlib path was untested. fixed by adding the registerStdlib call alongside the other lib/ modules. new test-discovery annotation: `@test-requires-env: VAR` skips a fixture unless the env var is set and non-empty. enables conditional integration tests without a separate test runner. extra docs added to the annotation header in test-discovery.ts. pg-bridge.o now appears in the verify-vendor loop, the package release artifact cp commands (linux + macos), and scripts/build-target-sdk.sh, matching the pattern used by every other bridge. verified locally: full `PG_TESTS_ENABLED=1 npm run verify` green against podman postgres:16 — all 5 fixtures pass, stage 0/1/2 self-hosting clean. * ci: pg_config-first libpq detection in build-vendor.sh ubuntu/debian's libpq-dev installs libpq-fe.h to /usr/include/postgresql/, not /usr/include/, so the previous fallback (cc -xc -fsyntax-only against default include path) silently failed on linux. pg-bridge.o was never built, the verify-vendor step caught the missing object, and ci #514's build-linux-glibc job failed in 1m22s. new detection order: 1. pg_config --includedir — works on any platform with libpq dev headers 2. brew --prefix libpq — keg-only on macos, no pg_config in PATH 3. /usr/include/postgresql/libpq-fe.h — debian/ubuntu fallback 4. default cc include path — last resort also bumps the cache key (file content hash includes build-vendor.sh), which forces ci to re-run build-vendor instead of restoring a stale c_bridges/ from before libpq was installed. --------- Co-authored-by: cs01 <cs01@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What you get
A first-class PostgreSQL client for ChadScript, built on
libpq. Pool is the recommended entry point; Client is available for low-level lifecycle control. Parameterized queries with$1/$2placeholders are safe against SQL injection — libpq handles escaping.Why
Postgres is the most common server database, and until now ChadScript had no story for talking to it. You had to shell out to
psqlor write raw sockets. This closes that gap and lines up sqlite (ships today) + postgres (this PR) as the two supported databases, putting ChadScript on equal footing with Node for backend work that uses a real RDBMS.This is also the first stdlib module built with the
declare function cs_*+ prefix-detection pattern, which makes adding new C-backed modules basically trivial going forward (MySQL and Redis next).What's in the box
Pool— recommended primary API, lazy-connects on first queryClient— low-level explicit connect/end lifecycleQueryResultwithrowCount,numRows,numCols,fields, and accessorsgetValue,getInt,getFloat,getBool,getRow$1,$2, ... — builds libpqPQexecParamsvia a stateful param-builder pattern to avoid marshaling astring[]across the FFI boundarydocs/stdlib/postgres.md, listed indocs/stdlib/index.md@test-skip— they need a running postgres; CI service container is in a follow-up)Current limitations (documented)
getInt/getFloat/getBoolparse on access. No OID-based auto-typing — users know their schema, they call the right getter.Poolis a thin wrapper over a singleClient. No real connection reuse yet. Calls are sequential. Real pooling (multiple conns, queuing, limits) needs async, which is a follow-up.libpqcalls block. Real async (libuv integration) is a follow-up.NULL, which collides with legitimate empty text. Parallel null flags are a follow-up.LISTEN/NOTIFY, noCOPY, no streaming. The basics only.libpqinstall required. macOS:brew install libpq. Debian/Ubuntu:apt install libpq-dev. The build gracefully skipspg-bridge.oif headers are missing.How it works
New convention: any C bridge function declared with the
cs_pg_prefix auto-flips ausesPostgresflag in the codegen, and the linker picks up-lpq+pg-bridge.owithout any per-function wiring. Same pattern ascs_llvm_*/cs_lld_*. This is the pattern the future sqlite migration and MySQL/Redis stdlib modules should follow.Also fixed an ABI gotcha that would have silently bitten future bridges: C bridge functions called from ChadScript as
numbermust usedoublein C, notint. ARM64 returns ints inw0and doubles ind0— completely separate registers. Memory note saved atmemory/c-bridge-abi-double-rule.md.Test plan
npm run verify:quickgreen (tests + stage 1 self-hosting)podman run postgres:16— all 4 fixtures pass:postgres-connect.ts— connect + endpostgres-query-rowcount.ts— CREATE, INSERT, UPDATE, DELETE with expected rowCountspostgres-query-select.ts— SELECT 3 rows, column lookup by name, empty result setpostgres-pool.ts— Pool lazy-connect + CRUDpostgres-params-types.ts— parameterized queries,getInt/getFloat/getBool, SQL-injection-safety with a quoted value🤖 Generated with Claude Code