Skip to content

fix(orca): never build index conditions on INCLUDE-only index columns - #1957

Open
yjhjstz wants to merge 1 commit into
apache:mainfrom
yjhjstz:fix/orca-include-col-indexqual-1948
Open

fix(orca): never build index conditions on INCLUDE-only index columns#1957
yjhjstz wants to merge 1 commit into
apache:mainfrom
yjhjstz:fix/orca-include-col-indexqual-1948

Conversation

@yjhjstz

@yjhjstz yjhjstz commented Sep 4, 2026

Copy link
Copy Markdown
Member

Since 37224a3 CXformUtils::PdrgpcrIndexKeys() has been a thin wrapper around PdrgpcrIndexColumns(), which appends the index's INCLUDE columns after its key columns. CPredicateUtils::ExtractIndexPredicates() therefore treated a predicate on an INCLUDE-only column as indexable. Ordinary comparisons were still rejected later by CMDIndexGPDB::IsCompatible(), but the shortcuts for boolean column references (col -> col = true, NOT col -> col = false) and for IS [NOT] NULL bypass that check and went straight into the index condition list.

The DXL-to-plan translator then maps the column through GetKeyPos(), which returns ulong_max for a non-key column, so the Var's varattno wrapped to 0 and execution failed with

ERROR: bogus index qualification (nodeIndexscan.c:1250)

or, for IS [NOT] NULL,

ERROR: btree index keys must be ordered by attribute (nbtutils.c:777)

Restore the key-only semantics: reintroduce the EIndexCols selector (EicKey / EicKeyAndIncluded) on PdrgpcrIndexColumns()/PcrsIndexColumns() and make PdrgpcrIndexKeys()/PcrsIndexKeys() request key columns only. The cost model keeps asking for key + INCLUDE columns so that ComputeUnusedIndexWeight() costing is unchanged.

Index-only-scan coverage checks are unaffected: they already use PcrsIndexReturnableColumns().

Add regression coverage to gp_covering_index.

Fixes #1948

Fixes #ISSUE_Number

What does this PR do?

Type of Change

  • Bug fix (non-breaking change)
  • New feature (non-breaking change)
  • Breaking change (fix or feature with breaking changes)
  • Documentation update

Breaking Changes

Test Plan

  • Unit tests added/updated
  • Integration tests added/updated
  • Passed make installcheck
  • Passed make -C src/test installcheck-cbdb-parallel

Impact

Performance:

User-facing changes:

Dependencies:

Checklist

Additional Context

CI Skip Instructions


Since 37224a3 CXformUtils::PdrgpcrIndexKeys() has been a thin wrapper
around PdrgpcrIndexColumns(), which appends the index's INCLUDE columns
after its key columns. CPredicateUtils::ExtractIndexPredicates() therefore
treated a predicate on an INCLUDE-only column as indexable. Ordinary
comparisons were still rejected later by CMDIndexGPDB::IsCompatible(),
but the shortcuts for boolean column references (col -> col = true,
NOT col -> col = false) and for IS [NOT] NULL bypass that check and went
straight into the index condition list.

The DXL-to-plan translator then maps the column through GetKeyPos(), which
returns ulong_max for a non-key column, so the Var's varattno wrapped to 0
and execution failed with

  ERROR:  bogus index qualification (nodeIndexscan.c:1250)

or, for IS [NOT] NULL,

  ERROR:  btree index keys must be ordered by attribute (nbtutils.c:777)

Restore the key-only semantics: reintroduce the EIndexCols selector
(EicKey / EicKeyAndIncluded) on PdrgpcrIndexColumns()/PcrsIndexColumns()
and make PdrgpcrIndexKeys()/PcrsIndexKeys() request key columns only.
The cost model keeps asking for key + INCLUDE columns so that
ComputeUnusedIndexWeight() costing is unchanged.

Index-only-scan coverage checks are unaffected: they already use
PcrsIndexReturnableColumns().

Add regression coverage to gp_covering_index.

Fixes apache#1948

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It changes core ORCA index predicate extraction and costing behavior and should receive a final domain-expert review despite the added regression coverage.

Pull request overview

This PR fixes an ORCA planning bug where predicates on INCLUDE-only index columns could incorrectly become index quals, leading to executor failures (e.g., “bogus index qualification”), and adds regression coverage for the affected predicate forms.

Changes:

  • Reintroduce a key-only vs key+INCLUDE column selector in CXformUtils so index-qual construction only considers key columns.
  • Keep cost model behavior unchanged by explicitly requesting key+INCLUDE columns for unused-index-column weighting.
  • Add regression tests (and expected outputs) covering boolean column refs and IS [NOT] NULL predicates on INCLUDE-only columns.
File summaries
File Description
src/backend/gporca/libgpopt/src/xforms/CXformUtils.cpp Make index-key APIs key-only and add selectable index column retrieval (key vs key+INCLUDE).
src/backend/gporca/libgpopt/include/gpopt/xforms/CXformUtils.h Add EIndexCols selector and update function signatures/docs accordingly.
src/backend/gporca/libgpdbcost/src/CCostModelGPDB.cpp Request key+INCLUDE columns for costing to preserve prior cost behavior.
src/test/regress/sql/gp_covering_index.sql Add regression queries ensuring INCLUDE-only predicates do not become index conditions.
src/test/regress/expected/gp_covering_index.out Update expected output for Postgres planner variant.
src/test/regress/expected/gp_covering_index_optimizer.out Update expected output for ORCA variant.
Review details

Suppressed comments (1)

src/backend/gporca/libgpopt/src/xforms/CXformUtils.cpp:1845

  • The comment for PcrsIndexReturnableColumns contains a typo: "retunable" should be "returnable".
//		CXformUtils::PcrsIndexReturnableColumns
//
//	@doc:
//		Return the set of columns from the given array of columns which are
//		retunable through the index (to determine index-only scan capable)
  • Files reviewed: 6/6 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

yjhjstz added a commit to quantumiodb/pgorca that referenced this pull request Sep 6, 2026
A predicate on a column that appears only in an index's INCLUDE list was
turned into an index condition, and the executor rejected the plan:

  create table t (k int, pay int, flag boolean);
  insert into t select i, case when i % 997 = 0 then null else i % 50 end,
                     i % 7 = 0 from generate_series(1, 100000) i;
  create index t_i on t (k) include (pay, flag);
  analyze t;
  set pg_orca.enable_orca = on;
  explain (costs off) select * from t where k < 30 and flag;
  ERROR:  bogus varattno for INDEX_VAR var: 0
  select * from t where k < 30 and flag;
  ERROR:  bogus index qualification

An IS [NOT] NULL test on an INCLUDE column fails the same way with
"btree index keys must be ordered by attribute", and both shapes reach the
executor through the plain index scan and the bitmap paths alike.

CXformUtils::PdrgpcrIndexKeys was a straight wrapper around
PdrgpcrIndexColumns, which appends pmdindex->Keys() followed by
pmdindex->IncludedCols(). Its three index-predicate call sites --
PexprBuildIndexPlan and the two bitmap plan builders -- therefore passed
INCLUDE columns to CPredicateUtils::ExtractIndexPredicates as if they were
keys. Most predicates are still filtered out there by the opfamily lookup
and FCompatiblePredicates, which is why a plain `pay = 7` never broke; the
boolean-column-reference and NULL-test shortcuts skip that validation and
put the predicate straight into the index conditions. The executor only
accepts index quals on key attributes (ExecIndexBuildScanKeys), so the
INCLUDE column's attno, which has no scan-key slot, is what blows up.

Reintroduce a key-only vs. key+INCLUDE selector on the column accessors:
PdrgpcrIndexColumns and PcrsIndexColumns take an EIndexCols, and the
PdrgpcrIndexKeys / PcrsIndexKeys wrappers pass EicKey. INCLUDE columns are
not keys, and a caller building index conditions must never see them.

CCostModelGPDB::GetCommonIndexData is the one caller that wanted the old
key+INCLUDE list: ComputeUnusedIndexWeight has always weighed every column
physically stored in the index, so it now asks for EicKeyAndIncluded
explicitly and plan costs are unaffected -- cost_align.sh is unchanged at
311 / 183 OK / 5 off / 123 diff-plan.

Index-only-scan coverage is decided by PcrsIndexReturnableColumns, which
reads pmdindex->ReturnableCols() and is untouched, so INCLUDE columns keep
serving index-only scans; the fix only stops them from carrying quals.
FIndexApplicable's disjointness check now considers key columns alone,
which is the intended reading: an index whose keys none of the predicate
columns touch offers nothing to an index condition.

Tests: test/sql/covering_index.sql asserts that only the key predicate
becomes an Index Cond while boolean, IS NULL, IS NOT NULL and equality
predicates on INCLUDE columns stay a Filter, and checks results for
INCLUDE-only predicates with enable_seqscan (and then enable_indexscan)
off, where the index is the only access path left. Reverting the source
change turns all of them into the three executor errors above.
--orca-tests 23/23; --pg-tests --ignore-plans keeps the pre-existing six
failures.

Port of apache/cloudberry#1957, fixing apache/cloudberry#1948.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] ORCA: "bogus index qualification" when a filter on an INCLUDE-only index column is pushed into the index qual

2 participants