From 7988159e44d7786769bc2cb4fc7ab243e32a31a5 Mon Sep 17 00:00:00 2001 From: Julien Tachoires Date: Mon, 25 Aug 2025 18:23:19 +0200 Subject: [PATCH 2/6] Simple quals push down to table AMs Simple quals like: are now converted to ScanKeys and then passed to the underlying layer via the table AM API. During the execution of sequential scans, the table AM can use the given ScanKeys to filter out the tuples not satisfying the condition before returning them to the executor. Doing this kind of early tuples filtering speed up sequential scans execution time when a large portion of the table must be excluded from the final result. The query planner, via fix_tablequal_references(), is in charge of pre-processing the quals and exclude those that cannot be used as ScanKeys. Pre-processing quals consists in making sure that the key is on left part of the expression, the value is on the right, and both left and right are not relabeled. Non-constant values are registered as run-time keys and then evaluated and converted to ScanKeys when a rescan is requested via ExecReScanSeqScan(). InitPlan (sub-SELECT executed only once) is the only type of SubQuery supported for now. A new instrumention counter is added in order to make the distinction between the tuples excluded by the table AM and those excluded by the executor. The explain command output is modified in that sense too. --- .../postgres_fdw/expected/postgres_fdw.out | 4 +- src/backend/access/heap/heapam.c | 30 +- src/backend/commands/explain.c | 51 ++- src/backend/executor/instrument.c | 1 + src/backend/executor/nodeSeqscan.c | 356 +++++++++++++++++- src/backend/optimizer/plan/createplan.c | 213 ++++++++++- src/include/access/relscan.h | 1 + src/include/executor/instrument.h | 7 +- src/include/executor/nodeSeqscan.h | 3 + src/include/nodes/execnodes.h | 40 +- src/include/nodes/plannodes.h | 2 + src/test/isolation/expected/stats.out | 26 +- src/test/regress/expected/memoize.out | 21 +- src/test/regress/expected/merge.out | 2 +- src/test/regress/expected/partition_prune.out | 28 +- src/test/regress/expected/select_parallel.out | 4 +- src/test/regress/expected/updatable_views.out | 3 - src/test/regress/sql/partition_prune.sql | 2 +- 18 files changed, 701 insertions(+), 93 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index d3323b04676..bc7242835df 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 by Filter: 1 + Rows Removed In Table AM 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) @@ -12225,7 +12225,7 @@ SELECT * FROM async_pt t1 WHERE t1.b === 505 LIMIT 1; Filter: (b === 505) -> Seq Scan on async_p3 t1_3 (actual rows=1.00 loops=1) Filter: (b === 505) - Rows Removed by Filter: 101 + Rows Removed In Executor by Filter: 101 (9 rows) SELECT * FROM async_pt t1 WHERE t1.b === 505 LIMIT 1; diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index a5c74d8948e..71d8e06d8dd 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -347,7 +347,8 @@ bitmapheap_stream_read_next(ReadStream *pgsr, void *private_data, * ---------------- */ static void -initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock) +initscan(HeapScanDesc scan, int nkeys, ScanKey keys, bool keep_startblock, + bool update_stats) { ParallelBlockTableScanDesc bpscan = NULL; bool allow_strat; @@ -456,17 +457,20 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock) /* page-at-a-time fields are always invalid when not rs_inited */ /* - * copy the scan key, if appropriate + * copy the scan keys, if appropriate */ - if (key != NULL && scan->rs_base.rs_nkeys > 0) - memcpy(scan->rs_base.rs_key, key, scan->rs_base.rs_nkeys * sizeof(ScanKeyData)); + if (keys != NULL && nkeys > 0) + { + scan->rs_base.rs_nkeys = nkeys; + memcpy(scan->rs_base.rs_key, keys, nkeys * sizeof(ScanKeyData)); + } /* * Currently, we only have a stats counter for sequential heap scans (but * e.g for bitmap scans the underlying bitmap index scans will be counted, * and for sample scans we update stats for tuple fetches). */ - if (scan->rs_base.rs_flags & SO_TYPE_SEQSCAN) + if (update_stats && (scan->rs_base.rs_flags & SO_TYPE_SEQSCAN)) pgstat_count_heap_scan(scan->rs_base.rs_rd); } @@ -964,7 +968,10 @@ continue_page: if (key != NULL && !HeapKeyTest(tuple, RelationGetDescr(scan->rs_base.rs_rd), nkeys, key)) + { + scan->rs_base.rs_nskip++; continue; + } LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK); scan->rs_coffset = lineoff; @@ -1072,7 +1079,10 @@ continue_page: if (key != NULL && !HeapKeyTest(tuple, RelationGetDescr(scan->rs_base.rs_rd), nkeys, key)) + { + scan->rs_base.rs_nskip++; continue; + } scan->rs_cindex = lineindex; return; @@ -1098,7 +1108,7 @@ continue_page: TableScanDesc heap_beginscan(Relation relation, Snapshot snapshot, - int nkeys, ScanKey key, + int nkeys, ScanKey keys, ParallelTableScanDesc parallel_scan, uint32 flags) { @@ -1132,6 +1142,7 @@ heap_beginscan(Relation relation, Snapshot snapshot, scan->rs_base.rs_rd = relation; scan->rs_base.rs_snapshot = snapshot; scan->rs_base.rs_nkeys = nkeys; + scan->rs_base.rs_nskip = 0; scan->rs_base.rs_flags = flags; scan->rs_base.rs_parallel = parallel_scan; scan->rs_strategy = NULL; /* set in initscan */ @@ -1199,7 +1210,7 @@ heap_beginscan(Relation relation, Snapshot snapshot, else scan->rs_base.rs_key = NULL; - initscan(scan, key, false); + initscan(scan, nkeys, keys, false, true); scan->rs_read_stream = NULL; @@ -1251,7 +1262,7 @@ heap_beginscan(Relation relation, Snapshot snapshot, } void -heap_rescan(TableScanDesc sscan, int nkeys, ScanKey key, bool set_params, +heap_rescan(TableScanDesc sscan, int nkeys, ScanKey keys, bool set_params, bool allow_strat, bool allow_sync, bool allow_pagemode) { HeapScanDesc scan = (HeapScanDesc) sscan; @@ -1297,10 +1308,11 @@ heap_rescan(TableScanDesc sscan, int nkeys, ScanKey key, bool set_params, if (scan->rs_read_stream) read_stream_reset(scan->rs_read_stream); + /* * reinitialize scan descriptor */ - initscan(scan, key, true); + initscan(scan, nkeys, keys, true, false); } void diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 8345bc0264b..a3c8889632a 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -1963,7 +1963,7 @@ ExplainNode(PlanState *planstate, List *ancestors, "Order By", planstate, ancestors, es); show_scan_qual(plan->qual, "Filter", planstate, ancestors, es); if (plan->qual) - show_instrumentation_count("Rows Removed by Filter", 1, + show_instrumentation_count("Rows Removed In Executor by Filter", 1, planstate, es); show_indexsearches_info(planstate, es); break; @@ -1977,7 +1977,7 @@ ExplainNode(PlanState *planstate, List *ancestors, "Order By", planstate, ancestors, es); show_scan_qual(plan->qual, "Filter", planstate, ancestors, es); if (plan->qual) - show_instrumentation_count("Rows Removed by Filter", 1, + show_instrumentation_count("Rows Removed In Executor by Filter", 1, planstate, es); if (es->analyze) ExplainPropertyFloat("Heap Fetches", NULL, @@ -1997,7 +1997,7 @@ ExplainNode(PlanState *planstate, List *ancestors, planstate, es); show_scan_qual(plan->qual, "Filter", planstate, ancestors, es); if (plan->qual) - show_instrumentation_count("Rows Removed by Filter", 1, + show_instrumentation_count("Rows Removed In Executor by Filter", 1, planstate, es); show_tidbitmap_info((BitmapHeapScanState *) planstate, es); break; @@ -2007,6 +2007,15 @@ ExplainNode(PlanState *planstate, List *ancestors, /* fall through to print additional fields the same as SeqScan */ /* FALLTHROUGH */ case T_SeqScan: + show_scan_qual(plan->qual, "Filter", planstate, ancestors, es); + if (plan->qual) + { + show_instrumentation_count("Rows Removed In Table AM by Filter", 3, + planstate, es); + show_instrumentation_count("Rows Removed In Executor by Filter", 1, + planstate, es); + } + break; case T_ValuesScan: case T_CteScan: case T_NamedTuplestoreScan: @@ -2014,7 +2023,7 @@ ExplainNode(PlanState *planstate, List *ancestors, case T_SubqueryScan: show_scan_qual(plan->qual, "Filter", planstate, ancestors, es); if (plan->qual) - show_instrumentation_count("Rows Removed by Filter", 1, + show_instrumentation_count("Rows Removed In Executor by Filter", 1, planstate, es); if (IsA(plan, CteScan)) show_ctescan_info(castNode(CteScanState, planstate), es); @@ -2025,7 +2034,7 @@ ExplainNode(PlanState *planstate, List *ancestors, show_scan_qual(plan->qual, "Filter", planstate, ancestors, es); if (plan->qual) - show_instrumentation_count("Rows Removed by Filter", 1, + show_instrumentation_count("Rows Removed In Executor by Filter", 1, planstate, es); ExplainPropertyInteger("Workers Planned", NULL, gather->num_workers, es); @@ -2049,7 +2058,7 @@ ExplainNode(PlanState *planstate, List *ancestors, show_scan_qual(plan->qual, "Filter", planstate, ancestors, es); if (plan->qual) - show_instrumentation_count("Rows Removed by Filter", 1, + show_instrumentation_count("Rows Removed In Executor by Filter", 1, planstate, es); ExplainPropertyInteger("Workers Planned", NULL, gm->num_workers, es); @@ -2083,7 +2092,7 @@ ExplainNode(PlanState *planstate, List *ancestors, } show_scan_qual(plan->qual, "Filter", planstate, ancestors, es); if (plan->qual) - show_instrumentation_count("Rows Removed by Filter", 1, + show_instrumentation_count("Rows Removed In Executor by Filter", 1, planstate, es); break; case T_TableFuncScan: @@ -2097,7 +2106,7 @@ ExplainNode(PlanState *planstate, List *ancestors, } show_scan_qual(plan->qual, "Filter", planstate, ancestors, es); if (plan->qual) - show_instrumentation_count("Rows Removed by Filter", 1, + show_instrumentation_count("Rows Removed In Executor by Filter", 1, planstate, es); show_table_func_scan_info(castNode(TableFuncScanState, planstate), es); @@ -2115,7 +2124,7 @@ ExplainNode(PlanState *planstate, List *ancestors, show_scan_qual(tidquals, "TID Cond", planstate, ancestors, es); show_scan_qual(plan->qual, "Filter", planstate, ancestors, es); if (plan->qual) - show_instrumentation_count("Rows Removed by Filter", 1, + show_instrumentation_count("Rows Removed In Executor by Filter", 1, planstate, es); } break; @@ -2132,14 +2141,14 @@ ExplainNode(PlanState *planstate, List *ancestors, show_scan_qual(tidquals, "TID Cond", planstate, ancestors, es); show_scan_qual(plan->qual, "Filter", planstate, ancestors, es); if (plan->qual) - show_instrumentation_count("Rows Removed by Filter", 1, + show_instrumentation_count("Rows Removed In Executor by Filter", 1, planstate, es); } break; case T_ForeignScan: show_scan_qual(plan->qual, "Filter", planstate, ancestors, es); if (plan->qual) - show_instrumentation_count("Rows Removed by Filter", 1, + show_instrumentation_count("Rows Removed In Executor by Filter", 1, planstate, es); show_foreignscan_info((ForeignScanState *) planstate, es); break; @@ -2149,7 +2158,7 @@ ExplainNode(PlanState *planstate, List *ancestors, show_scan_qual(plan->qual, "Filter", planstate, ancestors, es); if (plan->qual) - show_instrumentation_count("Rows Removed by Filter", 1, + show_instrumentation_count("Rows Removed In Executor by Filter", 1, planstate, es); if (css->methods->ExplainCustomScan) css->methods->ExplainCustomScan(css, ancestors, es); @@ -2163,7 +2172,7 @@ ExplainNode(PlanState *planstate, List *ancestors, planstate, es); show_upper_qual(plan->qual, "Filter", planstate, ancestors, es); if (plan->qual) - show_instrumentation_count("Rows Removed by Filter", 2, + show_instrumentation_count("Rows Removed In Executor by Filter", 2, planstate, es); break; case T_MergeJoin: @@ -2176,7 +2185,7 @@ ExplainNode(PlanState *planstate, List *ancestors, planstate, es); show_upper_qual(plan->qual, "Filter", planstate, ancestors, es); if (plan->qual) - show_instrumentation_count("Rows Removed by Filter", 2, + show_instrumentation_count("Rows Removed In Executor by Filter", 2, planstate, es); break; case T_HashJoin: @@ -2189,7 +2198,7 @@ ExplainNode(PlanState *planstate, List *ancestors, planstate, es); show_upper_qual(plan->qual, "Filter", planstate, ancestors, es); if (plan->qual) - show_instrumentation_count("Rows Removed by Filter", 2, + show_instrumentation_count("Rows Removed In Executor by Filter", 2, planstate, es); break; case T_Agg: @@ -2197,7 +2206,7 @@ ExplainNode(PlanState *planstate, List *ancestors, show_upper_qual(plan->qual, "Filter", planstate, ancestors, es); show_hashagg_info((AggState *) planstate, es); if (plan->qual) - show_instrumentation_count("Rows Removed by Filter", 1, + show_instrumentation_count("Rows Removed In Executor by Filter", 1, planstate, es); break; case T_WindowAgg: @@ -2206,7 +2215,7 @@ ExplainNode(PlanState *planstate, List *ancestors, "Run Condition", planstate, ancestors, es); show_upper_qual(plan->qual, "Filter", planstate, ancestors, es); if (plan->qual) - show_instrumentation_count("Rows Removed by Filter", 1, + show_instrumentation_count("Rows Removed In Executor by Filter", 1, planstate, es); show_windowagg_info(castNode(WindowAggState, planstate), es); break; @@ -2214,7 +2223,7 @@ ExplainNode(PlanState *planstate, List *ancestors, show_group_keys(castNode(GroupState, planstate), ancestors, es); show_upper_qual(plan->qual, "Filter", planstate, ancestors, es); if (plan->qual) - show_instrumentation_count("Rows Removed by Filter", 1, + show_instrumentation_count("Rows Removed In Executor by Filter", 1, planstate, es); break; case T_Sort: @@ -2236,7 +2245,7 @@ ExplainNode(PlanState *planstate, List *ancestors, "One-Time Filter", planstate, ancestors, es); show_upper_qual(plan->qual, "Filter", planstate, ancestors, es); if (plan->qual) - show_instrumentation_count("Rows Removed by Filter", 1, + show_instrumentation_count("Rows Removed In Executor by Filter", 1, planstate, es); break; case T_ModifyTable: @@ -3990,7 +3999,9 @@ show_instrumentation_count(const char *qlabel, int which, if (!es->analyze || !planstate->instrument) return; - if (which == 2) + if (which == 3) + nfiltered = planstate->instrument->nfiltered3; + else if (which == 2) nfiltered = planstate->instrument->nfiltered2; else nfiltered = planstate->instrument->nfiltered1; diff --git a/src/backend/executor/instrument.c b/src/backend/executor/instrument.c index 56e635f4700..e9ddf39c42a 100644 --- a/src/backend/executor/instrument.c +++ b/src/backend/executor/instrument.c @@ -186,6 +186,7 @@ InstrAggNode(Instrumentation *dst, Instrumentation *add) dst->nloops += add->nloops; dst->nfiltered1 += add->nfiltered1; dst->nfiltered2 += add->nfiltered2; + dst->nfiltered3 += add->nfiltered3; /* Add delta of buffer usage since entry to node's totals */ if (dst->need_bufusage) diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c index c89aa6c6616..0562377c42a 100644 --- a/src/backend/executor/nodeSeqscan.c +++ b/src/backend/executor/nodeSeqscan.c @@ -29,9 +29,12 @@ #include "access/relscan.h" #include "access/tableam.h" +#include "executor/execExpr.h" #include "executor/execScan.h" #include "executor/executor.h" #include "executor/nodeSeqscan.h" +#include "nodes/nodeFuncs.h" +#include "utils/lsyscache.h" #include "utils/rel.h" static TupleTableSlot *SeqNext(SeqScanState *node); @@ -41,6 +44,146 @@ static TupleTableSlot *SeqNext(SeqScanState *node); * ---------------------------------------------------------------- */ +/* ---------------------------------------------------------------- + * ExecSeqBuildScanKeys + * + * Builds the scan keys pushed to the table AM API. Scan keys + * are used to filter out tuples before returning them to the + * executor, based on the quals list. + * ---------------------------------------------------------------- + */ +static void +ExecSeqBuildScanKeys(PlanState *planstate, List *quals, int *numScanKeys, + ScanKey *scanKeys, SeqScanRuntimeKeyInfo * *runtimeKeys, + int *numRuntimeKeys) +{ + ListCell *qual_cell; + ScanKey scan_keys; + int n_scan_keys = 0; + int n_quals; + SeqScanRuntimeKeyInfo *runtime_keys; + int n_runtime_keys; + int max_runtime_keys; + + n_quals = list_length(quals); + + /* + * If quals list is empty we have nothing to do. + */ + if (n_quals == 0) + return; + + /* + * Allocate an array of ScanKeyData structs: one per qual. + * + * Note: when we cannot convert all the quals to ScanKeys, then we waste + * some memory but this avoids memory reallocation on the fly. + */ + scan_keys = (ScanKey) palloc(n_quals * sizeof(ScanKeyData)); + + /* + * run-time_keys array is dynamically resized as needed. Caller must be + * sure to pass in NULL/0 for first call. + */ + runtime_keys = *runtimeKeys; + n_runtime_keys = max_runtime_keys = *numRuntimeKeys; + + foreach(qual_cell, quals) + { + Expr *clause = (Expr *) lfirst(qual_cell); + ScanKey this_scan_key = &scan_keys[n_scan_keys]; + RegProcedure opfuncid; /* operator proc id used in scan */ + Expr *leftop; /* expr on lhs of operator */ + Expr *rightop; /* expr on rhs ... */ + AttrNumber varattno; /* att number used in scan */ + + /* + * Simple qual case: + */ + if (IsA(clause, OpExpr)) + { + int flags = 0; + Datum scanvalue; + + opfuncid = ((OpExpr *) clause)->opfuncid; + + /* + * leftop and rightop are not relabeled and can be used as they + * are because they have been pre-computed by + * fix_tablequal_references(), so, the key Var is always on the + * left. + */ + leftop = (Expr *) get_leftop(clause); + rightop = (Expr *) get_rightop(clause); + + varattno = ((Var *) leftop)->varattno; + + if (IsA(rightop, Const)) + { + /* + * OK, simple constant comparison value + */ + scanvalue = ((Const *) rightop)->constvalue; + if (((Const *) rightop)->constisnull) + flags |= SK_ISNULL; + } + else + { + /* Need to treat this one as a run-time key */ + if (n_runtime_keys >= max_runtime_keys) + { + if (max_runtime_keys == 0) + { + max_runtime_keys = 8; + runtime_keys = (SeqScanRuntimeKeyInfo *) + palloc(max_runtime_keys * sizeof(SeqScanRuntimeKeyInfo)); + } + else + { + max_runtime_keys *= 2; + runtime_keys = (SeqScanRuntimeKeyInfo *) + repalloc(runtime_keys, + max_runtime_keys * sizeof(SeqScanRuntimeKeyInfo)); + } + } + runtime_keys[n_runtime_keys].scan_key = this_scan_key; + runtime_keys[n_runtime_keys].key_expr = + ExecInitExpr(rightop, planstate); + runtime_keys[n_runtime_keys].key_toastable = + TypeIsToastable(((Var *) leftop)->vartype); + n_runtime_keys++; + scanvalue = (Datum) 0; + } + + n_scan_keys++; + + ScanKeyEntryInitialize(this_scan_key, + flags, + varattno, + InvalidStrategy, /* no strategy */ + InvalidOid, /* no subtype */ + ((OpExpr *) clause)->inputcollid, + opfuncid, + scanvalue); + } + else + { + /* + * Unsupported qual, then do not push it to the table AM. + */ + continue; + } + } + + /* + * Return info to our caller. + */ + *scanKeys = scan_keys; + *numScanKeys = n_scan_keys; + *runtimeKeys = runtime_keys; + *numRuntimeKeys = n_runtime_keys; +} + /* ---------------------------------------------------------------- * SeqNext * @@ -71,15 +214,47 @@ SeqNext(SeqScanState *node) */ scandesc = table_beginscan(node->ss.ss_currentRelation, estate->es_snapshot, - 0, NULL); + node->sss_NumScanKeys, + node->sss_ScanKeys); node->ss.ss_currentScanDesc = scandesc; + + /* + * If no run-time key to calculate or if they are ready to use, go + * ahead and pass the ScanKeys to the table AM. + */ + if (node->sss_NumRuntimeKeys == 0 || node->sss_RuntimeKeysReady) + table_rescan(node->ss.ss_currentScanDesc, node->sss_NumScanKeys, + node->sss_ScanKeys); } /* * get the next tuple from the table */ if (table_scan_getnextslot(scandesc, direction, slot)) + { + /* + * Update the instrumentation counter in charge of tracking the number + * of tuples skipped during table/seq scan. + * + * Note: it seems necessary to do it after getting each tuple only + * when the table scan is executed by the postgres_fdw. In all other + * cases, we can update the counter only once when there is no next + * tuple to return. + */ + InstrCountFiltered3(node, scandesc->rs_nskip); + + /* + * We have to reset the local counter once the instrumentation counter + * has been updated. + */ + scandesc->rs_nskip = 0; + return slot; + } + + InstrCountFiltered3(node, scandesc->rs_nskip); + scandesc->rs_nskip = 0; + return NULL; } @@ -115,6 +290,15 @@ ExecSeqScan(PlanState *pstate) Assert(pstate->qual == NULL); Assert(pstate->ps_ProjInfo == NULL); + /* + * If we have run-time keys and they've not already been set up, do it + * now. + */ + if (node->sss_NumRuntimeKeys != 0 && !node->sss_RuntimeKeysReady) + { + ExecReScan((PlanState *) node); + } + return ExecScanExtended(&node->ss, (ExecScanAccessMtd) SeqNext, (ExecScanRecheckMtd) SeqRecheck, @@ -139,6 +323,15 @@ ExecSeqScanWithQual(PlanState *pstate) pg_assume(pstate->qual != NULL); Assert(pstate->ps_ProjInfo == NULL); + /* + * If we have run-time keys and they've not already been set up, do it + * now. + */ + if (node->sss_NumRuntimeKeys != 0 && !node->sss_RuntimeKeysReady) + { + ExecReScan((PlanState *) node); + } + return ExecScanExtended(&node->ss, (ExecScanAccessMtd) SeqNext, (ExecScanRecheckMtd) SeqRecheck, @@ -159,6 +352,15 @@ ExecSeqScanWithProject(PlanState *pstate) Assert(pstate->qual == NULL); pg_assume(pstate->ps_ProjInfo != NULL); + /* + * If we have run-time keys and they've not already been set up, do it + * now. + */ + if (node->sss_NumRuntimeKeys != 0 && !node->sss_RuntimeKeysReady) + { + ExecReScan((PlanState *) node); + } + return ExecScanExtended(&node->ss, (ExecScanAccessMtd) SeqNext, (ExecScanRecheckMtd) SeqRecheck, @@ -180,6 +382,15 @@ ExecSeqScanWithQualProject(PlanState *pstate) pg_assume(pstate->qual != NULL); pg_assume(pstate->ps_ProjInfo != NULL); + /* + * If we have run-time keys and they've not already been set up, do it + * now. + */ + if (node->sss_NumRuntimeKeys != 0 && !node->sss_RuntimeKeysReady) + { + ExecReScan((PlanState *) node); + } + return ExecScanExtended(&node->ss, (ExecScanAccessMtd) SeqNext, (ExecScanRecheckMtd) SeqRecheck, @@ -198,6 +409,15 @@ ExecSeqScanEPQ(PlanState *pstate) { SeqScanState *node = castNode(SeqScanState, pstate); + /* + * If we have run-time keys and they've not already been set up, do it + * now. + */ + if (node->sss_NumRuntimeKeys != 0 && !node->sss_RuntimeKeysReady) + { + ExecReScan((PlanState *) node); + } + return ExecScan(&node->ss, (ExecScanAccessMtd) SeqNext, (ExecScanRecheckMtd) SeqRecheck); @@ -225,6 +445,11 @@ ExecInitSeqScan(SeqScan *node, EState *estate, int eflags) scanstate = makeNode(SeqScanState); scanstate->ss.ps.plan = (Plan *) node; scanstate->ss.ps.state = estate; + scanstate->sss_ScanKeys = NULL; + scanstate->sss_NumScanKeys = 0; + scanstate->sss_RuntimeKeysReady = false; + scanstate->sss_RuntimeKeys = NULL; + scanstate->sss_NumRuntimeKeys = 0; /* * Miscellaneous initialization @@ -258,6 +483,14 @@ 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); + /* * When EvalPlanQual() is not in use, assign ExecProcNode for this node * based on the presence of qual and projection. Each ExecSeqScan*() @@ -280,6 +513,24 @@ ExecInitSeqScan(SeqScan *node, EState *estate, int eflags) scanstate->ss.ps.ExecProcNode = ExecSeqScanWithQualProject; } + /* + * If we have runtime keys, we need an ExprContext to evaluate them. The + * node's standard context won't do because we want to reset that context + * for every tuple. So, build another context just like the other one... + */ + if (scanstate->sss_NumRuntimeKeys != 0) + { + ExprContext *stdecontext = scanstate->ss.ps.ps_ExprContext; + + ExecAssignExprContext(estate, &scanstate->ss.ps); + scanstate->sss_RuntimeContext = scanstate->ss.ps.ps_ExprContext; + scanstate->ss.ps.ps_ExprContext = stdecontext; + } + else + { + scanstate->sss_RuntimeContext = NULL; + } + return scanstate; } @@ -322,16 +573,91 @@ ExecReScanSeqScan(SeqScanState *node) { TableScanDesc scan; + /* + * If we are doing runtime key calculations (ie, any of the scan key + * values weren't simple Consts), compute the new key values. But first, + * reset the context so we don't leak memory as each outer tuple is + * scanned. Note this assumes that we will recalculate *all* runtime keys + * on each call. + */ + if (node->sss_NumRuntimeKeys != 0) + { + ExprContext *econtext = node->sss_RuntimeContext; + + ResetExprContext(econtext); + ExecSeqScanEvalRuntimeKeys(econtext, + node->sss_RuntimeKeys, + node->sss_NumRuntimeKeys); + } + node->sss_RuntimeKeysReady = true; + + scan = node->ss.ss_currentScanDesc; if (scan != NULL) table_rescan(scan, /* scan desc */ - 0, /* number of scan keys */ - NULL); /* new scan keys */ + node->sss_NumScanKeys, /* number of scan keys */ + node->sss_ScanKeys); /* scan keys */ ExecScanReScan((ScanState *) node); } +/* ---------------------------------------------------------------- + * ExecSeqScanEvalRuntimeKeys + * + * Evaluate any run-time key values, and update the scankeys. + * ---------------------------------------------------------------- + */ +void +ExecSeqScanEvalRuntimeKeys(ExprContext *econtext, + SeqScanRuntimeKeyInfo * runtimeKeys, + int numRuntimeKeys) +{ + int j; + MemoryContext oldContext; + + /* We want to keep the key values in per-tuple memory */ + oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); + + for (j = 0; j < numRuntimeKeys; j++) + { + ScanKey scan_key = runtimeKeys[j].scan_key; + ExprState *key_expr = runtimeKeys[j].key_expr; + Datum scanvalue; + bool isNull; + + /* + * For each run-time key, extract the run-time expression and evaluate + * it with respect to the current context. We then stick the result + * into the proper scan key. + * + * Note: the result of the eval could be a pass-by-ref value that's + * stored in some outer scan's tuple, not in + * econtext->ecxt_per_tuple_memory. We assume that the outer tuple + * will stay put throughout our scan. If this is wrong, we could copy + * the result into our context explicitly, but I think that's not + * necessary. + */ + scanvalue = ExecEvalExpr(key_expr, + econtext, + &isNull); + if (isNull) + { + scan_key->sk_argument = scanvalue; + scan_key->sk_flags |= SK_ISNULL; + } + else + { + if (runtimeKeys[j].key_toastable) + scanvalue = PointerGetDatum(PG_DETOAST_DATUM(scanvalue)); + scan_key->sk_argument = scanvalue; + scan_key->sk_flags &= ~SK_ISNULL; + } + } + + MemoryContextSwitchTo(oldContext); +} + /* ---------------------------------------------------------------- * Parallel Scan Support * ---------------------------------------------------------------- @@ -375,7 +701,17 @@ ExecSeqScanInitializeDSM(SeqScanState *node, estate->es_snapshot); shm_toc_insert(pcxt->toc, node->ss.ps.plan->plan_node_id, pscan); node->ss.ss_currentScanDesc = - table_beginscan_parallel(node->ss.ss_currentRelation, pscan, 0, NULL); + table_beginscan_parallel(node->ss.ss_currentRelation, pscan, + node->sss_NumScanKeys, + node->sss_ScanKeys); + + /* + * If no run-time keys to calculate or they are ready, go ahead and pass + * the scankeys to the table AM. + */ + if (node->sss_NumRuntimeKeys == 0 || node->sss_RuntimeKeysReady) + table_rescan(node->ss.ss_currentScanDesc, node->sss_NumScanKeys, + node->sss_ScanKeys); } /* ---------------------------------------------------------------- @@ -408,5 +744,15 @@ ExecSeqScanInitializeWorker(SeqScanState *node, pscan = shm_toc_lookup(pwcxt->toc, node->ss.ps.plan->plan_node_id, false); node->ss.ss_currentScanDesc = - table_beginscan_parallel(node->ss.ss_currentRelation, pscan, 0, NULL); + table_beginscan_parallel(node->ss.ss_currentRelation, pscan, + node->sss_NumScanKeys, + node->sss_ScanKeys); + + /* + * If no run-time keys to calculate or they are ready, go ahead and pass + * the scankeys to the table AM. + */ + if (node->sss_NumRuntimeKeys == 0 || node->sss_RuntimeKeysReady) + table_rescan(node->ss.ss_currentScanDesc, node->sss_NumScanKeys, + node->sss_ScanKeys); } diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 6791cbeb416..bb0856ac0bc 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -20,6 +20,7 @@ #include "access/sysattr.h" #include "catalog/pg_class.h" +#include "executor/executor.h" #include "foreign/fdwapi.h" #include "miscadmin.h" #include "nodes/extensible.h" @@ -40,8 +41,10 @@ #include "optimizer/tlist.h" #include "parser/parse_clause.h" #include "parser/parsetree.h" +#include "parser/parse_relation.h" #include "partitioning/partprune.h" #include "tcop/tcopprot.h" +#include "utils/acl.h" #include "utils/lsyscache.h" @@ -170,6 +173,8 @@ static Node *fix_indexqual_clause(PlannerInfo *root, IndexOptInfo *index, int indexcol, Node *clause, List *indexcolnos); static Node *fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol); +static void fix_tablequal_references(PlannerInfo *root, Path *best_path, + List *scan_clauses, List **fixed_tablequals_p); static List *get_switched_clauses(List *clauses, Relids outerrelids); static List *order_qual_clauses(PlannerInfo *root, List *clauses); static void copy_generic_path_info(Plan *dest, Path *src); @@ -178,7 +183,7 @@ static void label_sort_with_costsize(PlannerInfo *root, Sort *plan, double limit_tuples); static void label_incrementalsort_with_costsize(PlannerInfo *root, IncrementalSort *plan, List *pathkeys, double limit_tuples); -static SeqScan *make_seqscan(List *qptlist, List *qpqual, Index scanrelid); +static SeqScan *make_seqscan(List *qptlist, List *qpqual, Index scanrelid, List *tablequal); static SampleScan *make_samplescan(List *qptlist, List *qpqual, Index scanrelid, TableSampleClause *tsc); static IndexScan *make_indexscan(List *qptlist, List *qpqual, Index scanrelid, @@ -2760,10 +2765,16 @@ create_seqscan_plan(PlannerInfo *root, Path *best_path, { SeqScan *scan_plan; Index scan_relid = best_path->parent->relid; + List *fixed_tablequals = NIL; + RangeTblEntry *rte; + RTEPermissionInfo *perminfo; + bool do_fix_tablequal_ref = true; /* it should be a base rel... */ Assert(scan_relid > 0); + rte = planner_rt_fetch(scan_relid, root); Assert(best_path->parent->rtekind == RTE_RELATION); + Assert(rte->rtekind == RTE_RELATION); /* Sort clauses into best execution order */ scan_clauses = order_qual_clauses(root, scan_clauses); @@ -2778,9 +2789,25 @@ create_seqscan_plan(PlannerInfo *root, Path *best_path, replace_nestloop_params(root, (Node *) scan_clauses); } + /* + * Check relation permission before doing any preliminary work on quals. + * If the permissions can't be checked, then we won't do unnecessary work + * related to quals push down. + */ + if (rte->perminfoindex != 0) + { + perminfo = getRTEPermissionInfo(root->parse->rteperminfos, rte); + if (!ExecCheckOneRelPerms(perminfo)) + do_fix_tablequal_ref = false; + } + + if (do_fix_tablequal_ref) + fix_tablequal_references(root, best_path, scan_clauses, &fixed_tablequals); + scan_plan = make_seqscan(tlist, scan_clauses, - scan_relid); + scan_relid, + fixed_tablequals); copy_generic_path_info(&scan_plan->scan.plan, best_path); @@ -5172,6 +5199,184 @@ fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol) return NULL; /* keep compiler quiet */ } +/* + * Check if the right part of a qual can be used in a ScanKey that will + * later be pushed down during sequential scan. + */ +static bool inline +check_tablequal_rightop(Expr *rightop) +{ + switch (nodeTag((Node *) rightop)) + { + /* Supported nodes */ + case T_Const: + case T_Param: + break; + + /* + * In case of function expression, make sure function args do not + * contain any reference to the table being scanned. + */ + case T_FuncExpr: + { + FuncExpr *func = (FuncExpr *) rightop; + ListCell *temp; + + foreach(temp, func->args) + { + Node *arg = lfirst(temp); + + if (IsA(arg, Var) && ((Var *) arg)->varattno > 0) + return false; + } + + break; + } + + /* + * In case of Var, check if this is an attribute of a relation, + * which is not supported. + */ + case T_Var: + { + if (((Var *) rightop)->varattno > 0) + return false; + break; + } + /* Unsupported nodes */ + default: + return false; + break; + } + + return true; +} + +/* + * fix_tablequal_references + * Precompute scan clauses in order to pass them ready to be pushed down by + * the executor during table scan. + * + * We do left/right commutation if needed because we want to keep the scan key + * on left. + */ +static void +fix_tablequal_references(PlannerInfo *root, Path *best_path, + List *scan_clauses, List **fixed_tablequals_p) +{ + List *fixed_tablequals; + ListCell *lc; + + fixed_tablequals = NIL; + + scan_clauses = (List *) replace_nestloop_params(root, (Node *) scan_clauses); + + foreach(lc, scan_clauses) + { + /* + * Let work with a "deep" copy of the original scan clause in order to + * avoid any update on the initial scan clause. + */ + Expr *clause = (Expr *) copyObject(lfirst(lc)); + + switch (nodeTag((Node *) clause)) + { + /* + * Simple qual case: + */ + case T_OpExpr: + { + OpExpr *opexpr = (OpExpr *) clause; + Expr *leftop; + Expr *rightop; + + leftop = (Expr *) get_leftop(clause); + rightop = (Expr *) get_rightop(clause); + + if (leftop && IsA(leftop, RelabelType)) + leftop = ((RelabelType *) leftop)->arg; + + if (rightop && IsA(rightop, RelabelType)) + rightop = ((RelabelType *) rightop)->arg; + + if (leftop == NULL || rightop == NULL) + continue; + + /* + * Ignore qual if the operator is user defined + */ + if (opexpr->opno >= FirstNormalObjectId) + continue; + + /* + * Ignore qual if the function is not leakproof + */ + if (!get_func_leakproof(opexpr->opfuncid)) + continue; + + /* + * Commute left and right if needed and reflect those + * changes on the clause, this way, the executor won't + * have to check positions of Var and Const/other: Var is + * always on the left while Const/other is on the right. + */ + if (IsA(rightop, Var) && !IsA(leftop, Var) + && ((Var *) rightop)->varattno > 0) + { + Expr *tmpop = leftop; + Oid commutator; + + leftop = rightop; + rightop = tmpop; + + commutator = get_commutator(opexpr->opno); + + if (OidIsValid(commutator)) + { + opexpr->opno = commutator; + opexpr->opfuncid = get_opcode(opexpr->opno); + } + else + { + /* + * If we don't have any commutator function + * available for this operator, then ignore the + * qual because we cannot commute it. + */ + continue; + } + } + + /* + * Make sure our left part is a Var referencing an + * attribute. + */ + if (!(IsA(leftop, Var) && ((Var *) leftop)->varattno > 0)) + continue; + + if (!check_tablequal_rightop(rightop)) + continue; + + /* + * Even if there is no left/right commutation, update the + * clause in order to avoid unnecessary checks by the + * executor. + */ + list_free(opexpr->args); + opexpr->args = list_make2(leftop, rightop); + + /* Append the modified clause to fixed_tablequals */ + fixed_tablequals = lappend(fixed_tablequals, clause); + break; + } + default: + continue; + } + } + + *fixed_tablequals_p = fixed_tablequals; +} + /* * get_switched_clauses * Given a list of merge or hash joinclauses (as RestrictInfo nodes), @@ -5484,7 +5689,8 @@ bitmap_subplan_mark_shared(Plan *plan) static SeqScan * make_seqscan(List *qptlist, List *qpqual, - Index scanrelid) + Index scanrelid, + List *tablequal) { SeqScan *node = makeNode(SeqScan); Plan *plan = &node->scan.plan; @@ -5494,6 +5700,7 @@ make_seqscan(List *qptlist, plan->lefttree = NULL; plan->righttree = NULL; node->scan.scanrelid = scanrelid; + node->tablequal = tablequal; return node; } diff --git a/src/include/access/relscan.h b/src/include/access/relscan.h index b5e0fb386c0..9549bc29f38 100644 --- a/src/include/access/relscan.h +++ b/src/include/access/relscan.h @@ -65,6 +65,7 @@ typedef struct TableScanDescData struct ParallelTableScanDescData *rs_parallel; /* parallel scan * information */ + uint64 rs_nskip; /* number of tuples skipped during table scan */ } TableScanDescData; typedef struct TableScanDescData *TableScanDesc; diff --git a/src/include/executor/instrument.h b/src/include/executor/instrument.h index 03653ab6c6c..8e07a57a767 100644 --- a/src/include/executor/instrument.h +++ b/src/include/executor/instrument.h @@ -87,8 +87,11 @@ typedef struct Instrumentation double ntuples; /* total tuples produced */ double ntuples2; /* secondary node-specific tuple counter */ double nloops; /* # of run cycles for this node */ - double nfiltered1; /* # of tuples removed by scanqual or joinqual */ - double nfiltered2; /* # of tuples removed by "other" quals */ + double nfiltered1; /* # of tuples in executor removed by scanqual + * or joinqual */ + double nfiltered2; /* # of tuples in executor removed by "other" + * quals */ + double nfiltered3; /* # of tuples in table AM removed by quals */ BufferUsage bufusage; /* total buffer usage */ WalUsage walusage; /* total WAL usage */ } Instrumentation; diff --git a/src/include/executor/nodeSeqscan.h b/src/include/executor/nodeSeqscan.h index 3adad8b585b..6285ebc0e58 100644 --- a/src/include/executor/nodeSeqscan.h +++ b/src/include/executor/nodeSeqscan.h @@ -20,6 +20,9 @@ extern SeqScanState *ExecInitSeqScan(SeqScan *node, EState *estate, int eflags); extern void ExecEndSeqScan(SeqScanState *node); extern void ExecReScanSeqScan(SeqScanState *node); +extern void ExecSeqScanEvalRuntimeKeys(ExprContext *econtext, + SeqScanRuntimeKeyInfo * runtimeKeys, + int numRuntimeKeys); /* parallel scan support */ extern void ExecSeqScanEstimate(SeqScanState *node, ParallelContext *pcxt); diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index de782014b2d..ec2d42c57b4 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1273,6 +1273,11 @@ typedef struct PlanState if (((PlanState *)(node))->instrument) \ ((PlanState *)(node))->instrument->nfiltered2 += (delta); \ } while(0) +#define InstrCountFiltered3(node, delta) \ + do { \ + if (((PlanState *)(node))->instrument) \ + ((PlanState *)(node))->instrument->nfiltered3 += (delta); \ + } while(0) /* * EPQState is state for executing an EvalPlanQual recheck on a candidate @@ -1621,14 +1626,38 @@ typedef struct ScanState TupleTableSlot *ss_ScanTupleSlot; } ScanState; +typedef struct RuntimeKeyInfo +{ + struct ScanKeyData *scan_key; /* scankey to put value into */ + ExprState *key_expr; /* expr to evaluate to get value */ + bool key_toastable; /* is expr's result a toastable datatype? */ +} RuntimeKeyInfo; + +typedef struct RuntimeKeyInfo SeqScanRuntimeKeyInfo; + /* ---------------- * SeqScanState information + * + * ss its first field is NodeTag + * pscan_len size of parallel heap scan descriptor + * sss_ScanKeys Skeys array used to push down quals + * sss_NumScanKeys number of Skeys + * sss_RuntimeKeys info about Skeys that must be evaluated at runtime + * sss_NumRuntimeKeys number of RuntimeKeys + * sss_RuntimeKeysReady true if runtime Skeys have been computed + * sss_RuntimeContext expr context for evaling runtime Skeys * ---------------- */ typedef struct SeqScanState { - ScanState ss; /* its first field is NodeTag */ - Size pscan_len; /* size of parallel heap scan descriptor */ + ScanState ss; + Size pscan_len; + struct ScanKeyData *sss_ScanKeys; + int sss_NumScanKeys; + SeqScanRuntimeKeyInfo *sss_RuntimeKeys; + int sss_NumRuntimeKeys; + bool sss_RuntimeKeysReady; + ExprContext *sss_RuntimeContext; } SeqScanState; /* ---------------- @@ -1657,12 +1686,7 @@ typedef struct SampleScanState * constant right-hand sides. See comments for ExecIndexBuildScanKeys() * for discussion. */ -typedef struct -{ - struct ScanKeyData *scan_key; /* scankey to put value into */ - ExprState *key_expr; /* expr to evaluate to get value */ - bool key_toastable; /* is expr's result a toastable datatype? */ -} IndexRuntimeKeyInfo; +typedef struct RuntimeKeyInfo IndexRuntimeKeyInfo; typedef struct { diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 29d7732d6a0..595bb7f5e5a 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -501,6 +501,8 @@ typedef struct Scan typedef struct SeqScan { Scan scan; + /* list of quals (usually OpExprs) pushed down to the table AM */ + List *tablequal; } SeqScan; /* ---------------- diff --git a/src/test/isolation/expected/stats.out b/src/test/isolation/expected/stats.out index 8c7fe60217e..0064c0c8df0 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| 6| 1| 1| 0| 1| 1| 0 + 3| 5| 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| 5| 2| 0| 1| 1| 1| 0 + 3| 4| 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| 9| 2| 1| 1| 1| 2| 0 + 5| 7| 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| 31| 4| 5| 1| 3| 6| 0 + 9| 13| 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| 31| 4| 5| 1| 3| 6| 0 + 9| 13| 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| 29| 4| 5| 1| 1| 8| 0 + 9| 11| 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| 29| 4| 5| 1| 1| 8| 0 + 9| 11| 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| 9| 5| 1| 0| 1| 1| 0 + 3| 3| 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| 9| 5| 1| 0| 1| 1| 0 + 3| 3| 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| 9| 4| 2| 0| 4| 2| 0 + 3| 3| 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| 9| 4| 2| 0| 4| 2| 0 + 3| 3| 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| 16| 5| 3| 1| 4| 4| 0 + 4| 4| 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| 16| 5| 3| 1| 4| 4| 0 + 4| 4| 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 150dc1b44cf..f2a92d1fdfd 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 by Filter: 9000 + Rows Removed In Table AM 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 by Filter: 9000 + Rows Removed In Table AM by Filter: 9000 -> Memoize (actual rows=1.00 loops=N) Cache Key: t1.twenty Cache Mode: binary @@ -117,7 +117,7 @@ WHERE t1.unique1 < 10;', false); Hits: 8 Misses: 2 Evictions: Zero Overflows: 0 Memory Usage: NkB -> Subquery Scan on t2 (actual rows=2.00 loops=N) Filter: (t1.two = t2.two) - Rows Removed by Filter: 2 + Rows Removed In Executor by Filter: 2 -> Index Scan using tenk1_unique1 on tenk1 t2_1 (actual rows=4.00 loops=N) Index Cond: (unique1 < 4) Index Searches: N @@ -146,14 +146,14 @@ 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 by Filter: 9000 + Rows Removed In Table AM by Filter: 9000 -> Memoize (actual rows=1.00 loops=N) Cache Key: (t1.two + 1) Cache Mode: binary Hits: 998 Misses: 2 Evictions: Zero Overflows: 0 Memory Usage: NkB -> Index Only Scan using tenk1_unique1 on tenk1 t2 (actual rows=1.00 loops=N) Filter: ((t1.two + 1) = unique1) - Rows Removed by Filter: 9999 + Rows Removed In Executor by Filter: 9999 Heap Fetches: N Index Searches: N (14 rows) @@ -179,15 +179,16 @@ 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 by Filter: 9000 + Rows Removed In Table AM 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 by Filter: 9999 -(12 rows) + Rows Removed In Table AM by Filter: 5000 + Rows Removed In Executor by Filter: 4999 +(13 rows) -- And check we get the expected results. SELECT COUNT(*), AVG(t1.twenty) FROM tenk1 t1 LEFT JOIN @@ -246,7 +247,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 by Filter: 8800 + Rows Removed In Table AM by Filter: 8800 -> Memoize (actual rows=1.00 loops=N) Cache Key: t2.thousand Cache Mode: logical @@ -522,7 +523,7 @@ WHERE t2.a IS NULL;', false); Hits: 97 Misses: 3 Evictions: Zero Overflows: 0 Memory Usage: NkB -> Subquery Scan on t2 (actual rows=0.67 loops=N) Filter: ((t1.a + 1) = t2.a) - Rows Removed by Filter: 2 + Rows Removed In Executor by Filter: 2 -> Unique (actual rows=2.67 loops=N) -> Sort (actual rows=67.33 loops=N) Sort Key: t2_1.a diff --git a/src/test/regress/expected/merge.out b/src/test/regress/expected/merge.out index cf2219df754..f8b9172df20 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 by Filter: 54 + Rows Removed In Table AM 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 d1966cd7d82..c633e7089ce 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -2333,16 +2333,16 @@ explain (analyze, costs off, summary off, timing off, buffers off) select * from Append (actual rows=0.00 loops=1) -> Seq Scan on list_part1 list_part_1 (actual rows=0.00 loops=1) Filter: (a = (list_part_fn(1) + a)) - Rows Removed by Filter: 1 + Rows Removed In Executor by Filter: 1 -> Seq Scan on list_part2 list_part_2 (actual rows=0.00 loops=1) Filter: (a = (list_part_fn(1) + a)) - Rows Removed by Filter: 1 + Rows Removed In Executor by Filter: 1 -> Seq Scan on list_part3 list_part_3 (actual rows=0.00 loops=1) Filter: (a = (list_part_fn(1) + a)) - Rows Removed by Filter: 1 + Rows Removed In Executor by Filter: 1 -> Seq Scan on list_part4 list_part_4 (actual rows=0.00 loops=1) Filter: (a = (list_part_fn(1) + a)) - Rows Removed by Filter: 1 + Rows Removed In Executor by Filter: 1 (13 rows) rollback; @@ -2368,7 +2368,7 @@ begin loop ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); ln := regexp_replace(ln, 'actual rows=\d+(?:\.\d+)? loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, 'Rows Removed by Filter: \d+', 'Rows Removed by Filter: N'); + ln := regexp_replace(ln, 'Rows Removed In Executor by Filter: \d+', 'Rows Removed In Executor by Filter: N'); perform regexp_matches(ln, 'Index Searches: \d+'); if found then continue; @@ -2610,7 +2610,7 @@ select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on -> Nested Loop (actual rows=N loops=N) -> Parallel Seq Scan on lprt_a a (actual rows=N loops=N) Filter: (a = ANY ('{1,0,0}'::integer[])) - Rows Removed by Filter: N + Rows Removed In Executor by Filter: N -> Append (actual rows=N loops=N) -> Index Scan using ab_a1_b1_a_idx on ab_a1_b1 ab_1 (actual rows=N loops=N) Index Cond: (a = a.a) @@ -2644,7 +2644,7 @@ select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on -> Nested Loop (actual rows=N loops=N) -> Parallel Seq Scan on lprt_a a (actual rows=N loops=N) Filter: (a = ANY ('{1,0,0}'::integer[])) - Rows Removed by Filter: N + Rows Removed In Executor by Filter: N -> Append (actual rows=N loops=N) -> Index Scan using ab_a1_b1_a_idx on ab_a1_b1 ab_1 (never executed) Index Cond: (a = a.a) @@ -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 by Filter: 1 + Rows Removed In Table AM 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) @@ -3529,7 +3529,7 @@ select * from boolp where a = (select value from boolvalues where value); InitPlan 1 -> Seq Scan on boolvalues (actual rows=1.00 loops=1) Filter: value - Rows Removed by Filter: 1 + Rows Removed In Executor by Filter: 1 -> Seq Scan on boolp_f boolp_1 (never executed) Filter: (a = (InitPlan 1).col1) -> Seq Scan on boolp_t boolp_2 (actual rows=0.00 loops=1) @@ -3544,7 +3544,7 @@ select * from boolp where a = (select value from boolvalues where not value); InitPlan 1 -> Seq Scan on boolvalues (actual rows=1.00 loops=1) Filter: (NOT value) - Rows Removed by Filter: 1 + Rows Removed In Executor by Filter: 1 -> Seq Scan on boolp_f boolp_1 (actual rows=0.00 loops=1) Filter: (a = (InitPlan 1).col1) -> Seq Scan on boolp_t boolp_2 (never executed) @@ -3573,11 +3573,11 @@ explain (analyze, costs off, summary off, timing off, buffers off) execute mt_q1 Subplans Removed: 1 -> Index Scan using ma_test_p2_b_idx on ma_test_p2 ma_test_1 (actual rows=1.00 loops=1) Filter: ((a >= $1) AND ((a % 10) = 5)) - Rows Removed by Filter: 9 + Rows Removed In Executor by Filter: 9 Index Searches: 1 -> Index Scan using ma_test_p3_b_idx on ma_test_p3 ma_test_2 (actual rows=1.00 loops=1) Filter: ((a >= $1) AND ((a % 10) = 5)) - Rows Removed by Filter: 9 + Rows Removed In Executor by Filter: 9 Index Searches: 1 (11 rows) @@ -3596,7 +3596,7 @@ explain (analyze, costs off, summary off, timing off, buffers off) execute mt_q1 Subplans Removed: 2 -> Index Scan using ma_test_p3_b_idx on ma_test_p3 ma_test_1 (actual rows=1.00 loops=1) Filter: ((a >= $1) AND ((a % 10) = 5)) - Rows Removed by Filter: 9 + Rows Removed In Executor by Filter: 9 Index Searches: 1 (7 rows) @@ -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 (never executed) + -> Result (actual rows=1.00 loops=1) (4 rows) -- diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out index 0185ef661b1..572337c7b77 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 by Filter: 9990 + Rows Removed In Table AM 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 by Filter: 40 + Rows Removed In Table AM 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 095df0a670c..8d513926b3b 100644 --- a/src/test/regress/expected/updatable_views.out +++ b/src/test/regress/expected/updatable_views.out @@ -2931,7 +2931,6 @@ $$ 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 -------- @@ -2941,10 +2940,8 @@ 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); diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql index d93c0c03bab..b939d725e91 100644 --- a/src/test/regress/sql/partition_prune.sql +++ b/src/test/regress/sql/partition_prune.sql @@ -587,7 +587,7 @@ begin loop ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); ln := regexp_replace(ln, 'actual rows=\d+(?:\.\d+)? loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, 'Rows Removed by Filter: \d+', 'Rows Removed by Filter: N'); + ln := regexp_replace(ln, 'Rows Removed In Executor by Filter: \d+', 'Rows Removed In Executor by Filter: N'); perform regexp_matches(ln, 'Index Searches: \d+'); if found then continue; -- 2.39.5