LCOV - code coverage report
Current view: top level - src/backend/executor - nodeMemoize.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 75.3 % 437 329
Test Date: 2026-01-26 10:56:24 Functions: 84.2 % 19 16
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 47.4 % 196 93

             Branch data     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                 :      120690 : MemoizeHash_hash(struct memoize_hash *tb, const MemoizeKey *key)
     160                 :             : {
     161                 :      120690 :         MemoizeState *mstate = (MemoizeState *) tb->private_data;
     162                 :      120690 :         ExprContext *econtext = mstate->ss.ps.ps_ExprContext;
     163                 :      120690 :         MemoryContext oldcontext;
     164                 :      120690 :         TupleTableSlot *pslot = mstate->probeslot;
     165                 :      120690 :         uint32          hashkey = 0;
     166                 :      120690 :         int                     numkeys = mstate->nkeys;
     167                 :             : 
     168                 :      120690 :         oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
     169                 :             : 
     170         [ +  + ]:      120690 :         if (mstate->binary_mode)
     171                 :             :         {
     172         [ +  + ]:       37265 :                 for (int i = 0; i < numkeys; i++)
     173                 :             :                 {
     174                 :             :                         /* combine successive hashkeys by rotating */
     175                 :       20124 :                         hashkey = pg_rotate_left32(hashkey, 1);
     176                 :             : 
     177         [ -  + ]:       20124 :                         if (!pslot->tts_isnull[i])   /* treat nulls as having hash key 0 */
     178                 :             :                         {
     179                 :       20124 :                                 CompactAttribute *attr;
     180                 :       20124 :                                 uint32          hkey;
     181                 :             : 
     182                 :       20124 :                                 attr = TupleDescCompactAttr(pslot->tts_tupleDescriptor, i);
     183                 :             : 
     184                 :       20124 :                                 hkey = datum_image_hash(pslot->tts_values[i], attr->attbyval, attr->attlen);
     185                 :             : 
     186                 :       20124 :                                 hashkey ^= hkey;
     187                 :       20124 :                         }
     188                 :       20124 :                 }
     189                 :       17141 :         }
     190                 :             :         else
     191                 :             :         {
     192                 :      103549 :                 FmgrInfo   *hashfunctions = mstate->hashfunctions;
     193                 :      103549 :                 Oid                *collations = mstate->collations;
     194                 :             : 
     195         [ +  + ]:      207138 :                 for (int i = 0; i < numkeys; i++)
     196                 :             :                 {
     197                 :             :                         /* combine successive hashkeys by rotating */
     198                 :      103589 :                         hashkey = pg_rotate_left32(hashkey, 1);
     199                 :             : 
     200         [ +  + ]:      103589 :                         if (!pslot->tts_isnull[i])   /* treat nulls as having hash key 0 */
     201                 :             :                         {
     202                 :      103449 :                                 uint32          hkey;
     203                 :             : 
     204                 :      206898 :                                 hkey = DatumGetUInt32(FunctionCall1Coll(&hashfunctions[i],
     205                 :      103449 :                                                                                                                 collations[i], pslot->tts_values[i]));
     206                 :      103449 :                                 hashkey ^= hkey;
     207                 :      103449 :                         }
     208                 :      103589 :                 }
     209                 :      103549 :         }
     210                 :             : 
     211                 :      120690 :         MemoryContextSwitchTo(oldcontext);
     212                 :      241380 :         return murmurhash32(hashkey);
     213                 :      120690 : }
     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                 :           0 : MemoizeHash_equal(struct memoize_hash *tb, const MemoizeKey *key1,
     223                 :             :                                   const MemoizeKey *key2)
     224                 :             : {
     225                 :           0 :         MemoizeState *mstate = (MemoizeState *) tb->private_data;
     226                 :           0 :         ExprContext *econtext = mstate->ss.ps.ps_ExprContext;
     227                 :           0 :         TupleTableSlot *tslot = mstate->tableslot;
     228                 :           0 :         TupleTableSlot *pslot = mstate->probeslot;
     229                 :             : 
     230                 :             :         /* probeslot should have already been prepared by prepare_probe_slot() */
     231                 :           0 :         ExecStoreMinimalTuple(key1->params, tslot, false);
     232                 :             : 
     233         [ #  # ]:           0 :         if (mstate->binary_mode)
     234                 :             :         {
     235                 :           0 :                 MemoryContext oldcontext;
     236                 :           0 :                 int                     numkeys = mstate->nkeys;
     237                 :           0 :                 bool            match = true;
     238                 :             : 
     239                 :           0 :                 oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
     240                 :             : 
     241                 :           0 :                 slot_getallattrs(tslot);
     242                 :           0 :                 slot_getallattrs(pslot);
     243                 :             : 
     244         [ #  # ]:           0 :                 for (int i = 0; i < numkeys; i++)
     245                 :             :                 {
     246                 :           0 :                         CompactAttribute *attr;
     247                 :             : 
     248         [ #  # ]:           0 :                         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         [ #  # ]:           0 :                         if (tslot->tts_isnull[i])
     256                 :           0 :                                 continue;
     257                 :             : 
     258                 :             :                         /* perform binary comparison on the two datums */
     259                 :           0 :                         attr = TupleDescCompactAttr(tslot->tts_tupleDescriptor, i);
     260   [ #  #  #  # ]:           0 :                         if (!datum_image_eq(tslot->tts_values[i], pslot->tts_values[i],
     261                 :           0 :                                                                 attr->attbyval, attr->attlen))
     262                 :             :                         {
     263                 :           0 :                                 match = false;
     264                 :           0 :                                 break;
     265                 :             :                         }
     266      [ #  #  # ]:           0 :                 }
     267                 :             : 
     268                 :           0 :                 MemoryContextSwitchTo(oldcontext);
     269                 :           0 :                 return match;
     270                 :           0 :         }
     271                 :             :         else
     272                 :             :         {
     273                 :           0 :                 econtext->ecxt_innertuple = tslot;
     274                 :           0 :                 econtext->ecxt_outertuple = pslot;
     275                 :           0 :                 return ExecQual(mstate->cache_eq_expr, econtext);
     276                 :             :         }
     277                 :           0 : }
     278                 :             : 
     279                 :             : /*
     280                 :             :  * Initialize the hash table to empty.  The MemoizeState's hashtable field
     281                 :             :  * must point to NULL.
     282                 :             :  */
     283                 :             : static void
     284                 :         177 : build_hash_table(MemoizeState *mstate, uint32 size)
     285                 :             : {
     286         [ +  - ]:         177 :         Assert(mstate->hashtable == NULL);
     287                 :             : 
     288                 :             :         /* Make a guess at a good size when we're not given a valid size. */
     289         [ +  - ]:         177 :         if (size == 0)
     290                 :           0 :                 size = 1024;
     291                 :             : 
     292                 :             :         /* memoize_create will convert the size to a power of 2 */
     293                 :         177 :         mstate->hashtable = memoize_create(mstate->tableContext, size, mstate);
     294                 :         177 : }
     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                 :      120690 : prepare_probe_slot(MemoizeState *mstate, MemoizeKey *key)
     304                 :             : {
     305                 :      120690 :         TupleTableSlot *pslot = mstate->probeslot;
     306                 :      120690 :         TupleTableSlot *tslot = mstate->tableslot;
     307                 :      120690 :         int                     numKeys = mstate->nkeys;
     308                 :             : 
     309                 :      120690 :         ExecClearTuple(pslot);
     310                 :             : 
     311         [ +  + ]:      120690 :         if (key == NULL)
     312                 :             :         {
     313                 :      120290 :                 ExprContext *econtext = mstate->ss.ps.ps_ExprContext;
     314                 :      120290 :                 MemoryContext oldcontext;
     315                 :             : 
     316                 :      120290 :                 oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
     317                 :             : 
     318                 :             :                 /* Set the probeslot's values based on the current parameter values */
     319         [ +  + ]:      243603 :                 for (int i = 0; i < numKeys; i++)
     320                 :      246626 :                         pslot->tts_values[i] = ExecEvalExpr(mstate->param_exprs[i],
     321                 :      123313 :                                                                                                 econtext,
     322                 :      123313 :                                                                                                 &pslot->tts_isnull[i]);
     323                 :             : 
     324                 :      120290 :                 MemoryContextSwitchTo(oldcontext);
     325                 :      120290 :         }
     326                 :             :         else
     327                 :             :         {
     328                 :             :                 /* Process the key's MinimalTuple and store the values in probeslot */
     329                 :         400 :                 ExecStoreMinimalTuple(key->params, tslot, false);
     330                 :         400 :                 slot_getallattrs(tslot);
     331                 :         400 :                 memcpy(pslot->tts_values, tslot->tts_values, sizeof(Datum) * numKeys);
     332                 :         400 :                 memcpy(pslot->tts_isnull, tslot->tts_isnull, sizeof(bool) * numKeys);
     333                 :             :         }
     334                 :             : 
     335                 :      120690 :         ExecStoreVirtualTuple(pslot);
     336                 :      120690 : }
     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                 :         398 : entry_purge_tuples(MemoizeState *mstate, MemoizeEntry *entry)
     346                 :             : {
     347                 :         398 :         MemoizeTuple *tuple = entry->tuplehead;
     348                 :         398 :         uint64          freed_mem = 0;
     349                 :             : 
     350         [ +  + ]:         796 :         while (tuple != NULL)
     351                 :             :         {
     352                 :         398 :                 MemoizeTuple *next = tuple->next;
     353                 :             : 
     354                 :         398 :                 freed_mem += CACHE_TUPLE_BYTES(tuple);
     355                 :             : 
     356                 :             :                 /* Free memory used for this tuple */
     357                 :         398 :                 pfree(tuple->mintuple);
     358                 :         398 :                 pfree(tuple);
     359                 :             : 
     360                 :         398 :                 tuple = next;
     361                 :         398 :         }
     362                 :             : 
     363                 :         398 :         entry->complete = false;
     364                 :         398 :         entry->tuplehead = NULL;
     365                 :             : 
     366                 :             :         /* Update the memory accounting */
     367                 :         398 :         mstate->mem_used -= freed_mem;
     368                 :         398 : }
     369                 :             : 
     370                 :             : /*
     371                 :             :  * remove_cache_entry
     372                 :             :  *              Remove 'entry' from the cache and free memory used by it.
     373                 :             :  */
     374                 :             : static void
     375                 :           0 : remove_cache_entry(MemoizeState *mstate, MemoizeEntry *entry)
     376                 :             : {
     377                 :           0 :         MemoizeKey *key = entry->key;
     378                 :             : 
     379                 :           0 :         dlist_delete(&entry->key->lru_node);
     380                 :             : 
     381                 :             :         /* Remove all of the tuples from this entry */
     382                 :           0 :         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                 :           0 :         mstate->mem_used -= EMPTY_ENTRY_MEMORY_BYTES(entry);
     390                 :             : 
     391                 :             :         /* Remove the entry from the cache */
     392                 :           0 :         memoize_delete_item(mstate->hashtable, entry);
     393                 :             : 
     394                 :           0 :         pfree(key->params);
     395                 :           0 :         pfree(key);
     396                 :           0 : }
     397                 :             : 
     398                 :             : /*
     399                 :             :  * cache_purge_all
     400                 :             :  *              Remove all items from the cache
     401                 :             :  */
     402                 :             : static void
     403                 :           3 : cache_purge_all(MemoizeState *mstate)
     404                 :             : {
     405                 :           3 :         uint64          evictions = 0;
     406                 :             : 
     407         [ +  + ]:           3 :         if (mstate->hashtable != NULL)
     408                 :           2 :                 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                 :           3 :         MemoryContextReset(mstate->tableContext);
     416                 :             : 
     417                 :             :         /* NULLify so we recreate the table on the next call */
     418                 :           3 :         mstate->hashtable = NULL;
     419                 :             : 
     420                 :             :         /* reset the LRU list */
     421                 :           3 :         dlist_init(&mstate->lru_list);
     422                 :           3 :         mstate->last_tuple = NULL;
     423                 :           3 :         mstate->entry = NULL;
     424                 :             : 
     425                 :           3 :         mstate->mem_used = 0;
     426                 :             : 
     427                 :             :         /* XXX should we add something new to track these purges? */
     428                 :           3 :         mstate->stats.cache_evictions += evictions; /* Update Stats */
     429                 :           3 : }
     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                 :         398 : cache_reduce_memory(MemoizeState *mstate, MemoizeKey *specialkey)
     442                 :             : {
     443                 :         398 :         bool            specialkey_intact = true;       /* for now */
     444                 :         398 :         dlist_mutable_iter iter;
     445                 :         398 :         uint64          evictions = 0;
     446                 :             : 
     447                 :             :         /* Update peak memory usage */
     448         [ +  + ]:         398 :         if (mstate->mem_used > mstate->stats.mem_peak)
     449                 :           1 :                 mstate->stats.mem_peak = mstate->mem_used;
     450                 :             : 
     451                 :             :         /* We expect only to be called when we've gone over budget on memory */
     452         [ +  - ]:         398 :         Assert(mstate->mem_used > mstate->mem_limit);
     453                 :             : 
     454                 :             :         /* Start the eviction process starting at the head of the LRU list. */
     455   [ +  -  -  + ]:         398 :         dlist_foreach_modify(iter, &mstate->lru_list)
     456                 :             :         {
     457                 :         398 :                 MemoizeKey *key = dlist_container(MemoizeKey, lru_node, iter.cur);
     458                 :         398 :                 MemoizeEntry *entry;
     459                 :             : 
     460                 :             :                 /*
     461                 :             :                  * Populate the hash probe slot in preparation for looking up this LRU
     462                 :             :                  * entry.
     463                 :             :                  */
     464                 :         398 :                 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                 :         398 :                 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   [ -  +  +  - ]:         398 :                 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         [ -  + ]:         398 :                 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                 :         398 :                 remove_cache_entry(mstate, entry);
     503                 :             : 
     504                 :         398 :                 evictions++;
     505                 :             : 
     506                 :             :                 /* Exit if we've freed enough memory */
     507         [ +  - ]:         398 :                 if (mstate->mem_used <= mstate->mem_limit)
     508                 :         398 :                         break;
     509      [ -  +  - ]:         398 :         }
     510                 :             : 
     511                 :         398 :         mstate->stats.cache_evictions += evictions; /* Update Stats */
     512                 :             : 
     513                 :         796 :         return specialkey_intact;
     514                 :         398 : }
     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                 :      120290 : cache_lookup(MemoizeState *mstate, bool *found)
     530                 :             : {
     531                 :      120290 :         MemoizeKey *key;
     532                 :      120290 :         MemoizeEntry *entry;
     533                 :      120290 :         MemoryContext oldcontext;
     534                 :             : 
     535                 :             :         /* prepare the probe slot with the current scan parameters */
     536                 :      120290 :         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                 :      120290 :         entry = memoize_insert(mstate->hashtable, NULL, found);
     543                 :             : 
     544         [ +  + ]:      120290 :         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                 :      105340 :                 dlist_move_tail(&mstate->lru_list, &entry->key->lru_node);
     551                 :             : 
     552                 :      105340 :                 return entry;
     553                 :             :         }
     554                 :             : 
     555                 :       14950 :         oldcontext = MemoryContextSwitchTo(mstate->tableContext);
     556                 :             : 
     557                 :             :         /* Allocate a new key */
     558                 :       14950 :         entry->key = key = palloc_object(MemoizeKey);
     559                 :       14950 :         key->params = ExecCopySlotMinimalTuple(mstate->probeslot);
     560                 :             : 
     561                 :             :         /* Update the total cache memory utilization */
     562                 :       14950 :         mstate->mem_used += EMPTY_ENTRY_MEMORY_BYTES(entry);
     563                 :             : 
     564                 :             :         /* Initialize this entry */
     565                 :       14950 :         entry->complete = false;
     566                 :       14950 :         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                 :       14950 :         dlist_push_tail(&mstate->lru_list, &entry->key->lru_node);
     573                 :             : 
     574                 :       14950 :         mstate->last_tuple = NULL;
     575                 :             : 
     576                 :       14950 :         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         [ +  + ]:       14950 :         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         [ -  + ]:         398 :                 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   [ +  -  +  + ]:         398 :                 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                 :           2 :                         prepare_probe_slot(mstate, key);
     608                 :             : 
     609                 :             :                         /* Re-find the newly added entry */
     610                 :           2 :                         entry = memoize_lookup(mstate->hashtable, NULL);
     611         [ +  - ]:           2 :                         Assert(entry != NULL);
     612                 :           2 :                 }
     613                 :         398 :         }
     614                 :             : 
     615                 :       14950 :         return entry;
     616                 :      120290 : }
     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                 :       13822 : cache_store_tuple(MemoizeState *mstate, TupleTableSlot *slot)
     627                 :             : {
     628                 :       13822 :         MemoizeTuple *tuple;
     629                 :       13822 :         MemoizeEntry *entry = mstate->entry;
     630                 :       13822 :         MemoryContext oldcontext;
     631                 :             : 
     632         [ +  - ]:       13822 :         Assert(slot != NULL);
     633         [ +  - ]:       13822 :         Assert(entry != NULL);
     634                 :             : 
     635                 :       13822 :         oldcontext = MemoryContextSwitchTo(mstate->tableContext);
     636                 :             : 
     637                 :       13822 :         tuple = palloc_object(MemoizeTuple);
     638                 :       13822 :         tuple->mintuple = ExecCopySlotMinimalTuple(slot);
     639                 :       13822 :         tuple->next = NULL;
     640                 :             : 
     641                 :             :         /* Account for the memory we just consumed */
     642                 :       13822 :         mstate->mem_used += CACHE_TUPLE_BYTES(tuple);
     643                 :             : 
     644         [ +  + ]:       13822 :         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                 :       13752 :                 entry->tuplehead = tuple;
     651                 :       13752 :         }
     652                 :             :         else
     653                 :             :         {
     654                 :             :                 /* push this tuple onto the tail of the list */
     655                 :          70 :                 mstate->last_tuple->next = tuple;
     656                 :             :         }
     657                 :             : 
     658                 :       13822 :         mstate->last_tuple = tuple;
     659                 :       13822 :         MemoryContextSwitchTo(oldcontext);
     660                 :             : 
     661                 :             :         /*
     662                 :             :          * If we've gone over our memory budget then free up some space in the
     663                 :             :          * cache.
     664                 :             :          */
     665         [ +  - ]:       13822 :         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         [ #  # ]:           0 :                         Assert(entry != NULL);
     691                 :           0 :                 }
     692         [ #  # ]:           0 :         }
     693                 :             : 
     694                 :       13822 :         return true;
     695                 :       13822 : }
     696                 :             : 
     697                 :             : static TupleTableSlot *
     698                 :      146640 : ExecMemoize(PlanState *pstate)
     699                 :             : {
     700                 :      146640 :         MemoizeState *node = castNode(MemoizeState, pstate);
     701                 :      146640 :         ExprContext *econtext = node->ss.ps.ps_ExprContext;
     702                 :      146640 :         PlanState  *outerNode;
     703                 :      146640 :         TupleTableSlot *slot;
     704                 :             : 
     705         [ +  - ]:      146640 :         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                 :      146640 :         ResetExprContext(econtext);
     712                 :             : 
     713   [ +  +  +  -  :      146640 :         switch (node->mstatus)
                   -  - ]
     714                 :             :         {
     715                 :             :                 case MEMO_CACHE_LOOKUP:
     716                 :             :                         {
     717                 :      120290 :                                 MemoizeEntry *entry;
     718                 :      120290 :                                 TupleTableSlot *outerslot;
     719                 :      120290 :                                 bool            found;
     720                 :             : 
     721         [ +  - ]:      120290 :                                 Assert(node->entry == NULL);
     722                 :             : 
     723                 :             :                                 /* first call? we'll need a hash table. */
     724         [ +  + ]:      120290 :                                 if (unlikely(node->hashtable == NULL))
     725                 :         177 :                                         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                 :      120290 :                                 entry = cache_lookup(node, &found);
     742                 :             : 
     743   [ +  +  -  + ]:      120290 :                                 if (found && entry->complete)
     744                 :             :                                 {
     745                 :      105340 :                                         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                 :      105340 :                                         node->last_tuple = entry->tuplehead;
     753                 :      105340 :                                         node->entry = entry;
     754                 :             : 
     755                 :             :                                         /* Fetch the first cached tuple, if there is one */
     756         [ +  + ]:      105340 :                                         if (entry->tuplehead)
     757                 :             :                                         {
     758                 :       58165 :                                                 node->mstatus = MEMO_CACHE_FETCH_NEXT_TUPLE;
     759                 :             : 
     760                 :       58165 :                                                 slot = node->ss.ps.ps_ResultTupleSlot;
     761                 :      116330 :                                                 ExecStoreMinimalTuple(entry->tuplehead->mintuple,
     762                 :       58165 :                                                                                           slot, false);
     763                 :             : 
     764                 :       58165 :                                                 return slot;
     765                 :             :                                         }
     766                 :             : 
     767                 :             :                                         /* The cache entry is void of any tuples. */
     768                 :       47175 :                                         node->mstatus = MEMO_END_OF_SCAN;
     769                 :       47175 :                                         return NULL;
     770                 :             :                                 }
     771                 :             : 
     772                 :             :                                 /* Handle cache miss */
     773                 :       14950 :                                 node->stats.cache_misses += 1;       /* stats update */
     774                 :             : 
     775         [ +  - ]:       14950 :                                 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                 :           0 :                                 }
     787                 :             : 
     788                 :             :                                 /* Scan the outer node for a tuple to cache */
     789                 :       14950 :                                 outerNode = outerPlanState(node);
     790                 :       14950 :                                 outerslot = ExecProcNode(outerNode);
     791   [ +  -  +  + ]:       14950 :                                 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         [ +  - ]:        1198 :                                         if (likely(entry))
     801                 :        1198 :                                                 entry->complete = true;
     802                 :             : 
     803                 :        1198 :                                         node->mstatus = MEMO_END_OF_SCAN;
     804                 :        1198 :                                         return NULL;
     805                 :             :                                 }
     806                 :             : 
     807                 :       13752 :                                 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   [ -  +  +  - ]:       13752 :                                 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                 :           0 :                                 }
     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                 :       13752 :                                         entry->complete = node->singlerow;
     834                 :       13752 :                                         node->mstatus = MEMO_FILLING_CACHE;
     835                 :             :                                 }
     836                 :             : 
     837                 :       13752 :                                 slot = node->ss.ps.ps_ResultTupleSlot;
     838                 :       13752 :                                 ExecCopySlot(slot, outerslot);
     839                 :       13752 :                                 return slot;
     840                 :      120290 :                         }
     841                 :             : 
     842                 :             :                 case MEMO_CACHE_FETCH_NEXT_TUPLE:
     843                 :             :                         {
     844                 :             :                                 /* We shouldn't be in this state if these are not set */
     845         [ +  - ]:       15046 :                                 Assert(node->entry != NULL);
     846         [ +  - ]:       15046 :                                 Assert(node->last_tuple != NULL);
     847                 :             : 
     848                 :             :                                 /* Skip to the next tuple to output */
     849                 :       15046 :                                 node->last_tuple = node->last_tuple->next;
     850                 :             : 
     851                 :             :                                 /* No more tuples in the cache */
     852         [ +  + ]:       15046 :                                 if (node->last_tuple == NULL)
     853                 :             :                                 {
     854                 :       14180 :                                         node->mstatus = MEMO_END_OF_SCAN;
     855                 :       14180 :                                         return NULL;
     856                 :             :                                 }
     857                 :             : 
     858                 :         866 :                                 slot = node->ss.ps.ps_ResultTupleSlot;
     859                 :         866 :                                 ExecStoreMinimalTuple(node->last_tuple->mintuple, slot,
     860                 :             :                                                                           false);
     861                 :             : 
     862                 :         866 :                                 return slot;
     863                 :             :                         }
     864                 :             : 
     865                 :             :                 case MEMO_FILLING_CACHE:
     866                 :             :                         {
     867                 :       11304 :                                 TupleTableSlot *outerslot;
     868                 :       11304 :                                 MemoizeEntry *entry = node->entry;
     869                 :             : 
     870                 :             :                                 /* entry should already have been set by MEMO_CACHE_LOOKUP */
     871         [ +  - ]:       11304 :                                 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                 :       11304 :                                 outerNode = outerPlanState(node);
     879                 :       11304 :                                 outerslot = ExecProcNode(outerNode);
     880   [ +  +  +  + ]:       11304 :                                 if (TupIsNull(outerslot))
     881                 :             :                                 {
     882                 :             :                                         /* No more tuples.  Mark it as complete */
     883                 :       11234 :                                         entry->complete = true;
     884                 :       11234 :                                         node->mstatus = MEMO_END_OF_SCAN;
     885                 :       11234 :                                         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         [ +  - ]:          70 :                                 if (unlikely(entry->complete))
     894   [ #  #  #  # ]:           0 :                                         elog(ERROR, "cache entry already complete");
     895                 :             : 
     896                 :             :                                 /* Record the tuple in the current cache entry */
     897         [ -  + ]:          70 :                                 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                 :           0 :                                 }
     909                 :             : 
     910                 :          70 :                                 slot = node->ss.ps.ps_ResultTupleSlot;
     911                 :          70 :                                 ExecCopySlot(slot, outerslot);
     912                 :          70 :                                 return slot;
     913                 :       11304 :                         }
     914                 :             : 
     915                 :             :                 case MEMO_CACHE_BYPASS_MODE:
     916                 :             :                         {
     917                 :           0 :                                 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                 :           0 :                         }
     936                 :             : 
     937                 :             :                 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                 :             :                 default:
     946   [ #  #  #  # ]:           0 :                         elog(ERROR, "unrecognized memoize state: %d",
     947                 :             :                                  (int) node->mstatus);
     948                 :           0 :                         return NULL;
     949                 :             :         }                                                       /* switch */
     950                 :      146640 : }
     951                 :             : 
     952                 :             : MemoizeState *
     953                 :         217 : ExecInitMemoize(Memoize *node, EState *estate, int eflags)
     954                 :             : {
     955                 :         217 :         MemoizeState *mstate = makeNode(MemoizeState);
     956                 :         217 :         Plan       *outerNode;
     957                 :         217 :         int                     i;
     958                 :         217 :         int                     nkeys;
     959                 :         217 :         Oid                *eqfuncoids;
     960                 :             : 
     961                 :             :         /* check for unsupported flags */
     962         [ +  - ]:         217 :         Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
     963                 :             : 
     964                 :         217 :         mstate->ss.ps.plan = (Plan *) node;
     965                 :         217 :         mstate->ss.ps.state = estate;
     966                 :         217 :         mstate->ss.ps.ExecProcNode = ExecMemoize;
     967                 :             : 
     968                 :             :         /*
     969                 :             :          * Miscellaneous initialization
     970                 :             :          *
     971                 :             :          * create expression context for node
     972                 :             :          */
     973                 :         217 :         ExecAssignExprContext(estate, &mstate->ss.ps);
     974                 :             : 
     975                 :         217 :         outerNode = outerPlan(node);
     976                 :         217 :         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                 :         217 :         ExecInitResultTupleSlotTL(&mstate->ss.ps, &TTSOpsMinimalTuple);
     983                 :         217 :         mstate->ss.ps.ps_ProjInfo = NULL;
     984                 :             : 
     985                 :             :         /*
     986                 :             :          * Initialize scan slot and type.
     987                 :             :          */
     988                 :         217 :         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                 :         217 :         mstate->mstatus = MEMO_CACHE_LOOKUP;
     996                 :             : 
     997                 :         217 :         mstate->nkeys = nkeys = node->numKeys;
     998                 :         217 :         mstate->hashkeydesc = ExecTypeFromExprList(node->param_exprs);
     999                 :         217 :         mstate->tableslot = MakeSingleTupleTableSlot(mstate->hashkeydesc,
    1000                 :             :                                                                                                  &TTSOpsMinimalTuple);
    1001                 :         217 :         mstate->probeslot = MakeSingleTupleTableSlot(mstate->hashkeydesc,
    1002                 :             :                                                                                                  &TTSOpsVirtual);
    1003                 :             : 
    1004                 :         217 :         mstate->param_exprs = (ExprState **) palloc(nkeys * sizeof(ExprState *));
    1005                 :         217 :         mstate->collations = node->collations;    /* Just point directly to the plan
    1006                 :             :                                                                                          * data */
    1007                 :         217 :         mstate->hashfunctions = (FmgrInfo *) palloc(nkeys * sizeof(FmgrInfo));
    1008                 :             : 
    1009                 :         217 :         eqfuncoids = palloc(nkeys * sizeof(Oid));
    1010                 :             : 
    1011         [ +  + ]:         444 :         for (i = 0; i < nkeys; i++)
    1012                 :             :         {
    1013                 :         227 :                 Oid                     hashop = node->hashOperators[i];
    1014                 :         227 :                 Oid                     left_hashfn;
    1015                 :         227 :                 Oid                     right_hashfn;
    1016                 :         227 :                 Expr       *param_expr = (Expr *) list_nth(node->param_exprs, i);
    1017                 :             : 
    1018         [ +  - ]:         227 :                 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                 :         227 :                 fmgr_info(left_hashfn, &mstate->hashfunctions[i]);
    1023                 :             : 
    1024                 :         227 :                 mstate->param_exprs[i] = ExecInitExpr(param_expr, (PlanState *) mstate);
    1025                 :         227 :                 eqfuncoids[i] = get_opcode(hashop);
    1026                 :         227 :         }
    1027                 :             : 
    1028                 :         434 :         mstate->cache_eq_expr = ExecBuildParamSetEqual(mstate->hashkeydesc,
    1029                 :             :                                                                                                    &TTSOpsMinimalTuple,
    1030                 :             :                                                                                                    &TTSOpsVirtual,
    1031                 :         217 :                                                                                                    eqfuncoids,
    1032                 :         217 :                                                                                                    node->collations,
    1033                 :         217 :                                                                                                    node->param_exprs,
    1034                 :         217 :                                                                                                    (PlanState *) mstate);
    1035                 :             : 
    1036                 :         217 :         pfree(eqfuncoids);
    1037                 :         217 :         mstate->mem_used = 0;
    1038                 :             : 
    1039                 :             :         /* Limit the total memory consumed by the cache to this */
    1040                 :         217 :         mstate->mem_limit = get_hash_memory_limit();
    1041                 :             : 
    1042                 :             :         /* A memory context dedicated for the cache */
    1043                 :         217 :         mstate->tableContext = AllocSetContextCreate(CurrentMemoryContext,
    1044                 :             :                                                                                                  "MemoizeHashTable",
    1045                 :             :                                                                                                  ALLOCSET_DEFAULT_SIZES);
    1046                 :             : 
    1047                 :         217 :         dlist_init(&mstate->lru_list);
    1048                 :         217 :         mstate->last_tuple = NULL;
    1049                 :         217 :         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                 :         217 :         mstate->singlerow = node->singlerow;
    1060                 :         217 :         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                 :         217 :         mstate->binary_mode = node->binary_mode;
    1067                 :             : 
    1068                 :             :         /* Zero the statistics counters */
    1069                 :         217 :         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                 :         217 :         mstate->hashtable = NULL;
    1076                 :             : 
    1077                 :         434 :         return mstate;
    1078                 :         217 : }
    1079                 :             : 
    1080                 :             : void
    1081                 :         217 : ExecEndMemoize(MemoizeState *node)
    1082                 :             : {
    1083                 :             : #ifdef USE_ASSERT_CHECKING
    1084                 :             :         /* Validate the memory accounting code is correct in assert builds. */
    1085         [ +  + ]:         217 :         if (node->hashtable != NULL)
    1086                 :             :         {
    1087                 :         175 :                 int                     count;
    1088                 :         175 :                 uint64          mem = 0;
    1089                 :         175 :                 memoize_iterator i;
    1090                 :         175 :                 MemoizeEntry *entry;
    1091                 :             : 
    1092                 :         175 :                 memoize_start_iterate(node->hashtable, &i);
    1093                 :             : 
    1094                 :         175 :                 count = 0;
    1095         [ +  + ]:       14577 :                 while ((entry = memoize_iterate(node->hashtable, &i)) != NULL)
    1096                 :             :                 {
    1097                 :       14402 :                         MemoizeTuple *tuple = entry->tuplehead;
    1098                 :             : 
    1099                 :       14402 :                         mem += EMPTY_ENTRY_MEMORY_BYTES(entry);
    1100         [ +  + ]:       27826 :                         while (tuple != NULL)
    1101                 :             :                         {
    1102                 :       13424 :                                 mem += CACHE_TUPLE_BYTES(tuple);
    1103                 :       13424 :                                 tuple = tuple->next;
    1104                 :             :                         }
    1105                 :       14402 :                         count++;
    1106                 :       14402 :                 }
    1107                 :             : 
    1108         [ +  - ]:         175 :                 Assert(count == node->hashtable->members);
    1109         [ +  - ]:         175 :                 Assert(mem == node->mem_used);
    1110                 :         175 :         }
    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   [ -  +  #  # ]:         217 :         if (node->shared_info != NULL && IsParallelWorker())
    1119                 :             :         {
    1120                 :           0 :                 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         [ #  # ]:           0 :                 Assert(ParallelWorkerNumber <= node->shared_info->num_workers);
    1127                 :           0 :                 si = &node->shared_info->sinstrument[ParallelWorkerNumber];
    1128                 :           0 :                 memcpy(si, &node->stats, sizeof(MemoizeInstrumentation));
    1129                 :           0 :         }
    1130                 :             : 
    1131                 :             :         /* Remove the cache context */
    1132                 :         217 :         MemoryContextDelete(node->tableContext);
    1133                 :             : 
    1134                 :             :         /*
    1135                 :             :          * shut down the subplan
    1136                 :             :          */
    1137                 :         217 :         ExecEndNode(outerPlanState(node));
    1138                 :         217 : }
    1139                 :             : 
    1140                 :             : void
    1141                 :      120290 : ExecReScanMemoize(MemoizeState *node)
    1142                 :             : {
    1143                 :      120290 :         PlanState  *outerPlan = outerPlanState(node);
    1144                 :             : 
    1145                 :             :         /* Mark that we must lookup the cache for a new set of parameters */
    1146                 :      120290 :         node->mstatus = MEMO_CACHE_LOOKUP;
    1147                 :             : 
    1148                 :             :         /* nullify pointers used for the last scan */
    1149                 :      120290 :         node->entry = NULL;
    1150                 :      120290 :         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         [ +  - ]:      120290 :         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         [ +  + ]:      120290 :         if (bms_nonempty_difference(outerPlan->chgParam, node->keyparamids))
    1164                 :           3 :                 cache_purge_all(node);
    1165                 :      120290 : }
    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                 :       18403 : ExecEstimateCacheEntryOverheadBytes(double ntuples)
    1174                 :             : {
    1175                 :       18403 :         return sizeof(MemoizeEntry) + sizeof(MemoizeKey) + sizeof(MemoizeTuple) *
    1176                 :       18403 :                 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                 :           1 : ExecMemoizeEstimate(MemoizeState *node, ParallelContext *pcxt)
    1192                 :             : {
    1193                 :           1 :         Size            size;
    1194                 :             : 
    1195                 :             :         /* don't need this if not instrumenting or no workers */
    1196   [ -  +  #  # ]:           1 :         if (!node->ss.ps.instrument || pcxt->nworkers == 0)
    1197                 :           1 :                 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         [ -  + ]:           1 : }
    1204                 :             : 
    1205                 :             : /* ----------------------------------------------------------------
    1206                 :             :  *              ExecMemoizeInitializeDSM
    1207                 :             :  *
    1208                 :             :  *              Initialize DSM space for memoize statistics.
    1209                 :             :  * ----------------------------------------------------------------
    1210                 :             :  */
    1211                 :             : void
    1212                 :           1 : ExecMemoizeInitializeDSM(MemoizeState *node, ParallelContext *pcxt)
    1213                 :             : {
    1214                 :           1 :         Size            size;
    1215                 :             : 
    1216                 :             :         /* don't need this if not instrumenting or no workers */
    1217   [ -  +  #  # ]:           1 :         if (!node->ss.ps.instrument || pcxt->nworkers == 0)
    1218                 :           1 :                 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         [ -  + ]:           1 : }
    1229                 :             : 
    1230                 :             : /* ----------------------------------------------------------------
    1231                 :             :  *              ExecMemoizeInitializeWorker
    1232                 :             :  *
    1233                 :             :  *              Attach worker to DSM space for memoize statistics.
    1234                 :             :  * ----------------------------------------------------------------
    1235                 :             :  */
    1236                 :             : void
    1237                 :           2 : ExecMemoizeInitializeWorker(MemoizeState *node, ParallelWorkerContext *pwcxt)
    1238                 :             : {
    1239                 :           2 :         node->shared_info =
    1240                 :           2 :                 shm_toc_lookup(pwcxt->toc, node->ss.ps.plan->plan_node_id, true);
    1241                 :           2 : }
    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                 :           0 :         Size            size;
    1253                 :           0 :         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         [ #  # ]:           0 : }
        

Generated by: LCOV version 2.3.2-1