Re: BUG #19638: Planner chooses an index-only scan for an index AM without amcanreturn, and execution fails - Mailing list pgsql-bugs

From Manuel Reyes Bravo
Subject Re: BUG #19638: Planner chooses an index-only scan for an index AM without amcanreturn, and execution fails
Date
Msg-id CA+bCEdD77EUEsm1+WDQM=m9rcgvibOKv9A9uz+Fg46Yj+zk=Kw@mail.gmail.com
Whole thread
In response to BUG #19638: Planner chooses an index-only scan for an index AM without amcanreturn, and execution fails  (PG Bug reporting form <noreply@postgresql.org>)
List pgsql-bugs
Thanks for the patch. I tested it on REL_18_STABLE-equivalent sources (18.6,
built from source), since that is the branch this would need to be
back-patched to. Short version: it fixes the bug, it passes the full
regression suite, and I believe it also rejects a legitimate plan.

What works
----------

Applied cleanly (hunk offset 2 lines). With the test module:

    before:  Index Only Scan using i_nokey  -> ERROR: no data returned...
    after:   Seq Scan (disabled)            -> 3      correct

make check: all 231 tests passed.

What I think is a false positive
--------------------------------

The guard keys off bms_is_empty(index_canreturn_attrs), but that bitmapset
is
also empty for an index whose columns are all expressions, because the
loop
just above skips them:

    /*
     * For the moment, we just ignore index expressions.  It might be nice
     * to do something with them, later.
     */
    if (attno == 0)
        continue;

So "empty" does not mean "the AM can return nothing", it means "no plain
columns are returnable". A btree over an expression can feed an index-only
scan perfectly well. Measured on 18.6, with enable_seqscan off:

    CREATE TABLE t_expr (a int, b int);
    INSERT INTO t_expr SELECT g, g*2 FROM generate_series(1,50000) g;
    CREATE INDEX i_expr ON t_expr ((a + b));
    VACUUM ANALYZE t_expr;
    SELECT count(*) FROM t_expr;

    unpatched:  Aggregate -> Index Only Scan using i_expr on t_expr
    patched:    Aggregate -> Seq Scan on t_expr (disabled)

Both return 50000, so this is a plan regression rather than a correctness
one -- counting can no longer walk the smaller index. A control with an
ordinary column index (CREATE INDEX i_col ON t_col (a)) keeps its index-only
scan under the patch, so the effect is specific to expression-only indexes.

Worth noting: make check does not catch this. The suite passed 231/231 with
the patch applied, so this would go in unnoticed.

A variant that avoids it
------------------------

Attached as a patch this time, rather than an archive. It tests the AM's
capability directly instead of the bitmapset:

    if (result)
    {
        bool        any_canreturn = false;

        for (i = 0; i < index->ncolumns; i++)
        {
            if (index->canreturn[i])
            {
                any_canreturn = true;
                break;
            }
        }
        if (!any_canreturn)
            result = false;
    }

index->canreturn[] is filled per column from index_can_return() in
plancat.c,
including expression columns, so an expression btree has a true entry
while an
AM with amcanreturn == NULL has none.

Measured on 18.6 with that variant:

    the reproducer            -> 3, correct (bug fixed)
    count(*) over i_expr      -> Index Only Scan (no regression)
    count(*) over i_col       -> Index Only Scan (unchanged)
    make check                -> all 231 tests passed

The patch is against 18.6 sources, since that is what I tested on; it should
apply to master with an offset.

I have not tried to judge which shape you would prefer, and there may be a
reason to keep it keyed off the bitmapset that I am not seeing. I can rerun
any of this on 19beta2 as well if that is useful.

El lun, 24 ago 2026 a las 14:20, Andrey Rachitskiy (<pl0h0yp1@gmail.com>) escribió:


пн, 24 авг. 2026 г. в 21:51, Manuel Reyes Bravo <manuelreyesbravo@gmail.com>:

The attached tarball includes that script as alcance.sql.

In the future, it would be better to attach patches rather than archives.
I kept the fix minimal: one guard after bms_is_subset() in check_index_only(), rejecting the plan when no key column is returnable.

--
Regards,
Rachitskiy Andrey


--
Saludos cordiales,

Manuel Reyes
Attachment

pgsql-bugs by date:

Previous
From: Andrey Rachitskiy
Date:
Subject: Re: BUG #19638: Planner chooses an index-only scan for an index AM without amcanreturn, and execution fails
Next
From: Andrey Rachitskiy
Date:
Subject: Re: BUG #19638: Planner chooses an index-only scan for an index AM without amcanreturn, and execution fails