LCOV - code coverage report
Current view: top level - src/backend/executor - nodeFunctionscan.c (source / functions) Hit Total Coverage
Test: PostgreSQL 17devel Lines: 153 156 98.1 %
Date: 2024-04-16 07:11:41 Functions: 5 6 83.3 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * nodeFunctionscan.c
       4             :  *    Support routines for scanning RangeFunctions (functions in rangetable).
       5             :  *
       6             :  * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
       7             :  * Portions Copyright (c) 1994, Regents of the University of California
       8             :  *
       9             :  *
      10             :  * IDENTIFICATION
      11             :  *    src/backend/executor/nodeFunctionscan.c
      12             :  *
      13             :  *-------------------------------------------------------------------------
      14             :  */
      15             : /*
      16             :  * INTERFACE ROUTINES
      17             :  *      ExecFunctionScan        scans a function.
      18             :  *      ExecFunctionNext        retrieve next tuple in sequential order.
      19             :  *      ExecInitFunctionScan    creates and initializes a functionscan node.
      20             :  *      ExecEndFunctionScan     releases any storage allocated.
      21             :  *      ExecReScanFunctionScan  rescans the function
      22             :  */
      23             : #include "postgres.h"
      24             : 
      25             : #include "catalog/pg_type.h"
      26             : #include "executor/nodeFunctionscan.h"
      27             : #include "funcapi.h"
      28             : #include "nodes/nodeFuncs.h"
      29             : #include "utils/memutils.h"
      30             : 
      31             : 
      32             : /*
      33             :  * Runtime data for each function being scanned.
      34             :  */
      35             : typedef struct FunctionScanPerFuncState
      36             : {
      37             :     SetExprState *setexpr;      /* state of the expression being evaluated */
      38             :     TupleDesc   tupdesc;        /* desc of the function result type */
      39             :     int         colcount;       /* expected number of result columns */
      40             :     Tuplestorestate *tstore;    /* holds the function result set */
      41             :     int64       rowcount;       /* # of rows in result set, -1 if not known */
      42             :     TupleTableSlot *func_slot;  /* function result slot (or NULL) */
      43             : } FunctionScanPerFuncState;
      44             : 
      45             : static TupleTableSlot *FunctionNext(FunctionScanState *node);
      46             : 
      47             : 
      48             : /* ----------------------------------------------------------------
      49             :  *                      Scan Support
      50             :  * ----------------------------------------------------------------
      51             :  */
      52             : /* ----------------------------------------------------------------
      53             :  *      FunctionNext
      54             :  *
      55             :  *      This is a workhorse for ExecFunctionScan
      56             :  * ----------------------------------------------------------------
      57             :  */
      58             : static TupleTableSlot *
      59    15521972 : FunctionNext(FunctionScanState *node)
      60             : {
      61             :     EState     *estate;
      62             :     ScanDirection direction;
      63             :     TupleTableSlot *scanslot;
      64             :     bool        alldone;
      65             :     int64       oldpos;
      66             :     int         funcno;
      67             :     int         att;
      68             : 
      69             :     /*
      70             :      * get information from the estate and scan state
      71             :      */
      72    15521972 :     estate = node->ss.ps.state;
      73    15521972 :     direction = estate->es_direction;
      74    15521972 :     scanslot = node->ss.ss_ScanTupleSlot;
      75             : 
      76    15521972 :     if (node->simple)
      77             :     {
      78             :         /*
      79             :          * Fast path for the trivial case: the function return type and scan
      80             :          * result type are the same, so we fetch the function result straight
      81             :          * into the scan result slot. No need to update ordinality or
      82             :          * rowcounts either.
      83             :          */
      84    15502838 :         Tuplestorestate *tstore = node->funcstates[0].tstore;
      85             : 
      86             :         /*
      87             :          * If first time through, read all tuples from function and put them
      88             :          * in a tuplestore. Subsequent calls just fetch tuples from
      89             :          * tuplestore.
      90             :          */
      91    15502838 :         if (tstore == NULL)
      92             :         {
      93      131560 :             node->funcstates[0].tstore = tstore =
      94      136716 :                 ExecMakeTableFunctionResult(node->funcstates[0].setexpr,
      95             :                                             node->ss.ps.ps_ExprContext,
      96             :                                             node->argcontext,
      97      136716 :                                             node->funcstates[0].tupdesc,
      98      136716 :                                             node->eflags & EXEC_FLAG_BACKWARD);
      99             : 
     100             :             /*
     101             :              * paranoia - cope if the function, which may have constructed the
     102             :              * tuplestore itself, didn't leave it pointing at the start. This
     103             :              * call is fast, so the overhead shouldn't be an issue.
     104             :              */
     105      131560 :             tuplestore_rescan(tstore);
     106             :         }
     107             : 
     108             :         /*
     109             :          * Get the next tuple from tuplestore.
     110             :          */
     111    15497682 :         (void) tuplestore_gettupleslot(tstore,
     112             :                                        ScanDirectionIsForward(direction),
     113             :                                        false,
     114             :                                        scanslot);
     115    15497682 :         return scanslot;
     116             :     }
     117             : 
     118             :     /*
     119             :      * Increment or decrement ordinal counter before checking for end-of-data,
     120             :      * so that we can move off either end of the result by 1 (and no more than
     121             :      * 1) without losing correct count.  See PortalRunSelect for why we can
     122             :      * assume that we won't be called repeatedly in the end-of-data state.
     123             :      */
     124       19134 :     oldpos = node->ordinal;
     125       19134 :     if (ScanDirectionIsForward(direction))
     126       19074 :         node->ordinal++;
     127             :     else
     128          60 :         node->ordinal--;
     129             : 
     130             :     /*
     131             :      * Main loop over functions.
     132             :      *
     133             :      * We fetch the function results into func_slots (which match the function
     134             :      * return types), and then copy the values to scanslot (which matches the
     135             :      * scan result type), setting the ordinal column (if any) as well.
     136             :      */
     137       19134 :     ExecClearTuple(scanslot);
     138       19134 :     att = 0;
     139       19134 :     alldone = true;
     140       49286 :     for (funcno = 0; funcno < node->nfuncs; funcno++)
     141             :     {
     142       30152 :         FunctionScanPerFuncState *fs = &node->funcstates[funcno];
     143             :         int         i;
     144             : 
     145             :         /*
     146             :          * If first time through, read all tuples from function and put them
     147             :          * in a tuplestore. Subsequent calls just fetch tuples from
     148             :          * tuplestore.
     149             :          */
     150       30152 :         if (fs->tstore == NULL)
     151             :         {
     152        1764 :             fs->tstore =
     153        1764 :                 ExecMakeTableFunctionResult(fs->setexpr,
     154             :                                             node->ss.ps.ps_ExprContext,
     155             :                                             node->argcontext,
     156             :                                             fs->tupdesc,
     157        1764 :                                             node->eflags & EXEC_FLAG_BACKWARD);
     158             : 
     159             :             /*
     160             :              * paranoia - cope if the function, which may have constructed the
     161             :              * tuplestore itself, didn't leave it pointing at the start. This
     162             :              * call is fast, so the overhead shouldn't be an issue.
     163             :              */
     164        1764 :             tuplestore_rescan(fs->tstore);
     165             :         }
     166             : 
     167             :         /*
     168             :          * Get the next tuple from tuplestore.
     169             :          *
     170             :          * If we have a rowcount for the function, and we know the previous
     171             :          * read position was out of bounds, don't try the read. This allows
     172             :          * backward scan to work when there are mixed row counts present.
     173             :          */
     174       30152 :         if (fs->rowcount != -1 && fs->rowcount < oldpos)
     175          72 :             ExecClearTuple(fs->func_slot);
     176             :         else
     177       30080 :             (void) tuplestore_gettupleslot(fs->tstore,
     178             :                                            ScanDirectionIsForward(direction),
     179             :                                            false,
     180             :                                            fs->func_slot);
     181             : 
     182       30152 :         if (TupIsNull(fs->func_slot))
     183             :         {
     184             :             /*
     185             :              * If we ran out of data for this function in the forward
     186             :              * direction then we now know how many rows it returned. We need
     187             :              * to know this in order to handle backwards scans. The row count
     188             :              * we store is actually 1+ the actual number, because we have to
     189             :              * position the tuplestore 1 off its end sometimes.
     190             :              */
     191        2112 :             if (ScanDirectionIsForward(direction) && fs->rowcount == -1)
     192        1740 :                 fs->rowcount = node->ordinal;
     193             : 
     194             :             /*
     195             :              * populate the result cols with nulls
     196             :              */
     197        5212 :             for (i = 0; i < fs->colcount; i++)
     198             :             {
     199        3100 :                 scanslot->tts_values[att] = (Datum) 0;
     200        3100 :                 scanslot->tts_isnull[att] = true;
     201        3100 :                 att++;
     202             :             }
     203             :         }
     204             :         else
     205             :         {
     206             :             /*
     207             :              * we have a result, so just copy it to the result cols.
     208             :              */
     209       28040 :             slot_getallattrs(fs->func_slot);
     210             : 
     211       73730 :             for (i = 0; i < fs->colcount; i++)
     212             :             {
     213       45690 :                 scanslot->tts_values[att] = fs->func_slot->tts_values[i];
     214       45690 :                 scanslot->tts_isnull[att] = fs->func_slot->tts_isnull[i];
     215       45690 :                 att++;
     216             :             }
     217             : 
     218             :             /*
     219             :              * We're not done until every function result is exhausted; we pad
     220             :              * the shorter results with nulls until then.
     221             :              */
     222       28040 :             alldone = false;
     223             :         }
     224             :     }
     225             : 
     226             :     /*
     227             :      * ordinal col is always last, per spec.
     228             :      */
     229       19134 :     if (node->ordinality)
     230             :     {
     231       13862 :         scanslot->tts_values[att] = Int64GetDatumFast(node->ordinal);
     232       13862 :         scanslot->tts_isnull[att] = false;
     233             :     }
     234             : 
     235             :     /*
     236             :      * If alldone, we just return the previously-cleared scanslot.  Otherwise,
     237             :      * finish creating the virtual tuple.
     238             :      */
     239       19134 :     if (!alldone)
     240       17658 :         ExecStoreVirtualTuple(scanslot);
     241             : 
     242       19134 :     return scanslot;
     243             : }
     244             : 
     245             : /*
     246             :  * FunctionRecheck -- access method routine to recheck a tuple in EvalPlanQual
     247             :  */
     248             : static bool
     249           0 : FunctionRecheck(FunctionScanState *node, TupleTableSlot *slot)
     250             : {
     251             :     /* nothing to check */
     252           0 :     return true;
     253             : }
     254             : 
     255             : /* ----------------------------------------------------------------
     256             :  *      ExecFunctionScan(node)
     257             :  *
     258             :  *      Scans the function sequentially and returns the next qualifying
     259             :  *      tuple.
     260             :  *      We call the ExecScan() routine and pass it the appropriate
     261             :  *      access method functions.
     262             :  * ----------------------------------------------------------------
     263             :  */
     264             : static TupleTableSlot *
     265    13772242 : ExecFunctionScan(PlanState *pstate)
     266             : {
     267    13772242 :     FunctionScanState *node = castNode(FunctionScanState, pstate);
     268             : 
     269    13772242 :     return ExecScan(&node->ss,
     270             :                     (ExecScanAccessMtd) FunctionNext,
     271             :                     (ExecScanRecheckMtd) FunctionRecheck);
     272             : }
     273             : 
     274             : /* ----------------------------------------------------------------
     275             :  *      ExecInitFunctionScan
     276             :  * ----------------------------------------------------------------
     277             :  */
     278             : FunctionScanState *
     279       61232 : ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
     280             : {
     281             :     FunctionScanState *scanstate;
     282       61232 :     int         nfuncs = list_length(node->functions);
     283             :     TupleDesc   scan_tupdesc;
     284             :     int         i,
     285             :                 natts;
     286             :     ListCell   *lc;
     287             : 
     288             :     /* check for unsupported flags */
     289             :     Assert(!(eflags & EXEC_FLAG_MARK));
     290             : 
     291             :     /*
     292             :      * FunctionScan should not have any children.
     293             :      */
     294             :     Assert(outerPlan(node) == NULL);
     295             :     Assert(innerPlan(node) == NULL);
     296             : 
     297             :     /*
     298             :      * create new ScanState for node
     299             :      */
     300       61232 :     scanstate = makeNode(FunctionScanState);
     301       61232 :     scanstate->ss.ps.plan = (Plan *) node;
     302       61232 :     scanstate->ss.ps.state = estate;
     303       61232 :     scanstate->ss.ps.ExecProcNode = ExecFunctionScan;
     304       61232 :     scanstate->eflags = eflags;
     305             : 
     306             :     /*
     307             :      * are we adding an ordinality column?
     308             :      */
     309       61232 :     scanstate->ordinality = node->funcordinality;
     310             : 
     311       61232 :     scanstate->nfuncs = nfuncs;
     312       61232 :     if (nfuncs == 1 && !node->funcordinality)
     313       60324 :         scanstate->simple = true;
     314             :     else
     315         908 :         scanstate->simple = false;
     316             : 
     317             :     /*
     318             :      * Ordinal 0 represents the "before the first row" position.
     319             :      *
     320             :      * We need to track ordinal position even when not adding an ordinality
     321             :      * column to the result, in order to handle backwards scanning properly
     322             :      * with multiple functions with different result sizes. (We can't position
     323             :      * any individual function's tuplestore any more than 1 place beyond its
     324             :      * end, so when scanning backwards, we need to know when to start
     325             :      * including the function in the scan again.)
     326             :      */
     327       61232 :     scanstate->ordinal = 0;
     328             : 
     329             :     /*
     330             :      * Miscellaneous initialization
     331             :      *
     332             :      * create expression context for node
     333             :      */
     334       61232 :     ExecAssignExprContext(estate, &scanstate->ss.ps);
     335             : 
     336       61232 :     scanstate->funcstates = palloc(nfuncs * sizeof(FunctionScanPerFuncState));
     337             : 
     338       61232 :     natts = 0;
     339       61232 :     i = 0;
     340      122768 :     foreach(lc, node->functions)
     341             :     {
     342       61544 :         RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
     343       61544 :         Node       *funcexpr = rtfunc->funcexpr;
     344       61544 :         int         colcount = rtfunc->funccolcount;
     345       61544 :         FunctionScanPerFuncState *fs = &scanstate->funcstates[i];
     346             :         TupleDesc   tupdesc;
     347             : 
     348       61536 :         fs->setexpr =
     349       61544 :             ExecInitTableFunctionResult((Expr *) funcexpr,
     350             :                                         scanstate->ss.ps.ps_ExprContext,
     351             :                                         &scanstate->ss.ps);
     352             : 
     353             :         /*
     354             :          * Don't allocate the tuplestores; the actual calls to the functions
     355             :          * do that.  NULL means that we have not called the function yet (or
     356             :          * need to call it again after a rescan).
     357             :          */
     358       61536 :         fs->tstore = NULL;
     359       61536 :         fs->rowcount = -1;
     360             : 
     361             :         /*
     362             :          * Now build a tupdesc showing the result type we expect from the
     363             :          * function.  If we have a coldeflist then that takes priority (note
     364             :          * the parser enforces that there is one if the function's nominal
     365             :          * output type is RECORD).  Otherwise use get_expr_result_type.
     366             :          *
     367             :          * Note that if the function returns a named composite type, that may
     368             :          * now contain more or different columns than it did when the plan was
     369             :          * made.  For both that and the RECORD case, we need to check tuple
     370             :          * compatibility.  ExecMakeTableFunctionResult handles some of this,
     371             :          * and CheckVarSlotCompatibility provides a backstop.
     372             :          */
     373       61536 :         if (rtfunc->funccolnames != NIL)
     374             :         {
     375        1512 :             tupdesc = BuildDescFromLists(rtfunc->funccolnames,
     376         756 :                                          rtfunc->funccoltypes,
     377         756 :                                          rtfunc->funccoltypmods,
     378         756 :                                          rtfunc->funccolcollations);
     379             : 
     380             :             /*
     381             :              * For RECORD results, make sure a typmod has been assigned.  (The
     382             :              * function should do this for itself, but let's cover things in
     383             :              * case it doesn't.)
     384             :              */
     385         756 :             BlessTupleDesc(tupdesc);
     386             :         }
     387             :         else
     388             :         {
     389             :             TypeFuncClass functypclass;
     390             :             Oid         funcrettype;
     391             : 
     392       60780 :             functypclass = get_expr_result_type(funcexpr,
     393             :                                                 &funcrettype,
     394             :                                                 &tupdesc);
     395             : 
     396       60780 :             if (functypclass == TYPEFUNC_COMPOSITE ||
     397             :                 functypclass == TYPEFUNC_COMPOSITE_DOMAIN)
     398             :             {
     399             :                 /* Composite data type, e.g. a table's row type */
     400             :                 Assert(tupdesc);
     401             :                 /* Must copy it out of typcache for safety */
     402       36300 :                 tupdesc = CreateTupleDescCopy(tupdesc);
     403             :             }
     404       24480 :             else if (functypclass == TYPEFUNC_SCALAR)
     405             :             {
     406             :                 /* Base data type, i.e. scalar */
     407       24480 :                 tupdesc = CreateTemplateTupleDesc(1);
     408       24480 :                 TupleDescInitEntry(tupdesc,
     409             :                                    (AttrNumber) 1,
     410             :                                    NULL,    /* don't care about the name here */
     411             :                                    funcrettype,
     412             :                                    -1,
     413             :                                    0);
     414       24480 :                 TupleDescInitEntryCollation(tupdesc,
     415             :                                             (AttrNumber) 1,
     416             :                                             exprCollation(funcexpr));
     417             :             }
     418             :             else
     419             :             {
     420             :                 /* crummy error message, but parser should have caught this */
     421           0 :                 elog(ERROR, "function in FROM has unsupported return type");
     422             :             }
     423             :         }
     424             : 
     425       61536 :         fs->tupdesc = tupdesc;
     426       61536 :         fs->colcount = colcount;
     427             : 
     428             :         /*
     429             :          * We only need separate slots for the function results if we are
     430             :          * doing ordinality or multiple functions; otherwise, we'll fetch
     431             :          * function results directly into the scan slot.
     432             :          */
     433       61536 :         if (!scanstate->simple)
     434             :         {
     435        1220 :             fs->func_slot = ExecInitExtraTupleSlot(estate, fs->tupdesc,
     436             :                                                    &TTSOpsMinimalTuple);
     437             :         }
     438             :         else
     439       60316 :             fs->func_slot = NULL;
     440             : 
     441       61536 :         natts += colcount;
     442       61536 :         i++;
     443             :     }
     444             : 
     445             :     /*
     446             :      * Create the combined TupleDesc
     447             :      *
     448             :      * If there is just one function without ordinality, the scan result
     449             :      * tupdesc is the same as the function result tupdesc --- except that we
     450             :      * may stuff new names into it below, so drop any rowtype label.
     451             :      */
     452       61224 :     if (scanstate->simple)
     453             :     {
     454       60316 :         scan_tupdesc = CreateTupleDescCopy(scanstate->funcstates[0].tupdesc);
     455       60316 :         scan_tupdesc->tdtypeid = RECORDOID;
     456       60316 :         scan_tupdesc->tdtypmod = -1;
     457             :     }
     458             :     else
     459             :     {
     460         908 :         AttrNumber  attno = 0;
     461             : 
     462         908 :         if (node->funcordinality)
     463         836 :             natts++;
     464             : 
     465         908 :         scan_tupdesc = CreateTemplateTupleDesc(natts);
     466             : 
     467        2128 :         for (i = 0; i < nfuncs; i++)
     468             :         {
     469        1220 :             TupleDesc   tupdesc = scanstate->funcstates[i].tupdesc;
     470        1220 :             int         colcount = scanstate->funcstates[i].colcount;
     471             :             int         j;
     472             : 
     473        3080 :             for (j = 1; j <= colcount; j++)
     474        1860 :                 TupleDescCopyEntry(scan_tupdesc, ++attno, tupdesc, j);
     475             :         }
     476             : 
     477             :         /* If doing ordinality, add a column of type "bigint" at the end */
     478         908 :         if (node->funcordinality)
     479             :         {
     480         836 :             TupleDescInitEntry(scan_tupdesc,
     481             :                                ++attno,
     482             :                                NULL,    /* don't care about the name here */
     483             :                                INT8OID,
     484             :                                -1,
     485             :                                0);
     486             :         }
     487             : 
     488             :         Assert(attno == natts);
     489             :     }
     490             : 
     491             :     /*
     492             :      * Initialize scan slot and type.
     493             :      */
     494       61224 :     ExecInitScanTupleSlot(estate, &scanstate->ss, scan_tupdesc,
     495             :                           &TTSOpsMinimalTuple);
     496             : 
     497             :     /*
     498             :      * Initialize result slot, type and projection.
     499             :      */
     500       61224 :     ExecInitResultTypeTL(&scanstate->ss.ps);
     501       61224 :     ExecAssignScanProjectionInfo(&scanstate->ss);
     502             : 
     503             :     /*
     504             :      * initialize child expressions
     505             :      */
     506       61224 :     scanstate->ss.ps.qual =
     507       61224 :         ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
     508             : 
     509             :     /*
     510             :      * Create a memory context that ExecMakeTableFunctionResult can use to
     511             :      * evaluate function arguments in.  We can't use the per-tuple context for
     512             :      * this because it gets reset too often; but we don't want to leak
     513             :      * evaluation results into the query-lifespan context either.  We just
     514             :      * need one context, because we evaluate each function separately.
     515             :      */
     516       61224 :     scanstate->argcontext = AllocSetContextCreate(CurrentMemoryContext,
     517             :                                                   "Table function arguments",
     518             :                                                   ALLOCSET_DEFAULT_SIZES);
     519             : 
     520       61224 :     return scanstate;
     521             : }
     522             : 
     523             : /* ----------------------------------------------------------------
     524             :  *      ExecEndFunctionScan
     525             :  *
     526             :  *      frees any storage allocated through C routines.
     527             :  * ----------------------------------------------------------------
     528             :  */
     529             : void
     530       55900 : ExecEndFunctionScan(FunctionScanState *node)
     531             : {
     532             :     int         i;
     533             : 
     534             :     /*
     535             :      * Release slots and tuplestore resources
     536             :      */
     537      112100 :     for (i = 0; i < node->nfuncs; i++)
     538             :     {
     539       56200 :         FunctionScanPerFuncState *fs = &node->funcstates[i];
     540             : 
     541       56200 :         if (fs->tstore != NULL)
     542             :         {
     543       51306 :             tuplestore_end(node->funcstates[i].tstore);
     544       51306 :             fs->tstore = NULL;
     545             :         }
     546             :     }
     547       55900 : }
     548             : 
     549             : /* ----------------------------------------------------------------
     550             :  *      ExecReScanFunctionScan
     551             :  *
     552             :  *      Rescans the relation.
     553             :  * ----------------------------------------------------------------
     554             :  */
     555             : void
     556      101558 : ExecReScanFunctionScan(FunctionScanState *node)
     557             : {
     558      101558 :     FunctionScan *scan = (FunctionScan *) node->ss.ps.plan;
     559             :     int         i;
     560      101558 :     Bitmapset  *chgparam = node->ss.ps.chgParam;
     561             : 
     562      101558 :     if (node->ss.ps.ps_ResultTupleSlot)
     563       46272 :         ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
     564      203224 :     for (i = 0; i < node->nfuncs; i++)
     565             :     {
     566      101666 :         FunctionScanPerFuncState *fs = &node->funcstates[i];
     567             : 
     568      101666 :         if (fs->func_slot)
     569        1086 :             ExecClearTuple(fs->func_slot);
     570             :     }
     571             : 
     572      101558 :     ExecScanReScan(&node->ss);
     573             : 
     574             :     /*
     575             :      * Here we have a choice whether to drop the tuplestores (and recompute
     576             :      * the function outputs) or just rescan them.  We must recompute if an
     577             :      * expression contains changed parameters, else we rescan.
     578             :      *
     579             :      * XXX maybe we should recompute if the function is volatile?  But in
     580             :      * general the executor doesn't conditionalize its actions on that.
     581             :      */
     582      101558 :     if (chgparam)
     583             :     {
     584             :         ListCell   *lc;
     585             : 
     586       95574 :         i = 0;
     587      191232 :         foreach(lc, scan->functions)
     588             :         {
     589       95658 :             RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
     590             : 
     591       95658 :             if (bms_overlap(chgparam, rtfunc->funcparams))
     592             :             {
     593       92200 :                 if (node->funcstates[i].tstore != NULL)
     594             :                 {
     595       81838 :                     tuplestore_end(node->funcstates[i].tstore);
     596       81838 :                     node->funcstates[i].tstore = NULL;
     597             :                 }
     598       92200 :                 node->funcstates[i].rowcount = -1;
     599             :             }
     600       95658 :             i++;
     601             :         }
     602             :     }
     603             : 
     604             :     /* Reset ordinality counter */
     605      101558 :     node->ordinal = 0;
     606             : 
     607             :     /* Make sure we rewind any remaining tuplestores */
     608      203224 :     for (i = 0; i < node->nfuncs; i++)
     609             :     {
     610      101666 :         if (node->funcstates[i].tstore != NULL)
     611        8806 :             tuplestore_rescan(node->funcstates[i].tstore);
     612             :     }
     613      101558 : }

Generated by: LCOV version 1.14