44
44
typedef struct
45
45
{
46
46
DestReceiver pub ; /* publicly-known function pointers */
47
- Tuplestorestate * tstore ; /* where to put result tuples */
47
+ Tuplestorestate * tstore ; /* where to put result tuples, or NULL */
48
48
JunkFilter * filter ; /* filter to convert tuple type */
49
49
} DR_sqlfunction ;
50
50
@@ -145,11 +145,13 @@ typedef struct SQLFunctionCache
145
145
bool lazyEvalOK ; /* true if lazyEval is safe */
146
146
bool shutdown_reg ; /* true if registered shutdown callback */
147
147
bool lazyEval ; /* true if using lazyEval for result query */
148
+ bool randomAccess ; /* true if tstore needs random access */
148
149
bool ownSubcontext ; /* is subcontext really a separate context? */
149
150
150
151
ParamListInfo paramLI ; /* Param list representing current args */
151
152
152
- Tuplestorestate * tstore ; /* where we accumulate result tuples */
153
+ Tuplestorestate * tstore ; /* where we accumulate result for a SRF */
154
+ MemoryContext tscontext ; /* memory context that tstore should be in */
153
155
154
156
JunkFilter * junkFilter ; /* will be NULL if function returns VOID */
155
157
int jf_generation ; /* tracks whether junkFilter is up-to-date */
@@ -1250,7 +1252,7 @@ static void
1250
1252
postquel_start (execution_state * es , SQLFunctionCachePtr fcache )
1251
1253
{
1252
1254
DestReceiver * dest ;
1253
- MemoryContext oldcontext ;
1255
+ MemoryContext oldcontext = CurrentMemoryContext ;
1254
1256
1255
1257
Assert (es -> qd == NULL );
1256
1258
@@ -1296,12 +1298,27 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
1296
1298
fcache -> ownSubcontext = false;
1297
1299
}
1298
1300
1301
+ /*
1302
+ * Build a tuplestore if needed, that is if it's a set-returning function
1303
+ * and we're producing the function result without using lazyEval mode.
1304
+ */
1305
+ if (es -> setsResult )
1306
+ {
1307
+ Assert (fcache -> tstore == NULL );
1308
+ if (fcache -> func -> returnsSet && !es -> lazyEval )
1309
+ {
1310
+ MemoryContextSwitchTo (fcache -> tscontext );
1311
+ fcache -> tstore = tuplestore_begin_heap (fcache -> randomAccess ,
1312
+ false, work_mem );
1313
+ }
1314
+ }
1315
+
1299
1316
/* Switch into the selected subcontext (might be a no-op) */
1300
- oldcontext = MemoryContextSwitchTo (fcache -> subcontext );
1317
+ MemoryContextSwitchTo (fcache -> subcontext );
1301
1318
1302
1319
/*
1303
- * If this query produces the function result, send its output to the
1304
- * tuplestore ; else discard any output.
1320
+ * If this query produces the function result, collect its output using
1321
+ * our custom DestReceiver ; else discard any output.
1305
1322
*/
1306
1323
if (es -> setsResult )
1307
1324
{
@@ -1311,8 +1328,11 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
1311
1328
/* pass down the needed info to the dest receiver routines */
1312
1329
myState = (DR_sqlfunction * ) dest ;
1313
1330
Assert (myState -> pub .mydest == DestSQLFunction );
1314
- myState -> tstore = fcache -> tstore ;
1331
+ myState -> tstore = fcache -> tstore ; /* might be NULL */
1315
1332
myState -> filter = fcache -> junkFilter ;
1333
+
1334
+ /* Make very sure the junkfilter's result slot is empty */
1335
+ ExecClearTuple (fcache -> junkFilter -> jf_resultSlot );
1316
1336
}
1317
1337
else
1318
1338
dest = None_Receiver ;
@@ -1500,8 +1520,8 @@ postquel_get_single_result(TupleTableSlot *slot,
1500
1520
/*
1501
1521
* Set up to return the function value. For pass-by-reference datatypes,
1502
1522
* be sure to copy the result into the current context. We can't leave
1503
- * the data in the TupleTableSlot because we intend to clear the slot
1504
- * before returning.
1523
+ * the data in the TupleTableSlot because we must clear the slot before
1524
+ * returning.
1505
1525
*/
1506
1526
if (fcache -> func -> returnsTuple )
1507
1527
{
@@ -1521,6 +1541,9 @@ postquel_get_single_result(TupleTableSlot *slot,
1521
1541
value = datumCopy (value , fcache -> func -> typbyval , fcache -> func -> typlen );
1522
1542
}
1523
1543
1544
+ /* Clear the slot for next time */
1545
+ ExecClearTuple (slot );
1546
+
1524
1547
return value ;
1525
1548
}
1526
1549
@@ -1532,6 +1555,7 @@ fmgr_sql(PG_FUNCTION_ARGS)
1532
1555
{
1533
1556
SQLFunctionCachePtr fcache ;
1534
1557
ErrorContextCallback sqlerrcontext ;
1558
+ MemoryContext tscontext ;
1535
1559
bool randomAccess ;
1536
1560
bool lazyEvalOK ;
1537
1561
bool pushed_snapshot ;
@@ -1558,18 +1582,26 @@ fmgr_sql(PG_FUNCTION_ARGS)
1558
1582
errmsg ("set-valued function called in context that cannot accept a set" )));
1559
1583
randomAccess = rsi -> allowedModes & SFRM_Materialize_Random ;
1560
1584
lazyEvalOK = !(rsi -> allowedModes & SFRM_Materialize_Preferred );
1585
+ /* tuplestore, if used, must have query lifespan */
1586
+ tscontext = rsi -> econtext -> ecxt_per_query_memory ;
1561
1587
}
1562
1588
else
1563
1589
{
1564
1590
randomAccess = false;
1565
1591
lazyEvalOK = true;
1592
+ /* we won't need a tuplestore */
1593
+ tscontext = NULL ;
1566
1594
}
1567
1595
1568
1596
/*
1569
1597
* Initialize fcache if starting a fresh execution.
1570
1598
*/
1571
1599
fcache = init_sql_fcache (fcinfo , lazyEvalOK );
1572
1600
1601
+ /* Remember info that we might need later to construct tuplestore */
1602
+ fcache -> tscontext = tscontext ;
1603
+ fcache -> randomAccess = randomAccess ;
1604
+
1573
1605
/*
1574
1606
* Now we can set up error traceback support for ereport()
1575
1607
*/
@@ -1578,20 +1610,6 @@ fmgr_sql(PG_FUNCTION_ARGS)
1578
1610
sqlerrcontext .previous = error_context_stack ;
1579
1611
error_context_stack = & sqlerrcontext ;
1580
1612
1581
- /*
1582
- * Build tuplestore to hold results, if we don't have one already. We
1583
- * want to re-use the tuplestore across calls, so it needs to live in
1584
- * fcontext.
1585
- */
1586
- if (!fcache -> tstore )
1587
- {
1588
- MemoryContext oldcontext ;
1589
-
1590
- oldcontext = MemoryContextSwitchTo (fcache -> fcontext );
1591
- fcache -> tstore = tuplestore_begin_heap (randomAccess , false, work_mem );
1592
- MemoryContextSwitchTo (oldcontext );
1593
- }
1594
-
1595
1613
/*
1596
1614
* Find first unfinished execution_state. If none, advance to the next
1597
1615
* query in function.
@@ -1661,11 +1679,12 @@ fmgr_sql(PG_FUNCTION_ARGS)
1661
1679
1662
1680
/*
1663
1681
* If we ran the command to completion, we can shut it down now. Any
1664
- * row(s) we need to return are safely stashed in the tuplestore, and
1665
- * we want to be sure that, for example, AFTER triggers get fired
1666
- * before we return anything. Also, if the function doesn't return
1667
- * set, we can shut it down anyway because it must be a SELECT and we
1668
- * don't care about fetching any more result rows.
1682
+ * row(s) we need to return are safely stashed in the result slot or
1683
+ * tuplestore, and we want to be sure that, for example, AFTER
1684
+ * triggers get fired before we return anything. Also, if the
1685
+ * function doesn't return set, we can shut it down anyway because it
1686
+ * must be a SELECT and we don't care about fetching any more result
1687
+ * rows.
1669
1688
*/
1670
1689
if (completed || !fcache -> func -> returnsSet )
1671
1690
postquel_end (es , fcache );
@@ -1708,7 +1727,8 @@ fmgr_sql(PG_FUNCTION_ARGS)
1708
1727
}
1709
1728
1710
1729
/*
1711
- * The tuplestore now contains whatever row(s) we are supposed to return.
1730
+ * The result slot or tuplestore now contains whatever row(s) we are
1731
+ * supposed to return.
1712
1732
*/
1713
1733
if (fcache -> func -> returnsSet )
1714
1734
{
@@ -1721,16 +1741,12 @@ fmgr_sql(PG_FUNCTION_ARGS)
1721
1741
* row.
1722
1742
*/
1723
1743
Assert (es -> lazyEval );
1724
- /* Re-use the junkfilter's output slot to fetch back the tuple */
1744
+ /* The junkfilter's result slot contains the query result tuple */
1725
1745
Assert (fcache -> junkFilter );
1726
1746
slot = fcache -> junkFilter -> jf_resultSlot ;
1727
- if (!tuplestore_gettupleslot (fcache -> tstore , true, false, slot ))
1728
- elog (ERROR , "failed to fetch lazy-eval tuple" );
1747
+ Assert (!TTS_EMPTY (slot ));
1729
1748
/* Extract the result as a datum, and copy out from the slot */
1730
1749
result = postquel_get_single_result (slot , fcinfo , fcache );
1731
- /* Clear the tuplestore, but keep it for next time */
1732
- /* NB: this might delete the slot's content, but we don't care */
1733
- tuplestore_clear (fcache -> tstore );
1734
1750
1735
1751
/*
1736
1752
* Let caller know we're not finished.
@@ -1752,12 +1768,8 @@ fmgr_sql(PG_FUNCTION_ARGS)
1752
1768
else if (fcache -> lazyEval )
1753
1769
{
1754
1770
/*
1755
- * We are done with a lazy evaluation. Clean up.
1756
- */
1757
- tuplestore_clear (fcache -> tstore );
1758
-
1759
- /*
1760
- * Let caller know we're finished.
1771
+ * We are done with a lazy evaluation. Let caller know we're
1772
+ * finished.
1761
1773
*/
1762
1774
rsi -> isDone = ExprEndResult ;
1763
1775
@@ -1779,7 +1791,12 @@ fmgr_sql(PG_FUNCTION_ARGS)
1779
1791
* We are done with a non-lazy evaluation. Return whatever is in
1780
1792
* the tuplestore. (It is now caller's responsibility to free the
1781
1793
* tuplestore when done.)
1794
+ *
1795
+ * Note an edge case: we could get here without having made a
1796
+ * tuplestore if the function is declared to return SETOF VOID.
1797
+ * ExecMakeTableFunctionResult will cope with null setResult.
1782
1798
*/
1799
+ Assert (fcache -> tstore || fcache -> func -> rettype == VOIDOID );
1783
1800
rsi -> returnMode = SFRM_Materialize ;
1784
1801
rsi -> setResult = fcache -> tstore ;
1785
1802
fcache -> tstore = NULL ;
@@ -1807,9 +1824,9 @@ fmgr_sql(PG_FUNCTION_ARGS)
1807
1824
*/
1808
1825
if (fcache -> junkFilter )
1809
1826
{
1810
- /* Re-use the junkfilter's output slot to fetch back the tuple */
1827
+ /* The junkfilter's result slot contains the query result tuple */
1811
1828
slot = fcache -> junkFilter -> jf_resultSlot ;
1812
- if (tuplestore_gettupleslot ( fcache -> tstore , true, false, slot ))
1829
+ if (! TTS_EMPTY ( slot ))
1813
1830
result = postquel_get_single_result (slot , fcinfo , fcache );
1814
1831
else
1815
1832
{
@@ -1824,9 +1841,6 @@ fmgr_sql(PG_FUNCTION_ARGS)
1824
1841
fcinfo -> isnull = true;
1825
1842
result = (Datum ) 0 ;
1826
1843
}
1827
-
1828
- /* Clear the tuplestore, but keep it for next time */
1829
- tuplestore_clear (fcache -> tstore );
1830
1844
}
1831
1845
1832
1846
/* Pop snapshot if we have pushed one */
@@ -2604,11 +2618,32 @@ sqlfunction_receive(TupleTableSlot *slot, DestReceiver *self)
2604
2618
{
2605
2619
DR_sqlfunction * myState = (DR_sqlfunction * ) self ;
2606
2620
2607
- /* Filter tuple as needed */
2608
- slot = ExecFilterJunk (myState -> filter , slot );
2621
+ if (myState -> tstore )
2622
+ {
2623
+ /* We are collecting all of a set result into the tuplestore */
2624
+
2625
+ /* Filter tuple as needed */
2626
+ slot = ExecFilterJunk (myState -> filter , slot );
2609
2627
2610
- /* Store the filtered tuple into the tuplestore */
2611
- tuplestore_puttupleslot (myState -> tstore , slot );
2628
+ /* Store the filtered tuple into the tuplestore */
2629
+ tuplestore_puttupleslot (myState -> tstore , slot );
2630
+ }
2631
+ else
2632
+ {
2633
+ /*
2634
+ * We only want the first tuple, which we'll save in the junkfilter's
2635
+ * result slot. Ignore any additional tuples passed.
2636
+ */
2637
+ if (TTS_EMPTY (myState -> filter -> jf_resultSlot ))
2638
+ {
2639
+ /* Filter tuple as needed */
2640
+ slot = ExecFilterJunk (myState -> filter , slot );
2641
+ Assert (slot == myState -> filter -> jf_resultSlot );
2642
+
2643
+ /* Materialize the slot so it preserves pass-by-ref values */
2644
+ ExecMaterializeSlot (slot );
2645
+ }
2646
+ }
2612
2647
2613
2648
return true;
2614
2649
}
0 commit comments