From b8cf0b028d2202d503a39f18b62a106d0ad45906 Mon Sep 17 00:00:00 2001 From: Julien Tachoires Date: Mon, 25 Aug 2025 18:43:57 +0200 Subject: [PATCH 3/6] Add the table reloption quals_push_down The reloption quals_push_down enables or disables qualifiers to ScanKey transformation and push down to the table access method during table scan execution. The default value is off, making the Quals Push Down feature disabled by default. --- .../postgres_fdw/expected/postgres_fdw.out | 2 +- doc/src/sgml/ref/create_table.sgml | 19 ++++++++++++++ src/backend/access/common/reloptions.c | 13 +++++++++- src/backend/executor/nodeSeqscan.c | 21 ++++++++++----- src/bin/psql/tab-complete.in.c | 1 + src/include/utils/rel.h | 9 +++++++ src/test/isolation/expected/stats.out | 26 +++++++++---------- src/test/regress/expected/memoize.out | 15 +++++------ src/test/regress/expected/merge.out | 2 +- src/test/regress/expected/partition_prune.out | 4 +-- src/test/regress/expected/select_parallel.out | 4 +-- src/test/regress/expected/updatable_views.out | 3 +++ 12 files changed, 84 insertions(+), 35 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index bc7242835df..c5e3761f648 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -11930,7 +11930,7 @@ SELECT * FROM local_tbl, async_pt WHERE local_tbl.a = async_pt.a AND local_tbl.c Nested Loop (actual rows=1.00 loops=1) -> Seq Scan on local_tbl (actual rows=1.00 loops=1) Filter: (c = 'bar'::text) - Rows Removed In Table AM by Filter: 1 + Rows Removed In Executor by Filter: 1 -> Append (actual rows=1.00 loops=1) -> Async Foreign Scan on async_p1 async_pt_1 (never executed) -> Async Foreign Scan on async_p2 async_pt_2 (actual rows=1.00 loops=1) diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index dc000e913c1..7cc52852fc0 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -1997,6 +1997,25 @@ WITH ( MODULUS numeric_literal, REM + + quals_push_down (boolean) + + quals_push_down storage parameter + + + + + Enables or disables qualifiers (WHERE clause) push + down to the table access method. When enabled, during table scan execution, + the table access method is able to apply early tuple filtering and returns + only the tuples satisfying the qualifiers. By default, this option is + disabled, then the table access method returns all the visible tuples and + let the query executor alone in charge of doing tuple filtering based + on the qualifiers. + + + + diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..dd57599b080 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -166,6 +166,15 @@ static relopt_bool boolRelOpts[] = }, true }, + { + { + "quals_push_down", + "Enables query qualifiers push down to the table access method during table scan", + RELOPT_KIND_HEAP, + AccessExclusiveLock + }, + false + }, /* list terminator */ {{NULL}} }; @@ -1915,7 +1924,9 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) {"vacuum_truncate", RELOPT_TYPE_BOOL, offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, - offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} + offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}, + {"quals_push_down", RELOPT_TYPE_BOOL, + offsetof(StdRdOptions, quals_push_down)} }; return (bytea *) build_reloptions(reloptions, validate, kind, diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c index 0562377c42a..f134ff591c3 100644 --- a/src/backend/executor/nodeSeqscan.c +++ b/src/backend/executor/nodeSeqscan.c @@ -483,13 +483,20 @@ ExecInitSeqScan(SeqScan *node, EState *estate, int eflags) scanstate->ss.ps.qual = ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate); - /* Build sequential scan keys */ - ExecSeqBuildScanKeys((PlanState *) scanstate, - node->tablequal, - &scanstate->sss_NumScanKeys, - &scanstate->sss_ScanKeys, - &scanstate->sss_RuntimeKeys, - &scanstate->sss_NumRuntimeKeys); + /* + * Build an push the ScanKeys only if the relation's reloption + * quals_push_down is on. + */ + if (RelationGetQualsPushDown(scanstate->ss.ss_currentRelation)) + { + /* Build sequential scan keys */ + ExecSeqBuildScanKeys((PlanState *) scanstate, + node->tablequal, + &scanstate->sss_NumScanKeys, + &scanstate->sss_ScanKeys, + &scanstate->sss_RuntimeKeys, + &scanstate->sss_NumRuntimeKeys); + } /* * When EvalPlanQual() is not in use, assign ExecProcNode for this node diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c index 8b10f2313f3..0827679649b 100644 --- a/src/bin/psql/tab-complete.in.c +++ b/src/bin/psql/tab-complete.in.c @@ -1412,6 +1412,7 @@ static const char *const table_storage_parameters[] = { "fillfactor", "log_autovacuum_min_duration", "parallel_workers", + "quals_push_down", "toast.autovacuum_enabled", "toast.autovacuum_freeze_max_age", "toast.autovacuum_freeze_min_age", diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..8907d53a4ca 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -354,6 +354,7 @@ typedef struct StdRdOptions * to freeze. 0 if disabled, -1 if unspecified. */ double vacuum_max_eager_freeze_failure_rate; + bool quals_push_down; /* enable quals push down to the table AM */ } StdRdOptions; #define HEAP_MIN_FILLFACTOR 10 @@ -409,6 +410,14 @@ typedef struct StdRdOptions ((relation)->rd_options ? \ ((StdRdOptions *) (relation)->rd_options)->parallel_workers : (defaultpw)) +/* + * RelationGetQualsPushDown + * Returns the relation's quals_push_down reloption setting. + */ +#define RelationGetQualsPushDown(relation) \ + ((relation)->rd_options ? \ + ((StdRdOptions *) (relation)->rd_options)->quals_push_down : false) + /* ViewOptions->check_option values */ typedef enum ViewOptCheckOption { diff --git a/src/test/isolation/expected/stats.out b/src/test/isolation/expected/stats.out index 0064c0c8df0..8c7fe60217e 100644 --- a/src/test/isolation/expected/stats.out +++ b/src/test/isolation/expected/stats.out @@ -2414,7 +2414,7 @@ step s1_table_stats: seq_scan|seq_tup_read|n_tup_ins|n_tup_upd|n_tup_del|n_live_tup|n_dead_tup|vacuum_count --------+------------+---------+---------+---------+----------+----------+------------ - 3| 5| 1| 1| 0| 1| 1| 0 + 3| 6| 1| 1| 0| 1| 1| 0 (1 row) @@ -2476,7 +2476,7 @@ step s1_table_stats: seq_scan|seq_tup_read|n_tup_ins|n_tup_upd|n_tup_del|n_live_tup|n_dead_tup|vacuum_count --------+------------+---------+---------+---------+----------+----------+------------ - 3| 4| 2| 0| 1| 1| 1| 0 + 3| 5| 2| 0| 1| 1| 1| 0 (1 row) step s1_table_select: SELECT * FROM test_stat_tab ORDER BY key, value; @@ -2508,7 +2508,7 @@ step s1_table_stats: seq_scan|seq_tup_read|n_tup_ins|n_tup_upd|n_tup_del|n_live_tup|n_dead_tup|vacuum_count --------+------------+---------+---------+---------+----------+----------+------------ - 5| 7| 2| 1| 1| 1| 2| 0 + 5| 9| 2| 1| 1| 1| 2| 0 (1 row) @@ -2571,7 +2571,7 @@ step s1_table_stats: seq_scan|seq_tup_read|n_tup_ins|n_tup_upd|n_tup_del|n_live_tup|n_dead_tup|vacuum_count --------+------------+---------+---------+---------+----------+----------+------------ - 9| 13| 4| 5| 1| 3| 6| 0 + 9| 31| 4| 5| 1| 3| 6| 0 (1 row) @@ -2640,7 +2640,7 @@ step s1_table_stats: seq_scan|seq_tup_read|n_tup_ins|n_tup_upd|n_tup_del|n_live_tup|n_dead_tup|vacuum_count --------+------------+---------+---------+---------+----------+----------+------------ - 9| 13| 4| 5| 1| 3| 6| 0 + 9| 31| 4| 5| 1| 3| 6| 0 (1 row) @@ -2701,7 +2701,7 @@ step s1_table_stats: seq_scan|seq_tup_read|n_tup_ins|n_tup_upd|n_tup_del|n_live_tup|n_dead_tup|vacuum_count --------+------------+---------+---------+---------+----------+----------+------------ - 9| 11| 4| 5| 1| 1| 8| 0 + 9| 29| 4| 5| 1| 1| 8| 0 (1 row) @@ -2768,7 +2768,7 @@ step s1_table_stats: seq_scan|seq_tup_read|n_tup_ins|n_tup_upd|n_tup_del|n_live_tup|n_dead_tup|vacuum_count --------+------------+---------+---------+---------+----------+----------+------------ - 9| 11| 4| 5| 1| 1| 8| 0 + 9| 29| 4| 5| 1| 1| 8| 0 (1 row) @@ -2808,7 +2808,7 @@ step s1_table_stats: seq_scan|seq_tup_read|n_tup_ins|n_tup_upd|n_tup_del|n_live_tup|n_dead_tup|vacuum_count --------+------------+---------+---------+---------+----------+----------+------------ - 3| 3| 5| 1| 0| 1| 1| 0 + 3| 9| 5| 1| 0| 1| 1| 0 (1 row) @@ -2854,7 +2854,7 @@ step s1_table_stats: seq_scan|seq_tup_read|n_tup_ins|n_tup_upd|n_tup_del|n_live_tup|n_dead_tup|vacuum_count --------+------------+---------+---------+---------+----------+----------+------------ - 3| 3| 5| 1| 0| 1| 1| 0 + 3| 9| 5| 1| 0| 1| 1| 0 (1 row) @@ -2894,7 +2894,7 @@ step s1_table_stats: seq_scan|seq_tup_read|n_tup_ins|n_tup_upd|n_tup_del|n_live_tup|n_dead_tup|vacuum_count --------+------------+---------+---------+---------+----------+----------+------------ - 3| 3| 4| 2| 0| 4| 2| 0 + 3| 9| 4| 2| 0| 4| 2| 0 (1 row) @@ -2940,7 +2940,7 @@ step s1_table_stats: seq_scan|seq_tup_read|n_tup_ins|n_tup_upd|n_tup_del|n_live_tup|n_dead_tup|vacuum_count --------+------------+---------+---------+---------+----------+----------+------------ - 3| 3| 4| 2| 0| 4| 2| 0 + 3| 9| 4| 2| 0| 4| 2| 0 (1 row) @@ -2981,7 +2981,7 @@ step s1_table_stats: seq_scan|seq_tup_read|n_tup_ins|n_tup_upd|n_tup_del|n_live_tup|n_dead_tup|vacuum_count --------+------------+---------+---------+---------+----------+----------+------------ - 4| 4| 5| 3| 1| 4| 4| 0 + 4| 16| 5| 3| 1| 4| 4| 0 (1 row) @@ -3028,7 +3028,7 @@ step s1_table_stats: seq_scan|seq_tup_read|n_tup_ins|n_tup_upd|n_tup_del|n_live_tup|n_dead_tup|vacuum_count --------+------------+---------+---------+---------+----------+----------+------------ - 4| 4| 5| 3| 1| 4| 4| 0 + 4| 16| 5| 3| 1| 4| 4| 0 (1 row) diff --git a/src/test/regress/expected/memoize.out b/src/test/regress/expected/memoize.out index f2a92d1fdfd..4af6bb4ce0f 100644 --- a/src/test/regress/expected/memoize.out +++ b/src/test/regress/expected/memoize.out @@ -43,7 +43,7 @@ WHERE t2.unique1 < 1000;', false); -> Nested Loop (actual rows=1000.00 loops=N) -> Seq Scan on tenk1 t2 (actual rows=1000.00 loops=N) Filter: (unique1 < 1000) - Rows Removed In Table AM by Filter: 9000 + Rows Removed In Executor by Filter: 9000 -> Memoize (actual rows=1.00 loops=N) Cache Key: t2.twenty Cache Mode: logical @@ -75,7 +75,7 @@ WHERE t1.unique1 < 1000;', false); -> Nested Loop (actual rows=1000.00 loops=N) -> Seq Scan on tenk1 t1 (actual rows=1000.00 loops=N) Filter: (unique1 < 1000) - Rows Removed In Table AM by Filter: 9000 + Rows Removed In Executor by Filter: 9000 -> Memoize (actual rows=1.00 loops=N) Cache Key: t1.twenty Cache Mode: binary @@ -146,7 +146,7 @@ WHERE s.c1 = s.c2 AND t1.unique1 < 1000;', false); -> Nested Loop (actual rows=1000.00 loops=N) -> Seq Scan on tenk1 t1 (actual rows=1000.00 loops=N) Filter: (unique1 < 1000) - Rows Removed In Table AM by Filter: 9000 + Rows Removed In Executor by Filter: 9000 -> Memoize (actual rows=1.00 loops=N) Cache Key: (t1.two + 1) Cache Mode: binary @@ -179,16 +179,15 @@ WHERE s.c1 = s.c2 AND t1.unique1 < 1000;', false); -> Nested Loop (actual rows=1000.00 loops=N) -> Seq Scan on tenk1 t1 (actual rows=1000.00 loops=N) Filter: (unique1 < 1000) - Rows Removed In Table AM by Filter: 9000 + Rows Removed In Executor by Filter: 9000 -> Memoize (actual rows=1.00 loops=N) Cache Key: t1.two, t1.twenty Cache Mode: binary Hits: 980 Misses: 20 Evictions: Zero Overflows: 0 Memory Usage: NkB -> Seq Scan on tenk1 t2 (actual rows=1.00 loops=N) Filter: ((t1.twenty = unique1) AND (t1.two = two)) - Rows Removed In Table AM by Filter: 5000 - Rows Removed In Executor by Filter: 4999 -(13 rows) + Rows Removed In Executor by Filter: 9999 +(12 rows) -- And check we get the expected results. SELECT COUNT(*), AVG(t1.twenty) FROM tenk1 t1 LEFT JOIN @@ -247,7 +246,7 @@ WHERE t2.unique1 < 1200;', true); -> Nested Loop (actual rows=1200.00 loops=N) -> Seq Scan on tenk1 t2 (actual rows=1200.00 loops=N) Filter: (unique1 < 1200) - Rows Removed In Table AM by Filter: 8800 + Rows Removed In Executor by Filter: 8800 -> Memoize (actual rows=1.00 loops=N) Cache Key: t2.thousand Cache Mode: logical diff --git a/src/test/regress/expected/merge.out b/src/test/regress/expected/merge.out index f8b9172df20..3029bb6ba10 100644 --- a/src/test/regress/expected/merge.out +++ b/src/test/regress/expected/merge.out @@ -1801,7 +1801,7 @@ WHEN MATCHED AND t.a < 10 THEN Sort Method: quicksort Memory: xxx -> Seq Scan on ex_mtarget t (actual rows=0.00 loops=1) Filter: (a < '-1000'::integer) - Rows Removed In Table AM by Filter: 54 + Rows Removed In Executor by Filter: 54 -> Sort (never executed) Sort Key: s.a -> Seq Scan on ex_msource s (never executed) diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index c633e7089ce..dbbd7b05e11 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -2866,7 +2866,7 @@ explain (analyze, costs off, summary off, timing off, buffers off) execute ab_q6 Filter: ((a = $1) AND (b = (InitPlan 1).col1)) -> Seq Scan on xy_1 (actual rows=0.00 loops=1) Filter: ((x = $1) AND (y = (InitPlan 1).col1)) - Rows Removed In Table AM by Filter: 1 + Rows Removed In Executor by Filter: 1 -> Seq Scan on ab_a1_b1 ab_4 (never executed) Filter: ((a = $1) AND (b = (InitPlan 1).col1)) -> Seq Scan on ab_a1_b2 ab_5 (never executed) @@ -4096,7 +4096,7 @@ select * from listp where a = (select 2) and b <> 10; Seq Scan on listp1 listp (actual rows=0.00 loops=1) Filter: ((b <> 10) AND (a = (InitPlan 1).col1)) InitPlan 1 - -> Result (actual rows=1.00 loops=1) + -> Result (never executed) (4 rows) -- diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out index 572337c7b77..b1e6f5681ac 100644 --- a/src/test/regress/expected/select_parallel.out +++ b/src/test/regress/expected/select_parallel.out @@ -589,13 +589,13 @@ explain (analyze, timing off, summary off, costs off, buffers off) -> Nested Loop (actual rows=98000.00 loops=1) -> Seq Scan on tenk2 (actual rows=10.00 loops=1) Filter: (thousand = 0) - Rows Removed In Table AM by Filter: 9990 + Rows Removed In Executor by Filter: 9990 -> Gather (actual rows=9800.00 loops=10) Workers Planned: 4 Workers Launched: 4 -> Parallel Seq Scan on tenk1 (actual rows=1960.00 loops=50) Filter: (hundred > 1) - Rows Removed In Table AM by Filter: 40 + Rows Removed In Executor by Filter: 40 (11 rows) alter table tenk2 reset (parallel_workers); diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out index 8d513926b3b..095df0a670c 100644 --- a/src/test/regress/expected/updatable_views.out +++ b/src/test/regress/expected/updatable_views.out @@ -2931,6 +2931,7 @@ $$ LANGUAGE plpgsql STRICT IMMUTABLE LEAKPROOF; SELECT * FROM rw_view1 WHERE snoop(person); NOTICE: snooped value: Tom +NOTICE: snooped value: Dick NOTICE: snooped value: Harry person -------- @@ -2940,8 +2941,10 @@ NOTICE: snooped value: Harry UPDATE rw_view1 SET person=person WHERE snoop(person); NOTICE: snooped value: Tom +NOTICE: snooped value: Dick NOTICE: snooped value: Harry DELETE FROM rw_view1 WHERE NOT snoop(person); +NOTICE: snooped value: Dick NOTICE: snooped value: Tom NOTICE: snooped value: Harry ALTER VIEW rw_view1 SET (security_barrier = true); -- 2.39.5