Repository navigation
fix(diff): prune extension-managed state from plans (TimescaleDB, PostGIS) - #1
Merged
Merged
Conversation
False-drift sources reported by the drift check on a real PostgreSQL/TimescaleDB/PostGIS schema, both pruned from live-server catalogs (no hardcoded names): - TimescaleDB's implicit time-column index (<hypertable>_<dimension>_idx, from timescaledb_information.hypertables) is skipped in the DB-only branch of include_object when both name and parent table match. - Extension-owned namespaces (pg_extension JOIN pg_namespace, e.g. postgis_topology's topology) are excluded from the scope derived from the search_path — such extensions ALTER DATABASE themselves onto the search_path, so they would otherwise enter the scope uninvited. - The reflection session's search_path is now pinned to the resolved scope (and restored afterwards): alembic's unqualified pass resolves names via pg_table_is_visible, which previously surfaced those same extension tables a second time, unqualified (one object, two drops). - Explicit schemas= are never filtered; the default schema stays in scope even though extensions relocate into it.
Alembic's None-schema reflection pass resolves table names via pg_table_is_visible over the session search_path, so pinning to the full scope list made non-default tables masquerade as default-schema tables in mixed scopes: false destructive DROP TABLE plus duplicate unqualified drops. Pin to the default schema alone, and only when it is a scope member (otherwise _make_include_name filters the None-pass out before reflection and no pin or restore happens). Restore-side hardening: a failed restore now invalidates the pooled connection instead of recycling it with the restricted path.
Both service containers move from timescale/timescaledb:2.29.2-pg16 to the pinned timescale/timescaledb-ha:2.25.2-pg17 — the exact stack dev runs (PG 17, timescaledb 2.25.2, PostGIS carried), so the spatial and extension-scope tests are actually exercised in CI instead of skipping. Pinning style unchanged (no floating tags).
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.
Summary
<hypertable>_<dimension>_idx) is pruned from DB-only plans. The prune map is keyed by qualified{schema}.{index}with a set of qualified hypertable owners, so heuristic name collisions across distinct hypertables (tableadimb_cvs tablea_bdimc, both yieldinga_b_c_idx) no longer leak one side as false (destructive) drift.<table>_<column>_idxon ageometry/geographycolumn — created by geoalchemy2 (<0.18 orspatial_index=True) at table-create time — are pruned. Detection keys on the column type viapg_typecatalogs, never the name alone; works without geoalchemy2 importable in the sqlpush process. Indexes declared in metadata are never affected.topologyfrom postgis_topology, whichALTER DATABASEs itself onto the search_path at install time) never enter the scope derived from the live search_path. Explicitschemas=is never filtered; the default schema stays in scope even when extensions are relocated into it.pg_table_is_visibleover the session search_path; the previous full-scope pin made non-default tables masquerade as default-schema tables, producing false destructiveDROP TABLEand duplicate unqualified drops in mixed-schema scopes. Restore failures now invalidate the pooled connection (SETsurvives ROLLBACK) instead of potentially leaking the restricted path.timescale/timescaledb-ha:pg17-ts2.25(tag verified on Docker Hub; exact pin matching dev: PG 17.9 / ts 2.25.2) so the spatial pruning tests actually execute in CI.Motivation
Dogfooding sqlpush against a FastAPI + TimescaleDB + PostGIS app produced false drift: plans tried to drop extension-managed indexes, objects in extension-owned schemas, and emitted duplicate/unqualified drops when postgis_topology stretched the database search_path beyond the declared scope.
Verification
89 passed, 1 xfailedagainst livetimescaledb-ha:pg17(PG 17.9 / ts 2.25.2 / PostGIS 3.6.2); DB-free subset skips cleanly without a DBruff check,ruff format --check,ty checkall cleanschemas=never filtered.Release surface
CHANGELOG updated under
[Unreleased](Added + Fixed). No linked issue — this fix originated from dogfooding; context lives in this PR.