Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/backend/gporca/libgpdbcost/src/CCostModelGPDB.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1823,8 +1823,12 @@ CCostModelGPDB::GetCommonIndexData(T *ptr, ULONG &ulIndexKeys,
const IMDIndex *pmdindex =
md_accessor->RetrieveIndex(ptr->Pindexdesc()->MDId());

pdrgpcrIndexColumns = CXformUtils::PdrgpcrIndexKeys(
mp, ptr->PdrgpcrOutput(), pmdindex, pmdrel);
// Costing has always weighed every column stored in the index (keys and
// INCLUDE columns alike) in ComputeUnusedIndexWeight; keep that behavior
// here so plan costs are unaffected by PdrgpcrIndexKeys becoming key-only.
pdrgpcrIndexColumns = CXformUtils::PdrgpcrIndexColumns(
mp, ptr->PdrgpcrOutput(), pmdindex, pmdrel,
CXformUtils::EicKeyAndIncluded);

stats = ptr->PstatsBaseTable();
}
Expand Down
37 changes: 27 additions & 10 deletions src/backend/gporca/libgpopt/include/gpopt/xforms/CXformUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,19 @@ using CExpressionArrays = CDynamicPtrArray<CExpressionArray, CleanupRelease>;
//---------------------------------------------------------------------------
class CXformUtils
{
public:
// which columns of an index to consider
enum EIndexCols
{
// KEY columns only: these are the only columns an index condition
// (index qual) may be built on
EicKey,

// KEY columns followed by INCLUDE ("payload") columns: everything
// physically stored in the index
EicKeyAndIncluded
};

private:
// create a logical assert for the not nullable columns of the given table
// on top of the given child expression
Expand All @@ -83,11 +96,12 @@ class CXformUtils
CColRefArray *colref_array);

// return the set of columns from the given array of columns which appear
// in the index included / key columns
// in the index columns of the specified type (key only, or key + included)
static CColRefSet *PcrsIndexColumns(CMemoryPool *mp,
CColRefArray *colref_array,
const IMDIndex *pmdindex,
const IMDRelation *pmdrel);
const IMDRelation *pmdrel,
EIndexCols eic);

// return the set of columns from the given array of columns which are
// returnable through the index (to determine index-only scan capable)
Expand All @@ -96,13 +110,6 @@ class CXformUtils
const IMDIndex *pmdindex,
const IMDRelation *pmdrel);

// return the ordered array of columns from the given array of columns which appear
// in the index included / key columns
static CColRefArray *PdrgpcrIndexColumns(CMemoryPool *mp,
CColRefArray *colref_array,
const IMDIndex *pmdindex,
const IMDRelation *pmdrel);

// lookup join keys in scalar child group
static void LookupJoinKeys(CMemoryPool *mp, CExpression *pexpr,
CExpressionArray **ppdrgpexprOuter,
Expand Down Expand Up @@ -369,8 +376,18 @@ class CXformUtils
static CWStringConst *PstrErrorMessage(CMemoryPool *mp, ULONG major,
ULONG minor, ...);

// return the ordered array of columns from the given array of columns which
// appear in the index columns of the specified type (key only, or
// key + included)
static CColRefArray *PdrgpcrIndexColumns(CMemoryPool *mp,
CColRefArray *colref_array,
const IMDIndex *pmdindex,
const IMDRelation *pmdrel,
EIndexCols eic);

// return the array of key columns from the given array of columns which appear
// in the index key columns
// in the index key columns (INCLUDE columns are deliberately excluded:
// a predicate on them must never become an index condition)
static CColRefArray *PdrgpcrIndexKeys(CMemoryPool *mp,
CColRefArray *colref_array,
const IMDIndex *pmdindex,
Expand Down
28 changes: 19 additions & 9 deletions src/backend/gporca/libgpopt/src/xforms/CXformUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1784,15 +1784,17 @@ CXformUtils::PstrErrorMessage(CMemoryPool *mp, ULONG major, ULONG minor, ...)
//
// @doc:
// Return the array of columns from the given array of columns which appear
// in the index key columns
// in the index key columns. INCLUDE columns are not keys: the executor
// only accepts index quals on key attributes (see ExecIndexBuildScanKeys),
// so callers building index conditions must never see them here.
//
//---------------------------------------------------------------------------
CColRefArray *
CXformUtils::PdrgpcrIndexKeys(CMemoryPool *mp, CColRefArray *colref_array,
const IMDIndex *pmdindex,
const IMDRelation *pmdrel)
{
return PdrgpcrIndexColumns(mp, colref_array, pmdindex, pmdrel);
return PdrgpcrIndexColumns(mp, colref_array, pmdindex, pmdrel, EicKey);
}

//---------------------------------------------------------------------------
Expand All @@ -1808,7 +1810,7 @@ CColRefSet *
CXformUtils::PcrsIndexKeys(CMemoryPool *mp, CColRefArray *colref_array,
const IMDIndex *pmdindex, const IMDRelation *pmdrel)
{
return PcrsIndexColumns(mp, colref_array, pmdindex, pmdrel);
return PcrsIndexColumns(mp, colref_array, pmdindex, pmdrel, EicKey);
}

//---------------------------------------------------------------------------
Expand All @@ -1817,16 +1819,16 @@ CXformUtils::PcrsIndexKeys(CMemoryPool *mp, CColRefArray *colref_array,
//
// @doc:
// Return the set of columns from the given array of columns which appear
// in the index columns of the specified type (included / key)
// in the index columns of the specified type (key only / key + included)
//
//---------------------------------------------------------------------------
CColRefSet *
CXformUtils::PcrsIndexColumns(CMemoryPool *mp, CColRefArray *colref_array,
const IMDIndex *pmdindex,
const IMDRelation *pmdrel)
const IMDRelation *pmdrel, EIndexCols eic)
{
CColRefArray *pdrgpcrIndexColumns =
PdrgpcrIndexColumns(mp, colref_array, pmdindex, pmdrel);
PdrgpcrIndexColumns(mp, colref_array, pmdindex, pmdrel, eic);
CColRefSet *pcrsCols = GPOS_NEW(mp) CColRefSet(mp, pdrgpcrIndexColumns);

pdrgpcrIndexColumns->Release();
Expand All @@ -1836,7 +1838,7 @@ CXformUtils::PcrsIndexColumns(CMemoryPool *mp, CColRefArray *colref_array,

//---------------------------------------------------------------------------
// @function:
// CXformUtils::PdrgpcrIndexColumns
// CXformUtils::PcrsIndexReturnableColumns
//
// @doc:
// Return the set of columns from the given array of columns which are
Expand Down Expand Up @@ -1872,14 +1874,17 @@ CXformUtils::PcrsIndexReturnableColumns(CMemoryPool *mp,
//
// @doc:
// Return the ordered list of columns from the given array of columns which
// appear in the index columns of the specified type (included / key)
// appear in the index columns of the specified type: key columns only
// (EicKey), or key columns followed by INCLUDE columns (EicKeyAndIncluded)
//
//---------------------------------------------------------------------------
CColRefArray *
CXformUtils::PdrgpcrIndexColumns(CMemoryPool *mp, CColRefArray *colref_array,
const IMDIndex *pmdindex,
const IMDRelation *pmdrel)
const IMDRelation *pmdrel, EIndexCols eic)
{
GPOS_ASSERT(EicKey == eic || EicKeyAndIncluded == eic);

CColRefArray *pdrgpcrIndex = GPOS_NEW(mp) CColRefArray(mp);

// key columns
Expand All @@ -1894,6 +1899,11 @@ CXformUtils::PdrgpcrIndexColumns(CMemoryPool *mp, CColRefArray *colref_array,
pdrgpcrIndex->Append(colref);
}

if (EicKey == eic)
{
return pdrgpcrIndex;
}

// included columns
for (ULONG ul = 0; ul < pmdindex->IncludedCols(); ul++)
{
Expand Down
98 changes: 98 additions & 0 deletions src/test/regress/expected/gp_covering_index.out
Original file line number Diff line number Diff line change
Expand Up @@ -837,5 +837,103 @@ SELECT b FROM test_combined_index_scan WHERE a < 42 OR b < 42;
Optimizer: Postgres query optimizer
(6 rows)

-- Test predicates on INCLUDE-only columns (https://github.com/apache/cloudberry/issues/1948)
--
-- A predicate that only references INCLUDE ("payload") columns must never be
-- turned into an index condition: the executor rejects index quals on non-key
-- attributes ("bogus index qualification"). Boolean column references and
-- IS [NOT] NULL tests used to slip through ORCA's index predicate extraction,
-- so cover them explicitly.
CREATE TABLE test_include_col_pred(c0 boolean, c1 boolean, c2 int) DISTRIBUTED BY (c2);
CREATE INDEX i_test_include_col_pred ON test_include_col_pred(c0) INCLUDE (c1);
INSERT INTO test_include_col_pred VALUES (true, true, 1), (false, true, 2), (true, false, 3), (true, NULL, 4);
VACUUM ANALYZE test_include_col_pred;
-- KEYS: [c0] INCLUDED: [c1]
EXPLAIN (COSTS OFF)
SELECT * FROM test_include_col_pred WHERE c1;
QUERY PLAN
------------------------------------------
Gather Motion 3:1 (slice1; segments: 3)
-> Seq Scan on test_include_col_pred
Filter: c1
Optimizer: Postgres query optimizer
(4 rows)

SELECT * FROM test_include_col_pred WHERE c1 ORDER BY c2;
c0 | c1 | c2
----+----+----
t | t | 1
f | t | 2
(2 rows)

EXPLAIN (COSTS OFF)
SELECT * FROM test_include_col_pred WHERE NOT c1;
QUERY PLAN
------------------------------------------
Gather Motion 3:1 (slice1; segments: 3)
-> Seq Scan on test_include_col_pred
Filter: (NOT c1)
Optimizer: Postgres query optimizer
(4 rows)

SELECT * FROM test_include_col_pred WHERE NOT c1 ORDER BY c2;
c0 | c1 | c2
----+----+----
t | f | 3
(1 row)

EXPLAIN (COSTS OFF)
SELECT * FROM test_include_col_pred WHERE c1 IS NULL;
QUERY PLAN
------------------------------------------
Gather Motion 3:1 (slice1; segments: 3)
-> Seq Scan on test_include_col_pred
Filter: (c1 IS NULL)
Optimizer: Postgres query optimizer
(4 rows)

SELECT * FROM test_include_col_pred WHERE c1 IS NULL ORDER BY c2;
c0 | c1 | c2
----+----+----
t | | 4
(1 row)

EXPLAIN (COSTS OFF)
SELECT c1 FROM test_include_col_pred WHERE c1 IS NOT NULL;
QUERY PLAN
------------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3)
-> Index Only Scan using i_test_include_col_pred on test_include_col_pred
Filter: (c1 IS NOT NULL)
Optimizer: Postgres query optimizer
(4 rows)

SELECT c1 FROM test_include_col_pred WHERE c1 IS NOT NULL ORDER BY 1;
c1
----
f
t
t
(3 rows)

-- key predicate combined with an INCLUDE-column predicate: only the key
-- predicate may become an index condition, the rest must stay a filter
EXPLAIN (COSTS OFF)
SELECT * FROM test_include_col_pred WHERE c0 AND c1;
QUERY PLAN
-------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3)
-> Index Scan using i_test_include_col_pred on test_include_col_pred
Index Cond: (c0 = true)
Filter: c1
Optimizer: Postgres query optimizer
(5 rows)

SELECT * FROM test_include_col_pred WHERE c0 AND c1 ORDER BY c2;
c0 | c1 | c2
----+----+----
t | t | 1
(1 row)

reset optimizer_trace_fallback;
reset enable_seqscan;
98 changes: 98 additions & 0 deletions src/test/regress/expected/gp_covering_index_optimizer.out
Original file line number Diff line number Diff line change
Expand Up @@ -795,5 +795,103 @@ SELECT b FROM test_combined_index_scan WHERE a < 42 OR b < 42;
Optimizer: Pivotal Optimizer (GPORCA)
(5 rows)

-- Test predicates on INCLUDE-only columns (https://github.com/apache/cloudberry/issues/1948)
--
-- A predicate that only references INCLUDE ("payload") columns must never be
-- turned into an index condition: the executor rejects index quals on non-key
-- attributes ("bogus index qualification"). Boolean column references and
-- IS [NOT] NULL tests used to slip through ORCA's index predicate extraction,
-- so cover them explicitly.
CREATE TABLE test_include_col_pred(c0 boolean, c1 boolean, c2 int) DISTRIBUTED BY (c2);
CREATE INDEX i_test_include_col_pred ON test_include_col_pred(c0) INCLUDE (c1);
INSERT INTO test_include_col_pred VALUES (true, true, 1), (false, true, 2), (true, false, 3), (true, NULL, 4);
VACUUM ANALYZE test_include_col_pred;
-- KEYS: [c0] INCLUDED: [c1]
EXPLAIN (COSTS OFF)
SELECT * FROM test_include_col_pred WHERE c1;
QUERY PLAN
------------------------------------------
Gather Motion 3:1 (slice1; segments: 3)
-> Seq Scan on test_include_col_pred
Filter: c1
Optimizer: GPORCA
(4 rows)

SELECT * FROM test_include_col_pred WHERE c1 ORDER BY c2;
c0 | c1 | c2
----+----+----
t | t | 1
f | t | 2
(2 rows)

EXPLAIN (COSTS OFF)
SELECT * FROM test_include_col_pred WHERE NOT c1;
QUERY PLAN
------------------------------------------
Gather Motion 3:1 (slice1; segments: 3)
-> Seq Scan on test_include_col_pred
Filter: (NOT c1)
Optimizer: GPORCA
(4 rows)

SELECT * FROM test_include_col_pred WHERE NOT c1 ORDER BY c2;
c0 | c1 | c2
----+----+----
t | f | 3
(1 row)

EXPLAIN (COSTS OFF)
SELECT * FROM test_include_col_pred WHERE c1 IS NULL;
QUERY PLAN
------------------------------------------
Gather Motion 3:1 (slice1; segments: 3)
-> Seq Scan on test_include_col_pred
Filter: (c1 IS NULL)
Optimizer: GPORCA
(4 rows)

SELECT * FROM test_include_col_pred WHERE c1 IS NULL ORDER BY c2;
c0 | c1 | c2
----+----+----
t | | 4
(1 row)

EXPLAIN (COSTS OFF)
SELECT c1 FROM test_include_col_pred WHERE c1 IS NOT NULL;
QUERY PLAN
------------------------------------------
Gather Motion 3:1 (slice1; segments: 3)
-> Seq Scan on test_include_col_pred
Filter: (NOT (c1 IS NULL))
Optimizer: GPORCA
(4 rows)

SELECT c1 FROM test_include_col_pred WHERE c1 IS NOT NULL ORDER BY 1;
c1
----
f
t
t
(3 rows)

-- key predicate combined with an INCLUDE-column predicate: only the key
-- predicate may become an index condition, the rest must stay a filter
EXPLAIN (COSTS OFF)
SELECT * FROM test_include_col_pred WHERE c0 AND c1;
QUERY PLAN
-------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3)
-> Index Scan using i_test_include_col_pred on test_include_col_pred
Index Cond: (c0 = true)
Filter: c1
Optimizer: GPORCA
(5 rows)

SELECT * FROM test_include_col_pred WHERE c0 AND c1 ORDER BY c2;
c0 | c1 | c2
----+----+----
t | t | 1
(1 row)

reset optimizer_trace_fallback;
reset enable_seqscan;
Loading
Loading