StringInfo buf;
List *unrolled_joins[NUM_PGPA_JOIN_STRATEGY];
List *clumped_joins[NUM_PGPA_CLUMP_JOIN_STRATEGY];
+ List *query_features[NUM_PGPA_QF_TYPES];
int wrap_column;
} pgpa_output_context;
static void pgpa_maybe_linebreak(StringInfo buf, int wrap_column);
+/*
+ * Append query advice to the provided buffer.
+ *
+ * Before calling this function, 'walker' must be used to iterate over the
+ * main plan tree and all subplans from the PlannedStmt.
+ *
+ * 'rt_identifiers' is a table of unique identifiers, one for each RTI.
+ * See pgpa_create_identifiers_for_planned_stmt().
+ *
+ * Results will be appended to 'buf'.
+ */
void
pgpa_output_advice(StringInfo buf, pgpa_plan_walker_context *walker,
const char **rt_identifiers)
context.buf = buf;
context.wrap_column = 79; /* XXX */
+ /*
+ * Put all the top-level clumped joins for each strategy into a single
+ * list.
+ */
+ foreach(lc, walker->clumped_joins)
+ {
+ pgpa_clumped_join *cjoin = lfirst(lc);
+
+ context.clumped_joins[cjoin->strategy] =
+ lappend(context.clumped_joins[cjoin->strategy], cjoin->relids);
+ }
+
+ /*
+ * Each piece of JOIN_ORDER() advice fully describes the join order for a
+ * a single unrolled join. Merging is not permitted, because that would
+ * change the meaning, e.g. SEQ_SCAN(a b c d) means simply that sequential
+ * scans should be used for all of those relations, and is thus equivalent
+ * to SEQ_SCAN(a b) SEQ_SCAN(c d), but JOIN_ORDER(a b c d) means that "a"
+ * is the driving table which is then joined to "b" then "c" then "d",
+ * which is totally different from JOIN_ORDER(a b) and JOIN_ORDER(c d).
+ *
+ * As a side effect, the calls to pgpa_output_unrolled_join() will
+ * add to the context.clumped_joins[] and context.unrolled_joins[] lists,
+ * which will be important for emitting join strategy advice further down.
+ */
foreach(lc, walker->unrolled_joins)
{
pgpa_unrolled_join *ujoin = lfirst(lc);
appendStringInfoChar(context.buf, ')');
}
- foreach(lc, walker->clumped_joins)
- {
- pgpa_clumped_join *cjoin = lfirst(lc);
-
- context.clumped_joins[cjoin->strategy] =
- lappend(context.clumped_joins[cjoin->strategy], cjoin->relids);
- }
-
+ /*
+ * Emit just one piece of advice for each unrolled-join strategy that is
+ * used at least once in the query.
+ */
for (int s = 0; s < NUM_PGPA_JOIN_STRATEGY; ++s)
{
char *strategy = pgpa_cstring_join_strategy(s);
appendStringInfoChar(buf, ')');
}
+ /*
+ * Emit just one piece of advice for each clumped-join strategy that is
+ * used at least once in the query.
+ *
+ * XXX. It's not entirely clear that emitting DEGENERATE() advice is
+ * useful at all, since there is no real decision to be made: you can't
+ * decide whether or not something is provably empty. On the other hand,
+ * it might be useful to human beings even if the computer doesn't need it.
+ * FOREIGN() and PARTITIONWISE() are clearly useful, but they are scan
+ * types as well as join types, and the question of how to handle that
+ * deserves more thought.
+ */
for (int c = 0; c < NUM_PGPA_CLUMP_JOIN_STRATEGY; ++c)
{
char *cstrategy = pgpa_cstring_join_clump_strategy(c);
appendStringInfoChar(buf, ')');
}
+ /* Sort query features into one list per query feature type. */
foreach(lc, walker->query_features)
{
pgpa_query_feature *qf = lfirst(lc);
+ context.query_features[qf->type] =
+ lappend(context.query_features[qf->type], qf);
+ }
+
+ /*
+ * Emit just one piece of advice for each type of query feature that is
+ * used at least once in the query.
+ */
+ for (int t = 0; t < NUM_PGPA_QF_TYPES; ++t)
+ {
+ bool first = true;
+
+ if (context.query_features[t] == NIL)
+ continue;
+
if (buf->len > 0)
appendStringInfoChar(buf, '\n');
- switch (qf->type)
+
+ switch (t)
{
case PGPAQF_GATHER:
appendStringInfo(buf, "GATHER(");
appendStringInfo(buf, "SEMIJOIN_UNIQUE(");
break;
}
- pgpa_output_relations(&context, buf, qf->relids);
+
+ foreach(lc, context.query_features[t])
+ {
+ pgpa_query_feature *qf = lfirst(lc);
+
+ if (first)
+ first = false;
+ else
+ {
+ pgpa_maybe_linebreak(context.buf, context.wrap_column);
+ appendStringInfoChar(context.buf, ' ');
+ }
+
+ if (bms_membership(qf->relids) == BMS_SINGLETON)
+ pgpa_output_relations(&context, buf, qf->relids);
+ else
+ {
+ appendStringInfoChar(buf, '(');
+ pgpa_output_relations(&context, buf, qf->relids);
+ appendStringInfoChar(buf, ')');
+ }
+ }
appendStringInfoChar(buf, ')');
}
}
+/*
+ * Output the members of an unrolled join, first the outermost member, and
+ * then the inner members one by one, as part of JOIN_ORDER() advice.
+ *
+ * As we visit members, we add to the context->unrolled_joins[] and
+ * context->clumped_joins[] lists. The former updates are done by this
+ * function directly, and the latter via pgpa_output_join_member.
+ */
static void
pgpa_output_unrolled_join(pgpa_output_context *context,
pgpa_unrolled_join *join)
}
}
+/*
+ * Output a single member of an unrolled join as part of JOIN_ORDER() advice.
+ *
+ * If that member happens to be a clumped join, also add it to the appropriate
+ * context->clump_joins[] list.
+ */
static void
pgpa_output_join_member(pgpa_output_context *context,
pgpa_join_member *member)