Support Gather node and related properties.
\echo ###### text format output test
SELECT '### '||'TEXT-short '||title||E'\n'||
pg_store_plans_textplan(splan)
- FROM plans ORDER BY id;
+ FROM plans WHERE id >= 0 ORDER BY id;
+
\echo ###### long-json-as-a-source test
SELECT '### '||'yaml-long JSON '||title||E'\n'||
"Node Type": "SetOp",
"Node Type": "LockRows",
"Node Type": "Limit",
+ "Node Type": "Gather",
"Parent Relationship": "Outer",
"Parent Relationship": "Inner",
"Parent Relationship": "Subquery",
"Group Key": "a",
"Grouping Sets": "a",
"Group Keys": "a",
+ "Parallel Aware": "true",
+ "Workers Planned": "0",
+ "Workers Launched": "0",
+ "Workers": "x",
+ "Worker Number": "0",
"Filter": "a",
"Join Filter": "a",
"Hash Cond": "a",
explain (analyze on, buffers on, verbose on, format :format)
SELECT * FROM tt1 TABLESAMPLE system(1) REPEATABLE (1);
+\echo ###### Parallel
+create table lt1 (a int, b text);
+alter table lt1 alter column b set storage plain;
+insert into lt1 (select a, repeat('x', 1000) from generate_series(0, 99999) a);
+set max_parallel_worders_per_gather to 2;
+set parallel_tuple_cost to 0;
+set parallel_setup_cost to 0;
+explain (analyze on, buffers on, verbose on, format :format)
+ SELECT * FROM lt1;
+
-- BitmapAnd/Inner/Right/ForegnScan
{P_GroupKey, "-" ,"Group Key", NULL, true, NULL, SETTER(group_key)},
{P_GroupSets, "=" ,"Grouping Sets", NULL, true, NULL, NULL},
{P_GroupKeys, "\\" ,"Group Keys", NULL, true, NULL, SETTER(group_key)},
+ {P_Parallel, "`" ,"Parallel Aware", NULL, true, NULL, SETTER(parallel_aware)},
+ {P_WorkersPlanned, "{" ,"Workers Planned", NULL, true, NULL, SETTER(workers_planned)},
+ {P_WorkersLaunched, "}" ,"Workers Launched", NULL, true, NULL, SETTER(workers_launched)},
/* Values of these properties are ignored on normalization */
{P_FunctionCall, "y" ,"Function Call", NULL, false, NULL, SETTER(func_call)},
{P_SamplingMethod, "" ,"Sampling Method" , NULL, false, NULL, SETTER(sampling_method)},
{P_SamplingParams, "" ,"Sampling Parameters" , NULL, false, NULL, SETTER(sampling_params)},
{P_RepeatableSeed, "" ,"Repeatable Seed" , NULL, false, NULL, SETTER(repeatable_seed)},
+ {P_Workers, "[" ,"Workers", NULL, false, NULL, NULL},
+ {P_WorkerNumber, "]" ,"Worker Number", NULL, false, NULL, SETTER(worker_number)},
+
{P_Invalid, NULL, NULL, NULL, false, NULL, NULL}
};
{T_SetOp, "3" ,"SetOp", NULL, false, NULL, NULL},
{T_LockRows, "4" ,"LockRows", NULL, false, NULL, NULL},
{T_Limit, "5" ,"Limit", NULL, false, NULL, NULL},
+#if PG_VERSION_NUM >= 90600
+ {T_Gather, "6" ,"Gather", NULL, false, NULL, NULL},
+#endif
{T_Invalid, NULL, NULL, NULL, false, NULL, NULL}
};
P_TriggerName,
P_TrgRelation,
P_ConstraintName,
+ P_Parallel,
+ P_WorkersPlanned,
P_FunctionCall,
P_StartupCost,
P_ConfTuples,
P_SamplingMethod,
P_SamplingParams,
- P_RepeatableSeed
+ P_RepeatableSeed,
+ P_Workers,
+ P_WorkersLaunched,
+ P_WorkerNumber
} pgsp_prop_tags;
typedef struct
CONVERSION_SETTER(sort_method, conv_sortmethod);
LIST_SETTER(sort_key);
LIST_SETTER(group_key);
+BOOL_SETTER(parallel_aware);
SQLQUOTE_SETTER(index_name);
DEFAULT_SETTER(startup_cost);
DEFAULT_SETTER(total_cost);
DEFAULT_SETTER(sampling_method);
LIST_SETTER(sampling_params);
DEFAULT_SETTER(repeatable_seed);
+DEFAULT_SETTER(worker_number);
+DEFAULT_SETTER(workers_planned);
+DEFAULT_SETTER(workers_launched);
#define ISZERO(s) (!s || strcmp(s, "0") == 0 || strcmp(s, "0.000") == 0 )
#define HASSTRING(s) (s && strlen(s) > 0)
bool comma = false;
int exind = 0;
- if (v->node_type == T_Invalid)
+ /*
+ * The element objects in "Workers" list doesn't have node type, which
+ * would be named T_Worker if there were in node.h. So it needs a special
+ * treat.
+ */
+
+ if (v->node_type == T_Invalid && !HASSTRING(v->worker_number))
return;
if (s->len > 0)
if (level > 1 && ctx->current_list == P_Invalid)
appendStringInfoString(s, "-> ");
+ if (v->parallel_aware)
+ appendStringInfoString(s, "Parallel ");
+
switch (v->nodetag)
{
case T_ModifyTable:
break;
default:
- appendStringInfoString(s, v->node_type);
+ /* Existence of worker_number suggests this is a Worker node */
+ if (HASSTRING(v->worker_number))
+ {
+ appendStringInfoString(s, "Worker");
+ print_prop_if_exists(s, " ", v->worker_number, 0, 0);
+
+ /*
+ * "Worker"s are individual JSON objects in a JSON list but
+ * should be printed as just a property in text
+ * representaion. Correct indent using exind here.
+ */
+ exind = -4;
+ }
+ else
+ appendStringInfoString(s, v->node_type);
break;
}
print_prop_if_exists(s, "Join Filter: " , v->join_filter, level, exind);
print_prop_if_exists(s, "Index Cond: " , v->index_cond, level, exind);
print_prop_if_exists(s, "Recheck Cond: ", v->recheck_cond, level, exind);
+ print_prop_if_exists(s, "Workers Planned: ", v->workers_planned, level, exind);
+ print_prop_if_exists(s, "Workers Launched: ", v->workers_launched, level, exind);
if (HASSTRING(v->sampling_method))
{
else
{
/*
- * Print node immediately if the next level of Plan/Plans comes. The
- * plan construct is tail-recursive so this doesn't harm.
+ * Print the current node immediately if the next level of
+ * Plan/Plans/Worers comes. This assumes that the plan output is
+ * strcutured tail-recursively.
*/
- if (p->tag == P_Plan || p->tag == P_Plans)
+ if (p->tag == P_Plan || p->tag == P_Plans || p->tag == P_Workers)
{
print_current_node(ctx);
clear_nodeval(ctx->nodevals);
v->tmp_schema_name = v->schema_name;
v->tmp_alias = v->alias;
}
- else if (p->tag == P_GroupSets)
+
+ if (p->tag == P_GroupSets || p->tag == P_Workers)
{
ctx->current_list = p->tag;
ctx->list_fname = fname;
* This paser prints partial result at the end of every P_Plan object,
* which includes elements in P_Plans list.
*/
- if (p->tag == P_Plan || p->tag == P_Plans)
+ if (p->tag == P_Plan || p->tag == P_Plans || p->tag == P_Workers)
ctx->plan_levels = bms_add_member(ctx->plan_levels, ctx->level);
else
ctx->plan_levels = bms_del_member(ctx->plan_levels, ctx->level);
const char *sampling_method;
StringInfo sampling_params;
const char *repeatable_seed;
+ bool parallel_aware;
+ const char *worker_number;
+ const char *workers_planned;
+ const char *workers_launched;
const char *tmp_obj_name;
const char *tmp_schema_name;
#define SQLQUOTE_SETTER(name) \
SETTERDECL(name) { vals->name = quote_identifier(val);}
+#define BOOL_SETTER(name) \
+ SETTERDECL(name) { vals->parallel_aware = (strcmp(val, "true") == 0 ? 1 : 0);}
+
#define LIST_SETTER(name) \
SETTERDECL(name) { \
if (!vals->name || !vals->name->data[0])\
SETTERDECL(sort_key);
SETTERDECL(group_key);
SETTERDECL(group_keys);
+SETTERDECL(parallel_aware);
SETTERDECL(index_name);
SETTERDECL(startup_cost);
SETTERDECL(total_cost);
SETTERDECL(sampling_method);
SETTERDECL(sampling_params);
SETTERDECL(repeatable_seed);
+SETTERDECL(worker_number);
+SETTERDECL(workers_planned);
+SETTERDECL(workers_launched);