Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * test_plan_advice.c
4 : * Test pg_plan_advice by planning every query with generated advice.
5 : *
6 : * With this module loaded, every time a query is executed, we end up
7 : * planning it twice. The first time we plan it, we generate plan advice,
8 : * which we then feed back to pg_plan_advice as the supplied plan advice.
9 : * It is then planned a second time using that advice. This hopefully
10 : * allows us to detect cases where the advice is incorrect or causes
11 : * failures or plan changes for some reason.
12 : *
13 : * Copyright (c) 2016-2026, PostgreSQL Global Development Group
14 : *
15 : * src/test/modules/test_plan_advice/test_plan_advice.c
16 : *
17 : *-------------------------------------------------------------------------
18 : */
19 : #include "postgres.h"
20 :
21 : #include "access/xact.h"
22 : #include "fmgr.h"
23 : #include "optimizer/optimizer.h"
24 : #include "pg_plan_advice.h"
25 : #include "utils/guc.h"
26 :
27 1 : PG_MODULE_MAGIC;
28 :
29 : static bool in_recursion = false;
30 :
31 : static char *test_plan_advice_advisor(PlannerGlobal *glob,
32 : Query *parse,
33 : const char *query_string,
34 : int cursorOptions,
35 : ExplainState *es);
36 : static DefElem *find_defelem_by_defname(List *deflist, char *defname);
37 :
38 : /*
39 : * Initialize this module.
40 : */
41 : void
42 1 : _PG_init(void)
43 : {
44 : void (*add_advisor_fn) (pg_plan_advice_advisor_hook hook);
45 :
46 : /*
47 : * Ask pg_plan_advice to get advice strings from test_plan_advice_advisor
48 : */
49 1 : add_advisor_fn =
50 1 : load_external_function("pg_plan_advice", "pg_plan_advice_add_advisor",
51 : true, NULL);
52 :
53 1 : (*add_advisor_fn) (test_plan_advice_advisor);
54 1 : }
55 :
56 : /*
57 : * Re-plan the given query and return the generated advice string as the
58 : * supplied advice.
59 : */
60 : static char *
61 86827 : test_plan_advice_advisor(PlannerGlobal *glob, Query *parse,
62 : const char *query_string, int cursorOptions,
63 : ExplainState *es)
64 : {
65 : PlannedStmt *pstmt;
66 86827 : int save_nestlevel = 0;
67 : DefElem *pgpa_item;
68 : DefElem *advice_string_item;
69 :
70 : /*
71 : * Since this function is called from the planner and triggers planning,
72 : * we need a recursion guard.
73 : */
74 86827 : if (in_recursion)
75 43447 : return NULL;
76 :
77 43380 : PG_TRY();
78 : {
79 43380 : in_recursion = true;
80 :
81 : /*
82 : * Planning can trigger expression evaluation, which can result in
83 : * sending NOTICE messages or other output to the client. To avoid
84 : * that, we set client_min_messages = ERROR in the hopes of getting
85 : * the same output with and without this module.
86 : *
87 : * We also need to set pg_plan_advice.always_store_advice_details so
88 : * that pg_plan_advice will generate an advice string, since the whole
89 : * point of this function is to get access to that.
90 : */
91 43380 : save_nestlevel = NewGUCNestLevel();
92 43380 : set_config_option("client_min_messages", "error",
93 : PGC_SUSET, PGC_S_SESSION,
94 : GUC_ACTION_SAVE, true, 0, false);
95 43380 : set_config_option("pg_plan_advice.always_store_advice_details", "true",
96 : PGC_SUSET, PGC_S_SESSION,
97 : GUC_ACTION_SAVE, true, 0, false);
98 :
99 : /*
100 : * Replan. We must copy the Query, because the planner modifies it.
101 : * (As noted elsewhere, that's unfortunate; perhaps it will be fixed
102 : * some day.)
103 : */
104 43380 : pstmt = planner(copyObject(parse), query_string, cursorOptions,
105 : glob->boundParams, es);
106 : }
107 764 : PG_FINALLY();
108 : {
109 43380 : in_recursion = false;
110 : }
111 43380 : PG_END_TRY();
112 :
113 : /* Roll back any GUC changes */
114 42616 : if (save_nestlevel > 0)
115 42616 : AtEOXact_GUC(false, save_nestlevel);
116 :
117 : /* Extract and return the advice string */
118 42616 : pgpa_item = find_defelem_by_defname(pstmt->extension_state,
119 : "pg_plan_advice");
120 42616 : if (pgpa_item == NULL)
121 0 : elog(ERROR, "extension state for pg_plan_advice not found");
122 42616 : advice_string_item = find_defelem_by_defname((List *) pgpa_item->arg,
123 : "advice_string");
124 42616 : if (advice_string_item == NULL)
125 0 : elog(ERROR,
126 : "advice string for pg_plan_advice not found in extension state");
127 42616 : return strVal(advice_string_item->arg);
128 : }
129 :
130 : /*
131 : * Search a list of DefElem objects for a given defname.
132 : */
133 : static DefElem *
134 85232 : find_defelem_by_defname(List *deflist, char *defname)
135 : {
136 85232 : foreach_node(DefElem, item, deflist)
137 : {
138 85232 : if (strcmp(item->defname, defname) == 0)
139 85232 : return item;
140 : }
141 :
142 0 : return NULL;
143 : }
|