blob: e67cd8d7ad1a2adc6bef75f980f95f49ebd05cce [file] [log] [blame]
-- Test Covering Indexes Feature
--
-- Purpose: Test that plans are optimal and correct using permutations of cover
-- indexes in varying scenarios. Correctness is determined by the
-- number of output rows from each query.
-- Does the plan use index-scan, index-only-scan, or seq-scan?
--
-- N.B. "VACUUM ANALYZE" is to update relallvisible used to determine cost of an
-- index-only scan.
-- start_matchsubs
-- m/Memory Usage: \d+\w?B/
-- s/Memory Usage: \d+\w?B/Memory Usage: ###B/
-- m/Memory: \d+kB/
-- s/Memory: \d+kB/Memory: ###kB/
-- m/Buckets: \d+/
-- s/Buckets: \d+/Buckets: ###/
-- m/Hash chain length \d+\.\d+ avg, \d+ max/
-- s/Hash chain length \d+\.\d+ avg, \d+ max/Hash chain length ###/
-- m/using \d+ of \d+ buckets/
-- s/using \d+ of \d+ buckets/using ## of ### buckets/
-- end_matchsubs
set optimizer_trace_fallback=on;
set enable_seqscan=off;
-- Basic scenario
CREATE TABLE test_basic_cover_index(a int, b int, c int);
NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table.
HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew.
CREATE INDEX i_test_basic_index ON test_basic_cover_index(a) INCLUDE (b);
INSERT INTO test_basic_cover_index SELECT i, i+i, i*i FROM generate_series(1, 100)i;
VACUUM ANALYZE test_basic_cover_index;
-- KEYS: [a] INCLUDED: [b]
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT b FROM test_basic_cover_index WHERE a>42 AND b>42;
QUERY PLAN
---------------------------------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (actual rows=58 loops=1)
-> Index Only Scan using i_test_basic_index on test_basic_cover_index (actual rows=25 loops=1)
Index Cond: (a > 42)
Filter: (b > 42)
Heap Fetches: 0
Optimizer: Postgres query optimizer
(6 rows)
-- Test CTE with cover indexes
--
-- Check that CTE over scan with cover index and cover index over cte both work
-- KEYS: [a] INCLUDED: [b]
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
WITH cte AS
(
SELECT b FROM test_basic_cover_index WHERE a < 42
)
SELECT b FROM cte WHERE b%2=0;
QUERY PLAN
---------------------------------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (actual rows=41 loops=1)
-> Index Only Scan using i_test_basic_index on test_basic_cover_index (actual rows=16 loops=1)
Index Cond: (a < 42)
Filter: ((b % 2) = 0)
Heap Fetches: 0
Optimizer: Postgres query optimizer
(6 rows)
-- KEYS: [a] INCLUDED: [b]
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
WITH cte AS
(
SELECT a, b FROM test_basic_cover_index
)
SELECT b FROM cte WHERE a<42;
QUERY PLAN
---------------------------------------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (actual rows=41 loops=1)
-> Subquery Scan on cte (actual rows=16 loops=1)
-> Index Only Scan using i_test_basic_index on test_basic_cover_index (actual rows=16 loops=1)
Index Cond: (a < 42)
Heap Fetches: 0
Optimizer: Postgres query optimizer
(6 rows)
-- Views over cover indexes
CREATE VIEW view_test_cover_indexes_with_filter AS
SELECT a, b FROM test_basic_cover_index WHERE a<42;
CREATE VIEW view_test_cover_indexes_without_filter AS
SELECT a, b FROM test_basic_cover_index;
-- KEYS: [a] INCLUDED: [b]
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT b FROM view_test_cover_indexes_with_filter;
QUERY PLAN
---------------------------------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (actual rows=41 loops=1)
-> Index Only Scan using i_test_basic_index on test_basic_cover_index (actual rows=16 loops=1)
Index Cond: (a < 42)
Heap Fetches: 0
Optimizer: Postgres query optimizer
(5 rows)
-- KEYS: [a] INCLUDED: [b]
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT b FROM view_test_cover_indexes_without_filter WHERE a<42;
QUERY PLAN
---------------------------------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (actual rows=41 loops=1)
-> Index Only Scan using i_test_basic_index on test_basic_cover_index (actual rows=16 loops=1)
Index Cond: (a < 42)
Heap Fetches: 0
Optimizer: Postgres query optimizer
(5 rows)
-- Various Column Types
--
-- Use different column types to check that the scan associates the correct
-- type to the correct column
CREATE TABLE test_various_col_types(inttype int, texttype text, decimaltype decimal(10,2));
NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'inttype' as the Apache Cloudberry data distribution key for this table.
HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew.
INSERT INTO test_various_col_types SELECT i, 'texttype'||i, i FROM generate_series(1,9999) i;
CREATE INDEX i_test_various_col_types ON test_various_col_types(inttype) INCLUDE (texttype);
VACUUM ANALYZE test_various_col_types;
-- KEYS: [inttype] INCLUDED: [texttype]
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT texttype FROM test_various_col_types WHERE inttype<42;
QUERY PLAN
---------------------------------------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (actual rows=41 loops=1)
-> Index Only Scan using i_test_various_col_types on test_various_col_types (actual rows=16 loops=1)
Index Cond: (inttype < 42)
Heap Fetches: 0
Optimizer: Postgres query optimizer
(5 rows)
DROP INDEX i_test_various_col_types;
CREATE INDEX i_test_various_col_types ON test_various_col_types(decimaltype) INCLUDE (inttype);
VACUUM ANALYZE test_various_col_types;
-- KEYS: [decimaltype] INCLUDED: [inttype]
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT decimaltype, inttype FROM test_various_col_types WHERE decimaltype<42;
QUERY PLAN
---------------------------------------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (actual rows=41 loops=1)
-> Index Only Scan using i_test_various_col_types on test_various_col_types (actual rows=16 loops=1)
Index Cond: (decimaltype < '42'::numeric)
Heap Fetches: 0
Optimizer: Postgres query optimizer
(5 rows)
ALTER TABLE test_various_col_types ADD COLUMN boxtype box;
DROP INDEX i_test_various_col_types;
CREATE INDEX i_test_various_col_types ON test_various_col_types(decimaltype) INCLUDE (boxtype);
VACUUM ANALYZE test_various_col_types;
-- KEYS: [decimaltype] INCLUDED: [boxtype]
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT decimaltype, boxtype FROM test_various_col_types WHERE decimaltype<42;
QUERY PLAN
---------------------------------------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (actual rows=41 loops=1)
-> Index Only Scan using i_test_various_col_types on test_various_col_types (actual rows=16 loops=1)
Index Cond: (decimaltype < '42'::numeric)
Heap Fetches: 0
Optimizer: Postgres query optimizer
(5 rows)
-- Test drop/add columns before and after creation of the index
--
-- Alter (add/drop) columns to check that the correct data is read from the
-- physical scan.
CREATE TABLE test_add_drop_columns(a int, aa int, b int, bb int, c int, d int);
NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table.
HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew.
ALTER TABLE test_add_drop_columns DROP COLUMN aa;
INSERT INTO test_add_drop_columns SELECT i, i+i, i*i, i*i*i, i+i+i FROM generate_series(1, 100)i;
ALTER TABLE test_add_drop_columns DROP COLUMN bb;
ALTER TABLE test_add_drop_columns ADD COLUMN e int;
CREATE INDEX i_test_add_drop_columns ON test_add_drop_columns(a, b) INCLUDE (c);
ALTER TABLE test_add_drop_columns ADD COLUMN f int;
VACUUM ANALYZE test_add_drop_columns;
-- KEYS: [a, b] INCLUDED: [c]
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT a, b FROM test_add_drop_columns WHERE a<42 AND b>42;
QUERY PLAN
------------------------------------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (actual rows=20 loops=1)
-> Index Only Scan using i_test_add_drop_columns on test_add_drop_columns (actual rows=8 loops=1)
Index Cond: ((a < 42) AND (b > 42))
Heap Fetches: 0
Optimizer: Postgres query optimizer
(5 rows)
-- KEYS: [a, b] INCLUDED: [c]
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT a, b FROM test_add_drop_columns WHERE a<42 AND b>42 AND c>42;
QUERY PLAN
------------------------------------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (actual rows=20 loops=1)
-> Index Only Scan using i_test_add_drop_columns on test_add_drop_columns (actual rows=8 loops=1)
Index Cond: ((a < 42) AND (b > 42))
Filter: (c > 42)
Heap Fetches: 0
Optimizer: Postgres query optimizer
(6 rows)
-- KEYS: [a, b] INCLUDED: [c]
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT a, b, c, d, e FROM test_add_drop_columns WHERE a<42 AND b>42 AND c>42 AND e IS NULL;
QUERY PLAN
-------------------------------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (actual rows=20 loops=1)
-> Index Scan using i_test_add_drop_columns on test_add_drop_columns (actual rows=8 loops=1)
Index Cond: ((a < 42) AND (b > 42))
Filter: ((e IS NULL) AND (c > 42))
Optimizer: Postgres query optimizer
(5 rows)
-- Test various table types (e.g. AO/AOCO/replicated)
--
-- Check that different tables types (storage/distribution) leveage cover
-- indexes correctly.
CREATE TABLE test_replicated(a int, b int, c int) DISTRIBUTED REPLICATED;
CREATE INDEX i_test_replicated ON test_replicated(a) INCLUDE (b);
INSERT INTO test_replicated SELECT i, i+i, i*i FROM generate_series(1, 100)i;
VACUUM ANALYZE test_replicated;
-- KEYS: [a] INCLUDED: [b]
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT b FROM test_replicated WHERE a<42 AND b>42;
QUERY PLAN
-------------------------------------------------------------------------------------------
Gather Motion 1:1 (slice1; segments: 1) (actual rows=20 loops=1)
-> Index Only Scan using i_test_replicated on test_replicated (actual rows=20 loops=1)
Index Cond: (a < 42)
Filter: (b > 42)
Rows Removed by Filter: 21
Heap Fetches: 0
Optimizer: Postgres query optimizer
(7 rows)
-- KEYS: [a] INCLUDED: [b]
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT b, c FROM test_replicated WHERE a<42 AND b>42;
QUERY PLAN
--------------------------------------------------------------------------------------
Gather Motion 1:1 (slice1; segments: 1) (actual rows=20 loops=1)
-> Index Scan using i_test_replicated on test_replicated (actual rows=20 loops=1)
Index Cond: (a < 42)
Filter: (b > 42)
Rows Removed by Filter: 21
Optimizer: Postgres query optimizer
(6 rows)
-- Expect Seq Scan because predicate "c" is not in KEYS
-- KEYS: [a] INCLUDED: [b]
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT a, b, c FROM test_replicated WHERE c>42;
QUERY PLAN
-------------------------------------------------------------------
Gather Motion 1:1 (slice1; segments: 1) (actual rows=94 loops=1)
-> Seq Scan on test_replicated (actual rows=94 loops=1)
Filter: (c > 42)
Rows Removed by Filter: 6
Optimizer: Postgres query optimizer
(5 rows)
CREATE TABLE test_ao(a int, b int, c int) WITH (appendonly=true) DISTRIBUTED BY (a);
CREATE INDEX i_test_ao ON test_ao(a) INCLUDE (b);
INSERT INTO test_ao SELECT i, i+i, i*i FROM generate_series(1, 100)i;
VACUUM ANALYZE test_ao;
-- KEYS: [a] INCLUDED: [b]
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT b FROM test_ao WHERE a<42 AND b>42;
QUERY PLAN
---------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (actual rows=20 loops=1)
-> Bitmap Heap Scan on test_ao (actual rows=8 loops=1)
Recheck Cond: (a < 42)
Filter: (b > 42)
Rows Removed by Filter: 8
-> Bitmap Index Scan on i_test_ao (actual rows=16 loops=1)
Index Cond: (a < 42)
Optimizer: Postgres query optimizer
(8 rows)
-- KEYS: [a] INCLUDED: [b]
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT b, c FROM test_ao WHERE a<42 AND b>42;
QUERY PLAN
---------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (actual rows=20 loops=1)
-> Bitmap Heap Scan on test_ao (actual rows=8 loops=1)
Recheck Cond: (a < 42)
Filter: (b > 42)
Rows Removed by Filter: 8
-> Bitmap Index Scan on i_test_ao (actual rows=16 loops=1)
Index Cond: (a < 42)
Optimizer: Postgres query optimizer
(8 rows)
-- KEYS: [a] INCLUDED: [b]
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT a, b, c FROM test_ao WHERE c>42;
QUERY PLAN
-------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (actual rows=94 loops=1)
-> Seq Scan on test_ao (actual rows=36 loops=1)
Filter: (c > 42)
Optimizer: Postgres query optimizer
(4 rows)
-- Test select best covering index
--
-- Check that the best cover index is chosen for a plan when multiple cover
-- indexes are available.
CREATE TABLE test_select_best_cover(a int, b int, c int);
NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table.
HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew.
CREATE INDEX i_test_select_best_cover_a_bc ON test_select_best_cover(a) INCLUDE (b, c);
CREATE INDEX i_test_select_best_cover_a_b ON test_select_best_cover(a) INCLUDE (b);
CREATE INDEX i_test_select_best_cover_a ON test_select_best_cover(a);
CREATE INDEX i_test_select_best_cover_ab ON test_select_best_cover(a, b);
CREATE INDEX i_test_select_best_cover_b_ac ON test_select_best_cover(b) INCLUDE (a, c);
CREATE INDEX i_test_select_best_cover_b_a ON test_select_best_cover(b) INCLUDE (a);
CREATE INDEX i_test_select_best_cover_b ON test_select_best_cover(b);
INSERT INTO test_select_best_cover SELECT i, i+i, i*i FROM generate_series(1, 100)i;
VACUUM ANALYZE test_select_best_cover;
-- KEYS: [a] INCLUDED: []
-- KEYS: [a] INCLUDED: [b]
-- KEYS: [a, b] INCLUDED: []
-- KEYS: [b] INCLUDED: []
-- KEYS: [a] INCLUDED: [b]
-- KEYS: [a] INCLUDED: [b, c]
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT a FROM test_select_best_cover WHERE a>42;
QUERY PLAN
------------------------------------------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (actual rows=58 loops=1)
-> Index Only Scan using i_test_select_best_cover_ab on test_select_best_cover (actual rows=25 loops=1)
Index Cond: (a > 42)
Heap Fetches: 0
Optimizer: Postgres query optimizer
(5 rows)
-- KEYS: [a] INCLUDED: []
-- KEYS: [a] INCLUDED: [b]
-- KEYS: [a, b] INCLUDED: []
-- KEYS: [b] INCLUDED: []
-- KEYS: [a] INCLUDED: [b]
-- KEYS: [a] INCLUDED: [b, c]
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT b FROM test_select_best_cover WHERE b>42;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (actual rows=79 loops=1)
-> Index Only Scan using i_test_select_best_cover_b on test_select_best_cover (actual rows=33 loops=1)
Index Cond: (b > 42)
Heap Fetches: 0
Optimizer: Postgres query optimizer
(5 rows)
-- KEYS: [a] INCLUDED: []
-- KEYS: [a] INCLUDED: [b]
-- KEYS: [a, b] INCLUDED: []
-- KEYS: [b] INCLUDED: []
-- KEYS: [a] INCLUDED: [b]
-- KEYS: [a] INCLUDED: [b, c]
-- ORCA_FEATURE_NOT_SUPPORTED: use i_test_select_best_cover_ab
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT b FROM test_select_best_cover WHERE a>42 AND b>42;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (actual rows=58 loops=1)
-> Index Only Scan using i_test_select_best_cover_a_b on test_select_best_cover (actual rows=25 loops=1)
Index Cond: (a > 42)
Filter: (b > 42)
Heap Fetches: 0
Optimizer: Postgres query optimizer
(6 rows)
-- KEYS: [a] INCLUDED: []
-- KEYS: [a] INCLUDED: [b]
-- KEYS: [a, b] INCLUDED: []
-- KEYS: [b] INCLUDED: []
-- KEYS: [a] INCLUDED: [b]
-- KEYS: [a] INCLUDED: [b, c]
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT b FROM test_select_best_cover WHERE a>42 AND b>42 AND c>42;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (actual rows=58 loops=1)
-> Index Only Scan using i_test_select_best_cover_a_bc on test_select_best_cover (actual rows=25 loops=1)
Index Cond: (a > 42)
Filter: ((b > 42) AND (c > 42))
Heap Fetches: 0
Optimizer: Postgres query optimizer
(6 rows)
-- Test DML operations
--
-- Check that cover indexes can be used with DML operations
CREATE TABLE test_dml_using_cover_index(a int, b int, c int);
NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table.
HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew.
CREATE INDEX i_test_dml_using_cover_index ON test_dml_using_cover_index(a) INCLUDE (b);
INSERT INTO test_dml_using_cover_index SELECT i, i+i, i*i FROM generate_series(1, 100)i;
VACUUM ANALYZE test_dml_using_cover_index;
-- KEYS: [a] INCLUDED: [b]
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
INSERT INTO test_dml_using_cover_index (SELECT a, a, a FROM test_dml_using_cover_index WHERE a>42);
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------
Insert on test_dml_using_cover_index (actual rows=0 loops=1)
-> Index Only Scan using i_test_dml_using_cover_index on test_dml_using_cover_index test_dml_using_cover_index_1 (actual rows=25 loops=1)
Index Cond: (a > 42)
Heap Fetches: 24
Optimizer: Postgres query optimizer
(5 rows)
-- Test index scan over partition tables
--
-- Check that cover indexes can be used with partition tables. This includes
-- scenario when root/leaf partitions have different underlying physical format
-- (e.g. drop column / exchange partition or leaf partition has cover index not
-- defined on root).
CREATE TABLE test_cover_index_on_pt(a int, b int, c int)
DISTRIBUTED BY (a)
PARTITION BY RANGE (b)
(
START (0) END (4) EVERY (1)
);
CREATE INDEX i_test_cover_index_scan_on_partition_table ON test_cover_index_on_pt(a) INCLUDE(b);
INSERT INTO test_cover_index_on_pt SELECT i+i, i%4 FROM generate_series(1, 10)i;
VACUUM ANALYZE test_cover_index_on_pt;
-- KEYS: [a] INCLUDED: [b]
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT a, b FROM test_cover_index_on_pt WHERE a<10;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (actual rows=4 loops=1)
-> Append (actual rows=3 loops=1)
-> Index Only Scan using test_cover_index_on_pt_1_prt_1_a_b_idx on test_cover_index_on_pt_1_prt_1 test_cover_index_on_pt_1 (actual rows=1 loops=1)
Index Cond: (a < 10)
Heap Fetches: 0
-> Index Only Scan using test_cover_index_on_pt_1_prt_2_a_b_idx on test_cover_index_on_pt_1_prt_2 test_cover_index_on_pt_2 (actual rows=1 loops=1)
Index Cond: (a < 10)
Heap Fetches: 0
-> Index Only Scan using test_cover_index_on_pt_1_prt_3_a_b_idx on test_cover_index_on_pt_1_prt_3 test_cover_index_on_pt_3 (actual rows=1 loops=1)
Index Cond: (a < 10)
Heap Fetches: 0
-> Index Only Scan using test_cover_index_on_pt_1_prt_4_a_b_idx on test_cover_index_on_pt_1_prt_4 test_cover_index_on_pt_4 (actual rows=1 loops=1)
Index Cond: (a < 10)
Heap Fetches: 0
Optimizer: Postgres query optimizer
(15 rows)
-- KEYS: [a] INCLUDED: [b]
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT a, b, c FROM test_cover_index_on_pt WHERE a<10;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (actual rows=4 loops=1)
-> Append (actual rows=3 loops=1)
-> Index Scan using test_cover_index_on_pt_1_prt_1_a_b_idx on test_cover_index_on_pt_1_prt_1 test_cover_index_on_pt_1 (actual rows=1 loops=1)
Index Cond: (a < 10)
-> Index Scan using test_cover_index_on_pt_1_prt_2_a_b_idx on test_cover_index_on_pt_1_prt_2 test_cover_index_on_pt_2 (actual rows=1 loops=1)
Index Cond: (a < 10)
-> Index Scan using test_cover_index_on_pt_1_prt_3_a_b_idx on test_cover_index_on_pt_1_prt_3 test_cover_index_on_pt_3 (actual rows=1 loops=1)
Index Cond: (a < 10)
-> Index Scan using test_cover_index_on_pt_1_prt_4_a_b_idx on test_cover_index_on_pt_1_prt_4 test_cover_index_on_pt_4 (actual rows=1 loops=1)
Index Cond: (a < 10)
Optimizer: Postgres query optimizer
(11 rows)
-- Expect Seq Scan because predicate "b" is not in KEYS
-- KEYS: [a] INCLUDED: [b]
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT a, b, c FROM test_cover_index_on_pt WHERE b<10;
QUERY PLAN
---------------------------------------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (actual rows=10 loops=1)
-> Append (actual rows=5 loops=1)
-> Seq Scan on test_cover_index_on_pt_1_prt_1 test_cover_index_on_pt_1 (actual rows=2 loops=1)
Filter: (b < 10)
-> Seq Scan on test_cover_index_on_pt_1_prt_2 test_cover_index_on_pt_2 (actual rows=2 loops=1)
Filter: (b < 10)
-> Seq Scan on test_cover_index_on_pt_1_prt_3 test_cover_index_on_pt_3 (actual rows=2 loops=1)
Filter: (b < 10)
-> Seq Scan on test_cover_index_on_pt_1_prt_4 test_cover_index_on_pt_4 (actual rows=2 loops=1)
Filter: (b < 10)
Optimizer: Postgres query optimizer
(11 rows)
-- Expect static eliminated partitions due to predicate on column 'b'
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT a, b FROM test_cover_index_on_pt WHERE a<10 and b=2;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (actual rows=1 loops=1)
-> Index Only Scan using test_cover_index_on_pt_1_prt_3_a_b_idx on test_cover_index_on_pt_1_prt_3 test_cover_index_on_pt (actual rows=1 loops=1)
Index Cond: (a < 10)
Filter: (b = 2)
Heap Fetches: 0
Optimizer: Postgres query optimizer
(6 rows)
-- Expect join to perform dynamic index only scan
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT pt.a, pt.b FROM test_cover_index_on_pt AS pt JOIN test_basic_cover_index AS t ON pt.a=t.a WHERE pt.a<10 and pt.b=2;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (actual rows=1 loops=1)
-> Hash Join (actual rows=1 loops=1)
Hash Cond: (pt.a = t.a)
Extra Text: (seg0) Hash chain length 1.0 avg, 1 max, using 5 of 524288 buckets.
-> Index Only Scan using test_cover_index_on_pt_1_prt_3_a_b_idx on test_cover_index_on_pt_1_prt_3 pt (actual rows=1 loops=1)
Index Cond: (a < 10)
Filter: (b = 2)
Heap Fetches: 0
-> Hash (actual rows=5 loops=1)
Buckets: 524288 Batches: 1 Memory Usage: 4097kB
-> Index Only Scan using i_test_basic_index on test_basic_cover_index t (actual rows=5 loops=1)
Index Cond: (a < 10)
Heap Fetches: 0
Optimizer: Postgres query optimizer
(14 rows)
SELECT pt.a, pt.b FROM test_cover_index_on_pt AS pt JOIN test_basic_cover_index AS t ON pt.a=t.a WHERE pt.a<10 and pt.b=2;
a | b
---+---
4 | 2
(1 row)
-- Expect join to perform dynamic index only scan
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT pt.a, pt.b FROM test_cover_index_on_pt AS pt LEFT JOIN test_basic_cover_index AS t ON pt.a=t.a WHERE pt.a<10 and pt.b=2;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (actual rows=1 loops=1)
-> Hash Right Join (actual rows=1 loops=1)
Hash Cond: (t.a = pt.a)
Extra Text: (seg0) Hash chain length 1.0 avg, 1 max, using 1 of 524288 buckets.
-> Index Only Scan using i_test_basic_index on test_basic_cover_index t (actual rows=38 loops=1)
Heap Fetches: 0
-> Hash (actual rows=1 loops=1)
Buckets: 524288 Batches: 1 Memory Usage: 4097kB
-> Index Only Scan using test_cover_index_on_pt_1_prt_3_a_b_idx on test_cover_index_on_pt_1_prt_3 pt (actual rows=1 loops=1)
Index Cond: (a < 10)
Filter: (b = 2)
Heap Fetches: 0
Optimizer: Postgres query optimizer
(13 rows)
SELECT pt.a, pt.b FROM test_cover_index_on_pt AS pt LEFT JOIN test_basic_cover_index AS t ON pt.a=t.a WHERE pt.a<10 and pt.b=2;
a | b
---+---
4 | 2
(1 row)
CREATE TABLE leaf_part(a int, b int, c int) DISTRIBUTED BY (a);
-- without explicit index declared on leaf_part
ALTER TABLE test_cover_index_on_pt EXCHANGE PARTITION FOR(2) WITH TABLE leaf_part;
INSERT INTO test_cover_index_on_pt VALUES (2, 2, 2);
VACUUM ANALYZE test_cover_index_on_pt;
-- KEYS: [a] INCLUDED: [b]
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT a, b FROM test_cover_index_on_pt WHERE a<10;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (actual rows=4 loops=1)
-> Append (actual rows=3 loops=1)
-> Index Only Scan using test_cover_index_on_pt_1_prt_1_a_b_idx on test_cover_index_on_pt_1_prt_1 test_cover_index_on_pt_1 (actual rows=1 loops=1)
Index Cond: (a < 10)
Heap Fetches: 0
-> Index Only Scan using test_cover_index_on_pt_1_prt_2_a_b_idx on test_cover_index_on_pt_1_prt_2 test_cover_index_on_pt_2 (actual rows=1 loops=1)
Index Cond: (a < 10)
Heap Fetches: 0
-> Index Only Scan using leaf_part_a_b_idx on test_cover_index_on_pt_1_prt_3 test_cover_index_on_pt_3 (actual rows=1 loops=1)
Index Cond: (a < 10)
Heap Fetches: 0
-> Index Only Scan using test_cover_index_on_pt_1_prt_4_a_b_idx on test_cover_index_on_pt_1_prt_4 test_cover_index_on_pt_4 (actual rows=1 loops=1)
Index Cond: (a < 10)
Heap Fetches: 0
Optimizer: Postgres query optimizer
(15 rows)
DROP INDEX i_test_cover_index_scan_on_partition_table;
-- with explicit index declared on leaf_part
CREATE INDEX i_test_cover_index_scan_on_partition_table ON leaf_part(a) INCLUDE(b);
VACUUM ANALYZE test_cover_index_on_pt;
-- ORCA_FEATURE_NOT_SUPPORTED: partial dynamic index scan
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT a, b FROM test_cover_index_on_pt WHERE a<10;
QUERY PLAN
---------------------------------------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (actual rows=4 loops=1)
-> Append (actual rows=3 loops=1)
-> Seq Scan on test_cover_index_on_pt_1_prt_1 test_cover_index_on_pt_1 (actual rows=1 loops=1)
Filter: (a < 10)
Rows Removed by Filter: 1
-> Seq Scan on test_cover_index_on_pt_1_prt_2 test_cover_index_on_pt_2 (actual rows=1 loops=1)
Filter: (a < 10)
Rows Removed by Filter: 1
-> Seq Scan on test_cover_index_on_pt_1_prt_3 test_cover_index_on_pt_3 (actual rows=1 loops=1)
Filter: (a < 10)
-> Seq Scan on test_cover_index_on_pt_1_prt_4 test_cover_index_on_pt_4 (actual rows=1 loops=1)
Filter: (a < 10)
Rows Removed by Filter: 1
Optimizer: Postgres query optimizer
(14 rows)
-- Test mixed partitioned tables
--
-- AO partitioned table contains a non-AO leaf partition
CREATE TABLE ao_pt(a bigint) WITH (appendonly=true) PARTITION BY RANGE(a)
(
START (1) END (11) WITH (tablename='ao_pt_1_prt_1'),
START (11) END (21) WITH (tablename='ao_pt_1_prt_2', appendonly=false),
START (21) END (31) WITH (tablename='ao_pt_1_prt_3')
);
NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table.
HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew.
INSERT INTO ao_pt SELECT i FROM generate_series(1,30)i;
CREATE INDEX idx_ao_pt_a ON ao_pt USING btree (a);
VACUUM ANALYZE ao_pt;
-- Allow dynamic index-only scan on mixed partitioned AO table
EXPLAIN SELECT a FROM ao_pt WHERE a=29;
QUERY PLAN
----------------------------------------------------------------------------------------
Gather Motion 1:1 (slice1; segments: 1) (cost=4.14..8.18 rows=1 width=8)
-> Bitmap Heap Scan on ao_pt_1_prt_3 ao_pt (cost=4.14..8.16 rows=1 width=8)
Recheck Cond: (a = 29)
-> Bitmap Index Scan on ao_pt_1_prt_3_a_idx (cost=0.00..4.14 rows=1 width=0)
Index Cond: (a = 29)
Optimizer: Postgres query optimizer
(6 rows)
DROP TABLE ao_pt;
-- AO/CO partitioned table contains a non-AO leaf partition
CREATE TABLE aocs_pt(a bigint) WITH (appendonly=true, orientation=column) PARTITION BY RANGE(a)
(
START (1) END (11) WITH (tablename='aocs_pt_1_prt_1'),
START (11) END (21) WITH (tablename='aocs_pt_1_prt_2', appendonly=false),
START (21) END (31) WITH (tablename='aocs_pt_1_prt_3')
);
NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table.
HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew.
INSERT INTO aocs_pt SELECT i FROM generate_series(1,30)i;
CREATE INDEX idx_aocs_pt_a ON aocs_pt USING btree (a);
VACUUM ANALYZE aocs_pt;
-- Allow dynamic index-only scan on mixed partitioned AO/CO table
EXPLAIN SELECT a FROM aocs_pt WHERE a=29;
QUERY PLAN
------------------------------------------------------------------------------------------
Gather Motion 1:1 (slice1; segments: 1) (cost=4.14..8.18 rows=1 width=8)
-> Bitmap Heap Scan on aocs_pt_1_prt_3 aocs_pt (cost=4.14..8.16 rows=1 width=8)
Recheck Cond: (a = 29)
-> Bitmap Index Scan on aocs_pt_1_prt_3_a_idx (cost=0.00..4.14 rows=1 width=0)
Index Cond: (a = 29)
Optimizer: Postgres query optimizer
(6 rows)
DROP TABLE aocs_pt;
-- Test various index types
--
-- Check that different index types can be used with cover indexes.
-- Note: brin, hash, and spgist do not suport included columns.
CREATE TABLE test_index_types(a box, b int, c int) DISTRIBUTED BY (b);
INSERT INTO test_index_types VALUES ('(2.0,2.0,0.0,0.0)', 2, 2);
INSERT INTO test_index_types VALUES ('(1.0,1.0,3.0,3.0)', 3, 3);
CREATE INDEX i_test_index_types ON test_index_types USING GIST (a) INCLUDE (b);
VACUUM ANALYZE test_index_types;
-- KEYS: [a] INCLUDED: [b]
-- Check support index-only-scan on GIST indexes
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT a, b FROM test_index_types WHERE a<@ box '(0,0,3,3)';
QUERY PLAN
--------------------------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (actual rows=2 loops=1)
-> Index Only Scan using i_test_index_types on test_index_types (actual rows=2 loops=1)
Index Cond: (a <@ '(3,3),(0,0)'::box)
Heap Fetches: 0
Optimizer: Postgres query optimizer
(5 rows)
-- KEYS: [a] INCLUDED: []
CREATE TABLE tsvector_table(t text, a tsvector) DISTRIBUTED BY (t);
INSERT INTO tsvector_table values('\n', '');
CREATE INDEX a_gist_index ON tsvector_table USING GIST (a);
-- Check index-only-scan is not used when index can not return column
SET optimizer_enable_indexscan=off;
SET optimizer_enable_bitmapscan=off;
SET optimizer_enable_tablescan=off;
-- expect fallback
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT count(*) FROM tsvector_table WHERE a @@ 'w:*|q:*';
QUERY PLAN
-----------------------------------------------------------------------------------
Finalize Aggregate (actual rows=1 loops=1)
-> Gather Motion 3:1 (slice1; segments: 3) (actual rows=3 loops=1)
-> Partial Aggregate (actual rows=1 loops=1)
-> Bitmap Heap Scan on tsvector_table (actual rows=0 loops=1)
Recheck Cond: (a @@ '''w'':* | ''q'':*'::tsquery)
Rows Removed by Index Recheck: 1
-> Bitmap Index Scan on a_gist_index (actual rows=1 loops=1)
Index Cond: (a @@ '''w'':* | ''q'':*'::tsquery)
Optimizer: Postgres query optimizer
(9 rows)
SELECT count(*) FROM tsvector_table WHERE a @@ 'w:*|q:*';
count
-------
0
(1 row)
RESET optimizer_enable_indexscan;
RESET optimizer_enable_bitmapscan;
RESET optimizer_enable_tablescan;
-- KEYS: [a] INCLUDED: [b]
-- Check support dynamic-index-only-scan on GIST indexes
CREATE TABLE test_partition_table_with_gist_index(a int, b_box box)
DISTRIBUTED BY (a)
PARTITION BY RANGE (a)
(
START (0) END (4) EVERY (1)
);
CREATE INDEX gist_index_on_column_a_box ON test_partition_table_with_gist_index USING GIST (b_box) INCLUDE (a);
INSERT INTO test_partition_table_with_gist_index VALUES (2, '(2.0,2.0,0.0,0.0)');
INSERT INTO test_partition_table_with_gist_index VALUES (3, '(1.0,1.0,3.0,3.0)');
VACUUM ANALYZE test_partition_table_with_gist_index;
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT a, b_box FROM test_partition_table_with_gist_index WHERE b_box<@ box '(0,0,3,3)';
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (actual rows=2 loops=1)
-> Append (actual rows=2 loops=1)
-> Index Only Scan using test_partition_table_with_gist_index_1_prt_1_b_box_a_idx on test_partition_table_with_gist_index_1_prt_1 test_partition_table_with_gist_index_1 (actual rows=0 loops=1)
Index Cond: (b_box <@ '(3,3),(0,0)'::box)
Heap Fetches: 0
-> Index Only Scan using test_partition_table_with_gist_index_1_prt_2_b_box_a_idx on test_partition_table_with_gist_index_1_prt_2 test_partition_table_with_gist_index_2 (actual rows=0 loops=1)
Index Cond: (b_box <@ '(3,3),(0,0)'::box)
Heap Fetches: 0
-> Index Only Scan using test_partition_table_with_gist_index_1_prt_3_b_box_a_idx on test_partition_table_with_gist_index_1_prt_3 test_partition_table_with_gist_index_3 (actual rows=1 loops=1)
Index Cond: (b_box <@ '(3,3),(0,0)'::box)
Heap Fetches: 0
-> Index Only Scan using test_partition_table_with_gist_index_1_prt_4_b_box_a_idx on test_partition_table_with_gist_index_1_prt_4 test_partition_table_with_gist_index_4 (actual rows=1 loops=1)
Index Cond: (b_box <@ '(3,3),(0,0)'::box)
Heap Fetches: 0
Optimizer: Postgres query optimizer
(15 rows)
-- 8) Test partial indexes
--
-- Check that partial cover indexes may be used
CREATE TABLE test_partial_index(a int, b int, c int);
NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table.
HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew.
CREATE INDEX i_test_partial_index ON test_partial_index(a) INCLUDE (b) WHERE a<42;
INSERT INTO test_partial_index SELECT i, i+i, i*i FROM generate_series(1, 100)i;
VACUUM ANALYZE test_partial_index;
-- KEYS: [a] INCLUDED: [b]
-- ORCA_FEATURE_NOT_SUPPORTED: support partial indexes
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT b FROM test_partial_index WHERE a>42 AND b>42;
QUERY PLAN
-------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (actual rows=58 loops=1)
-> Seq Scan on test_partial_index (actual rows=25 loops=1)
Filter: ((a > 42) AND (b > 42))
Rows Removed by Filter: 12
Optimizer: Postgres query optimizer
(5 rows)
-- Test backward index scan
--
-- Check that cover indexes may be used for backward index scan
CREATE TABLE test_backward_index_scan(a int, b int, c int) DISTRIBUTED BY (a);
CREATE INDEX i_test_backward_index_scan ON test_backward_index_scan(a) INCLUDE (b);
INSERT INTO test_backward_index_scan SELECT i, i+i, i*i FROM generate_series(1, 100)i;
VACUUM ANALYZE test_backward_index_scan;
-- KEYS: [a] INCLUDED: [b]
-- ORCA_FEATURE_NOT_SUPPORTED enable backward index scan
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT b FROM test_backward_index_scan WHERE a>42 AND b>42 ORDER BY a DESC;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (actual rows=58 loops=1)
Merge Key: a
-> Index Only Scan Backward using i_test_backward_index_scan on test_backward_index_scan (actual rows=25 loops=1)
Index Cond: (a > 42)
Filter: (b > 42)
Heap Fetches: 0
Optimizer: Postgres query optimizer
(7 rows)
-- Test index expressions
--
-- Check that cover indexes may be used for index expressions
CREATE OR REPLACE FUNCTION add_one(integer)
RETURNS INTEGER
LANGUAGE 'plpgsql'
AS $$
BEGIN
RETURN $1 + 1;
END;
$$;
CREATE TABLE test_index_expression_scan(a int, b int, c int) DISTRIBUTED BY (a);
CREATE INDEX i_test_index_expression_scan ON test_index_expression_scan(a) INCLUDE (b);
INSERT INTO test_index_expression_scan SELECT i, i+i, i*i FROM generate_series(1, 100)i;
VACUUM ANALYZE test_index_expression_scan;
-- KEYS: [a] INCLUDED: [b]
-- ORCA_FEATURE_NOT_SUPPORTED enable index expression
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT add_one(b) FROM test_index_expression_scan WHERE add_one(a) < 42;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (actual rows=40 loops=1)
-> Index Only Scan using i_test_index_expression_scan on test_index_expression_scan (actual rows=15 loops=1)
Filter: (add_one(a) < 42)
Rows Removed by Filter: 23
Heap Fetches: 0
Optimizer: Postgres query optimizer
(6 rows)
-- Test combined indexes
--
-- Check that combined indexes may be used. (on OR conditions) https://www.postgresql.org/docs/current/indexes-bitmap-scans.html
CREATE TABLE test_combined_index_scan(a int, b int, c int) DISTRIBUTED BY (a);
CREATE INDEX i_test_combined_index_scan_a ON test_combined_index_scan(a) INCLUDE (b);
CREATE INDEX i_test_combined_index_scan_b ON test_combined_index_scan(b) INCLUDE (a);
INSERT INTO test_combined_index_scan SELECT i, i+i, i*i FROM generate_series(1, 100)i;
VACUUM ANALYZE test_combined_index_scan;
-- KEYS: [a] INCLUDED: [b]
-- KEYS: [b] INCLUDED: [a]
-- ORCA_FEATURE_NOT_SUPPORTED enable combined index
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT b FROM test_combined_index_scan WHERE a < 42 OR b < 42;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (actual rows=41 loops=1)
-> Index Only Scan using i_test_combined_index_scan_b on test_combined_index_scan (actual rows=16 loops=1)
Filter: ((a < 42) OR (b < 42))
Rows Removed by Filter: 22
Heap Fetches: 0
Optimizer: Postgres query optimizer
(6 rows)
reset optimizer_trace_fallback;
reset enable_seqscan;