diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/optimizer/path/costsize.c | 9 | ||||
-rw-r--r-- | src/backend/optimizer/path/tidpath.c | 394 | ||||
-rw-r--r-- | src/backend/optimizer/plan/createplan.c | 67 | ||||
-rw-r--r-- | src/backend/optimizer/util/pathnode.c | 6 | ||||
-rw-r--r-- | src/include/nodes/plannodes.h | 3 | ||||
-rw-r--r-- | src/include/nodes/relation.h | 4 | ||||
-rw-r--r-- | src/test/regress/expected/tidscan.out | 55 | ||||
-rw-r--r-- | src/test/regress/sql/tidscan.sql | 17 |
8 files changed, 415 insertions, 140 deletions
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index 480fd250e93..88780c0e693 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -1202,15 +1202,18 @@ cost_tidscan(Path *path, PlannerInfo *root, ntuples = 0; foreach(l, tidquals) { - if (IsA(lfirst(l), ScalarArrayOpExpr)) + RestrictInfo *rinfo = lfirst_node(RestrictInfo, l); + Expr *qual = rinfo->clause; + + if (IsA(qual, ScalarArrayOpExpr)) { /* Each element of the array yields 1 tuple */ - ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) lfirst(l); + ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) qual; Node *arraynode = (Node *) lsecond(saop->args); ntuples += estimate_array_length(arraynode); } - else if (IsA(lfirst(l), CurrentOfExpr)) + else if (IsA(qual, CurrentOfExpr)) { /* CURRENT OF yields 1 tuple */ isCurrentOf = true; diff --git a/src/backend/optimizer/path/tidpath.c b/src/backend/optimizer/path/tidpath.c index 3bb5b8def60..335de0a4c1d 100644 --- a/src/backend/optimizer/path/tidpath.c +++ b/src/backend/optimizer/path/tidpath.c @@ -12,18 +12,17 @@ * this allows * WHERE ctid IN (tid1, tid2, ...) * + * As with indexscans, our definition of "pseudoconstant" is pretty liberal: + * we allow anything that doesn't involve a volatile function or a Var of + * the relation under consideration. Vars belonging to other relations of + * the query are allowed, giving rise to parameterized TID scans. + * * We also support "WHERE CURRENT OF cursor" conditions (CurrentOfExpr), * which amount to "CTID = run-time-determined-TID". These could in * theory be translated to a simple comparison of CTID to the result of * a function, but in practice it works better to keep the special node * representation all the way through to execution. * - * There is currently no special support for joins involving CTID; in - * particular nothing corresponding to best_inner_indexscan(). Since it's - * not very useful to store TIDs of one table in another table, there - * doesn't seem to be enough use-case to justify adding a lot of code - * for that. - * * * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California @@ -44,82 +43,97 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/restrictinfo.h" +#include "optimizer/var.h" -static bool IsTidEqualClause(OpExpr *node, int varno); -static bool IsTidEqualAnyClause(ScalarArrayOpExpr *node, int varno); -static List *TidQualFromExpr(Node *expr, int varno); -static List *TidQualFromBaseRestrictinfo(RelOptInfo *rel); - +/* + * Does this Var represent the CTID column of the specified baserel? + */ +static inline bool +IsCTIDVar(Var *var, RelOptInfo *rel) +{ + /* The vartype check is strictly paranoia */ + if (var->varattno == SelfItemPointerAttributeNumber && + var->vartype == TIDOID && + var->varno == rel->relid && + var->varlevelsup == 0) + return true; + return false; +} /* - * Check to see if an opclause is of the form + * Check to see if a RestrictInfo is of the form * CTID = pseudoconstant * or * pseudoconstant = CTID - * - * We check that the CTID Var belongs to relation "varno". That is probably - * redundant considering this is only applied to restriction clauses, but - * let's be safe. + * where the CTID Var belongs to relation "rel", and nothing on the + * other side of the clause does. */ static bool -IsTidEqualClause(OpExpr *node, int varno) +IsTidEqualClause(RestrictInfo *rinfo, RelOptInfo *rel) { + OpExpr *node; Node *arg1, *arg2, *other; - Var *var; + Relids other_relids; + + /* Must be an OpExpr */ + if (!is_opclause(rinfo->clause)) + return false; + node = (OpExpr *) rinfo->clause; /* Operator must be tideq */ if (node->opno != TIDEqualOperator) return false; - if (list_length(node->args) != 2) - return false; + Assert(list_length(node->args) == 2); arg1 = linitial(node->args); arg2 = lsecond(node->args); /* Look for CTID as either argument */ other = NULL; - if (arg1 && IsA(arg1, Var)) + other_relids = NULL; + if (arg1 && IsA(arg1, Var) && + IsCTIDVar((Var *) arg1, rel)) { - var = (Var *) arg1; - if (var->varattno == SelfItemPointerAttributeNumber && - var->vartype == TIDOID && - var->varno == varno && - var->varlevelsup == 0) - other = arg2; + other = arg2; + other_relids = rinfo->right_relids; } - if (!other && arg2 && IsA(arg2, Var)) + if (!other && arg2 && IsA(arg2, Var) && + IsCTIDVar((Var *) arg2, rel)) { - var = (Var *) arg2; - if (var->varattno == SelfItemPointerAttributeNumber && - var->vartype == TIDOID && - var->varno == varno && - var->varlevelsup == 0) - other = arg1; + other = arg1; + other_relids = rinfo->left_relids; } if (!other) return false; - if (exprType(other) != TIDOID) - return false; /* probably can't happen */ /* The other argument must be a pseudoconstant */ - if (!is_pseudo_constant_clause(other)) + if (bms_is_member(rel->relid, other_relids) || + contain_volatile_functions(other)) return false; return true; /* success */ } /* - * Check to see if a clause is of the form + * Check to see if a RestrictInfo is of the form * CTID = ANY (pseudoconstant_array) + * where the CTID Var belongs to relation "rel", and nothing on the + * other side of the clause does. */ static bool -IsTidEqualAnyClause(ScalarArrayOpExpr *node, int varno) +IsTidEqualAnyClause(RestrictInfo *rinfo, RelOptInfo *rel) { + ScalarArrayOpExpr *node; Node *arg1, *arg2; + /* Must be a ScalarArrayOpExpr */ + if (!(rinfo->clause && IsA(rinfo->clause, ScalarArrayOpExpr))) + return false; + node = (ScalarArrayOpExpr *) rinfo->clause; + /* Operator must be tideq */ if (node->opno != TIDEqualOperator) return false; @@ -130,117 +144,230 @@ IsTidEqualAnyClause(ScalarArrayOpExpr *node, int varno) arg2 = lsecond(node->args); /* CTID must be first argument */ - if (arg1 && IsA(arg1, Var)) + if (arg1 && IsA(arg1, Var) && + IsCTIDVar((Var *) arg1, rel)) { - Var *var = (Var *) arg1; + /* The other argument must be a pseudoconstant */ + if (bms_is_member(rel->relid, pull_varnos(arg2)) || + contain_volatile_functions(arg2)) + return false; - if (var->varattno == SelfItemPointerAttributeNumber && - var->vartype == TIDOID && - var->varno == varno && - var->varlevelsup == 0) - { - /* The other argument must be a pseudoconstant */ - if (is_pseudo_constant_clause(arg2)) - return true; /* success */ - } + return true; /* success */ } return false; } /* - * Extract a set of CTID conditions from the given qual expression + * Check to see if a RestrictInfo is a CurrentOfExpr referencing "rel". + */ +static bool +IsCurrentOfClause(RestrictInfo *rinfo, RelOptInfo *rel) +{ + CurrentOfExpr *node; + + /* Must be a CurrentOfExpr */ + if (!(rinfo->clause && IsA(rinfo->clause, CurrentOfExpr))) + return false; + node = (CurrentOfExpr *) rinfo->clause; + + /* If it references this rel, we're good */ + if (node->cvarno == rel->relid) + return true; + + return false; +} + +/* + * Extract a set of CTID conditions from the given RestrictInfo + * + * Returns a List of CTID qual RestrictInfos for the specified rel (with + * implicit OR semantics across the list), or NIL if there are no usable + * conditions. * - * Returns a List of CTID qual expressions (with implicit OR semantics - * across the list), or NIL if there are no usable conditions. + * This function considers only base cases; AND/OR combination is handled + * below. Therefore the returned List never has more than one element. + * (Using a List may seem a bit weird, but it simplifies the caller.) + */ +static List * +TidQualFromRestrictInfo(RestrictInfo *rinfo, RelOptInfo *rel) +{ + /* + * We may ignore pseudoconstant clauses (they can't contain Vars, so could + * not match anyway). + */ + if (rinfo->pseudoconstant) + return NIL; + + /* + * If clause must wait till after some lower-security-level restriction + * clause, reject it. + */ + if (!restriction_is_securely_promotable(rinfo, rel)) + return NIL; + + /* + * Check all base cases. If we get a match, return the clause. + */ + if (IsTidEqualClause(rinfo, rel) || + IsTidEqualAnyClause(rinfo, rel) || + IsCurrentOfClause(rinfo, rel)) + return list_make1(rinfo); + + return NIL; +} + +/* + * Extract a set of CTID conditions from implicit-AND List of RestrictInfos * - * If the expression is an AND clause, we can use a CTID condition - * from any sub-clause. If it is an OR clause, we must be able to - * extract a CTID condition from every sub-clause, or we can't use it. + * Returns a List of CTID qual RestrictInfos for the specified rel (with + * implicit OR semantics across the list), or NIL if there are no usable + * conditions. * - * In theory, in the AND case we could get CTID conditions from different - * sub-clauses, in which case we could try to pick the most efficient one. - * In practice, such usage seems very unlikely, so we don't bother; we - * just exit as soon as we find the first candidate. + * This function is just concerned with handling AND/OR recursion. */ static List * -TidQualFromExpr(Node *expr, int varno) +TidQualFromRestrictInfoList(List *rlist, RelOptInfo *rel) { List *rlst = NIL; ListCell *l; - if (is_opclause(expr)) - { - /* base case: check for tideq opclause */ - if (IsTidEqualClause((OpExpr *) expr, varno)) - rlst = list_make1(expr); - } - else if (expr && IsA(expr, ScalarArrayOpExpr)) - { - /* another base case: check for tid = ANY clause */ - if (IsTidEqualAnyClause((ScalarArrayOpExpr *) expr, varno)) - rlst = list_make1(expr); - } - else if (expr && IsA(expr, CurrentOfExpr)) - { - /* another base case: check for CURRENT OF on this rel */ - if (((CurrentOfExpr *) expr)->cvarno == varno) - rlst = list_make1(expr); - } - else if (and_clause(expr)) - { - foreach(l, ((BoolExpr *) expr)->args) - { - rlst = TidQualFromExpr((Node *) lfirst(l), varno); - if (rlst) - break; - } - } - else if (or_clause(expr)) + foreach(l, rlist) { - foreach(l, ((BoolExpr *) expr)->args) + RestrictInfo *rinfo = lfirst_node(RestrictInfo, l); + + if (restriction_is_or_clause(rinfo)) { - List *frtn = TidQualFromExpr((Node *) lfirst(l), varno); + ListCell *j; - if (frtn) - rlst = list_concat(rlst, frtn); - else + /* + * We must be able to extract a CTID condition from every + * sub-clause of an OR, or we can't use it. + */ + foreach(j, ((BoolExpr *) rinfo->orclause)->args) { - if (rlst) - list_free(rlst); - rlst = NIL; - break; + Node *orarg = (Node *) lfirst(j); + List *sublist; + + /* OR arguments should be ANDs or sub-RestrictInfos */ + if (and_clause(orarg)) + { + List *andargs = ((BoolExpr *) orarg)->args; + + /* Recurse in case there are sub-ORs */ + sublist = TidQualFromRestrictInfoList(andargs, rel); + } + else + { + RestrictInfo *rinfo = castNode(RestrictInfo, orarg); + + Assert(!restriction_is_or_clause(rinfo)); + sublist = TidQualFromRestrictInfo(rinfo, rel); + } + + /* + * If nothing found in this arm, we can't do anything with + * this OR clause. + */ + if (sublist == NIL) + { + rlst = NIL; /* forget anything we had */ + break; /* out of loop over OR args */ + } + + /* + * OK, continue constructing implicitly-OR'ed result list. + */ + rlst = list_concat(rlst, sublist); } } + else + { + /* Not an OR clause, so handle base cases */ + rlst = TidQualFromRestrictInfo(rinfo, rel); + } + + /* + * Stop as soon as we find any usable CTID condition. In theory we + * could get CTID equality conditions from different AND'ed clauses, + * in which case we could try to pick the most efficient one. In + * practice, such usage seems very unlikely, so we don't bother; we + * just exit as soon as we find the first candidate. + */ + if (rlst) + break; } + return rlst; } /* - * Extract a set of CTID conditions from the rel's baserestrictinfo list + * Given a list of join clauses involving our rel, create a parameterized + * TidPath for each one that is a suitable TidEqual clause. + * + * In principle we could combine clauses that reference the same outer rels, + * but it doesn't seem like such cases would arise often enough to be worth + * troubling over. */ -static List * -TidQualFromBaseRestrictinfo(RelOptInfo *rel) +static void +BuildParameterizedTidPaths(PlannerInfo *root, RelOptInfo *rel, List *clauses) { - List *rlst = NIL; ListCell *l; - foreach(l, rel->baserestrictinfo) + foreach(l, clauses) { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); + RestrictInfo *rinfo = lfirst_node(RestrictInfo, l); + List *tidquals; + Relids required_outer; /* - * If clause must wait till after some lower-security-level - * restriction clause, reject it. + * Validate whether each clause is actually usable; we must check this + * even when examining clauses generated from an EquivalenceClass, + * since they might not satisfy the restriction on not having Vars of + * our rel on the other side, or somebody might've built an operator + * class that accepts type "tid" but has other operators in it. + * + * We currently consider only TidEqual join clauses. In principle we + * might find a suitable ScalarArrayOpExpr in the rel's joininfo list, + * but it seems unlikely to be worth checking for. */ - if (!restriction_is_securely_promotable(rinfo, rel)) + if (!IsTidEqualClause(rinfo, rel)) continue; - rlst = TidQualFromExpr((Node *) rinfo->clause, rel->relid); - if (rlst) - break; + /* + * Check if clause can be moved to this rel; this is probably + * redundant when considering EC-derived clauses, but we must check it + * for "loose" join clauses. + */ + if (!join_clause_is_movable_to(rinfo, rel)) + continue; + + /* OK, make list of clauses for this path */ + tidquals = list_make1(rinfo); + + /* Compute required outer rels for this path */ + required_outer = bms_union(rinfo->required_relids, rel->lateral_relids); + required_outer = bms_del_member(required_outer, rel->relid); + + add_path(rel, (Path *) create_tidscan_path(root, rel, tidquals, + required_outer)); } - return rlst; +} + +/* + * Test whether an EquivalenceClass member matches our rel's CTID Var. + * + * This is a callback for use by generate_implied_equalities_for_column. + */ +static bool +ec_member_matches_ctid(PlannerInfo *root, RelOptInfo *rel, + EquivalenceClass *ec, EquivalenceMember *em, + void *arg) +{ + if (em->em_expr && IsA(em->em_expr, Var) && + IsCTIDVar((Var *) em->em_expr, rel)) + return true; + return false; } /* @@ -252,19 +379,50 @@ TidQualFromBaseRestrictinfo(RelOptInfo *rel) void create_tidscan_paths(PlannerInfo *root, RelOptInfo *rel) { - Relids required_outer; List *tidquals; /* - * We don't support pushing join clauses into the quals of a tidscan, but - * it could still have required parameterization due to LATERAL refs in - * its tlist. + * If any suitable quals exist in the rel's baserestrict list, generate a + * plain (unparameterized) TidPath with them. */ - required_outer = rel->lateral_relids; - - tidquals = TidQualFromBaseRestrictinfo(rel); + tidquals = TidQualFromRestrictInfoList(rel->baserestrictinfo, rel); if (tidquals) + { + /* + * This path uses no join clauses, but it could still have required + * parameterization due to LATERAL refs in its tlist. + */ + Relids required_outer = rel->lateral_relids; + add_path(rel, (Path *) create_tidscan_path(root, rel, tidquals, required_outer)); + } + + /* + * Try to generate parameterized TidPaths using equality clauses extracted + * from EquivalenceClasses. (This is important since simple "t1.ctid = + * t2.ctid" clauses will turn into ECs.) + */ + if (rel->has_eclass_joins) + { + List *clauses; + + /* Generate clauses, skipping any that join to lateral_referencers */ + clauses = generate_implied_equalities_for_column(root, + rel, + ec_member_matches_ctid, + NULL, + rel->lateral_referencers); + + /* Generate a path for each usable join clause */ + BuildParameterizedTidPaths(root, rel, clauses); + } + + /* + * Also consider parameterized TidPaths using "loose" join quals. Quals + * of the form "t1.ctid = t2.ctid" would turn into these if they are outer + * join quals, for example. + */ + BuildParameterizedTidPaths(root, rel, rel->joininfo); } diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 91cf78233d5..9e918793271 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -3083,18 +3083,72 @@ create_tidscan_plan(PlannerInfo *root, TidPath *best_path, TidScan *scan_plan; Index scan_relid = best_path->path.parent->relid; List *tidquals = best_path->tidquals; - List *ortidquals; /* it should be a base rel... */ Assert(scan_relid > 0); Assert(best_path->path.parent->rtekind == RTE_RELATION); + /* + * The qpqual list must contain all restrictions not enforced by the + * tidquals list. Since tidquals has OR semantics, we have to be careful + * about matching it up to scan_clauses. It's convenient to handle the + * single-tidqual case separately from the multiple-tidqual case. In the + * single-tidqual case, we look through the scan_clauses while they are + * still in RestrictInfo form, and drop any that are redundant with the + * tidqual. + * + * In normal cases simple pointer equality checks will be enough to spot + * duplicate RestrictInfos, so we try that first. + * + * Another common case is that a scan_clauses entry is generated from the + * same EquivalenceClass as some tidqual, and is therefore redundant with + * it, though not equal. + * + * Unlike indexpaths, we don't bother with predicate_implied_by(); the + * number of cases where it could win are pretty small. + */ + if (list_length(tidquals) == 1) + { + List *qpqual = NIL; + ListCell *l; + + foreach(l, scan_clauses) + { + RestrictInfo *rinfo = lfirst_node(RestrictInfo, l); + + if (rinfo->pseudoconstant) + continue; /* we may drop pseudoconstants here */ + if (list_member_ptr(tidquals, rinfo)) + continue; /* simple duplicate */ + if (is_redundant_derived_clause(rinfo, tidquals)) + continue; /* derived from same EquivalenceClass */ + qpqual = lappend(qpqual, rinfo); + } + scan_clauses = qpqual; + } + /* Sort clauses into best execution order */ scan_clauses = order_qual_clauses(root, scan_clauses); - /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */ + /* Reduce RestrictInfo lists to bare expressions; ignore pseudoconstants */ + tidquals = extract_actual_clauses(tidquals, false); scan_clauses = extract_actual_clauses(scan_clauses, false); + /* + * If we have multiple tidquals, it's more convenient to remove duplicate + * scan_clauses after stripping the RestrictInfos. In this situation, + * because the tidquals represent OR sub-clauses, they could not have come + * from EquivalenceClasses so we don't have to worry about matching up + * non-identical clauses. On the other hand, because tidpath.c will have + * extracted those sub-clauses from some OR clause and built its own list, + * we will certainly not have pointer equality to any scan clause. So + * convert the tidquals list to an explicit OR clause and see if we can + * match it via equal() to any scan clause. + */ + if (list_length(tidquals) > 1) + scan_clauses = list_difference(scan_clauses, + list_make1(make_orclause(tidquals))); + /* Replace any outer-relation variables with nestloop params */ if (best_path->path.param_info) { @@ -3104,15 +3158,6 @@ create_tidscan_plan(PlannerInfo *root, TidPath *best_path, replace_nestloop_params(root, (Node *) scan_clauses); } - /* - * Remove any clauses that are TID quals. This is a bit tricky since the - * tidquals list has implicit OR semantics. - */ - ortidquals = tidquals; - if (list_length(ortidquals) > 1) - ortidquals = list_make1(make_orclause(ortidquals)); - scan_clauses = list_difference(scan_clauses, ortidquals); - scan_plan = make_tidscan(tlist, scan_clauses, scan_relid, diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index d50d86b252f..81cc9cb31bf 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -3715,12 +3715,8 @@ do { \ { TidPath *tpath; - /* - * TidPath contains tidquals, which do not contain any - * external parameters per create_tidscan_path(). So don't - * bother to translate those. - */ FLAT_COPY_PATH(tpath, path, TidPath); + ADJUST_CHILD_ATTRS(tpath->tidquals); new_path = (Path *) tpath; } break; diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index f116bc23ffe..b7d33fcf48b 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -478,7 +478,8 @@ typedef struct BitmapHeapScan * tid scan node * * tidquals is an implicitly OR'ed list of qual expressions of the form - * "CTID = pseudoconstant" or "CTID = ANY(pseudoconstant_array)". + * "CTID = pseudoconstant", or "CTID = ANY(pseudoconstant_array)", + * or a CurrentOfExpr for the relation. * ---------------- */ typedef struct TidScan diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 6fd24203dd6..db3fb214712 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -1229,8 +1229,8 @@ typedef struct BitmapOrPath * TidPath represents a scan by TID * * tidquals is an implicitly OR'ed list of qual expressions of the form - * "CTID = pseudoconstant" or "CTID = ANY(pseudoconstant_array)". - * Note they are bare expressions, not RestrictInfos. + * "CTID = pseudoconstant", or "CTID = ANY(pseudoconstant_array)", + * or a CurrentOfExpr for the relation. */ typedef struct TidPath { diff --git a/src/test/regress/expected/tidscan.out b/src/test/regress/expected/tidscan.out index 521ed1b2f99..8f15c04dad1 100644 --- a/src/test/regress/expected/tidscan.out +++ b/src/test/regress/expected/tidscan.out @@ -40,6 +40,22 @@ SELECT ctid, * FROM tidscan WHERE '(0,1)' = ctid; (0,1) | 1 (1 row) +-- OR'd clauses +EXPLAIN (COSTS OFF) +SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid; + QUERY PLAN +-------------------------------------------------------------- + Tid Scan on tidscan + TID Cond: ((ctid = '(0,2)'::tid) OR ('(0,1)'::tid = ctid)) +(2 rows) + +SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid; + ctid | id +-------+---- + (0,1) | 1 + (0,2) | 2 +(2 rows) + -- ctid = ScalarArrayOp - implemented as tidscan EXPLAIN (COSTS OFF) SELECT ctid, * FROM tidscan WHERE ctid = ANY(ARRAY['(0,1)', '(0,2)']::tid[]); @@ -92,6 +108,45 @@ WHERE (id = 3 AND ctid IN ('(0,2)', '(0,3)')) OR (ctid = '(0,1)' AND id = 1); (0,3) | 3 (2 rows) +-- nestloop-with-inner-tidscan joins on tid +EXPLAIN (COSTS OFF) +SELECT t1.ctid, t1.*, t2.ctid, t2.* +FROM tidscan t1 JOIN tidscan t2 ON t1.ctid = t2.ctid WHERE t1.id = 1; + QUERY PLAN +------------------------------------ + Nested Loop + -> Seq Scan on tidscan t1 + Filter: (id = 1) + -> Tid Scan on tidscan t2 + TID Cond: (ctid = t1.ctid) +(5 rows) + +SELECT t1.ctid, t1.*, t2.ctid, t2.* +FROM tidscan t1 JOIN tidscan t2 ON t1.ctid = t2.ctid WHERE t1.id = 1; + ctid | id | ctid | id +-------+----+-------+---- + (0,1) | 1 | (0,1) | 1 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT t1.ctid, t1.*, t2.ctid, t2.* +FROM tidscan t1 LEFT JOIN tidscan t2 ON t1.ctid = t2.ctid WHERE t1.id = 1; + QUERY PLAN +------------------------------------ + Nested Loop Left Join + -> Seq Scan on tidscan t1 + Filter: (id = 1) + -> Tid Scan on tidscan t2 + TID Cond: (t1.ctid = ctid) +(5 rows) + +SELECT t1.ctid, t1.*, t2.ctid, t2.* +FROM tidscan t1 LEFT JOIN tidscan t2 ON t1.ctid = t2.ctid WHERE t1.id = 1; + ctid | id | ctid | id +-------+----+-------+---- + (0,1) | 1 | (0,1) | 1 +(1 row) + -- exercise backward scan and rewind BEGIN; DECLARE c CURSOR FOR diff --git a/src/test/regress/sql/tidscan.sql b/src/test/regress/sql/tidscan.sql index a8472e09acd..2d63aa067fe 100644 --- a/src/test/regress/sql/tidscan.sql +++ b/src/test/regress/sql/tidscan.sql @@ -17,6 +17,11 @@ EXPLAIN (COSTS OFF) SELECT ctid, * FROM tidscan WHERE '(0,1)' = ctid; SELECT ctid, * FROM tidscan WHERE '(0,1)' = ctid; +-- OR'd clauses +EXPLAIN (COSTS OFF) +SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid; +SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid; + -- ctid = ScalarArrayOp - implemented as tidscan EXPLAIN (COSTS OFF) SELECT ctid, * FROM tidscan WHERE ctid = ANY(ARRAY['(0,1)', '(0,2)']::tid[]); @@ -34,6 +39,18 @@ WHERE (id = 3 AND ctid IN ('(0,2)', '(0,3)')) OR (ctid = '(0,1)' AND id = 1); SELECT ctid, * FROM tidscan WHERE (id = 3 AND ctid IN ('(0,2)', '(0,3)')) OR (ctid = '(0,1)' AND id = 1); +-- nestloop-with-inner-tidscan joins on tid +EXPLAIN (COSTS OFF) +SELECT t1.ctid, t1.*, t2.ctid, t2.* +FROM tidscan t1 JOIN tidscan t2 ON t1.ctid = t2.ctid WHERE t1.id = 1; +SELECT t1.ctid, t1.*, t2.ctid, t2.* +FROM tidscan t1 JOIN tidscan t2 ON t1.ctid = t2.ctid WHERE t1.id = 1; +EXPLAIN (COSTS OFF) +SELECT t1.ctid, t1.*, t2.ctid, t2.* +FROM tidscan t1 LEFT JOIN tidscan t2 ON t1.ctid = t2.ctid WHERE t1.id = 1; +SELECT t1.ctid, t1.*, t2.ctid, t2.* +FROM tidscan t1 LEFT JOIN tidscan t2 ON t1.ctid = t2.ctid WHERE t1.id = 1; + -- exercise backward scan and rewind BEGIN; DECLARE c CURSOR FOR |