ADR 0012: pg_catalog and information_schema are real catalog schemas

Status: Accepted Date: 2026-10-24 Scope: orbit-rs Deciders: Ravindra Boddipalli Amends: ADR 0003 (item 6, and its open item 2)

Context

ADR 0003 gave schemas a place in the catalog but left pg_catalog and information_schema virtual: the engine matches their relation names (pg_namespace, pg_class, information_schema.columns, and so on) before it looks at the catalog (sql/durable/pg_catalog.rs, sql/durable/information_schema.rs), and reports fixed values for current_schema() and search_path. The owner decided on 2026-10-24 that they become real schemas now, in the same step as user schemas, not later.

If they stay special-cased by name, name resolution has two code paths. A user table called pg_class in public, an explicit pg_catalog.pg_class, and a search_path that omits pg_catalog would each behave differently from PostgreSQL, and tools that introspect (psql \d, ORMs, pg_dump) depend on those rules.

Decision

  1. pg_catalog and information_schema are entries in the schema set with fixed oids (pg_catalog 11, public 2200, information_schema a fixed oid), owned by the bootstrap superuser, and listed in pg_namespace like any other schema.
  2. Their relations are built-in relations registered in the catalog under their schema, with rows computed from the current State. They resolve through the same resolver as user relations. The by-name special case is removed.
  3. pg_catalog is implicitly first in every search_path, as in PostgreSQL, unless the path names it explicitly. information_schema is reachable by qualification or by putting it in the path.
  4. Users cannot create, alter or drop objects in either schema (42501), and cannot drop the schemas.
  5. A user relation never shadows a pg_catalog relation of the same name unless the path puts public before an explicit pg_catalog, matching PostgreSQL.
  6. Nothing is stored for them: replay is unaffected, and the rolling-upgrade guard in ADR 0003 item 4 does not apply to them.

Consequences

  • One resolution path for every relation; fewer name-based exceptions to maintain.
  • A large part of pg_catalog.rs (1.5k lines) and information_schema.rs (0.9k lines) changes shape, from name-matched generators to registered built-ins. Their behaviour must not change: the existing catalog tests and the introspection corpus file (15_introspection.slt, 42 of 47 today) must not fall.
  • Work order inside the schema change: user schemas and the resolver first, then migrate the catalog relations onto it.

Verification

Corpus records for qualified and unqualified catalog access, shadowing, search_path without pg_catalog, and the 42501 errors, recorded from PostgreSQL 18; the existing catalog tests; 15_introspection.slt and 06_catalog.slt do not fall.