Branch data Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * pgpa_identifier.c
4 : : * create appropriate identifiers for range table entries
5 : : *
6 : : * The goal of this module is to be able to produce identifiers for range
7 : : * table entries that are unique, understandable to human beings, and
8 : : * able to be reconstructed during future planning cycles. As an
9 : : * exception, we do not care about, or want to produce, identifiers for
10 : : * RTE_JOIN entries. This is because (1) we would end up with a ton of
11 : : * RTEs with unhelpful names like unnamed_join_17; (2) not all joins have
12 : : * RTEs; and (3) we intend to refer to joins by their constituent members
13 : : * rather than by reference to the join RTE.
14 : : *
15 : : * In general, we construct identifiers of the following form:
16 : : *
17 : : * alias_name#occurrence_number/child_table_name@subquery_name
18 : : *
19 : : * However, occurrence_number is omitted when it is the first occurrence
20 : : * within the same subquery, child_table_name is omitted for relations that
21 : : * are not child tables, and subquery_name is omitted for the topmost
22 : : * query level. Whenever an item is omitted, the preceding punctuation mark
23 : : * is also omitted. Identifier-style escaping is applied to alias_name and
24 : : * subquery_name. Child table names are always schema-qualified.
25 : : * Identifier-style escaping is applied to the schema and to the relation
26 : : * name separately.
27 : : *
28 : : * The upshot of all of these rules is that in simple cases, the relation
29 : : * identifier is textually identical to the alias name, making life easier
30 : : * for users. However, even in complex cases, every relation identifier
31 : : * for a given query will be unique (or at least we hope so: if not, this
32 : : * code is buggy and the identifier format might need to be rethought).
33 : : *
34 : : * A key goal of this system is that we want to be able to reconstruct the
35 : : * same identifiers during a future planning cycle for the same query, so
36 : : * that if a certain behavior is specified for a certain identifier, we can
37 : : * properly identify the RTI for which that behavior is mandated. In order
38 : : * for this to work, subquery names must be unique and known before the
39 : : * subquery is planned, and the remainder of the identifier must not depend
40 : : * on any part of the query outside of the current subquery level. In
41 : : * particular, occurrence_number must be calculated relative to the range
42 : : * table for the relevant subquery, not the final flattened range table.
43 : : *
44 : : * NB: All of this code must use rt_fetch(), not planner_rt_fetch()!
45 : : * Join removal and self-join elimination remove rels from the arrays
46 : : * that planner_rt_fetch() uses; using rt_fetch() is necessary to get
47 : : * stable results.
48 : : *
49 : : * Copyright (c) 2016-2026, PostgreSQL Global Development Group
50 : : *
51 : : * contrib/pg_plan_advice/pgpa_identifier.c
52 : : *
53 : : *-------------------------------------------------------------------------
54 : : */
55 : :
56 : : #include "postgres.h"
57 : :
58 : : #include "pgpa_identifier.h"
59 : :
60 : : #include "parser/parsetree.h"
61 : : #include "utils/builtins.h"
62 : : #include "utils/lsyscache.h"
63 : :
64 : : static Index *pgpa_create_top_rti_map(Index rtable_length, List *rtable,
65 : : List *appinfos);
66 : : static int pgpa_occurrence_number(List *rtable, Index *top_rti_map,
67 : : SubPlanRTInfo *rtinfo, Index rti);
68 : :
69 : : /*
70 : : * Create a range table identifier from scratch.
71 : : *
72 : : * This function leaves the caller to do all the heavy lifting, so it's
73 : : * generally better to use one of the functions below instead.
74 : : *
75 : : * See the file header comments for more details on the format of an
76 : : * identifier.
77 : : */
78 : : const char *
79 : 248790 : pgpa_identifier_string(const pgpa_identifier *rid)
80 : : {
81 : : const char *result;
82 : :
83 : : Assert(rid->alias_name != NULL);
84 : 248790 : result = quote_identifier(rid->alias_name);
85 : :
86 : : Assert(rid->occurrence >= 0);
87 [ + + ]: 248790 : if (rid->occurrence > 1)
88 : 6475 : result = psprintf("%s#%d", result, rid->occurrence);
89 : :
90 : : Assert((rid->partnsp == NULL) == (rid->partrel == NULL));
91 [ + + ]: 248790 : if (rid->partrel != NULL)
92 : 20366 : result = psprintf("%s/%s.%s", result,
93 : 20366 : quote_identifier(rid->partnsp),
94 : 20366 : quote_identifier(rid->partrel));
95 : :
96 [ + + ]: 248790 : if (rid->plan_name != NULL)
97 : 52526 : result = psprintf("%s@%s", result, quote_identifier(rid->plan_name));
98 : :
99 : 248790 : return result;
100 : : }
101 : :
102 : : /*
103 : : * Compute a relation identifier for a particular RTI.
104 : : *
105 : : * The caller provides root and rti, and gets the necessary details back via
106 : : * the remaining parameters.
107 : : */
108 : : void
109 : 352137 : pgpa_compute_identifier_by_rti(PlannerInfo *root, Index rti,
110 : : pgpa_identifier *rid)
111 : : {
112 : 352137 : Index top_rti = rti;
113 : 352137 : int occurrence = 1;
114 : : RangeTblEntry *rte;
115 : : RangeTblEntry *top_rte;
116 : 352137 : char *partnsp = NULL;
117 : 352137 : char *partrel = NULL;
118 : :
119 : : /*
120 : : * If this is a child RTE, find the topmost parent that is still of type
121 : : * RTE_RELATION. We do this because we identify children of partitioned
122 : : * tables by the name of the child table, but subqueries can also have
123 : : * child rels and we don't care about those here.
124 : : */
125 : : for (;;)
126 : 47708 : {
127 : : AppendRelInfo *appinfo;
128 : : RangeTblEntry *parent_rte;
129 : :
130 : : /* append_rel_array can be NULL if there are no children */
131 [ + + ]: 399845 : if (root->append_rel_array == NULL ||
132 [ + + ]: 99115 : (appinfo = root->append_rel_array[top_rti]) == NULL)
133 : : break;
134 : :
135 : 50824 : parent_rte = rt_fetch(appinfo->parent_relid, root->parse->rtable);
136 [ + + ]: 50824 : if (parent_rte->rtekind != RTE_RELATION)
137 : 3116 : break;
138 : :
139 : 47708 : top_rti = appinfo->parent_relid;
140 : : }
141 : :
142 : : /* Get the range table entries for the RTI and top RTI. */
143 : 352137 : rte = rt_fetch(rti, root->parse->rtable);
144 : 352137 : top_rte = rt_fetch(top_rti, root->parse->rtable);
145 : : Assert(rte->rtekind != RTE_JOIN);
146 : : Assert(top_rte->rtekind != RTE_JOIN);
147 : :
148 : : /* Work out the correct occurrence number. */
149 [ + + ]: 882874 : for (Index prior_rti = 1; prior_rti < top_rti; ++prior_rti)
150 : : {
151 : : RangeTblEntry *prior_rte;
152 : : AppendRelInfo *appinfo;
153 : :
154 : : /*
155 : : * If this is a child rel of a parent that is a relation, skip it.
156 : : *
157 : : * Such range table entries are disambiguated by mentioning the schema
158 : : * and name of the table, not by counting them as separate occurrences
159 : : * of the same table.
160 : : *
161 : : * NB: append_rel_array can be NULL if there are no children
162 : : */
163 [ + + ]: 530737 : if (root->append_rel_array != NULL &&
164 [ + + ]: 45801 : (appinfo = root->append_rel_array[prior_rti]) != NULL)
165 : : {
166 : : RangeTblEntry *parent_rte;
167 : :
168 : 2222 : parent_rte = rt_fetch(appinfo->parent_relid, root->parse->rtable);
169 [ - + ]: 2222 : if (parent_rte->rtekind == RTE_RELATION)
170 : 0 : continue;
171 : : }
172 : :
173 : : /* Skip NULL entries and joins. */
174 : 530737 : prior_rte = rt_fetch(prior_rti, root->parse->rtable);
175 [ + - + + ]: 530737 : if (prior_rte == NULL || prior_rte->rtekind == RTE_JOIN)
176 : 115824 : continue;
177 : :
178 : : /* Skip if the alias name differs. */
179 [ + + ]: 414913 : if (strcmp(prior_rte->eref->aliasname, rte->eref->aliasname) != 0)
180 : 405093 : continue;
181 : :
182 : : /* Looks like a true duplicate. */
183 : 9820 : ++occurrence;
184 : : }
185 : :
186 : : /* If this is a child table, get the schema and relation names. */
187 [ + + ]: 352137 : if (rti != top_rti)
188 : : {
189 : 36316 : partnsp = get_namespace_name_or_temp(get_rel_namespace(rte->relid));
190 : 36316 : partrel = get_rel_name(rte->relid);
191 : : }
192 : :
193 : : /* OK, we have all the answers we need. Return them to the caller. */
194 : 352137 : rid->alias_name = top_rte->eref->aliasname;
195 : 352137 : rid->occurrence = occurrence;
196 : 352137 : rid->partnsp = partnsp;
197 : 352137 : rid->partrel = partrel;
198 : 352137 : rid->plan_name = root->plan_name;
199 : 352137 : }
200 : :
201 : : /*
202 : : * Compute a relation identifier for a set of RTIs, except for any RTE_JOIN
203 : : * RTIs that may be present.
204 : : *
205 : : * RTE_JOIN entries are excluded because they cannot be mentioned by plan
206 : : * advice.
207 : : *
208 : : * The caller is responsible for making sure that the "rids" array is large
209 : : * enough to store the results.
210 : : *
211 : : * The return value is the number of identifiers computed.
212 : : */
213 : : int
214 : 149692 : pgpa_compute_identifiers_by_relids(PlannerInfo *root, Bitmapset *relids,
215 : : pgpa_identifier *rids)
216 : : {
217 : 149692 : int count = 0;
218 : 149692 : int rti = -1;
219 : :
220 [ + + ]: 356497 : while ((rti = bms_next_member(relids, rti)) >= 0)
221 : : {
222 : 206805 : RangeTblEntry *rte = rt_fetch(rti, root->parse->rtable);
223 : :
224 [ + + ]: 206805 : if (rte->rtekind == RTE_JOIN)
225 : 8338 : continue;
226 : 198467 : pgpa_compute_identifier_by_rti(root, rti, &rids[count++]);
227 : : }
228 : :
229 : : Assert(count > 0);
230 : 149692 : return count;
231 : : }
232 : :
233 : : /*
234 : : * Create an array of range table identifiers for all the non-NULL,
235 : : * non-RTE_JOIN entries in the PlannedStmt's range table.
236 : : */
237 : : pgpa_identifier *
238 : 86466 : pgpa_create_identifiers_for_planned_stmt(PlannedStmt *pstmt)
239 : : {
240 : 86466 : Index rtable_length = list_length(pstmt->rtable);
241 : 86466 : pgpa_identifier *result = palloc0_array(pgpa_identifier, rtable_length);
242 : : Index *top_rti_map;
243 : 86466 : int rtinfoindex = 0;
244 : 86466 : SubPlanRTInfo *rtinfo = NULL;
245 : 86466 : SubPlanRTInfo *nextrtinfo = NULL;
246 : :
247 : : /*
248 : : * Account for relations added by inheritance expansion of partitioned
249 : : * tables.
250 : : */
251 : 86466 : top_rti_map = pgpa_create_top_rti_map(rtable_length, pstmt->rtable,
252 : : pstmt->appendRelations);
253 : :
254 : : /*
255 : : * When we begin iterating, we're processing the portion of the range
256 : : * table that originated from the top-level PlannerInfo, so subrtinfo is
257 : : * NULL. Later, subrtinfo will be the SubPlanRTInfo for the subquery whose
258 : : * portion of the range table we are processing. nextrtinfo is always the
259 : : * SubPlanRTInfo that follows the current one, if any, so when we're
260 : : * processing the top-level query's portion of the range table, the next
261 : : * SubPlanRTInfo is the very first one.
262 : : */
263 [ + + ]: 86466 : if (pstmt->subrtinfos != NULL)
264 : 9521 : nextrtinfo = linitial(pstmt->subrtinfos);
265 : :
266 : : /* Main loop over the range table. */
267 [ + + ]: 283039 : for (Index rti = 1; rti <= rtable_length; rti++)
268 : : {
269 : : const char *plan_name;
270 : : Index top_rti;
271 : : RangeTblEntry *rte;
272 : : RangeTblEntry *top_rte;
273 : 196573 : char *partnsp = NULL;
274 : 196573 : char *partrel = NULL;
275 : : int occurrence;
276 : : pgpa_identifier *rid;
277 : :
278 : : /*
279 : : * Advance to the next SubPlanRTInfo, if it's time to do that.
280 : : *
281 : : * This loop probably shouldn't ever iterate more than once, because
282 : : * that would imply that a subquery was planned but added nothing to
283 : : * the range table; but let's be defensive and assume it can happen.
284 : : */
285 [ + + + + ]: 213371 : while (nextrtinfo != NULL && rti > nextrtinfo->rtoffset)
286 : : {
287 : 16798 : rtinfo = nextrtinfo;
288 [ + + ]: 16798 : if (++rtinfoindex >= list_length(pstmt->subrtinfos))
289 : 9521 : nextrtinfo = NULL;
290 : : else
291 : 7277 : nextrtinfo = list_nth(pstmt->subrtinfos, rtinfoindex);
292 : : }
293 : :
294 : : /* Fetch the range table entry, if any. */
295 : 196573 : rte = rt_fetch(rti, pstmt->rtable);
296 : :
297 : : /*
298 : : * We can't and don't need to identify null entries, and we don't want
299 : : * to identify join entries.
300 : : */
301 [ + - + + ]: 196573 : if (rte == NULL || rte->rtekind == RTE_JOIN)
302 : 18741 : continue;
303 : :
304 : : /*
305 : : * If this is not a relation added by partitioned table expansion,
306 : : * then the top RTI/RTE are just the same as this RTI/RTE. Otherwise,
307 : : * we need the information for the top RTI/RTE, and must also fetch
308 : : * the partition schema and name.
309 : : */
310 : 177832 : top_rti = top_rti_map[rti - 1];
311 [ + + ]: 177832 : if (rti == top_rti)
312 : 164980 : top_rte = rte;
313 : : else
314 : : {
315 : 12852 : top_rte = rt_fetch(top_rti, pstmt->rtable);
316 : : partnsp =
317 : 12852 : get_namespace_name_or_temp(get_rel_namespace(rte->relid));
318 : 12852 : partrel = get_rel_name(rte->relid);
319 : : }
320 : :
321 : : /* Compute the correct occurrence number. */
322 : 177832 : occurrence = pgpa_occurrence_number(pstmt->rtable, top_rti_map,
323 : : rtinfo, top_rti);
324 : :
325 : : /* Get the name of the current plan (NULL for toplevel query). */
326 [ + + ]: 177832 : plan_name = rtinfo == NULL ? NULL : rtinfo->plan_name;
327 : :
328 : : /* Save all the details we've derived. */
329 : 177832 : rid = &result[rti - 1];
330 : 177832 : rid->alias_name = top_rte->eref->aliasname;
331 : 177832 : rid->occurrence = occurrence;
332 : 177832 : rid->partnsp = partnsp;
333 : 177832 : rid->partrel = partrel;
334 : 177832 : rid->plan_name = plan_name;
335 : : }
336 : :
337 : 86466 : return result;
338 : : }
339 : :
340 : : /*
341 : : * Search for a pgpa_identifier in the array of identifiers computed for the
342 : : * range table. If exactly one match is found, return the matching RTI; else
343 : : * return 0.
344 : : */
345 : : Index
346 : 162717 : pgpa_compute_rti_from_identifier(int rtable_length,
347 : : pgpa_identifier *rt_identifiers,
348 : : pgpa_identifier *rid)
349 : : {
350 : 162717 : Index result = 0;
351 : :
352 [ + + ]: 1225249 : for (int rti = 1; rti <= rtable_length; ++rti)
353 : : {
354 : 1062532 : pgpa_identifier *rti_rid = &rt_identifiers[rti - 1];
355 : :
356 : : /* If there's no identifier for this RTI, skip it. */
357 [ + + ]: 1062532 : if (rti_rid->alias_name == NULL)
358 : 185903 : continue;
359 : :
360 : : /*
361 : : * If it matches, return this RTI. The partition schema, partition
362 : : * name, and plan name must either match exactly or be omitted on both
363 : : * sides.
364 : : */
365 [ + + ]: 876629 : if (strcmp(rid->alias_name, rti_rid->alias_name) == 0 &&
366 [ + + + + ]: 569497 : rid->occurrence == rti_rid->occurrence &&
367 [ + + ]: 533282 : strings_equal_or_both_null(rid->partnsp, rti_rid->partnsp) &&
368 [ + + ]: 445012 : strings_equal_or_both_null(rid->partrel, rti_rid->partrel) &&
369 : 189767 : strings_equal_or_both_null(rid->plan_name, rti_rid->plan_name))
370 : : {
371 [ - + ]: 162717 : if (result != 0)
372 : : {
373 : : /* Multiple matches were found. */
374 : 0 : return 0;
375 : : }
376 : 162717 : result = rti;
377 : : }
378 : : }
379 : :
380 : 162717 : return result;
381 : : }
382 : :
383 : : /*
384 : : * Build a mapping from each RTI to the RTI whose alias_name will be used to
385 : : * construct the range table identifier.
386 : : *
387 : : * For child relations, this is the topmost parent that is still of type
388 : : * RTE_RELATION. For other relations, it's just the original RTI.
389 : : *
390 : : * Since we're eventually going to need this information for every RTI in
391 : : * the range table, it's best to compute all the answers in a single pass over
392 : : * the AppendRelInfo list. Otherwise, we might end up searching through that
393 : : * list repeatedly for entries of interest.
394 : : *
395 : : * Note that the returned array is uses zero-based indexing, while RTIs use
396 : : * 1-based indexing, so subtract 1 from the RTI before looking it up in the
397 : : * array.
398 : : */
399 : : static Index *
400 : 86466 : pgpa_create_top_rti_map(Index rtable_length, List *rtable, List *appinfos)
401 : : {
402 : 86466 : Index *top_rti_map = palloc0_array(Index, rtable_length);
403 : :
404 : : /* Initially, make every RTI point to itself. */
405 [ + + ]: 283039 : for (Index rti = 1; rti <= rtable_length; ++rti)
406 : 196573 : top_rti_map[rti - 1] = rti;
407 : :
408 : : /* Update the map for each AppendRelInfo object. */
409 [ + + + + : 188680 : foreach_node(AppendRelInfo, appinfo, appinfos)
+ + ]
410 : : {
411 : 15748 : Index parent_rti = appinfo->parent_relid;
412 : 15748 : RangeTblEntry *parent_rte = rt_fetch(parent_rti, rtable);
413 : :
414 : : /* If the parent is not RTE_RELATION, ignore this entry. */
415 [ + + ]: 15748 : if (parent_rte->rtekind != RTE_RELATION)
416 : 2896 : continue;
417 : :
418 : : /*
419 : : * Map the child to wherever we mapped the parent. Parents always
420 : : * precede their children in the AppendRelInfo list, so this should
421 : : * work out.
422 : : */
423 : 12852 : top_rti_map[appinfo->child_relid - 1] = top_rti_map[parent_rti - 1];
424 : : }
425 : :
426 : 86466 : return top_rti_map;
427 : : }
428 : :
429 : : /*
430 : : * Find the occurrence number of a certain relation within a certain subquery.
431 : : *
432 : : * The same alias name can occur multiple times within a subquery, but we want
433 : : * to disambiguate by giving different occurrences different integer indexes.
434 : : * However, child tables are disambiguated by including the table name rather
435 : : * than by incrementing the occurrence number; and joins are not named and so
436 : : * shouldn't increment the occurrence number either.
437 : : */
438 : : static int
439 : 177832 : pgpa_occurrence_number(List *rtable, Index *top_rti_map,
440 : : SubPlanRTInfo *rtinfo, Index rti)
441 : : {
442 [ + + ]: 177832 : Index rtoffset = (rtinfo == NULL) ? 0 : rtinfo->rtoffset;
443 : 177832 : int occurrence = 1;
444 : 177832 : RangeTblEntry *rte = rt_fetch(rti, rtable);
445 : :
446 [ + + ]: 301863 : for (Index prior_rti = rtoffset + 1; prior_rti < rti; ++prior_rti)
447 : : {
448 : : RangeTblEntry *prior_rte;
449 : :
450 : : /*
451 : : * If this is a child rel of a parent that is a relation, skip it.
452 : : *
453 : : * Such range table entries are disambiguated by mentioning the schema
454 : : * and name of the table, not by counting them as separate occurrences
455 : : * of the same table.
456 : : */
457 [ - + ]: 124031 : if (top_rti_map[prior_rti - 1] != prior_rti)
458 : 0 : continue;
459 : :
460 : : /* Skip joins. */
461 : 124031 : prior_rte = rt_fetch(prior_rti, rtable);
462 [ + + ]: 124031 : if (prior_rte->rtekind == RTE_JOIN)
463 : 14928 : continue;
464 : :
465 : : /* Skip if the alias name differs. */
466 [ + + ]: 109103 : if (strcmp(prior_rte->eref->aliasname, rte->eref->aliasname) != 0)
467 : 99369 : continue;
468 : :
469 : : /* Looks like a true duplicate. */
470 : 9734 : ++occurrence;
471 : : }
472 : :
473 : 177832 : return occurrence;
474 : : }
|