Skip to content

[2.1.0] Schema-qualify plx's own objects in generated code; make plx non-relocatable - #17

Open
OffgridwithJD wants to merge 2 commits into
commandprompt:masterfrom
OffgridwithJD:feat/qualify-extension-objects
Open

OffgridwithJD wants to merge 2 commits into
commandprompt:masterfrom
OffgridwithJD:feat/qualify-extension-objects

Conversation

@OffgridwithJD

@OffgridwithJD OffgridwithJD commented Nov 9, 2026 •

Copy link
Copy Markdown

Summary

A string append (s << x, $s .= x, s += x, COBOL STRING-APPEND) lowers to a plx_strbuild variable and a plx_sb_append(...) call. Both were emitted unqualified, so PostgreSQL looked them up through search_path on every call. That caused three problems:

  1. The standard SECURITY DEFINER hardening broke. A function with SET search_path = pg_catalog failed with type "plx_strbuild" does not exist.
  2. Functions broke when plx lived in a schema off the caller's path.
  3. Impostor functions could run. A plx_sb_append planted earlier on the path ran in place of plx's, with the definer's privileges for a SECURITY DEFINER function. Unlike in hand-written plpgsql, the author never wrote that call, so they had no reason to defend against it.

Generated code now writes <plx schema>.plx_strbuild and <plx schema>.plx_sb_append:

DECLARE
  s public.plx_strbuild := '';
BEGIN
  FOR i IN 1..n LOOP
    s := public.plx_sb_append(s, (i::text)::text);

The schema is taken from the namespace of the running validator or inline handler, which are extension members. That works on PG 13+; get_extension_schema() isn't exported before 16.

Non-relocatable, and why this needs 2.1.0

Stored functions now contain the schema name, so ALTER EXTENSION plx SET SCHEMA would leave them pointing at the old one. plx.control therefore sets relocatable = false, and SET SCHEMA now fails with extension "plx" does not support SET SCHEMA.

PostgreSQL records relocatability in pg_extension at CREATE EXTENSION and ALTER EXTENSION UPDATE, not when the control file changes. Existing installations would stay relocatable without an update to run, so this adds:

  • Extension version 2.1.0 (plx--2.1.0.sql, and plx--2.0.0--2.1.0.sql, which changes no objects)
  • default_version = '2.1.0'
  • META.json provides updated to 2.1.0. The distribution version (2.0.1) is left for the release to set.

Verified: a database whose plx was created under the 2.0.0 control file reports 2.0.0|t (simulated by setting extrelocatable in pg_extension, since CREATE EXTENSION plx VERSION '2.0.0' with this build installed already reads relocatable = false from the new control file and reports 2.0.0|f), and after ALTER EXTENSION plx UPDATE reports 2.1.0|f. Run the update straight after installing the build: until then the new build already writes the schema into functions while SET SCHEMA is still allowed, and moving the extension in that window breaks those functions (verified: type "public.plx_strbuild" does not exist).

Restoring a function that uses a string append into a database where plx is in a different schema fails when it runs (schema "..." does not exist). The README install section and the CHANGELOG say so: install plx in the same schema on the target, or run the function's DDL again. Functions without a string append restore anywhere.

Existing functions keep their unqualified names until their DDL is re-run, as with every transpiler change.

Relation to the other PRs: independent of #12 to #16. #13 documents that the extension's schema must be on search_path; that note should be replaced when this merges. The version number is a proposal. If #16's behaviour change ships in the same release, 2.1.0 fits both.

Testing

  • New plx_strbuild cases:
    • a SECURITY DEFINER SET search_path = pg_catalog function appending
    • an impostor sb_evil.plx_sb_append ahead of plx on the path, which is not used
    • ALTER EXTENSION plx SET SCHEMA refused
  • Manual: plx installed SCHEMA ext, a hardened function, a COBOL STRING-APPEND DO block, and the 2.0.0 → 2.1.0 upgrade.
  • make installcheck (13/13), make differentialcheck and make metacheck all pass on PG 18.4.

🤖 Generated with Claude Code

…catable

A string append lowered to a plx_strbuild variable and a plx_sb_append()
call, both unqualified, so they resolved through search_path at run time.
A function hardened with SET search_path = pg_catalog then failed, as did
one whose search_path omitted plx's schema, and a same-named function
earlier on the path ran instead (with the definer's privileges for a
SECURITY DEFINER function).

The validator and inline handler now record plx's schema (the namespace
of the running validator/inline handler, which are extension members;
this works on PG 13+, where get_extension_schema is not exported before
16), and the transpiler emits <schema>.plx_strbuild and
<schema>.plx_sb_append.

Stored functions now carry the schema name, so the extension is made
non-relocatable. PostgreSQL records relocatability when an extension is
created or updated, so this adds extension version 2.1.0 with a
no-catalog-change update script; running ALTER EXTENSION plx UPDATE is
what records it for an existing installation.

Regression tests cover a hardened search_path, an impostor
plx_sb_append earlier on the path, and SET SCHEMA being refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@jdatcmd jdatcmd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adversarial review. Tested on PostgreSQL 18.4: builds without warnings, make installcheck passes 13/13, and make metacheck passes.

These hold up:

  • No unqualified reference is left: plx_strbuild and plx_sb_append are the only extension objects generated code names, and both go through plx_ext_object.
  • The in-place append still works with the qualified name: 200,000 s += "x" appends in plxjs take 10 ms.
  • A schema that needs quoting is emitted correctly: CREATE EXTENSION plx SCHEMA "My Ext" gives s "My Ext".plx_strbuild := '';, and the function runs.
  • ALTER EXTENSION plx UPDATE from 2.0.0 reaches 2.1.0 with extrelocatable = f.

Must fix (documentation)

Restoring into a database with plx in a different schema now breaks functions

A dump stores the generated plpgsql, which now contains the schema name. Restoring into a database where plx lives in another schema used to work, because the names were unqualified. Now the restored function fails when it runs:

-- source database: CREATE EXTENSION plx SCHEMA "My Ext"; a plxjs function using s += ...
-- pg_dump -n public, restore into a database with plx in public, then call it:
ERROR:  schema "My Ext" does not exist
LINE 3:   s "My Ext".plx_strbuild := '';

This follows from the design and I don't think the code should change. But it is a new restriction, so please add it to the CHANGELOG Changed entry and the upgrading or migration docs: install plx in the same schema on the target, or re-run the function DDL after restoring.

Minor

  • The PR body says a pre-update installation "reports 2.0.0|t". That is only true for a database created with the old control file. With this build installed, CREATE EXTENSION plx VERSION '2.0.0' already reports 2.0.0|f, because PostgreSQL reads relocatable from the current control file. The update script is still needed for existing installations, so this is a wording fix only.
  • Between installing the new build and running ALTER EXTENSION plx UPDATE, a database generates schema-qualified names while still being relocatable. A SET SCHEMA in that window breaks the new functions. Worth one line in the upgrade instructions: run the update straight after installing.

CI has not run on this PR yet (fork run waiting for approval).

@OffgridwithJD OffgridwithJD changed the title Schema-qualify plx's own objects in generated code; make plx non-relocatable (2.1.0) [2.1.0] Schema-qualify plx's own objects in generated code; make plx non-relocatable Nov 9, 2026
A function that uses a string append stores plx's schema name, so
restoring it into a database where plx is in a different schema leaves it
pointing at a schema that is not there. Say so in the README install
section and the CHANGELOG, with the two ways out. The CHANGELOG upgrade
note now says to run ALTER EXTENSION plx UPDATE straight after installing
the build: until then the extension is still relocatable, and moving it
breaks functions created with the new build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@OffgridwithJD

Copy link
Copy Markdown
Author

Thanks. All three points are confirmed and addressed in f77f4df (docs only) and in the PR body.

Restore into a database with plx in another schema. Reproduced: a pg_dump -n public from a database with plx in "My Ext", restored into one with plx in public, fails with schema "My Ext" does not exist.

One correction to the scope: only functions that use a string append are affected. plx_strbuild and plx_sb_append are the only names that get qualified. In the same restore, a plxjs function without an append ran fine. The README install section and the CHANGELOG now say this, with the two fixes: install plx in the same schema on the target, or run the function's DDL again. The README links to the DEBUGGING.md recipe for recovering the original source.

2.0.0|t wording. Confirmed: with this build installed, CREATE EXTENSION plx VERSION '2.0.0' reports 2.0.0|f. The PR body now says the t case is a database created under the old control file. I simulated that by setting extrelocatable in pg_extension.

Window before the update. Confirmed in that simulated database:

  • Before the update, ALTER EXTENSION plx SET SCHEMA s1 succeeds, and a function created with the new build then fails with type "public.plx_strbuild" does not exist.
  • After ALTER EXTENSION plx UPDATE, SET SCHEMA is refused.

The CHANGELOG upgrade note now says to run the update straight after installing the build.

🤖 Generated with Claude Code

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants