Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * queryjumblefuncs.c
4 : : * Query normalization and fingerprinting.
5 : : *
6 : : * Normalization is a process whereby similar queries, typically differing only
7 : : * in their constants (though the exact rules are somewhat more subtle than
8 : : * that) are recognized as equivalent, and are tracked as a single entry. This
9 : : * is particularly useful for non-prepared queries.
10 : : *
11 : : * Normalization is implemented by fingerprinting queries, selectively
12 : : * serializing those fields of each query tree's nodes that are judged to be
13 : : * essential to the query. This is referred to as a query jumble. This is
14 : : * distinct from a regular serialization in that various extraneous
15 : : * information is ignored as irrelevant or not essential to the query, such
16 : : * as the collations of Vars and, most notably, the values of constants.
17 : : *
18 : : * This jumble is acquired at the end of parse analysis of each query, and
19 : : * a 64-bit hash of it is stored into the query's Query.queryId field.
20 : : * The server then copies this value around, making it available in plan
21 : : * tree(s) generated from the query. The executor can then use this value
22 : : * to blame query costs on the proper queryId.
23 : : *
24 : : * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
25 : : * Portions Copyright (c) 1994, Regents of the University of California
26 : : *
27 : : *
28 : : * IDENTIFICATION
29 : : * src/backend/nodes/queryjumblefuncs.c
30 : : *
31 : : *-------------------------------------------------------------------------
32 : : */
33 : : #include "postgres.h"
34 : :
35 : : #include "common/hashfn.h"
36 : : #include "miscadmin.h"
37 : : #include "nodes/queryjumble.h"
38 : : #include "parser/scansup.h"
39 : :
40 : : #define JUMBLE_SIZE 1024 /* query serialization buffer size */
41 : :
42 : : /* GUC parameters */
43 : : int compute_query_id = COMPUTE_QUERY_ID_AUTO;
44 : :
45 : : /*
46 : : * True when compute_query_id is ON or AUTO, and a module requests them.
47 : : *
48 : : * Note that IsQueryIdEnabled() should be used instead of checking
49 : : * query_id_enabled or compute_query_id directly when we want to know
50 : : * whether query identifiers are computed in the core or not.
51 : : */
52 : : bool query_id_enabled = false;
53 : :
54 : : static void AppendJumble(JumbleState *jstate,
55 : : const unsigned char *item, Size size);
56 : : static void RecordConstLocation(JumbleState *jstate, int location);
57 : : static void _jumbleNode(JumbleState *jstate, Node *node);
58 : : static void _jumbleA_Const(JumbleState *jstate, Node *node);
59 : : static void _jumbleList(JumbleState *jstate, Node *node);
60 : : static void _jumbleRangeTblEntry(JumbleState *jstate, Node *node);
61 : :
62 : : /*
63 : : * Given a possibly multi-statement source string, confine our attention to the
64 : : * relevant part of the string.
65 : : */
66 : : const char *
1103 bruce@momjian.us 67 :CBC 75958 : CleanQuerytext(const char *query, int *location, int *len)
68 : : {
1068 tgl@sss.pgh.pa.us 69 : 75958 : int query_location = *location;
70 : 75958 : int query_len = *len;
71 : :
72 : : /* First apply starting offset, unless it's -1 (unknown). */
1103 bruce@momjian.us 73 [ + + ]: 75958 : if (query_location >= 0)
74 : : {
75 [ - + ]: 75774 : Assert(query_location <= strlen(query));
76 : 75774 : query += query_location;
77 : : /* Length of 0 (or -1) means "rest of string" */
78 [ + + ]: 75774 : if (query_len <= 0)
79 : 4521 : query_len = strlen(query);
80 : : else
81 [ - + ]: 71253 : Assert(query_len <= strlen(query));
82 : : }
83 : : else
84 : : {
85 : : /* If query location is unknown, distrust query_len as well */
86 : 184 : query_location = 0;
87 : 184 : query_len = strlen(query);
88 : : }
89 : :
90 : : /*
91 : : * Discard leading and trailing whitespace, too. Use scanner_isspace()
92 : : * not libc's isspace(), because we want to match the lexer's behavior.
93 : : */
94 [ + - + + ]: 76216 : while (query_len > 0 && scanner_isspace(query[0]))
95 : 258 : query++, query_location++, query_len--;
96 [ + - + + ]: 76411 : while (query_len > 0 && scanner_isspace(query[query_len - 1]))
97 : 453 : query_len--;
98 : :
99 : 75958 : *location = query_location;
100 : 75958 : *len = query_len;
101 : :
102 : 75958 : return query;
103 : : }
104 : :
105 : : JumbleState *
291 michael@paquier.xyz 106 : 66001 : JumbleQuery(Query *query)
107 : : {
1103 bruce@momjian.us 108 : 66001 : JumbleState *jstate = NULL;
109 : :
1065 alvherre@alvh.no-ip. 110 [ - + ]: 66001 : Assert(IsQueryIdEnabled());
111 : :
439 michael@paquier.xyz 112 : 66001 : jstate = (JumbleState *) palloc(sizeof(JumbleState));
113 : :
114 : : /* Set up workspace for query jumbling */
115 : 66001 : jstate->jumble = (unsigned char *) palloc(JUMBLE_SIZE);
116 : 66001 : jstate->jumble_len = 0;
117 : 66001 : jstate->clocations_buf_size = 32;
118 : 66001 : jstate->clocations = (LocationLen *)
119 : 66001 : palloc(jstate->clocations_buf_size * sizeof(LocationLen));
120 : 66001 : jstate->clocations_count = 0;
121 : 66001 : jstate->highest_extern_param_id = 0;
122 : :
123 : : /* Compute query ID and mark the Query node with it */
124 : 66001 : _jumbleNode(jstate, (Node *) query);
125 : 66001 : query->queryId = DatumGetUInt64(hash_any_extended(jstate->jumble,
126 : 66001 : jstate->jumble_len,
127 : : 0));
128 : :
129 : : /*
130 : : * If we are unlucky enough to get a hash of zero, use 1 instead for
131 : : * normal statements and 2 for utility queries.
132 : : */
133 [ - + ]: 66001 : if (query->queryId == UINT64CONST(0))
134 : : {
439 michael@paquier.xyz 135 [ # # ]:UBC 0 : if (query->utilityStmt)
136 : 0 : query->queryId = UINT64CONST(2);
137 : : else
1103 bruce@momjian.us 138 : 0 : query->queryId = UINT64CONST(1);
139 : : }
140 : :
1103 bruce@momjian.us 141 :CBC 66001 : return jstate;
142 : : }
143 : :
144 : : /*
145 : : * Enables query identifier computation.
146 : : *
147 : : * Third-party plugins can use this function to inform core that they require
148 : : * a query identifier to be computed.
149 : : */
150 : : void
1065 alvherre@alvh.no-ip. 151 : 6 : EnableQueryId(void)
152 : : {
153 [ + - ]: 6 : if (compute_query_id != COMPUTE_QUERY_ID_OFF)
154 : 6 : query_id_enabled = true;
155 : 6 : }
156 : :
157 : : /*
158 : : * AppendJumble: Append a value that is substantive in a given query to
159 : : * the current jumble.
160 : : */
161 : : static void
1103 bruce@momjian.us 162 : 3352698 : AppendJumble(JumbleState *jstate, const unsigned char *item, Size size)
163 : : {
164 : 3352698 : unsigned char *jumble = jstate->jumble;
165 : 3352698 : Size jumble_len = jstate->jumble_len;
166 : :
167 : : /*
168 : : * Whenever the jumble buffer is full, we hash the current contents and
169 : : * reset the buffer to contain just that hash value, thus relying on the
170 : : * hash to summarize everything so far.
171 : : */
172 [ + + ]: 6706283 : while (size > 0)
173 : : {
174 : : Size part_size;
175 : :
176 [ + + ]: 3353585 : if (jumble_len >= JUMBLE_SIZE)
177 : : {
178 : : uint64 start_hash;
179 : :
180 : 1093 : start_hash = DatumGetUInt64(hash_any_extended(jumble,
181 : : JUMBLE_SIZE, 0));
182 : 1093 : memcpy(jumble, &start_hash, sizeof(start_hash));
183 : 1093 : jumble_len = sizeof(start_hash);
184 : : }
185 : 3353585 : part_size = Min(size, JUMBLE_SIZE - jumble_len);
186 : 3353585 : memcpy(jumble + jumble_len, item, part_size);
187 : 3353585 : jumble_len += part_size;
188 : 3353585 : item += part_size;
189 : 3353585 : size -= part_size;
190 : : }
191 : 3352698 : jstate->jumble_len = jumble_len;
192 : 3352698 : }
193 : :
194 : : /*
195 : : * Record location of constant within query string of query tree
196 : : * that is currently being walked.
197 : : */
198 : : static void
439 michael@paquier.xyz 199 : 99113 : RecordConstLocation(JumbleState *jstate, int location)
200 : : {
201 : : /* -1 indicates unknown or undefined location */
202 [ + + ]: 99113 : if (location >= 0)
203 : : {
204 : : /* enlarge array if needed */
205 [ + + ]: 93257 : if (jstate->clocations_count >= jstate->clocations_buf_size)
206 : : {
207 : 57 : jstate->clocations_buf_size *= 2;
208 : 57 : jstate->clocations = (LocationLen *)
209 : 57 : repalloc(jstate->clocations,
210 : 57 : jstate->clocations_buf_size *
211 : : sizeof(LocationLen));
212 : : }
213 : 93257 : jstate->clocations[jstate->clocations_count].location = location;
214 : : /* initialize lengths to -1 to simplify third-party module usage */
215 : 93257 : jstate->clocations[jstate->clocations_count].length = -1;
216 : 93257 : jstate->clocations_count++;
217 : : }
1103 bruce@momjian.us 218 : 99113 : }
219 : :
220 : : #define JUMBLE_NODE(item) \
221 : : _jumbleNode(jstate, (Node *) expr->item)
222 : : #define JUMBLE_LOCATION(location) \
223 : : RecordConstLocation(jstate, expr->location)
224 : : #define JUMBLE_FIELD(item) \
225 : : AppendJumble(jstate, (const unsigned char *) &(expr->item), sizeof(expr->item))
226 : : #define JUMBLE_FIELD_SINGLE(item) \
227 : : AppendJumble(jstate, (const unsigned char *) &(item), sizeof(item))
228 : : #define JUMBLE_STRING(str) \
229 : : do { \
230 : : if (expr->str) \
231 : : AppendJumble(jstate, (const unsigned char *) (expr->str), strlen(expr->str) + 1); \
232 : : } while(0)
233 : :
234 : : #include "queryjumblefuncs.funcs.c"
235 : :
236 : : static void
439 michael@paquier.xyz 237 : 3124393 : _jumbleNode(JumbleState *jstate, Node *node)
238 : : {
239 : 3124393 : Node *expr = node;
240 : :
241 [ + + ]: 3124393 : if (expr == NULL)
1103 bruce@momjian.us 242 : 1873727 : return;
243 : :
244 : : /* Guard against stack overflow due to overly complex expressions */
245 : 1250666 : check_stack_depth();
246 : :
247 : : /*
248 : : * We always emit the node's NodeTag, then any additional fields that are
249 : : * considered significant, and then we recurse to any child nodes.
250 : : */
439 michael@paquier.xyz 251 : 1250666 : JUMBLE_FIELD(type);
252 : :
253 [ + + + + : 1250666 : switch (nodeTag(expr))
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
- - - + +
+ + - + +
- + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + -
+ + + - +
+ - + + +
+ + + + -
- + + + +
+ + + + +
+ - - - +
+ + + + +
+ + + + +
+ + - + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ - + + +
+ + + + +
+ + + + +
+ + + - +
+ + - + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ - - + +
+ + - +
- ]
254 : : {
255 : : #include "queryjumblefuncs.switch.c"
256 : :
257 : 307090 : case T_List:
258 : : case T_IntList:
259 : : case T_OidList:
260 : : case T_XidList:
261 : 307090 : _jumbleList(jstate, expr);
1103 bruce@momjian.us 262 : 307090 : break;
263 : :
439 michael@paquier.xyz 264 :UBC 0 : default:
265 : : /* Only a warning, since we can stumble along anyway */
266 [ # # ]: 0 : elog(WARNING, "unrecognized node type: %d",
267 : : (int) nodeTag(expr));
1103 bruce@momjian.us 268 : 0 : break;
269 : : }
270 : :
271 : : /* Special cases to handle outside the automated code */
439 michael@paquier.xyz 272 [ + + ]:CBC 1250666 : switch (nodeTag(expr))
273 : : {
1103 bruce@momjian.us 274 : 5229 : case T_Param:
275 : : {
276 : 5229 : Param *p = (Param *) node;
277 : :
278 : : /*
279 : : * Update the highest Param id seen, in order to start
280 : : * normalization correctly.
281 : : */
282 [ + + ]: 5229 : if (p->paramkind == PARAM_EXTERN &&
283 [ + + ]: 4856 : p->paramid > jstate->highest_extern_param_id)
284 : 4269 : jstate->highest_extern_param_id = p->paramid;
285 : : }
286 : 5229 : break;
439 michael@paquier.xyz 287 : 1245437 : default:
1103 bruce@momjian.us 288 : 1245437 : break;
289 : : }
290 : : }
291 : :
292 : : static void
439 michael@paquier.xyz 293 : 307090 : _jumbleList(JumbleState *jstate, Node *node)
294 : : {
295 : 307090 : List *expr = (List *) node;
296 : : ListCell *l;
297 : :
298 [ + + - - : 307090 : switch (expr->type)
- ]
299 : : {
300 : 306704 : case T_List:
301 [ + - + + : 844046 : foreach(l, expr)
+ + ]
302 : 537342 : _jumbleNode(jstate, lfirst(l));
1103 bruce@momjian.us 303 : 306704 : break;
439 michael@paquier.xyz 304 : 386 : case T_IntList:
305 [ + - + + : 865 : foreach(l, expr)
+ + ]
306 : 479 : JUMBLE_FIELD_SINGLE(lfirst_int(l));
1103 bruce@momjian.us 307 : 386 : break;
439 michael@paquier.xyz 308 :UBC 0 : case T_OidList:
309 [ # # # # : 0 : foreach(l, expr)
# # ]
310 : 0 : JUMBLE_FIELD_SINGLE(lfirst_oid(l));
1103 bruce@momjian.us 311 : 0 : break;
439 michael@paquier.xyz 312 : 0 : case T_XidList:
313 [ # # # # : 0 : foreach(l, expr)
# # ]
314 : 0 : JUMBLE_FIELD_SINGLE(lfirst_xid(l));
1103 bruce@momjian.us 315 : 0 : break;
439 michael@paquier.xyz 316 : 0 : default:
317 [ # # ]: 0 : elog(ERROR, "unrecognized list node type: %d",
318 : : (int) expr->type);
319 : : return;
320 : : }
321 : : }
322 : :
323 : : static void
432 michael@paquier.xyz 324 :CBC 8229 : _jumbleA_Const(JumbleState *jstate, Node *node)
325 : : {
326 : 8229 : A_Const *expr = (A_Const *) node;
327 : :
328 : 8229 : JUMBLE_FIELD(isnull);
329 [ + + ]: 8229 : if (!expr->isnull)
330 : : {
331 : 8158 : JUMBLE_FIELD(val.node.type);
332 [ + + + + : 8158 : switch (nodeTag(&expr->val))
+ - ]
333 : : {
334 : 3841 : case T_Integer:
335 : 3841 : JUMBLE_FIELD(val.ival.ival);
336 : 3841 : break;
337 : 42 : case T_Float:
338 [ + - ]: 42 : JUMBLE_STRING(val.fval.fval);
339 : 42 : break;
340 : 112 : case T_Boolean:
341 : 112 : JUMBLE_FIELD(val.boolval.boolval);
342 : 112 : break;
343 : 4161 : case T_String:
344 [ + - ]: 4161 : JUMBLE_STRING(val.sval.sval);
345 : 4161 : break;
346 : 2 : case T_BitString:
347 [ + - ]: 2 : JUMBLE_STRING(val.bsval.bsval);
348 : 2 : break;
432 michael@paquier.xyz 349 :UBC 0 : default:
350 [ # # ]: 0 : elog(ERROR, "unrecognized node type: %d",
351 : : (int) nodeTag(&expr->val));
352 : : break;
353 : : }
354 : : }
432 michael@paquier.xyz 355 :CBC 8229 : }
|