agora inbox for pgsql-bugs@postgresql.org  
help / color / mirror / Atom feed
BUG #19638: Planner chooses an index-only scan for an index AM without amcanreturn, and execution fails
8+ messages / 4 participants
[nested] [flat]

* BUG #19638: Planner chooses an index-only scan for an index AM without amcanreturn, and execution fails
@ 2026-08-23 23:45  PG Bug reporting form <noreply@postgresql.org>
  0 siblings, 1 reply; 8+ messages in thread

From: PG Bug reporting form @ 2026-08-23 23:45 UTC (permalink / raw)
  To: pgsql-bugs@lists.postgresql.org; +Cc: manuelreyesbravo@gmail.com

The following bug has been logged on the website:

Bug reference:      19638
Logged by:          Manuel Reyes Bravo
Email address:      manuelreyesbravo@gmail.com
PostgreSQL version: 19beta3
Operating system:   Fedora 44, Linux 7.1.8, gcc 16.1.1, PostgreSQL bui
Description:        

Note up front: reproducing this needs a third-party index access method, but
the bug itself is in core, not in the extension. An AM that does not
implement
amcanreturn is legal per the documented index AM API; the planner
nevertheless
builds an index-only scan over it, and the executor then cannot run the
plan.
The extension is only the vehicle that exposes it -- I could not find any
in-core AM with the required combination (see "Why no in-core reproducer"
below), which is probably why this has gone unnoticed.

On PostgreSQL 19beta3 the following query produces a plan that cannot be
executed:

    ERROR:  no data returned for index-only scan

The same query, same schema and same extension code works correctly on 18.6.


Reproducer
----------

Using pgvectorscale 0.9.0 (its "diskann" AM) with pgvector 0.8.6:

    CREATE EXTENSION vector;
    CREATE EXTENSION vectorscale;

    CREATE TABLE t_nopk (embedding vector(3));
    CREATE INDEX idx_nopk ON t_nopk USING diskann (embedding);
    INSERT INTO t_nopk VALUES ('[1,2,3]'), ('[4,5,6]'), ('[7,8,9]');

    SET enable_seqscan = 0;
    SELECT COUNT(*)
      FROM (SELECT embedding FROM t_nopk ORDER BY embedding <-> NULL LIMIT
3) x;

19beta3:

    QUERY PLAN
    ---------------------------------------------------
     Aggregate
       ->  Limit
             ->  Index Only Scan using idx_nopk on t_nopk

    ERROR:  no data returned for index-only scan

18.6 (same extension, same schema, same query):

    QUERY PLAN
    ---------------------------------
     Aggregate
       ->  Limit
             ->  Seq Scan on t_nopk
                   Disabled: true

     count
    -------
         3

So 18 correctly falls back to a disabled sequential scan and returns the
right
answer, while 19 produces an unexecutable plan.


Note: the table must have no PRIMARY KEY
----------------------------------------

With a btree primary key present, the planner uses that index for the
index-only scan instead and the problem does not appear. That cost me some
time, so it may save yours.


Why there is no in-core reproducer
----------------------------------

I tried to reproduce this with in-core AMs and could not. GIN and hash also
lack amcanreturn, but they require an index qual, so the path is never
considered. It appears to need amoptionalkey = true together with a missing
amcanreturn, and as far as I can tell no in-core AM has that combination. A
regression test would probably have to go through a test module.


Where it seems to come from
---------------------------

check_index_only() in src/backend/optimizer/path/indxpath.c ends with

    return bms_is_subset(attrs_used, index_canreturn_attrs);

When attrs_used is empty, bms_is_subset() returns true regardless of what
the
AM can actually return, while index_can_return() returns false for an AM
whose
amcanreturn is NULL. So an index that can return nothing at all passes the
check as long as the query needs no attributes from it.

I have not bisected this, so what follows is a guess rather than a finding:
indxpath.c gained a path-generation mask in "Allow for plugin control over
path
generation strategies" (2026-01-28), and PGS_CONSIDER_INDEXONLY looks like a
plausible reason why this path is now considered where it previously was
not.
Someone familiar with that code will see it much faster than I did.

This also looks related to the earlier discussion in "[PATCH] Check that
index
can return in get_actual_variable_range()" (Sept-Oct 2025), which addressed
the
same underlying assumption in a different place. This case does not appear
to
be covered by that fix.


Versions tested
---------------

  PostgreSQL 19beta3, built from source: fails as shown above
  PostgreSQL 18.6, built from source with the same compiler and flags:
correct

Happy to test a patch or provide any further detail.







^ permalink  raw  reply  [nested|flat] 8+ messages in thread

* Re: BUG #19638: Planner chooses an index-only scan for an index AM without amcanreturn, and execution fails
@ 2026-08-24 14:05  David Rowley <dgrowleyml@gmail.com>
  parent: PG Bug reporting form <noreply@postgresql.org>
  0 siblings, 1 reply; 8+ messages in thread

From: David Rowley @ 2026-08-24 14:05 UTC (permalink / raw)
  To: manuelreyesbravo@gmail.com; pgsql-bugs@lists.postgresql.org

On Tue, 25 Aug 2026 at 00:16, PG Bug reporting form
<noreply@postgresql.org> wrote:
> Note up front: reproducing this needs a third-party index access method, but
> the bug itself is in core, not in the extension. An AM that does not
> implement
> amcanreturn is legal per the documented index AM API; the planner
> nevertheless
> builds an index-only scan over it, and the executor then cannot run the
> plan.
> The extension is only the vehicle that exposes it -- I could not find any
> in-core AM with the required combination (see "Why no in-core reproducer"
> below), which is probably why this has gone unnoticed.

What do you mean by "required combination"?  We have plenty of
IndexAMs that don't implement amcanreturn, e.g. brin.c.

Going by what you've reported in bug #19639, I'm suspecting you might
have done something to mix up the binaries for the extension.

I tried to test this, but pgvectorscale doesn't seem to support pg19:

drowley@amd3990x:~/pgvectorscale/pgvectorscale$ cargo pgrx install --release
       Using PgConfig("pg19") and `pg_config` from
/home/drowley/pg/bin/pg_config
    Building extension with features build_parallel pg19
     Running command
"/home/drowley/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin/cargo"
"build" "--lib" "--release" "--features" "build_parallel pg19"
"--no-default-features" "--message-format=json-render-diagnostics"
error: the package 'vectorscale' does not contain this feature: pg19
help: there are similarly named features: pg14, pg15, pg16, pg17, pg18

Did you compile it yourself? How?

David





^ permalink  raw  reply  [nested|flat] 8+ messages in thread

* Re: BUG #19638: Planner chooses an index-only scan for an index AM without amcanreturn, and execution fails
@ 2026-08-24 16:33  Andrey Rachitskiy <pl0h0yp1@gmail.com>
  parent: David Rowley <dgrowleyml@gmail.com>
  0 siblings, 1 reply; 8+ messages in thread

From: Andrey Rachitskiy @ 2026-08-24 16:33 UTC (permalink / raw)
  To: David Rowley <dgrowleyml@gmail.com>; +Cc: manuelreyesbravo@gmail.com; pgsql-bugs@lists.postgresql.org

пн, 24 авг. 2026 г. в 19:05, David Rowley <dgrowleyml@gmail.com>:

> What do you mean by "required combination"?  We have plenty of
> IndexAMs that don't implement amcanreturn, e.g. brin.c.
>
>
Dear David,

"No amcanreturn" alone is not enough.

The failure needs
amoptionalkey = true together with amgettuple != NULL and amcanreturn
NULL.  I could not find an in-core AM with that combination; diskann
has it, which is why the third-party extension showed the bug.

https://github.com/timescale/pgvectorscale/blob/main/pgvectorscale/src/access_method/mod.rs
```
amroutine.amoptionalkey = true;
...
amroutine.amgettuple = Some(scan::amgettuple);
amroutine.amgetbitmap = None;
```

I reproduced it on current master without pgvectorscale, using a tiny
module AM that only implements those flags and returns heap TIDs
without filling xs_hitup/xs_itup.  With enable_seqscan/bitmapscan off:
```
  Aggregate
    ->  Index Only Scan using idx_nopk on t_nopk

  ERROR:  no data returned for index-only scan
```
So this is not a mixed-binary problem with the extension.

The hole is in check_index_only(): bms_is_subset(attrs_used,
index_canreturn_attrs) is true when attrs_used is empty even if the
index cannot return any column.  That matches the empty-targetlist
count(*) case.  It is in the same family as the earlier
get_actual_variable_range() amcanreturn check (74197bdc842).


-- 
Regards,
Rachitskiy Andrey

^ permalink  raw  reply  [nested|flat] 8+ messages in thread

* Re: BUG #19638: Planner chooses an index-only scan for an index AM without amcanreturn, and execution fails
@ 2026-08-24 16:51  Manuel Reyes Bravo <manuelreyesbravo@gmail.com>
  parent: Andrey Rachitskiy <pl0h0yp1@gmail.com>
  0 siblings, 1 reply; 8+ messages in thread

From: Manuel Reyes Bravo @ 2026-08-24 16:51 UTC (permalink / raw)
  To: Andrey Rachitskiy <pl0h0yp1@gmail.com>; +Cc: David Rowley <dgrowleyml@gmail.com>; pgsql-bugs@lists.postgresql.org

Andrey -- thank you for the independent confirmation, and for the pointer
to 74197bdc842, which is a better reference than the thread title I had.

David: re-attaching nokeyam.tar.gz here in case it did not survive my
previous message. Same module as described: dummy_index_am plus
amoptionalkey = true, an amgettuple returning one heap TID, and
amcanreturn left NULL.

One thing I have not seen mentioned yet, which may matter for the fix and
for whatever test you settle on: the trigger is not count(*), it is any
query whose target list needs no attribute from the index. Measured on
18.6 with the module above, seqscan and bitmapscan off:

    SELECT COUNT(*) FROM t_nokey;            Index Only Scan  -> ERROR
    SELECT FROM t_nokey;                     Index Only Scan  -> ERROR
    SELECT EXISTS (SELECT 1 FROM t_nokey);   Index Only Scan  -> ERROR

    SELECT a FROM t_nokey;                   Seq Scan (disabled) -> correct
    SELECT b FROM t_nokey;                   Seq Scan (disabled) -> correct

where a is the indexed column and b is not. So as soon as anything is
actually requested from the relation, attrs_used is non-empty, the subset
test does its job and the path is rejected -- which lines up with the
empty-set reading of check_index_only() that the three of us arrived at
separately.

EXISTS seems worth noting because it shows up in ordinary code through
semi-joins, without anyone writing count(*).

The attached tarball includes that script as alcance.sql.

Happy to test a patch on both branches.

Attachments:

  [application/gzip] nokeyam.tar.gz (3.2K, ../../CA+bCEdCPowQFAWXehcrMPp1PwEz-WBqbNcE9M=uxPUhUAO0NqQ@mail.gmail.com/3-nokeyam.tar.gz)
  download

^ permalink  raw  reply  [nested|flat] 8+ messages in thread

* Re: BUG #19638: Planner chooses an index-only scan for an index AM without amcanreturn, and execution fails
@ 2026-08-24 18:19  Andrey Rachitskiy <pl0h0yp1@gmail.com>
  parent: Manuel Reyes Bravo <manuelreyesbravo@gmail.com>
  0 siblings, 1 reply; 8+ messages in thread

From: Andrey Rachitskiy @ 2026-08-24 18:19 UTC (permalink / raw)
  To: Manuel Reyes Bravo <manuelreyesbravo@gmail.com>; +Cc: David Rowley <dgrowleyml@gmail.com>; pgsql-bugs@lists.postgresql.org

пн, 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

Attachments:

  [text/x-patch] 0002-Reject-index-only-scans-when-index-cannot-return.patch (1.4K, ../../CAB8bMisGzpx1TJCqEruff9tcjCPpRMq-HP55N+bcEQNfSNzQVw@mail.gmail.com/3-0002-Reject-index-only-scans-when-index-cannot-return.patch)
  download | inline diff:
From 89345a0f561b5e61b0ba6781455e0a53ac5a4825 Mon Sep 17 00:00:00 2001
From: Andrey Rachitskiy <pl0h0yp1@gmail.com>
Date: Mon, 24 Aug 2026 22:45:44 +0500
Subject: [PATCH] Reject index-only scans when the index cannot return any
 columns

If the query needs no heap columns and the index returns none,
check_index_only() still succeeds because bms_is_subset(empty, empty)
is true.  Reject index-only scans when index_canreturn_attrs is empty.

Bug: 19638
Author: Andrey Rachitskiy <pl0h0yp1@gmail.com>
Reported-by: Manuel Reyes Bravo <manuelreyesbravo@gmail.com>
Discussion: https://www.postgresql.org/message-id/19638-277d0f73dfaeaec8@postgresql.org
---
 src/backend/optimizer/path/indxpath.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 3f5d4fa3182..f6c408eef1f 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -2289,6 +2289,14 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index)
 	/* Do we have all the necessary attributes? */
 	result = bms_is_subset(attrs_used, index_canreturn_attrs);
 
+	/*
+	 * bms_is_subset() is true when attrs_used is empty, even if the index
+	 * returns nothing.  That would allow a broken index-only scan for AMs
+	 * with amcanreturn == NULL.
+	 */
+	if (result && bms_is_empty(index_canreturn_attrs))
+		result = false;
+
 	bms_free(attrs_used);
 	bms_free(index_canreturn_attrs);
 
-- 
2.53.0



^ permalink  raw  reply  [nested|flat] 8+ messages in thread

* Re: BUG #19638: Planner chooses an index-only scan for an index AM without amcanreturn, and execution fails
@ 2026-08-24 18:52  Manuel Reyes Bravo <manuelreyesbravo@gmail.com>
  parent: Andrey Rachitskiy <pl0h0yp1@gmail.com>
  0 siblings, 1 reply; 8+ messages in thread

From: Manuel Reyes Bravo @ 2026-08-24 18:52 UTC (permalink / raw)
  To: Andrey Rachitskiy <pl0h0yp1@gmail.com>; +Cc: David Rowley <dgrowleyml@gmail.com>; pgsql-bugs@lists.postgresql.org

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

Attachments:

  [text/x-patch] v2-0001-Reject-index-only-scans-when-the-AM-can-return-nothing.patch (2.3K, ../../CA+bCEdD77EUEsm1+WDQM=m9rcgvibOKv9A9uz+Fg46Yj+zk=Kw@mail.gmail.com/3-v2-0001-Reject-index-only-scans-when-the-AM-can-return-nothing.patch)
  download | inline diff:
From: Manuel Reyes Bravo <manuelreyesbravo@gmail.com>
Subject: [PATCH v2] Reject index-only scans when the AM can return nothing

Variant of Andrey Rachitskiy's patch for BUG #19638.

His guard keys off bms_is_empty(index_canreturn_attrs).  That bitmapset is
also empty for an index whose columns are all expressions, because the loop
that fills it skips them (attno == 0), so the guard rejects index-only scans
over expression indexes as well -- for example count(*) over a table whose
only index is on (a + b), which is a legitimate and useful plan.

Test index->canreturn[] directly instead.  plancat.c fills it per column
from index_can_return(), expression columns included, so an expression btree
has a true entry while an AM with amcanreturn == NULL has none.

Measured on 18.6: the #19638 reproducer returns the correct answer, count(*)
over an expression-only index keeps its Index Only Scan, and make check
passes 231/231.

Bug: 19638
Author: Andrey Rachitskiy <pl0h0yp1@gmail.com>
Co-authored-by: Manuel Reyes Bravo <manuelreyesbravo@gmail.com>
Reported-by: Manuel Reyes Bravo <manuelreyesbravo@gmail.com>
Discussion: https://www.postgresql.org/message-id/19638-277d0f73dfaeaec8@postgresql.org
---
 src/backend/optimizer/path/indxpath.c | 25 ++++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -2291,6 +2291,31 @@
 	/* Do we have all the necessary attributes? */
 	result = bms_is_subset(attrs_used, index_canreturn_attrs);
 
+	/*
+	 * bms_is_subset() is true when attrs_used is empty, even if the index
+	 * returns nothing.  That would allow a broken index-only scan for AMs
+	 * with amcanreturn == NULL.
+	 *
+	 * Test the AM's capability directly rather than the bitmapset, which is
+	 * empty for expression-only indexes too (attno == 0 is skipped above)
+	 * even though such an index can perfectly well feed an index-only scan.
+	 */
+	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;
+	}
+
 	bms_free(attrs_used);
 	bms_free(index_canreturn_attrs);
 


^ permalink  raw  reply  [nested|flat] 8+ messages in thread

* Re: BUG #19638: Planner chooses an index-only scan for an index AM without amcanreturn, and execution fails
@ 2026-08-24 19:34  Andrey Rachitskiy <pl0h0yp1@gmail.com>
  parent: Manuel Reyes Bravo <manuelreyesbravo@gmail.com>
  0 siblings, 1 reply; 8+ messages in thread

From: Andrey Rachitskiy @ 2026-08-24 19:34 UTC (permalink / raw)
  To: Manuel Reyes Bravo <manuelreyesbravo@gmail.com>; +Cc: David Rowley <dgrowleyml@gmail.com>; pgsql-bugs@lists.postgresql.org

пн, 24 авг. 2026 г. в 23:52, Manuel Reyes Bravo <manuelreyesbravo@gmail.com
>:
    unpatched:  Aggregate -> Index Only Scan using i_expr on t_expr
    patched:    Aggregate -> Seq Scan on t_expr (disabled)

Agreed, v3 fixed this.

-- 
Regards,
Rachitskiy Andrey

Attachments:

  [text/x-patch] v3-0002-Reject-index-only-scans-when-index-cannot-return.patch (2.6K, ../../CAB8bMit+uS_sdXTa_8eqRJwN-K3LcaCJNoADz5ZHOdSwe_h3Nw@mail.gmail.com/3-v3-0002-Reject-index-only-scans-when-index-cannot-return.patch)
  download | inline diff:
From d9280942f4ae7556da4384df596bc599207fceae Mon Sep 17 00:00:00 2001
From: Andrey Rachitskiy <pl0h0yp1@gmail.com>
Date: Tue, 25 Aug 2026 00:20:18 +0500
Subject: [PATCH 1/2] Reject index-only scans when the index cannot return any
 columns

If the query needs no heap columns and no index column is returnable,
check_index_only() still succeeds because bms_is_subset(empty, empty)
is true.  Track any_canreturn while building the key-column bitmap and
reject index-only scans when none are returnable (including expression
columns, which the bitmap omits).

Bug: 19638
Author: Andrey Rachitskiy <pl0h0yp1@gmail.com>
Reported-by: Manuel Reyes Bravo <manuelreyesbravo@gmail.com>
Discussion: https://www.postgresql.org/message-id/19638-277d0f73dfaeaec8@postgresql.org
---
 src/backend/optimizer/path/indxpath.c | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 3f5d4fa3182..0d4b37149d1 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -2226,6 +2226,7 @@ static bool
 check_index_only(RelOptInfo *rel, IndexOptInfo *index)
 {
 	bool		result;
+	bool		any_canreturn;
 	Bitmapset  *attrs_used = NULL;
 	Bitmapset  *index_canreturn_attrs = NULL;
 	ListCell   *lc;
@@ -2269,9 +2270,14 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index)
 	 * Construct a bitmapset of columns that the index can return back in an
 	 * index-only scan.
 	 */
+	any_canreturn = false;
 	for (i = 0; i < index->ncolumns; i++)
 	{
 		int			attno = index->indexkeys[i];
+		bool		col_canreturn = index->canreturn[i];
+
+		if (col_canreturn)
+			any_canreturn = true;
 
 		/*
 		 * For the moment, we just ignore index expressions.  It might be nice
@@ -2280,7 +2286,7 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index)
 		if (attno == 0)
 			continue;
 
-		if (index->canreturn[i])
+		if (col_canreturn)
 			index_canreturn_attrs =
 				bms_add_member(index_canreturn_attrs,
 							   attno - FirstLowInvalidHeapAttributeNumber);
@@ -2289,6 +2295,15 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index)
 	/* Do we have all the necessary attributes? */
 	result = bms_is_subset(attrs_used, index_canreturn_attrs);
 
+	/*
+	 * bms_is_subset() is true when attrs_used is empty, even if the index
+	 * returns nothing.  That would allow a broken index-only scan for AMs
+	 * with amcanreturn == NULL.  Expression columns can be returnable even
+	 * when the key-column bitmap is empty, so test canreturn[] directly.
+	 */
+	if (result && !any_canreturn)
+		result = false;
+
 	bms_free(attrs_used);
 	bms_free(index_canreturn_attrs);
 
-- 
2.53.0



^ permalink  raw  reply  [nested|flat] 8+ messages in thread

* Re: BUG #19638: Planner chooses an index-only scan for an index AM without amcanreturn, and execution fails
@ 2026-08-24 19:42  Manuel Reyes Bravo <manuelreyesbravo@gmail.com>
  parent: Andrey Rachitskiy <pl0h0yp1@gmail.com>
  0 siblings, 0 replies; 8+ messages in thread

From: Manuel Reyes Bravo @ 2026-08-24 19:42 UTC (permalink / raw)
  To: Andrey Rachitskiy <pl0h0yp1@gmail.com>; +Cc: David Rowley <dgrowleyml@gmail.com>; pgsql-bugs@lists.postgresql.org

> Agreed, v3 fixed this.

Confirmed on 18.6. v3 applies with a 2-line offset and passes everything I
threw at the previous one:

    the #19638 reproducer                  Seq Scan (disabled) -> 3, correct
    count(*) over i_expr                       Index Only Scan (kept)
    count(*) over i_col                         Index Only Scan (unchanged)
    SELECT FROM t_nokey               Seq Scan (disabled), correct
    EXISTS (SELECT 1 FROM ...)     Seq Scan (disabled), correct
    make check                                   all 231 tests passed

Tracking any_canreturn inside the existing loop is nicer than the separate
pass I suggested -- one traversal, and it reads as part of building the
bitmap rather than as an afterthought.

Nothing further from me on this one.

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

>
> пн, 24 авг. 2026 г. в 23:52, Manuel Reyes Bravo <
> manuelreyesbravo@gmail.com>:
>     unpatched:  Aggregate -> Index Only Scan using i_expr on t_expr
>     patched:    Aggregate -> Seq Scan on t_expr (disabled)
>
> Agreed, v3 fixed this.
>
> --
> Regards,
> Rachitskiy Andrey
>


-- 
Saludos cordiales,

Manuel Reyes

^ permalink  raw  reply  [nested|flat] 8+ messages in thread


end of thread, other threads:[~2026-08-24 19:42 UTC | newest]

Thread overview: 8+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2026-08-23 23:45 BUG #19638: Planner chooses an index-only scan for an index AM without amcanreturn, and execution fails PG Bug reporting form <noreply@postgresql.org>
2026-08-24 14:05 ` David Rowley <dgrowleyml@gmail.com>
2026-08-24 16:33   ` Andrey Rachitskiy <pl0h0yp1@gmail.com>
2026-08-24 16:51     ` Manuel Reyes Bravo <manuelreyesbravo@gmail.com>
2026-08-24 18:19       ` Andrey Rachitskiy <pl0h0yp1@gmail.com>
2026-08-24 18:52         ` Manuel Reyes Bravo <manuelreyesbravo@gmail.com>
2026-08-24 19:34           ` Andrey Rachitskiy <pl0h0yp1@gmail.com>
2026-08-24 19:42             ` Manuel Reyes Bravo <manuelreyesbravo@gmail.com>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox