Age Owner Branch data TLA 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 *
192 rhaas@postgresql.org 79 :CBC 248401 : pgpa_identifier_string(const pgpa_identifier *rid)
80 : : {
81 : : const char *result;
82 : :
83 [ - + ]: 248401 : Assert(rid->alias_name != NULL);
84 : 248401 : result = quote_identifier(rid->alias_name);
85 : :
86 [ - + ]: 248401 : Assert(rid->occurrence >= 0);
87 [ + + ]: 248401 : if (rid->occurrence > 1)
88 : 6456 : result = psprintf("%s#%d", result, rid->occurrence);
89 : :
4 90 [ - + ]: 248401 : Assert((rid->partnsp == NULL) == (rid->partrel == NULL));
192 91 [ + + ]: 248401 : if (rid->partrel != NULL)
4 92 : 20339 : result = psprintf("%s/%s.%s", result,
93 : 20339 : quote_identifier(rid->partnsp),
94 : 20339 : quote_identifier(rid->partrel));
95 : :
192 96 [ + + ]: 248401 : if (rid->plan_name != NULL)
97 : 52340 : result = psprintf("%s@%s", result, quote_identifier(rid->plan_name));
98 : :
99 : 248401 : 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 : 350773 : pgpa_compute_identifier_by_rti(PlannerInfo *root, Index rti,
110 : : pgpa_identifier *rid)
111 : : {
112 : 350773 : Index top_rti = rti;
113 : 350773 : int occurrence = 1;
114 : : RangeTblEntry *rte;
115 : : RangeTblEntry *top_rte;
116 : 350773 : char *partnsp = NULL;
117 : 350773 : 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 : 47664 : {
127 : : AppendRelInfo *appinfo;
128 : : RangeTblEntry *parent_rte;
129 : :
130 : : /* append_rel_array can be NULL if there are no children */
131 [ + + ]: 398437 : if (root->append_rel_array == NULL ||
132 [ + + ]: 99033 : (appinfo = root->append_rel_array[top_rti]) == NULL)
133 : : break;
134 : :
135 : 50772 : parent_rte = rt_fetch(appinfo->parent_relid, root->parse->rtable);
136 [ + + ]: 50772 : if (parent_rte->rtekind != RTE_RELATION)
137 : 3108 : break;
138 : :
139 : 47664 : top_rti = appinfo->parent_relid;
140 : : }
141 : :
142 : : /* Get the range table entries for the RTI and top RTI. */
143 : 350773 : rte = rt_fetch(rti, root->parse->rtable);
144 : 350773 : top_rte = rt_fetch(top_rti, root->parse->rtable);
145 [ - + ]: 350773 : Assert(rte->rtekind != RTE_JOIN);
146 [ - + ]: 350773 : Assert(top_rte->rtekind != RTE_JOIN);
147 : :
148 : : /* Work out the correct occurrence number. */
149 [ + + ]: 878132 : 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 [ + + ]: 527359 : if (root->append_rel_array != NULL &&
164 [ + + ]: 45773 : (appinfo = root->append_rel_array[prior_rti]) != NULL)
165 : : {
166 : : RangeTblEntry *parent_rte;
167 : :
168 : 2218 : parent_rte = rt_fetch(appinfo->parent_relid, root->parse->rtable);
169 [ - + ]: 2218 : if (parent_rte->rtekind == RTE_RELATION)
192 rhaas@postgresql.org 170 :UBC 0 : continue;
171 : : }
172 : :
173 : : /* Skip NULL entries and joins. */
192 rhaas@postgresql.org 174 :CBC 527359 : prior_rte = rt_fetch(prior_rti, root->parse->rtable);
175 [ + - + + ]: 527359 : if (prior_rte == NULL || prior_rte->rtekind == RTE_JOIN)
176 : 114612 : continue;
177 : :
178 : : /* Skip if the alias name differs. */
179 [ + + ]: 412747 : if (strcmp(prior_rte->eref->aliasname, rte->eref->aliasname) != 0)
180 : 402935 : continue;
181 : :
182 : : /* Looks like a true duplicate. */
183 : 9812 : ++occurrence;
184 : : }
185 : :
186 : : /* If this is a child table, get the schema and relation names. */
187 [ + + ]: 350773 : if (rti != top_rti)
188 : : {
189 : 36290 : partnsp = get_namespace_name_or_temp(get_rel_namespace(rte->relid));
190 : 36290 : partrel = get_rel_name(rte->relid);
191 : : }
192 : :
193 : : /* OK, we have all the answers we need. Return them to the caller. */
194 : 350773 : rid->alias_name = top_rte->eref->aliasname;
195 : 350773 : rid->occurrence = occurrence;
196 : 350773 : rid->partnsp = partnsp;
197 : 350773 : rid->partrel = partrel;
198 : 350773 : rid->plan_name = root->plan_name;
199 : 350773 : }
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 : 148980 : pgpa_compute_identifiers_by_relids(PlannerInfo *root, Bitmapset *relids,
215 : : pgpa_identifier *rids)
216 : : {
217 : 148980 : int count = 0;
218 : 148980 : int rti = -1;
219 : :
220 [ + + ]: 354657 : while ((rti = bms_next_member(relids, rti)) >= 0)
221 : : {
222 : 205677 : RangeTblEntry *rte = rt_fetch(rti, root->parse->rtable);
223 : :
224 [ + + ]: 205677 : if (rte->rtekind == RTE_JOIN)
225 : 8338 : continue;
226 : 197339 : pgpa_compute_identifier_by_rti(root, rti, &rids[count++]);
227 : : }
228 : :
229 [ - + ]: 148980 : Assert(count > 0);
230 : 148980 : 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 : 172745 : pgpa_create_identifiers_for_planned_stmt(PlannedStmt *pstmt)
239 : : {
240 : 172745 : Index rtable_length = list_length(pstmt->rtable);
241 : 172745 : pgpa_identifier *result = palloc0_array(pgpa_identifier, rtable_length);
242 : : Index *top_rti_map;
243 : 172745 : int rtinfoindex = 0;
244 : 172745 : SubPlanRTInfo *rtinfo = NULL;
245 : 172745 : SubPlanRTInfo *nextrtinfo = NULL;
246 : :
247 : : /*
248 : : * Account for relations added by inheritance expansion of partitioned
249 : : * tables.
250 : : */
251 : 172745 : 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 [ + + ]: 172745 : if (pstmt->subrtinfos != NULL)
264 : 19022 : nextrtinfo = linitial(pstmt->subrtinfos);
265 : :
266 : : /* Main loop over the range table. */
267 [ + + ]: 565224 : 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 : 392479 : char *partnsp = NULL;
274 : 392479 : 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 [ + + + + ]: 426011 : while (nextrtinfo != NULL && rti > nextrtinfo->rtoffset)
286 : : {
287 : 33532 : rtinfo = nextrtinfo;
288 [ + + ]: 33532 : if (++rtinfoindex >= list_length(pstmt->subrtinfos))
289 : 19022 : nextrtinfo = NULL;
290 : : else
291 : 14510 : nextrtinfo = list_nth(pstmt->subrtinfos, rtinfoindex);
292 : : }
293 : :
294 : : /* Fetch the range table entry, if any. */
295 : 392479 : 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 [ + - + + ]: 392479 : if (rte == NULL || rte->rtekind == RTE_JOIN)
302 : 37346 : 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 : 355133 : top_rti = top_rti_map[rti - 1];
311 [ + + ]: 355133 : if (rti == top_rti)
312 : 329481 : top_rte = rte;
313 : : else
314 : : {
315 : 25652 : top_rte = rt_fetch(top_rti, pstmt->rtable);
316 : : partnsp =
317 : 25652 : get_namespace_name_or_temp(get_rel_namespace(rte->relid));
318 : 25652 : partrel = get_rel_name(rte->relid);
319 : : }
320 : :
321 : : /* Compute the correct occurrence number. */
322 : 355133 : 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 [ + + ]: 355133 : plan_name = rtinfo == NULL ? NULL : rtinfo->plan_name;
327 : :
328 : : /* Save all the details we've derived. */
329 : 355133 : rid = &result[rti - 1];
330 : 355133 : rid->alias_name = top_rte->eref->aliasname;
331 : 355133 : rid->occurrence = occurrence;
332 : 355133 : rid->partnsp = partnsp;
333 : 355133 : rid->partrel = partrel;
334 : 355133 : rid->plan_name = plan_name;
335 : : }
336 : :
337 : 172745 : 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 : 162458 : pgpa_compute_rti_from_identifier(int rtable_length,
347 : : pgpa_identifier *rt_identifiers,
348 : : pgpa_identifier *rid)
349 : : {
350 : 162458 : Index result = 0;
351 : :
71 peter@eisentraut.org 352 [ + + ]:GNC 1222573 : for (int rti = 1; rti <= rtable_length; ++rti)
353 : : {
192 rhaas@postgresql.org 354 :CBC 1060115 : pgpa_identifier *rti_rid = &rt_identifiers[rti - 1];
355 : :
356 : : /* If there's no identifier for this RTI, skip it. */
357 [ + + ]: 1060115 : if (rti_rid->alias_name == NULL)
358 : 185284 : 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 [ + + ]: 874831 : if (strcmp(rid->alias_name, rti_rid->alias_name) == 0 &&
366 [ + + + + ]: 568620 : rid->occurrence == rti_rid->occurrence &&
4 367 [ + + ]: 532611 : strings_equal_or_both_null(rid->partnsp, rti_rid->partnsp) &&
192 368 [ + + ]: 444512 : strings_equal_or_both_null(rid->partrel, rti_rid->partrel) &&
369 : 189583 : strings_equal_or_both_null(rid->plan_name, rti_rid->plan_name))
370 : : {
371 [ - + ]: 162458 : if (result != 0)
372 : : {
373 : : /* Multiple matches were found. */
192 rhaas@postgresql.org 374 :UBC 0 : return 0;
375 : : }
192 rhaas@postgresql.org 376 :CBC 162458 : result = rti;
377 : : }
378 : : }
379 : :
380 : 162458 : 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 : 172745 : pgpa_create_top_rti_map(Index rtable_length, List *rtable, List *appinfos)
401 : : {
402 : 172745 : Index *top_rti_map = palloc0_array(Index, rtable_length);
403 : :
404 : : /* Initially, make every RTI point to itself. */
405 [ + + ]: 565224 : for (Index rti = 1; rti <= rtable_length; ++rti)
406 : 392479 : top_rti_map[rti - 1] = rti;
407 : :
408 : : /* Update the map for each AppendRelInfo object. */
409 [ + + + + : 376918 : foreach_node(AppendRelInfo, appinfo, appinfos)
+ + ]
410 : : {
411 : 31428 : Index parent_rti = appinfo->parent_relid;
412 : 31428 : RangeTblEntry *parent_rte = rt_fetch(parent_rti, rtable);
413 : :
414 : : /* If the parent is not RTE_RELATION, ignore this entry. */
415 [ + + ]: 31428 : if (parent_rte->rtekind != RTE_RELATION)
416 : 5776 : 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 : 25652 : top_rti_map[appinfo->child_relid - 1] = top_rti_map[parent_rti - 1];
424 : : }
425 : :
426 : 172745 : 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 : 355133 : pgpa_occurrence_number(List *rtable, Index *top_rti_map,
440 : : SubPlanRTInfo *rtinfo, Index rti)
441 : : {
442 [ + + ]: 355133 : Index rtoffset = (rtinfo == NULL) ? 0 : rtinfo->rtoffset;
443 : 355133 : int occurrence = 1;
444 : 355133 : RangeTblEntry *rte = rt_fetch(rti, rtable);
445 : :
446 [ + + ]: 602595 : 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 [ - + ]: 247462 : if (top_rti_map[prior_rti - 1] != prior_rti)
192 rhaas@postgresql.org 458 :UBC 0 : continue;
459 : :
460 : : /* Skip joins. */
192 rhaas@postgresql.org 461 :CBC 247462 : prior_rte = rt_fetch(prior_rti, rtable);
462 [ + + ]: 247462 : if (prior_rte->rtekind == RTE_JOIN)
463 : 29696 : continue;
464 : :
465 : : /* Skip if the alias name differs. */
466 [ + + ]: 217766 : if (strcmp(prior_rte->eref->aliasname, rte->eref->aliasname) != 0)
467 : 198338 : continue;
468 : :
469 : : /* Looks like a true duplicate. */
470 : 19428 : ++occurrence;
471 : : }
472 : :
473 : 355133 : return occurrence;
474 : : }
|