| From: | Manu <manuelreyesbravo(at)gmail(dot)com> |
|---|---|
| To: | pgsql-hackers(at)lists(dot)postgresql(dot)org |
| Cc: | Álvaro Herrera <alvherre(at)kurilemu(dot)de> |
| Subject: | Partial indexes on system catalogs |
| Date: | 2026-10-01 21:49:21 |
| Message-ID: | 179089136170.117434.3768797469846977966@gmail.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-hackers |
Hi,
In the ATTACH PARTITION thread [1] Álvaro suggested exploring partial
indexes on system catalogs. The open question there was how to evaluate
the predicate during catalog maintenance without running the full
executor, while still representing the restriction in the catalogs.
Here is a sketch that seems to work, with a prototype behind it.
Evaluation. CatalogIndexInsert() is deliberately a cut-down version of
ExecInsertIndexTuples that avoids building an EState. Rather than give
that up, I keep a very small whitelist of predicate shapes that can be
evaluated by hand against the tuple: "oidcol <> 0" and "col IS NOT
NULL". For those the value comes from slot_getattr() and is compared
directly, with no expression machinery. Anything outside the whitelist
is an error.
Representation. The predicate is stored as the ordinary
pg_index.indpred, so the planner uses the partial index unchanged. The
restriction is not a new catalog field; it is an invariant checked in
two places that share the same whitelist: at CREATE/bootstrap time a
catalog index whose predicate is outside the whitelist is rejected, and
the maintenance evaluator re-checks the shape and errors out if it ever
sees anything else.
What the prototype does today, on top of master:
- a WHERE production in the bootstrap index grammar (bootparse.y), so
a predicate declared in the catalog headers survives initdb;
- the restricted evaluator and the creation-time guard above;
- genbki emits a partial unique index without a UNIQUE constraint,
since a constraint cannot be backed by a partial index.
With that, initdb is clean, the index carries the expected predicate,
the planner uses it, and amcheck (bt_index_check with heapallindexed)
passes. As a concrete case I split ConstraintRelidTypidNameIndexId into
two partial indexes, "WHERE conrelid <> 0" and "WHERE contypid <> 0":
each row falls into exactly one of them, no InvalidOid is stored, each
is unique on its own, and both pass amcheck.
What I have not done, and where I would value your view before going
further: actually replacing ConstraintRelidTypidNameIndexId reaches into
its callers (around thirty systable_beginscan sites), so I kept it as a
standalone demonstration for now. Tests and dropping a hardcoded
operator OID are still pending; pg_upgrade of a cluster carrying such
constraints migrates cleanly and amcheck passes on the new cluster.
The attached proof-of-concept patch (the core changes and a
catalog_partial_index regression test) reproduces everything above; it
applies on master and is meant for experimentation, not for commit.
Does this match what you had in mind for the evaluation and the
representation, or would you shape either differently?
[1] https://www.postgresql.org/message-id/arvPP0KhJvyf7sLA%40alvherre.pgsql
Thanks,
Manu
| Attachment | Content-Type | Size |
|---|---|---|
| partial-catalog-index-poc-v1.patch.txt | text/plain | 19.3 KB |
| From | Date | Subject | |
|---|---|---|---|
| Next Message | Ayush Tiwari | 2026-10-01 22:16:36 | Re: [Patch] Batch fsyncs when recycling WAL segments |
| Previous Message | Jim Jones | 2026-10-01 21:36:56 | Re: COMMENTS are not being copied in CREATE TABLE LIKE |