diff --git a/README.asc b/README.asc index 5fdc15c..ba5e14a 100644 --- a/README.asc +++ b/README.asc @@ -39,15 +39,19 @@ include pgxntool/base.mk == make targets These are the make targets that are provided by base.mk -NOTE: all the targets normally provided by Postgres http://www.postgresql.org/docs/current/static/extend-pgxs.html[PGXS] still work. +NOTE: all the targets normally provided by Postgres http://www.postgresql.org/docs/current/static/extend-pgxs.html[PGXS] still work. If you need to skip PGXS being included entirely (for advanced/non-standard setups), set `PGXNTOOL_NO_PGXS_INCLUDE` in your Makefile before `include pgxntool/base.mk`. === html This will build any .html files that can be created. See <<_Document_Handling>>. === test -Runs unit tests via the PGXS `installcheck` target. Unlike a simple `make installcheck` though, the `test` rule has the following prerequisites: clean testdeps install installcheck. All of those are PGXS rules, except for `testdeps`. +Runs your extension's test suite: installs the extension and runs it through PGXS's `installcheck`, first pulling in anything you've hooked into <<_testdeps>> and, if enabled, sanity-checking your test SQL via <<_test_build>>. -NOTE: While you can still run `make installcheck` or any other valid PGXS make target directly, it's recommended to use `make test` when using pgxntool. The `test` target ensures clean builds, proper test isolation, and correct dependency installation. +Whether `test-build` runs is controlled by the `PGXNTOOL_ENABLE_TEST_BUILD` variable — see <<_test_build>> for what it does and how to turn it on/off. + +NOTE: `test` intentionally does *not* depend on `clean` — that caused problems with incremental/watch-based builds. If your tests need a clean build to pass, that's a sign of a missing dependency elsewhere rather than something to fix by adding `clean` back. + +NOTE: While you can still run `make installcheck` or any other valid PGXS make target directly, it's recommended to use `make test` when using pgxntool. The `test` target ensures proper test isolation and correct dependency installation. === test-build Validates that extension SQL files are syntactically correct before running the full test suite. This feature runs SQL files from `test/build/` through `pg_regress`, providing better error messages than `CREATE EXTENSION` failures when there are syntax errors in your extension code. @@ -227,15 +231,20 @@ IMPORTANT: *`make results` requires manual verification first*. The correct work Never run `make results` without first verifying the test changes are correct. The `results` target copies files from `test/results/` to `test/expected/`, so running it blindly will make incorrect output become the new expected behavior. ==== verify-results safeguard -By default, `make results` will refuse to run if `test/results/regression.diffs` exists (indicating failing tests). This prevents accidentally updating expected files with incorrect output. +By default, `make results` will refuse to run if your tests are failing, so you can't accidentally promote incorrect output into the new expected results. Which failures it looks for is controlled by `PGXNTOOL_VERIFY_RESULTS_MODE`: + +`pgtap` (the default):: Scans `test/results/*.out` for pgTAP `not ok` lines (ignoring `# TODO` items) and plan-count mismatches, then also checks `test/results/regression.diffs` for any output mismatch pgTAP itself wouldn't catch. +`diffs`:: Only checks whether `test/results/regression.diffs` exists. + +If tests are failing, `make results` prints an explanation of what failed and exits without touching `test/expected/`; fix the failures, then re-run `make results`. -If tests are failing, you'll see: +To select a mode explicitly: ---- -ERROR: Tests are failing. Cannot run 'make results'. -Fix test failures first, then run 'make results'. +# In your Makefile +PGXNTOOL_VERIFY_RESULTS_MODE = diffs # or pgtap (the default) ---- -To disable this safeguard (not recommended): +To disable this safeguard entirely (not recommended): ---- # In your Makefile PGXNTOOL_ENABLE_VERIFY_RESULTS = no @@ -245,9 +254,9 @@ make PGXNTOOL_ENABLE_VERIFY_RESULTS=no results ---- === tag -`make tag` will create an annotated git tag for the current version of your extension, as determined by the META.json file (generated from `META.in.json` — see <<_pgxn_distributions_vs_extensions>>), and push it to `origin`. The reason to do this is so you can always refer to the exact code that went into a released version. +`make tag` will create a git tag for the current version of your extension, as determined by the META.json file (generated from `META.in.json` — see <<_pgxn_distributions_vs_extensions>>), and push it to `origin`. The reason to do this is so you can always refer to the exact code that went into a released version. -If there's already a tag for the current version that probably means you forgot to update META.json, so you'll get an error. If you're certain you want to over-write the tag, you can do `make forcetag`, which removes the existing tag (via `make rmtag`) and creates a new one. +If a tag for the current version already exists and points at your current commit, `make tag` does nothing — this makes it safe for other targets (like `dist`) to depend on `tag` without worrying about re-running it. If the existing tag points at a *different* commit — meaning you likely forgot to bump the version — you'll get an error. If you're certain you want to over-write the tag, you can do `make forcetag`, which removes the existing tag (via `make rmtag`) and creates a new one. WARNING: You will be very unhappy if you forget to update the .control file for your extension! There is an https://github.com/decibel/pgxntool/issues/1[open issue] to improve this. @@ -256,15 +265,27 @@ WARNING: You will be very unhappy if you forget to update the .control file for NOTE: Part of the `clean` recipe is cleaning up these .zip files. If you accidentally clean before uploading, just run `make dist-only`. +WARNING: If your project has a `.gitattributes` file, `make dist`/`make dist-only` will refuse to run unless it's committed to git, and will tell you so. This is because `git archive` (which builds the .zip) only honors `export-ignore` attributes on *committed* files — an uncommitted `.gitattributes` would silently have no effect on the archive contents, which could leak files into your PGXN distribution that you meant to exclude. + +`make forcedist` is a shortcut for `forcetag dist`: it force-recreates the tag (see `forcetag` above) before rebuilding the distribution .zip. + === pgxntool-sync This rule will pull down the latest released version of PGXNtool via `git subtree pull` and then reconcile the files `setup.sh` copied into your project (`.gitignore`, `test/deps.sql`) with a 3-way merge. NOTE: Your repository must be clean (no modified files) in order to run this. Running this command will produce a git commit of the merge. +NOTE: `git subtree pull` copies pgxntool's entire tree into your project, including dev-only directories (`pgxntool/.github`, `pgxntool/.claude`) that don't belong in a project that merely embeds pgxntool. Every sync automatically removes these two directories from `pgxntool/` afterward — this is expected, not a bug, and doesn't affect anything else in your project. + TIP: The actual work is done by `pgxntool/pgxntool-sync.sh`, so you can run it directly (`pgxntool/pgxntool-sync.sh`) if you'd rather not go through `make`. It optionally takes `` and `` arguments to pull from somewhere other than the default. TIP: There is also a `pgxntool-sync-%` rule if you need to do more advanced things. `make pgxntool-sync-` pulls from the ` ` defined by the `pgxntool-sync-` make variable. +=== list +`make list` prints every make target defined in your project — including ones provided by PGXS and pgxntool, not just ones you wrote — one per line. Useful for discovering what's available without reading through the Makefile. + +=== print-% +`make print-VARNAME` prints the current value (and origin) of any make variable, e.g. `make print-PGXNVERSION`. Useful for debugging why a variable isn't set to what you expect. + === distclean `make distclean` removes generated configuration files (`META.json` — generated from `META.in.json` — plus `meta.mk` and `control.mk`) that survive a normal `make clean`. @@ -293,7 +314,7 @@ make check-pgtle === run-pgtle Registers all extensions with pg_tle by executing the generated pg_tle registration SQL files in a PostgreSQL database. This target: -- Requires pg_tle extension to be installed (checked via `check-pgtle`) +- Requires pg_tle to be installed in the target database. This isn't a make-level dependency on `check-pgtle` — `pgtle.sh` checks it itself when it runs, and will tell you to run `make check-pgtle` if pg_tle isn't there - Uses `pgtle.sh` to determine which version range directory to use based on the installed pg_tle version - Runs all generated SQL files via `psql` to register your extensions with pg_tle @@ -459,9 +480,8 @@ PGXNtool replaces each `$(ASCIIDOC_EXTS)` in `$(ASCIIDOC_FILES)` with `html`. The result is appended to `ASCIIDOC_HTML` using `+=`. === Document Rules -If Asciidoc is found (or `$(ASCIIDOC)` is set), the `html` rule will be added as a prerequisite to the `install` and `installchec` rules. -That will ensure that docs are generated for install and test, but only if Asciidoc is available. -The `dist` rule will always depend on `html` though, to ensure html files are up-to-date before creating a distribution. +If Asciidoc is found (or `$(ASCIIDOC)` is set), `html` is added as a dependency of the `all` target, which both `install` and `installcheck` depend on — so docs get built for install and test, but only if Asciidoc is available. +The `dist` rule always depends on `html` directly, regardless of whether Asciidoc was found, to ensure html files are up-to-date before creating a distribution. The `html` rule simply depends on `$(ASCIIDOC_HTML). This rule is always present. @@ -474,11 +494,11 @@ These rules are generated from `ASCIIDOC_template`: ---- define ASCIIDOC_template %.html: %.$(1) # <1> -ifndef ASCIIDOC +ifeq (,$(strip $(ASCIIDOC))) $$(warning Could not find "asciidoc" or "asciidoctor". Add one of them to your PATH,) $$(warning or set ASCIIDOC to the correct location.) $$(error Could not build %$$@) -endif # ifndef ASCIIDOC +endif # ifeq ASCIIDOC $$(ASCIIDOC) $$(ASCIIDOC_FLAGS) $$< endef # define ASCIIDOC_template ---- @@ -488,6 +508,8 @@ These rules will *always* exist, even if `$(ASCIIDOC)` isn't set (ie: if Asciido These rules will throw an error if they are run if `$(ASCIIDOC)` isn't defined. On a normal user system that should never happen, because the `html` rule won't be included in `install` or `installcheck`. +`make docclean` removes all generated HTML files (`$(DOCS_HTML)`). It's separate from `make clean`/`make distclean`, so you can clear out generated docs without touching your build artifacts. + === The DOCS variable This variable has special meaning to PGXS. See the Postgres documentation for full details. @@ -565,6 +587,15 @@ myproject/ └── ext2.sql ---- +==== Direct pgtle.sh Invocation + +`pgtle`, `check-pgtle`, and `run-pgtle` are thin make wrappers around `pgxntool/pgtle.sh`. For more targeted operations you can run it directly: + +- `pgtle.sh --extension NAME [--pgtle-version VERSION]` — generate registration SQL for one extension (what `make pgtle` wraps). +- `pgtle.sh --get-version` — print the pg_tle version installed in the target database (empty if not installed). +- `pgtle.sh --get-dir VERSION` — print which version-range directory (see <<_version_groupings>>) a given pg_tle version maps to, without generating anything. +- `pgtle.sh --run` — execute the generated registration SQL against the target database (what `make run-pgtle` wraps). + === How It Works `make pgtle` does the following: diff --git a/README.html b/README.html index 431358f..12d4a03 100644 --- a/README.html +++ b/README.html @@ -4,7 +4,7 @@ - + PGXNtool @@ -141,7 +141,7 @@ #content::before{content:none} #header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0} #header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf} -#header>h1:only-child{border-bottom:1px solid #dddddf;padding-bottom:8px} +#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px} #header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:flex;flex-flow:row wrap} #header .details span:first-child{margin-left:-.125em} #header .details span.email a{color:rgba(0,0,0,.85)} @@ -163,7 +163,6 @@ #toctitle{color:#7a2518;font-size:1.2em} @media screen and (min-width:768px){#toctitle{font-size:1.375em} body.toc2{padding-left:15em;padding-right:0} -body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px} #toc.toc2{margin-top:0!important;background:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto} #toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em} #toc.toc2>ul{font-size:.9em;margin-bottom:0} @@ -329,7 +328,7 @@ a.image object{pointer-events:none} sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super} sup.footnote a,sup.footnoteref a{text-decoration:none} -sup.footnote a:active,sup.footnoteref a:active,#footnotes .footnote a:first-of-type:active{text-decoration:underline} +sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline} #footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em} #footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0} #footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em} @@ -458,20 +457,23 @@

PGXNtool

  • 4.7. tag
  • 4.8. dist
  • 4.9. pgxntool-sync
  • -
  • 4.10. distclean
  • -
  • 4.11. pgtle
  • -
  • 4.12. check-pgtle
  • -
  • 4.13. run-pgtle
  • +
  • 4.10. list
  • +
  • 4.11. print-%
  • +
  • 4.12. distclean
  • +
  • 4.13. pgtle
  • +
  • 4.14. check-pgtle
  • +
  • 4.15. run-pgtle
  • 5. Version-Specific SQL Files
  • 6. Document Handling @@ -502,7 +504,7 @@

    PGXNtool

    PGXNtool is meant to make developing new Postgres extensions for PGXN easier.

    -

    Currently, it consists a base Makefile that you can include instead of writing your own, a template META.json, and some test framework. More features will be added over time.

    +

    Currently, it consists a base Makefile that you can include instead of writing your own, a template META.in.json (from which META.json is generated — you edit the former, never the latter directly), and some test framework. More features will be added over time.

    If you find any bugs or have ideas for improvements, please open an issue.

    @@ -575,7 +577,7 @@

    Note

    -all the targets normally provided by Postgres PGXS still work. +all the targets normally provided by Postgres PGXS still work. If you need to skip PGXS being included entirely (for advanced/non-standard setups), set PGXNTOOL_NO_PGXS_INCLUDE in your Makefile before include pgxntool/base.mk. @@ -589,7 +591,22 @@

    4.2. test

    -

    Runs unit tests via the PGXS installcheck target. Unlike a simple make installcheck though, the test rule has the following prerequisites: clean testdeps install installcheck. All of those are PGXS rules, except for testdeps.

    +

    Runs your extension’s test suite: installs the extension and runs it through PGXS’s installcheck, first pulling in anything you’ve hooked into testdeps and, if enabled, sanity-checking your test SQL via test-build.

    +
    +
    +

    Whether test-build runs is controlled by the PGXNTOOL_ENABLE_TEST_BUILD variable — see test-build for what it does and how to turn it on/off.

    +
    +
    + + + + + +
    +
    Note
    +
    +test intentionally does not depend on clean — that caused problems with incremental/watch-based builds. If your tests need a clean build to pass, that’s a sign of a missing dependency elsewhere rather than something to fix by adding clean back. +
    @@ -598,7 +615,7 @@

    Note

    -While you can still run make installcheck or any other valid PGXS make target directly, it’s recommended to use make test when using pgxntool. The test target ensures clean builds, proper test isolation, and correct dependency installation. +While you can still run make installcheck or any other valid PGXS make target directly, it’s recommended to use make test when using pgxntool. The test target ensures proper test isolation and correct dependency installation.
    @@ -786,6 +803,81 @@

    Key detail: Install files and regular tests run in a single pg_regress invocation. This means the database is NOT dropped between install and test phases — state created by install files persists into the main test suite.

    +
    +

    4.4.1. Update & Upgrade (U&U) Testing

    +
    +

    Beyond validating a plain install, it’s worth testing that your extension behaves correctly across the two transitions every extension with more than one release eventually goes through:

    +
    +
    +
      +
    • +

      Update: ALTER EXTENSION ext UPDATE, moving to a newer extension version on the same PostgreSQL version.

      +
    • +
    • +

      Upgrade: pg_upgrade, moving to a newer PostgreSQL major version while carrying the extension’s on-disk catalog state along with it.

      +
    • +
    +
    +
    +

    Both catch bugs that a simple "did the script run without error" check misses — wrong mappings, missing `REVOKE`s, objects that drifted from the base SQL, or catalog state that doesn’t survive a binary upgrade cleanly. This isn’t a niche concern that only applies if your extension does something unusual (adds enum types, custom operators, etc.) — it applies to any extension that will ever be updated or upgraded in place, which in practice is every extension.

    +
    +
    +

    test/install is the recommended place to build this, because its "load once, committed, before the suite" mechanic (described above) is exactly what update/upgrade testing needs.

    +
    +
    +

    The pattern: load the extension exactly once, committed, from a test/install/*.sql file, in one of several modes selected by a custom backend setting that your Makefile sets via PGOPTIONS (e.g. -c myext.test_load_mode=$(TEST_LOAD_SOURCE)) and that test/install reads with current_setting('myext.test_load_mode'):

    +
    +
    +
      +
    • +

      fresh: CREATE EXTENSION ext;

      +
    • +
    • +

      update: CREATE EXTENSION ext VERSION 'oldest_supported'; then ALTER EXTENSION ext UPDATE; (to the current default_version, or an explicit intermediate version)

      +
    • +
    • +

      existing: the extension is already installed — by a real pg_upgrade, or an update performed outside the suite — and test/install only asserts it’s present at the expected version, without dropping or touching it

      +
    • +
    +
    +
    +

    The same test/sql/ suite and the *same test/expected/ output then run unchanged against every mode. Identical expected output passing in *update mode is the update-equivalence assertion — if the updated database behaves any differently than a fresh install, some test will diff. Running that same suite in existing mode against a database that just went through a real pg_upgrade gives you upgrade testing using the same test files, no separate suite required.

    +
    +
    +

    If you adopt this pattern, test/deps.sql should no longer run CREATE EXTENSION itself for the per-test setup — the committed install performed by test/install persists into every (rolled-back) test file, exactly as described above.

    +
    +
    +

    Why the install step must be committed, not run per-test in test/deps.sql: every test/sql/ file runs inside BEGIN; …​ ROLLBACK; — a pgxntool convention (test/pgxntool/setup.sql opens the transaction; since it’s never committed, it rolls back when the session ends). Running the update inside that transaction means the suite never actually exercises a committed update — which doesn’t match production, where ALTER EXTENSION UPDATE commits before anything else touches the database, and it hides bugs that only show up once the update is durably committed. (Modifying an enum is one example of how this can turn into an outright failure rather than just a hidden bug — but it’s just an example; the reason to commit first holds regardless of what the update script does.)

    +
    +
    +

    As a bonus, sharing one committed install across every test file also removes the per-test re-install cost.

    +
    +
    + + + + + +
    +
    Note
    +
    +This pattern isn’t (yet) native to pgxntool — you wire up the mode selection and PGOPTIONS plumbing yourself in test/install and your Makefile. First-class support (a standard mode-switching toggle, and scaffolding for real pg_upgrade testing) is planned; see issue #42. +
    +
    +
    +

    Working examples (specific files, not full PRs — both extensions' histories include plenty of unrelated changes):

    +
    +
    +
      +
    • +

      cat_tools test/install/load.sql and the TEST_LOAD_SOURCE block in its Makefile — the fresh/update/existing pattern above, including the PGOPTIONS wiring. This is a reasonably complete example, and somewhat more elaborate than the minimum needed to get started.

      +
    • +
    • +

      pg_count_nulls test/sql/extension_tests.sql additionally demonstrates testing an extension installed into a non-default schema. It’s a useful reference for that specific problem, but it also defines its assertions as plpgsql functions rather than plain pgTAP calls — a pattern that adds complexity of its own and isn’t recommended as a model for U&U testing itself.

      +
    • +
    +
    +

    4.5. testdeps

    @@ -862,19 +954,34 @@

    4.6.1. verify-results safeguard

    -

    By default, make results will refuse to run if test/results/regression.diffs exists (indicating failing tests). This prevents accidentally updating expected files with incorrect output.

    +

    By default, make results will refuse to run if your tests are failing, so you can’t accidentally promote incorrect output into the new expected results. Which failures it looks for is controlled by PGXNTOOL_VERIFY_RESULTS_MODE:

    +
    +
    +
    +
    pgtap (the default)
    +
    +

    Scans test/results/*.out for pgTAP not ok lines (ignoring # TODO items) and plan-count mismatches, then also checks test/results/regression.diffs for any output mismatch pgTAP itself wouldn’t catch.

    +
    +
    diffs
    +
    +

    Only checks whether test/results/regression.diffs exists.

    +
    +
    +
    +
    +

    If tests are failing, make results prints an explanation of what failed and exits without touching test/expected/; fix the failures, then re-run make results.

    -

    If tests are failing, you’ll see:

    +

    To select a mode explicitly:

    -
    ERROR: Tests are failing. Cannot run 'make results'.
    -Fix test failures first, then run 'make results'.
    +
    # In your Makefile
    +PGXNTOOL_VERIFY_RESULTS_MODE = diffs  # or pgtap (the default)
    -

    To disable this safeguard (not recommended):

    +

    To disable this safeguard entirely (not recommended):

    @@ -890,10 +997,10 @@

    4.7. tag

    -

    make tag will create a git branch for the current version of your extension, as determined by the META.json file. The reason to do this is so you can always refer to the exact code that went into a released version.

    +

    make tag will create a git tag for the current version of your extension, as determined by the META.json file (generated from META.in.json — see PGXN Distributions vs. Extensions), and push it to origin. The reason to do this is so you can always refer to the exact code that went into a released version.

    -

    If there’s already a tag for the current version that probably means you forgot to update META.json, so you’ll get an error. If you’re certain you want to over-write the tag, you can do make forcetag, which removes the existing tag (via make rmtag) and creates a new one.

    +

    If a tag for the current version already exists and points at your current commit, make tag does nothing — this makes it safe for other targets (like dist) to depend on tag without worrying about re-running it. If the existing tag points at a different commit — meaning you likely forgot to bump the version — you’ll get an error. If you’re certain you want to over-write the tag, you can do make forcetag, which removes the existing tag (via make rmtag) and creates a new one.

    @@ -911,7 +1018,7 @@

    4.

    4.8. dist

    -

    make dist will create a .zip file for your current version that you can upload to PGXN. The file is named after the PGXN name and version (the top-level "name" and "version" attributes in META.json). The .zip file is placed in the parent directory so as not to clutter up your git repo.

    +

    make dist will create a .zip file for your current version that you can upload to PGXN. It first runs tag (creating/pushing the version’s git tag as described above if needed), then uses git archive at that tag to build the .zip file, so the archive always matches the exact tagged commit. The file is named after the PGXN name and version (the top-level "name" and "version" attributes in META.json, generated from META.in.json). The .zip file is placed in the parent directory so as not to clutter up your git repo.

    @@ -925,6 +1032,21 @@

    +
    + + + + + +
    +
    Warning
    +
    +If your project has a .gitattributes file, make dist/make dist-only will refuse to run unless it’s committed to git, and will tell you so. This is because git archive (which builds the .zip) only honors export-ignore attributes on committed files — an uncommitted .gitattributes would silently have no effect on the archive contents, which could leak files into your PGXN distribution that you meant to exclude. +
    +
    +
    +

    make forcedist is a shortcut for forcetag dist: it force-recreates the tag (see forcetag above) before rebuilding the distribution .zip.

    +

    @@ -969,9 +1103,21 @@

    -

    4.10. distclean

    +

    4.10. list

    +
    +

    make list prints every make target defined in your project — including ones provided by PGXS and pgxntool, not just ones you wrote — one per line. Useful for discovering what’s available without reading through the Makefile.

    +
    + +
    +

    4.11. print-%

    +
    +

    make print-VARNAME prints the current value (and origin) of any make variable, e.g. make print-PGXNVERSION. Useful for debugging why a variable isn’t set to what you expect.

    +
    +
    +
    +

    4.12. distclean

    -

    make distclean removes generated configuration files (META.json, meta.mk, control.mk) that survive a normal make clean.

    +

    make distclean removes generated configuration files (META.json — generated from META.in.json — plus meta.mk and control.mk) that survive a normal make clean.

    @@ -995,7 +1141,7 @@

    -

    4.11. pgtle

    +

    4.13. pgtle

    Generates pg_tle (Trusted Language Extensions) registration SQL files for deploying extensions in managed environments like AWS RDS/Aurora. See [_pg_tle_Support] for complete documentation.

    @@ -1004,7 +1150,7 @@

    -

    4.12. check-pgtle

    +

    4.14. check-pgtle

    Checks if pg_tle is installed and reports the version. This target: - Reports the version from pg_extension if CREATE EXTENSION pg_tle has been run in the database @@ -1020,10 +1166,10 @@

    -

    4.13. run-pgtle

    +

    4.15. run-pgtle

    Registers all extensions with pg_tle by executing the generated pg_tle registration SQL files in a PostgreSQL database. This target: -- Requires pg_tle extension to be installed (checked via check-pgtle) +- Requires pg_tle to be installed in the target database. This isn’t a make-level dependency on check-pgtlepgtle.sh checks it itself when it runs, and will tell you to run make check-pgtle if pg_tle isn’t there - Uses pgtle.sh to determine which version range directory to use based on the installed pg_tle version - Runs all generated SQL files via psql to register your extensions with pg_tle

    @@ -1065,14 +1211,36 @@

    -

    5.1. How Version Files Are Generated

    +

    5.1. PGXN Distributions vs. Extensions

    +
    +

    PGXN distinguishes two things it’s easy to conflate: a distribution and an extension. Per the PGXN meta spec, a distribution is "a collection of extensions, source code, utilities, tests, and/or documents that are distributed together" — the thing you release and upload to PGXN, identified by the top-level name/version in META.json (generated from META.in.json — you edit the latter, never META.json directly). An extension is the individual thing installed via CREATE EXTENSION, described by its own .control file. A single distribution routinely provides more than one extension — META.in.json’s `provides map lists each one; the pgTAP distribution, for example, provides both the pgtap and schematap extensions from one release.

    +
    +
    +

    This split matters for versioning, and it’s where pgxntool has a real gap: META.json’s top-level `version is the distribution’s release version (used by make tag and make dist — it’s the git tag name and the .zip filename). Each extension’s own version, though, ends up tracked in two separate places that pgxntool does not keep in sync for you:

    +
    +
    +
      +
    • +

      the extension’s .control file (default_version) — what PostgreSQL and pgxntool’s own build actually use; see What Controls the Version Number below

      +
    • +
    • +

      that extension’s entry under META.in.json’s `provides map (provides.{extension}.version) — which the PGXN meta spec defines as "a Version for the extension" in its own right, not the distribution’s version, and which is what PGXN’s public index reads

      +
    • +
    +
    +
    +

    Nothing generates one from the other or checks that they still agree, so it’s easy to bump one and forget the other; issue #47 tracks fixing that.

    +
    +
    +
    +

    5.2. How Version Files Are Generated

    When you run make (or make all), PGXNtool:

    1. -

      Reads your META.json file to determine the extension version from provides.{extension}.version

      +

      Reads each extension’s own .control file to determine its version from default_version (via control.mk.sh)

    2. Generates a Makefile rule that copies your base SQL file (sql/{extension}.sql) to the version-specific file (sql/{extension}--{version}.sql)

      @@ -1083,16 +1251,11 @@

      -

      For example, if your META.json contains:

      +

      For example, if your myext.control contains:

    -
    "provides": {
    -  "myext": {
    -    "version": "1.2.3",
    -    ...
    -  }
    -}
    +
    default_version = '1.2.3'
    @@ -1100,21 +1263,22 @@

    -

    5.2. What Controls the Version Number

    +

    5.3. What Controls the Version Number

    -

    The version number comes from META.jsonprovides.{extension}.version, not from your .control file’s default_version field. The .control file’s default_version is used by PostgreSQL to determine which version to install by default, but the actual version-specific file that gets generated is determined by what’s in META.json.

    +

    The version number comes from each extension’s own .control file → default_version, not from META.json. PostgreSQL itself uses default_version to pick which versioned SQL file to load, so control.mk.sh parses the .control file(s) directly to keep the generated filename in sync with what PostgreSQL will actually use. META.in.json has its own, separate provides.{extension}.version for that same extension (see PGXN Distributions vs. Extensions above) — pgxntool never reads it when generating SQL files, and nothing keeps it in sync with the .control file’s default_version.

    -

    To change the version of your extension: -1. Update provides.{extension}.version in META.json +

    To change the version of one of your extensions: +1. Update default_version in that extension’s .control file 2. Run make to regenerate the version-specific file -3. Update default_version in your .control file to match (if needed)

    +3. Update that extension’s provides.{extension}.version in META.in.json to match by hand, and regenerate META.json (make META.json) — pgxntool won’t do this for you +4. Update META.in.json’s top-level `version too if you’re also cutting a new distribution release

    -

    5.3. Committing Version Files

    +

    5.4. Committing Version Files

    -

    Version-specific SQL files are now treated as permanent files that should be committed to your repository. This makes it much easier to test updates to extensions, as you can see exactly what SQL was included in each version.

    +

    Version-specific SQL files are treated as permanent files that should be committed to your repository by default. This makes it much easier to test updates to extensions, as you can see exactly what SQL was included in each version.

    @@ -1128,9 +1292,82 @@

    +

    5.4.1. Why Commit Them: Update Testing

    +
    +

    The primary value of committing a version’s install script is update testing: with the file in place, you can install that exact version (CREATE EXTENSION ext VERSION 'x.y.z'), run ALTER EXTENSION ext UPDATE, and verify the update path actually works. In principle you would only ever need the very first committed version’s install script — every later version is reachable by chaining upgrade scripts from it. See test/install for how to wire this into your test suite.

    +
    +
    +

    In practice, you should keep most old versions tracked rather than relying purely on that chain, because you cannot predict when a new major PostgreSQL version will break the ability to install an older extension version — a system catalog column changes type, a SELECT * over a catalog starts failing, and so on. Committing the old version’s install script lets CI catch that regression directly, by attempting to install that exact historical version against the new PostgreSQL release. For any non-trivial extension, most old versions should stay tracked for this reason alone.

    +
    + +
    +

    5.4.2. When It’s OK to Skip a Version

    +
    +

    For a minor version change that doesn’t meaningfully alter the extension (e.g. a small bug fix), it’s unlikely to straddle a PostgreSQL supported-version boundary, so there’s much less test-coverage value in committing that specific version’s generated install script. For large extensions, deliberately not committing some of these individual version files is worth doing to keep repository size down — the base sql/{extension}.sql still fully captures the change in git history, and the install script itself is trivially regenerated from that base file at build time.

    +
    +
    +
    +

    5.4.3. Don’t .gitignore a Skipped Version — rm It Once

    +
    +

    When you decide not to track a particular version’s generated install script, do not add a .gitignore entry for it.

    +
    +
    +

    make only ever generates the current version’s file, stamped from the base source according to that extension’s .control file default_version. The moment you bump the version, make starts generating the new current version’s file and stops touching the old one — the old file left behind in your working tree is a stale, one-off artifact, not something make keeps recreating on every build.

    +
    +
    +

    Because it’s only ever transiently present, the correct cleanup is a single rm sql/{extension}--{old-version}.sql right after the version bump — not a permanent .gitignore rule. A per-version .gitignore line would be perpetual clutter added on every release, for a file that’s only ever present for one build cycle. A broad glob (e.g. sql/--.sql) is wrong for the same reason it’s wrong below in Alternative: Ignoring All Version Files: it would also hide the prior-version install scripts and upgrade scripts (sql/{extension}--{a}--{b}.sql) that you do want tracked and visible in git status.

    +
    +
    +
    +

    5.4.4. Never Hand-Edit a Version File That’s No Longer Current

    +
    +

    control.mk.sh generates a Make rule along these lines for the current version’s file:

    +
    +
    +
    +
    $(EXTENSION_ext_VERSION_FILE): sql/ext.sql extension.control
    +	@echo '/* DO NOT EDIT - AUTO-GENERATED FILE */' > $(EXTENSION_ext_VERSION_FILE)
    +	@cat sql/ext.sql >> $(EXTENSION_ext_VERSION_FILE)
    +
    +
    +
    +

    That rule only ever targets the file matching the extension’s current default_version, so editing the base sql/{extension}.sql and running make is the correct, safe way to change that one file.

    +
    +
    +

    Every other versioned file — any sql/{extension}--{version}.sql where {version} is no longer current — is a frozen historical record, not something make will ever regenerate or overwrite again. Its entire purpose is to preserve exactly what shipped in that version (see Why Commit Them: Update Testing above), so it can keep being used to test update paths and PostgreSQL-version compatibility against exactly what your users actually installed.

    +
    +
    +

    Hand-editing an old versioned file directly is never correct — it silently corrupts that historical record. If you need to change behavior after a version has already shipped, bump the version and add a proper sql/{extension}--{old}--{new}.sql upgrade script instead. Never edit sql/{extension}--{old}.sql in place.

    +
    +
    +

    + + + + +
    +
    Caution
    +
    +This is especially relevant for AI coding agents, which won’t know this convention exists unless it’s spelled out. The generic DO NOT EDIT - AUTO-GENERATED FILE header on every version file doesn’t distinguish "regenerated on every build" (the current version) from "was generated once, now frozen" (every other version). An agent without this context may reasonably — but incorrectly — treat any auto-generated file as fair game to patch directly. Old versioned SQL files must instead be treated as append-only history: the fix is always a new version plus an upgrade script, never an in-place edit. +
    +
    +

    -

    5.4. Alternative: Ignoring Version Files

    +

    5.5. Alternative: Ignoring All Version Files

    +
    + + + + + +
    +
    Note
    +
    +This is a different, all-or-nothing choice from When It’s OK to Skip a Version above, where the recommendation is to keep tracking most versions and selectively skip only a few low-value minor ones. This section instead covers not tracking any version-specific files at all. +
    +

    If you prefer not to commit version-specific SQL files, you must add them to your .gitignore to prevent make dist from failing due to untracked files. Add the following to your .gitignore:

    @@ -1158,13 +1395,13 @@

    -

    5.5. Distribution Inclusion

    +

    5.6. Distribution Inclusion

    Version-specific files are included in distributions created by make dist only if they are committed to git. Since make dist uses git archive, only tracked files are included in the distribution archive.

  • -

    Update META.json to reflect the current version you’re working on

    +

    Update default_version in the extension’s .control file to reflect the current version you’re working on

  • Commit all version files and upgrade scripts to your repository

    @@ -1185,7 +1422,7 @@

    -

    The version file for the current version (specified in META.json) will be automatically regenerated when you run make, but other version files you create manually will be preserved.

    +

    The version file for the current version (specified in the .control file’s default_version) will be automatically regenerated when you run make, but other version files you create manually will be preserved.

    @@ -1251,9 +1488,8 @@

    <

    6.2. Document Rules

    -

    If Asciidoc is found (or $(ASCIIDOC) is set), the html rule will be added as a prerequisite to the install and installchec rules. -That will ensure that docs are generated for install and test, but only if Asciidoc is available. -The dist rule will always depend on html though, to ensure html files are up-to-date before creating a distribution.

    +

    If Asciidoc is found (or $(ASCIIDOC) is set), html is added as a dependency of the all target, which both install and installcheck depend on — so docs get built for install and test, but only if Asciidoc is available. +The dist rule always depends on html directly, regardless of whether Asciidoc was found, to ensure html files are up-to-date before creating a distribution.

    @@ -1289,6 +1525,9 @@

    $(ASCIIDOC) isn’t defined. On a normal user system that should never happen, because the html rule won’t be included in install or installcheck.

    +
    +

    make docclean removes all generated HTML files ($(DOCS_HTML)). It’s separate from make clean/make distclean, so you can clear out generated docs without touching your build artifacts.

    +

    6.3. The DOCS variable

    @@ -1423,6 +1662,28 @@

    +

    7.5.2. Direct pgtle.sh Invocation

    +
    +

    pgtle, check-pgtle, and run-pgtle are thin make wrappers around pgxntool/pgtle.sh. For more targeted operations you can run it directly:

    +
    +
    +
      +
    • +

      pgtle.sh --extension NAME [--pgtle-version VERSION] — generate registration SQL for one extension (what make pgtle wraps).

      +
    • +
    • +

      pgtle.sh --get-version — print the pg_tle version installed in the target database (empty if not installed).

      +
    • +
    • +

      pgtle.sh --get-dir VERSION — print which version-range directory (see Version Groupings) a given pg_tle version maps to, without generating anything.

      +
    • +
    • +

      pgtle.sh --run — execute the generated registration SQL against the target database (what make run-pgtle wraps).

      +
    • +
    +
    +