@@ -55,7 +55,9 @@ typedef struct
55
55
*
56
56
* The "next" fields chain together all the execution_state records generated
57
57
* from a single original parsetree. (There will only be more than one in
58
- * case of rule expansion of the original parsetree.)
58
+ * case of rule expansion of the original parsetree.) The chain structure is
59
+ * quite vestigial at this point, because we allocate the records in an array
60
+ * for ease of memory management. But we'll get rid of it some other day.
59
61
*/
60
62
typedef enum
61
63
{
@@ -158,17 +160,20 @@ typedef struct SQLFunctionCache
158
160
159
161
/*
160
162
* While executing a particular query within the function, cplan is the
161
- * CachedPlan we've obtained for that query, and eslist is a list of
163
+ * CachedPlan we've obtained for that query, and eslist is a chain of
162
164
* execution_state records for the individual plans within the CachedPlan.
163
165
* next_query_index is the 0-based index of the next CachedPlanSource to
164
166
* get a CachedPlan from.
165
167
*/
166
168
CachedPlan * cplan ; /* Plan for current query, if any */
167
169
ResourceOwner cowner ; /* CachedPlan is registered with this owner */
168
- execution_state * eslist ; /* execution_state records */
169
170
int next_query_index ; /* index of next CachedPlanSource to run */
170
171
171
- /* if positive, this is the index of the query we're processing */
172
+ execution_state * eslist ; /* chain of execution_state records */
173
+ execution_state * esarray ; /* storage for eslist */
174
+ int esarray_len ; /* allocated length of esarray[] */
175
+
176
+ /* if positive, this is the 1-based index of the query we're processing */
172
177
int error_query_index ;
173
178
174
179
MemoryContext fcontext ; /* memory context holding this struct and all
@@ -212,13 +217,12 @@ static void sql_delete_callback(CachedFunction *cfunc);
212
217
static void sql_postrewrite_callback (List * querytree_list , void * arg );
213
218
static void postquel_start (execution_state * es , SQLFunctionCachePtr fcache );
214
219
static bool postquel_getnext (execution_state * es , SQLFunctionCachePtr fcache );
215
- static void postquel_end (execution_state * es );
220
+ static void postquel_end (execution_state * es , SQLFunctionCachePtr fcache );
216
221
static void postquel_sub_params (SQLFunctionCachePtr fcache ,
217
222
FunctionCallInfo fcinfo );
218
223
static Datum postquel_get_single_result (TupleTableSlot * slot ,
219
224
FunctionCallInfo fcinfo ,
220
- SQLFunctionCachePtr fcache ,
221
- MemoryContext resultcontext );
225
+ SQLFunctionCachePtr fcache );
222
226
static void sql_compile_error_callback (void * arg );
223
227
static void sql_exec_error_callback (void * arg );
224
228
static void ShutdownSQLFunction (Datum arg );
@@ -658,12 +662,11 @@ init_execution_state(SQLFunctionCachePtr fcache)
658
662
CachedPlanSource * plansource ;
659
663
execution_state * preves = NULL ;
660
664
execution_state * lasttages = NULL ;
665
+ int nstmts ;
661
666
ListCell * lc ;
662
667
663
668
/*
664
- * Clean up after previous query, if there was one. Note that we just
665
- * leak the old execution_state records until end of function execution;
666
- * there aren't likely to be enough of them to matter.
669
+ * Clean up after previous query, if there was one.
667
670
*/
668
671
if (fcache -> cplan )
669
672
{
@@ -704,6 +707,22 @@ init_execution_state(SQLFunctionCachePtr fcache)
704
707
fcache -> cowner ,
705
708
NULL );
706
709
710
+ /*
711
+ * If necessary, make esarray[] bigger to hold the needed state.
712
+ */
713
+ nstmts = list_length (fcache -> cplan -> stmt_list );
714
+ if (nstmts > fcache -> esarray_len )
715
+ {
716
+ if (fcache -> esarray == NULL )
717
+ fcache -> esarray = (execution_state * )
718
+ MemoryContextAlloc (fcache -> fcontext ,
719
+ sizeof (execution_state ) * nstmts );
720
+ else
721
+ fcache -> esarray = repalloc_array (fcache -> esarray ,
722
+ execution_state , nstmts );
723
+ fcache -> esarray_len = nstmts ;
724
+ }
725
+
707
726
/*
708
727
* Build execution_state list to match the number of contained plans.
709
728
*/
@@ -740,7 +759,7 @@ init_execution_state(SQLFunctionCachePtr fcache)
740
759
CreateCommandName ((Node * ) stmt ))));
741
760
742
761
/* OK, build the execution_state for this query */
743
- newes = ( execution_state * ) palloc ( sizeof ( execution_state )) ;
762
+ newes = & fcache -> esarray [ foreach_current_index ( lc )] ;
744
763
if (preves )
745
764
preves -> next = newes ;
746
765
else
@@ -775,9 +794,14 @@ init_execution_state(SQLFunctionCachePtr fcache)
775
794
*/
776
795
if (fcache -> func -> rettype != VOIDOID )
777
796
{
778
- TupleTableSlot * slot = MakeSingleTupleTableSlot (NULL ,
779
- & TTSOpsMinimalTuple );
797
+ TupleTableSlot * slot ;
780
798
List * resulttlist ;
799
+ MemoryContext oldcontext ;
800
+
801
+ /* The result slot and JunkFilter must be in the fcontext */
802
+ oldcontext = MemoryContextSwitchTo (fcache -> fcontext );
803
+
804
+ slot = MakeSingleTupleTableSlot (NULL , & TTSOpsMinimalTuple );
781
805
782
806
/*
783
807
* Re-fetch the (possibly modified) output tlist of the final
@@ -811,14 +835,17 @@ init_execution_state(SQLFunctionCachePtr fcache)
811
835
* pointer.
812
836
*/
813
837
fcache -> junkFilter -> jf_targetList = NIL ;
814
- }
815
838
816
- if (fcache -> func -> returnsTuple )
817
- {
818
839
/* Make sure output rowtype is properly blessed */
819
- BlessTupleDesc (fcache -> junkFilter -> jf_resultSlot -> tts_tupleDescriptor );
840
+ if (fcache -> func -> returnsTuple )
841
+ BlessTupleDesc (fcache -> junkFilter -> jf_resultSlot -> tts_tupleDescriptor );
842
+
843
+ MemoryContextSwitchTo (oldcontext );
820
844
}
821
- else if (fcache -> func -> returnsSet && type_is_rowtype (fcache -> func -> rettype ))
845
+
846
+ if (fcache -> func -> returnsSet &&
847
+ !fcache -> func -> returnsTuple &&
848
+ type_is_rowtype (fcache -> func -> rettype ))
822
849
{
823
850
/*
824
851
* Returning rowtype as if it were scalar --- materialize won't work.
@@ -1230,12 +1257,23 @@ static void
1230
1257
postquel_start (execution_state * es , SQLFunctionCachePtr fcache )
1231
1258
{
1232
1259
DestReceiver * dest ;
1260
+ MemoryContext oldcontext ;
1233
1261
1234
1262
Assert (es -> qd == NULL );
1235
1263
1236
1264
/* Caller should have ensured a suitable snapshot is active */
1237
1265
Assert (ActiveSnapshotSet ());
1238
1266
1267
+ /*
1268
+ * Run the sub-executor in a child of fcontext. The sub-executor is
1269
+ * responsible for deleting per-tuple information. (XXX in the case of a
1270
+ * long-lived FmgrInfo, this policy potentially causes memory leakage, but
1271
+ * it's not very clear where we could keep stuff instead. Fortunately,
1272
+ * there are few if any cases where set-returning functions are invoked
1273
+ * via FmgrInfos that would outlive the calling query.)
1274
+ */
1275
+ oldcontext = MemoryContextSwitchTo (fcache -> fcontext );
1276
+
1239
1277
/*
1240
1278
* If this query produces the function result, send its output to the
1241
1279
* tuplestore; else discard any output.
@@ -1285,6 +1323,8 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
1285
1323
}
1286
1324
1287
1325
es -> status = F_EXEC_RUN ;
1326
+
1327
+ MemoryContextSwitchTo (oldcontext );
1288
1328
}
1289
1329
1290
1330
/* Run one execution_state; either to completion or to first result row */
@@ -1293,6 +1333,10 @@ static bool
1293
1333
postquel_getnext (execution_state * es , SQLFunctionCachePtr fcache )
1294
1334
{
1295
1335
bool result ;
1336
+ MemoryContext oldcontext ;
1337
+
1338
+ /* Run the sub-executor in a child of fcontext */
1339
+ oldcontext = MemoryContextSwitchTo (fcache -> fcontext );
1296
1340
1297
1341
if (es -> qd -> operation == CMD_UTILITY )
1298
1342
{
@@ -1320,13 +1364,20 @@ postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache)
1320
1364
result = (count == 0 || es -> qd -> estate -> es_processed == 0 );
1321
1365
}
1322
1366
1367
+ MemoryContextSwitchTo (oldcontext );
1368
+
1323
1369
return result ;
1324
1370
}
1325
1371
1326
1372
/* Shut down execution of one execution_state node */
1327
1373
static void
1328
- postquel_end (execution_state * es )
1374
+ postquel_end (execution_state * es , SQLFunctionCachePtr fcache )
1329
1375
{
1376
+ MemoryContext oldcontext ;
1377
+
1378
+ /* Run the sub-executor in a child of fcontext */
1379
+ oldcontext = MemoryContextSwitchTo (fcache -> fcontext );
1380
+
1330
1381
/* mark status done to ensure we don't do ExecutorEnd twice */
1331
1382
es -> status = F_EXEC_DONE ;
1332
1383
@@ -1341,9 +1392,16 @@ postquel_end(execution_state *es)
1341
1392
1342
1393
FreeQueryDesc (es -> qd );
1343
1394
es -> qd = NULL ;
1395
+
1396
+ MemoryContextSwitchTo (oldcontext );
1344
1397
}
1345
1398
1346
- /* Build ParamListInfo array representing current arguments */
1399
+ /*
1400
+ * Build ParamListInfo array representing current arguments.
1401
+ *
1402
+ * This must be called in the fcontext so that the results have adequate
1403
+ * lifespan.
1404
+ */
1347
1405
static void
1348
1406
postquel_sub_params (SQLFunctionCachePtr fcache ,
1349
1407
FunctionCallInfo fcinfo )
@@ -1398,25 +1456,22 @@ postquel_sub_params(SQLFunctionCachePtr fcache,
1398
1456
/*
1399
1457
* Extract the SQL function's value from a single result row. This is used
1400
1458
* both for scalar (non-set) functions and for each row of a lazy-eval set
1401
- * result.
1459
+ * result. We expect the current memory context to be that of the caller
1460
+ * of fmgr_sql.
1402
1461
*/
1403
1462
static Datum
1404
1463
postquel_get_single_result (TupleTableSlot * slot ,
1405
1464
FunctionCallInfo fcinfo ,
1406
- SQLFunctionCachePtr fcache ,
1407
- MemoryContext resultcontext )
1465
+ SQLFunctionCachePtr fcache )
1408
1466
{
1409
1467
Datum value ;
1410
- MemoryContext oldcontext ;
1411
1468
1412
1469
/*
1413
1470
* Set up to return the function value. For pass-by-reference datatypes,
1414
- * be sure to allocate the result in resultcontext, not the current memory
1415
- * context (which has query lifespan). We can't leave the data in the
1416
- * TupleTableSlot because we intend to clear the slot before returning.
1471
+ * be sure to copy the result into the current context. We can't leave
1472
+ * the data in the TupleTableSlot because we intend to clear the slot
1473
+ * before returning.
1417
1474
*/
1418
- oldcontext = MemoryContextSwitchTo (resultcontext );
1419
-
1420
1475
if (fcache -> func -> returnsTuple )
1421
1476
{
1422
1477
/* We must return the whole tuple as a Datum. */
@@ -1427,16 +1482,14 @@ postquel_get_single_result(TupleTableSlot *slot,
1427
1482
{
1428
1483
/*
1429
1484
* Returning a scalar, which we have to extract from the first column
1430
- * of the SELECT result, and then copy into result context if needed.
1485
+ * of the SELECT result, and then copy into current context if needed.
1431
1486
*/
1432
1487
value = slot_getattr (slot , 1 , & (fcinfo -> isnull ));
1433
1488
1434
1489
if (!fcinfo -> isnull )
1435
1490
value = datumCopy (value , fcache -> func -> typbyval , fcache -> func -> typlen );
1436
1491
}
1437
1492
1438
- MemoryContextSwitchTo (oldcontext );
1439
-
1440
1493
return value ;
1441
1494
}
1442
1495
@@ -1450,7 +1503,6 @@ fmgr_sql(PG_FUNCTION_ARGS)
1450
1503
SQLFunctionLink * flink ;
1451
1504
ErrorContextCallback sqlerrcontext ;
1452
1505
MemoryContext tscontext ;
1453
- MemoryContext oldcontext ;
1454
1506
bool randomAccess ;
1455
1507
bool lazyEvalOK ;
1456
1508
bool pushed_snapshot ;
@@ -1507,20 +1559,14 @@ fmgr_sql(PG_FUNCTION_ARGS)
1507
1559
* Build tuplestore to hold results, if we don't have one already. Make
1508
1560
* sure it's in a suitable context.
1509
1561
*/
1510
- oldcontext = MemoryContextSwitchTo (tscontext );
1511
-
1512
1562
if (!fcache -> tstore )
1513
- fcache -> tstore = tuplestore_begin_heap (randomAccess , false, work_mem );
1563
+ {
1564
+ MemoryContext oldcontext ;
1514
1565
1515
- /*
1516
- * Switch to context in which the fcache lives. The sub-executor is
1517
- * responsible for deleting per-tuple information. (XXX in the case of a
1518
- * long-lived FmgrInfo, this policy potentially causes memory leakage, but
1519
- * it's not very clear where we could keep stuff instead. Fortunately,
1520
- * there are few if any cases where set-returning functions are invoked
1521
- * via FmgrInfos that would outlive the calling query.)
1522
- */
1523
- MemoryContextSwitchTo (fcache -> fcontext );
1566
+ oldcontext = MemoryContextSwitchTo (tscontext );
1567
+ fcache -> tstore = tuplestore_begin_heap (randomAccess , false, work_mem );
1568
+ MemoryContextSwitchTo (oldcontext );
1569
+ }
1524
1570
1525
1571
/*
1526
1572
* Find first unfinished execution_state. If none, advance to the next
@@ -1598,7 +1644,7 @@ fmgr_sql(PG_FUNCTION_ARGS)
1598
1644
* don't care about fetching any more result rows.
1599
1645
*/
1600
1646
if (completed || !fcache -> func -> returnsSet )
1601
- postquel_end (es );
1647
+ postquel_end (es , fcache );
1602
1648
1603
1649
/*
1604
1650
* Break from loop if we didn't shut down (implying we got a
@@ -1657,8 +1703,7 @@ fmgr_sql(PG_FUNCTION_ARGS)
1657
1703
if (!tuplestore_gettupleslot (fcache -> tstore , true, false, slot ))
1658
1704
elog (ERROR , "failed to fetch lazy-eval tuple" );
1659
1705
/* Extract the result as a datum, and copy out from the slot */
1660
- result = postquel_get_single_result (slot , fcinfo ,
1661
- fcache , oldcontext );
1706
+ result = postquel_get_single_result (slot , fcinfo , fcache );
1662
1707
/* Clear the tuplestore, but keep it for next time */
1663
1708
/* NB: this might delete the slot's content, but we don't care */
1664
1709
tuplestore_clear (fcache -> tstore );
@@ -1716,12 +1761,7 @@ fmgr_sql(PG_FUNCTION_ARGS)
1716
1761
fcache -> tstore = NULL ;
1717
1762
/* must copy desc because execSRF.c will free it */
1718
1763
if (fcache -> junkFilter )
1719
- {
1720
- /* setDesc must be allocated in suitable context */
1721
- MemoryContextSwitchTo (tscontext );
1722
1764
rsi -> setDesc = CreateTupleDescCopy (fcache -> junkFilter -> jf_cleanTupType );
1723
- MemoryContextSwitchTo (fcache -> fcontext );
1724
- }
1725
1765
1726
1766
fcinfo -> isnull = true;
1727
1767
result = (Datum ) 0 ;
@@ -1746,8 +1786,7 @@ fmgr_sql(PG_FUNCTION_ARGS)
1746
1786
/* Re-use the junkfilter's output slot to fetch back the tuple */
1747
1787
slot = fcache -> junkFilter -> jf_resultSlot ;
1748
1788
if (tuplestore_gettupleslot (fcache -> tstore , true, false, slot ))
1749
- result = postquel_get_single_result (slot , fcinfo ,
1750
- fcache , oldcontext );
1789
+ result = postquel_get_single_result (slot , fcinfo , fcache );
1751
1790
else
1752
1791
{
1753
1792
fcinfo -> isnull = true;
@@ -1770,8 +1809,6 @@ fmgr_sql(PG_FUNCTION_ARGS)
1770
1809
if (pushed_snapshot )
1771
1810
PopActiveSnapshot ();
1772
1811
1773
- MemoryContextSwitchTo (oldcontext );
1774
-
1775
1812
/*
1776
1813
* If we've gone through every command in the function, we are done.
1777
1814
* Release the cache to start over again on next call.
@@ -1889,7 +1926,7 @@ ShutdownSQLFunction(Datum arg)
1889
1926
if (!fcache -> func -> readonly_func )
1890
1927
PushActiveSnapshot (es -> qd -> snapshot );
1891
1928
1892
- postquel_end (es );
1929
+ postquel_end (es , fcache );
1893
1930
1894
1931
if (!fcache -> func -> readonly_func )
1895
1932
PopActiveSnapshot ();
0 commit comments