Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wyTeD-003PwL-23 for pgsql-bugs@arkaria.postgresql.org; Mon, 24 Aug 2026 12:19:29 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wyTeA-002PXF-2s for pgsql-bugs@arkaria.postgresql.org; Mon, 24 Aug 2026 12:19:26 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wyHtD-000Ug4-1x for pgsql-bugs@lists.postgresql.org; Sun, 23 Aug 2026 23:46:11 +0000 Received: from mahout.postgresql.org ([2001:4800:3e1:1::227]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1wyHt9-00000000v1M-1C1K for pgsql-bugs@lists.postgresql.org; Sun, 23 Aug 2026 23:46:09 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=postgresql.org; s=20171124; h=Message-ID:Date:Reply-To:Cc:From:To:Subject: Content-Transfer-Encoding:MIME-Version:Content-Type:Sender:Content-ID: Content-Description:In-Reply-To:References; bh=SRu7+Hr2N1iWb5rWvr8mXU4MuqEMfaZIFAUX96VLm+4=; b=O5zdCLhus5dSmOzdKIHLAqlHH7 Vfz9lG54gbby7g4ZzlNm+Z/vplir4QAW34GlIcaNIbqjMT3DugKoTsqQv/S4BIs6uTl3cJJuuHMYB fFlB7hJmUSdpbo43YTryqEwz3ylwo6/vboAug67JU2SCTzpYf9fgCCLqDClk3SDGhIXn5KqMB1MV4 1WDc2hWFvxuPuL2abuSb5B8WcGQD7TqV9En9Q4Xt50udimr5TSzovnveoJn6P89l+D+sz037+7Oh1 khZLnuovP1freMqYyT4U9IM6brPa98Yc09lRR8Oe4q94crxi76AbN5ElJjeSE2SzxzTbKwoWrPq+s FgOnP/CQ==; Received: from wrigleys.postgresql.org ([2a02:16a8:dc51::60]) by mahout.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wyHt7-006PqF-1F for pgsql-bugs@lists.postgresql.org; Sun, 23 Aug 2026 23:46:05 +0000 Received: from localhost ([127.0.0.1] helo=wrigleys.postgresql.org) by wrigleys.postgresql.org with esmtp (Exim 4.98.2) (envelope-from ) id 1wyHt5-0000000H4g2-2B8g for pgsql-bugs@lists.postgresql.org; Sun, 23 Aug 2026 23:46:03 +0000 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Subject: BUG #19638: Planner chooses an index-only scan for an index AM without amcanreturn, and execution fails To: pgsql-bugs@lists.postgresql.org From: PG Bug reporting form Cc: manuelreyesbravo@gmail.com Reply-To: manuelreyesbravo@gmail.com, pgsql-bugs@lists.postgresql.org Date: Sun, 23 Aug 2026 23:45:47 +0000 Message-ID: <19638-277d0f73dfaeaec8@postgresql.org> X-Auto-Response-Suppress: All Auto-Submitted: auto-generated List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk 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: =20 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 =3D 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 =3D true together with a missi= ng 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.