LCOV - code coverage report
Current view: top level - src/backend/executor - nodeMaterial.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 67.8 % 121 82
Test Date: 2026-10-23 11:15:58 Functions: 60.0 % 10 6
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 68.1 % 72 49

             Branch data     Line data    Source code
       1                 :             : /*-------------------------------------------------------------------------
       2                 :             :  *
       3                 :             :  * nodeMaterial.c
       4                 :             :  *    Routines to handle materialization nodes.
       5                 :             :  *
       6                 :             :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       7                 :             :  * Portions Copyright (c) 1994, Regents of the University of California
       8                 :             :  *
       9                 :             :  *
      10                 :             :  * IDENTIFICATION
      11                 :             :  *    src/backend/executor/nodeMaterial.c
      12                 :             :  *
      13                 :             :  *-------------------------------------------------------------------------
      14                 :             :  */
      15                 :             : /*
      16                 :             :  * INTERFACE ROUTINES
      17                 :             :  *      ExecMaterial            - materialize the result of a subplan
      18                 :             :  *      ExecInitMaterial        - initialize node and subnodes
      19                 :             :  *      ExecEndMaterial         - shutdown node and subnodes
      20                 :             :  *
      21                 :             :  */
      22                 :             : #include "postgres.h"
      23                 :             : 
      24                 :             : #include "executor/executor.h"
      25                 :             : #include "executor/nodeMaterial.h"
      26                 :             : #include "miscadmin.h"
      27                 :             : #include "utils/tuplestore.h"
      28                 :             : 
      29                 :             : /* ----------------------------------------------------------------
      30                 :             :  *      ExecMaterial
      31                 :             :  *
      32                 :             :  *      As long as we are at the end of the data collected in the tuplestore,
      33                 :             :  *      we collect one new row from the subplan on each call, and stash it
      34                 :             :  *      aside in the tuplestore before returning it.  The tuplestore is
      35                 :             :  *      only read if we are asked to scan backwards, rescan, or mark/restore.
      36                 :             :  *
      37                 :             :  * ----------------------------------------------------------------
      38                 :             :  */
      39                 :             : static TupleTableSlot *         /* result tuple from subplan */
      40                 :     1934446 : ExecMaterial(PlanState *pstate)
      41                 :             : {
      42                 :     1934446 :     MaterialState *node = castNode(MaterialState, pstate);
      43                 :             :     EState     *estate;
      44                 :             :     ScanDirection dir;
      45                 :             :     bool        forward;
      46                 :             :     Tuplestorestate *tuplestorestate;
      47                 :             :     bool        eof_tuplestore;
      48                 :             :     TupleTableSlot *slot;
      49                 :             : 
      50         [ -  + ]:     1934446 :     CHECK_FOR_INTERRUPTS();
      51                 :             : 
      52                 :             :     /*
      53                 :             :      * get state info from node
      54                 :             :      */
      55                 :     1934446 :     estate = node->ss.ps.state;
      56                 :     1934446 :     dir = estate->es_direction;
      57                 :     1934446 :     forward = ScanDirectionIsForward(dir);
      58                 :     1934446 :     tuplestorestate = node->tuplestorestate;
      59                 :             : 
      60                 :             :     /*
      61                 :             :      * If first time through, and we need a tuplestore, initialize it.
      62                 :             :      */
      63   [ +  +  +  + ]:     1934446 :     if (tuplestorestate == NULL && node->eflags != 0)
      64                 :             :     {
      65                 :        2014 :         tuplestorestate = tuplestore_begin_heap(true, false, work_mem);
      66                 :        2014 :         tuplestore_set_eflags(tuplestorestate, node->eflags);
      67         [ +  + ]:        2014 :         if (node->eflags & EXEC_FLAG_MARK)
      68                 :             :         {
      69                 :             :             /*
      70                 :             :              * Allocate a second read pointer to serve as the mark. We know it
      71                 :             :              * must have index 1, so needn't store that.
      72                 :             :              */
      73                 :             :             int         ptrno PG_USED_FOR_ASSERTS_ONLY;
      74                 :             : 
      75                 :          84 :             ptrno = tuplestore_alloc_read_pointer(tuplestorestate,
      76                 :             :                                                   node->eflags);
      77                 :             :             Assert(ptrno == 1);
      78                 :             :         }
      79                 :        2014 :         node->tuplestorestate = tuplestorestate;
      80                 :             :     }
      81                 :             : 
      82                 :             :     /*
      83                 :             :      * If we are not at the end of the tuplestore, or are going backwards, try
      84                 :             :      * to fetch a tuple from tuplestore.
      85                 :             :      */
      86   [ +  +  +  + ]:     3868827 :     eof_tuplestore = (tuplestorestate == NULL) ||
      87                 :     1934381 :         tuplestore_ateof(tuplestorestate);
      88                 :             : 
      89   [ +  +  +  + ]:     1934446 :     if (!forward && eof_tuplestore)
      90                 :             :     {
      91         [ -  + ]:           8 :         if (!node->eof_underlying)
      92                 :             :         {
      93                 :             :             /*
      94                 :             :              * When reversing direction at tuplestore EOF, the first
      95                 :             :              * gettupleslot call will fetch the last-added tuple; but we want
      96                 :             :              * to return the one before that, if possible. So do an extra
      97                 :             :              * fetch.
      98                 :             :              */
      99         [ #  # ]:           0 :             if (!tuplestore_advance(tuplestorestate, forward))
     100                 :           0 :                 return NULL;    /* the tuplestore must be empty */
     101                 :             :         }
     102                 :           8 :         eof_tuplestore = false;
     103                 :             :     }
     104                 :             : 
     105                 :             :     /*
     106                 :             :      * If we can fetch another tuple from the tuplestore, return it.
     107                 :             :      */
     108                 :     1934446 :     slot = node->ss.ps.ps_ResultTupleSlot;
     109         [ +  + ]:     1934446 :     if (!eof_tuplestore)
     110                 :             :     {
     111         [ +  + ]:     1915834 :         if (tuplestore_gettupleslot(tuplestorestate, forward, false, slot))
     112                 :     1777770 :             return slot;
     113         [ +  + ]:      138064 :         if (forward)
     114                 :      138056 :             eof_tuplestore = true;
     115                 :             :     }
     116                 :             : 
     117                 :             :     /*
     118                 :             :      * If necessary, try to fetch another row from the subplan.
     119                 :             :      *
     120                 :             :      * Note: the eof_underlying state variable exists to short-circuit further
     121                 :             :      * subplan calls.  It's not optional, unfortunately, because some plan
     122                 :             :      * node types are not robust about being called again when they've already
     123                 :             :      * returned NULL.
     124                 :             :      */
     125   [ +  +  +  + ]:      156676 :     if (eof_tuplestore && !node->eof_underlying)
     126                 :             :     {
     127                 :             :         PlanState  *outerNode;
     128                 :             :         TupleTableSlot *outerslot;
     129                 :             : 
     130                 :             :         /*
     131                 :             :          * We can only get here with forward==true, so no need to worry about
     132                 :             :          * which direction the subplan will go.
     133                 :             :          */
     134                 :       24723 :         outerNode = outerPlanState(node);
     135                 :       24723 :         outerslot = ExecProcNode(outerNode);
     136   [ +  +  +  + ]:       24723 :         if (TupIsNull(outerslot))
     137                 :             :         {
     138                 :        1873 :             node->eof_underlying = true;
     139                 :        1873 :             return NULL;
     140                 :             :         }
     141                 :             : 
     142                 :             :         /*
     143                 :             :          * Append a copy of the returned tuple to tuplestore.  NOTE: because
     144                 :             :          * the tuplestore is certainly in EOF state, its read position will
     145                 :             :          * move forward over the added tuple.  This is what we want.
     146                 :             :          */
     147         [ +  + ]:       22850 :         if (tuplestorestate)
     148                 :       22788 :             tuplestore_puttupleslot(tuplestorestate, outerslot);
     149                 :             : 
     150                 :       22850 :         ExecCopySlot(slot, outerslot);
     151                 :       22850 :         return slot;
     152                 :             :     }
     153                 :             : 
     154                 :             :     /*
     155                 :             :      * Nothing left ...
     156                 :             :      */
     157                 :      131953 :     return ExecClearTuple(slot);
     158                 :             : }
     159                 :             : 
     160                 :             : /* ----------------------------------------------------------------
     161                 :             :  *      ExecInitMaterial
     162                 :             :  * ----------------------------------------------------------------
     163                 :             :  */
     164                 :             : MaterialState *
     165                 :        3070 : ExecInitMaterial(Material *node, EState *estate, int eflags)
     166                 :             : {
     167                 :             :     MaterialState *matstate;
     168                 :             :     Plan       *outerPlan;
     169                 :             : 
     170                 :             :     /*
     171                 :             :      * create state structure
     172                 :             :      */
     173                 :        3070 :     matstate = makeNode(MaterialState);
     174                 :        3070 :     matstate->ss.ps.plan = (Plan *) node;
     175                 :        3070 :     matstate->ss.ps.state = estate;
     176                 :        3070 :     matstate->ss.ps.ExecProcNode = ExecMaterial;
     177                 :             : 
     178                 :             :     /*
     179                 :             :      * We must have a tuplestore buffering the subplan output to do backward
     180                 :             :      * scan or mark/restore.  We also prefer to materialize the subplan output
     181                 :             :      * if we might be called on to rewind and replay it many times. However,
     182                 :             :      * if none of these cases apply, we can skip storing the data.
     183                 :             :      */
     184                 :        3070 :     matstate->eflags = (eflags & (EXEC_FLAG_REWIND |
     185                 :             :                                   EXEC_FLAG_BACKWARD |
     186                 :             :                                   EXEC_FLAG_MARK));
     187                 :             : 
     188                 :             :     /*
     189                 :             :      * Tuplestore's interpretation of the flag bits is subtly different from
     190                 :             :      * the general executor meaning: it doesn't think BACKWARD necessarily
     191                 :             :      * means "backwards all the way to start".  If told to support BACKWARD we
     192                 :             :      * must include REWIND in the tuplestore eflags, else tuplestore_trim
     193                 :             :      * might throw away too much.
     194                 :             :      */
     195         [ +  + ]:        3070 :     if (eflags & EXEC_FLAG_BACKWARD)
     196                 :          13 :         matstate->eflags |= EXEC_FLAG_REWIND;
     197                 :             : 
     198                 :        3070 :     matstate->eof_underlying = false;
     199                 :        3070 :     matstate->tuplestorestate = NULL;
     200                 :        3070 :     matstate->shared_info = NULL;
     201                 :             : 
     202                 :             :     /*
     203                 :             :      * Miscellaneous initialization
     204                 :             :      *
     205                 :             :      * Materialization nodes don't need ExprContexts because they never call
     206                 :             :      * ExecQual or ExecProject.
     207                 :             :      */
     208                 :             : 
     209                 :             :     /*
     210                 :             :      * initialize child nodes
     211                 :             :      *
     212                 :             :      * We shield the child node from the need to support REWIND, BACKWARD, or
     213                 :             :      * MARK/RESTORE.
     214                 :             :      */
     215                 :        3070 :     eflags &= ~(EXEC_FLAG_REWIND | EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK);
     216                 :             : 
     217                 :        3070 :     outerPlan = outerPlan(node);
     218                 :        3070 :     outerPlanState(matstate) = ExecInitNode(outerPlan, estate, eflags);
     219                 :             : 
     220                 :             :     /*
     221                 :             :      * Initialize result type and slot. No need to initialize projection info
     222                 :             :      * because this node doesn't do projections.
     223                 :             :      *
     224                 :             :      * material nodes only return tuples from their materialized relation.
     225                 :             :      */
     226                 :        3070 :     ExecInitResultTupleSlotTL(&matstate->ss.ps, &TTSOpsMinimalTuple);
     227                 :        3070 :     matstate->ss.ps.ps_ProjInfo = NULL;
     228                 :             : 
     229                 :             :     /*
     230                 :             :      * initialize tuple type.
     231                 :             :      */
     232                 :        3070 :     ExecCreateScanSlotFromOuterPlan(estate, &matstate->ss, &TTSOpsMinimalTuple);
     233                 :             : 
     234                 :        3070 :     return matstate;
     235                 :             : }
     236                 :             : 
     237                 :             : /* ----------------------------------------------------------------
     238                 :             :  *      ExecEndMaterial
     239                 :             :  * ----------------------------------------------------------------
     240                 :             :  */
     241                 :             : void
     242                 :        3026 : ExecEndMaterial(MaterialState *node)
     243                 :             : {
     244                 :             :     /*
     245                 :             :      * When ending a parallel worker, copy the tuplestore usage details into
     246                 :             :      * shared memory so that they can be picked up by the main process to
     247                 :             :      * report in EXPLAIN ANALYZE.  The Gather node above us may be rescanned,
     248                 :             :      * in which case a new set of workers will write to the same slots, so
     249                 :             :      * merge with any existing values rather than overwriting them.
     250                 :             :      */
     251   [ -  +  -  - ]:        3026 :     if (node->shared_info != NULL && IsParallelWorker() &&
     252         [ #  # ]:           0 :         node->tuplestorestate != NULL)
     253                 :             :     {
     254                 :             :         MaterialInstrumentation *si;
     255                 :             :         bool        usedDisk;
     256                 :             :         int64       maxSpaceUsed;
     257                 :             : 
     258                 :             :         Assert(ParallelWorkerNumber < node->shared_info->num_workers);
     259                 :             : 
     260                 :           0 :         tuplestore_get_stats(node->tuplestorestate, &usedDisk, &maxSpaceUsed);
     261                 :             : 
     262                 :           0 :         si = &node->shared_info->sinstrument[ParallelWorkerNumber];
     263                 :           0 :         si->maxSpaceUsed = Max(si->maxSpaceUsed, maxSpaceUsed);
     264                 :           0 :         si->usedDisk |= usedDisk;
     265                 :             :     }
     266                 :             : 
     267                 :             :     /*
     268                 :             :      * Release tuplestore resources
     269                 :             :      */
     270         [ +  + ]:        3026 :     if (node->tuplestorestate != NULL)
     271                 :        1970 :         tuplestore_end(node->tuplestorestate);
     272                 :        3026 :     node->tuplestorestate = NULL;
     273                 :             : 
     274                 :             :     /*
     275                 :             :      * shut down the subplan
     276                 :             :      */
     277                 :        3026 :     ExecEndNode(outerPlanState(node));
     278                 :        3026 : }
     279                 :             : 
     280                 :             : /* ----------------------------------------------------------------
     281                 :             :  *      ExecMaterialMarkPos
     282                 :             :  *
     283                 :             :  *      Calls tuplestore to save the current position in the stored file.
     284                 :             :  * ----------------------------------------------------------------
     285                 :             :  */
     286                 :             : void
     287                 :        4341 : ExecMaterialMarkPos(MaterialState *node)
     288                 :             : {
     289                 :             :     Assert(node->eflags & EXEC_FLAG_MARK);
     290                 :             : 
     291                 :             :     /*
     292                 :             :      * if we haven't materialized yet, just return.
     293                 :             :      */
     294         [ +  + ]:        4341 :     if (!node->tuplestorestate)
     295                 :           8 :         return;
     296                 :             : 
     297                 :             :     /*
     298                 :             :      * copy the active read pointer to the mark.
     299                 :             :      */
     300                 :        4333 :     tuplestore_copy_read_pointer(node->tuplestorestate, 0, 1);
     301                 :             : 
     302                 :             :     /*
     303                 :             :      * since we may have advanced the mark, try to truncate the tuplestore.
     304                 :             :      */
     305                 :        4333 :     tuplestore_trim(node->tuplestorestate);
     306                 :             : }
     307                 :             : 
     308                 :             : /* ----------------------------------------------------------------
     309                 :             :  *      ExecMaterialRestrPos
     310                 :             :  *
     311                 :             :  *      Calls tuplestore to restore the last saved file position.
     312                 :             :  * ----------------------------------------------------------------
     313                 :             :  */
     314                 :             : void
     315                 :       36132 : ExecMaterialRestrPos(MaterialState *node)
     316                 :             : {
     317                 :             :     Assert(node->eflags & EXEC_FLAG_MARK);
     318                 :             : 
     319                 :             :     /*
     320                 :             :      * if we haven't materialized yet, just return.
     321                 :             :      */
     322         [ -  + ]:       36132 :     if (!node->tuplestorestate)
     323                 :           0 :         return;
     324                 :             : 
     325                 :             :     /*
     326                 :             :      * copy the mark to the active read pointer.
     327                 :             :      */
     328                 :       36132 :     tuplestore_copy_read_pointer(node->tuplestorestate, 1, 0);
     329                 :             : }
     330                 :             : 
     331                 :             : /* ----------------------------------------------------------------
     332                 :             :  *      ExecReScanMaterial
     333                 :             :  *
     334                 :             :  *      Rescans the materialized relation.
     335                 :             :  * ----------------------------------------------------------------
     336                 :             :  */
     337                 :             : void
     338                 :      139026 : ExecReScanMaterial(MaterialState *node)
     339                 :             : {
     340                 :      139026 :     PlanState  *outerPlan = outerPlanState(node);
     341                 :             : 
     342                 :      139026 :     ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
     343                 :             : 
     344         [ +  + ]:      139026 :     if (node->eflags != 0)
     345                 :             :     {
     346                 :             :         /*
     347                 :             :          * If we haven't materialized yet, just return. If outerplan's
     348                 :             :          * chgParam is not NULL then it will be re-scanned by ExecProcNode,
     349                 :             :          * else no reason to re-scan it at all.
     350                 :             :          */
     351         [ +  + ]:      139024 :         if (!node->tuplestorestate)
     352                 :        1921 :             return;
     353                 :             : 
     354                 :             :         /*
     355                 :             :          * If subnode is to be rescanned then we clear out the previously
     356                 :             :          * stored results; we have to re-read the subplan and re-store.  Also,
     357                 :             :          * if we told tuplestore it needn't support rescan, we lose and must
     358                 :             :          * re-read.  (This last should not happen in common cases; else our
     359                 :             :          * caller lied by not passing EXEC_FLAG_REWIND to us.)
     360                 :             :          *
     361                 :             :          * Otherwise we can just rewind and rescan the stored output. The
     362                 :             :          * state of the subnode does not change.
     363                 :             :          */
     364         [ +  + ]:      137103 :         if (outerPlan->chgParam != NULL ||
     365         [ -  + ]:      137095 :             (node->eflags & EXEC_FLAG_REWIND) == 0)
     366                 :             :         {
     367                 :             :             /*
     368                 :             :              * It's important to use tuplestore_clear() here so that we keep
     369                 :             :              * track of the maximum storage used by all rescans.
     370                 :             :              */
     371                 :           8 :             tuplestore_clear(node->tuplestorestate);
     372         [ -  + ]:           8 :             if (outerPlan->chgParam == NULL)
     373                 :           0 :                 ExecReScan(outerPlan);
     374                 :           8 :             node->eof_underlying = false;
     375                 :             :         }
     376                 :             :         else
     377                 :      137095 :             tuplestore_rescan(node->tuplestorestate);
     378                 :             :     }
     379                 :             :     else
     380                 :             :     {
     381                 :             :         /* In this case we are just passing on the subquery's output */
     382                 :             : 
     383                 :             :         /*
     384                 :             :          * if chgParam of subnode is not null then plan will be re-scanned by
     385                 :             :          * first ExecProcNode.
     386                 :             :          */
     387         [ -  + ]:           2 :         if (outerPlan->chgParam == NULL)
     388                 :           0 :             ExecReScan(outerPlan);
     389                 :           2 :         node->eof_underlying = false;
     390                 :             :     }
     391                 :             : }
     392                 :             : 
     393                 :             : /* ----------------------------------------------------------------
     394                 :             :  *                      Parallel Query Support
     395                 :             :  * ----------------------------------------------------------------
     396                 :             :  */
     397                 :             : 
     398                 :             : /* ----------------------------------------------------------------
     399                 :             :  *      ExecMaterialEstimate
     400                 :             :  *
     401                 :             :  *      Estimate space required to propagate material statistics.
     402                 :             :  * ----------------------------------------------------------------
     403                 :             :  */
     404                 :             : void
     405                 :           0 : ExecMaterialEstimate(MaterialState *node, ParallelContext *pcxt)
     406                 :             : {
     407                 :             :     Size        size;
     408                 :             : 
     409                 :             :     /* don't need this if not instrumenting or no workers */
     410   [ #  #  #  # ]:           0 :     if (!node->ss.ps.instrument || pcxt->nworkers == 0)
     411                 :           0 :         return;
     412                 :             : 
     413                 :           0 :     size = mul_size(pcxt->nworkers, sizeof(MaterialInstrumentation));
     414                 :           0 :     size = add_size(size, offsetof(SharedMaterialInfo, sinstrument));
     415                 :           0 :     shm_toc_estimate_chunk(&pcxt->estimator, size);
     416                 :           0 :     shm_toc_estimate_keys(&pcxt->estimator, 1);
     417                 :             : }
     418                 :             : 
     419                 :             : /* ----------------------------------------------------------------
     420                 :             :  *      ExecMaterialInitializeDSM
     421                 :             :  *
     422                 :             :  *      Initialize DSM space for material statistics.
     423                 :             :  * ----------------------------------------------------------------
     424                 :             :  */
     425                 :             : void
     426                 :           0 : ExecMaterialInitializeDSM(MaterialState *node, ParallelContext *pcxt)
     427                 :             : {
     428                 :             :     Size        size;
     429                 :             : 
     430                 :             :     /* don't need this if not instrumenting or no workers */
     431   [ #  #  #  # ]:           0 :     if (!node->ss.ps.instrument || pcxt->nworkers == 0)
     432                 :           0 :         return;
     433                 :             : 
     434                 :           0 :     size = offsetof(SharedMaterialInfo, sinstrument)
     435                 :           0 :         + pcxt->nworkers * sizeof(MaterialInstrumentation);
     436                 :           0 :     node->shared_info = shm_toc_allocate(pcxt->toc, size);
     437                 :             :     /* ensure any unfilled slots will contain zeroes */
     438                 :           0 :     memset(node->shared_info, 0, size);
     439                 :           0 :     node->shared_info->num_workers = pcxt->nworkers;
     440                 :           0 :     shm_toc_insert(pcxt->toc, node->ss.ps.plan->plan_node_id,
     441                 :           0 :                    node->shared_info);
     442                 :             : }
     443                 :             : 
     444                 :             : /* ----------------------------------------------------------------
     445                 :             :  *      ExecMaterialInitializeWorker
     446                 :             :  *
     447                 :             :  *      Attach worker to DSM space for material statistics.
     448                 :             :  * ----------------------------------------------------------------
     449                 :             :  */
     450                 :             : void
     451                 :           0 : ExecMaterialInitializeWorker(MaterialState *node, ParallelWorkerContext *pwcxt)
     452                 :             : {
     453                 :           0 :     node->shared_info =
     454                 :           0 :         shm_toc_lookup(pwcxt->toc, node->ss.ps.plan->plan_node_id, true);
     455                 :           0 : }
     456                 :             : 
     457                 :             : /* ----------------------------------------------------------------
     458                 :             :  *      ExecMaterialRetrieveInstrumentation
     459                 :             :  *
     460                 :             :  *      Transfer material statistics from DSM to private memory.
     461                 :             :  * ----------------------------------------------------------------
     462                 :             :  */
     463                 :             : void
     464                 :           0 : ExecMaterialRetrieveInstrumentation(MaterialState *node)
     465                 :             : {
     466                 :             :     Size        size;
     467                 :             :     SharedMaterialInfo *si;
     468                 :             : 
     469         [ #  # ]:           0 :     if (node->shared_info == NULL)
     470                 :           0 :         return;
     471                 :             : 
     472                 :           0 :     size = offsetof(SharedMaterialInfo, sinstrument)
     473                 :           0 :         + node->shared_info->num_workers * sizeof(MaterialInstrumentation);
     474                 :           0 :     si = palloc(size);
     475                 :           0 :     memcpy(si, node->shared_info, size);
     476                 :           0 :     node->shared_info = si;
     477                 :             : }
        

Generated by: LCOV version 2.0-1