PGPAQF_SEMIJOIN_(NON_)UNIQUE
authorRobert Haas <[email protected]>
Tue, 15 Jul 2025 17:13:53 +0000 (13:13 -0400)
committerRobert Haas <[email protected]>
Tue, 15 Jul 2025 17:13:53 +0000 (13:13 -0400)
contrib/pg_plan_advice/pgpa_join.c
contrib/pg_plan_advice/pgpa_output.c
contrib/pg_plan_advice/pgpa_walker.h

index d7a5c99ffde755f64f4792c01fbb7e163d073147..14c0a444da62522876c6e64d6d09ae827c0aa35a 100644 (file)
@@ -402,6 +402,7 @@ pgpa_decompose_join(pgpa_plan_walker_context *context, Plan *plan,
                                        ElidedNode **elidedrealouter, ElidedNode **elidedrealinner)
 {
        PlannedStmt *pstmt = context->pstmt;
+       JoinType        jointype = ((Join *) plan)->jointype;
        Plan       *outerplan = plan->lefttree;
        Plan       *innerplan = plan->righttree;
        ElidedNode *elidedouter;
@@ -517,16 +518,31 @@ pgpa_decompose_join(pgpa_plan_walker_context *context, Plan *plan,
                elidedinner = pgpa_descend_node(pstmt, &innerplan);
 
        /*
-        * If we descended through a unique node, make a note that the identified
-        * inner or outer subplan should be treated as a query plan feature when
-        * the main tree traversal reaches it. (We could designate the Agg or
-        * Unique node itself as the feature, but there shouldn't be any
-        * RTI-bearing nodes between that node and this one.)
+        * If this is a semijoin that was converted to an inner join by making
+        * one side or the other unique, make a note that the inner or outer
+        * subplan, as appropriate, should be treated as a query plan feature
+        * when the main tree traversal reaches it.
+        *
+        * Conversely, if the planner could have made one side of the join unique
+        * and thereby converted it to an inner join, and chose not to do so,
+        * that is also worth noting. (XXX: I'm not sure that we need to emit
+        * non-unique advice in every case where these join-type tests pass.)
+        *
+        * NB: This code could appear slightly higher up in in this function,
+        * but none of the nodes through which we just descended should be have
+        * associated RTIs.
+        *
+        * NB: This seems like a somewhat hacky way of passing information up
+        * to the main tree walk, but I don't currently have a better idea.
         */
        if (uniqueouter)
-               pgpa_add_future_feature(context, PGPAQF_SJ_UNIQUE, outerplan);
+               pgpa_add_future_feature(context, PGPAQF_SEMIJOIN_UNIQUE, outerplan);
+       else if (jointype == JOIN_RIGHT_SEMI)
+               pgpa_add_future_feature(context, PGPAQF_SEMIJOIN_NON_UNIQUE, outerplan);
        if (uniqueinner)
-               pgpa_add_future_feature(context, PGPAQF_SJ_UNIQUE, innerplan);
+               pgpa_add_future_feature(context, PGPAQF_SEMIJOIN_UNIQUE, innerplan);
+       else if (jointype == JOIN_SEMI)
+               pgpa_add_future_feature(context, PGPAQF_SEMIJOIN_NON_UNIQUE, innerplan);
 
        /* Set output parameters. */
        *realouter = outerplan;
index a8c121d9c8a88b372ace5e06afac2c726598fca3..69ca0c36f44a0183df806ee357dad04842af480c 100644 (file)
@@ -147,8 +147,11 @@ pgpa_output_advice(StringInfo buf, pgpa_plan_walker_context *walker,
                        case PGPAQF_GATHER_MERGE:
                                appendStringInfo(buf, "GATHER_MERGE(");
                                break;
-                       case PGPAQF_SJ_UNIQUE:
-                               appendStringInfo(buf, "SJ_UNIQUE(");
+                       case PGPAQF_SEMIJOIN_NON_UNIQUE:
+                               appendStringInfo(buf, "SEMIJOIN_NON_UNIQUE(");
+                               break;
+                       case PGPAQF_SEMIJOIN_UNIQUE:
+                               appendStringInfo(buf, "SEMIJOIN_UNIQUE(");
                                break;
                }
                pgpa_output_relations(&context, buf, qf->relids);
index 65a366ee06ebd0f92d557899dc531a38a204bac0..223d42d74612e5f6c82c133011bb1f3284b6363e 100644 (file)
  * For example, Gather nodes, desiginated by PGPAQF_GATHER, and Gather Merge
  * nodes, designated by PGPAQF_GATHER_MERGE, are query features, because we'll
  * want to admit some kind of advice that describes the portion of the plan
- * tree that appears beneath those nodes. Whenever we decide to implement a
- * semijoin by making one side unique and then performing a normal join, we
- * create a feature of PGPAQF_SG_UNIQUE, so that we can describe what RTIs need
- * to be made unique to perform the join. XXX We should also probably emit
- * advice for the decision NOT to unique-ify a semijoin.
+ * tree that appears beneath those nodes.
+ *
+ * Each semijoin can be implemented either by directly performing a semijoin,
+ * or by making one side unique and then performing a normal join. Either way,
+ * we use a query feature to notice what decision was made, so that we can
+ * describe it by enumerating the RTIs on that side of the join.
  *
  * To elaborate on the "no admixture of parent and child RTIs" rule, in all of
  * these cases, if the entirety of an inheritance hierarchy appears beneath
@@ -41,7 +42,8 @@ typedef enum pgpa_qf_type
 {
        PGPAQF_GATHER,
        PGPAQF_GATHER_MERGE,
-       PGPAQF_SJ_UNIQUE
+       PGPAQF_SEMIJOIN_NON_UNIQUE,
+       PGPAQF_SEMIJOIN_UNIQUE
 } pgpa_qf_type;
 
 /*