docs: retarget upgrade caveats to 15.19/17.11 + add btree_gist reindex note (#49621)

## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Docs update — Postgres upgrade guide for the 15.19 / 17.11 minor
release.

## What is the current behavior?

The upgrade guide's caveats are tagged for 15.18 / 17.10.

## What is the new behavior?

- Retarget the ltree-reindex and custom-operator caveats: `15.18 or
17.10` → `15.19 or 17.11`.
- Replace the ltree detection query with an operator-class-based one
that also catches expression indexes and excludes `INCLUDE`d columns.
- Add a `btree_gist` caveat: `float4`/`float8` gist indexes that may
contain `NaN` need a `REINDEX` (upstream fixed NaN handling in
15.19/17.11).
- Note the separate ltree >~14,653-label overflow case
(encoding-independent).
- Use schema-qualified names in `REINDEX INDEX CONCURRENTLY` and note it
cannot run inside a transaction block.

Detection queries validated on real 15.19 and 17.11.

## Additional context

Refs: PSQL-1245. A pgcrypto (CVE-2026-14663) caveat is intentionally not
included here.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Documentation**
  * Updated upgrade guidance for PostgreSQL 15.19 and 17.11.
* Documented schema-qualified ltree index names and transaction-block
restrictions for reindexing.
* Improved the ltree overflow query to account for partial-index
predicates when identifying values exceeding approximately 14,653
labels.
* Added guidance for identifying and concurrently rebuilding affected
`btree_gist` floating-point indexes containing `NaN` values.
* Updated supported-version guidance for custom operator selectivity
estimators.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Utkarash Kumar Singh authored and GitHub committed 2026-08-27 16:33:57 +01:00
1 parent 45d8f30ba8
commit aeb9511967
1 file changed
+104 -16
+104 -16
View File
@@ -192,7 +192,7 @@ Existing projects on pg_graphql 1.5.x are not impacted unless they choose to upg
### Ltree indexes require reindexing after upgrade
_Applies when upgrading to Postgres 15.18 or 17.10._
_Applies when upgrading to Postgres 15.19 or 17.11._
<Admonition type="caution">
@@ -221,29 +221,115 @@ To mitigate this issue:
2. If `reindex_required` is `true`, find the affected indexes:
```sql
select schemaname, tablename, indexname
from pg_indexes
select distinct
n.nspname as schema_name,
cls.relname as table_name,
ic.relname as index_name
from pg_index idx
join pg_class ic on idx.indexrelid = ic.oid
join pg_class cls on idx.indrelid = cls.oid
join pg_namespace n on ic.relnamespace = n.oid
join lateral unnest(idx.indclass::oid[]) with ordinality as k(opclass, pos) on true
join pg_opclass oc on oc.oid = k.opclass
join pg_type ty on ty.oid = oc.opcintype
where
indexname in (
select c.relname
from
pg_index as i
join pg_class as c on i.indexrelid = c.oid
join pg_attribute as a on a.attrelid = i.indrelid and a.attnum = ANY(i.indkey)
join pg_type as t on a.atttypid = t.oid
where t.typname in ('ltree', '_ltree')
);
k.pos <= idx.indnkeyatts -- key columns only, excludes INCLUDE
and ty.typname in ('ltree', '_ltree');
```
3. Reindex each affected index. `REINDEX INDEX CONCURRENTLY` runs online with no downtime:
3. Reindex each affected index using its schema-qualified name. `REINDEX INDEX CONCURRENTLY` runs online with no downtime, but cannot run inside a transaction block:
```sql
REINDEX INDEX CONCURRENTLY <index_name>;
REINDEX INDEX CONCURRENTLY <schema_name>.<index_name>;
```
Separately from the encoding case above, this release also fixes an integer overflow in `ltree` comparisons: values with more than about 14,653 labels could compare incorrectly, which can corrupt B-tree indexes built over them, regardless of your database encoding. The following query lists only the B-tree indexes whose `ltree` column or expression actually contains such values, so they are the ones to reindex (an empty result means no action is needed):
```sql
SELECT s.schema_name || '.' || s.index_name AS index_to_reindex
FROM (
SELECT
n.nspname AS schema_name,
c.relname AS table_name,
ic.relname AS index_name,
min(pg_get_expr(i.indpred, i.indrelid)) AS pred, -- partial-index predicate, if any
string_agg('nlevel(' || pg_get_indexdef(i.indexrelid, k.pos::int, true) || ') > 14653', ' OR ') AS keys_cond
FROM pg_index i
JOIN pg_class ic ON ic.oid = i.indexrelid
JOIN pg_class c ON c.oid = i.indrelid
JOIN pg_namespace n ON n.oid = c.relnamespace
JOIN pg_am am ON am.oid = ic.relam
JOIN LATERAL generate_series(1, i.indnkeyatts) AS k(pos) ON true
JOIN pg_attribute ia ON ia.attrelid = i.indexrelid AND ia.attnum = k.pos
JOIN pg_type t ON t.oid = ia.atttypid
WHERE am.amname = 'btree'
AND t.typname = 'ltree'
AND n.nspname NOT IN ('pg_catalog', 'information_schema')
GROUP BY n.nspname, c.relname, ic.relname
) s
WHERE (xpath(
'/row/cnt/text()',
query_to_xml(
format('SELECT count(*) AS cnt FROM %I.%I WHERE %s(%s)',
s.schema_name, s.table_name,
CASE WHEN s.pred IS NOT NULL THEN '(' || s.pred || ') AND ' ELSE '' END,
s.keys_cond),
false, true, ''
)
))[1]::text::bigint > 0
ORDER BY 1;
```
Reindex each index it returns, using the schema-qualified name. `REINDEX INDEX CONCURRENTLY` runs online with no downtime, but cannot run inside a transaction block:
```sql
REINDEX INDEX CONCURRENTLY <schema_name>.<index_name>;
```
### Btree_gist indexes on float columns require reindexing after upgrade
_Applies when upgrading to Postgres 15.19 or 17.11._
<Admonition type="caution">
You are affected only if you have `btree_gist` indexes on `float4` or `float8` columns that may contain `NaN` values.
</Admonition>
This release fixes `NaN` handling in `btree_gist`'s `float4` and `float8` operator classes. Indexes on those columns built under the previous version can return wrong results for rows containing `NaN` until the index is rebuilt.
To mitigate this issue:
1. Find `btree_gist` indexes on float columns:
```sql
select distinct
n.nspname as schema_name,
cls.relname as table_name,
ic.relname as index_name
from pg_index idx
join pg_class ic on idx.indexrelid = ic.oid
join pg_am am on ic.relam = am.oid
join pg_class cls on idx.indrelid = cls.oid
join pg_namespace n on ic.relnamespace = n.oid
join lateral unnest(idx.indclass::oid[]) with ordinality as k(opclass, pos) on true
join pg_opclass oc on oc.oid = k.opclass
join pg_type ty on ty.oid = oc.opcintype
where
am.amname = 'gist'
and k.pos <= idx.indnkeyatts
and ty.typname in ('float4', 'float8');
```
2. If any indexes are returned and those columns may contain `NaN` values, reindex them using the schema-qualified name. `REINDEX INDEX CONCURRENTLY` runs online with no downtime, but cannot run inside a transaction block:
```sql
REINDEX INDEX CONCURRENTLY <schema_name>.<index_name>;
```
### Custom operator selectivity estimators
_Applies when upgrading to Postgres 15.18 or 17.10._
_Applies when upgrading to Postgres 15.19 or 17.11._
Attaching a non-built-in (extension- or user-provided) selectivity estimator function to an operator now requires superuser. Existing operators continue to work — the check only fires when an operator is (re)created, most commonly during `pg_dump` / `pg_restore`, a logical restore, or a branch.
@@ -256,7 +342,9 @@ ERROR: must be superuser to specify a non-built-in restriction estimator functio
Most projects are not affected. To check whether your database has any user-defined operators that reference a non-built-in estimator:
```sql
SELECT n.nspname AS schema, o.oprname AS operator
SELECT n.nspname AS schema, o.oprname AS operator,
o.oprrest::regproc AS restrict_estimator,
o.oprjoin::regproc AS join_estimator
FROM pg_operator o
JOIN pg_namespace n ON o.oprnamespace = n.oid
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')