From 856048101bf4c5ab4a3497fdf3872426df9cc7ff Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 15:52:41 +0000 Subject: [PATCH 1/3] chore: add CI and make test targets Add a GitHub Actions workflow that runs the regression suite on PostgreSQL 14-18 against all three install paths: standalone build, pg_tle (built from a pinned commit), and filesystem CREATE EXTENSION in both the default and an explicit schema. Add make install/uninstall and test-local, test-tle, test-extension targets that recreate a scratch database per run. The test suite previously exited 0 even with failures; it now raises at the end when any assertion fails, and compares with IS NOT DISTINCT FROM so NULL results count as failures instead of being dropped. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_014JAaWhjV4FjNThbcAAWksS --- .github/workflows/test.yml | 73 ++++++++++++++++++++++++++++++++++++++ AGENTS.md | 18 +++++++--- Makefile | 48 ++++++++++++++++++++++++- README.md | 17 +++++++++ test_pgtoon.sql | 14 +++++++- 5 files changed, 163 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..983f7a9 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,73 @@ +name: test + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + name: PostgreSQL ${{ matrix.pg }} + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + pg: [14, 15, 16, 17, 18] + env: + PG_CONFIG: /usr/lib/postgresql/${{ matrix.pg }}/bin/pg_config + # Pinned pg_tle commit (main, supports PG 12-18; no tagged release yet + # covers 18). + PG_TLE_REF: 2f4b7b34ac3e65a4c4ec358765839d5f24a910bd + PGHOST: /var/run/postgresql + PGPORT: 5499 + PGDATABASE: postgres + steps: + - uses: actions/checkout@v4 + + - name: Install PostgreSQL ${{ matrix.pg }} + run: | + sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y + # Don't let the package auto-create a "main" cluster; we make our own. + echo 'create_main_cluster = false' | sudo tee -a /etc/postgresql-common/createcluster.conf + sudo apt-get install -y --no-install-recommends \ + postgresql-${{ matrix.pg }} postgresql-server-dev-${{ matrix.pg }} \ + flex bison libkrb5-dev + + - name: Build and install pg_tle + run: | + git init -q pg_tle + git -C pg_tle fetch -q --depth 1 https://github.com/aws/pg_tle "$PG_TLE_REF" + git -C pg_tle checkout -q FETCH_HEAD + make -C pg_tle PG_CONFIG="$PG_CONFIG" + sudo make -C pg_tle install PG_CONFIG="$PG_CONFIG" + + - name: Start cluster + run: | + sudo pg_createcluster ${{ matrix.pg }} ci -p "$PGPORT" --start \ + -o shared_preload_libraries=pg_tle + sudo -u postgres createuser -s "$USER" + + # pg_tle refuses to register an extension that also exists on the + # filesystem, so run it before `make install`. + - name: Test standalone build + run: make test-local + + - name: Test pg_tle install + run: make test-tle + + - name: Test filesystem extension + run: | + sudo make install PG_CONFIG="$PG_CONFIG" + make test-extension + + - name: Server log + if: failure() + run: sudo cat /var/log/postgresql/postgresql-${{ matrix.pg }}-ci.log diff --git a/AGENTS.md b/AGENTS.md index 4f0c760..2fcd39d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -19,7 +19,8 @@ designed for LLM prompt contexts. It conforms to TOON Specification v3.3. pgtoon--0.1.sql # Canonical extension source (contains @extschema@ markers) pgtoon.control # PostgreSQL extension metadata (relocatable=false) test_pgtoon.sql # Regression suite (67 assertions) -Makefile # Build targets: tle (default), local, clean, help +Makefile # Build: tle (default), local, install; test-*; clean, help +.github/workflows/test.yml # CI: all three install paths on PG 14-18 create_pgtle_scripts.sh # Vendored pg_tle helper (from github.com/aws/pg_tle) README.md # User-facing documentation AGENTS.md # This file @@ -90,7 +91,12 @@ cause a parse error. Always use one of the three paths above. ### Running tests ```sh -# Against a standalone install: +# Make targets (recreate $TESTDB, fail non-zero on any assertion failure): +make test # standalone build +make test-tle # pg_tle install; run BEFORE make install +make install && make test-extension + +# Or by hand, against a standalone install: make local psql -f pgtoon-local.sql psql -c "SET search_path = toon, pg_catalog, pg_temp" -f test_pgtoon.sql @@ -104,7 +110,8 @@ psql -f test_pgtoon.sql # functions are in public by default Tests use a temp table + `assert_toon(name, actual, expected)` helper. Output is a summary row: `passed | failed | total`. Any failures also print -the test name with expected vs actual values via `RAISE NOTICE`. +the test name with expected vs actual values via `RAISE NOTICE`, and a final +`DO` block raises an exception so psql exits non-zero (this is what CI keys on). ### Test requirements @@ -139,8 +146,9 @@ Imperative mood, max 50-char subject. Body explains what/why. ### Before committing -- Run `make local && psql -f pgtoon-local.sql && psql -f test_pgtoon.sql` (all tests pass) -- Ideally test `make tle` + `CREATE EXTENSION` on a real pg_tle install +- Run `make test` (all tests pass) +- Ideally also `make test-tle` and `make install && make test-extension`; + CI runs all three on PostgreSQL 14–18 for every push and PR ## TOON Spec Quick Reference (for encoders) diff --git a/Makefile b/Makefile index db84444..4481dda 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,13 @@ SRC = $(EXTENSION)--$(EXTVERSION).sql # Schema used by `make local` (the standalone, non-extension build). SCHEMA ?= toon -.PHONY: tle local clean help +PG_CONFIG ?= pg_config +PSQL ?= psql -X -v ON_ERROR_STOP=1 +# Scratch database used by the test targets; dropped and recreated each run. +TESTDB ?= pgtoon_test +EXTDIR = $(shell $(PG_CONFIG) --sharedir)/extension + +.PHONY: tle local install uninstall test test-local test-extension test-tle clean help # Default target: build the pg_tle installable script. tle: .pgtle-$(EXTENSION).sql @@ -33,6 +39,42 @@ $(EXTENSION)-local.sql: $(SRC) sed 's/@extschema@/$(SCHEMA)/g' $(SRC) ) > $@ @echo "Built $@ — load with: psql -f $@" +# Install the control file and SQL script for `CREATE EXTENSION pgtoon`. +install: + install -d '$(DESTDIR)$(EXTDIR)' + install -m 644 $(EXTENSION).control $(SRC) '$(DESTDIR)$(EXTDIR)/' + +uninstall: + rm -f '$(DESTDIR)$(EXTDIR)/$(EXTENSION).control' '$(DESTDIR)$(EXTDIR)/$(SRC)' + +# Test targets connect using the standard libpq env vars (PGHOST, PGUSER, ...). +# Each one recreates $(TESTDB) and runs test_pgtoon.sql against one install path. +define fresh_testdb + $(PSQL) -d postgres -c 'DROP DATABASE IF EXISTS $(TESTDB)' -c 'CREATE DATABASE $(TESTDB)' +endef + +test: test-local + +# Standalone build (sed-substituted schema). +test-local: local + $(fresh_testdb) + $(PSQL) -d $(TESTDB) -f $(EXTENSION)-local.sql + $(PSQL) -d $(TESTDB) -c 'SET search_path = $(SCHEMA), pg_catalog, pg_temp' -f test_pgtoon.sql + +# Filesystem extension (requires `make install`), default and explicit schema. +test-extension: + $(fresh_testdb) + $(PSQL) -d $(TESTDB) -c 'CREATE EXTENSION $(EXTENSION)' -f test_pgtoon.sql + $(fresh_testdb) + $(PSQL) -d $(TESTDB) -c 'CREATE SCHEMA ext' -c 'CREATE EXTENSION $(EXTENSION) SCHEMA ext' \ + -c 'SET search_path = ext, public' -f test_pgtoon.sql + +# pg_tle install (requires pg_tle in shared_preload_libraries). +test-tle: tle + $(fresh_testdb) + $(PSQL) -d $(TESTDB) -f .pgtle-$(EXTENSION).sql + $(PSQL) -d $(TESTDB) -c 'CREATE EXTENSION $(EXTENSION)' -f test_pgtoon.sql + clean: rm -f .pgtle-$(EXTENSION).sql $(EXTENSION)-local.sql @@ -40,4 +82,8 @@ help: @echo "Targets:" @echo " make tle - build pg_tle install script (.pgtle-$(EXTENSION).sql) [default]" @echo " make local - build standalone script ($(EXTENSION)-local.sql), SCHEMA=$(SCHEMA)" + @echo " make install - install control + SQL into \`$(PG_CONFIG) --sharedir\`/extension" + @echo " make test - run the suite against a standalone build (alias: test-local)" + @echo " make test-extension - run the suite via CREATE EXTENSION (needs make install)" + @echo " make test-tle - run the suite via pg_tle (needs pg_tle preloaded)" @echo " make clean - remove generated files" diff --git a/README.md b/README.md index 6bd61f7..6bca639 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,23 @@ psql -c "SET search_path = toon, pg_catalog, pg_temp" -f test_pgtoon.sql Or against a `CREATE EXTENSION` install, with the extension's schema on `search_path`. +The Makefile wraps each install path in a test target. Each one recreates a +scratch database (`TESTDB`, default `pgtoon_test`), connects using the usual +libpq environment variables (`PGHOST`, `PGPORT`, `PGUSER`), and exits non-zero +if any assertion fails: + +```sh +make test # standalone build (alias for test-local) +make test-tle # via pg_tle (pg_tle must be in shared_preload_libraries) +make install # copy control + SQL into `pg_config --sharedir`/extension +make test-extension # via CREATE EXTENSION, default schema and SCHEMA ext +``` + +`test-tle` fails if a filesystem copy is installed (pg_tle won't register an +extension that already exists on disk), so run it before `make install`. + +CI (`.github/workflows/test.yml`) runs all three on PostgreSQL 14–18. + The test suite validates key quoting, value quoting, object encoding, tabular array encoding, null handling, NaN/Infinity normalization, and delimiter variants. ## Future Work diff --git a/test_pgtoon.sql b/test_pgtoon.sql index 0d123bf..34bf682 100644 --- a/test_pgtoon.sql +++ b/test_pgtoon.sql @@ -21,7 +21,7 @@ CREATE TEMP TABLE test_results ( CREATE OR REPLACE FUNCTION assert_toon(test_name text, actual text, expected text) RETURNS void LANGUAGE plpgsql AS $$ BEGIN - INSERT INTO test_results VALUES (test_name, actual = expected, expected, actual); + INSERT INTO test_results VALUES (test_name, actual IS NOT DISTINCT FROM expected, expected, actual); IF actual IS DISTINCT FROM expected THEN RAISE NOTICE 'FAIL: % — expected [%], got [%]', test_name, expected, actual; END IF; @@ -337,6 +337,18 @@ FROM test_results WHERE NOT passed ORDER BY test_name; +-- Fail the run (non-zero psql exit under ON_ERROR_STOP) if any assertion failed +DO $$ +DECLARE + n_failed int; +BEGIN + SELECT count(*) INTO n_failed FROM test_results WHERE NOT passed; + IF n_failed > 0 THEN + RAISE EXCEPTION '% test(s) failed', n_failed; + END IF; +END; +$$; + -- Cleanup DROP TABLE test_results; DROP TABLE users; From e8ac22ce61d5792f63adaa201d45aabf7a161d69 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 26 Sep 2026 13:19:56 +0000 Subject: [PATCH 2/3] docs: set PG 14+ floor and sync AGENTS.md Declare PostgreSQL 14 as the minimum supported version (12 and 13 are past upstream EOL and not covered by CI) in AGENTS.md and README.md, and document how to maintain the CI version matrix and pg_tle pin. Bring AGENTS.md in line with the code: row_to_toon takes anyelement, list the toon_agg state/final helper functions, and point the new-function checklist at the make test targets. Fix the workflow push trigger to use the default branch, master. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_014JAaWhjV4FjNThbcAAWksS --- .github/workflows/test.yml | 2 +- AGENTS.md | 22 ++++++++++++++++++---- README.md | 2 +- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 983f7a9..68a1170 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,7 +2,7 @@ name: test on: push: - branches: [main] + branches: [master] pull_request: workflow_dispatch: diff --git a/AGENTS.md b/AGENTS.md index 2fcd39d..4f86e5f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -33,7 +33,7 @@ AGENTS.md # This file | Function | Purpose | |----------|---------| | `to_toon(anyelement, delim)` | Generic encoder: scalars, arrays, records → TOON | -| `row_to_toon(record, delim)` | Record → TOON object (`key: value` lines) | +| `row_to_toon(anyelement, delim)` | Record → TOON object (`key: value` lines) | | `toon_agg(anyelement [, delim])` | Aggregate → TOON tabular array with header + rows | ### Internal helpers (not intended for direct use) @@ -44,6 +44,9 @@ AGENTS.md # This file | `toon_quote_key(text)` | §7.3 key quoting | | `toon_quote_value(text, delim)` | §7.2 value quoting | | `toon_encode_field(raw_json, text_val, delim)` | Type-aware field encoding | +| `toon_agg_sfunc(state, rec, delim)` | `toon_agg` state transition | +| `toon_agg_sfunc_default(state, rec)` | State transition for the 1-arg `toon_agg` (comma delimiter) | +| `toon_agg_ffunc(state)` | `toon_agg` final function (header + rows) | ### Type @@ -62,6 +65,17 @@ AGENTS.md # This file (→ null per §3) from the string literal "NaN" (→ normal string). PG wraps float NaN as a JSON string `"NaN"`, making them otherwise indistinguishable. +## Supported Versions + +PostgreSQL 14 and newer. Versions 12 and 13 are past upstream end-of-life and +are not tested; don't add workarounds for them. When a new PostgreSQL major is +released, add it to the matrix in `.github/workflows/test.yml`; when one goes +EOL, drop it from the matrix and bump the floor here and in README.md. + +CI builds pg_tle from a pinned commit (`PG_TLE_REF` in the workflow) because +no tagged pg_tle release supports PG 18 yet. Switch to a release tag once one +does. + ## Security Model Every function has: @@ -115,7 +129,7 @@ the test name with expected vs actual values via `RAISE NOTICE`, and a final ### Test requirements -- PostgreSQL 12+ (tested on 16 and 18) +- PostgreSQL 14+ (the supported floor; CI runs 14, 15, 16, 17 and 18) - The extension must be installed before running tests - Tests are self-contained (CREATE/DROP their own temp tables) @@ -141,8 +155,8 @@ Imperative mood, max 50-char subject. Body explains what/why. 2. Add `SET search_path = pg_catalog, pg_temp` 3. Qualify any calls to other pgtoon functions with `@extschema@.` 4. Add tests in `test_pgtoon.sql` -5. Verify all three install paths work (`make tle`, `make local`, filesystem) -6. Run the regression suite: expect 0 failures +5. Run the suite on all three install paths: `make test`, `make test-tle`, + `make install && make test-extension` — expect 0 failures ### Before committing diff --git a/README.md b/README.md index 6bca639..2eb1c5b 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ vs. the equivalent JSON (96 bytes larger): ## Installation -Requires PostgreSQL 12+. +Requires PostgreSQL 14+. The canonical source `pgtoon--0.1.sql` contains `@extschema@` markers and a locked `search_path`, so it is installed as a PostgreSQL **extension** (the From 11853c8d0caec6837afdb550cafbb502471b2e38 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 26 Sep 2026 13:21:26 +0000 Subject: [PATCH 3/3] fix(ci): pass PGPORT to createuser under sudo sudo resets the environment, so createuser ignored PGPORT=5499 and tried the default 5432 socket, failing every matrix job at cluster setup. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_014JAaWhjV4FjNThbcAAWksS --- .github/workflows/test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 68a1170..0348647 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -53,7 +53,8 @@ jobs: run: | sudo pg_createcluster ${{ matrix.pg }} ci -p "$PGPORT" --start \ -o shared_preload_libraries=pg_tle - sudo -u postgres createuser -s "$USER" + # sudo resets the environment, so PGPORT has to be passed explicitly. + sudo -u postgres createuser -p "$PGPORT" -s "$USER" # pg_tle refuses to register an extension that also exists on the # filesystem, so run it before `make install`.