Split out of #88, where it was scoped out to keep that change off the pg_class path one week after #87 landed there.
Problem
Migration v28 assigns namespaces in the pg_class view with a prefix test (src/migration/registry.rs):
CASE
WHEN name LIKE 'pg\_%' ESCAPE '\' THEN 11
WHEN name LIKE 'information\_schema\_%' ESCAPE '\' THEN 13000
ELSE 2200
END as relnamespace
This is wrong in both directions.
Over-matches: user tables named pg_* are hidden
A user who creates pg_myreport gets it filed under pg_catalog. \dt and every ORM that filters on nspname skip it. The table is invisible with no error.
This is the failure mode #88 called out when rejecting a blanket NOT LIKE 'pg_%' filter — the same objection applies to using the prefix for namespace assignment.
Under-matches: internal indexes leak into \di
Migrations create eight indexes named idx_*, which neither the __pgsqlite_% nor the pg_% test catches:
$ psql -c "\di"
List of indexes
Schema | Name | Type | Owner | Table
--------+-------------------------------+-------+----------+-------
public | idx_array_types_table | index | postgres |
public | idx_comments_lookup | index | postgres |
public | idx_datetime_cache_table | index | postgres |
public | idx_enum_values_label | index | postgres |
public | idx_enum_values_type | index | postgres |
public | idx_fts_metadata_table | index | postgres |
public | idx_numeric_constraints_table | index | postgres |
public | idx_string_constraints_table | index | postgres |
(8 rows)
All eight are CREATE INDEX IF NOT EXISTS statements in src/migration/registry.rs. They are indexes on __pgsqlite_* tables but are not themselves named __pgsqlite_*. They pollute \di and ORM index reflection.
Fix
#88 introduces the machinery this needs: an exact-name internal-relation registry (src/catalog/internal_relations.rs) covering all 4 internal tables, 24 views, and 8 indexes, exposed to SQL as __pgsqlite_relnamespace(name) -> 11 | 13000 | 2200, plus a drift test asserting the list matches a migrated-to-head database.
So this reduces to one expression swap in a new migration:
__pgsqlite_relnamespace(name) as relnamespace
By the time this lands, the UDF will already be serving information_schema.tables and .columns in production.
Verification
Context
Split out of #88, where it was scoped out to keep that change off the
pg_classpath one week after #87 landed there.Problem
Migration v28 assigns namespaces in the
pg_classview with a prefix test (src/migration/registry.rs):CASE WHEN name LIKE 'pg\_%' ESCAPE '\' THEN 11 WHEN name LIKE 'information\_schema\_%' ESCAPE '\' THEN 13000 ELSE 2200 END as relnamespaceThis is wrong in both directions.
Over-matches: user tables named
pg_*are hiddenA user who creates
pg_myreportgets it filed underpg_catalog.\dtand every ORM that filters onnspnameskip it. The table is invisible with no error.This is the failure mode #88 called out when rejecting a blanket
NOT LIKE 'pg_%'filter — the same objection applies to using the prefix for namespace assignment.Under-matches: internal indexes leak into
\diMigrations create eight indexes named
idx_*, which neither the__pgsqlite_%nor thepg_%test catches:All eight are
CREATE INDEX IF NOT EXISTSstatements insrc/migration/registry.rs. They are indexes on__pgsqlite_*tables but are not themselves named__pgsqlite_*. They pollute\diand ORM index reflection.Fix
#88 introduces the machinery this needs: an exact-name internal-relation registry (
src/catalog/internal_relations.rs) covering all 4 internal tables, 24 views, and 8 indexes, exposed to SQL as__pgsqlite_relnamespace(name) -> 11 | 13000 | 2200, plus a drift test asserting the list matches a migrated-to-head database.So this reduces to one expression swap in a new migration:
__pgsqlite_relnamespace(name) as relnamespaceBy the time this lands, the UDF will already be serving
information_schema.tablesand.columnsin production.Verification
pg_myreportappears in\dtunderpublic. information_schema.tables lists pgsqlite's materialized pg_* and information_schema_* relations #88 ships a test asserting the current (misfiled) behavior to document the divergence — that assertion flips here.idx_*indexes appear in\di.information_schema.tablesandpg_classagree ontable_schema/nspnamefor every relation, closing the divergence information_schema.tables lists pgsqlite's materialized pg_* and information_schema_* relations #88 leaves open.\dt,\di,\d <table>and the SQLAlchemy suite stay green — this touches the view every ORM introspection query reads.Context
pg_classfrom a Rust handler to SQLite, added the v28 namespacinginformation_schema, introduces the registry and UDFdocs/superpowers/specs/2026-08-11-information-schema-namespace-design.md