1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
|
/*-------------------------------------------------------------------------
*
* plancache.c
* Plan cache management.
*
* The plan cache manager has two principal responsibilities: deciding when
* to use a generic plan versus a custom (parameter-value-specific) plan,
* and tracking whether cached plans need to be invalidated because of schema
* changes in the objects they depend on.
*
* The logic for choosing generic or custom plans is in choose_custom_plan,
* which see for comments.
*
* Cache invalidation is driven off sinval events. Any CachedPlanSource
* that matches the event is marked invalid, as is its generic CachedPlan
* if it has one. When (and if) the next demand for a cached plan occurs,
* parse analysis and rewrite is repeated to build a new valid query tree,
* and then planning is performed as normal. We also force re-analysis and
* re-planning if the active search_path is different from the previous time
* or, if RLS is involved, if the user changes or the RLS environment changes.
*
* Note that if the sinval was a result of user DDL actions, parse analysis
* could throw an error, for example if a column referenced by the query is
* no longer present. Another possibility is for the query's output tupdesc
* to change (for instance "SELECT *" might expand differently than before).
* The creator of a cached plan can specify whether it is allowable for the
* query to change output tupdesc on replan --- if so, it's up to the
* caller to notice changes and cope with them.
*
* Currently, we track exactly the dependencies of plans on relations,
* user-defined functions, and domains. On relcache invalidation events or
* pg_proc or pg_type syscache invalidation events, we invalidate just those
* plans that depend on the particular object being modified. (Note: this
* scheme assumes that any table modification that requires replanning will
* generate a relcache inval event.) We also watch for inval events on
* certain other system catalogs, such as pg_namespace; but for them, our
* response is just to invalidate all plans. We expect updates on those
* catalogs to be infrequent enough that more-detailed tracking is not worth
* the effort.
*
* In addition to full-fledged query plans, we provide a facility for
* detecting invalidations of simple scalar expressions. This is fairly
* bare-bones; it's the caller's responsibility to build a new expression
* if the old one gets invalidated.
*
*
* Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* src/backend/utils/cache/plancache.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <limits.h>
#include "access/transam.h"
#include "catalog/namespace.h"
#include "executor/executor.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
#include "parser/analyze.h"
#include "parser/parsetree.h"
#include "storage/lmgr.h"
#include "tcop/pquery.h"
#include "tcop/utility.h"
#include "utils/inval.h"
#include "utils/memutils.h"
#include "utils/resowner_private.h"
#include "utils/rls.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
/*
* We must skip "overhead" operations that involve database access when the
* cached plan's subject statement is a transaction control command.
*/
#define IsTransactionStmtPlan(plansource) \
((plansource)->raw_parse_tree && \
IsA((plansource)->raw_parse_tree->stmt, TransactionStmt))
/*
* This is the head of the backend's list of "saved" CachedPlanSources (i.e.,
* those that are in long-lived storage and are examined for sinval events).
* We use a dlist instead of separate List cells so that we can guarantee
* to save a CachedPlanSource without error.
*/
static dlist_head saved_plan_list = DLIST_STATIC_INIT(saved_plan_list);
/*
* This is the head of the backend's list of CachedExpressions.
*/
static dlist_head cached_expression_list = DLIST_STATIC_INIT(cached_expression_list);
static void ReleaseGenericPlan(CachedPlanSource *plansource);
static List *RevalidateCachedQuery(CachedPlanSource *plansource,
QueryEnvironment *queryEnv);
static bool CheckCachedPlan(CachedPlanSource *plansource);
static CachedPlan *BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
ParamListInfo boundParams, QueryEnvironment *queryEnv);
static bool choose_custom_plan(CachedPlanSource *plansource,
ParamListInfo boundParams);
static double cached_plan_cost(CachedPlan *plan, bool include_planner);
static Query *QueryListGetPrimaryStmt(List *stmts);
static void AcquireExecutorLocks(List *stmt_list, bool acquire);
static void AcquirePlannerLocks(List *stmt_list, bool acquire);
static void ScanQueryForLocks(Query *parsetree, bool acquire);
static bool ScanQueryWalker(Node *node, bool *acquire);
static TupleDesc PlanCacheComputeResultDesc(List *stmt_list);
static void PlanCacheRelCallback(Datum arg, Oid relid);
static void PlanCacheObjectCallback(Datum arg, int cacheid, uint32 hashvalue);
static void PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue);
/* GUC parameter */
int plan_cache_mode;
/*
* InitPlanCache: initialize module during InitPostgres.
*
* All we need to do is hook into inval.c's callback lists.
*/
void
InitPlanCache(void)
{
CacheRegisterRelcacheCallback(PlanCacheRelCallback, (Datum) 0);
CacheRegisterSyscacheCallback(PROCOID, PlanCacheObjectCallback, (Datum) 0);
CacheRegisterSyscacheCallback(TYPEOID, PlanCacheObjectCallback, (Datum) 0);
CacheRegisterSyscacheCallback(NAMESPACEOID, PlanCacheSysCallback, (Datum) 0);
CacheRegisterSyscacheCallback(OPEROID, PlanCacheSysCallback, (Datum) 0);
CacheRegisterSyscacheCallback(AMOPOPID, PlanCacheSysCallback, (Datum) 0);
CacheRegisterSyscacheCallback(FOREIGNSERVEROID, PlanCacheSysCallback, (Datum) 0);
CacheRegisterSyscacheCallback(FOREIGNDATAWRAPPEROID, PlanCacheSysCallback, (Datum) 0);
}
/*
* CreateCachedPlan: initially create a plan cache entry.
*
* Creation of a cached plan is divided into two steps, CreateCachedPlan and
* CompleteCachedPlan. CreateCachedPlan should be called after running the
* query through raw_parser, but before doing parse analysis and rewrite;
* CompleteCachedPlan is called after that. The reason for this arrangement
* is that it can save one round of copying of the raw parse tree, since
* the parser will normally scribble on the raw parse tree. Callers would
* otherwise need to make an extra copy of the parse tree to ensure they
* still had a clean copy to present at plan cache creation time.
*
* All arguments presented to CreateCachedPlan are copied into a memory
* context created as a child of the call-time CurrentMemoryContext, which
* should be a reasonably short-lived working context that will go away in
* event of an error. This ensures that the cached plan data structure will
* likewise disappear if an error occurs before we have fully constructed it.
* Once constructed, the cached plan can be made longer-lived, if needed,
* by calling SaveCachedPlan.
*
* raw_parse_tree: output of raw_parser(), or NULL if empty query
* query_string: original query text
* commandTag: compile-time-constant tag for query, or NULL if empty query
*/
CachedPlanSource *
CreateCachedPlan(RawStmt *raw_parse_tree,
const char *query_string,
const char *commandTag)
{
CachedPlanSource *plansource;
MemoryContext source_context;
MemoryContext oldcxt;
Assert(query_string != NULL); /* required as of 8.4 */
/*
* Make a dedicated memory context for the CachedPlanSource and its
* permanent subsidiary data. It's probably not going to be large, but
* just in case, allow it to grow large. Initially it's a child of the
* caller's context (which we assume to be transient), so that it will be
* cleaned up on error.
*/
source_context = AllocSetContextCreate(CurrentMemoryContext,
"CachedPlanSource",
ALLOCSET_START_SMALL_SIZES);
/*
* Create and fill the CachedPlanSource struct within the new context.
* Most fields are just left empty for the moment.
*/
oldcxt = MemoryContextSwitchTo(source_context);
plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource));
plansource->magic = CACHEDPLANSOURCE_MAGIC;
plansource->raw_parse_tree = copyObject(raw_parse_tree);
plansource->query_string = pstrdup(query_string);
MemoryContextSetIdentifier(source_context, plansource->query_string);
plansource->commandTag = commandTag;
plansource->param_types = NULL;
plansource->num_params = 0;
plansource->parserSetup = NULL;
plansource->parserSetupArg = NULL;
plansource->cursor_options = 0;
plansource->fixed_result = false;
plansource->resultDesc = NULL;
plansource->context = source_context;
plansource->query_list = NIL;
plansource->relationOids = NIL;
plansource->invalItems = NIL;
plansource->search_path = NULL;
plansource->query_context = NULL;
plansource->rewriteRoleId = InvalidOid;
plansource->rewriteRowSecurity = false;
plansource->dependsOnRLS = false;
plansource->gplan = NULL;
plansource->is_oneshot = false;
plansource->is_complete = false;
plansource->is_saved = false;
plansource->is_valid = false;
plansource->generation = 0;
plansource->generic_cost = -1;
plansource->total_custom_cost = 0;
plansource->num_custom_plans = 0;
MemoryContextSwitchTo(oldcxt);
return plansource;
}
/*
* CreateOneShotCachedPlan: initially create a one-shot plan cache entry.
*
* This variant of CreateCachedPlan creates a plan cache entry that is meant
* to be used only once. No data copying occurs: all data structures remain
* in the caller's memory context (which typically should get cleared after
* completing execution). The CachedPlanSource struct itself is also created
* in that context.
*
* A one-shot plan cannot be saved or copied, since we make no effort to
* preserve the raw parse tree unmodified. There is also no support for
* invalidation, so plan use must be completed in the current transaction,
* and DDL that might invalidate the querytree_list must be avoided as well.
*
* raw_parse_tree: output of raw_parser(), or NULL if empty query
* query_string: original query text
* commandTag: compile-time-constant tag for query, or NULL if empty query
*/
CachedPlanSource *
CreateOneShotCachedPlan(RawStmt *raw_parse_tree,
const char *query_string,
const char *commandTag)
{
CachedPlanSource *plansource;
Assert(query_string != NULL); /* required as of 8.4 */
/*
* Create and fill the CachedPlanSource struct within the caller's memory
* context. Most fields are just left empty for the moment.
*/
plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource));
plansource->magic = CACHEDPLANSOURCE_MAGIC;
plansource->raw_parse_tree = raw_parse_tree;
plansource->query_string = query_string;
plansource->commandTag = commandTag;
plansource->param_types = NULL;
plansource->num_params = 0;
plansource->parserSetup = NULL;
plansource->parserSetupArg = NULL;
plansource->cursor_options = 0;
plansource->fixed_result = false;
plansource->resultDesc = NULL;
plansource->context = CurrentMemoryContext;
plansource->query_list = NIL;
plansource->relationOids = NIL;
plansource->invalItems = NIL;
plansource->search_path = NULL;
plansource->query_context = NULL;
plansource->rewriteRoleId = InvalidOid;
plansource->rewriteRowSecurity = false;
plansource->dependsOnRLS = false;
plansource->gplan = NULL;
plansource->is_oneshot = true;
plansource->is_complete = false;
plansource->is_saved = false;
plansource->is_valid = false;
plansource->generation = 0;
plansource->generic_cost = -1;
plansource->total_custom_cost = 0;
plansource->num_custom_plans = 0;
return plansource;
}
/*
* CompleteCachedPlan: second step of creating a plan cache entry.
*
* Pass in the analyzed-and-rewritten form of the query, as well as the
* required subsidiary data about parameters and such. All passed values will
* be copied into the CachedPlanSource's memory, except as specified below.
* After this is called, GetCachedPlan can be called to obtain a plan, and
* optionally the CachedPlanSource can be saved using SaveCachedPlan.
*
* If querytree_context is not NULL, the querytree_list must be stored in that
* context (but the other parameters need not be). The querytree_list is not
* copied, rather the given context is kept as the initial query_context of
* the CachedPlanSource. (It should have been created as a child of the
* caller's working memory context, but it will now be reparented to belong
* to the CachedPlanSource.) The querytree_context is normally the context in
* which the caller did raw parsing and parse analysis. This approach saves
* one tree copying step compared to passing NULL, but leaves lots of extra
* cruft in the query_context, namely whatever extraneous stuff parse analysis
* created, as well as whatever went unused from the raw parse tree. Using
* this option is a space-for-time tradeoff that is appropriate if the
* CachedPlanSource is not expected to survive long.
*
* plancache.c cannot know how to copy the data referenced by parserSetupArg,
* and it would often be inappropriate to do so anyway. When using that
* option, it is caller's responsibility that the referenced data remains
* valid for as long as the CachedPlanSource exists.
*
* If the CachedPlanSource is a "oneshot" plan, then no querytree copying
* occurs at all, and querytree_context is ignored; it is caller's
* responsibility that the passed querytree_list is sufficiently long-lived.
*
* plansource: structure returned by CreateCachedPlan
* querytree_list: analyzed-and-rewritten form of query (list of Query nodes)
* querytree_context: memory context containing querytree_list,
* or NULL to copy querytree_list into a fresh context
* param_types: array of fixed parameter type OIDs, or NULL if none
* num_params: number of fixed parameters
* parserSetup: alternate method for handling query parameters
* parserSetupArg: data to pass to parserSetup
* cursor_options: options bitmask to pass to planner
* fixed_result: true to disallow future changes in query's result tupdesc
*/
void
CompleteCachedPlan(CachedPlanSource *plansource,
List *querytree_list,
MemoryContext querytree_context,
Oid *param_types,
int num_params,
ParserSetupHook parserSetup,
void *parserSetupArg,
int cursor_options,
bool fixed_result)
{
MemoryContext source_context = plansource->context;
MemoryContext oldcxt = CurrentMemoryContext;
/* Assert caller is doing things in a sane order */
Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
Assert(!plansource->is_complete);
/*
* If caller supplied a querytree_context, reparent it underneath the
* CachedPlanSource's context; otherwise, create a suitable context and
* copy the querytree_list into it. But no data copying should be done
* for one-shot plans; for those, assume the passed querytree_list is
* sufficiently long-lived.
*/
if (plansource->is_oneshot)
{
querytree_context = CurrentMemoryContext;
}
else if (querytree_context != NULL)
{
MemoryContextSetParent(querytree_context, source_context);
MemoryContextSwitchTo(querytree_context);
}
else
{
/* Again, it's a good bet the querytree_context can be small */
querytree_context = AllocSetContextCreate(source_context,
"CachedPlanQuery",
ALLOCSET_START_SMALL_SIZES);
MemoryContextSwitchTo(querytree_context);
querytree_list = copyObject(querytree_list);
}
plansource->query_context = querytree_context;
plansource->query_list = querytree_list;
if (!plansource->is_oneshot && !IsTransactionStmtPlan(plansource))
{
/*
* Use the planner machinery to extract dependencies. Data is saved
* in query_context. (We assume that not a lot of extra cruft is
* created by this call.) We can skip this for one-shot plans, and
* transaction control commands have no such dependencies anyway.
*/
extract_query_dependencies((Node *) querytree_list,
&plansource->relationOids,
&plansource->invalItems,
&plansource->dependsOnRLS);
/* Update RLS info as well. */
plansource->rewriteRoleId = GetUserId();
plansource->rewriteRowSecurity = row_security;
/*
* Also save the current search_path in the query_context. (This
* should not generate much extra cruft either, since almost certainly
* the path is already valid.) Again, we don't really need this for
* one-shot plans; and we *must* skip this for transaction control
* commands, because this could result in catalog accesses.
*/
plansource->search_path = GetOverrideSearchPath(querytree_context);
}
/*
* Save the final parameter types (or other parameter specification data)
* into the source_context, as well as our other parameters. Also save
* the result tuple descriptor.
*/
MemoryContextSwitchTo(source_context);
if (num_params > 0)
{
plansource->param_types = (Oid *) palloc(num_params * sizeof(Oid));
memcpy(plansource->param_types, param_types, num_params * sizeof(Oid));
}
else
plansource->param_types = NULL;
plansource->num_params = num_params;
plansource->parserSetup = parserSetup;
plansource->parserSetupArg = parserSetupArg;
plansource->cursor_options = cursor_options;
plansource->fixed_result = fixed_result;
plansource->resultDesc = PlanCacheComputeResultDesc(querytree_list);
MemoryContextSwitchTo(oldcxt);
plansource->is_complete = true;
plansource->is_valid = true;
}
/*
* SaveCachedPlan: save a cached plan permanently
*
* This function moves the cached plan underneath CacheMemoryContext (making
* it live for the life of the backend, unless explicitly dropped), and adds
* it to the list of cached plans that are checked for invalidation when an
* sinval event occurs.
*
* This is guaranteed not to throw error, except for the caller-error case
* of trying to save a one-shot plan. Callers typically depend on that
* since this is called just before or just after adding a pointer to the
* CachedPlanSource to some permanent data structure of their own. Up until
* this is done, a CachedPlanSource is just transient data that will go away
* automatically on transaction abort.
*/
void
SaveCachedPlan(CachedPlanSource *plansource)
{
/* Assert caller is doing things in a sane order */
Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
Assert(plansource->is_complete);
Assert(!plansource->is_saved);
/* This seems worth a real test, though */
if (plansource->is_oneshot)
elog(ERROR, "cannot save one-shot cached plan");
/*
* In typical use, this function would be called before generating any
* plans from the CachedPlanSource. If there is a generic plan, moving it
* into CacheMemoryContext would be pretty risky since it's unclear
* whether the caller has taken suitable care with making references
* long-lived. Best thing to do seems to be to discard the plan.
*/
ReleaseGenericPlan(plansource);
/*
* Reparent the source memory context under CacheMemoryContext so that it
* will live indefinitely. The query_context follows along since it's
* already a child of the other one.
*/
MemoryContextSetParent(plansource->context, CacheMemoryContext);
/*
* Add the entry to the global list of cached plans.
*/
dlist_push_tail(&saved_plan_list, &plansource->node);
plansource->is_saved = true;
}
/*
* DropCachedPlan: destroy a cached plan.
*
* Actually this only destroys the CachedPlanSource: any referenced CachedPlan
* is released, but not destroyed until its refcount goes to zero. That
* handles the situation where DropCachedPlan is called while the plan is
* still in use.
*/
void
DropCachedPlan(CachedPlanSource *plansource)
{
Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
/* If it's been saved, remove it from the list */
if (plansource->is_saved)
{
dlist_delete(&plansource->node);
plansource->is_saved = false;
}
/* Decrement generic CachedPlan's refcount and drop if no longer needed */
ReleaseGenericPlan(plansource);
/* Mark it no longer valid */
plansource->magic = 0;
/*
* Remove the CachedPlanSource and all subsidiary data (including the
* query_context if any). But if it's a one-shot we can't free anything.
*/
if (!plansource->is_oneshot)
MemoryContextDelete(plansource->context);
}
/*
* ReleaseGenericPlan: release a CachedPlanSource's generic plan, if any.
*/
static void
ReleaseGenericPlan(CachedPlanSource *plansource)
{
/* Be paranoid about the possibility that ReleaseCachedPlan fails */
if (plansource->gplan)
{
CachedPlan *plan = plansource->gplan;
Assert(plan->magic == CACHEDPLAN_MAGIC);
plansource->gplan = NULL;
ReleaseCachedPlan(plan, false);
}
}
/*
* RevalidateCachedQuery: ensure validity of analyzed-and-rewritten query tree.
*
* What we do here is re-acquire locks and redo parse analysis if necessary.
* On return, the query_list is valid and we have sufficient locks to begin
* planning.
*
* If any parse analysis activity is required, the caller's memory context is
* used for that work.
*
* The result value is the transient analyzed-and-rewritten query tree if we
* had to do re-analysis, and NIL otherwise. (This is returned just to save
* a tree copying step in a subsequent BuildCachedPlan call.)
*/
static List *
RevalidateCachedQuery(CachedPlanSource *plansource,
QueryEnvironment *queryEnv)
{
bool snapshot_set;
RawStmt *rawtree;
List *tlist; /* transient query-tree list */
List *qlist; /* permanent query-tree list */
TupleDesc resultDesc;
MemoryContext querytree_context;
MemoryContext oldcxt;
/*
* For one-shot plans, we do not support revalidation checking; it's
* assumed the query is parsed, planned, and executed in one transaction,
* so that no lock re-acquisition is necessary. Also, there is never any
* need to revalidate plans for transaction control commands (and we
* mustn't risk any catalog accesses when handling those).
*/
if (plansource->is_oneshot || IsTransactionStmtPlan(plansource))
{
Assert(plansource->is_valid);
return NIL;
}
/*
* If the query is currently valid, we should have a saved search_path ---
* check to see if that matches the current environment. If not, we want
* to force replan.
*/
if (plansource->is_valid)
{
Assert(plansource->search_path != NULL);
if (!OverrideSearchPathMatchesCurrent(plansource->search_path))
{
/* Invalidate the querytree and generic plan */
plansource->is_valid = false;
if (plansource->gplan)
plansource->gplan->is_valid = false;
}
}
/*
* If the query rewrite phase had a possible RLS dependency, we must redo
* it if either the role or the row_security setting has changed.
*/
if (plansource->is_valid && plansource->dependsOnRLS &&
(plansource->rewriteRoleId != GetUserId() ||
plansource->rewriteRowSecurity != row_security))
plansource->is_valid = false;
/*
* If the query is currently valid, acquire locks on the referenced
* objects; then check again. We need to do it this way to cover the race
* condition that an invalidation message arrives before we get the locks.
*/
if (plansource->is_valid)
{
AcquirePlannerLocks(plansource->query_list, true);
/*
* By now, if any invalidation has happened, the inval callback
* functions will have marked the query invalid.
*/
if (plansource->is_valid)
{
/* Successfully revalidated and locked the query. */
return NIL;
}
/* Oops, the race case happened. Release useless locks. */
AcquirePlannerLocks(plansource->query_list, false);
}
/*
* Discard the no-longer-useful query tree. (Note: we don't want to do
* this any earlier, else we'd not have been able to release locks
* correctly in the race condition case.)
*/
plansource->is_valid = false;
plansource->query_list = NIL;
plansource->relationOids = NIL;
plansource->invalItems = NIL;
plansource->search_path = NULL;
/*
* Free the query_context. We don't really expect MemoryContextDelete to
* fail, but just in case, make sure the CachedPlanSource is left in a
* reasonably sane state. (The generic plan won't get unlinked yet, but
* that's acceptable.)
*/
if (plansource->query_context)
{
MemoryContext qcxt = plansource->query_context;
plansource->query_context = NULL;
MemoryContextDelete(qcxt);
}
/* Drop the generic plan reference if any */
ReleaseGenericPlan(plansource);
/*
* Now re-do parse analysis and rewrite. This not incidentally acquires
* the locks we need to do planning safely.
*/
Assert(plansource->is_complete);
/*
* If a snapshot is already set (the normal case), we can just use that
* for parsing/planning. But if it isn't, install one. Note: no point in
* checking whether parse analysis requires a snapshot; utility commands
* don't have invalidatable plans, so we'd not get here for such a
* command.
*/
snapshot_set = false;
if (!ActiveSnapshotSet())
{
PushActiveSnapshot(GetTransactionSnapshot());
snapshot_set = true;
}
/*
* Run parse analysis and rule rewriting. The parser tends to scribble on
* its input, so we must copy the raw parse tree to prevent corruption of
* the cache.
*/
rawtree = copyObject(plansource->raw_parse_tree);
if (rawtree == NULL)
tlist = NIL;
else if (plansource->parserSetup != NULL)
tlist = pg_analyze_and_rewrite_params(rawtree,
plansource->query_string,
plansource->parserSetup,
plansource->parserSetupArg,
queryEnv);
else
tlist = pg_analyze_and_rewrite(rawtree,
plansource->query_string,
plansource->param_types,
plansource->num_params,
queryEnv);
/* Release snapshot if we got one */
if (snapshot_set)
PopActiveSnapshot();
/*
* Check or update the result tupdesc. XXX should we use a weaker
* condition than equalTupleDescs() here?
*
* We assume the parameter types didn't change from the first time, so no
* need to update that.
*/
resultDesc = PlanCacheComputeResultDesc(tlist);
if (resultDesc == NULL && plansource->resultDesc == NULL)
{
/* OK, doesn't return tuples */
}
else if (resultDesc == NULL || plansource->resultDesc == NULL ||
!equalTupleDescs(resultDesc, plansource->resultDesc))
{
/* can we give a better error message? */
if (plansource->fixed_result)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cached plan must not change result type")));
oldcxt = MemoryContextSwitchTo(plansource->context);
if (resultDesc)
resultDesc = CreateTupleDescCopy(resultDesc);
if (plansource->resultDesc)
FreeTupleDesc(plansource->resultDesc);
plansource->resultDesc = resultDesc;
MemoryContextSwitchTo(oldcxt);
}
/*
* Allocate new query_context and copy the completed querytree into it.
* It's transient until we complete the copying and dependency extraction.
*/
querytree_context = AllocSetContextCreate(CurrentMemoryContext,
"CachedPlanQuery",
ALLOCSET_START_SMALL_SIZES);
oldcxt = MemoryContextSwitchTo(querytree_context);
qlist = copyObject(tlist);
/*
* Use the planner machinery to extract dependencies. Data is saved in
* query_context. (We assume that not a lot of extra cruft is created by
* this call.)
*/
extract_query_dependencies((Node *) qlist,
&plansource->relationOids,
&plansource->invalItems,
&plansource->dependsOnRLS);
/* Update RLS info as well. */
plansource->rewriteRoleId = GetUserId();
plansource->rewriteRowSecurity = row_security;
/*
* Also save the current search_path in the query_context. (This should
* not generate much extra cruft either, since almost certainly the path
* is already valid.)
*/
plansource->search_path = GetOverrideSearchPath(querytree_context);
MemoryContextSwitchTo(oldcxt);
/* Now reparent the finished query_context and save the links */
MemoryContextSetParent(querytree_context, plansource->context);
plansource->query_context = querytree_context;
plansource->query_list = qlist;
/*
* Note: we do not reset generic_cost or total_custom_cost, although we
* could choose to do so. If the DDL or statistics change that prompted
* the invalidation meant a significant change in the cost estimates, it
* would be better to reset those variables and start fresh; but often it
* doesn't, and we're better retaining our hard-won knowledge about the
* relative costs.
*/
plansource->is_valid = true;
/* Return transient copy of querytrees for possible use in planning */
return tlist;
}
/*
* CheckCachedPlan: see if the CachedPlanSource's generic plan is valid.
*
* Caller must have already called RevalidateCachedQuery to verify that the
* querytree is up to date.
*
* On a "true" return, we have acquired the locks needed to run the plan.
* (We must do this for the "true" result to be race-condition-free.)
*/
static bool
CheckCachedPlan(CachedPlanSource *plansource)
{
CachedPlan *plan = plansource->gplan;
/* Assert that caller checked the querytree */
Assert(plansource->is_valid);
/* If there's no generic plan, just say "false" */
if (!plan)
return false;
Assert(plan->magic == CACHEDPLAN_MAGIC);
/* Generic plans are never one-shot */
Assert(!plan->is_oneshot);
/*
* If plan isn't valid for current role, we can't use it.
*/
if (plan->is_valid && plan->dependsOnRole &&
plan->planRoleId != GetUserId())
plan->is_valid = false;
/*
* If it appears valid, acquire locks and recheck; this is much the same
* logic as in RevalidateCachedQuery, but for a plan.
*/
if (plan->is_valid)
{
/*
* Plan must have positive refcount because it is referenced by
* plansource; so no need to fear it disappears under us here.
*/
Assert(plan->refcount > 0);
AcquireExecutorLocks(plan->stmt_list, true);
/*
* If plan was transient, check to see if TransactionXmin has
* advanced, and if so invalidate it.
*/
if (plan->is_valid &&
TransactionIdIsValid(plan->saved_xmin) &&
!TransactionIdEquals(plan->saved_xmin, TransactionXmin))
plan->is_valid = false;
/*
* By now, if any invalidation has happened, the inval callback
* functions will have marked the plan invalid.
*/
if (plan->is_valid)
{
/* Successfully revalidated and locked the query. */
return true;
}
/* Oops, the race case happened. Release useless locks. */
AcquireExecutorLocks(plan->stmt_list, false);
}
/*
* Plan has been invalidated, so unlink it from the parent and release it.
*/
ReleaseGenericPlan(plansource);
return false;
}
/*
* BuildCachedPlan: construct a new CachedPlan from a CachedPlanSource.
*
* qlist should be the result value from a previous RevalidateCachedQuery,
* or it can be set to NIL if we need to re-copy the plansource's query_list.
*
* To build a generic, parameter-value-independent plan, pass NULL for
* boundParams. To build a custom plan, pass the actual parameter values via
* boundParams. For best effect, the PARAM_FLAG_CONST flag should be set on
* each parameter value; otherwise the planner will treat the value as a
* hint rather than a hard constant.
*
* Planning work is done in the caller's memory context. The finished plan
* is in a child memory context, which typically should get reparented
* (unless this is a one-shot plan, in which case we don't copy the plan).
*/
static CachedPlan *
BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
ParamListInfo boundParams, QueryEnvironment *queryEnv)
{
CachedPlan *plan;
List *plist;
bool snapshot_set;
bool is_transient;
MemoryContext plan_context;
MemoryContext oldcxt = CurrentMemoryContext;
ListCell *lc;
/*
* Normally the querytree should be valid already, but if it's not,
* rebuild it.
*
* NOTE: GetCachedPlan should have called RevalidateCachedQuery first, so
* we ought to be holding sufficient locks to prevent any invalidation.
* However, if we're building a custom plan after having built and
* rejected a generic plan, it's possible to reach here with is_valid
* false due to an invalidation while making the generic plan. In theory
* the invalidation must be a false positive, perhaps a consequence of an
* sinval reset event or the CLOBBER_CACHE_ALWAYS debug code. But for
* safety, let's treat it as real and redo the RevalidateCachedQuery call.
*/
if (!plansource->is_valid)
qlist = RevalidateCachedQuery(plansource, queryEnv);
/*
* If we don't already have a copy of the querytree list that can be
* scribbled on by the planner, make one. For a one-shot plan, we assume
* it's okay to scribble on the original query_list.
*/
if (qlist == NIL)
{
if (!plansource->is_oneshot)
qlist = copyObject(plansource->query_list);
else
qlist = plansource->query_list;
}
/*
* If a snapshot is already set (the normal case), we can just use that
* for planning. But if it isn't, and we need one, install one.
*/
snapshot_set = false;
if (!ActiveSnapshotSet() &&
plansource->raw_parse_tree &&
analyze_requires_snapshot(plansource->raw_parse_tree))
{
PushActiveSnapshot(GetTransactionSnapshot());
snapshot_set = true;
}
/*
* Generate the plan.
*/
plist = pg_plan_queries(qlist, plansource->cursor_options, boundParams);
/* Release snapshot if we got one */
if (snapshot_set)
PopActiveSnapshot();
/*
* Normally we make a dedicated memory context for the CachedPlan and its
* subsidiary data. (It's probably not going to be large, but just in
* case, allow it to grow large. It's transient for the moment.) But for
* a one-shot plan, we just leave it in the caller's memory context.
*/
if (!plansource->is_oneshot)
{
plan_context = AllocSetContextCreate(CurrentMemoryContext,
"CachedPlan",
ALLOCSET_START_SMALL_SIZES);
MemoryContextCopyAndSetIdentifier(plan_context, plansource->query_string);
/*
* Copy plan into the new context.
*/
MemoryContextSwitchTo(plan_context);
plist = copyObject(plist);
}
else
plan_context = CurrentMemoryContext;
/*
* Create and fill the CachedPlan struct within the new context.
*/
plan = (CachedPlan *) palloc(sizeof(CachedPlan));
plan->magic = CACHEDPLAN_MAGIC;
plan->stmt_list = plist;
/*
* CachedPlan is dependent on role either if RLS affected the rewrite
* phase or if a role dependency was injected during planning. And it's
* transient if any plan is marked so.
*/
plan->planRoleId = GetUserId();
plan->dependsOnRole = plansource->dependsOnRLS;
is_transient = false;
foreach(lc, plist)
{
PlannedStmt *plannedstmt = lfirst_node(PlannedStmt, lc);
if (plannedstmt->commandType == CMD_UTILITY)
continue; /* Ignore utility statements */
if (plannedstmt->transientPlan)
is_transient = true;
if (plannedstmt->dependsOnRole)
plan->dependsOnRole = true;
}
if (is_transient)
{
Assert(TransactionIdIsNormal(TransactionXmin));
plan->saved_xmin = TransactionXmin;
}
else
plan->saved_xmin = InvalidTransactionId;
plan->refcount = 0;
plan->context = plan_context;
plan->is_oneshot = plansource->is_oneshot;
plan->is_saved = false;
plan->is_valid = true;
/* assign generation number to new plan */
plan->generation = ++(plansource->generation);
MemoryContextSwitchTo(oldcxt);
return plan;
}
/*
* choose_custom_plan: choose whether to use custom or generic plan
*
* This defines the policy followed by GetCachedPlan.
*/
static bool
choose_custom_plan(CachedPlanSource *plansource, ParamListInfo boundParams)
{
double avg_custom_cost;
/* One-shot plans will always be considered custom */
if (plansource->is_oneshot)
return true;
/* Otherwise, never any point in a custom plan if there's no parameters */
if (boundParams == NULL)
return false;
/* ... nor for transaction control statements */
if (IsTransactionStmtPlan(plansource))
return false;
/* Let settings force the decision */
if (plan_cache_mode == PLAN_CACHE_MODE_FORCE_GENERIC_PLAN)
return false;
if (plan_cache_mode == PLAN_CACHE_MODE_FORCE_CUSTOM_PLAN)
return true;
/* See if caller wants to force the decision */
if (plansource->cursor_options & CURSOR_OPT_GENERIC_PLAN)
return false;
if (plansource->cursor_options & CURSOR_OPT_CUSTOM_PLAN)
return true;
/* Generate custom plans until we have done at least 5 (arbitrary) */
if (plansource->num_custom_plans < 5)
return true;
avg_custom_cost = plansource->total_custom_cost / plansource->num_custom_plans;
/*
* Prefer generic plan if it's less expensive than the average custom
* plan. (Because we include a charge for cost of planning in the
* custom-plan costs, this means the generic plan only has to be less
* expensive than the execution cost plus replan cost of the custom
* plans.)
*
* Note that if generic_cost is -1 (indicating we've not yet determined
* the generic plan cost), we'll always prefer generic at this point.
*/
if (plansource->generic_cost < avg_custom_cost)
return false;
return true;
}
/*
* cached_plan_cost: calculate estimated cost of a plan
*
* If include_planner is true, also include the estimated cost of constructing
* the plan. (We must factor that into the cost of using a custom plan, but
* we don't count it for a generic plan.)
*/
static double
cached_plan_cost(CachedPlan *plan, bool include_planner)
{
double result = 0;
ListCell *lc;
foreach(lc, plan->stmt_list)
{
PlannedStmt *plannedstmt = lfirst_node(PlannedStmt, lc);
if (plannedstmt->commandType == CMD_UTILITY)
continue; /* Ignore utility statements */
result += plannedstmt->planTree->total_cost;
if (include_planner)
{
/*
* Currently we use a very crude estimate of planning effort based
* on the number of relations in the finished plan's rangetable.
* Join planning effort actually scales much worse than linearly
* in the number of relations --- but only until the join collapse
* limits kick in. Also, while inheritance child relations surely
* add to planning effort, they don't make the join situation
* worse. So the actual shape of the planning cost curve versus
* number of relations isn't all that obvious. It will take
* considerable work to arrive at a less crude estimate, and for
* now it's not clear that's worth doing.
*
* The other big difficulty here is that we don't have any very
* good model of how planning cost compares to execution costs.
* The current multiplier of 1000 * cpu_operator_cost is probably
* on the low side, but we'll try this for awhile before making a
* more aggressive correction.
*
* If we ever do write a more complicated estimator, it should
* probably live in src/backend/optimizer/ not here.
*/
int nrelations = list_length(plannedstmt->rtable);
result += 1000.0 * cpu_operator_cost * (nrelations + 1);
}
}
return result;
}
/*
* GetCachedPlan: get a cached plan from a CachedPlanSource.
*
* This function hides the logic that decides whether to use a generic
* plan or a custom plan for the given parameters: the caller does not know
* which it will get.
*
* On return, the plan is valid and we have sufficient locks to begin
* execution.
*
* On return, the refcount of the plan has been incremented; a later
* ReleaseCachedPlan() call is expected. The refcount has been reported
* to the CurrentResourceOwner if useResOwner is true (note that that must
* only be true if it's a "saved" CachedPlanSource).
*
* Note: if any replanning activity is required, the caller's memory context
* is used for that work.
*/
CachedPlan *
GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
bool useResOwner, QueryEnvironment *queryEnv)
{
CachedPlan *plan = NULL;
List *qlist;
bool customplan;
/* Assert caller is doing things in a sane order */
Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
Assert(plansource->is_complete);
/* This seems worth a real test, though */
if (useResOwner && !plansource->is_saved)
elog(ERROR, "cannot apply ResourceOwner to non-saved cached plan");
/* Make sure the querytree list is valid and we have parse-time locks */
qlist = RevalidateCachedQuery(plansource, queryEnv);
/* Decide whether to use a custom plan */
customplan = choose_custom_plan(plansource, boundParams);
if (!customplan)
{
if (CheckCachedPlan(plansource))
{
/* We want a generic plan, and we already have a valid one */
plan = plansource->gplan;
Assert(plan->magic == CACHEDPLAN_MAGIC);
}
else
{
/* Build a new generic plan */
plan = BuildCachedPlan(plansource, qlist, NULL, queryEnv);
/* Just make real sure plansource->gplan is clear */
ReleaseGenericPlan(plansource);
/* Link the new generic plan into the plansource */
plansource->gplan = plan;
plan->refcount++;
/* Immediately reparent into appropriate context */
if (plansource->is_saved)
{
/* saved plans all live under CacheMemoryContext */
MemoryContextSetParent(plan->context, CacheMemoryContext);
plan->is_saved = true;
}
else
{
/* otherwise, it should be a sibling of the plansource */
MemoryContextSetParent(plan->context,
MemoryContextGetParent(plansource->context));
}
/* Update generic_cost whenever we make a new generic plan */
plansource->generic_cost = cached_plan_cost(plan, false);
/*
* If, based on the now-known value of generic_cost, we'd not have
* chosen to use a generic plan, then forget it and make a custom
* plan. This is a bit of a wart but is necessary to avoid a
* glitch in behavior when the custom plans are consistently big
* winners; at some point we'll experiment with a generic plan and
* find it's a loser, but we don't want to actually execute that
* plan.
*/
customplan = choose_custom_plan(plansource, boundParams);
/*
* If we choose to plan again, we need to re-copy the query_list,
* since the planner probably scribbled on it. We can force
* BuildCachedPlan to do that by passing NIL.
*/
qlist = NIL;
}
}
if (customplan)
{
/* Build a custom plan */
plan = BuildCachedPlan(plansource, qlist, boundParams, queryEnv);
/* Accumulate total costs of custom plans, but 'ware overflow */
if (plansource->num_custom_plans < INT_MAX)
{
plansource->total_custom_cost += cached_plan_cost(plan, true);
plansource->num_custom_plans++;
}
}
Assert(plan != NULL);
/* Flag the plan as in use by caller */
if (useResOwner)
ResourceOwnerEnlargePlanCacheRefs(CurrentResourceOwner);
plan->refcount++;
if (useResOwner)
ResourceOwnerRememberPlanCacheRef(CurrentResourceOwner, plan);
/*
* Saved plans should be under CacheMemoryContext so they will not go away
* until their reference count goes to zero. In the generic-plan cases we
* already took care of that, but for a custom plan, do it as soon as we
* have created a reference-counted link.
*/
if (customplan && plansource->is_saved)
{
MemoryContextSetParent(plan->context, CacheMemoryContext);
plan->is_saved = true;
}
return plan;
}
/*
* ReleaseCachedPlan: release active use of a cached plan.
*
* This decrements the reference count, and frees the plan if the count
* has thereby gone to zero. If useResOwner is true, it is assumed that
* the reference count is managed by the CurrentResourceOwner.
*
* Note: useResOwner = false is used for releasing references that are in
* persistent data structures, such as the parent CachedPlanSource or a
* Portal. Transient references should be protected by a resource owner.
*/
void
ReleaseCachedPlan(CachedPlan *plan, bool useResOwner)
{
Assert(plan->magic == CACHEDPLAN_MAGIC);
if (useResOwner)
{
Assert(plan->is_saved);
ResourceOwnerForgetPlanCacheRef(CurrentResourceOwner, plan);
}
Assert(plan->refcount > 0);
plan->refcount--;
if (plan->refcount == 0)
{
/* Mark it no longer valid */
plan->magic = 0;
/* One-shot plans do not own their context, so we can't free them */
if (!plan->is_oneshot)
MemoryContextDelete(plan->context);
}
}
/*
* CachedPlanSetParentContext: move a CachedPlanSource to a new memory context
*
* This can only be applied to unsaved plans; once saved, a plan always
* lives underneath CacheMemoryContext.
*/
void
CachedPlanSetParentContext(CachedPlanSource *plansource,
MemoryContext newcontext)
{
/* Assert caller is doing things in a sane order */
Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
Assert(plansource->is_complete);
/* These seem worth real tests, though */
if (plansource->is_saved)
elog(ERROR, "cannot move a saved cached plan to another context");
if (plansource->is_oneshot)
elog(ERROR, "cannot move a one-shot cached plan to another context");
/* OK, let the caller keep the plan where he wishes */
MemoryContextSetParent(plansource->context, newcontext);
/*
* The query_context needs no special handling, since it's a child of
* plansource->context. But if there's a generic plan, it should be
* maintained as a sibling of plansource->context.
*/
if (plansource->gplan)
{
Assert(plansource->gplan->magic == CACHEDPLAN_MAGIC);
MemoryContextSetParent(plansource->gplan->context, newcontext);
}
}
/*
* CopyCachedPlan: make a copy of a CachedPlanSource
*
* This is a convenience routine that does the equivalent of
* CreateCachedPlan + CompleteCachedPlan, using the data stored in the
* input CachedPlanSource. The result is therefore "unsaved" (regardless
* of the state of the source), and we don't copy any generic plan either.
* The result will be currently valid, or not, the same as the source.
*/
CachedPlanSource *
CopyCachedPlan(CachedPlanSource *plansource)
{
CachedPlanSource *newsource;
MemoryContext source_context;
MemoryContext querytree_context;
MemoryContext oldcxt;
Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
Assert(plansource->is_complete);
/*
* One-shot plans can't be copied, because we haven't taken care that
* parsing/planning didn't scribble on the raw parse tree or querytrees.
*/
if (plansource->is_oneshot)
elog(ERROR, "cannot copy a one-shot cached plan");
source_context = AllocSetContextCreate(CurrentMemoryContext,
"CachedPlanSource",
ALLOCSET_START_SMALL_SIZES);
oldcxt = MemoryContextSwitchTo(source_context);
newsource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource));
newsource->magic = CACHEDPLANSOURCE_MAGIC;
newsource->raw_parse_tree = copyObject(plansource->raw_parse_tree);
newsource->query_string = pstrdup(plansource->query_string);
MemoryContextSetIdentifier(source_context, newsource->query_string);
newsource->commandTag = plansource->commandTag;
if (plansource->num_params > 0)
{
newsource->param_types = (Oid *)
palloc(plansource->num_params * sizeof(Oid));
memcpy(newsource->param_types, plansource->param_types,
plansource->num_params * sizeof(Oid));
}
else
newsource->param_types = NULL;
newsource->num_params = plansource->num_params;
newsource->parserSetup = plansource->parserSetup;
newsource->parserSetupArg = plansource->parserSetupArg;
newsource->cursor_options = plansource->cursor_options;
newsource->fixed_result = plansource->fixed_result;
if (plansource->resultDesc)
newsource->resultDesc = CreateTupleDescCopy(plansource->resultDesc);
else
newsource->resultDesc = NULL;
newsource->context = source_context;
querytree_context = AllocSetContextCreate(source_context,
"CachedPlanQuery",
ALLOCSET_START_SMALL_SIZES);
MemoryContextSwitchTo(querytree_context);
newsource->query_list = copyObject(plansource->query_list);
newsource->relationOids = copyObject(plansource->relationOids);
newsource->invalItems = copyObject(plansource->invalItems);
if (plansource->search_path)
newsource->search_path = CopyOverrideSearchPath(plansource->search_path);
newsource->query_context = querytree_context;
newsource->rewriteRoleId = plansource->rewriteRoleId;
newsource->rewriteRowSecurity = plansource->rewriteRowSecurity;
newsource->dependsOnRLS = plansource->dependsOnRLS;
newsource->gplan = NULL;
newsource->is_oneshot = false;
newsource->is_complete = true;
newsource->is_saved = false;
newsource->is_valid = plansource->is_valid;
newsource->generation = plansource->generation;
/* We may as well copy any acquired cost knowledge */
newsource->generic_cost = plansource->generic_cost;
newsource->total_custom_cost = plansource->total_custom_cost;
newsource->num_custom_plans = plansource->num_custom_plans;
MemoryContextSwitchTo(oldcxt);
return newsource;
}
/*
* CachedPlanIsValid: test whether the rewritten querytree within a
* CachedPlanSource is currently valid (that is, not marked as being in need
* of revalidation).
*
* This result is only trustworthy (ie, free from race conditions) if
* the caller has acquired locks on all the relations used in the plan.
*/
bool
CachedPlanIsValid(CachedPlanSource *plansource)
{
Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
return plansource->is_valid;
}
/*
* CachedPlanGetTargetList: return tlist, if any, describing plan's output
*
* The result is guaranteed up-to-date. However, it is local storage
* within the cached plan, and may disappear next time the plan is updated.
*/
List *
CachedPlanGetTargetList(CachedPlanSource *plansource,
QueryEnvironment *queryEnv)
{
Query *pstmt;
/* Assert caller is doing things in a sane order */
Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
Assert(plansource->is_complete);
/*
* No work needed if statement doesn't return tuples (we assume this
* feature cannot be changed by an invalidation)
*/
if (plansource->resultDesc == NULL)
return NIL;
/* Make sure the querytree list is valid and we have parse-time locks */
RevalidateCachedQuery(plansource, queryEnv);
/* Get the primary statement and find out what it returns */
pstmt = QueryListGetPrimaryStmt(plansource->query_list);
return FetchStatementTargetList((Node *) pstmt);
}
/*
* GetCachedExpression: construct a CachedExpression for an expression.
*
* This performs the same transformations on the expression as
* expression_planner(), ie, convert an expression as emitted by parse
* analysis to be ready to pass to the executor.
*
* The result is stashed in a private, long-lived memory context.
* (Note that this might leak a good deal of memory in the caller's
* context before that.) The passed-in expr tree is not modified.
*/
CachedExpression *
GetCachedExpression(Node *expr)
{
CachedExpression *cexpr;
List *relationOids;
List *invalItems;
MemoryContext cexpr_context;
MemoryContext oldcxt;
/*
* Pass the expression through the planner, and collect dependencies.
* Everything built here is leaked in the caller's context; that's
* intentional to minimize the size of the permanent data structure.
*/
expr = (Node *) expression_planner_with_deps((Expr *) expr,
&relationOids,
&invalItems);
/*
* Make a private memory context, and copy what we need into that. To
* avoid leaking a long-lived context if we fail while copying data, we
* initially make the context under the caller's context.
*/
cexpr_context = AllocSetContextCreate(CurrentMemoryContext,
"CachedExpression",
ALLOCSET_SMALL_SIZES);
oldcxt = MemoryContextSwitchTo(cexpr_context);
cexpr = (CachedExpression *) palloc(sizeof(CachedExpression));
cexpr->magic = CACHEDEXPR_MAGIC;
cexpr->expr = copyObject(expr);
cexpr->is_valid = true;
cexpr->relationOids = copyObject(relationOids);
cexpr->invalItems = copyObject(invalItems);
cexpr->context = cexpr_context;
MemoryContextSwitchTo(oldcxt);
/*
* Reparent the expr's memory context under CacheMemoryContext so that it
* will live indefinitely.
*/
MemoryContextSetParent(cexpr_context, CacheMemoryContext);
/*
* Add the entry to the global list of cached expressions.
*/
dlist_push_tail(&cached_expression_list, &cexpr->node);
return cexpr;
}
/*
* FreeCachedExpression
* Delete a CachedExpression.
*/
void
FreeCachedExpression(CachedExpression *cexpr)
{
/* Sanity check */
Assert(cexpr->magic == CACHEDEXPR_MAGIC);
/* Unlink from global list */
dlist_delete(&cexpr->node);
/* Free all storage associated with CachedExpression */
MemoryContextDelete(cexpr->context);
}
/*
* QueryListGetPrimaryStmt
* Get the "primary" stmt within a list, ie, the one marked canSetTag.
*
* Returns NULL if no such stmt. If multiple queries within the list are
* marked canSetTag, returns the first one. Neither of these cases should
* occur in present usages of this function.
*/
static Query *
QueryListGetPrimaryStmt(List *stmts)
{
ListCell *lc;
foreach(lc, stmts)
{
Query *stmt = lfirst_node(Query, lc);
if (stmt->canSetTag)
return stmt;
}
return NULL;
}
/*
* AcquireExecutorLocks: acquire locks needed for execution of a cached plan;
* or release them if acquire is false.
*/
static void
AcquireExecutorLocks(List *stmt_list, bool acquire)
{
ListCell *lc1;
foreach(lc1, stmt_list)
{
PlannedStmt *plannedstmt = lfirst_node(PlannedStmt, lc1);
ListCell *lc2;
if (plannedstmt->commandType == CMD_UTILITY)
{
/*
* Ignore utility statements, except those (such as EXPLAIN) that
* contain a parsed-but-not-planned query. Note: it's okay to use
* ScanQueryForLocks, even though the query hasn't been through
* rule rewriting, because rewriting doesn't change the query
* representation.
*/
Query *query = UtilityContainsQuery(plannedstmt->utilityStmt);
if (query)
ScanQueryForLocks(query, acquire);
continue;
}
foreach(lc2, plannedstmt->rtable)
{
RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc2);
if (rte->rtekind != RTE_RELATION)
continue;
/*
* Acquire the appropriate type of lock on each relation OID. Note
* that we don't actually try to open the rel, and hence will not
* fail if it's been dropped entirely --- we'll just transiently
* acquire a non-conflicting lock.
*/
if (acquire)
LockRelationOid(rte->relid, rte->rellockmode);
else
UnlockRelationOid(rte->relid, rte->rellockmode);
}
}
}
/*
* AcquirePlannerLocks: acquire locks needed for planning of a querytree list;
* or release them if acquire is false.
*
* Note that we don't actually try to open the relations, and hence will not
* fail if one has been dropped entirely --- we'll just transiently acquire
* a non-conflicting lock.
*/
static void
AcquirePlannerLocks(List *stmt_list, bool acquire)
{
ListCell *lc;
foreach(lc, stmt_list)
{
Query *query = lfirst_node(Query, lc);
if (query->commandType == CMD_UTILITY)
{
/* Ignore utility statements, unless they contain a Query */
query = UtilityContainsQuery(query->utilityStmt);
if (query)
ScanQueryForLocks(query, acquire);
continue;
}
ScanQueryForLocks(query, acquire);
}
}
/*
* ScanQueryForLocks: recursively scan one Query for AcquirePlannerLocks.
*/
static void
ScanQueryForLocks(Query *parsetree, bool acquire)
{
ListCell *lc;
/* Shouldn't get called on utility commands */
Assert(parsetree->commandType != CMD_UTILITY);
/*
* First, process RTEs of the current query level.
*/
foreach(lc, parsetree->rtable)
{
RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
switch (rte->rtekind)
{
case RTE_RELATION:
/* Acquire or release the appropriate type of lock */
if (acquire)
LockRelationOid(rte->relid, rte->rellockmode);
else
UnlockRelationOid(rte->relid, rte->rellockmode);
break;
case RTE_SUBQUERY:
/* Recurse into subquery-in-FROM */
ScanQueryForLocks(rte->subquery, acquire);
break;
default:
/* ignore other types of RTEs */
break;
}
}
/* Recurse into subquery-in-WITH */
foreach(lc, parsetree->cteList)
{
CommonTableExpr *cte = lfirst_node(CommonTableExpr, lc);
ScanQueryForLocks(castNode(Query, cte->ctequery), acquire);
}
/*
* Recurse into sublink subqueries, too. But we already did the ones in
* the rtable and cteList.
*/
if (parsetree->hasSubLinks)
{
query_tree_walker(parsetree, ScanQueryWalker,
(void *) &acquire,
QTW_IGNORE_RC_SUBQUERIES);
}
}
/*
* Walker to find sublink subqueries for ScanQueryForLocks
*/
static bool
ScanQueryWalker(Node *node, bool *acquire)
{
if (node == NULL)
return false;
if (IsA(node, SubLink))
{
SubLink *sub = (SubLink *) node;
/* Do what we came for */
ScanQueryForLocks(castNode(Query, sub->subselect), *acquire);
/* Fall through to process lefthand args of SubLink */
}
/*
* Do NOT recurse into Query nodes, because ScanQueryForLocks already
* processed subselects of subselects for us.
*/
return expression_tree_walker(node, ScanQueryWalker,
(void *) acquire);
}
/*
* PlanCacheComputeResultDesc: given a list of analyzed-and-rewritten Queries,
* determine the result tupledesc it will produce. Returns NULL if the
* execution will not return tuples.
*
* Note: the result is created or copied into current memory context.
*/
static TupleDesc
PlanCacheComputeResultDesc(List *stmt_list)
{
Query *query;
switch (ChoosePortalStrategy(stmt_list))
{
case PORTAL_ONE_SELECT:
case PORTAL_ONE_MOD_WITH:
query = linitial_node(Query, stmt_list);
return ExecCleanTypeFromTL(query->targetList);
case PORTAL_ONE_RETURNING:
query = QueryListGetPrimaryStmt(stmt_list);
Assert(query->returningList);
return ExecCleanTypeFromTL(query->returningList);
case PORTAL_UTIL_SELECT:
query = linitial_node(Query, stmt_list);
Assert(query->utilityStmt);
return UtilityTupleDescriptor(query->utilityStmt);
case PORTAL_MULTI_QUERY:
/* will not return tuples */
break;
}
return NULL;
}
/*
* PlanCacheRelCallback
* Relcache inval callback function
*
* Invalidate all plans mentioning the given rel, or all plans mentioning
* any rel at all if relid == InvalidOid.
*/
static void
PlanCacheRelCallback(Datum arg, Oid relid)
{
dlist_iter iter;
dlist_foreach(iter, &saved_plan_list)
{
CachedPlanSource *plansource = dlist_container(CachedPlanSource,
node, iter.cur);
Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
/* No work if it's already invalidated */
if (!plansource->is_valid)
continue;
/* Never invalidate transaction control commands */
if (IsTransactionStmtPlan(plansource))
continue;
/*
* Check the dependency list for the rewritten querytree.
*/
if ((relid == InvalidOid) ? plansource->relationOids != NIL :
list_member_oid(plansource->relationOids, relid))
{
/* Invalidate the querytree and generic plan */
plansource->is_valid = false;
if (plansource->gplan)
plansource->gplan->is_valid = false;
}
/*
* The generic plan, if any, could have more dependencies than the
* querytree does, so we have to check it too.
*/
if (plansource->gplan && plansource->gplan->is_valid)
{
ListCell *lc;
foreach(lc, plansource->gplan->stmt_list)
{
PlannedStmt *plannedstmt = lfirst_node(PlannedStmt, lc);
if (plannedstmt->commandType == CMD_UTILITY)
continue; /* Ignore utility statements */
if ((relid == InvalidOid) ? plannedstmt->relationOids != NIL :
list_member_oid(plannedstmt->relationOids, relid))
{
/* Invalidate the generic plan only */
plansource->gplan->is_valid = false;
break; /* out of stmt_list scan */
}
}
}
}
/* Likewise check cached expressions */
dlist_foreach(iter, &cached_expression_list)
{
CachedExpression *cexpr = dlist_container(CachedExpression,
node, iter.cur);
Assert(cexpr->magic == CACHEDEXPR_MAGIC);
/* No work if it's already invalidated */
if (!cexpr->is_valid)
continue;
if ((relid == InvalidOid) ? cexpr->relationOids != NIL :
list_member_oid(cexpr->relationOids, relid))
{
cexpr->is_valid = false;
}
}
}
/*
* PlanCacheObjectCallback
* Syscache inval callback function for PROCOID and TYPEOID caches
*
* Invalidate all plans mentioning the object with the specified hash value,
* or all plans mentioning any member of this cache if hashvalue == 0.
*/
static void
PlanCacheObjectCallback(Datum arg, int cacheid, uint32 hashvalue)
{
dlist_iter iter;
dlist_foreach(iter, &saved_plan_list)
{
CachedPlanSource *plansource = dlist_container(CachedPlanSource,
node, iter.cur);
ListCell *lc;
Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
/* No work if it's already invalidated */
if (!plansource->is_valid)
continue;
/* Never invalidate transaction control commands */
if (IsTransactionStmtPlan(plansource))
continue;
/*
* Check the dependency list for the rewritten querytree.
*/
foreach(lc, plansource->invalItems)
{
PlanInvalItem *item = (PlanInvalItem *) lfirst(lc);
if (item->cacheId != cacheid)
continue;
if (hashvalue == 0 ||
item->hashValue == hashvalue)
{
/* Invalidate the querytree and generic plan */
plansource->is_valid = false;
if (plansource->gplan)
plansource->gplan->is_valid = false;
break;
}
}
/*
* The generic plan, if any, could have more dependencies than the
* querytree does, so we have to check it too.
*/
if (plansource->gplan && plansource->gplan->is_valid)
{
foreach(lc, plansource->gplan->stmt_list)
{
PlannedStmt *plannedstmt = lfirst_node(PlannedStmt, lc);
ListCell *lc3;
if (plannedstmt->commandType == CMD_UTILITY)
continue; /* Ignore utility statements */
foreach(lc3, plannedstmt->invalItems)
{
PlanInvalItem *item = (PlanInvalItem *) lfirst(lc3);
if (item->cacheId != cacheid)
continue;
if (hashvalue == 0 ||
item->hashValue == hashvalue)
{
/* Invalidate the generic plan only */
plansource->gplan->is_valid = false;
break; /* out of invalItems scan */
}
}
if (!plansource->gplan->is_valid)
break; /* out of stmt_list scan */
}
}
}
/* Likewise check cached expressions */
dlist_foreach(iter, &cached_expression_list)
{
CachedExpression *cexpr = dlist_container(CachedExpression,
node, iter.cur);
ListCell *lc;
Assert(cexpr->magic == CACHEDEXPR_MAGIC);
/* No work if it's already invalidated */
if (!cexpr->is_valid)
continue;
foreach(lc, cexpr->invalItems)
{
PlanInvalItem *item = (PlanInvalItem *) lfirst(lc);
if (item->cacheId != cacheid)
continue;
if (hashvalue == 0 ||
item->hashValue == hashvalue)
{
cexpr->is_valid = false;
break;
}
}
}
}
/*
* PlanCacheSysCallback
* Syscache inval callback function for other caches
*
* Just invalidate everything...
*/
static void
PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue)
{
ResetPlanCache();
}
/*
* ResetPlanCache: invalidate all cached plans.
*/
void
ResetPlanCache(void)
{
dlist_iter iter;
dlist_foreach(iter, &saved_plan_list)
{
CachedPlanSource *plansource = dlist_container(CachedPlanSource,
node, iter.cur);
ListCell *lc;
Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
/* No work if it's already invalidated */
if (!plansource->is_valid)
continue;
/*
* We *must not* mark transaction control statements as invalid,
* particularly not ROLLBACK, because they may need to be executed in
* aborted transactions when we can't revalidate them (cf bug #5269).
*/
if (IsTransactionStmtPlan(plansource))
continue;
/*
* In general there is no point in invalidating utility statements
* since they have no plans anyway. So invalidate it only if it
* contains at least one non-utility statement, or contains a utility
* statement that contains a pre-analyzed query (which could have
* dependencies.)
*/
foreach(lc, plansource->query_list)
{
Query *query = lfirst_node(Query, lc);
if (query->commandType != CMD_UTILITY ||
UtilityContainsQuery(query->utilityStmt))
{
/* non-utility statement, so invalidate */
plansource->is_valid = false;
if (plansource->gplan)
plansource->gplan->is_valid = false;
/* no need to look further */
break;
}
}
}
/* Likewise invalidate cached expressions */
dlist_foreach(iter, &cached_expression_list)
{
CachedExpression *cexpr = dlist_container(CachedExpression,
node, iter.cur);
Assert(cexpr->magic == CACHEDEXPR_MAGIC);
cexpr->is_valid = false;
}
}
|