diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 2be14c5437..eff7b04f11 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -1602,7 +1602,6 @@ SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL 20 | 0 | AAA020 (10 rows) -SET enable_resultcache TO off; -- right outer join + left outer join EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; @@ -1629,7 +1628,6 @@ SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT 20 | 0 | AAA020 (10 rows) -RESET enable_resultcache; -- left outer join + right outer join EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; @@ -2141,25 +2139,22 @@ SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 -- join with lateral reference EXPLAIN (VERBOSE, COSTS OFF) SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10; - QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1."C 1" -> Nested Loop Output: t1."C 1" -> Index Scan using t1_pkey on "S 1"."T 1" t1 Output: t1."C 1", t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 - -> Result Cache - Cache Key: t1.c2 - -> Subquery Scan on q - -> HashAggregate - Output: t2.c1, t3.c1 - Group Key: t2.c1, t3.c1 - -> Foreign Scan - Output: t2.c1, t3.c1 - Relations: (public.ft1 t2) INNER JOIN (public.ft2 t3) - Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r1.c2 = $1::integer)))) -(16 rows) + -> HashAggregate + Output: t2.c1, t3.c1 + Group Key: t2.c1, t3.c1 + -> Foreign Scan + Output: t2.c1, t3.c1 + Relations: (public.ft1 t2) INNER JOIN (public.ft2 t3) + Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r1.c2 = $1::integer)))) +(13 rows) SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10; C 1 diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql index 21a29cc062..806a5bca28 100644 --- a/contrib/postgres_fdw/sql/postgres_fdw.sql +++ b/contrib/postgres_fdw/sql/postgres_fdw.sql @@ -502,12 +502,10 @@ SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; -SET enable_resultcache TO off; -- right outer join + left outer join EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; -RESET enable_resultcache; -- left outer join + right outer join EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 9d87b5097a..d1e2e8c4c3 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -1770,9 +1770,8 @@ include_dir 'conf.d' fact in mind when choosing the value. Sort operations are used for ORDER BY, DISTINCT, and merge joins. - Hash tables are used in hash joins, hash-based aggregation, result - cache nodes and hash-based processing of IN - subqueries. + Hash tables are used in hash joins, hash-based aggregation, and + hash-based processing of IN subqueries. Hash-based operations are generally more sensitive to memory @@ -4926,25 +4925,6 @@ ANY num_sync ( - enable_resultcache (boolean) - - enable_resultcache configuration parameter - - - - - Enables or disables the query planner's use of result cache plans for - caching results from parameterized scans inside nested-loop joins. - This plan type allows scans to the underlying plans to be skipped when - the results for the current parameters are already in the cache. Less - commonly looked up results may be evicted from the cache when more - space is required for new entries. The default is - on. - - - - enable_mergejoin (boolean) diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index b0582d7826..872aaa7aed 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -108,8 +108,6 @@ static void show_sort_info(SortState *sortstate, ExplainState *es); static void show_incremental_sort_info(IncrementalSortState *incrsortstate, ExplainState *es); static void show_hash_info(HashState *hashstate, ExplainState *es); -static void show_resultcache_info(ResultCacheState *rcstate, List *ancestors, - ExplainState *es); static void show_hashagg_info(AggState *hashstate, ExplainState *es); static void show_tidbitmap_info(BitmapHeapScanState *planstate, ExplainState *es); @@ -1286,9 +1284,6 @@ ExplainNode(PlanState *planstate, List *ancestors, case T_Material: pname = sname = "Materialize"; break; - case T_ResultCache: - pname = sname = "Result Cache"; - break; case T_Sort: pname = sname = "Sort"; break; @@ -2001,10 +1996,6 @@ ExplainNode(PlanState *planstate, List *ancestors, case T_Hash: show_hash_info(castNode(HashState, planstate), es); break; - case T_ResultCache: - show_resultcache_info(castNode(ResultCacheState, planstate), - ancestors, es); - break; default: break; } @@ -3072,137 +3063,6 @@ show_hash_info(HashState *hashstate, ExplainState *es) } } -/* - * Show information on result cache hits/misses/evictions and memory usage. - */ -static void -show_resultcache_info(ResultCacheState *rcstate, List *ancestors, ExplainState *es) -{ - Plan *plan = ((PlanState *) rcstate)->plan; - ListCell *lc; - List *context; - StringInfoData keystr; - char *seperator = ""; - bool useprefix; - int64 memPeakKb; - - initStringInfo(&keystr); - - /* - * It's hard to imagine having a result cache with fewer than 2 RTEs, but - * let's just keep the same useprefix logic as elsewhere in this file. - */ - useprefix = list_length(es->rtable) > 1 || es->verbose; - - /* Set up deparsing context */ - context = set_deparse_context_plan(es->deparse_cxt, - plan, - ancestors); - - foreach(lc, ((ResultCache *) plan)->param_exprs) - { - Node *expr = (Node *) lfirst(lc); - - appendStringInfoString(&keystr, seperator); - - appendStringInfoString(&keystr, deparse_expression(expr, context, - useprefix, false)); - seperator = ", "; - } - - if (es->format != EXPLAIN_FORMAT_TEXT) - { - ExplainPropertyText("Cache Key", keystr.data, es); - } - else - { - ExplainIndentText(es); - appendStringInfo(es->str, "Cache Key: %s\n", keystr.data); - } - - pfree(keystr.data); - - if (!es->analyze) - return; - - /* - * mem_peak is only set when we freed memory, so we must use mem_used when - * mem_peak is 0. - */ - if (rcstate->stats.mem_peak > 0) - memPeakKb = (rcstate->stats.mem_peak + 1023) / 1024; - else - memPeakKb = (rcstate->mem_used + 1023) / 1024; - - if (es->format != EXPLAIN_FORMAT_TEXT) - { - ExplainPropertyInteger("Cache Hits", NULL, rcstate->stats.cache_hits, es); - ExplainPropertyInteger("Cache Misses", NULL, rcstate->stats.cache_misses, es); - ExplainPropertyInteger("Cache Evictions", NULL, rcstate->stats.cache_evictions, es); - ExplainPropertyInteger("Cache Overflows", NULL, rcstate->stats.cache_overflows, es); - ExplainPropertyInteger("Peak Memory Usage", "kB", memPeakKb, es); - } - else - { - ExplainIndentText(es); - appendStringInfo(es->str, - "Hits: " UINT64_FORMAT " Misses: " UINT64_FORMAT " Evictions: " UINT64_FORMAT " Overflows: " UINT64_FORMAT " Memory Usage: " INT64_FORMAT "kB\n", - rcstate->stats.cache_hits, - rcstate->stats.cache_misses, - rcstate->stats.cache_evictions, - rcstate->stats.cache_overflows, - memPeakKb); - } - - if (rcstate->shared_info == NULL) - return; - - /* Show details from parallel workers */ - for (int n = 0; n < rcstate->shared_info->num_workers; n++) - { - ResultCacheInstrumentation *si; - - si = &rcstate->shared_info->sinstrument[n]; - - if (es->workers_state) - ExplainOpenWorker(n, es); - - /* - * Since the worker's ResultCacheState.mem_used field is unavailable - * to us, ExecEndResultCache will have set the - * ResultCacheInstrumentation.mem_peak field for us. No need to do - * the zero checks like we did for the serial case above. - */ - memPeakKb = (si->mem_peak + 1023) / 1024; - - if (es->format == EXPLAIN_FORMAT_TEXT) - { - ExplainIndentText(es); - appendStringInfo(es->str, - "Hits: " UINT64_FORMAT " Misses: " UINT64_FORMAT " Evictions: " UINT64_FORMAT " Overflows: " UINT64_FORMAT " Memory Usage: " INT64_FORMAT "kB\n", - si->cache_hits, si->cache_misses, - si->cache_evictions, si->cache_overflows, - memPeakKb); - } - else - { - ExplainPropertyInteger("Cache Hits", NULL, - si->cache_hits, es); - ExplainPropertyInteger("Cache Misses", NULL, - si->cache_misses, es); - ExplainPropertyInteger("Cache Evictions", NULL, - si->cache_evictions, es); - ExplainPropertyInteger("Cache Overflows", NULL, - si->cache_overflows, es); - ExplainPropertyInteger("Peak Memory Usage", "kB", memPeakKb, - es); - } - - if (es->workers_state) - ExplainCloseWorker(n, es); - } -} - /* * Show information on hash aggregate memory usage and batches. */ diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile index f08b282a5e..680fd69151 100644 --- a/src/backend/executor/Makefile +++ b/src/backend/executor/Makefile @@ -61,7 +61,6 @@ OBJS = \ nodeProjectSet.o \ nodeRecursiveunion.o \ nodeResult.o \ - nodeResultCache.o \ nodeSamplescan.o \ nodeSeqscan.o \ nodeSetOp.o \ diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c index b3726a54f3..58a8aa5ab7 100644 --- a/src/backend/executor/execAmi.c +++ b/src/backend/executor/execAmi.c @@ -44,7 +44,6 @@ #include "executor/nodeProjectSet.h" #include "executor/nodeRecursiveunion.h" #include "executor/nodeResult.h" -#include "executor/nodeResultCache.h" #include "executor/nodeSamplescan.h" #include "executor/nodeSeqscan.h" #include "executor/nodeSetOp.h" @@ -255,10 +254,6 @@ ExecReScan(PlanState *node) ExecReScanMaterial((MaterialState *) node); break; - case T_ResultCacheState: - ExecReScanResultCache((ResultCacheState *) node); - break; - case T_SortState: ExecReScanSort((SortState *) node); break; diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index 23c0fb9379..e33231f7be 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -3696,137 +3696,3 @@ ExecBuildGroupingEqual(TupleDesc ldesc, TupleDesc rdesc, return state; } - -/* - * Build equality expression that can be evaluated using ExecQual(), returning - * true if the expression context's inner/outer tuples are equal. Datums in - * the inner/outer slots are assumed to be in the same order and quantity as - * the 'eqfunctions' parameter. NULLs are treated as equal. - * - * desc: tuple descriptor of the to-be-compared tuples - * lops: the slot ops for the inner tuple slots - * rops: the slot ops for the outer tuple slots - * eqFunctions: array of function oids of the equality functions to use - * this must be the same length as the 'param_exprs' list. - * collations: collation Oids to use for equality comparison. Must be the - * same length as the 'param_exprs' list. - * parent: parent executor node - */ -ExprState * -ExecBuildParamSetEqual(TupleDesc desc, - const TupleTableSlotOps *lops, - const TupleTableSlotOps *rops, - const Oid *eqfunctions, - const Oid *collations, - const List *param_exprs, - PlanState *parent) -{ - ExprState *state = makeNode(ExprState); - ExprEvalStep scratch = {0}; - int maxatt = list_length(param_exprs); - List *adjust_jumps = NIL; - ListCell *lc; - - state->expr = NULL; - state->flags = EEO_FLAG_IS_QUAL; - state->parent = parent; - - scratch.resvalue = &state->resvalue; - scratch.resnull = &state->resnull; - - /* push deform steps */ - scratch.opcode = EEOP_INNER_FETCHSOME; - scratch.d.fetch.last_var = maxatt; - scratch.d.fetch.fixed = false; - scratch.d.fetch.known_desc = desc; - scratch.d.fetch.kind = lops; - if (ExecComputeSlotInfo(state, &scratch)) - ExprEvalPushStep(state, &scratch); - - scratch.opcode = EEOP_OUTER_FETCHSOME; - scratch.d.fetch.last_var = maxatt; - scratch.d.fetch.fixed = false; - scratch.d.fetch.known_desc = desc; - scratch.d.fetch.kind = rops; - if (ExecComputeSlotInfo(state, &scratch)) - ExprEvalPushStep(state, &scratch); - - for (int attno = 0; attno < maxatt; attno++) - { - Form_pg_attribute att = TupleDescAttr(desc, attno); - Oid foid = eqfunctions[attno]; - Oid collid = collations[attno]; - FmgrInfo *finfo; - FunctionCallInfo fcinfo; - AclResult aclresult; - - /* Check permission to call function */ - aclresult = pg_proc_aclcheck(foid, GetUserId(), ACL_EXECUTE); - if (aclresult != ACLCHECK_OK) - aclcheck_error(aclresult, OBJECT_FUNCTION, get_func_name(foid)); - - InvokeFunctionExecuteHook(foid); - - /* Set up the primary fmgr lookup information */ - finfo = palloc0(sizeof(FmgrInfo)); - fcinfo = palloc0(SizeForFunctionCallInfo(2)); - fmgr_info(foid, finfo); - fmgr_info_set_expr(NULL, finfo); - InitFunctionCallInfoData(*fcinfo, finfo, 2, - collid, NULL, NULL); - - /* left arg */ - scratch.opcode = EEOP_INNER_VAR; - scratch.d.var.attnum = attno; - scratch.d.var.vartype = att->atttypid; - scratch.resvalue = &fcinfo->args[0].value; - scratch.resnull = &fcinfo->args[0].isnull; - ExprEvalPushStep(state, &scratch); - - /* right arg */ - scratch.opcode = EEOP_OUTER_VAR; - scratch.d.var.attnum = attno; - scratch.d.var.vartype = att->atttypid; - scratch.resvalue = &fcinfo->args[1].value; - scratch.resnull = &fcinfo->args[1].isnull; - ExprEvalPushStep(state, &scratch); - - /* evaluate distinctness */ - scratch.opcode = EEOP_NOT_DISTINCT; - scratch.d.func.finfo = finfo; - scratch.d.func.fcinfo_data = fcinfo; - scratch.d.func.fn_addr = finfo->fn_addr; - scratch.d.func.nargs = 2; - scratch.resvalue = &state->resvalue; - scratch.resnull = &state->resnull; - ExprEvalPushStep(state, &scratch); - - /* then emit EEOP_QUAL to detect if result is false (or null) */ - scratch.opcode = EEOP_QUAL; - scratch.d.qualexpr.jumpdone = -1; - scratch.resvalue = &state->resvalue; - scratch.resnull = &state->resnull; - ExprEvalPushStep(state, &scratch); - adjust_jumps = lappend_int(adjust_jumps, - state->steps_len - 1); - } - - /* adjust jump targets */ - foreach(lc, adjust_jumps) - { - ExprEvalStep *as = &state->steps[lfirst_int(lc)]; - - Assert(as->opcode == EEOP_QUAL); - Assert(as->d.qualexpr.jumpdone == -1); - as->d.qualexpr.jumpdone = state->steps_len; - } - - scratch.resvalue = NULL; - scratch.resnull = NULL; - scratch.opcode = EEOP_DONE; - ExprEvalPushStep(state, &scratch); - - ExecReadyExpr(state); - - return state; -} diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c index 366d0b20b9..c95d5170e4 100644 --- a/src/backend/executor/execParallel.c +++ b/src/backend/executor/execParallel.c @@ -35,7 +35,6 @@ #include "executor/nodeIncrementalSort.h" #include "executor/nodeIndexonlyscan.h" #include "executor/nodeIndexscan.h" -#include "executor/nodeResultCache.h" #include "executor/nodeSeqscan.h" #include "executor/nodeSort.h" #include "executor/nodeSubplan.h" @@ -293,10 +292,6 @@ ExecParallelEstimate(PlanState *planstate, ExecParallelEstimateContext *e) /* even when not parallel-aware, for EXPLAIN ANALYZE */ ExecAggEstimate((AggState *) planstate, e->pcxt); break; - case T_ResultCacheState: - /* even when not parallel-aware, for EXPLAIN ANALYZE */ - ExecResultCacheEstimate((ResultCacheState *) planstate, e->pcxt); - break; default: break; } @@ -517,10 +512,6 @@ ExecParallelInitializeDSM(PlanState *planstate, /* even when not parallel-aware, for EXPLAIN ANALYZE */ ExecAggInitializeDSM((AggState *) planstate, d->pcxt); break; - case T_ResultCacheState: - /* even when not parallel-aware, for EXPLAIN ANALYZE */ - ExecResultCacheInitializeDSM((ResultCacheState *) planstate, d->pcxt); - break; default: break; } @@ -997,7 +988,6 @@ ExecParallelReInitializeDSM(PlanState *planstate, case T_HashState: case T_SortState: case T_IncrementalSortState: - case T_ResultCacheState: /* these nodes have DSM state, but no reinitialization is required */ break; @@ -1067,9 +1057,6 @@ ExecParallelRetrieveInstrumentation(PlanState *planstate, case T_AggState: ExecAggRetrieveInstrumentation((AggState *) planstate); break; - case T_ResultCacheState: - ExecResultCacheRetrieveInstrumentation((ResultCacheState *) planstate); - break; default: break; } @@ -1362,11 +1349,6 @@ ExecParallelInitializeWorker(PlanState *planstate, ParallelWorkerContext *pwcxt) /* even when not parallel-aware, for EXPLAIN ANALYZE */ ExecAggInitializeWorker((AggState *) planstate, pwcxt); break; - case T_ResultCacheState: - /* even when not parallel-aware, for EXPLAIN ANALYZE */ - ExecResultCacheInitializeWorker((ResultCacheState *) planstate, - pwcxt); - break; default: break; } diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c index 9f8c7582e0..29766d8196 100644 --- a/src/backend/executor/execProcnode.c +++ b/src/backend/executor/execProcnode.c @@ -102,7 +102,6 @@ #include "executor/nodeProjectSet.h" #include "executor/nodeRecursiveunion.h" #include "executor/nodeResult.h" -#include "executor/nodeResultCache.h" #include "executor/nodeSamplescan.h" #include "executor/nodeSeqscan.h" #include "executor/nodeSetOp.h" @@ -326,11 +325,6 @@ ExecInitNode(Plan *node, EState *estate, int eflags) estate, eflags); break; - case T_ResultCache: - result = (PlanState *) ExecInitResultCache((ResultCache *) node, - estate, eflags); - break; - case T_Group: result = (PlanState *) ExecInitGroup((Group *) node, estate, eflags); @@ -719,10 +713,6 @@ ExecEndNode(PlanState *node) ExecEndIncrementalSort((IncrementalSortState *) node); break; - case T_ResultCacheState: - ExecEndResultCache((ResultCacheState *) node); - break; - case T_GroupState: ExecEndGroup((GroupState *) node); break; diff --git a/src/backend/executor/nodeResultCache.c b/src/backend/executor/nodeResultCache.c deleted file mode 100644 index 906b68c945..0000000000 --- a/src/backend/executor/nodeResultCache.c +++ /dev/null @@ -1,1137 +0,0 @@ -/*------------------------------------------------------------------------- - * - * nodeResultCache.c - * Routines to handle caching of results from parameterized nodes - * - * Portions Copyright (c) 2021, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California - * - * - * IDENTIFICATION - * src/backend/executor/nodeResultCache.c - * - * ResultCache nodes are intended to sit above parameterized nodes in the plan - * tree in order to cache results from them. The intention here is that a - * repeat scan with a parameter value that has already been seen by the node - * can fetch tuples from the cache rather than having to re-scan the outer - * node all over again. The query planner may choose to make use of one of - * these when it thinks rescans for previously seen values are likely enough - * to warrant adding the additional node. - * - * The method of cache we use is a hash table. When the cache fills, we never - * spill tuples to disk, instead, we choose to evict the least recently used - * cache entry from the cache. We remember the least recently used entry by - * always pushing new entries and entries we look for onto the tail of a - * doubly linked list. This means that older items always bubble to the top - * of this LRU list. - * - * Sometimes our callers won't run their scans to completion. For example a - * semi-join only needs to run until it finds a matching tuple, and once it - * does, the join operator skips to the next outer tuple and does not execute - * the inner side again on that scan. Because of this, we must keep track of - * when a cache entry is complete, and by default, we know it is when we run - * out of tuples to read during the scan. However, there are cases where we - * can mark the cache entry as complete without exhausting the scan of all - * tuples. One case is unique joins, where the join operator knows that there - * will only be at most one match for any given outer tuple. In order to - * support such cases we allow the "singlerow" option to be set for the cache. - * This option marks the cache entry as complete after we read the first tuple - * from the subnode. - * - * It's possible when we're filling the cache for a given set of parameters - * that we're unable to free enough memory to store any more tuples. If this - * happens then we'll have already evicted all other cache entries. When - * caching another tuple would cause us to exceed our memory budget, we must - * free the entry that we're currently populating and move the state machine - * into RC_CACHE_BYPASS_MODE. This means that we'll not attempt to cache any - * further tuples for this particular scan. We don't have the memory for it. - * The state machine will be reset again on the next rescan. If the memory - * requirements to cache the next parameter's tuples are less demanding, then - * that may allow us to start putting useful entries back into the cache - * again. - * - * - * INTERFACE ROUTINES - * ExecResultCache - lookup cache, exec subplan when not found - * ExecInitResultCache - initialize node and subnodes - * ExecEndResultCache - shutdown node and subnodes - * ExecReScanResultCache - rescan the result cache - * - * ExecResultCacheEstimate estimates DSM space needed for parallel plan - * ExecResultCacheInitializeDSM initialize DSM for parallel plan - * ExecResultCacheInitializeWorker attach to DSM info in parallel worker - * ExecResultCacheRetrieveInstrumentation get instrumentation from worker - *------------------------------------------------------------------------- - */ - -#include "postgres.h" - -#include "access/parallel.h" -#include "common/hashfn.h" -#include "executor/executor.h" -#include "executor/nodeResultCache.h" -#include "lib/ilist.h" -#include "miscadmin.h" -#include "utils/lsyscache.h" - -/* States of the ExecResultCache state machine */ -#define RC_CACHE_LOOKUP 1 /* Attempt to perform a cache lookup */ -#define RC_CACHE_FETCH_NEXT_TUPLE 2 /* Get another tuple from the cache */ -#define RC_FILLING_CACHE 3 /* Read outer node to fill cache */ -#define RC_CACHE_BYPASS_MODE 4 /* Bypass mode. Just read from our - * subplan without caching anything */ -#define RC_END_OF_SCAN 5 /* Ready for rescan */ - - -/* Helper macros for memory accounting */ -#define EMPTY_ENTRY_MEMORY_BYTES(e) (sizeof(ResultCacheEntry) + \ - sizeof(ResultCacheKey) + \ - (e)->key->params->t_len); -#define CACHE_TUPLE_BYTES(t) (sizeof(ResultCacheTuple) + \ - (t)->mintuple->t_len) - - /* ResultCacheTuple Stores an individually cached tuple */ -typedef struct ResultCacheTuple -{ - MinimalTuple mintuple; /* Cached tuple */ - struct ResultCacheTuple *next; /* The next tuple with the same parameter - * values or NULL if it's the last one */ -} ResultCacheTuple; - -/* - * ResultCacheKey - * The hash table key for cached entries plus the LRU list link - */ -typedef struct ResultCacheKey -{ - MinimalTuple params; - dlist_node lru_node; /* Pointer to next/prev key in LRU list */ -} ResultCacheKey; - -/* - * ResultCacheEntry - * The data struct that the cache hash table stores - */ -typedef struct ResultCacheEntry -{ - ResultCacheKey *key; /* Hash key for hash table lookups */ - ResultCacheTuple *tuplehead; /* Pointer to the first tuple or NULL if - * no tuples are cached for this entry */ - uint32 hash; /* Hash value (cached) */ - char status; /* Hash status */ - bool complete; /* Did we read the outer plan to completion? */ -} ResultCacheEntry; - - -#define SH_PREFIX resultcache -#define SH_ELEMENT_TYPE ResultCacheEntry -#define SH_KEY_TYPE ResultCacheKey * -#define SH_SCOPE static inline -#define SH_DECLARE -#include "lib/simplehash.h" - -static uint32 ResultCacheHash_hash(struct resultcache_hash *tb, - const ResultCacheKey *key); -static int ResultCacheHash_equal(struct resultcache_hash *tb, - const ResultCacheKey *params1, - const ResultCacheKey *params2); - -#define SH_PREFIX resultcache -#define SH_ELEMENT_TYPE ResultCacheEntry -#define SH_KEY_TYPE ResultCacheKey * -#define SH_KEY key -#define SH_HASH_KEY(tb, key) ResultCacheHash_hash(tb, key) -#define SH_EQUAL(tb, a, b) (ResultCacheHash_equal(tb, a, b) == 0) -#define SH_SCOPE static inline -#define SH_STORE_HASH -#define SH_GET_HASH(tb, a) a->hash -#define SH_DEFINE -#include "lib/simplehash.h" - -/* - * ResultCacheHash_hash - * Hash function for simplehash hashtable. 'key' is unused here as we - * require that all table lookups first populate the ResultCacheState's - * probeslot with the key values to be looked up. - */ -static uint32 -ResultCacheHash_hash(struct resultcache_hash *tb, const ResultCacheKey *key) -{ - ResultCacheState *rcstate = (ResultCacheState *) tb->private_data; - TupleTableSlot *pslot = rcstate->probeslot; - uint32 hashkey = 0; - int numkeys = rcstate->nkeys; - FmgrInfo *hashfunctions = rcstate->hashfunctions; - Oid *collations = rcstate->collations; - - for (int i = 0; i < numkeys; i++) - { - /* rotate hashkey left 1 bit at each step */ - hashkey = (hashkey << 1) | ((hashkey & 0x80000000) ? 1 : 0); - - if (!pslot->tts_isnull[i]) /* treat nulls as having hash key 0 */ - { - uint32 hkey; - - hkey = DatumGetUInt32(FunctionCall1Coll(&hashfunctions[i], - collations[i], pslot->tts_values[i])); - hashkey ^= hkey; - } - } - - return murmurhash32(hashkey); -} - -/* - * ResultCacheHash_equal - * Equality function for confirming hash value matches during a hash - * table lookup. 'key2' is never used. Instead the ResultCacheState's - * probeslot is always populated with details of what's being looked up. - */ -static int -ResultCacheHash_equal(struct resultcache_hash *tb, const ResultCacheKey *key1, - const ResultCacheKey *key2) -{ - ResultCacheState *rcstate = (ResultCacheState *) tb->private_data; - ExprContext *econtext = rcstate->ss.ps.ps_ExprContext; - TupleTableSlot *tslot = rcstate->tableslot; - TupleTableSlot *pslot = rcstate->probeslot; - - /* probeslot should have already been prepared by prepare_probe_slot() */ - - ExecStoreMinimalTuple(key1->params, tslot, false); - - econtext->ecxt_innertuple = tslot; - econtext->ecxt_outertuple = pslot; - return !ExecQualAndReset(rcstate->cache_eq_expr, econtext); -} - -/* - * Initialize the hash table to empty. - */ -static void -build_hash_table(ResultCacheState *rcstate, uint32 size) -{ - /* Make a guess at a good size when we're not given a valid size. */ - if (size == 0) - size = 1024; - - /* resultcache_create will convert the size to a power of 2 */ - rcstate->hashtable = resultcache_create(rcstate->tableContext, size, - rcstate); -} - -/* - * prepare_probe_slot - * Populate rcstate's probeslot with the values from the tuple stored - * in 'key'. If 'key' is NULL, then perform the population by evaluating - * rcstate's param_exprs. - */ -static inline void -prepare_probe_slot(ResultCacheState *rcstate, ResultCacheKey *key) -{ - TupleTableSlot *pslot = rcstate->probeslot; - TupleTableSlot *tslot = rcstate->tableslot; - int numKeys = rcstate->nkeys; - - ExecClearTuple(pslot); - - if (key == NULL) - { - /* Set the probeslot's values based on the current parameter values */ - for (int i = 0; i < numKeys; i++) - pslot->tts_values[i] = ExecEvalExpr(rcstate->param_exprs[i], - rcstate->ss.ps.ps_ExprContext, - &pslot->tts_isnull[i]); - } - else - { - /* Process the key's MinimalTuple and store the values in probeslot */ - ExecStoreMinimalTuple(key->params, tslot, false); - slot_getallattrs(tslot); - memcpy(pslot->tts_values, tslot->tts_values, sizeof(Datum) * numKeys); - memcpy(pslot->tts_isnull, tslot->tts_isnull, sizeof(bool) * numKeys); - } - - ExecStoreVirtualTuple(pslot); -} - -/* - * entry_purge_tuples - * Remove all tuples from the cache entry pointed to by 'entry'. This - * leaves an empty cache entry. Also, update the memory accounting to - * reflect the removal of the tuples. - */ -static inline void -entry_purge_tuples(ResultCacheState *rcstate, ResultCacheEntry *entry) -{ - ResultCacheTuple *tuple = entry->tuplehead; - uint64 freed_mem = 0; - - while (tuple != NULL) - { - ResultCacheTuple *next = tuple->next; - - freed_mem += CACHE_TUPLE_BYTES(tuple); - - /* Free memory used for this tuple */ - pfree(tuple->mintuple); - pfree(tuple); - - tuple = next; - } - - entry->complete = false; - entry->tuplehead = NULL; - - /* Update the memory accounting */ - rcstate->mem_used -= freed_mem; - - Assert(rcstate->mem_used >= 0); -} - -/* - * remove_cache_entry - * Remove 'entry' from the cache and free memory used by it. - */ -static void -remove_cache_entry(ResultCacheState *rcstate, ResultCacheEntry *entry) -{ - ResultCacheKey *key = entry->key; - - dlist_delete(&entry->key->lru_node); - -#ifdef USE_ASSERT_CHECKING - /* - * Validate the memory accounting code is correct in assert builds. XXX is - * this too expensive for USE_ASSERT_CHECKING? - */ - { - int i, - count; - uint64 mem = 0; - - count = 0; - for (i = 0; i < rcstate->hashtable->size; i++) - { - ResultCacheEntry *entry = &rcstate->hashtable->data[i]; - - if (entry->status == resultcache_SH_IN_USE) - { - ResultCacheTuple *tuple = entry->tuplehead; - - mem += EMPTY_ENTRY_MEMORY_BYTES(entry); - while (tuple != NULL) - { - mem += CACHE_TUPLE_BYTES(tuple); - tuple = tuple->next; - } - count++; - } - } - - Assert(count == rcstate->hashtable->members); - Assert(mem == rcstate->mem_used); - } -#endif - - /* Remove all of the tuples from this entry */ - entry_purge_tuples(rcstate, entry); - - /* - * Update memory accounting. entry_purge_tuples should have already - * subtracted the memory used for each cached tuple. Here we just update - * the amount used by the entry itself. - */ - rcstate->mem_used -= EMPTY_ENTRY_MEMORY_BYTES(entry); - - Assert(rcstate->mem_used >= 0); - - /* Remove the entry from the cache */ - resultcache_delete_item(rcstate->hashtable, entry); - - pfree(key->params); - pfree(key); -} - -/* - * cache_reduce_memory - * Evict older and less recently used items from the cache in order to - * reduce the memory consumption back to something below the - * ResultCacheState's mem_limit. - * - * 'specialkey', if not NULL, causes the function to return false if the entry - * which the key belongs to is removed from the cache. - */ -static bool -cache_reduce_memory(ResultCacheState *rcstate, ResultCacheKey *specialkey) -{ - bool specialkey_intact = true; /* for now */ - dlist_mutable_iter iter; - uint64 evictions = 0; - - /* Update peak memory usage */ - if (rcstate->mem_used > rcstate->stats.mem_peak) - rcstate->stats.mem_peak = rcstate->mem_used; - - /* We expect only to be called when we've gone over budget on memory */ - Assert(rcstate->mem_used > rcstate->mem_limit); - - /* Start the eviction process starting at the head of the LRU list. */ - dlist_foreach_modify(iter, &rcstate->lru_list) - { - ResultCacheKey *key = dlist_container(ResultCacheKey, lru_node, - iter.cur); - ResultCacheEntry *entry; - - /* - * Populate the hash probe slot in preparation for looking up this LRU - * entry. - */ - prepare_probe_slot(rcstate, key); - - /* - * Ideally the LRU list pointers would be stored in the entry itself - * rather than in the key. Unfortunately, we can't do that as the - * simplehash.h code may resize the table and allocate new memory for - * entries which would result in those pointers pointing to the old - * buckets. However, it's fine to use the key to store this as that's - * only referenced by a pointer in the entry, which of course follows - * the entry whenever the hash table is resized. Since we only have a - * pointer to the key here, we must perform a hash table lookup to - * find the entry that the key belongs to. - */ - entry = resultcache_lookup(rcstate->hashtable, NULL); - - /* A good spot to check for corruption of the table and LRU list. */ - Assert(entry != NULL); - Assert(entry->key == key); - - /* - * If we're being called to free memory while the cache is being - * populated with new tuples, then we'd better take some care as we - * could end up freeing the entry which 'specialkey' belongs to. - * Generally callers will pass 'specialkey' as the key for the cache - * entry which is currently being populated, so we must set - * 'specialkey_intact' to false to inform the caller the specialkey - * entry has been removed. - */ - if (key == specialkey) - specialkey_intact = false; - - /* - * Finally remove the entry. This will remove from the LRU list too. - */ - remove_cache_entry(rcstate, entry); - - evictions++; - - /* Exit if we've freed enough memory */ - if (rcstate->mem_used <= rcstate->mem_limit) - break; - } - - rcstate->stats.cache_evictions += evictions; /* Update Stats */ - - return specialkey_intact; -} - -/* - * cache_lookup - * Perform a lookup to see if we've already cached results based on the - * scan's current parameters. If we find an existing entry we move it to - * the end of the LRU list, set *found to true then return it. If we - * don't find an entry then we create a new one and add it to the end of - * the LRU list. We also update cache memory accounting and remove older - * entries if we go over the memory budget. If we managed to free enough - * memory we return the new entry, else we return NULL. - * - * Callers can assume we'll never return NULL when *found is true. - */ -static ResultCacheEntry * -cache_lookup(ResultCacheState *rcstate, bool *found) -{ - ResultCacheKey *key; - ResultCacheEntry *entry; - MemoryContext oldcontext; - - /* prepare the probe slot with the current scan parameters */ - prepare_probe_slot(rcstate, NULL); - - /* - * Add the new entry to the cache. No need to pass a valid key since the - * hash function uses rcstate's probeslot, which we populated above. - */ - entry = resultcache_insert(rcstate->hashtable, NULL, found); - - if (*found) - { - /* - * Move existing entry to the tail of the LRU list to mark it as the - * most recently used item. - */ - dlist_move_tail(&rcstate->lru_list, &entry->key->lru_node); - - return entry; - } - - oldcontext = MemoryContextSwitchTo(rcstate->tableContext); - - /* Allocate a new key */ - entry->key = key = (ResultCacheKey *) palloc(sizeof(ResultCacheKey)); - key->params = ExecCopySlotMinimalTuple(rcstate->probeslot); - - /* Update the total cache memory utilization */ - rcstate->mem_used += EMPTY_ENTRY_MEMORY_BYTES(entry); - - /* Initialize this entry */ - entry->complete = false; - entry->tuplehead = NULL; - - /* - * Since this is the most recently used entry, push this entry onto the - * end of the LRU list. - */ - dlist_push_tail(&rcstate->lru_list, &entry->key->lru_node); - - rcstate->last_tuple = NULL; - - MemoryContextSwitchTo(oldcontext); - - /* - * If we've gone over our memory budget, then we'll free up some space in - * the cache. - */ - if (rcstate->mem_used > rcstate->mem_limit) - { - /* - * Try to free up some memory. It's highly unlikely that we'll fail - * to do so here since the entry we've just added is yet to contain - * any tuples and we're able to remove any other entry to reduce the - * memory consumption. - */ - if (unlikely(!cache_reduce_memory(rcstate, key))) - return NULL; - - /* - * The process of removing entries from the cache may have caused the - * code in simplehash.h to shuffle elements to earlier buckets in the - * hash table. If it has, we'll need to find the entry again by - * performing a lookup. Fortunately, we can detect if this has - * happened by seeing if the entry is still in use and that the key - * pointer matches our expected key. - */ - if (entry->status != resultcache_SH_IN_USE || entry->key != key) - { - /* - * We need to repopulate the probeslot as lookups performed during - * the cache evictions above will have stored some other key. - */ - prepare_probe_slot(rcstate, key); - - /* Re-find the newly added entry */ - entry = resultcache_lookup(rcstate->hashtable, NULL); - Assert(entry != NULL); - } - } - - return entry; -} - -/* - * cache_store_tuple - * Add the tuple stored in 'slot' to the rcstate's current cache entry. - * The cache entry must have already been made with cache_lookup(). - * rcstate's last_tuple field must point to the tail of rcstate->entry's - * list of tuples. - */ -static bool -cache_store_tuple(ResultCacheState *rcstate, TupleTableSlot *slot) -{ - ResultCacheTuple *tuple; - ResultCacheEntry *entry = rcstate->entry; - MemoryContext oldcontext; - - Assert(slot != NULL); - Assert(entry != NULL); - - oldcontext = MemoryContextSwitchTo(rcstate->tableContext); - - tuple = (ResultCacheTuple *) palloc(sizeof(ResultCacheTuple)); - tuple->mintuple = ExecCopySlotMinimalTuple(slot); - tuple->next = NULL; - - /* Account for the memory we just consumed */ - rcstate->mem_used += CACHE_TUPLE_BYTES(tuple); - - if (entry->tuplehead == NULL) - { - /* - * This is the first tuple for this entry, so just point the list head - * to it. - */ - entry->tuplehead = tuple; - } - else - { - /* push this tuple onto the tail of the list */ - rcstate->last_tuple->next = tuple; - } - - rcstate->last_tuple = tuple; - MemoryContextSwitchTo(oldcontext); - - /* - * If we've gone over our memory budget then free up some space in the - * cache. - */ - if (rcstate->mem_used > rcstate->mem_limit) - { - ResultCacheKey *key = entry->key; - - if (!cache_reduce_memory(rcstate, key)) - return false; - - /* - * The process of removing entries from the cache may have caused the - * code in simplehash.h to shuffle elements to earlier buckets in the - * hash table. If it has, we'll need to find the entry again by - * performing a lookup. Fortunately, we can detect if this has - * happened by seeing if the entry is still in use and that the key - * pointer matches our expected key. - */ - if (entry->status != resultcache_SH_IN_USE || entry->key != key) - { - /* - * We need to repopulate the probeslot as lookups performed during - * the cache evictions above will have stored some other key. - */ - prepare_probe_slot(rcstate, key); - - /* Re-find the entry */ - rcstate->entry = entry = resultcache_lookup(rcstate->hashtable, - NULL); - Assert(entry != NULL); - } - } - - return true; -} - -static TupleTableSlot * -ExecResultCache(PlanState *pstate) -{ - ResultCacheState *node = castNode(ResultCacheState, pstate); - PlanState *outerNode; - TupleTableSlot *slot; - - switch (node->rc_status) - { - case RC_CACHE_LOOKUP: - { - ResultCacheEntry *entry; - TupleTableSlot *outerslot; - bool found; - - Assert(node->entry == NULL); - - /* - * We're only ever in this state for the first call of the - * scan. Here we have a look to see if we've already seen the - * current parameters before and if we have already cached a - * complete set of records that the outer plan will return for - * these parameters. - * - * When we find a valid cache entry, we'll return the first - * tuple from it. If not found, we'll create a cache entry and - * then try to fetch a tuple from the outer scan. If we find - * one there, we'll try to cache it. - */ - - /* see if we've got anything cached for the current parameters */ - entry = cache_lookup(node, &found); - - if (found && entry->complete) - { - node->stats.cache_hits += 1; /* stats update */ - - /* - * Set last_tuple and entry so that the state - * RC_CACHE_FETCH_NEXT_TUPLE can easily find the next - * tuple for these parameters. - */ - node->last_tuple = entry->tuplehead; - node->entry = entry; - - /* Fetch the first cached tuple, if there is one */ - if (entry->tuplehead) - { - node->rc_status = RC_CACHE_FETCH_NEXT_TUPLE; - - slot = node->ss.ps.ps_ResultTupleSlot; - ExecStoreMinimalTuple(entry->tuplehead->mintuple, - slot, false); - - return slot; - } - - /* The cache entry is void of any tuples. */ - node->rc_status = RC_END_OF_SCAN; - return NULL; - } - - /* Handle cache miss */ - node->stats.cache_misses += 1; /* stats update */ - - if (found) - { - /* - * A cache entry was found, but the scan for that entry - * did not run to completion. We'll just remove all - * tuples and start again. It might be tempting to - * continue where we left off, but there's no guarantee - * the outer node will produce the tuples in the same - * order as it did last time. - */ - entry_purge_tuples(node, entry); - } - - /* Scan the outer node for a tuple to cache */ - outerNode = outerPlanState(node); - outerslot = ExecProcNode(outerNode); - if (TupIsNull(outerslot)) - { - /* - * cache_lookup may have returned NULL due to failure to - * free enough cache space, so ensure we don't do anything - * here that assumes it worked. There's no need to go into - * bypass mode here as we're setting rc_status to end of - * scan. - */ - if (likely(entry)) - entry->complete = true; - - node->rc_status = RC_END_OF_SCAN; - return NULL; - } - - node->entry = entry; - - /* - * If we failed to create the entry or failed to store the - * tuple in the entry, then go into bypass mode. - */ - if (unlikely(entry == NULL || - !cache_store_tuple(node, outerslot))) - { - node->stats.cache_overflows += 1; /* stats update */ - - node->rc_status = RC_CACHE_BYPASS_MODE; - - /* - * No need to clear out last_tuple as we'll stay in bypass - * mode until the end of the scan. - */ - } - else - { - /* - * If we only expect a single row from this scan then we - * can mark that we're not expecting more. This allows - * cache lookups to work even when the scan has not been - * executed to completion. - */ - entry->complete = node->singlerow; - node->rc_status = RC_FILLING_CACHE; - } - - slot = node->ss.ps.ps_ResultTupleSlot; - ExecCopySlot(slot, outerslot); - return slot; - } - - case RC_CACHE_FETCH_NEXT_TUPLE: - { - /* We shouldn't be in this state if these are not set */ - Assert(node->entry != NULL); - Assert(node->last_tuple != NULL); - - /* Skip to the next tuple to output */ - node->last_tuple = node->last_tuple->next; - - /* No more tuples in the cache */ - if (node->last_tuple == NULL) - { - node->rc_status = RC_END_OF_SCAN; - return NULL; - } - - slot = node->ss.ps.ps_ResultTupleSlot; - ExecStoreMinimalTuple(node->last_tuple->mintuple, slot, - false); - - return slot; - } - - case RC_FILLING_CACHE: - { - TupleTableSlot *outerslot; - ResultCacheEntry *entry = node->entry; - - /* entry should already have been set by RC_CACHE_LOOKUP */ - Assert(entry != NULL); - - /* - * When in the RC_FILLING_CACHE state, we've just had a cache - * miss and are populating the cache with the current scan - * tuples. - */ - outerNode = outerPlanState(node); - outerslot = ExecProcNode(outerNode); - if (TupIsNull(outerslot)) - { - /* No more tuples. Mark it as complete */ - entry->complete = true; - node->rc_status = RC_END_OF_SCAN; - return NULL; - } - - /* - * Validate if the planner properly set the singlerow flag. - * It should only set that if each cache entry can, at most, - * return 1 row. XXX maybe this should be an Assert? - */ - if (unlikely(entry->complete)) - elog(ERROR, "cache entry already complete"); - - /* Record the tuple in the current cache entry */ - if (unlikely(!cache_store_tuple(node, outerslot))) - { - /* Couldn't store it? Handle overflow */ - node->stats.cache_overflows += 1; /* stats update */ - - node->rc_status = RC_CACHE_BYPASS_MODE; - - /* - * No need to clear out entry or last_tuple as we'll stay - * in bypass mode until the end of the scan. - */ - } - - slot = node->ss.ps.ps_ResultTupleSlot; - ExecCopySlot(slot, outerslot); - return slot; - } - - case RC_CACHE_BYPASS_MODE: - { - TupleTableSlot *outerslot; - - /* - * When in bypass mode we just continue to read tuples without - * caching. We need to wait until the next rescan before we - * can come out of this mode. - */ - outerNode = outerPlanState(node); - outerslot = ExecProcNode(outerNode); - if (TupIsNull(outerslot)) - { - node->rc_status = RC_END_OF_SCAN; - return NULL; - } - - slot = node->ss.ps.ps_ResultTupleSlot; - ExecCopySlot(slot, outerslot); - return slot; - } - - case RC_END_OF_SCAN: - - /* - * We've already returned NULL for this scan, but just in case - * something calls us again by mistake. - */ - return NULL; - - default: - elog(ERROR, "unrecognized resultcache state: %d", - (int) node->rc_status); - return NULL; - } /* switch */ -} - -ResultCacheState * -ExecInitResultCache(ResultCache *node, EState *estate, int eflags) -{ - ResultCacheState *rcstate = makeNode(ResultCacheState); - Plan *outerNode; - int i; - int nkeys; - Oid *eqfuncoids; - - /* check for unsupported flags */ - Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK))); - - rcstate->ss.ps.plan = (Plan *) node; - rcstate->ss.ps.state = estate; - rcstate->ss.ps.ExecProcNode = ExecResultCache; - - /* - * Miscellaneous initialization - * - * create expression context for node - */ - ExecAssignExprContext(estate, &rcstate->ss.ps); - - outerNode = outerPlan(node); - outerPlanState(rcstate) = ExecInitNode(outerNode, estate, eflags); - - /* - * Initialize return slot and type. No need to initialize projection info - * because this node doesn't do projections. - */ - ExecInitResultTupleSlotTL(&rcstate->ss.ps, &TTSOpsMinimalTuple); - rcstate->ss.ps.ps_ProjInfo = NULL; - - /* - * Initialize scan slot and type. - */ - ExecCreateScanSlotFromOuterPlan(estate, &rcstate->ss, &TTSOpsMinimalTuple); - - /* - * Set the state machine to lookup the cache. We won't find anything - * until we cache something, but this saves a special case to create the - * first entry. - */ - rcstate->rc_status = RC_CACHE_LOOKUP; - - rcstate->nkeys = nkeys = node->numKeys; - rcstate->hashkeydesc = ExecTypeFromExprList(node->param_exprs); - rcstate->tableslot = MakeSingleTupleTableSlot(rcstate->hashkeydesc, - &TTSOpsMinimalTuple); - rcstate->probeslot = MakeSingleTupleTableSlot(rcstate->hashkeydesc, - &TTSOpsVirtual); - - rcstate->param_exprs = (ExprState **) palloc(nkeys * sizeof(ExprState *)); - rcstate->collations = node->collations; /* Just point directly to the plan - * data */ - rcstate->hashfunctions = (FmgrInfo *) palloc(nkeys * sizeof(FmgrInfo)); - - eqfuncoids = palloc(nkeys * sizeof(Oid)); - - for (i = 0; i < nkeys; i++) - { - Oid hashop = node->hashOperators[i]; - Oid left_hashfn; - Oid right_hashfn; - Expr *param_expr = (Expr *) list_nth(node->param_exprs, i); - - if (!get_op_hash_functions(hashop, &left_hashfn, &right_hashfn)) - elog(ERROR, "could not find hash function for hash operator %u", - hashop); - - fmgr_info(left_hashfn, &rcstate->hashfunctions[i]); - - rcstate->param_exprs[i] = ExecInitExpr(param_expr, (PlanState *) rcstate); - eqfuncoids[i] = get_opcode(hashop); - } - - rcstate->cache_eq_expr = ExecBuildParamSetEqual(rcstate->hashkeydesc, - &TTSOpsMinimalTuple, - &TTSOpsVirtual, - eqfuncoids, - node->collations, - node->param_exprs, - (PlanState *) rcstate); - - pfree(eqfuncoids); - rcstate->mem_used = 0; - - /* Limit the total memory consumed by the cache to this */ - rcstate->mem_limit = get_hash_mem() * 1024L; - - /* A memory context dedicated for the cache */ - rcstate->tableContext = AllocSetContextCreate(CurrentMemoryContext, - "ResultCacheHashTable", - ALLOCSET_DEFAULT_SIZES); - - dlist_init(&rcstate->lru_list); - rcstate->last_tuple = NULL; - rcstate->entry = NULL; - - /* - * Mark if we can assume the cache entry is completed after we get the - * first record for it. Some callers might not call us again after - * getting the first match. e.g. A join operator performing a unique join - * is able to skip to the next outer tuple after getting the first - * matching inner tuple. In this case, the cache entry is complete after - * getting the first tuple. This allows us to mark it as so. - */ - rcstate->singlerow = node->singlerow; - - /* Zero the statistics counters */ - memset(&rcstate->stats, 0, sizeof(ResultCacheInstrumentation)); - - /* Allocate and set up the actual cache */ - build_hash_table(rcstate, node->est_entries); - - return rcstate; -} - -void -ExecEndResultCache(ResultCacheState *node) -{ - /* - * When ending a parallel worker, copy the statistics gathered by the - * worker back into shared memory so that it can be picked up by the main - * process to report in EXPLAIN ANALYZE. - */ - if (node->shared_info != NULL && IsParallelWorker()) - { - ResultCacheInstrumentation *si; - - /* Make mem_peak available for EXPLAIN */ - if (node->stats.mem_peak == 0) - node->stats.mem_peak = node->mem_used; - - Assert(ParallelWorkerNumber <= node->shared_info->num_workers); - si = &node->shared_info->sinstrument[ParallelWorkerNumber]; - memcpy(si, &node->stats, sizeof(ResultCacheInstrumentation)); - } - - /* Remove the cache context */ - MemoryContextDelete(node->tableContext); - - ExecClearTuple(node->ss.ss_ScanTupleSlot); - /* must drop pointer to cache result tuple */ - ExecClearTuple(node->ss.ps.ps_ResultTupleSlot); - - /* - * free exprcontext - */ - ExecFreeExprContext(&node->ss.ps); - - /* - * shut down the subplan - */ - ExecEndNode(outerPlanState(node)); -} - -void -ExecReScanResultCache(ResultCacheState *node) -{ - PlanState *outerPlan = outerPlanState(node); - - /* Mark that we must lookup the cache for a new set of parameters */ - node->rc_status = RC_CACHE_LOOKUP; - - /* nullify pointers used for the last scan */ - node->entry = NULL; - node->last_tuple = NULL; - - /* - * if chgParam of subnode is not null then plan will be re-scanned by - * first ExecProcNode. - */ - if (outerPlan->chgParam == NULL) - ExecReScan(outerPlan); - -} - -/* - * ExecEstimateCacheEntryOverheadBytes - * For use in the query planner to help it estimate the amount of memory - * required to store a single entry in the cache. - */ -double -ExecEstimateCacheEntryOverheadBytes(double ntuples) -{ - return sizeof(ResultCacheEntry) + sizeof(ResultCacheKey) + - sizeof(ResultCacheTuple) * ntuples; -} - -/* ---------------------------------------------------------------- - * Parallel Query Support - * ---------------------------------------------------------------- - */ - - /* ---------------------------------------------------------------- - * ExecResultCacheEstimate - * - * Estimate space required to propagate result cache statistics. - * ---------------------------------------------------------------- - */ -void -ExecResultCacheEstimate(ResultCacheState *node, ParallelContext *pcxt) -{ - Size size; - - /* don't need this if not instrumenting or no workers */ - if (!node->ss.ps.instrument || pcxt->nworkers == 0) - return; - - size = mul_size(pcxt->nworkers, sizeof(ResultCacheInstrumentation)); - size = add_size(size, offsetof(SharedResultCacheInfo, sinstrument)); - shm_toc_estimate_chunk(&pcxt->estimator, size); - shm_toc_estimate_keys(&pcxt->estimator, 1); -} - -/* ---------------------------------------------------------------- - * ExecResultCacheInitializeDSM - * - * Initialize DSM space for result cache statistics. - * ---------------------------------------------------------------- - */ -void -ExecResultCacheInitializeDSM(ResultCacheState *node, ParallelContext *pcxt) -{ - Size size; - - /* don't need this if not instrumenting or no workers */ - if (!node->ss.ps.instrument || pcxt->nworkers == 0) - return; - - size = offsetof(SharedResultCacheInfo, sinstrument) - + pcxt->nworkers * sizeof(ResultCacheInstrumentation); - node->shared_info = shm_toc_allocate(pcxt->toc, size); - /* ensure any unfilled slots will contain zeroes */ - memset(node->shared_info, 0, size); - node->shared_info->num_workers = pcxt->nworkers; - shm_toc_insert(pcxt->toc, node->ss.ps.plan->plan_node_id, - node->shared_info); -} - -/* ---------------------------------------------------------------- - * ExecResultCacheInitializeWorker - * - * Attach worker to DSM space for result cache statistics. - * ---------------------------------------------------------------- - */ -void -ExecResultCacheInitializeWorker(ResultCacheState *node, ParallelWorkerContext *pwcxt) -{ - node->shared_info = - shm_toc_lookup(pwcxt->toc, node->ss.ps.plan->plan_node_id, true); -} - -/* ---------------------------------------------------------------- - * ExecResultCacheRetrieveInstrumentation - * - * Transfer result cache statistics from DSM to private memory. - * ---------------------------------------------------------------- - */ -void -ExecResultCacheRetrieveInstrumentation(ResultCacheState *node) -{ - Size size; - SharedResultCacheInfo *si; - - if (node->shared_info == NULL) - return; - - size = offsetof(SharedResultCacheInfo, sinstrument) - + node->shared_info->num_workers * sizeof(ResultCacheInstrumentation); - si = palloc(size); - memcpy(si, node->shared_info, size); - node->shared_info = si; -} diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index ad729d10a8..44c7fce20a 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -948,33 +948,6 @@ _copyMaterial(const Material *from) } -/* - * _copyResultCache - */ -static ResultCache * -_copyResultCache(const ResultCache *from) -{ - ResultCache *newnode = makeNode(ResultCache); - - /* - * copy node superclass fields - */ - CopyPlanFields((const Plan *) from, (Plan *) newnode); - - /* - * copy remainder of node - */ - COPY_SCALAR_FIELD(numKeys); - COPY_POINTER_FIELD(hashOperators, sizeof(Oid) * from->numKeys); - COPY_POINTER_FIELD(collations, sizeof(Oid) * from->numKeys); - COPY_NODE_FIELD(param_exprs); - COPY_SCALAR_FIELD(singlerow); - COPY_SCALAR_FIELD(est_entries); - - return newnode; -} - - /* * CopySortFields * @@ -2367,7 +2340,6 @@ _copyRestrictInfo(const RestrictInfo *from) COPY_SCALAR_FIELD(right_bucketsize); COPY_SCALAR_FIELD(left_mcvfreq); COPY_SCALAR_FIELD(right_mcvfreq); - COPY_SCALAR_FIELD(hasheqoperator); return newnode; } @@ -5052,9 +5024,6 @@ copyObjectImpl(const void *from) case T_Material: retval = _copyMaterial(from); break; - case T_ResultCache: - retval = _copyResultCache(from); - break; case T_Sort: retval = _copySort(from); break; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index fa8f65fbc5..785465d8c4 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -846,21 +846,6 @@ _outMaterial(StringInfo str, const Material *node) _outPlanInfo(str, (const Plan *) node); } -static void -_outResultCache(StringInfo str, const ResultCache *node) -{ - WRITE_NODE_TYPE("RESULTCACHE"); - - _outPlanInfo(str, (const Plan *) node); - - WRITE_INT_FIELD(numKeys); - WRITE_OID_ARRAY(hashOperators, node->numKeys); - WRITE_OID_ARRAY(collations, node->numKeys); - WRITE_NODE_FIELD(param_exprs); - WRITE_BOOL_FIELD(singlerow); - WRITE_UINT_FIELD(est_entries); -} - static void _outSortInfo(StringInfo str, const Sort *node) { @@ -1935,21 +1920,6 @@ _outMaterialPath(StringInfo str, const MaterialPath *node) WRITE_NODE_FIELD(subpath); } -static void -_outResultCachePath(StringInfo str, const ResultCachePath *node) -{ - WRITE_NODE_TYPE("RESULTCACHEPATH"); - - _outPathInfo(str, (const Path *) node); - - WRITE_NODE_FIELD(subpath); - WRITE_NODE_FIELD(hash_operators); - WRITE_NODE_FIELD(param_exprs); - WRITE_BOOL_FIELD(singlerow); - WRITE_FLOAT_FIELD(calls, "%.0f"); - WRITE_UINT_FIELD(est_entries); -} - static void _outUniquePath(StringInfo str, const UniquePath *node) { @@ -2551,7 +2521,6 @@ _outRestrictInfo(StringInfo str, const RestrictInfo *node) WRITE_NODE_FIELD(right_em); WRITE_BOOL_FIELD(outer_is_left); WRITE_OID_FIELD(hashjoinoperator); - WRITE_OID_FIELD(hasheqoperator); } static void @@ -3938,9 +3907,6 @@ outNode(StringInfo str, const void *obj) case T_Material: _outMaterial(str, obj); break; - case T_ResultCache: - _outResultCache(str, obj); - break; case T_Sort: _outSort(str, obj); break; @@ -4175,9 +4141,6 @@ outNode(StringInfo str, const void *obj) case T_MaterialPath: _outMaterialPath(str, obj); break; - case T_ResultCachePath: - _outResultCachePath(str, obj); - break; case T_UniquePath: _outUniquePath(str, obj); break; diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index ecce23b747..a6e723a273 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -2211,26 +2211,6 @@ _readMaterial(void) READ_DONE(); } -/* - * _readResultCache - */ -static ResultCache * -_readResultCache(void) -{ - READ_LOCALS(ResultCache); - - ReadCommonPlan(&local_node->plan); - - READ_INT_FIELD(numKeys); - READ_OID_ARRAY(hashOperators, local_node->numKeys); - READ_OID_ARRAY(collations, local_node->numKeys); - READ_NODE_FIELD(param_exprs); - READ_BOOL_FIELD(singlerow); - READ_UINT_FIELD(est_entries); - - READ_DONE(); -} - /* * ReadCommonSort * Assign the basic stuff of all nodes that inherit from Sort @@ -2919,8 +2899,6 @@ parseNodeString(void) return_value = _readHashJoin(); else if (MATCH("MATERIAL", 8)) return_value = _readMaterial(); - else if (MATCH("RESULTCACHE", 11)) - return_value = _readResultCache(); else if (MATCH("SORT", 4)) return_value = _readSort(); else if (MATCH("INCREMENTALSORT", 15)) diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 3c9520d00a..f34399e3ec 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -4032,10 +4032,6 @@ print_path(PlannerInfo *root, Path *path, int indent) ptype = "Material"; subpath = ((MaterialPath *) path)->subpath; break; - case T_ResultCache: - ptype = "ResultCache"; - subpath = ((ResultCachePath *) path)->subpath; - break; case T_UniquePath: ptype = "Unique"; subpath = ((UniquePath *) path)->subpath; diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index 05686d0194..0c016a03dd 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -79,7 +79,6 @@ #include "executor/executor.h" #include "executor/nodeAgg.h" #include "executor/nodeHash.h" -#include "executor/nodeResultCache.h" #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" @@ -140,7 +139,6 @@ bool enable_incremental_sort = true; bool enable_hashagg = true; bool enable_nestloop = true; bool enable_material = true; -bool enable_resultcache = true; bool enable_mergejoin = true; bool enable_hashjoin = true; bool enable_gathermerge = true; @@ -2404,147 +2402,6 @@ cost_material(Path *path, path->total_cost = startup_cost + run_cost; } -/* - * cost_resultcache_rescan - * Determines the estimated cost of rescanning a ResultCache node. - * - * In order to estimate this, we must gain knowledge of how often we expect to - * be called and how many distinct sets of parameters we are likely to be - * called with. If we expect a good cache hit ratio, then we can set our - * costs to account for that hit ratio, plus a little bit of cost for the - * caching itself. Caching will not work out well if we expect to be called - * with too many distinct parameter values. The worst-case here is that we - * never see any parameter value twice, in which case we'd never get a cache - * hit and caching would be a complete waste of effort. - */ -static void -cost_resultcache_rescan(PlannerInfo *root, ResultCachePath *rcpath, - Cost *rescan_startup_cost, Cost *rescan_total_cost) -{ - EstimationInfo estinfo; - Cost input_startup_cost = rcpath->subpath->startup_cost; - Cost input_total_cost = rcpath->subpath->total_cost; - double tuples = rcpath->subpath->rows; - double calls = rcpath->calls; - int width = rcpath->subpath->pathtarget->width; - - double hash_mem_bytes; - double est_entry_bytes; - double est_cache_entries; - double ndistinct; - double evict_ratio; - double hit_ratio; - Cost startup_cost; - Cost total_cost; - - /* available cache space */ - hash_mem_bytes = get_hash_mem() * 1024L; - - /* - * Set the number of bytes each cache entry should consume in the cache. - * To provide us with better estimations on how many cache entries we can - * store at once, we make a call to the executor here to ask it what - * memory overheads there are for a single cache entry. - * - * XXX we also store the cache key, but that's not accounted for here. - */ - est_entry_bytes = relation_byte_size(tuples, width) + - ExecEstimateCacheEntryOverheadBytes(tuples); - - /* estimate on the upper limit of cache entries we can hold at once */ - est_cache_entries = floor(hash_mem_bytes / est_entry_bytes); - - /* estimate on the distinct number of parameter values */ - ndistinct = estimate_num_groups(root, rcpath->param_exprs, calls, NULL, - &estinfo); - - /* - * When the estimation fell back on using a default value, it's a bit too - * risky to assume that it's ok to use a Result Cache. The use of a - * default could cause us to use a Result Cache when it's really - * inappropriate to do so. If we see that this has been done, then we'll - * assume that every call will have unique parameters, which will almost - * certainly mean a ResultCachePath will never survive add_path(). - */ - if ((estinfo.flags & SELFLAG_USED_DEFAULT) != 0) - ndistinct = calls; - - /* - * Since we've already estimated the maximum number of entries we can - * store at once and know the estimated number of distinct values we'll be - * called with, we'll take this opportunity to set the path's est_entries. - * This will ultimately determine the hash table size that the executor - * will use. If we leave this at zero, the executor will just choose the - * size itself. Really this is not the right place to do this, but it's - * convenient since everything is already calculated. - */ - rcpath->est_entries = Min(Min(ndistinct, est_cache_entries), - PG_UINT32_MAX); - - /* - * When the number of distinct parameter values is above the amount we can - * store in the cache, then we'll have to evict some entries from the - * cache. This is not free. Here we estimate how often we'll incur the - * cost of that eviction. - */ - evict_ratio = 1.0 - Min(est_cache_entries, ndistinct) / ndistinct; - - /* - * In order to estimate how costly a single scan will be, we need to - * attempt to estimate what the cache hit ratio will be. To do that we - * must look at how many scans are estimated in total for this node and - * how many of those scans we expect to get a cache hit. - */ - hit_ratio = 1.0 / ndistinct * Min(est_cache_entries, ndistinct) - - (ndistinct / calls); - - /* Ensure we don't go negative */ - hit_ratio = Max(hit_ratio, 0.0); - - /* - * Set the total_cost accounting for the expected cache hit ratio. We - * also add on a cpu_operator_cost to account for a cache lookup. This - * will happen regardless of whether it's a cache hit or not. - */ - total_cost = input_total_cost * (1.0 - hit_ratio) + cpu_operator_cost; - - /* Now adjust the total cost to account for cache evictions */ - - /* Charge a cpu_tuple_cost for evicting the actual cache entry */ - total_cost += cpu_tuple_cost * evict_ratio; - - /* - * Charge a 10th of cpu_operator_cost to evict every tuple in that entry. - * The per-tuple eviction is really just a pfree, so charging a whole - * cpu_operator_cost seems a little excessive. - */ - total_cost += cpu_operator_cost / 10.0 * evict_ratio * tuples; - - /* - * Now adjust for storing things in the cache, since that's not free - * either. Everything must go in the cache. We don't proportion this - * over any ratio, just apply it once for the scan. We charge a - * cpu_tuple_cost for the creation of the cache entry and also a - * cpu_operator_cost for each tuple we expect to cache. - */ - total_cost += cpu_tuple_cost + cpu_operator_cost * tuples; - - /* - * Getting the first row must be also be proportioned according to the - * expected cache hit ratio. - */ - startup_cost = input_startup_cost * (1.0 - hit_ratio); - - /* - * Additionally we charge a cpu_tuple_cost to account for cache lookups, - * which we'll do regardless of whether it was a cache hit or not. - */ - startup_cost += cpu_tuple_cost; - - *rescan_startup_cost = startup_cost; - *rescan_total_cost = total_cost; -} - /* * cost_agg * Determines and returns the cost of performing an Agg plan node, @@ -4285,11 +4142,6 @@ cost_rescan(PlannerInfo *root, Path *path, *rescan_total_cost = run_cost; } break; - case T_ResultCache: - /* All the hard work is done by cost_resultcache_rescan */ - cost_resultcache_rescan(root, (ResultCachePath *) path, - rescan_startup_cost, rescan_total_cost); - break; default: *rescan_startup_cost = path->startup_cost; *rescan_total_cost = path->total_cost; diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c index 3894991a95..57ce97fd53 100644 --- a/src/backend/optimizer/path/joinpath.c +++ b/src/backend/optimizer/path/joinpath.c @@ -18,13 +18,10 @@ #include "executor/executor.h" #include "foreign/fdwapi.h" -#include "nodes/nodeFuncs.h" #include "optimizer/cost.h" -#include "optimizer/optimizer.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" -#include "utils/typcache.h" /* Hook for plugins to get control in add_paths_to_joinrel() */ set_join_pathlist_hook_type set_join_pathlist_hook = NULL; @@ -55,9 +52,6 @@ static void try_partial_mergejoin_path(PlannerInfo *root, static void sort_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, RelOptInfo *innerrel, JoinType jointype, JoinPathExtraData *extra); -static inline bool clause_sides_match_join(RestrictInfo *rinfo, - RelOptInfo *outerrel, - RelOptInfo *innerrel); static void match_unsorted_outer(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, RelOptInfo *innerrel, JoinType jointype, JoinPathExtraData *extra); @@ -169,11 +163,6 @@ add_paths_to_joinrel(PlannerInfo *root, { case JOIN_SEMI: case JOIN_ANTI: - - /* - * XXX it may be worth proving this to allow a ResultCache to be - * considered for Nested Loop Semi/Anti Joins. - */ extra.inner_unique = false; /* well, unproven */ break; case JOIN_UNIQUE_INNER: @@ -365,180 +354,6 @@ allow_star_schema_join(PlannerInfo *root, bms_nonempty_difference(inner_paramrels, outerrelids)); } -/* - * paraminfo_get_equal_hashops - * Determine if param_info and innerrel's lateral_vars can be hashed. - * Returns true the hashing is possible, otherwise return false. - * - * Additionally we also collect the outer exprs and the hash operators for - * each parameter to innerrel. These set in 'param_exprs' and 'operators' - * when we return true. - */ -static bool -paraminfo_get_equal_hashops(PlannerInfo *root, ParamPathInfo *param_info, - RelOptInfo *outerrel, RelOptInfo *innerrel, - List **param_exprs, List **operators) - -{ - ListCell *lc; - - *param_exprs = NIL; - *operators = NIL; - - if (param_info != NULL) - { - List *clauses = param_info->ppi_clauses; - - foreach(lc, clauses) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - OpExpr *opexpr; - Node *expr; - - /* can't use result cache without a valid hash equals operator */ - if (!OidIsValid(rinfo->hasheqoperator) || - !clause_sides_match_join(rinfo, outerrel, innerrel)) - { - list_free(*operators); - list_free(*param_exprs); - return false; - } - - /* - * We already checked that this is an OpExpr with 2 args when - * setting hasheqoperator. - */ - opexpr = (OpExpr *) rinfo->clause; - if (rinfo->outer_is_left) - expr = (Node *) linitial(opexpr->args); - else - expr = (Node *) lsecond(opexpr->args); - - *operators = lappend_oid(*operators, rinfo->hasheqoperator); - *param_exprs = lappend(*param_exprs, expr); - } - } - - /* Now add any lateral vars to the cache key too */ - foreach(lc, innerrel->lateral_vars) - { - Node *expr = (Node *) lfirst(lc); - TypeCacheEntry *typentry; - - /* Reject if there are any volatile functions */ - if (contain_volatile_functions(expr)) - { - list_free(*operators); - list_free(*param_exprs); - return false; - } - - typentry = lookup_type_cache(exprType(expr), - TYPECACHE_HASH_PROC | TYPECACHE_EQ_OPR); - - /* can't use result cache without a valid hash equals operator */ - if (!OidIsValid(typentry->hash_proc) || !OidIsValid(typentry->eq_opr)) - { - list_free(*operators); - list_free(*param_exprs); - return false; - } - - *operators = lappend_oid(*operators, typentry->eq_opr); - *param_exprs = lappend(*param_exprs, expr); - } - - /* We're okay to use result cache */ - return true; -} - -/* - * get_resultcache_path - * If possible, make and return a Result Cache path atop of 'inner_path'. - * Otherwise return NULL. - */ -static Path * -get_resultcache_path(PlannerInfo *root, RelOptInfo *innerrel, - RelOptInfo *outerrel, Path *inner_path, - Path *outer_path, JoinType jointype, - JoinPathExtraData *extra) -{ - List *param_exprs; - List *hash_operators; - ListCell *lc; - - /* Obviously not if it's disabled */ - if (!enable_resultcache) - return NULL; - - /* - * We can safely not bother with all this unless we expect to perform more - * than one inner scan. The first scan is always going to be a cache - * miss. This would likely fail later anyway based on costs, so this is - * really just to save some wasted effort. - */ - if (outer_path->parent->rows < 2) - return NULL; - - /* - * We can only have a result cache when there's some kind of cache key, - * either parameterized path clauses or lateral Vars. No cache key sounds - * more like something a Materialize node might be more useful for. - */ - if ((inner_path->param_info == NULL || - inner_path->param_info->ppi_clauses == NIL) && - innerrel->lateral_vars == NIL) - return NULL; - - /* - * Currently we don't do this for SEMI and ANTI joins unless they're - * marked as inner_unique. This is because nested loop SEMI/ANTI joins - * don't scan the inner node to completion, which will mean result cache - * cannot mark the cache entry as complete. - * - * XXX Currently we don't attempt to mark SEMI/ANTI joins as inner_unique - * = true. Should we? See add_paths_to_joinrel() - */ - if (!extra->inner_unique && (jointype == JOIN_SEMI || - jointype == JOIN_ANTI)) - return NULL; - - /* - * We can't use a result cache if there are volatile functions in the - * inner rel's target list or restrict list. A cache hit could reduce the - * number of calls to these functions. - */ - if (contain_volatile_functions((Node *) innerrel->reltarget)) - return NULL; - - foreach(lc, innerrel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - - if (contain_volatile_functions((Node *) rinfo)) - return NULL; - } - - /* Check if we have hash ops for each parameter to the path */ - if (paraminfo_get_equal_hashops(root, - inner_path->param_info, - outerrel, - innerrel, - ¶m_exprs, - &hash_operators)) - { - return (Path *) create_resultcache_path(root, - innerrel, - inner_path, - param_exprs, - hash_operators, - extra->inner_unique, - outer_path->parent->rows); - } - - return NULL; -} - /* * try_nestloop_path * Consider a nestloop join path; if it appears useful, push it into @@ -1656,7 +1471,6 @@ match_unsorted_outer(PlannerInfo *root, foreach(lc2, innerrel->cheapest_parameterized_paths) { Path *innerpath = (Path *) lfirst(lc2); - Path *rcpath; try_nestloop_path(root, joinrel, @@ -1665,22 +1479,6 @@ match_unsorted_outer(PlannerInfo *root, merge_pathkeys, jointype, extra); - - /* - * Try generating a result cache path and see if that makes the - * nested loop any cheaper. - */ - rcpath = get_resultcache_path(root, innerrel, outerrel, - innerpath, outerpath, jointype, - extra); - if (rcpath != NULL) - try_nestloop_path(root, - joinrel, - outerpath, - rcpath, - merge_pathkeys, - jointype, - extra); } /* Also consider materialized form of the cheapest inner path */ @@ -1835,7 +1633,6 @@ consider_parallel_nestloop(PlannerInfo *root, foreach(lc2, innerrel->cheapest_parameterized_paths) { Path *innerpath = (Path *) lfirst(lc2); - Path *rcpath; /* Can't join to an inner path that is not parallel-safe */ if (!innerpath->parallel_safe) @@ -1860,17 +1657,6 @@ consider_parallel_nestloop(PlannerInfo *root, try_partial_nestloop_path(root, joinrel, outerpath, innerpath, pathkeys, jointype, extra); - - /* - * Try generating a result cache path and see if that makes the - * nested loop any cheaper. - */ - rcpath = get_resultcache_path(root, innerrel, outerrel, - innerpath, outerpath, jointype, - extra); - if (rcpath != NULL) - try_partial_nestloop_path(root, joinrel, outerpath, rcpath, - pathkeys, jointype, extra); } } } diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 22f10fa339..a56936e0e9 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -91,9 +91,6 @@ static Result *create_group_result_plan(PlannerInfo *root, static ProjectSet *create_project_set_plan(PlannerInfo *root, ProjectSetPath *best_path); static Material *create_material_plan(PlannerInfo *root, MaterialPath *best_path, int flags); -static ResultCache *create_resultcache_plan(PlannerInfo *root, - ResultCachePath *best_path, - int flags); static Plan *create_unique_plan(PlannerInfo *root, UniquePath *best_path, int flags); static Gather *create_gather_plan(PlannerInfo *root, GatherPath *best_path); @@ -280,11 +277,6 @@ static Sort *make_sort_from_groupcols(List *groupcls, AttrNumber *grpColIdx, Plan *lefttree); static Material *make_material(Plan *lefttree); -static ResultCache *make_resultcache(Plan *lefttree, Oid *hashoperators, - Oid *collations, - List *param_exprs, - bool singlerow, - uint32 est_entries); static WindowAgg *make_windowagg(List *tlist, Index winref, int partNumCols, AttrNumber *partColIdx, Oid *partOperators, Oid *partCollations, int ordNumCols, AttrNumber *ordColIdx, Oid *ordOperators, Oid *ordCollations, @@ -461,11 +453,6 @@ create_plan_recurse(PlannerInfo *root, Path *best_path, int flags) (MaterialPath *) best_path, flags); break; - case T_ResultCache: - plan = (Plan *) create_resultcache_plan(root, - (ResultCachePath *) best_path, - flags); - break; case T_Unique: if (IsA(best_path, UpperUniquePath)) { @@ -1579,56 +1566,6 @@ create_material_plan(PlannerInfo *root, MaterialPath *best_path, int flags) return plan; } -/* - * create_resultcache_plan - * Create a ResultCache plan for 'best_path' and (recursively) plans - * for its subpaths. - * - * Returns a Plan node. - */ -static ResultCache * -create_resultcache_plan(PlannerInfo *root, ResultCachePath *best_path, int flags) -{ - ResultCache *plan; - Plan *subplan; - Oid *operators; - Oid *collations; - List *param_exprs = NIL; - ListCell *lc; - ListCell *lc2; - int nkeys; - int i; - - subplan = create_plan_recurse(root, best_path->subpath, - flags | CP_SMALL_TLIST); - - param_exprs = (List *) replace_nestloop_params(root, (Node *) - best_path->param_exprs); - - nkeys = list_length(param_exprs); - Assert(nkeys > 0); - operators = palloc(nkeys * sizeof(Oid)); - collations = palloc(nkeys * sizeof(Oid)); - - i = 0; - forboth(lc, param_exprs, lc2, best_path->hash_operators) - { - Expr *param_expr = (Expr *) lfirst(lc); - Oid opno = lfirst_oid(lc2); - - operators[i] = opno; - collations[i] = exprCollation((Node *) param_expr); - i++; - } - - plan = make_resultcache(subplan, operators, collations, param_exprs, - best_path->singlerow, best_path->est_entries); - - copy_generic_path_info(&plan->plan, (Path *) best_path); - - return plan; -} - /* * create_unique_plan * Create a Unique plan for 'best_path' and (recursively) plans @@ -6515,28 +6452,6 @@ materialize_finished_plan(Plan *subplan) return matplan; } -static ResultCache * -make_resultcache(Plan *lefttree, Oid *hashoperators, Oid *collations, - List *param_exprs, bool singlerow, uint32 est_entries) -{ - ResultCache *node = makeNode(ResultCache); - Plan *plan = &node->plan; - - plan->targetlist = lefttree->targetlist; - plan->qual = NIL; - plan->lefttree = lefttree; - plan->righttree = NULL; - - node->numKeys = list_length(param_exprs); - node->hashOperators = hashoperators; - node->collations = collations; - node->param_exprs = param_exprs; - node->singlerow = singlerow; - node->est_entries = est_entries; - - return node; -} - Agg * make_agg(List *tlist, List *qual, AggStrategy aggstrategy, AggSplit aggsplit, @@ -7123,7 +7038,6 @@ is_projection_capable_path(Path *path) { case T_Hash: case T_Material: - case T_ResultCache: case T_Sort: case T_IncrementalSort: case T_Unique: @@ -7169,7 +7083,6 @@ is_projection_capable_plan(Plan *plan) { case T_Hash: case T_Material: - case T_ResultCache: case T_Sort: case T_Unique: case T_SetOp: diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 8232f45c58..20df2152ea 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -33,7 +33,6 @@ #include "parser/analyze.h" #include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" -#include "utils/typcache.h" /* These parameters are set by GUC */ int from_collapse_limit; @@ -78,7 +77,6 @@ static bool check_equivalence_delay(PlannerInfo *root, static bool check_redundant_nullability_qual(PlannerInfo *root, Node *clause); static void check_mergejoinable(RestrictInfo *restrictinfo); static void check_hashjoinable(RestrictInfo *restrictinfo); -static void check_resultcacheable(RestrictInfo *restrictinfo); /***************************************************************************** @@ -2210,13 +2208,6 @@ distribute_restrictinfo_to_rels(PlannerInfo *root, */ check_hashjoinable(restrictinfo); - /* - * Likewise, check if the clause is suitable to be used with a - * Result Cache node to cache inner tuples during a parameterized - * nested loop. - */ - check_resultcacheable(restrictinfo); - /* * Add clause to the join lists of all the relevant relations. */ @@ -2459,7 +2450,6 @@ build_implied_join_equality(PlannerInfo *root, /* Set mergejoinability/hashjoinability flags */ check_mergejoinable(restrictinfo); check_hashjoinable(restrictinfo); - check_resultcacheable(restrictinfo); return restrictinfo; } @@ -2707,34 +2697,3 @@ check_hashjoinable(RestrictInfo *restrictinfo) !contain_volatile_functions((Node *) restrictinfo)) restrictinfo->hashjoinoperator = opno; } - -/* - * check_resultcacheable - * If the restrictinfo's clause is suitable to be used for a Result Cache - * node, set the hasheqoperator to the hash equality operator that will be - * needed during caching. - */ -static void -check_resultcacheable(RestrictInfo *restrictinfo) -{ - TypeCacheEntry *typentry; - Expr *clause = restrictinfo->clause; - Node *leftarg; - - if (restrictinfo->pseudoconstant) - return; - if (!is_opclause(clause)) - return; - if (list_length(((OpExpr *) clause)->args) != 2) - return; - - leftarg = linitial(((OpExpr *) clause)->args); - - typentry = lookup_type_cache(exprType(leftarg), TYPECACHE_HASH_PROC | - TYPECACHE_EQ_OPR); - - if (!OidIsValid(typentry->hash_proc) || !OidIsValid(typentry->eq_opr)) - return; - - restrictinfo->hasheqoperator = typentry->eq_opr; -} diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 6dd6f3001b..4a25431bec 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -752,15 +752,6 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) set_hash_references(root, plan, rtoffset); break; - case T_ResultCache: - { - ResultCache *rcplan = (ResultCache *) plan; - rcplan->param_exprs = fix_scan_list(root, rcplan->param_exprs, - rtoffset, - NUM_EXEC_TLIST(plan)); - break; - } - case T_Material: case T_Sort: case T_IncrementalSort: diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index 0881a208ac..15b9453975 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -2745,11 +2745,6 @@ finalize_plan(PlannerInfo *root, Plan *plan, /* rescan_param does *not* get added to scan_params */ break; - case T_ResultCache: - finalize_primnode((Node *) ((ResultCache *) plan)->param_exprs, - &context); - break; - case T_ProjectSet: case T_Hash: case T_Material: diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index b248b038e0..1c47a2fb49 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -1576,56 +1576,6 @@ create_material_path(RelOptInfo *rel, Path *subpath) return pathnode; } -/* - * create_resultcache_path - * Creates a path corresponding to a ResultCache plan, returning the - * pathnode. - */ -ResultCachePath * -create_resultcache_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, - List *param_exprs, List *hash_operators, - bool singlerow, double calls) -{ - ResultCachePath *pathnode = makeNode(ResultCachePath); - - Assert(subpath->parent == rel); - - pathnode->path.pathtype = T_ResultCache; - pathnode->path.parent = rel; - pathnode->path.pathtarget = rel->reltarget; - pathnode->path.param_info = subpath->param_info; - pathnode->path.parallel_aware = false; - pathnode->path.parallel_safe = rel->consider_parallel && - subpath->parallel_safe; - pathnode->path.parallel_workers = subpath->parallel_workers; - pathnode->path.pathkeys = subpath->pathkeys; - - pathnode->subpath = subpath; - pathnode->hash_operators = hash_operators; - pathnode->param_exprs = param_exprs; - pathnode->singlerow = singlerow; - pathnode->calls = calls; - - /* - * For now we set est_entries to 0. cost_resultcache_rescan() does all - * the hard work to determine how many cache entries there are likely to - * be, so it seems best to leave it up to that function to fill this field - * in. If left at 0, the executor will make a guess at a good value. - */ - pathnode->est_entries = 0; - - /* - * Add a small additional charge for caching the first entry. All the - * harder calculations for rescans are performed in - * cost_resultcache_rescan(). - */ - pathnode->path.startup_cost = subpath->startup_cost + cpu_tuple_cost; - pathnode->path.total_cost = subpath->total_cost + cpu_tuple_cost; - pathnode->path.rows = subpath->rows; - - return pathnode; -} - /* * create_unique_path * Creates a path representing elimination of distinct rows from the @@ -3919,17 +3869,6 @@ reparameterize_path(PlannerInfo *root, Path *path, apath->path.parallel_aware, -1); } - case T_ResultCache: - { - ResultCachePath *rcpath = (ResultCachePath *) path; - - return (Path *) create_resultcache_path(root, rel, - rcpath->subpath, - rcpath->param_exprs, - rcpath->hash_operators, - rcpath->singlerow, - rcpath->calls); - } default: break; } @@ -4148,16 +4087,6 @@ do { \ } break; - case T_ResultCachePath: - { - ResultCachePath *rcpath; - - FLAT_COPY_PATH(rcpath, path, ResultCachePath); - REPARAMETERIZE_CHILD_PATH(rcpath->subpath); - new_path = (Path *) rcpath; - } - break; - case T_GatherPath: { GatherPath *gpath; diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c index aa9fb3a9fa..59ff35926e 100644 --- a/src/backend/optimizer/util/restrictinfo.c +++ b/src/backend/optimizer/util/restrictinfo.c @@ -217,8 +217,6 @@ make_restrictinfo_internal(PlannerInfo *root, restrictinfo->left_mcvfreq = -1; restrictinfo->right_mcvfreq = -1; - restrictinfo->hasheqoperator = InvalidOid; - return restrictinfo; } @@ -368,7 +366,6 @@ commute_restrictinfo(RestrictInfo *rinfo, Oid comm_op) result->right_bucketsize = rinfo->left_bucketsize; result->left_mcvfreq = rinfo->right_mcvfreq; result->right_mcvfreq = rinfo->left_mcvfreq; - result->hasheqoperator = InvalidOid; return result; } diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 8a5d240385..03daec9a08 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -1036,16 +1036,6 @@ static struct config_bool ConfigureNamesBool[] = true, NULL, NULL, NULL }, - { - {"enable_resultcache", PGC_USERSET, QUERY_TUNING_METHOD, - gettext_noop("Enables the planner's use of result caching."), - NULL, - GUC_EXPLAIN - }, - &enable_resultcache, - true, - NULL, NULL, NULL - }, { {"enable_nestloop", PGC_USERSET, QUERY_TUNING_METHOD, gettext_noop("Enables the planner's use of nested-loop join plans."), diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index 30cfddac1f..791d39cf07 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -366,7 +366,6 @@ #enable_seqscan = on #enable_sort = on #enable_incremental_sort = on -#enable_resultcache = on #enable_tidscan = on #enable_partitionwise_join = off #enable_partitionwise_aggregate = off diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 26dcc4485e..34dd861eff 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -275,13 +275,6 @@ extern ExprState *ExecBuildGroupingEqual(TupleDesc ldesc, TupleDesc rdesc, const Oid *eqfunctions, const Oid *collations, PlanState *parent); -extern ExprState *ExecBuildParamSetEqual(TupleDesc desc, - const TupleTableSlotOps *lops, - const TupleTableSlotOps *rops, - const Oid *eqfunctions, - const Oid *collations, - const List *param_exprs, - PlanState *parent); extern ProjectionInfo *ExecBuildProjectionInfo(List *targetList, ExprContext *econtext, TupleTableSlot *slot, diff --git a/src/include/executor/nodeResultCache.h b/src/include/executor/nodeResultCache.h deleted file mode 100644 index df671d16f9..0000000000 --- a/src/include/executor/nodeResultCache.h +++ /dev/null @@ -1,31 +0,0 @@ -/*------------------------------------------------------------------------- - * - * nodeResultCache.h - * - * - * - * Portions Copyright (c) 2021, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California - * - * src/include/executor/nodeResultCache.h - * - *------------------------------------------------------------------------- - */ -#ifndef NODERESULTCACHE_H -#define NODERESULTCACHE_H - -#include "nodes/execnodes.h" - -extern ResultCacheState *ExecInitResultCache(ResultCache *node, EState *estate, int eflags); -extern void ExecEndResultCache(ResultCacheState *node); -extern void ExecReScanResultCache(ResultCacheState *node); -extern double ExecEstimateCacheEntryOverheadBytes(double ntuples); -extern void ExecResultCacheEstimate(ResultCacheState *node, - ParallelContext *pcxt); -extern void ExecResultCacheInitializeDSM(ResultCacheState *node, - ParallelContext *pcxt); -extern void ExecResultCacheInitializeWorker(ResultCacheState *node, - ParallelWorkerContext *pwcxt); -extern void ExecResultCacheRetrieveInstrumentation(ResultCacheState *node); - -#endif /* NODERESULTCACHE_H */ diff --git a/src/include/lib/ilist.h b/src/include/lib/ilist.h index ddbdb207af..aa196428ed 100644 --- a/src/include/lib/ilist.h +++ b/src/include/lib/ilist.h @@ -394,25 +394,6 @@ dlist_move_head(dlist_head *head, dlist_node *node) dlist_check(head); } -/* - * Move element from its current position in the list to the tail position in - * the same list. - * - * Undefined behaviour if 'node' is not already part of the list. - */ -static inline void -dlist_move_tail(dlist_head *head, dlist_node *node) -{ - /* fast path if it's already at the tail */ - if (head->head.prev == node) - return; - - dlist_delete(node); - dlist_push_tail(head, node); - - dlist_check(head); -} - /* * Check whether 'node' has a following node. * Caution: unreliable if 'node' is not in the list. diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 52d1fa018b..3b39369a49 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -17,7 +17,6 @@ #include "access/tupconvert.h" #include "executor/instrument.h" #include "fmgr.h" -#include "lib/ilist.h" #include "lib/pairingheap.h" #include "nodes/params.h" #include "nodes/plannodes.h" @@ -2038,71 +2037,6 @@ typedef struct MaterialState Tuplestorestate *tuplestorestate; } MaterialState; -struct ResultCacheEntry; -struct ResultCacheTuple; -struct ResultCacheKey; - -typedef struct ResultCacheInstrumentation -{ - uint64 cache_hits; /* number of rescans where we've found the - * scan parameter values to be cached */ - uint64 cache_misses; /* number of rescans where we've not found the - * scan parameter values to be cached. */ - uint64 cache_evictions; /* number of cache entries removed due to - * the need to free memory */ - uint64 cache_overflows; /* number of times we've had to bypass the - * cache when filling it due to not being - * able to free enough space to store the - * current scan's tuples. */ - uint64 mem_peak; /* peak memory usage in bytes */ -} ResultCacheInstrumentation; - -/* ---------------- - * Shared memory container for per-worker resultcache information - * ---------------- - */ -typedef struct SharedResultCacheInfo -{ - int num_workers; - ResultCacheInstrumentation sinstrument[FLEXIBLE_ARRAY_MEMBER]; -} SharedResultCacheInfo; - -/* ---------------- - * ResultCacheState information - * - * resultcache nodes are used to cache recent and commonly seen results - * from a parameterized scan. - * ---------------- - */ -typedef struct ResultCacheState -{ - ScanState ss; /* its first field is NodeTag */ - int rc_status; /* value of ExecResultCache state machine */ - int nkeys; /* number of cache keys */ - struct resultcache_hash *hashtable; /* hash table for cache entries */ - TupleDesc hashkeydesc; /* tuple descriptor for cache keys */ - TupleTableSlot *tableslot; /* min tuple slot for existing cache entries */ - TupleTableSlot *probeslot; /* virtual slot used for hash lookups */ - ExprState *cache_eq_expr; /* Compare exec params to hash key */ - ExprState **param_exprs; /* exprs containing the parameters to this - * node */ - FmgrInfo *hashfunctions; /* lookup data for hash funcs nkeys in size */ - Oid *collations; /* collation for comparisons nkeys in size */ - uint64 mem_used; /* bytes of memory used by cache */ - uint64 mem_limit; /* memory limit in bytes for the cache */ - MemoryContext tableContext; /* memory context to store cache data */ - dlist_head lru_list; /* least recently used entry list */ - struct ResultCacheTuple *last_tuple; /* Used to point to the last tuple - * returned during a cache hit and - * the tuple we last stored when - * populating the cache. */ - struct ResultCacheEntry *entry; /* the entry that 'last_tuple' belongs to - * or NULL if 'last_tuple' is NULL. */ - bool singlerow; /* true if the cache entry is to be marked as - * complete after caching the first tuple. */ - ResultCacheInstrumentation stats; /* execution statistics */ - SharedResultCacheInfo *shared_info; /* statistics for parallel workers */ -} ResultCacheState; /* ---------------- * When performing sorting by multiple keys, it's possible that the input diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 2051abbbf9..704f00fd30 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -74,7 +74,6 @@ typedef enum NodeTag T_MergeJoin, T_HashJoin, T_Material, - T_ResultCache, T_Sort, T_IncrementalSort, T_Group, @@ -133,7 +132,6 @@ typedef enum NodeTag T_MergeJoinState, T_HashJoinState, T_MaterialState, - T_ResultCacheState, T_SortState, T_IncrementalSortState, T_GroupState, @@ -244,7 +242,6 @@ typedef enum NodeTag T_MergeAppendPath, T_GroupResultPath, T_MaterialPath, - T_ResultCachePath, T_UniquePath, T_GatherPath, T_GatherMergePath, diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index a65bda7e3c..e4e1c15986 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -1494,25 +1494,6 @@ typedef struct MaterialPath Path *subpath; } MaterialPath; -/* - * ResultCachePath represents a ResultCache plan node, i.e., a cache that - * caches tuples from parameterized paths to save the underlying node from - * having to be rescanned for parameter values which are already cached. - */ -typedef struct ResultCachePath -{ - Path path; - Path *subpath; /* outerpath to cache tuples from */ - List *hash_operators; /* hash operators for each key */ - List *param_exprs; /* cache keys */ - bool singlerow; /* true if the cache entry is to be marked as - * complete after caching the first record. */ - double calls; /* expected number of rescans */ - uint32 est_entries; /* The maximum number of entries that the - * planner expects will fit in the cache, or 0 - * if unknown */ -} ResultCachePath; - /* * UniquePath represents elimination of distinct rows from the output of * its subpath. @@ -2110,9 +2091,6 @@ typedef struct RestrictInfo Selectivity right_bucketsize; /* avg bucketsize of right side */ Selectivity left_mcvfreq; /* left side's most common val's freq */ Selectivity right_mcvfreq; /* right side's most common val's freq */ - - /* hash equality operator used for result cache, else InvalidOid */ - Oid hasheqoperator; } RestrictInfo; /* diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 1678bd66fe..623dc450ee 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -779,27 +779,6 @@ typedef struct Material Plan plan; } Material; -/* ---------------- - * result cache node - * ---------------- - */ -typedef struct ResultCache -{ - Plan plan; - - int numKeys; /* size of the two arrays below */ - - Oid *hashOperators; /* hash operators for each key */ - Oid *collations; /* cache keys */ - List *param_exprs; /* exprs containing parameters */ - bool singlerow; /* true if the cache entry should be marked as - * complete after we store the first tuple in - * it. */ - uint32 est_entries; /* The maximum number of entries that the - * planner expects will fit in the cache, or 0 - * if unknown */ -} ResultCache; - /* ---------------- * sort node * ---------------- diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h index 0fe60d82e4..a3fd93fe07 100644 --- a/src/include/optimizer/cost.h +++ b/src/include/optimizer/cost.h @@ -57,7 +57,6 @@ extern PGDLLIMPORT bool enable_incremental_sort; extern PGDLLIMPORT bool enable_hashagg; extern PGDLLIMPORT bool enable_nestloop; extern PGDLLIMPORT bool enable_material; -extern PGDLLIMPORT bool enable_resultcache; extern PGDLLIMPORT bool enable_mergejoin; extern PGDLLIMPORT bool enable_hashjoin; extern PGDLLIMPORT bool enable_gathermerge; diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index 53261ee91f..d539bc2783 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -82,13 +82,6 @@ extern GroupResultPath *create_group_result_path(PlannerInfo *root, PathTarget *target, List *havingqual); extern MaterialPath *create_material_path(RelOptInfo *rel, Path *subpath); -extern ResultCachePath *create_resultcache_path(PlannerInfo *root, - RelOptInfo *rel, - Path *subpath, - List *param_exprs, - List *hash_operators, - bool singlerow, - double calls); extern UniquePath *create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, SpecialJoinInfo *sjinfo); extern GatherPath *create_gather_path(PlannerInfo *root, diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out index ca06d41dd0..1ae0e5d939 100644 --- a/src/test/regress/expected/aggregates.out +++ b/src/test/regress/expected/aggregates.out @@ -2584,7 +2584,6 @@ select v||'a', case when v||'a' = 'aa' then 1 else 0 end, count(*) -- Make sure that generation of HashAggregate for uniqification purposes -- does not lead to array overflow due to unexpected duplicate hash keys -- see CAFeeJoKKu0u+A_A9R9316djW-YW3-+Gtgvy3ju655qRHR3jtdA@mail.gmail.com -set enable_resultcache to off; explain (costs off) select 1 from tenk1 where (hundred, thousand) in (select twothousand, twothousand from onek); @@ -2600,7 +2599,6 @@ explain (costs off) -> Seq Scan on onek (8 rows) -reset enable_resultcache; -- -- Hash Aggregation Spill tests -- diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index 86fd3907c5..04e802d421 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -2536,7 +2536,6 @@ reset enable_nestloop; -- set work_mem to '64kB'; set enable_mergejoin to off; -set enable_resultcache to off; explain (costs off) select count(*) from tenk1 a, tenk1 b where a.hundred = b.thousand and (b.fivethous % 10) < 10; @@ -2560,7 +2559,6 @@ select count(*) from tenk1 a, tenk1 b reset work_mem; reset enable_mergejoin; -reset enable_resultcache; -- -- regression test for 8.2 bug with improper re-ordering of left joins -- @@ -3665,8 +3663,8 @@ select * from tenk1 t1 left join (tenk1 t2 join tenk1 t3 on t2.thousand = t3.unique2) on t1.hundred = t2.hundred and t1.ten = t3.ten where t1.unique1 = 1; - QUERY PLAN --------------------------------------------------------------- + QUERY PLAN +-------------------------------------------------------- Nested Loop Left Join -> Index Scan using tenk1_unique1 on tenk1 t1 Index Cond: (unique1 = 1) @@ -3676,19 +3674,17 @@ where t1.unique1 = 1; Recheck Cond: (t1.hundred = hundred) -> Bitmap Index Scan on tenk1_hundred Index Cond: (hundred = t1.hundred) - -> Result Cache - Cache Key: t2.thousand - -> Index Scan using tenk1_unique2 on tenk1 t3 - Index Cond: (unique2 = t2.thousand) -(13 rows) + -> Index Scan using tenk1_unique2 on tenk1 t3 + Index Cond: (unique2 = t2.thousand) +(11 rows) explain (costs off) select * from tenk1 t1 left join (tenk1 t2 join tenk1 t3 on t2.thousand = t3.unique2) on t1.hundred = t2.hundred and t1.ten + t2.ten = t3.ten where t1.unique1 = 1; - QUERY PLAN --------------------------------------------------------------- + QUERY PLAN +-------------------------------------------------------- Nested Loop Left Join -> Index Scan using tenk1_unique1 on tenk1 t1 Index Cond: (unique1 = 1) @@ -3698,11 +3694,9 @@ where t1.unique1 = 1; Recheck Cond: (t1.hundred = hundred) -> Bitmap Index Scan on tenk1_hundred Index Cond: (hundred = t1.hundred) - -> Result Cache - Cache Key: t2.thousand - -> Index Scan using tenk1_unique2 on tenk1 t3 - Index Cond: (unique2 = t2.thousand) -(13 rows) + -> Index Scan using tenk1_unique2 on tenk1 t3 + Index Cond: (unique2 = t2.thousand) +(11 rows) explain (costs off) select count(*) from @@ -4216,8 +4210,8 @@ where t1.f1 = ss.f1; QUERY PLAN -------------------------------------------------- Nested Loop - Output: t1.f1, i8.q1, i8.q2, q1, f1 - Join Filter: (t1.f1 = f1) + Output: t1.f1, i8.q1, i8.q2, (i8.q1), t2.f1 + Join Filter: (t1.f1 = t2.f1) -> Nested Loop Left Join Output: t1.f1, i8.q1, i8.q2 -> Seq Scan on public.text_tbl t1 @@ -4227,14 +4221,11 @@ where t1.f1 = ss.f1; -> Seq Scan on public.int8_tbl i8 Output: i8.q1, i8.q2 Filter: (i8.q2 = 123) - -> Result Cache - Output: q1, f1 - Cache Key: i8.q1 - -> Limit - Output: (i8.q1), t2.f1 - -> Seq Scan on public.text_tbl t2 - Output: i8.q1, t2.f1 -(19 rows) + -> Limit + Output: (i8.q1), t2.f1 + -> Seq Scan on public.text_tbl t2 + Output: i8.q1, t2.f1 +(16 rows) select * from text_tbl t1 @@ -4255,13 +4246,13 @@ select * from lateral (select i8.q1, t2.f1 from text_tbl t2 limit 1) as ss1, lateral (select ss1.* from text_tbl t3 limit 1) as ss2 where t1.f1 = ss2.f1; - QUERY PLAN --------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------- Nested Loop - Output: t1.f1, i8.q1, i8.q2, q1, f1, q1, f1 - Join Filter: (t1.f1 = f1) + Output: t1.f1, i8.q1, i8.q2, (i8.q1), t2.f1, ((i8.q1)), (t2.f1) + Join Filter: (t1.f1 = (t2.f1)) -> Nested Loop - Output: t1.f1, i8.q1, i8.q2, q1, f1 + Output: t1.f1, i8.q1, i8.q2, (i8.q1), t2.f1 -> Nested Loop Left Join Output: t1.f1, i8.q1, i8.q2 -> Seq Scan on public.text_tbl t1 @@ -4271,21 +4262,15 @@ where t1.f1 = ss2.f1; -> Seq Scan on public.int8_tbl i8 Output: i8.q1, i8.q2 Filter: (i8.q2 = 123) - -> Result Cache - Output: q1, f1 - Cache Key: i8.q1 - -> Limit - Output: (i8.q1), t2.f1 - -> Seq Scan on public.text_tbl t2 - Output: i8.q1, t2.f1 - -> Result Cache - Output: q1, f1 - Cache Key: q1, f1 -> Limit - Output: (q1), (f1) - -> Seq Scan on public.text_tbl t3 - Output: q1, f1 -(28 rows) + Output: (i8.q1), t2.f1 + -> Seq Scan on public.text_tbl t2 + Output: i8.q1, t2.f1 + -> Limit + Output: ((i8.q1)), (t2.f1) + -> Seq Scan on public.text_tbl t3 + Output: (i8.q1), t2.f1 +(22 rows) select * from text_tbl t1 @@ -4331,17 +4316,14 @@ where tt1.f1 = ss1.c0; -> Seq Scan on public.text_tbl tt4 Output: tt4.f1 Filter: (tt4.f1 = 'foo'::text) - -> Result Cache + -> Subquery Scan on ss1 Output: ss1.c0 - Cache Key: tt4.f1 - -> Subquery Scan on ss1 - Output: ss1.c0 - Filter: (ss1.c0 = 'foo'::text) - -> Limit - Output: (tt4.f1) - -> Seq Scan on public.text_tbl tt5 - Output: tt4.f1 -(32 rows) + Filter: (ss1.c0 = 'foo'::text) + -> Limit + Output: (tt4.f1) + -> Seq Scan on public.text_tbl tt5 + Output: tt4.f1 +(29 rows) select 1 from text_tbl as tt1 @@ -5015,40 +4997,34 @@ select count(*) from tenk1 a, lateral generate_series(1,two) g; explain (costs off) select count(*) from tenk1 a, lateral generate_series(1,two) g; - QUERY PLAN ------------------------------------------------------- + QUERY PLAN +------------------------------------------------ Aggregate -> Nested Loop -> Seq Scan on tenk1 a - -> Result Cache - Cache Key: a.two - -> Function Scan on generate_series g -(6 rows) + -> Function Scan on generate_series g +(4 rows) explain (costs off) select count(*) from tenk1 a cross join lateral generate_series(1,two) g; - QUERY PLAN ------------------------------------------------------- + QUERY PLAN +------------------------------------------------ Aggregate -> Nested Loop -> Seq Scan on tenk1 a - -> Result Cache - Cache Key: a.two - -> Function Scan on generate_series g -(6 rows) + -> Function Scan on generate_series g +(4 rows) -- don't need the explicit LATERAL keyword for functions explain (costs off) select count(*) from tenk1 a, generate_series(1,two) g; - QUERY PLAN ------------------------------------------------------- + QUERY PLAN +------------------------------------------------ Aggregate -> Nested Loop -> Seq Scan on tenk1 a - -> Result Cache - Cache Key: a.two - -> Function Scan on generate_series g -(6 rows) + -> Function Scan on generate_series g +(4 rows) -- lateral with UNION ALL subselect explain (costs off) @@ -5103,15 +5079,14 @@ explain (costs off) QUERY PLAN ------------------------------------------------------------------ Aggregate - -> Nested Loop + -> Hash Join + Hash Cond: ("*VALUES*".column1 = b.unique2) -> Nested Loop -> Index Only Scan using tenk1_unique1 on tenk1 a -> Values Scan on "*VALUES*" - -> Result Cache - Cache Key: "*VALUES*".column1 + -> Hash -> Index Only Scan using tenk1_unique2 on tenk1 b - Index Cond: (unique2 = "*VALUES*".column1) -(9 rows) +(8 rows) select count(*) from tenk1 a, tenk1 b join lateral (values(a.unique1),(-1)) ss(x) on b.unique2 = ss.x; diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index 10f3ce3d07..c4e827caec 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -1958,9 +1958,6 @@ begin ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); ln := regexp_replace(ln, 'actual rows=\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, 'Hits: \d+', 'Hits: N'); - ln := regexp_replace(ln, 'Misses: \d+', 'Misses: N'); - ln := regexp_replace(ln, 'Memory Usage: \d+', 'Memory Usage: N'); return next ln; end loop; end; @@ -2089,8 +2086,8 @@ create index ab_a3_b3_a_idx on ab_a3_b3 (a); set enable_hashjoin = 0; set enable_mergejoin = 0; select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in(0, 0, 1)'); - explain_parallel_append --------------------------------------------------------------------------------------------------------------- + explain_parallel_append +-------------------------------------------------------------------------------------------------------- Finalize Aggregate (actual rows=N loops=N) -> Gather (actual rows=N loops=N) Workers Planned: 1 @@ -2099,36 +2096,32 @@ 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 ('{0,0,1}'::integer[])) - -> Result Cache (actual rows=N loops=N) - Cache Key: a.a - Hits: N Misses: N Evictions: 0 Overflows: 0 Memory Usage: NkB - Worker 0: Hits: N Misses: N Evictions: 0 Overflows: 0 Memory Usage: NkB - -> 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) - -> Index Scan using ab_a1_b2_a_idx on ab_a1_b2 ab_2 (actual rows=N loops=N) - Index Cond: (a = a.a) - -> Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (actual rows=N loops=N) - Index Cond: (a = a.a) - -> Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (never executed) - Index Cond: (a = a.a) -(31 rows) + -> 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) + -> Index Scan using ab_a1_b2_a_idx on ab_a1_b2 ab_2 (actual rows=N loops=N) + Index Cond: (a = a.a) + -> Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (actual rows=N loops=N) + Index Cond: (a = a.a) + -> Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed) + Index Cond: (a = a.a) + -> Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed) + Index Cond: (a = a.a) + -> Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed) + Index Cond: (a = a.a) + -> Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (never executed) + Index Cond: (a = a.a) + -> Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (never executed) + Index Cond: (a = a.a) + -> Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (never executed) + Index Cond: (a = a.a) +(27 rows) -- Ensure the same partitions are pruned when we make the nested loop -- parameter an Expr rather than a plain Param. select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a + 0 where a.a in(0, 0, 1)'); - explain_parallel_append --------------------------------------------------------------------------------------------------------------- + explain_parallel_append +-------------------------------------------------------------------------------------------------------- Finalize Aggregate (actual rows=N loops=N) -> Gather (actual rows=N loops=N) Workers Planned: 1 @@ -2137,35 +2130,31 @@ 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 ('{0,0,1}'::integer[])) - -> Result Cache (actual rows=N loops=N) - Cache Key: (a.a + 0) - Hits: N Misses: N Evictions: 0 Overflows: 0 Memory Usage: NkB - Worker 0: Hits: N Misses: N Evictions: 0 Overflows: 0 Memory Usage: NkB - -> 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 + 0)) - -> Index Scan using ab_a1_b2_a_idx on ab_a1_b2 ab_2 (actual rows=N loops=N) - Index Cond: (a = (a.a + 0)) - -> Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (actual rows=N loops=N) - Index Cond: (a = (a.a + 0)) - -> Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed) - Index Cond: (a = (a.a + 0)) - -> Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed) - Index Cond: (a = (a.a + 0)) - -> Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed) - Index Cond: (a = (a.a + 0)) - -> Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (never executed) - Index Cond: (a = (a.a + 0)) - -> Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (never executed) - Index Cond: (a = (a.a + 0)) - -> Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (never executed) - Index Cond: (a = (a.a + 0)) -(31 rows) + -> 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 + 0)) + -> Index Scan using ab_a1_b2_a_idx on ab_a1_b2 ab_2 (actual rows=N loops=N) + Index Cond: (a = (a.a + 0)) + -> Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (actual rows=N loops=N) + Index Cond: (a = (a.a + 0)) + -> Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed) + Index Cond: (a = (a.a + 0)) + -> Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed) + Index Cond: (a = (a.a + 0)) + -> Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed) + Index Cond: (a = (a.a + 0)) + -> Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (never executed) + Index Cond: (a = (a.a + 0)) + -> Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (never executed) + Index Cond: (a = (a.a + 0)) + -> Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (never executed) + Index Cond: (a = (a.a + 0)) +(27 rows) insert into lprt_a values(3),(3); select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in(1, 0, 3)'); - explain_parallel_append --------------------------------------------------------------------------------------------------------------- + explain_parallel_append +-------------------------------------------------------------------------------------------------------- Finalize Aggregate (actual rows=N loops=N) -> Gather (actual rows=N loops=N) Workers Planned: 1 @@ -2174,69 +2163,27 @@ 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,3}'::integer[])) - -> Result Cache (actual rows=N loops=N) - Cache Key: a.a - Hits: N Misses: N Evictions: 0 Overflows: 0 Memory Usage: NkB - Worker 0: Hits: N Misses: N Evictions: 0 Overflows: 0 Memory Usage: NkB - -> 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) - -> Index Scan using ab_a1_b2_a_idx on ab_a1_b2 ab_2 (actual rows=N loops=N) - Index Cond: (a = a.a) - -> Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (actual rows=N loops=N) - Index Cond: (a = a.a) - -> Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (actual rows=N loops=N) - Index Cond: (a = a.a) - -> Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (actual rows=N loops=N) - Index Cond: (a = a.a) - -> Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (actual rows=N loops=N) - Index Cond: (a = a.a) -(31 rows) + -> 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) + -> Index Scan using ab_a1_b2_a_idx on ab_a1_b2 ab_2 (actual rows=N loops=N) + Index Cond: (a = a.a) + -> Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (actual rows=N loops=N) + Index Cond: (a = a.a) + -> Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed) + Index Cond: (a = a.a) + -> Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed) + Index Cond: (a = a.a) + -> Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed) + Index Cond: (a = a.a) + -> Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (actual rows=N loops=N) + Index Cond: (a = a.a) + -> Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (actual rows=N loops=N) + Index Cond: (a = a.a) + -> Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (actual rows=N loops=N) + Index Cond: (a = a.a) +(27 rows) -select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in(1, 0, 0)'); - explain_parallel_append --------------------------------------------------------------------------------------------------------------- - Finalize Aggregate (actual rows=N loops=N) - -> Gather (actual rows=N loops=N) - Workers Planned: 1 - Workers Launched: N - -> Partial Aggregate (actual rows=N loops=N) - -> 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 - -> Result Cache (actual rows=N loops=N) - Cache Key: a.a - Hits: N Misses: N Evictions: 0 Overflows: 0 Memory Usage: NkB - Worker 0: Hits: N Misses: N Evictions: 0 Overflows: 0 Memory Usage: NkB - -> 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) - -> Index Scan using ab_a1_b2_a_idx on ab_a1_b2 ab_2 (actual rows=N loops=N) - Index Cond: (a = a.a) - -> Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (actual rows=N loops=N) - Index Cond: (a = a.a) - -> Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (never executed) - Index Cond: (a = a.a) -(32 rows) - -delete from lprt_a where a = 1; select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in(1, 0, 0)'); explain_parallel_append -------------------------------------------------------------------------------------------------------- @@ -2249,30 +2196,60 @@ select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on -> Parallel Seq Scan on lprt_a a (actual rows=N loops=N) Filter: (a = ANY ('{1,0,0}'::integer[])) Rows Removed by Filter: N - -> Result Cache (actual rows=N loops=N) - Cache Key: a.a - Hits: N Misses: N Evictions: 0 Overflows: 0 Memory Usage: NkB - Worker 0: Hits: N Misses: N Evictions: 0 Overflows: 0 Memory Usage: NkB - -> 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) - -> Index Scan using ab_a1_b2_a_idx on ab_a1_b2 ab_2 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (never executed) - Index Cond: (a = a.a) - -> Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (never executed) - Index Cond: (a = a.a) -(32 rows) + -> 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) + -> Index Scan using ab_a1_b2_a_idx on ab_a1_b2 ab_2 (actual rows=N loops=N) + Index Cond: (a = a.a) + -> Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (actual rows=N loops=N) + Index Cond: (a = a.a) + -> Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed) + Index Cond: (a = a.a) + -> Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed) + Index Cond: (a = a.a) + -> Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed) + Index Cond: (a = a.a) + -> Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (never executed) + Index Cond: (a = a.a) + -> Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (never executed) + Index Cond: (a = a.a) + -> Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (never executed) + Index Cond: (a = a.a) +(28 rows) + +delete from lprt_a where a = 1; +select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in(1, 0, 0)'); + explain_parallel_append +------------------------------------------------------------------------------------------------- + Finalize Aggregate (actual rows=N loops=N) + -> Gather (actual rows=N loops=N) + Workers Planned: 1 + Workers Launched: N + -> Partial Aggregate (actual rows=N loops=N) + -> 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 + -> 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) + -> Index Scan using ab_a1_b2_a_idx on ab_a1_b2 ab_2 (never executed) + Index Cond: (a = a.a) + -> Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (never executed) + Index Cond: (a = a.a) + -> Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed) + Index Cond: (a = a.a) + -> Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed) + Index Cond: (a = a.a) + -> Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed) + Index Cond: (a = a.a) + -> Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (never executed) + Index Cond: (a = a.a) + -> Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (never executed) + Index Cond: (a = a.a) + -> Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (never executed) + Index Cond: (a = a.a) +(28 rows) reset enable_hashjoin; reset enable_mergejoin; diff --git a/src/test/regress/expected/resultcache.out b/src/test/regress/expected/resultcache.out deleted file mode 100644 index 79a1114b5c..0000000000 --- a/src/test/regress/expected/resultcache.out +++ /dev/null @@ -1,159 +0,0 @@ --- Perform tests on the Result Cache node. --- The cache hits/misses/evictions from the Result Cache node can vary between --- machines. Let's just replace the number with an 'N'. In order to allow us --- to perform validation when the measure was zero, we replace a zero value --- with "Zero". All other numbers are replaced with 'N'. -create function explain_resultcache(query text, hide_hitmiss bool) returns setof text -language plpgsql as -$$ -declare - ln text; -begin - for ln in - execute format('explain (analyze, costs off, summary off, timing off) %s', - query) - loop - if hide_hitmiss = true then - ln := regexp_replace(ln, 'Hits: 0', 'Hits: Zero'); - ln := regexp_replace(ln, 'Hits: \d+', 'Hits: N'); - ln := regexp_replace(ln, 'Misses: 0', 'Misses: Zero'); - ln := regexp_replace(ln, 'Misses: \d+', 'Misses: N'); - end if; - ln := regexp_replace(ln, 'Evictions: 0', 'Evictions: Zero'); - ln := regexp_replace(ln, 'Evictions: \d+', 'Evictions: N'); - ln := regexp_replace(ln, 'Memory Usage: \d+', 'Memory Usage: N'); - return next ln; - end loop; -end; -$$; --- Ensure we get a result cache on the inner side of the nested loop -SET enable_hashjoin TO off; -SELECT explain_resultcache(' -SELECT COUNT(*),AVG(t1.unique1) FROM tenk1 t1 -INNER JOIN tenk1 t2 ON t1.unique1 = t2.twenty -WHERE t2.unique1 < 1000;', false); - explain_resultcache --------------------------------------------------------------------------------------------- - Aggregate (actual rows=1 loops=1) - -> Nested Loop (actual rows=1000 loops=1) - -> Bitmap Heap Scan on tenk1 t2 (actual rows=1000 loops=1) - Recheck Cond: (unique1 < 1000) - Heap Blocks: exact=333 - -> Bitmap Index Scan on tenk1_unique1 (actual rows=1000 loops=1) - Index Cond: (unique1 < 1000) - -> Result Cache (actual rows=1 loops=1000) - Cache Key: t2.twenty - Hits: 980 Misses: 20 Evictions: Zero Overflows: 0 Memory Usage: NkB - -> Index Only Scan using tenk1_unique1 on tenk1 t1 (actual rows=1 loops=20) - Index Cond: (unique1 = t2.twenty) - Heap Fetches: 0 -(13 rows) - --- And check we get the expected results. -SELECT COUNT(*),AVG(t1.unique1) FROM tenk1 t1 -INNER JOIN tenk1 t2 ON t1.unique1 = t2.twenty -WHERE t2.unique1 < 1000; - count | avg --------+-------------------- - 1000 | 9.5000000000000000 -(1 row) - --- Try with LATERAL joins -SELECT explain_resultcache(' -SELECT COUNT(*),AVG(t2.unique1) FROM tenk1 t1, -LATERAL (SELECT t2.unique1 FROM tenk1 t2 WHERE t1.twenty = t2.unique1) t2 -WHERE t1.unique1 < 1000;', false); - explain_resultcache --------------------------------------------------------------------------------------------- - Aggregate (actual rows=1 loops=1) - -> Nested Loop (actual rows=1000 loops=1) - -> Bitmap Heap Scan on tenk1 t1 (actual rows=1000 loops=1) - Recheck Cond: (unique1 < 1000) - Heap Blocks: exact=333 - -> Bitmap Index Scan on tenk1_unique1 (actual rows=1000 loops=1) - Index Cond: (unique1 < 1000) - -> Result Cache (actual rows=1 loops=1000) - Cache Key: t1.twenty - Hits: 980 Misses: 20 Evictions: Zero Overflows: 0 Memory Usage: NkB - -> Index Only Scan using tenk1_unique1 on tenk1 t2 (actual rows=1 loops=20) - Index Cond: (unique1 = t1.twenty) - Heap Fetches: 0 -(13 rows) - --- And check we get the expected results. -SELECT COUNT(*),AVG(t2.unique1) FROM tenk1 t1, -LATERAL (SELECT t2.unique1 FROM tenk1 t2 WHERE t1.twenty = t2.unique1) t2 -WHERE t1.unique1 < 1000; - count | avg --------+-------------------- - 1000 | 9.5000000000000000 -(1 row) - --- Reduce work_mem so that we see some cache evictions -SET work_mem TO '64kB'; -SET enable_mergejoin TO off; --- Ensure we get some evictions. We're unable to validate the hits and misses --- here as the number of entries that fit in the cache at once will vary --- between different machines. -SELECT explain_resultcache(' -SELECT COUNT(*),AVG(t1.unique1) FROM tenk1 t1 -INNER JOIN tenk1 t2 ON t1.unique1 = t2.thousand -WHERE t2.unique1 < 800;', true); - explain_resultcache ---------------------------------------------------------------------------------------------- - Aggregate (actual rows=1 loops=1) - -> Nested Loop (actual rows=800 loops=1) - -> Bitmap Heap Scan on tenk1 t2 (actual rows=800 loops=1) - Recheck Cond: (unique1 < 800) - Heap Blocks: exact=318 - -> Bitmap Index Scan on tenk1_unique1 (actual rows=800 loops=1) - Index Cond: (unique1 < 800) - -> Result Cache (actual rows=1 loops=800) - Cache Key: t2.thousand - Hits: Zero Misses: N Evictions: N Overflows: 0 Memory Usage: NkB - -> Index Only Scan using tenk1_unique1 on tenk1 t1 (actual rows=1 loops=800) - Index Cond: (unique1 = t2.thousand) - Heap Fetches: 0 -(13 rows) - -RESET enable_mergejoin; -RESET work_mem; -RESET enable_hashjoin; --- Test parallel plans with Result Cache. -SET min_parallel_table_scan_size TO 0; -SET parallel_setup_cost TO 0; -SET parallel_tuple_cost TO 0; --- Ensure we get a parallel plan. -EXPLAIN (COSTS OFF) -SELECT COUNT(*),AVG(t2.unique1) FROM tenk1 t1, -LATERAL (SELECT t2.unique1 FROM tenk1 t2 WHERE t1.twenty = t2.unique1) t2 -WHERE t1.unique1 < 1000; - QUERY PLAN -------------------------------------------------------------------------------- - Finalize Aggregate - -> Gather - Workers Planned: 2 - -> Partial Aggregate - -> Nested Loop - -> Parallel Bitmap Heap Scan on tenk1 t1 - Recheck Cond: (unique1 < 1000) - -> Bitmap Index Scan on tenk1_unique1 - Index Cond: (unique1 < 1000) - -> Result Cache - Cache Key: t1.twenty - -> Index Only Scan using tenk1_unique1 on tenk1 t2 - Index Cond: (unique1 = t1.twenty) -(13 rows) - --- And ensure the parallel plan gives us the correct results. -SELECT COUNT(*),AVG(t2.unique1) FROM tenk1 t1, -LATERAL (SELECT t2.unique1 FROM tenk1 t2 WHERE t1.twenty = t2.unique1) t2 -WHERE t1.unique1 < 1000; - count | avg --------+-------------------- - 1000 | 9.5000000000000000 -(1 row) - -RESET parallel_tuple_cost; -RESET parallel_setup_cost; -RESET min_parallel_table_scan_size; diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out index c7986fb7fc..d5532d0ccc 100644 --- a/src/test/regress/expected/subselect.out +++ b/src/test/regress/expected/subselect.out @@ -1091,21 +1091,19 @@ select sum(o.four), sum(ss.a) from select * from x ) ss where o.ten = 1; - QUERY PLAN ---------------------------------------------------------- + QUERY PLAN +--------------------------------------------------- Aggregate -> Nested Loop -> Seq Scan on onek o Filter: (ten = 1) - -> Result Cache - Cache Key: o.four - -> CTE Scan on x - CTE x - -> Recursive Union - -> Result - -> WorkTable Scan on x x_1 - Filter: (a < 10) -(12 rows) + -> CTE Scan on x + CTE x + -> Recursive Union + -> Result + -> WorkTable Scan on x x_1 + Filter: (a < 10) +(10 rows) select sum(o.four), sum(ss.a) from onek o cross join lateral ( diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out index 0bb558d93c..98dde452e6 100644 --- a/src/test/regress/expected/sysviews.out +++ b/src/test/regress/expected/sysviews.out @@ -111,11 +111,10 @@ select name, setting from pg_settings where name like 'enable%'; enable_partition_pruning | on enable_partitionwise_aggregate | off enable_partitionwise_join | off - enable_resultcache | on enable_seqscan | on enable_sort | on enable_tidscan | on -(20 rows) +(19 rows) -- Test that the pg_timezone_names and pg_timezone_abbrevs views are -- more-or-less working. We can't test their contents in any great detail diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 2e89839089..312c11a4bd 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -119,7 +119,7 @@ test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion tr # ---------- # Another group of parallel tests # ---------- -test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info tuplesort explain compression resultcache +test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info tuplesort explain compression # event triggers cannot run concurrently with any test that runs DDL # oidjoins is read-only, though, and should run late for best coverage diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index a46f3d0178..5a80bfacd8 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -203,7 +203,6 @@ test: partition_info test: tuplesort test: explain test: compression -test: resultcache test: event_trigger test: oidjoins test: fast_default diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql index eb80a2fe06..eb53668299 100644 --- a/src/test/regress/sql/aggregates.sql +++ b/src/test/regress/sql/aggregates.sql @@ -1098,11 +1098,9 @@ select v||'a', case when v||'a' = 'aa' then 1 else 0 end, count(*) -- Make sure that generation of HashAggregate for uniqification purposes -- does not lead to array overflow due to unexpected duplicate hash keys -- see CAFeeJoKKu0u+A_A9R9316djW-YW3-+Gtgvy3ju655qRHR3jtdA@mail.gmail.com -set enable_resultcache to off; explain (costs off) select 1 from tenk1 where (hundred, thousand) in (select twothousand, twothousand from onek); -reset enable_resultcache; -- -- Hash Aggregation Spill tests diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql index 7f866c603b..8164383fb5 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -550,7 +550,6 @@ reset enable_nestloop; set work_mem to '64kB'; set enable_mergejoin to off; -set enable_resultcache to off; explain (costs off) select count(*) from tenk1 a, tenk1 b @@ -560,7 +559,6 @@ select count(*) from tenk1 a, tenk1 b reset work_mem; reset enable_mergejoin; -reset enable_resultcache; -- -- regression test for 8.2 bug with improper re-ordering of left joins diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql index bd40779d31..6ccb52ad1d 100644 --- a/src/test/regress/sql/partition_prune.sql +++ b/src/test/regress/sql/partition_prune.sql @@ -464,9 +464,6 @@ begin ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); ln := regexp_replace(ln, 'actual rows=\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, 'Hits: \d+', 'Hits: N'); - ln := regexp_replace(ln, 'Misses: \d+', 'Misses: N'); - ln := regexp_replace(ln, 'Memory Usage: \d+', 'Memory Usage: N'); return next ln; end loop; end; diff --git a/src/test/regress/sql/resultcache.sql b/src/test/regress/sql/resultcache.sql deleted file mode 100644 index 150820449c..0000000000 --- a/src/test/regress/sql/resultcache.sql +++ /dev/null @@ -1,85 +0,0 @@ --- Perform tests on the Result Cache node. - --- The cache hits/misses/evictions from the Result Cache node can vary between --- machines. Let's just replace the number with an 'N'. In order to allow us --- to perform validation when the measure was zero, we replace a zero value --- with "Zero". All other numbers are replaced with 'N'. -create function explain_resultcache(query text, hide_hitmiss bool) returns setof text -language plpgsql as -$$ -declare - ln text; -begin - for ln in - execute format('explain (analyze, costs off, summary off, timing off) %s', - query) - loop - if hide_hitmiss = true then - ln := regexp_replace(ln, 'Hits: 0', 'Hits: Zero'); - ln := regexp_replace(ln, 'Hits: \d+', 'Hits: N'); - ln := regexp_replace(ln, 'Misses: 0', 'Misses: Zero'); - ln := regexp_replace(ln, 'Misses: \d+', 'Misses: N'); - end if; - ln := regexp_replace(ln, 'Evictions: 0', 'Evictions: Zero'); - ln := regexp_replace(ln, 'Evictions: \d+', 'Evictions: N'); - ln := regexp_replace(ln, 'Memory Usage: \d+', 'Memory Usage: N'); - return next ln; - end loop; -end; -$$; - --- Ensure we get a result cache on the inner side of the nested loop -SET enable_hashjoin TO off; -SELECT explain_resultcache(' -SELECT COUNT(*),AVG(t1.unique1) FROM tenk1 t1 -INNER JOIN tenk1 t2 ON t1.unique1 = t2.twenty -WHERE t2.unique1 < 1000;', false); - --- And check we get the expected results. -SELECT COUNT(*),AVG(t1.unique1) FROM tenk1 t1 -INNER JOIN tenk1 t2 ON t1.unique1 = t2.twenty -WHERE t2.unique1 < 1000; - --- Try with LATERAL joins -SELECT explain_resultcache(' -SELECT COUNT(*),AVG(t2.unique1) FROM tenk1 t1, -LATERAL (SELECT t2.unique1 FROM tenk1 t2 WHERE t1.twenty = t2.unique1) t2 -WHERE t1.unique1 < 1000;', false); - --- And check we get the expected results. -SELECT COUNT(*),AVG(t2.unique1) FROM tenk1 t1, -LATERAL (SELECT t2.unique1 FROM tenk1 t2 WHERE t1.twenty = t2.unique1) t2 -WHERE t1.unique1 < 1000; - --- Reduce work_mem so that we see some cache evictions -SET work_mem TO '64kB'; -SET enable_mergejoin TO off; --- Ensure we get some evictions. We're unable to validate the hits and misses --- here as the number of entries that fit in the cache at once will vary --- between different machines. -SELECT explain_resultcache(' -SELECT COUNT(*),AVG(t1.unique1) FROM tenk1 t1 -INNER JOIN tenk1 t2 ON t1.unique1 = t2.thousand -WHERE t2.unique1 < 800;', true); -RESET enable_mergejoin; -RESET work_mem; -RESET enable_hashjoin; - --- Test parallel plans with Result Cache. -SET min_parallel_table_scan_size TO 0; -SET parallel_setup_cost TO 0; -SET parallel_tuple_cost TO 0; - --- Ensure we get a parallel plan. -EXPLAIN (COSTS OFF) -SELECT COUNT(*),AVG(t2.unique1) FROM tenk1 t1, -LATERAL (SELECT t2.unique1 FROM tenk1 t2 WHERE t1.twenty = t2.unique1) t2 -WHERE t1.unique1 < 1000; - --- And ensure the parallel plan gives us the correct results. -SELECT COUNT(*),AVG(t2.unique1) FROM tenk1 t1, -LATERAL (SELECT t2.unique1 FROM tenk1 t2 WHERE t1.twenty = t2.unique1) t2 -WHERE t1.unique1 < 1000; -RESET parallel_tuple_cost; -RESET parallel_setup_cost; -RESET min_parallel_table_scan_size;