Skip to content

Honour Extension schema on create and update - #451

Draft
chlunde wants to merge 1 commit into
crossplane-contrib:masterfrom
chlunde:worktree-issue-440-extension-schema
Draft

chlunde wants to merge 1 commit into
crossplane-contrib:masterfrom
chlunde:worktree-issue-440-extension-schema

Conversation

@chlunde

@chlunde chlunde commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Description of your changes

spec.forProvider.schema on Extension was accepted by the CRD but never read. Every extension landed in the connection's default schema, and non-relocatable extensions (postgis) cannot be moved afterwards.

  • Create appends SCHEMA <name> to CREATE EXTENSION.
  • Observe reads pg_namespace.nspname next to extversion, compares it verbatim with the spec, and late-initialises schema like version.
  • Update moves relocatable extensions with ALTER EXTENSION … SET SCHEMA. For non-relocatable ones the engine's does not support SET SCHEMA surfaces in Synced. Update re-observes first, so a version-only mismatch still issues no statement.
  • Both trees (cluster and namespaced), CRD doc comment regenerated.

Semantics worth a reviewer's attention:

  • The schema must exist. A missing schema fails Create with postgres' schema "x" does not exist.
  • Comparison is exact string equality on the raw identifier. Quoting on write (QuoteIdentifier) and nspname on read round-trip mixed case, spaces and hyphens.
  • Existing resources without schema late-initialise to their current schema (usually public); nothing moves.
  • Existing resources that already had schema set but ignored will now try to move the extension. Relocatable: it moves. Non-relocatable: Synced=False with the engine error until the spec matches reality. Before upgrading, compare specs against SELECT extname, extnamespace::regnamespace FROM pg_extension and fix or drop schema where they disagree.

Fixes #440

I have:

  • Read and followed Crossplane's contribution process.
  • Run make reviewable to ensure this PR is ready for review. (Locally as GOTOOLCHAIN=go1.26.1 make lint, make test, make check-diff; the pinned golangci-lint build panics under Go 1.27.)

How has this code been tested

  • Unit (reconciler_test.go, both trees): Observe schema mismatch, Create statement with version and quoted schema, Update no-op when the schema matches and ALTER EXTENSION … SET SCHEMA when it differs.
  • E2E core flow: the ltree example now installs into my-schema; check_extension_test asserts hstore=public ltree=my-schema from pg_extension. Stock postgres:18 image, no new dependencies.
  • Temporary e2e tests (not part of this PR; on my fork, branch worktree-custom-db-scripts, cluster/local/scripts/postgres-issue-440-extension-schema/) against imresamu/postgis:18-3.6 (postgres 18, postgis 3.6.1), both cluster and namespaced passes:
    1. postgis → gis, hstore → "Mixed Case", schema-less pg_trgm → public and late-init schema: public; ltree → missing schema stays not Ready with schema "nope" does not exist.
    2. Forced re-reconcile keeps Ready with an unchanged transition time.
    3. Out-of-band ALTER EXTENSION hstore SET SCHEMA public is reverted by Update.
    4. Patching postgis to schema: public gives Synced=False with does not support SET SCHEMA; patching back recovers.
    5. Deleting all leaves no extension behind; schemas survive.

DB_TYPES=postgresql make e2e green on both passes (cluster 118 s, namespaced 111 s), temporary tests i440: PASS on both.

@chlunde
chlunde force-pushed the worktree-issue-440-extension-schema branch 4 times, most recently from b0f5a07 to be4e952 Compare September 3, 2026 21:08
ExtensionParameters.Schema has been accepted by the CRD since the
resource was introduced, but no reconciler code read it, so every
extension landed in the connection's default schema. Non-relocatable
extensions such as postgis cannot be moved afterwards, which made
schema-scoped installs impossible through the provider.

Create now appends SCHEMA to CREATE EXTENSION. Observe reads the
installed schema from pg_namespace.nspname and compares it verbatim,
so quoted identifiers round-trip; an unset schema is late-initialised
like version. Update moves relocatable extensions with ALTER EXTENSION
SET SCHEMA and lets the engine's error surface for non-relocatable
ones. Update re-observes first so a version-only mismatch still issues
no statement.

The e2e ltree example now installs into my-schema and the check asserts
the schema of both example extensions.

Fixes crossplane-contrib#440

Signed-off-by: Carl Henrik Lunde <chlunde@ifi.uio.no>
@chlunde
chlunde force-pushed the worktree-issue-440-extension-schema branch from be4e952 to 1a9ae45 Compare September 3, 2026 21:08
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.

Extension's schema field is defined in the API but never used - extensions always install into the connection's default schema

1 participant