LCOV - code coverage report
Current view: top level - src/backend/executor - nodeMemoize.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 82.7 % 341 282
Test Date: 2026-02-28 23:15:01 Functions: 94.7 % 19 18
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * nodeMemoize.c
       4              :  *    Routines to handle caching of results from parameterized nodes
       5              :  *
       6              :  * Portions Copyright (c) 2021-2026, PostgreSQL Global Development Group
       7              :  * Portions Copyright (c) 1994, Regents of the University of California
       8              :  *
       9              :  *
      10              :  * IDENTIFICATION
      11              :  *    src/backend/executor/nodeMemoize.c
      12              :  *
      13              :  * Memoize nodes are intended to sit above parameterized nodes in the plan
      14              :  * tree in order to cache results from them.  The intention here is that a
      15              :  * repeat scan with a parameter value that has already been seen by the node
      16              :  * can fetch tuples from the cache rather than having to re-scan the inner
      17              :  * node all over again.  The query planner may choose to make use of one of
      18              :  * these when it thinks rescans for previously seen values are likely enough
      19              :  * to warrant adding the additional node.
      20              :  *
      21              :  * The method of cache we use is a hash table.  When the cache fills, we never
      22              :  * spill tuples to disk, instead, we choose to evict the least recently used
      23              :  * cache entry from the cache.  We remember the least recently used entry by
      24              :  * always pushing new entries and entries we look for onto the tail of a
      25              :  * doubly linked list.  This means that older items always bubble to the top
      26              :  * of this LRU list.
      27              :  *
      28              :  * Sometimes our callers won't run their scans to completion. For example a
      29              :  * semi-join only needs to run until it finds a matching tuple, and once it
      30              :  * does, the join operator skips to the next outer tuple and does not execute
      31              :  * the inner side again on that scan.  Because of this, we must keep track of
      32              :  * when a cache entry is complete, and by default, we know it is when we run
      33              :  * out of tuples to read during the scan.  However, there are cases where we
      34              :  * can mark the cache entry as complete without exhausting the scan of all
      35              :  * tuples.  One case is unique joins, where the join operator knows that there
      36              :  * will only be at most one match for any given outer tuple.  In order to
      37              :  * support such cases we allow the "singlerow" option to be set for the cache.
      38              :  * This option marks the cache entry as complete after we read the first tuple
      39              :  * from the subnode.
      40              :  *
      41              :  * It's possible when we're filling the cache for a given set of parameters
      42              :  * that we're unable to free enough memory to store any more tuples.  If this
      43              :  * happens then we'll have already evicted all other cache entries.  When
      44              :  * caching another tuple would cause us to exceed our memory budget, we must
      45              :  * free the entry that we're currently populating and move the state machine
      46              :  * into MEMO_CACHE_BYPASS_MODE.  This means that we'll not attempt to cache
      47              :  * any further tuples for this particular scan.  We don't have the memory for
      48              :  * it.  The state machine will be reset again on the next rescan.  If the
      49              :  * memory requirements to cache the next parameter's tuples are less
      50              :  * demanding, then that may allow us to start putting useful entries back into
      51              :  * the cache again.
      52              :  *
      53              :  *
      54              :  * INTERFACE ROUTINES
      55              :  *      ExecMemoize         - lookup cache, exec subplan when not found
      56              :  *      ExecInitMemoize     - initialize node and subnodes
      57              :  *      ExecEndMemoize      - shutdown node and subnodes
      58              :  *      ExecReScanMemoize   - rescan the memoize node
      59              :  *
      60              :  *      ExecMemoizeEstimate     estimates DSM space needed for parallel plan
      61              :  *      ExecMemoizeInitializeDSM initialize DSM for parallel plan
      62              :  *      ExecMemoizeInitializeWorker attach to DSM info in parallel worker
      63              :  *      ExecMemoizeRetrieveInstrumentation get instrumentation from worker
      64              :  *-------------------------------------------------------------------------
      65              :  */
      66              : 
      67              : #include "postgres.h"
      68              : 
      69              : #include "access/htup_details.h"
      70              : #include "common/hashfn.h"
      71              : #include "executor/executor.h"
      72              : #include "executor/nodeMemoize.h"
      73              : #include "lib/ilist.h"
      74              : #include "miscadmin.h"
      75              : #include "utils/datum.h"
      76              : #include "utils/lsyscache.h"
      77              : 
      78              : /* States of the ExecMemoize state machine */
      79              : #define MEMO_CACHE_LOOKUP           1   /* Attempt to perform a cache lookup */
      80              : #define MEMO_CACHE_FETCH_NEXT_TUPLE 2   /* Get another tuple from the cache */
      81              : #define MEMO_FILLING_CACHE          3   /* Read outer node to fill cache */
      82              : #define MEMO_CACHE_BYPASS_MODE      4   /* Bypass mode.  Just read from our
      83              :                                          * subplan without caching anything */
      84              : #define MEMO_END_OF_SCAN            5   /* Ready for rescan */
      85              : 
      86              : 
      87              : /* Helper macros for memory accounting */
      88              : #define EMPTY_ENTRY_MEMORY_BYTES(e)     (sizeof(MemoizeEntry) + \
      89              :                                          sizeof(MemoizeKey) + \
      90              :                                          (e)->key->params->t_len);
      91              : #define CACHE_TUPLE_BYTES(t)            (sizeof(MemoizeTuple) + \
      92              :                                          (t)->mintuple->t_len)
      93              : 
      94              :  /* MemoizeTuple Stores an individually cached tuple */
      95              : typedef struct MemoizeTuple
      96              : {
      97              :     MinimalTuple mintuple;      /* Cached tuple */
      98              :     struct MemoizeTuple *next;  /* The next tuple with the same parameter
      99              :                                  * values or NULL if it's the last one */
     100              : } MemoizeTuple;
     101              : 
     102              : /*
     103              :  * MemoizeKey
     104              :  * The hash table key for cached entries plus the LRU list link
     105              :  */
     106              : typedef struct MemoizeKey
     107              : {
     108              :     MinimalTuple params;
     109              :     dlist_node  lru_node;       /* Pointer to next/prev key in LRU list */
     110              : } MemoizeKey;
     111              : 
     112              : /*
     113              :  * MemoizeEntry
     114              :  *      The data struct that the cache hash table stores
     115              :  */
     116              : typedef struct MemoizeEntry
     117              : {
     118              :     MemoizeKey *key;            /* Hash key for hash table lookups */
     119              :     MemoizeTuple *tuplehead;    /* Pointer to the first tuple or NULL if no
     120              :                                  * tuples are cached for this entry */
     121              :     uint32      hash;           /* Hash value (cached) */
     122              :     char        status;         /* Hash status */
     123              :     bool        complete;       /* Did we read the outer plan to completion? */
     124              : } MemoizeEntry;
     125              : 
     126              : 
     127              : #define SH_PREFIX memoize
     128              : #define SH_ELEMENT_TYPE MemoizeEntry
     129              : #define SH_KEY_TYPE MemoizeKey *
     130              : #define SH_SCOPE static inline
     131              : #define SH_DECLARE
     132              : #include "lib/simplehash.h"
     133              : 
     134              : static uint32 MemoizeHash_hash(struct memoize_hash *tb,
     135              :                                const MemoizeKey *key);
     136              : static bool MemoizeHash_equal(struct memoize_hash *tb,
     137              :                               const MemoizeKey *key1,
     138              :                               const MemoizeKey *key2);
     139              : 
     140              : #define SH_PREFIX memoize
     141              : #define SH_ELEMENT_TYPE MemoizeEntry
     142              : #define SH_KEY_TYPE MemoizeKey *
     143              : #define SH_KEY key
     144              : #define SH_HASH_KEY(tb, key) MemoizeHash_hash(tb, key)
     145              : #define SH_EQUAL(tb, a, b) MemoizeHash_equal(tb, a, b)
     146              : #define SH_SCOPE static inline
     147              : #define SH_STORE_HASH
     148              : #define SH_GET_HASH(tb, a) a->hash
     149              : #define SH_DEFINE
     150              : #include "lib/simplehash.h"
     151              : 
     152              : /*
     153              :  * MemoizeHash_hash
     154              :  *      Hash function for simplehash hashtable.  'key' is unused here as we
     155              :  *      require that all table lookups first populate the MemoizeState's
     156              :  *      probeslot with the key values to be looked up.
     157              :  */
     158              : static uint32
     159       394654 : MemoizeHash_hash(struct memoize_hash *tb, const MemoizeKey *key)
     160              : {
     161       394654 :     MemoizeState *mstate = (MemoizeState *) tb->private_data;
     162       394654 :     ExprContext *econtext = mstate->ss.ps.ps_ExprContext;
     163              :     MemoryContext oldcontext;
     164       394654 :     TupleTableSlot *pslot = mstate->probeslot;
     165       394654 :     uint32      hashkey = 0;
     166       394654 :     int         numkeys = mstate->nkeys;
     167              : 
     168       394654 :     oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
     169              : 
     170       394654 :     if (mstate->binary_mode)
     171              :     {
     172       111833 :         for (int i = 0; i < numkeys; i++)
     173              :         {
     174              :             /* combine successive hashkeys by rotating */
     175        60394 :             hashkey = pg_rotate_left32(hashkey, 1);
     176              : 
     177        60394 :             if (!pslot->tts_isnull[i])   /* treat nulls as having hash key 0 */
     178              :             {
     179              :                 CompactAttribute *attr;
     180              :                 uint32      hkey;
     181              : 
     182        60394 :                 attr = TupleDescCompactAttr(pslot->tts_tupleDescriptor, i);
     183              : 
     184        60394 :                 hkey = datum_image_hash(pslot->tts_values[i], attr->attbyval, attr->attlen);
     185              : 
     186        60394 :                 hashkey ^= hkey;
     187              :             }
     188              :         }
     189              :     }
     190              :     else
     191              :     {
     192       343215 :         FmgrInfo   *hashfunctions = mstate->hashfunctions;
     193       343215 :         Oid        *collations = mstate->collations;
     194              : 
     195       686803 :         for (int i = 0; i < numkeys; i++)
     196              :         {
     197              :             /* combine successive hashkeys by rotating */
     198       343588 :             hashkey = pg_rotate_left32(hashkey, 1);
     199              : 
     200       343588 :             if (!pslot->tts_isnull[i])   /* treat nulls as having hash key 0 */
     201              :             {
     202              :                 uint32      hkey;
     203              : 
     204       343102 :                 hkey = DatumGetUInt32(FunctionCall1Coll(&hashfunctions[i],
     205       343102 :                                                         collations[i], pslot->tts_values[i]));
     206       343102 :                 hashkey ^= hkey;
     207              :             }
     208              :         }
     209              :     }
     210              : 
     211       394654 :     MemoryContextSwitchTo(oldcontext);
     212       394654 :     return murmurhash32(hashkey);
     213              : }
     214              : 
     215              : /*
     216              :  * MemoizeHash_equal
     217              :  *      Equality function for confirming hash value matches during a hash
     218              :  *      table lookup.  'key2' is never used.  Instead the MemoizeState's
     219              :  *      probeslot is always populated with details of what's being looked up.
     220              :  */
     221              : static bool
     222       346603 : MemoizeHash_equal(struct memoize_hash *tb, const MemoizeKey *key1,
     223              :                   const MemoizeKey *key2)
     224              : {
     225       346603 :     MemoizeState *mstate = (MemoizeState *) tb->private_data;
     226       346603 :     ExprContext *econtext = mstate->ss.ps.ps_ExprContext;
     227       346603 :     TupleTableSlot *tslot = mstate->tableslot;
     228       346603 :     TupleTableSlot *pslot = mstate->probeslot;
     229              : 
     230              :     /* probeslot should have already been prepared by prepare_probe_slot() */
     231       346603 :     ExecStoreMinimalTuple(key1->params, tslot, false);
     232              : 
     233       346603 :     if (mstate->binary_mode)
     234              :     {
     235              :         MemoryContext oldcontext;
     236        51015 :         int         numkeys = mstate->nkeys;
     237        51015 :         bool        match = true;
     238              : 
     239        51015 :         oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
     240              : 
     241        51015 :         slot_getallattrs(tslot);
     242        51015 :         slot_getallattrs(pslot);
     243              : 
     244       110817 :         for (int i = 0; i < numkeys; i++)
     245              :         {
     246              :             CompactAttribute *attr;
     247              : 
     248        59802 :             if (tslot->tts_isnull[i] != pslot->tts_isnull[i])
     249              :             {
     250            0 :                 match = false;
     251            0 :                 break;
     252              :             }
     253              : 
     254              :             /* both NULL? they're equal */
     255        59802 :             if (tslot->tts_isnull[i])
     256            0 :                 continue;
     257              : 
     258              :             /* perform binary comparison on the two datums */
     259        59802 :             attr = TupleDescCompactAttr(tslot->tts_tupleDescriptor, i);
     260        59802 :             if (!datum_image_eq(tslot->tts_values[i], pslot->tts_values[i],
     261        59802 :                                 attr->attbyval, attr->attlen))
     262              :             {
     263            0 :                 match = false;
     264            0 :                 break;
     265              :             }
     266              :         }
     267              : 
     268        51015 :         MemoryContextSwitchTo(oldcontext);
     269        51015 :         return match;
     270              :     }
     271              :     else
     272              :     {
     273       295588 :         econtext->ecxt_innertuple = tslot;
     274       295588 :         econtext->ecxt_outertuple = pslot;
     275       295588 :         return ExecQual(mstate->cache_eq_expr, econtext);
     276              :     }
     277              : }
     278              : 
     279              : /*
     280              :  * Initialize the hash table to empty.  The MemoizeState's hashtable field
     281              :  * must point to NULL.
     282              :  */
     283              : static void
     284          833 : build_hash_table(MemoizeState *mstate, uint32 size)
     285              : {
     286              :     Assert(mstate->hashtable == NULL);
     287              : 
     288              :     /* Make a guess at a good size when we're not given a valid size. */
     289          833 :     if (size == 0)
     290            0 :         size = 1024;
     291              : 
     292              :     /* memoize_create will convert the size to a power of 2 */
     293          833 :     mstate->hashtable = memoize_create(mstate->tableContext, size, mstate);
     294          833 : }
     295              : 
     296              : /*
     297              :  * prepare_probe_slot
     298              :  *      Populate mstate's probeslot with the values from the tuple stored
     299              :  *      in 'key'.  If 'key' is NULL, then perform the population by evaluating
     300              :  *      mstate's param_exprs.
     301              :  */
     302              : static inline void
     303       394654 : prepare_probe_slot(MemoizeState *mstate, MemoizeKey *key)
     304              : {
     305       394654 :     TupleTableSlot *pslot = mstate->probeslot;
     306       394654 :     TupleTableSlot *tslot = mstate->tableslot;
     307       394654 :     int         numKeys = mstate->nkeys;
     308              : 
     309       394654 :     ExecClearTuple(pslot);
     310              : 
     311       394654 :     if (key == NULL)
     312              :     {
     313       393454 :         ExprContext *econtext = mstate->ss.ps.ps_ExprContext;
     314              :         MemoryContext oldcontext;
     315              : 
     316       393454 :         oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
     317              : 
     318              :         /* Set the probeslot's values based on the current parameter values */
     319       796236 :         for (int i = 0; i < numKeys; i++)
     320       402782 :             pslot->tts_values[i] = ExecEvalExpr(mstate->param_exprs[i],
     321              :                                                 econtext,
     322       402782 :                                                 &pslot->tts_isnull[i]);
     323              : 
     324       393454 :         MemoryContextSwitchTo(oldcontext);
     325              :     }
     326              :     else
     327              :     {
     328              :         /* Process the key's MinimalTuple and store the values in probeslot */
     329         1200 :         ExecStoreMinimalTuple(key->params, tslot, false);
     330         1200 :         slot_getallattrs(tslot);
     331         1200 :         memcpy(pslot->tts_values, tslot->tts_values, sizeof(Datum) * numKeys);
     332         1200 :         memcpy(pslot->tts_isnull, tslot->tts_isnull, sizeof(bool) * numKeys);
     333              :     }
     334              : 
     335       394654 :     ExecStoreVirtualTuple(pslot);
     336       394654 : }
     337              : 
     338              : /*
     339              :  * entry_purge_tuples
     340              :  *      Remove all tuples from the cache entry pointed to by 'entry'.  This
     341              :  *      leaves an empty cache entry.  Also, update the memory accounting to
     342              :  *      reflect the removal of the tuples.
     343              :  */
     344              : static inline void
     345         1194 : entry_purge_tuples(MemoizeState *mstate, MemoizeEntry *entry)
     346              : {
     347         1194 :     MemoizeTuple *tuple = entry->tuplehead;
     348         1194 :     uint64      freed_mem = 0;
     349              : 
     350         2388 :     while (tuple != NULL)
     351              :     {
     352         1194 :         MemoizeTuple *next = tuple->next;
     353              : 
     354         1194 :         freed_mem += CACHE_TUPLE_BYTES(tuple);
     355              : 
     356              :         /* Free memory used for this tuple */
     357         1194 :         pfree(tuple->mintuple);
     358         1194 :         pfree(tuple);
     359              : 
     360         1194 :         tuple = next;
     361              :     }
     362              : 
     363         1194 :     entry->complete = false;
     364         1194 :     entry->tuplehead = NULL;
     365              : 
     366              :     /* Update the memory accounting */
     367         1194 :     mstate->mem_used -= freed_mem;
     368         1194 : }
     369              : 
     370              : /*
     371              :  * remove_cache_entry
     372              :  *      Remove 'entry' from the cache and free memory used by it.
     373              :  */
     374              : static void
     375         1194 : remove_cache_entry(MemoizeState *mstate, MemoizeEntry *entry)
     376              : {
     377         1194 :     MemoizeKey *key = entry->key;
     378              : 
     379         1194 :     dlist_delete(&entry->key->lru_node);
     380              : 
     381              :     /* Remove all of the tuples from this entry */
     382         1194 :     entry_purge_tuples(mstate, entry);
     383              : 
     384              :     /*
     385              :      * Update memory accounting. entry_purge_tuples should have already
     386              :      * subtracted the memory used for each cached tuple.  Here we just update
     387              :      * the amount used by the entry itself.
     388              :      */
     389         1194 :     mstate->mem_used -= EMPTY_ENTRY_MEMORY_BYTES(entry);
     390              : 
     391              :     /* Remove the entry from the cache */
     392         1194 :     memoize_delete_item(mstate->hashtable, entry);
     393              : 
     394         1194 :     pfree(key->params);
     395         1194 :     pfree(key);
     396         1194 : }
     397              : 
     398              : /*
     399              :  * cache_purge_all
     400              :  *      Remove all items from the cache
     401              :  */
     402              : static void
     403            9 : cache_purge_all(MemoizeState *mstate)
     404              : {
     405            9 :     uint64      evictions = 0;
     406              : 
     407            9 :     if (mstate->hashtable != NULL)
     408            6 :         evictions = mstate->hashtable->members;
     409              : 
     410              :     /*
     411              :      * Likely the most efficient way to remove all items is to just reset the
     412              :      * memory context for the cache and then rebuild a fresh hash table.  This
     413              :      * saves having to remove each item one by one and pfree each cached tuple
     414              :      */
     415            9 :     MemoryContextReset(mstate->tableContext);
     416              : 
     417              :     /* NULLify so we recreate the table on the next call */
     418            9 :     mstate->hashtable = NULL;
     419              : 
     420              :     /* reset the LRU list */
     421            9 :     dlist_init(&mstate->lru_list);
     422            9 :     mstate->last_tuple = NULL;
     423            9 :     mstate->entry = NULL;
     424              : 
     425            9 :     mstate->mem_used = 0;
     426              : 
     427              :     /* XXX should we add something new to track these purges? */
     428            9 :     mstate->stats.cache_evictions += evictions; /* Update Stats */
     429            9 : }
     430              : 
     431              : /*
     432              :  * cache_reduce_memory
     433              :  *      Evict older and less recently used items from the cache in order to
     434              :  *      reduce the memory consumption back to something below the
     435              :  *      MemoizeState's mem_limit.
     436              :  *
     437              :  * 'specialkey', if not NULL, causes the function to return false if the entry
     438              :  * which the key belongs to is removed from the cache.
     439              :  */
     440              : static bool
     441         1194 : cache_reduce_memory(MemoizeState *mstate, MemoizeKey *specialkey)
     442              : {
     443         1194 :     bool        specialkey_intact = true;   /* for now */
     444              :     dlist_mutable_iter iter;
     445         1194 :     uint64      evictions = 0;
     446              : 
     447              :     /* Update peak memory usage */
     448         1194 :     if (mstate->mem_used > mstate->stats.mem_peak)
     449            3 :         mstate->stats.mem_peak = mstate->mem_used;
     450              : 
     451              :     /* We expect only to be called when we've gone over budget on memory */
     452              :     Assert(mstate->mem_used > mstate->mem_limit);
     453              : 
     454              :     /* Start the eviction process starting at the head of the LRU list. */
     455         1194 :     dlist_foreach_modify(iter, &mstate->lru_list)
     456              :     {
     457         1194 :         MemoizeKey *key = dlist_container(MemoizeKey, lru_node, iter.cur);
     458              :         MemoizeEntry *entry;
     459              : 
     460              :         /*
     461              :          * Populate the hash probe slot in preparation for looking up this LRU
     462              :          * entry.
     463              :          */
     464         1194 :         prepare_probe_slot(mstate, key);
     465              : 
     466              :         /*
     467              :          * Ideally the LRU list pointers would be stored in the entry itself
     468              :          * rather than in the key.  Unfortunately, we can't do that as the
     469              :          * simplehash.h code may resize the table and allocate new memory for
     470              :          * entries which would result in those pointers pointing to the old
     471              :          * buckets.  However, it's fine to use the key to store this as that's
     472              :          * only referenced by a pointer in the entry, which of course follows
     473              :          * the entry whenever the hash table is resized.  Since we only have a
     474              :          * pointer to the key here, we must perform a hash table lookup to
     475              :          * find the entry that the key belongs to.
     476              :          */
     477         1194 :         entry = memoize_lookup(mstate->hashtable, NULL);
     478              : 
     479              :         /*
     480              :          * Sanity check that we found the entry belonging to the LRU list
     481              :          * item.  A misbehaving hash or equality function could cause the
     482              :          * entry not to be found or the wrong entry to be found.
     483              :          */
     484         1194 :         if (unlikely(entry == NULL || entry->key != key))
     485            0 :             elog(ERROR, "could not find memoization table entry");
     486              : 
     487              :         /*
     488              :          * If we're being called to free memory while the cache is being
     489              :          * populated with new tuples, then we'd better take some care as we
     490              :          * could end up freeing the entry which 'specialkey' belongs to.
     491              :          * Generally callers will pass 'specialkey' as the key for the cache
     492              :          * entry which is currently being populated, so we must set
     493              :          * 'specialkey_intact' to false to inform the caller the specialkey
     494              :          * entry has been removed.
     495              :          */
     496         1194 :         if (key == specialkey)
     497            0 :             specialkey_intact = false;
     498              : 
     499              :         /*
     500              :          * Finally remove the entry.  This will remove from the LRU list too.
     501              :          */
     502         1194 :         remove_cache_entry(mstate, entry);
     503              : 
     504         1194 :         evictions++;
     505              : 
     506              :         /* Exit if we've freed enough memory */
     507         1194 :         if (mstate->mem_used <= mstate->mem_limit)
     508         1194 :             break;
     509              :     }
     510              : 
     511         1194 :     mstate->stats.cache_evictions += evictions; /* Update Stats */
     512              : 
     513         1194 :     return specialkey_intact;
     514              : }
     515              : 
     516              : /*
     517              :  * cache_lookup
     518              :  *      Perform a lookup to see if we've already cached tuples based on the
     519              :  *      scan's current parameters.  If we find an existing entry we move it to
     520              :  *      the end of the LRU list, set *found to true then return it.  If we
     521              :  *      don't find an entry then we create a new one and add it to the end of
     522              :  *      the LRU list.  We also update cache memory accounting and remove older
     523              :  *      entries if we go over the memory budget.  If we managed to free enough
     524              :  *      memory we return the new entry, else we return NULL.
     525              :  *
     526              :  * Callers can assume we'll never return NULL when *found is true.
     527              :  */
     528              : static MemoizeEntry *
     529       393454 : cache_lookup(MemoizeState *mstate, bool *found)
     530              : {
     531              :     MemoizeKey *key;
     532              :     MemoizeEntry *entry;
     533              :     MemoryContext oldcontext;
     534              : 
     535              :     /* prepare the probe slot with the current scan parameters */
     536       393454 :     prepare_probe_slot(mstate, NULL);
     537              : 
     538              :     /*
     539              :      * Add the new entry to the cache.  No need to pass a valid key since the
     540              :      * hash function uses mstate's probeslot, which we populated above.
     541              :      */
     542       393454 :     entry = memoize_insert(mstate->hashtable, NULL, found);
     543              : 
     544       393454 :     if (*found)
     545              :     {
     546              :         /*
     547              :          * Move existing entry to the tail of the LRU list to mark it as the
     548              :          * most recently used item.
     549              :          */
     550       345403 :         dlist_move_tail(&mstate->lru_list, &entry->key->lru_node);
     551              : 
     552       345403 :         return entry;
     553              :     }
     554              : 
     555        48051 :     oldcontext = MemoryContextSwitchTo(mstate->tableContext);
     556              : 
     557              :     /* Allocate a new key */
     558        48051 :     entry->key = key = palloc_object(MemoizeKey);
     559        48051 :     key->params = ExecCopySlotMinimalTuple(mstate->probeslot);
     560              : 
     561              :     /* Update the total cache memory utilization */
     562        48051 :     mstate->mem_used += EMPTY_ENTRY_MEMORY_BYTES(entry);
     563              : 
     564              :     /* Initialize this entry */
     565        48051 :     entry->complete = false;
     566        48051 :     entry->tuplehead = NULL;
     567              : 
     568              :     /*
     569              :      * Since this is the most recently used entry, push this entry onto the
     570              :      * end of the LRU list.
     571              :      */
     572        48051 :     dlist_push_tail(&mstate->lru_list, &entry->key->lru_node);
     573              : 
     574        48051 :     mstate->last_tuple = NULL;
     575              : 
     576        48051 :     MemoryContextSwitchTo(oldcontext);
     577              : 
     578              :     /*
     579              :      * If we've gone over our memory budget, then we'll free up some space in
     580              :      * the cache.
     581              :      */
     582        48051 :     if (mstate->mem_used > mstate->mem_limit)
     583              :     {
     584              :         /*
     585              :          * Try to free up some memory.  It's highly unlikely that we'll fail
     586              :          * to do so here since the entry we've just added is yet to contain
     587              :          * any tuples and we're able to remove any other entry to reduce the
     588              :          * memory consumption.
     589              :          */
     590         1194 :         if (unlikely(!cache_reduce_memory(mstate, key)))
     591            0 :             return NULL;
     592              : 
     593              :         /*
     594              :          * The process of removing entries from the cache may have caused the
     595              :          * code in simplehash.h to shuffle elements to earlier buckets in the
     596              :          * hash table.  If it has, we'll need to find the entry again by
     597              :          * performing a lookup.  Fortunately, we can detect if this has
     598              :          * happened by seeing if the entry is still in use and that the key
     599              :          * pointer matches our expected key.
     600              :          */
     601         1194 :         if (entry->status != memoize_SH_IN_USE || entry->key != key)
     602              :         {
     603              :             /*
     604              :              * We need to repopulate the probeslot as lookups performed during
     605              :              * the cache evictions above will have stored some other key.
     606              :              */
     607            6 :             prepare_probe_slot(mstate, key);
     608              : 
     609              :             /* Re-find the newly added entry */
     610            6 :             entry = memoize_lookup(mstate->hashtable, NULL);
     611              :             Assert(entry != NULL);
     612              :         }
     613              :     }
     614              : 
     615        48051 :     return entry;
     616              : }
     617              : 
     618              : /*
     619              :  * cache_store_tuple
     620              :  *      Add the tuple stored in 'slot' to the mstate's current cache entry.
     621              :  *      The cache entry must have already been made with cache_lookup().
     622              :  *      mstate's last_tuple field must point to the tail of mstate->entry's
     623              :  *      list of tuples.
     624              :  */
     625              : static bool
     626        45145 : cache_store_tuple(MemoizeState *mstate, TupleTableSlot *slot)
     627              : {
     628              :     MemoizeTuple *tuple;
     629        45145 :     MemoizeEntry *entry = mstate->entry;
     630              :     MemoryContext oldcontext;
     631              : 
     632              :     Assert(slot != NULL);
     633              :     Assert(entry != NULL);
     634              : 
     635        45145 :     oldcontext = MemoryContextSwitchTo(mstate->tableContext);
     636              : 
     637        45145 :     tuple = palloc_object(MemoizeTuple);
     638        45145 :     tuple->mintuple = ExecCopySlotMinimalTuple(slot);
     639        45145 :     tuple->next = NULL;
     640              : 
     641              :     /* Account for the memory we just consumed */
     642        45145 :     mstate->mem_used += CACHE_TUPLE_BYTES(tuple);
     643              : 
     644        45145 :     if (entry->tuplehead == NULL)
     645              :     {
     646              :         /*
     647              :          * This is the first tuple for this entry, so just point the list head
     648              :          * to it.
     649              :          */
     650        44916 :         entry->tuplehead = tuple;
     651              :     }
     652              :     else
     653              :     {
     654              :         /* push this tuple onto the tail of the list */
     655          229 :         mstate->last_tuple->next = tuple;
     656              :     }
     657              : 
     658        45145 :     mstate->last_tuple = tuple;
     659        45145 :     MemoryContextSwitchTo(oldcontext);
     660              : 
     661              :     /*
     662              :      * If we've gone over our memory budget then free up some space in the
     663              :      * cache.
     664              :      */
     665        45145 :     if (mstate->mem_used > mstate->mem_limit)
     666              :     {
     667            0 :         MemoizeKey *key = entry->key;
     668              : 
     669            0 :         if (!cache_reduce_memory(mstate, key))
     670            0 :             return false;
     671              : 
     672              :         /*
     673              :          * The process of removing entries from the cache may have caused the
     674              :          * code in simplehash.h to shuffle elements to earlier buckets in the
     675              :          * hash table.  If it has, we'll need to find the entry again by
     676              :          * performing a lookup.  Fortunately, we can detect if this has
     677              :          * happened by seeing if the entry is still in use and that the key
     678              :          * pointer matches our expected key.
     679              :          */
     680            0 :         if (entry->status != memoize_SH_IN_USE || entry->key != key)
     681              :         {
     682              :             /*
     683              :              * We need to repopulate the probeslot as lookups performed during
     684              :              * the cache evictions above will have stored some other key.
     685              :              */
     686            0 :             prepare_probe_slot(mstate, key);
     687              : 
     688              :             /* Re-find the entry */
     689            0 :             mstate->entry = entry = memoize_lookup(mstate->hashtable, NULL);
     690              :             Assert(entry != NULL);
     691              :         }
     692              :     }
     693              : 
     694        45145 :     return true;
     695              : }
     696              : 
     697              : static TupleTableSlot *
     698       472841 : ExecMemoize(PlanState *pstate)
     699              : {
     700       472841 :     MemoizeState *node = castNode(MemoizeState, pstate);
     701       472841 :     ExprContext *econtext = node->ss.ps.ps_ExprContext;
     702              :     PlanState  *outerNode;
     703              :     TupleTableSlot *slot;
     704              : 
     705       472841 :     CHECK_FOR_INTERRUPTS();
     706              : 
     707              :     /*
     708              :      * Reset per-tuple memory context to free any expression evaluation
     709              :      * storage allocated in the previous tuple cycle.
     710              :      */
     711       472841 :     ResetExprContext(econtext);
     712              : 
     713       472841 :     switch (node->mstatus)
     714              :     {
     715       393454 :         case MEMO_CACHE_LOOKUP:
     716              :             {
     717              :                 MemoizeEntry *entry;
     718              :                 TupleTableSlot *outerslot;
     719              :                 bool        found;
     720              : 
     721              :                 Assert(node->entry == NULL);
     722              : 
     723              :                 /* first call? we'll need a hash table. */
     724       393454 :                 if (unlikely(node->hashtable == NULL))
     725          833 :                     build_hash_table(node, ((Memoize *) pstate->plan)->est_entries);
     726              : 
     727              :                 /*
     728              :                  * We're only ever in this state for the first call of the
     729              :                  * scan.  Here we have a look to see if we've already seen the
     730              :                  * current parameters before and if we have already cached a
     731              :                  * complete set of records that the outer plan will return for
     732              :                  * these parameters.
     733              :                  *
     734              :                  * When we find a valid cache entry, we'll return the first
     735              :                  * tuple from it. If not found, we'll create a cache entry and
     736              :                  * then try to fetch a tuple from the outer scan.  If we find
     737              :                  * one there, we'll try to cache it.
     738              :                  */
     739              : 
     740              :                 /* see if we've got anything cached for the current parameters */
     741       393454 :                 entry = cache_lookup(node, &found);
     742              : 
     743       393454 :                 if (found && entry->complete)
     744              :                 {
     745       345403 :                     node->stats.cache_hits += 1; /* stats update */
     746              : 
     747              :                     /*
     748              :                      * Set last_tuple and entry so that the state
     749              :                      * MEMO_CACHE_FETCH_NEXT_TUPLE can easily find the next
     750              :                      * tuple for these parameters.
     751              :                      */
     752       345403 :                     node->last_tuple = entry->tuplehead;
     753       345403 :                     node->entry = entry;
     754              : 
     755              :                     /* Fetch the first cached tuple, if there is one */
     756       345403 :                     if (entry->tuplehead)
     757              :                     {
     758       201309 :                         node->mstatus = MEMO_CACHE_FETCH_NEXT_TUPLE;
     759              : 
     760       201309 :                         slot = node->ss.ps.ps_ResultTupleSlot;
     761       201309 :                         ExecStoreMinimalTuple(entry->tuplehead->mintuple,
     762              :                                               slot, false);
     763              : 
     764       201309 :                         return slot;
     765              :                     }
     766              : 
     767              :                     /* The cache entry is void of any tuples. */
     768       144094 :                     node->mstatus = MEMO_END_OF_SCAN;
     769       144094 :                     return NULL;
     770              :                 }
     771              : 
     772              :                 /* Handle cache miss */
     773        48051 :                 node->stats.cache_misses += 1;   /* stats update */
     774              : 
     775        48051 :                 if (found)
     776              :                 {
     777              :                     /*
     778              :                      * A cache entry was found, but the scan for that entry
     779              :                      * did not run to completion.  We'll just remove all
     780              :                      * tuples and start again.  It might be tempting to
     781              :                      * continue where we left off, but there's no guarantee
     782              :                      * the outer node will produce the tuples in the same
     783              :                      * order as it did last time.
     784              :                      */
     785            0 :                     entry_purge_tuples(node, entry);
     786              :                 }
     787              : 
     788              :                 /* Scan the outer node for a tuple to cache */
     789        48051 :                 outerNode = outerPlanState(node);
     790        48051 :                 outerslot = ExecProcNode(outerNode);
     791        48051 :                 if (TupIsNull(outerslot))
     792              :                 {
     793              :                     /*
     794              :                      * cache_lookup may have returned NULL due to failure to
     795              :                      * free enough cache space, so ensure we don't do anything
     796              :                      * here that assumes it worked. There's no need to go into
     797              :                      * bypass mode here as we're setting mstatus to end of
     798              :                      * scan.
     799              :                      */
     800         3135 :                     if (likely(entry))
     801         3135 :                         entry->complete = true;
     802              : 
     803         3135 :                     node->mstatus = MEMO_END_OF_SCAN;
     804         3135 :                     return NULL;
     805              :                 }
     806              : 
     807        44916 :                 node->entry = entry;
     808              : 
     809              :                 /*
     810              :                  * If we failed to create the entry or failed to store the
     811              :                  * tuple in the entry, then go into bypass mode.
     812              :                  */
     813        44916 :                 if (unlikely(entry == NULL ||
     814              :                              !cache_store_tuple(node, outerslot)))
     815              :                 {
     816            0 :                     node->stats.cache_overflows += 1;    /* stats update */
     817              : 
     818            0 :                     node->mstatus = MEMO_CACHE_BYPASS_MODE;
     819              : 
     820              :                     /*
     821              :                      * No need to clear out last_tuple as we'll stay in bypass
     822              :                      * mode until the end of the scan.
     823              :                      */
     824              :                 }
     825              :                 else
     826              :                 {
     827              :                     /*
     828              :                      * If we only expect a single row from this scan then we
     829              :                      * can mark that we're not expecting more.  This allows
     830              :                      * cache lookups to work even when the scan has not been
     831              :                      * executed to completion.
     832              :                      */
     833        44916 :                     entry->complete = node->singlerow;
     834        44916 :                     node->mstatus = MEMO_FILLING_CACHE;
     835              :                 }
     836              : 
     837        44916 :                 slot = node->ss.ps.ps_ResultTupleSlot;
     838        44916 :                 ExecCopySlot(slot, outerslot);
     839        44916 :                 return slot;
     840              :             }
     841              : 
     842        45098 :         case MEMO_CACHE_FETCH_NEXT_TUPLE:
     843              :             {
     844              :                 /* We shouldn't be in this state if these are not set */
     845              :                 Assert(node->entry != NULL);
     846              :                 Assert(node->last_tuple != NULL);
     847              : 
     848              :                 /* Skip to the next tuple to output */
     849        45098 :                 node->last_tuple = node->last_tuple->next;
     850              : 
     851              :                 /* No more tuples in the cache */
     852        45098 :                 if (node->last_tuple == NULL)
     853              :                 {
     854        42500 :                     node->mstatus = MEMO_END_OF_SCAN;
     855        42500 :                     return NULL;
     856              :                 }
     857              : 
     858         2598 :                 slot = node->ss.ps.ps_ResultTupleSlot;
     859         2598 :                 ExecStoreMinimalTuple(node->last_tuple->mintuple, slot,
     860              :                                       false);
     861              : 
     862         2598 :                 return slot;
     863              :             }
     864              : 
     865        34289 :         case MEMO_FILLING_CACHE:
     866              :             {
     867              :                 TupleTableSlot *outerslot;
     868        34289 :                 MemoizeEntry *entry = node->entry;
     869              : 
     870              :                 /* entry should already have been set by MEMO_CACHE_LOOKUP */
     871              :                 Assert(entry != NULL);
     872              : 
     873              :                 /*
     874              :                  * When in the MEMO_FILLING_CACHE state, we've just had a
     875              :                  * cache miss and are populating the cache with the current
     876              :                  * scan tuples.
     877              :                  */
     878        34289 :                 outerNode = outerPlanState(node);
     879        34289 :                 outerslot = ExecProcNode(outerNode);
     880        34289 :                 if (TupIsNull(outerslot))
     881              :                 {
     882              :                     /* No more tuples.  Mark it as complete */
     883        34060 :                     entry->complete = true;
     884        34060 :                     node->mstatus = MEMO_END_OF_SCAN;
     885        34060 :                     return NULL;
     886              :                 }
     887              : 
     888              :                 /*
     889              :                  * Validate if the planner properly set the singlerow flag. It
     890              :                  * should only set that if each cache entry can, at most,
     891              :                  * return 1 row.
     892              :                  */
     893          229 :                 if (unlikely(entry->complete))
     894            0 :                     elog(ERROR, "cache entry already complete");
     895              : 
     896              :                 /* Record the tuple in the current cache entry */
     897          229 :                 if (unlikely(!cache_store_tuple(node, outerslot)))
     898              :                 {
     899              :                     /* Couldn't store it?  Handle overflow */
     900            0 :                     node->stats.cache_overflows += 1;    /* stats update */
     901              : 
     902            0 :                     node->mstatus = MEMO_CACHE_BYPASS_MODE;
     903              : 
     904              :                     /*
     905              :                      * No need to clear out entry or last_tuple as we'll stay
     906              :                      * in bypass mode until the end of the scan.
     907              :                      */
     908              :                 }
     909              : 
     910          229 :                 slot = node->ss.ps.ps_ResultTupleSlot;
     911          229 :                 ExecCopySlot(slot, outerslot);
     912          229 :                 return slot;
     913              :             }
     914              : 
     915            0 :         case MEMO_CACHE_BYPASS_MODE:
     916              :             {
     917              :                 TupleTableSlot *outerslot;
     918              : 
     919              :                 /*
     920              :                  * When in bypass mode we just continue to read tuples without
     921              :                  * caching.  We need to wait until the next rescan before we
     922              :                  * can come out of this mode.
     923              :                  */
     924            0 :                 outerNode = outerPlanState(node);
     925            0 :                 outerslot = ExecProcNode(outerNode);
     926            0 :                 if (TupIsNull(outerslot))
     927              :                 {
     928            0 :                     node->mstatus = MEMO_END_OF_SCAN;
     929            0 :                     return NULL;
     930              :                 }
     931              : 
     932            0 :                 slot = node->ss.ps.ps_ResultTupleSlot;
     933            0 :                 ExecCopySlot(slot, outerslot);
     934            0 :                 return slot;
     935              :             }
     936              : 
     937            0 :         case MEMO_END_OF_SCAN:
     938              : 
     939              :             /*
     940              :              * We've already returned NULL for this scan, but just in case
     941              :              * something calls us again by mistake.
     942              :              */
     943            0 :             return NULL;
     944              : 
     945            0 :         default:
     946            0 :             elog(ERROR, "unrecognized memoize state: %d",
     947              :                  (int) node->mstatus);
     948              :             return NULL;
     949              :     }                           /* switch */
     950              : }
     951              : 
     952              : MemoizeState *
     953         1002 : ExecInitMemoize(Memoize *node, EState *estate, int eflags)
     954              : {
     955         1002 :     MemoizeState *mstate = makeNode(MemoizeState);
     956              :     Plan       *outerNode;
     957              :     int         i;
     958              :     int         nkeys;
     959              :     Oid        *eqfuncoids;
     960              : 
     961              :     /* check for unsupported flags */
     962              :     Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
     963              : 
     964         1002 :     mstate->ss.ps.plan = (Plan *) node;
     965         1002 :     mstate->ss.ps.state = estate;
     966         1002 :     mstate->ss.ps.ExecProcNode = ExecMemoize;
     967              : 
     968              :     /*
     969              :      * Miscellaneous initialization
     970              :      *
     971              :      * create expression context for node
     972              :      */
     973         1002 :     ExecAssignExprContext(estate, &mstate->ss.ps);
     974              : 
     975         1002 :     outerNode = outerPlan(node);
     976         1002 :     outerPlanState(mstate) = ExecInitNode(outerNode, estate, eflags);
     977              : 
     978              :     /*
     979              :      * Initialize return slot and type. No need to initialize projection info
     980              :      * because this node doesn't do projections.
     981              :      */
     982         1002 :     ExecInitResultTupleSlotTL(&mstate->ss.ps, &TTSOpsMinimalTuple);
     983         1002 :     mstate->ss.ps.ps_ProjInfo = NULL;
     984              : 
     985              :     /*
     986              :      * Initialize scan slot and type.
     987              :      */
     988         1002 :     ExecCreateScanSlotFromOuterPlan(estate, &mstate->ss, &TTSOpsMinimalTuple);
     989              : 
     990              :     /*
     991              :      * Set the state machine to lookup the cache.  We won't find anything
     992              :      * until we cache something, but this saves a special case to create the
     993              :      * first entry.
     994              :      */
     995         1002 :     mstate->mstatus = MEMO_CACHE_LOOKUP;
     996              : 
     997         1002 :     mstate->nkeys = nkeys = node->numKeys;
     998         1002 :     mstate->hashkeydesc = ExecTypeFromExprList(node->param_exprs);
     999         1002 :     mstate->tableslot = MakeSingleTupleTableSlot(mstate->hashkeydesc,
    1000              :                                                  &TTSOpsMinimalTuple);
    1001         1002 :     mstate->probeslot = MakeSingleTupleTableSlot(mstate->hashkeydesc,
    1002              :                                                  &TTSOpsVirtual);
    1003              : 
    1004         1002 :     mstate->param_exprs = (ExprState **) palloc(nkeys * sizeof(ExprState *));
    1005         1002 :     mstate->collations = node->collations;    /* Just point directly to the plan
    1006              :                                              * data */
    1007         1002 :     mstate->hashfunctions = (FmgrInfo *) palloc(nkeys * sizeof(FmgrInfo));
    1008              : 
    1009         1002 :     eqfuncoids = palloc(nkeys * sizeof(Oid));
    1010              : 
    1011         2036 :     for (i = 0; i < nkeys; i++)
    1012              :     {
    1013         1034 :         Oid         hashop = node->hashOperators[i];
    1014              :         Oid         left_hashfn;
    1015              :         Oid         right_hashfn;
    1016         1034 :         Expr       *param_expr = (Expr *) list_nth(node->param_exprs, i);
    1017              : 
    1018         1034 :         if (!get_op_hash_functions(hashop, &left_hashfn, &right_hashfn))
    1019            0 :             elog(ERROR, "could not find hash function for hash operator %u",
    1020              :                  hashop);
    1021              : 
    1022         1034 :         fmgr_info(left_hashfn, &mstate->hashfunctions[i]);
    1023              : 
    1024         1034 :         mstate->param_exprs[i] = ExecInitExpr(param_expr, (PlanState *) mstate);
    1025         1034 :         eqfuncoids[i] = get_opcode(hashop);
    1026              :     }
    1027              : 
    1028         2004 :     mstate->cache_eq_expr = ExecBuildParamSetEqual(mstate->hashkeydesc,
    1029              :                                                    &TTSOpsMinimalTuple,
    1030              :                                                    &TTSOpsVirtual,
    1031              :                                                    eqfuncoids,
    1032         1002 :                                                    node->collations,
    1033         1002 :                                                    node->param_exprs,
    1034              :                                                    (PlanState *) mstate);
    1035              : 
    1036         1002 :     pfree(eqfuncoids);
    1037         1002 :     mstate->mem_used = 0;
    1038              : 
    1039              :     /* Limit the total memory consumed by the cache to this */
    1040         1002 :     mstate->mem_limit = get_hash_memory_limit();
    1041              : 
    1042              :     /* A memory context dedicated for the cache */
    1043         1002 :     mstate->tableContext = AllocSetContextCreate(CurrentMemoryContext,
    1044              :                                                  "MemoizeHashTable",
    1045              :                                                  ALLOCSET_DEFAULT_SIZES);
    1046              : 
    1047         1002 :     dlist_init(&mstate->lru_list);
    1048         1002 :     mstate->last_tuple = NULL;
    1049         1002 :     mstate->entry = NULL;
    1050              : 
    1051              :     /*
    1052              :      * Mark if we can assume the cache entry is completed after we get the
    1053              :      * first record for it.  Some callers might not call us again after
    1054              :      * getting the first match. e.g. A join operator performing a unique join
    1055              :      * is able to skip to the next outer tuple after getting the first
    1056              :      * matching inner tuple.  In this case, the cache entry is complete after
    1057              :      * getting the first tuple.  This allows us to mark it as so.
    1058              :      */
    1059         1002 :     mstate->singlerow = node->singlerow;
    1060         1002 :     mstate->keyparamids = node->keyparamids;
    1061              : 
    1062              :     /*
    1063              :      * Record if the cache keys should be compared bit by bit, or logically
    1064              :      * using the type's hash equality operator
    1065              :      */
    1066         1002 :     mstate->binary_mode = node->binary_mode;
    1067              : 
    1068              :     /* Zero the statistics counters */
    1069         1002 :     memset(&mstate->stats, 0, sizeof(MemoizeInstrumentation));
    1070              : 
    1071              :     /*
    1072              :      * Because it may require a large allocation, we delay building of the
    1073              :      * hash table until executor run.
    1074              :      */
    1075         1002 :     mstate->hashtable = NULL;
    1076              : 
    1077         1002 :     return mstate;
    1078              : }
    1079              : 
    1080              : void
    1081         1002 : ExecEndMemoize(MemoizeState *node)
    1082              : {
    1083              : #ifdef USE_ASSERT_CHECKING
    1084              :     /* Validate the memory accounting code is correct in assert builds. */
    1085              :     if (node->hashtable != NULL)
    1086              :     {
    1087              :         int         count;
    1088              :         uint64      mem = 0;
    1089              :         memoize_iterator i;
    1090              :         MemoizeEntry *entry;
    1091              : 
    1092              :         memoize_start_iterate(node->hashtable, &i);
    1093              : 
    1094              :         count = 0;
    1095              :         while ((entry = memoize_iterate(node->hashtable, &i)) != NULL)
    1096              :         {
    1097              :             MemoizeTuple *tuple = entry->tuplehead;
    1098              : 
    1099              :             mem += EMPTY_ENTRY_MEMORY_BYTES(entry);
    1100              :             while (tuple != NULL)
    1101              :             {
    1102              :                 mem += CACHE_TUPLE_BYTES(tuple);
    1103              :                 tuple = tuple->next;
    1104              :             }
    1105              :             count++;
    1106              :         }
    1107              : 
    1108              :         Assert(count == node->hashtable->members);
    1109              :         Assert(mem == node->mem_used);
    1110              :     }
    1111              : #endif
    1112              : 
    1113              :     /*
    1114              :      * When ending a parallel worker, copy the statistics gathered by the
    1115              :      * worker back into shared memory so that it can be picked up by the main
    1116              :      * process to report in EXPLAIN ANALYZE.
    1117              :      */
    1118         1002 :     if (node->shared_info != NULL && IsParallelWorker())
    1119              :     {
    1120              :         MemoizeInstrumentation *si;
    1121              : 
    1122              :         /* Make mem_peak available for EXPLAIN */
    1123            0 :         if (node->stats.mem_peak == 0)
    1124            0 :             node->stats.mem_peak = node->mem_used;
    1125              : 
    1126              :         Assert(ParallelWorkerNumber <= node->shared_info->num_workers);
    1127            0 :         si = &node->shared_info->sinstrument[ParallelWorkerNumber];
    1128            0 :         memcpy(si, &node->stats, sizeof(MemoizeInstrumentation));
    1129              :     }
    1130              : 
    1131              :     /* Remove the cache context */
    1132         1002 :     MemoryContextDelete(node->tableContext);
    1133              : 
    1134              :     /*
    1135              :      * shut down the subplan
    1136              :      */
    1137         1002 :     ExecEndNode(outerPlanState(node));
    1138         1002 : }
    1139              : 
    1140              : void
    1141       393454 : ExecReScanMemoize(MemoizeState *node)
    1142              : {
    1143       393454 :     PlanState  *outerPlan = outerPlanState(node);
    1144              : 
    1145              :     /* Mark that we must lookup the cache for a new set of parameters */
    1146       393454 :     node->mstatus = MEMO_CACHE_LOOKUP;
    1147              : 
    1148              :     /* nullify pointers used for the last scan */
    1149       393454 :     node->entry = NULL;
    1150       393454 :     node->last_tuple = NULL;
    1151              : 
    1152              :     /*
    1153              :      * if chgParam of subnode is not null then plan will be re-scanned by
    1154              :      * first ExecProcNode.
    1155              :      */
    1156       393454 :     if (outerPlan->chgParam == NULL)
    1157            0 :         ExecReScan(outerPlan);
    1158              : 
    1159              :     /*
    1160              :      * Purge the entire cache if a parameter changed that is not part of the
    1161              :      * cache key.
    1162              :      */
    1163       393454 :     if (bms_nonempty_difference(outerPlan->chgParam, node->keyparamids))
    1164            9 :         cache_purge_all(node);
    1165       393454 : }
    1166              : 
    1167              : /*
    1168              :  * ExecEstimateCacheEntryOverheadBytes
    1169              :  *      For use in the query planner to help it estimate the amount of memory
    1170              :  *      required to store a single entry in the cache.
    1171              :  */
    1172              : double
    1173       166470 : ExecEstimateCacheEntryOverheadBytes(double ntuples)
    1174              : {
    1175       166470 :     return sizeof(MemoizeEntry) + sizeof(MemoizeKey) + sizeof(MemoizeTuple) *
    1176              :         ntuples;
    1177              : }
    1178              : 
    1179              : /* ----------------------------------------------------------------
    1180              :  *                      Parallel Query Support
    1181              :  * ----------------------------------------------------------------
    1182              :  */
    1183              : 
    1184              :  /* ----------------------------------------------------------------
    1185              :   *     ExecMemoizeEstimate
    1186              :   *
    1187              :   *     Estimate space required to propagate memoize statistics.
    1188              :   * ----------------------------------------------------------------
    1189              :   */
    1190              : void
    1191            3 : ExecMemoizeEstimate(MemoizeState *node, ParallelContext *pcxt)
    1192              : {
    1193              :     Size        size;
    1194              : 
    1195              :     /* don't need this if not instrumenting or no workers */
    1196            3 :     if (!node->ss.ps.instrument || pcxt->nworkers == 0)
    1197            3 :         return;
    1198              : 
    1199            0 :     size = mul_size(pcxt->nworkers, sizeof(MemoizeInstrumentation));
    1200            0 :     size = add_size(size, offsetof(SharedMemoizeInfo, sinstrument));
    1201            0 :     shm_toc_estimate_chunk(&pcxt->estimator, size);
    1202            0 :     shm_toc_estimate_keys(&pcxt->estimator, 1);
    1203              : }
    1204              : 
    1205              : /* ----------------------------------------------------------------
    1206              :  *      ExecMemoizeInitializeDSM
    1207              :  *
    1208              :  *      Initialize DSM space for memoize statistics.
    1209              :  * ----------------------------------------------------------------
    1210              :  */
    1211              : void
    1212            3 : ExecMemoizeInitializeDSM(MemoizeState *node, ParallelContext *pcxt)
    1213              : {
    1214              :     Size        size;
    1215              : 
    1216              :     /* don't need this if not instrumenting or no workers */
    1217            3 :     if (!node->ss.ps.instrument || pcxt->nworkers == 0)
    1218            3 :         return;
    1219              : 
    1220            0 :     size = offsetof(SharedMemoizeInfo, sinstrument)
    1221            0 :         + pcxt->nworkers * sizeof(MemoizeInstrumentation);
    1222            0 :     node->shared_info = shm_toc_allocate(pcxt->toc, size);
    1223              :     /* ensure any unfilled slots will contain zeroes */
    1224            0 :     memset(node->shared_info, 0, size);
    1225            0 :     node->shared_info->num_workers = pcxt->nworkers;
    1226            0 :     shm_toc_insert(pcxt->toc, node->ss.ps.plan->plan_node_id,
    1227            0 :                    node->shared_info);
    1228              : }
    1229              : 
    1230              : /* ----------------------------------------------------------------
    1231              :  *      ExecMemoizeInitializeWorker
    1232              :  *
    1233              :  *      Attach worker to DSM space for memoize statistics.
    1234              :  * ----------------------------------------------------------------
    1235              :  */
    1236              : void
    1237            6 : ExecMemoizeInitializeWorker(MemoizeState *node, ParallelWorkerContext *pwcxt)
    1238              : {
    1239            6 :     node->shared_info =
    1240            6 :         shm_toc_lookup(pwcxt->toc, node->ss.ps.plan->plan_node_id, true);
    1241            6 : }
    1242              : 
    1243              : /* ----------------------------------------------------------------
    1244              :  *      ExecMemoizeRetrieveInstrumentation
    1245              :  *
    1246              :  *      Transfer memoize statistics from DSM to private memory.
    1247              :  * ----------------------------------------------------------------
    1248              :  */
    1249              : void
    1250            0 : ExecMemoizeRetrieveInstrumentation(MemoizeState *node)
    1251              : {
    1252              :     Size        size;
    1253              :     SharedMemoizeInfo *si;
    1254              : 
    1255            0 :     if (node->shared_info == NULL)
    1256            0 :         return;
    1257              : 
    1258            0 :     size = offsetof(SharedMemoizeInfo, sinstrument)
    1259            0 :         + node->shared_info->num_workers * sizeof(MemoizeInstrumentation);
    1260            0 :     si = palloc(size);
    1261            0 :     memcpy(si, node->shared_info, size);
    1262            0 :     node->shared_info = si;
    1263              : }
        

Generated by: LCOV version 2.0-1