Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * tidpath.c
4 : * Routines to determine which TID conditions are usable for scanning
5 : * a given relation, and create TidPaths and TidRangePaths accordingly.
6 : *
7 : * For TidPaths, we look for WHERE conditions of the form
8 : * "CTID = pseudoconstant", which can be implemented by just fetching
9 : * the tuple directly via heap_fetch(). We can also handle OR'd conditions
10 : * such as (CTID = const1) OR (CTID = const2), as well as ScalarArrayOpExpr
11 : * conditions of the form CTID = ANY(pseudoconstant_array). In particular
12 : * this allows
13 : * WHERE ctid IN (tid1, tid2, ...)
14 : *
15 : * As with indexscans, our definition of "pseudoconstant" is pretty liberal:
16 : * we allow anything that doesn't involve a volatile function or a Var of
17 : * the relation under consideration. Vars belonging to other relations of
18 : * the query are allowed, giving rise to parameterized TID scans.
19 : *
20 : * We also support "WHERE CURRENT OF cursor" conditions (CurrentOfExpr),
21 : * which amount to "CTID = run-time-determined-TID". These could in
22 : * theory be translated to a simple comparison of CTID to the result of
23 : * a function, but in practice it works better to keep the special node
24 : * representation all the way through to execution.
25 : *
26 : * Additionally, TidRangePaths may be created for conditions of the form
27 : * "CTID relop pseudoconstant", where relop is one of >,>=,<,<=, and
28 : * AND-clauses composed of such conditions.
29 : *
30 : * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
31 : * Portions Copyright (c) 1994, Regents of the University of California
32 : *
33 : *
34 : * IDENTIFICATION
35 : * src/backend/optimizer/path/tidpath.c
36 : *
37 : *-------------------------------------------------------------------------
38 : */
39 : #include "postgres.h"
40 :
41 : #include "access/sysattr.h"
42 : #include "catalog/pg_operator.h"
43 : #include "catalog/pg_type.h"
44 : #include "nodes/nodeFuncs.h"
45 : #include "optimizer/cost.h"
46 : #include "optimizer/optimizer.h"
47 : #include "optimizer/pathnode.h"
48 : #include "optimizer/paths.h"
49 : #include "optimizer/restrictinfo.h"
50 :
51 :
52 : /*
53 : * Does this Var represent the CTID column of the specified baserel?
54 : */
55 : static inline bool
56 825828 : IsCTIDVar(Var *var, RelOptInfo *rel)
57 : {
58 : /* The vartype check is strictly paranoia */
59 825828 : if (var->varattno == SelfItemPointerAttributeNumber &&
60 1478 : var->vartype == TIDOID &&
61 1478 : var->varno == rel->relid &&
62 1358 : var->varnullingrels == NULL &&
63 1358 : var->varlevelsup == 0)
64 1358 : return true;
65 824470 : return false;
66 : }
67 :
68 : /*
69 : * Check to see if a RestrictInfo is of the form
70 : * CTID OP pseudoconstant
71 : * or
72 : * pseudoconstant OP CTID
73 : * where OP is a binary operation, the CTID Var belongs to relation "rel",
74 : * and nothing on the other side of the clause does.
75 : */
76 : static bool
77 818778 : IsBinaryTidClause(RestrictInfo *rinfo, RelOptInfo *rel)
78 : {
79 : OpExpr *node;
80 : Node *arg1,
81 : *arg2,
82 : *other;
83 : Relids other_relids;
84 :
85 : /* Must be an OpExpr */
86 818778 : if (!is_opclause(rinfo->clause))
87 162596 : return false;
88 656182 : node = (OpExpr *) rinfo->clause;
89 :
90 : /* OpExpr must have two arguments */
91 656182 : if (list_length(node->args) != 2)
92 48 : return false;
93 656134 : arg1 = linitial(node->args);
94 656134 : arg2 = lsecond(node->args);
95 :
96 : /* Look for CTID as either argument */
97 656134 : other = NULL;
98 656134 : other_relids = NULL;
99 1281636 : if (arg1 && IsA(arg1, Var) &&
100 625502 : IsCTIDVar((Var *) arg1, rel))
101 : {
102 936 : other = arg2;
103 936 : other_relids = rinfo->right_relids;
104 : }
105 756170 : if (!other && arg2 && IsA(arg2, Var) &&
106 100036 : IsCTIDVar((Var *) arg2, rel))
107 : {
108 228 : other = arg1;
109 228 : other_relids = rinfo->left_relids;
110 : }
111 656134 : if (!other)
112 654970 : return false;
113 :
114 : /* The other argument must be a pseudoconstant */
115 2328 : if (bms_is_member(rel->relid, other_relids) ||
116 1164 : contain_volatile_functions(other))
117 0 : return false;
118 :
119 1164 : return true; /* success */
120 : }
121 :
122 : /*
123 : * Check to see if a RestrictInfo is of the form
124 : * CTID = pseudoconstant
125 : * or
126 : * pseudoconstant = CTID
127 : * where the CTID Var belongs to relation "rel", and nothing on the
128 : * other side of the clause does.
129 : */
130 : static bool
131 457082 : IsTidEqualClause(RestrictInfo *rinfo, RelOptInfo *rel)
132 : {
133 457082 : if (!IsBinaryTidClause(rinfo, rel))
134 456332 : return false;
135 :
136 750 : if (((OpExpr *) rinfo->clause)->opno == TIDEqualOperator)
137 374 : return true;
138 :
139 376 : return false;
140 : }
141 :
142 : /*
143 : * Check to see if a RestrictInfo is of the form
144 : * CTID OP pseudoconstant
145 : * or
146 : * pseudoconstant OP CTID
147 : * where OP is a range operator such as <, <=, >, or >=, the CTID Var belongs
148 : * to relation "rel", and nothing on the other side of the clause does.
149 : */
150 : static bool
151 361696 : IsTidRangeClause(RestrictInfo *rinfo, RelOptInfo *rel)
152 : {
153 : Oid opno;
154 :
155 361696 : if (!IsBinaryTidClause(rinfo, rel))
156 361282 : return false;
157 414 : opno = ((OpExpr *) rinfo->clause)->opno;
158 :
159 414 : if (opno == TIDLessOperator || opno == TIDLessEqOperator ||
160 236 : opno == TIDGreaterOperator || opno == TIDGreaterEqOperator)
161 232 : return true;
162 :
163 182 : return false;
164 : }
165 :
166 : /*
167 : * Check to see if a RestrictInfo is of the form
168 : * CTID = ANY (pseudoconstant_array)
169 : * where the CTID Var belongs to relation "rel", and nothing on the
170 : * other side of the clause does.
171 : */
172 : static bool
173 355126 : IsTidEqualAnyClause(PlannerInfo *root, RestrictInfo *rinfo, RelOptInfo *rel)
174 : {
175 : ScalarArrayOpExpr *node;
176 : Node *arg1,
177 : *arg2;
178 :
179 : /* Must be a ScalarArrayOpExpr */
180 355126 : if (!(rinfo->clause && IsA(rinfo->clause, ScalarArrayOpExpr)))
181 337132 : return false;
182 17994 : node = (ScalarArrayOpExpr *) rinfo->clause;
183 :
184 : /* Operator must be tideq */
185 17994 : if (node->opno != TIDEqualOperator)
186 17932 : return false;
187 62 : if (!node->useOr)
188 0 : return false;
189 : Assert(list_length(node->args) == 2);
190 62 : arg1 = linitial(node->args);
191 62 : arg2 = lsecond(node->args);
192 :
193 : /* CTID must be first argument */
194 124 : if (arg1 && IsA(arg1, Var) &&
195 62 : IsCTIDVar((Var *) arg1, rel))
196 : {
197 : /* The other argument must be a pseudoconstant */
198 124 : if (bms_is_member(rel->relid, pull_varnos(root, arg2)) ||
199 62 : contain_volatile_functions(arg2))
200 0 : return false;
201 :
202 62 : return true; /* success */
203 : }
204 :
205 0 : return false;
206 : }
207 :
208 : /*
209 : * Check to see if a RestrictInfo is a CurrentOfExpr referencing "rel".
210 : */
211 : static bool
212 355724 : IsCurrentOfClause(RestrictInfo *rinfo, RelOptInfo *rel)
213 : {
214 : CurrentOfExpr *node;
215 :
216 : /* Must be a CurrentOfExpr */
217 355724 : if (!(rinfo->clause && IsA(rinfo->clause, CurrentOfExpr)))
218 354916 : return false;
219 808 : node = (CurrentOfExpr *) rinfo->clause;
220 :
221 : /* If it references this rel, we're good */
222 808 : if (node->cvarno == rel->relid)
223 808 : return true;
224 :
225 0 : return false;
226 : }
227 :
228 : /*
229 : * Is the RestrictInfo usable as a CTID qual for the specified rel?
230 : *
231 : * This function considers only base cases; AND/OR combination is handled
232 : * below.
233 : */
234 : static bool
235 363040 : RestrictInfoIsTidQual(PlannerInfo *root, RestrictInfo *rinfo, RelOptInfo *rel)
236 : {
237 : /*
238 : * We may ignore pseudoconstant clauses (they can't contain Vars, so could
239 : * not match anyway).
240 : */
241 363040 : if (rinfo->pseudoconstant)
242 6110 : return false;
243 :
244 : /*
245 : * If clause must wait till after some lower-security-level restriction
246 : * clause, reject it.
247 : */
248 356930 : if (!restriction_is_securely_promotable(rinfo, rel))
249 1586 : return false;
250 :
251 : /*
252 : * Check all base cases.
253 : */
254 710470 : if (IsTidEqualClause(rinfo, rel) ||
255 710190 : IsTidEqualAnyClause(root, rinfo, rel) ||
256 355064 : IsCurrentOfClause(rinfo, rel))
257 684 : return true;
258 :
259 354660 : return false;
260 : }
261 :
262 : /*
263 : * Extract a set of CTID conditions from implicit-AND List of RestrictInfos
264 : *
265 : * Returns a List of CTID qual RestrictInfos for the specified rel (with
266 : * implicit OR semantics across the list), or NIL if there are no usable
267 : * equality conditions.
268 : *
269 : * This function is mainly concerned with handling AND/OR recursion.
270 : * However, we do have a special rule to enforce: if there is a CurrentOfExpr
271 : * qual, we *must* return that and only that, else the executor may fail.
272 : * Ordinarily a CurrentOfExpr would be all alone anyway because of grammar
273 : * restrictions, but it is possible for RLS quals to appear AND'ed with it.
274 : * It's even possible (if fairly useless) for the RLS quals to be CTID quals.
275 : * So we must scan the whole rlist to see if there's a CurrentOfExpr. Since
276 : * we have to do that, we also apply some very-trivial preference rules about
277 : * which of the other possibilities should be chosen, in the unlikely event
278 : * that there's more than one choice.
279 : */
280 : static List *
281 362812 : TidQualFromRestrictInfoList(PlannerInfo *root, List *rlist, RelOptInfo *rel,
282 : bool *isCurrentOf)
283 : {
284 362812 : RestrictInfo *tidclause = NULL; /* best simple CTID qual so far */
285 362812 : List *orlist = NIL; /* best OR'ed CTID qual so far */
286 : ListCell *l;
287 :
288 362812 : *isCurrentOf = false;
289 :
290 726114 : foreach(l, rlist)
291 : {
292 363706 : RestrictInfo *rinfo = lfirst_node(RestrictInfo, l);
293 :
294 363706 : if (restriction_is_or_clause(rinfo))
295 : {
296 3790 : List *rlst = NIL;
297 : ListCell *j;
298 :
299 : /*
300 : * We must be able to extract a CTID condition from every
301 : * sub-clause of an OR, or we can't use it.
302 : */
303 3838 : foreach(j, ((BoolExpr *) rinfo->orclause)->args)
304 : {
305 3814 : Node *orarg = (Node *) lfirst(j);
306 : List *sublist;
307 :
308 : /* OR arguments should be ANDs or sub-RestrictInfos */
309 3814 : if (is_andclause(orarg))
310 : {
311 690 : List *andargs = ((BoolExpr *) orarg)->args;
312 : bool sublistIsCurrentOf;
313 :
314 : /* Recurse in case there are sub-ORs */
315 690 : sublist = TidQualFromRestrictInfoList(root, andargs, rel,
316 : &sublistIsCurrentOf);
317 690 : if (sublistIsCurrentOf)
318 0 : elog(ERROR, "IS CURRENT OF within OR clause");
319 : }
320 : else
321 : {
322 3124 : RestrictInfo *ri = castNode(RestrictInfo, orarg);
323 :
324 : Assert(!restriction_is_or_clause(ri));
325 3124 : if (RestrictInfoIsTidQual(root, ri, rel))
326 24 : sublist = list_make1(ri);
327 : else
328 3100 : sublist = NIL;
329 : }
330 :
331 : /*
332 : * If nothing found in this arm, we can't do anything with
333 : * this OR clause.
334 : */
335 3814 : if (sublist == NIL)
336 : {
337 3766 : rlst = NIL; /* forget anything we had */
338 3766 : break; /* out of loop over OR args */
339 : }
340 :
341 : /*
342 : * OK, continue constructing implicitly-OR'ed result list.
343 : */
344 48 : rlst = list_concat(rlst, sublist);
345 : }
346 :
347 3790 : if (rlst)
348 : {
349 : /*
350 : * Accept the OR'ed list if it's the first one, or if it's
351 : * shorter than the previous one.
352 : */
353 24 : if (orlist == NIL || list_length(rlst) < list_length(orlist))
354 24 : orlist = rlst;
355 : }
356 : }
357 : else
358 : {
359 : /* Not an OR clause, so handle base cases */
360 359916 : if (RestrictInfoIsTidQual(root, rinfo, rel))
361 : {
362 : /* We can stop immediately if it's a CurrentOfExpr */
363 660 : if (IsCurrentOfClause(rinfo, rel))
364 : {
365 404 : *isCurrentOf = true;
366 404 : return list_make1(rinfo);
367 : }
368 :
369 : /*
370 : * Otherwise, remember the first non-OR CTID qual. We could
371 : * try to apply some preference order if there's more than
372 : * one, but such usage seems very unlikely, so don't bother.
373 : */
374 256 : if (tidclause == NULL)
375 256 : tidclause = rinfo;
376 : }
377 : }
378 : }
379 :
380 : /*
381 : * Prefer any singleton CTID qual to an OR'ed list. Again, it seems
382 : * unlikely to be worth thinking harder than that.
383 : */
384 362408 : if (tidclause)
385 244 : return list_make1(tidclause);
386 362164 : return orlist;
387 : }
388 :
389 : /*
390 : * Extract a set of CTID range conditions from implicit-AND List of RestrictInfos
391 : *
392 : * Returns a List of CTID range qual RestrictInfos for the specified rel
393 : * (with implicit AND semantics across the list), or NIL if there are no
394 : * usable range conditions or if the rel's table AM does not support TID range
395 : * scans.
396 : */
397 : static List *
398 361718 : TidRangeQualFromRestrictInfoList(List *rlist, RelOptInfo *rel)
399 : {
400 361718 : List *rlst = NIL;
401 : ListCell *l;
402 :
403 361718 : if ((rel->amflags & AMFLAG_HAS_TID_RANGE) == 0)
404 0 : return NIL;
405 :
406 723414 : foreach(l, rlist)
407 : {
408 361696 : RestrictInfo *rinfo = lfirst_node(RestrictInfo, l);
409 :
410 361696 : if (IsTidRangeClause(rinfo, rel))
411 232 : rlst = lappend(rlst, rinfo);
412 : }
413 :
414 361718 : return rlst;
415 : }
416 :
417 : /*
418 : * Given a list of join clauses involving our rel, create a parameterized
419 : * TidPath for each one that is a suitable TidEqual clause.
420 : *
421 : * In principle we could combine clauses that reference the same outer rels,
422 : * but it doesn't seem like such cases would arise often enough to be worth
423 : * troubling over.
424 : */
425 : static void
426 476928 : BuildParameterizedTidPaths(PlannerInfo *root, RelOptInfo *rel, List *clauses)
427 : {
428 : ListCell *l;
429 :
430 587570 : foreach(l, clauses)
431 : {
432 110642 : RestrictInfo *rinfo = lfirst_node(RestrictInfo, l);
433 : List *tidquals;
434 : Relids required_outer;
435 :
436 : /*
437 : * Validate whether each clause is actually usable; we must check this
438 : * even when examining clauses generated from an EquivalenceClass,
439 : * since they might not satisfy the restriction on not having Vars of
440 : * our rel on the other side, or somebody might've built an operator
441 : * class that accepts type "tid" but has other operators in it.
442 : *
443 : * We currently consider only TidEqual join clauses. In principle we
444 : * might find a suitable ScalarArrayOpExpr in the rel's joininfo list,
445 : * but it seems unlikely to be worth expending the cycles to check.
446 : * And we definitely won't find a CurrentOfExpr here. Hence, we don't
447 : * use RestrictInfoIsTidQual; but this must match that function
448 : * otherwise.
449 : */
450 110642 : if (rinfo->pseudoconstant ||
451 101738 : !restriction_is_securely_promotable(rinfo, rel) ||
452 101738 : !IsTidEqualClause(rinfo, rel))
453 110498 : continue;
454 :
455 : /*
456 : * Check if clause can be moved to this rel; this is probably
457 : * redundant when considering EC-derived clauses, but we must check it
458 : * for "loose" join clauses.
459 : */
460 156 : if (!join_clause_is_movable_to(rinfo, rel))
461 12 : continue;
462 :
463 : /* OK, make list of clauses for this path */
464 144 : tidquals = list_make1(rinfo);
465 :
466 : /* Compute required outer rels for this path */
467 144 : required_outer = bms_union(rinfo->required_relids, rel->lateral_relids);
468 144 : required_outer = bms_del_member(required_outer, rel->relid);
469 :
470 144 : add_path(rel, (Path *) create_tidscan_path(root, rel, tidquals,
471 : required_outer));
472 : }
473 476928 : }
474 :
475 : /*
476 : * Test whether an EquivalenceClass member matches our rel's CTID Var.
477 : *
478 : * This is a callback for use by generate_implied_equalities_for_column.
479 : */
480 : static bool
481 104050 : ec_member_matches_ctid(PlannerInfo *root, RelOptInfo *rel,
482 : EquivalenceClass *ec, EquivalenceMember *em,
483 : void *arg)
484 : {
485 204278 : if (em->em_expr && IsA(em->em_expr, Var) &&
486 100228 : IsCTIDVar((Var *) em->em_expr, rel))
487 132 : return true;
488 103918 : return false;
489 : }
490 :
491 : /*
492 : * create_tidscan_paths
493 : * Create paths corresponding to direct TID scans of the given rel.
494 : *
495 : * Candidate paths are added to the rel's pathlist (using add_path).
496 : */
497 : bool
498 362122 : create_tidscan_paths(PlannerInfo *root, RelOptInfo *rel)
499 : {
500 : List *tidquals;
501 : List *tidrangequals;
502 : bool isCurrentOf;
503 :
504 : /*
505 : * If any suitable quals exist in the rel's baserestrict list, generate a
506 : * plain (unparameterized) TidPath with them.
507 : *
508 : * We skip this when enable_tidscan = false, except when the qual is
509 : * CurrentOfExpr. In that case, a TID scan is the only correct path.
510 : */
511 362122 : tidquals = TidQualFromRestrictInfoList(root, rel->baserestrictinfo, rel,
512 : &isCurrentOf);
513 :
514 362122 : if (tidquals != NIL && (enable_tidscan || isCurrentOf))
515 : {
516 : /*
517 : * This path uses no join clauses, but it could still have required
518 : * parameterization due to LATERAL refs in its tlist.
519 : */
520 648 : Relids required_outer = rel->lateral_relids;
521 :
522 648 : add_path(rel, (Path *) create_tidscan_path(root, rel, tidquals,
523 : required_outer));
524 :
525 : /*
526 : * When the qual is CurrentOfExpr, the path that we just added is the
527 : * only one the executor can handle, so we should return before adding
528 : * any others. Returning true lets the caller know not to add any
529 : * others, either.
530 : */
531 648 : if (isCurrentOf)
532 404 : return true;
533 : }
534 :
535 : /* Skip the rest if TID scans are disabled. */
536 361718 : if (!enable_tidscan)
537 0 : return false;
538 :
539 : /*
540 : * If there are range quals in the baserestrict list, generate a
541 : * TidRangePath.
542 : */
543 361718 : tidrangequals = TidRangeQualFromRestrictInfoList(rel->baserestrictinfo,
544 : rel);
545 :
546 361718 : if (tidrangequals != NIL)
547 : {
548 : /*
549 : * This path uses no join clauses, but it could still have required
550 : * parameterization due to LATERAL refs in its tlist.
551 : */
552 202 : Relids required_outer = rel->lateral_relids;
553 :
554 202 : add_path(rel, (Path *) create_tidrangescan_path(root, rel,
555 : tidrangequals,
556 : required_outer));
557 : }
558 :
559 : /*
560 : * Try to generate parameterized TidPaths using equality clauses extracted
561 : * from EquivalenceClasses. (This is important since simple "t1.ctid =
562 : * t2.ctid" clauses will turn into ECs.)
563 : */
564 361718 : if (rel->has_eclass_joins)
565 : {
566 : List *clauses;
567 :
568 : /* Generate clauses, skipping any that join to lateral_referencers */
569 115210 : clauses = generate_implied_equalities_for_column(root,
570 : rel,
571 : ec_member_matches_ctid,
572 : NULL,
573 : rel->lateral_referencers);
574 :
575 : /* Generate a path for each usable join clause */
576 115210 : BuildParameterizedTidPaths(root, rel, clauses);
577 : }
578 :
579 : /*
580 : * Also consider parameterized TidPaths using "loose" join quals. Quals
581 : * of the form "t1.ctid = t2.ctid" would turn into these if they are outer
582 : * join quals, for example.
583 : */
584 361718 : BuildParameterizedTidPaths(root, rel, rel->joininfo);
585 :
586 361718 : return false;
587 : }
|