From 4aefb2de9b4a8d8fe28ee98cfeea1ca7293508f4 Mon Sep 17 00:00:00 2001 From: Yuzuko Hosoya <37567873+yuzupy@users.noreply.github.com> Date: Thu, 11 Jul 2019 00:43:43 +0900 Subject: [PATCH] Disable runtime partition pruning on hypothetical partitioned table Unfortunately, the runtime partition pruning code in v11 is written in such a way that it can't be made compatible with hypothetical partitioning. --- expected/hypo_table.out | 73 ++++++++++++++-- hypopg.c | 39 +++++++++ hypopg_table.c | 187 ++++++++++++++++++++++++++++++++++++++++ include/hypopg_table.h | 10 +++ test/sql/hypo_table.sql | 16 ++-- 5 files changed, 315 insertions(+), 10 deletions(-) diff --git a/expected/hypo_table.out b/expected/hypo_table.out index 1228962..3e7bb4a 100644 --- a/expected/hypo_table.out +++ b/expected/hypo_table.out @@ -1446,7 +1446,40 @@ EXPLAIN (COSTS OFF) WITH s AS (SELECT * FROM hypo_part_range WHERE id = hypo_num Filter: (id = hypo_number_one()) (9 rows) --- 6B.4 InitPlan +-- 6B.4 CTE with partitioning underneath an union +EXPLAIN (COSTS OFF) WITH s AS (SELECT 1 UNION ALL SELECT 2 FROM part_range WHERE id = hypo_number_one()) SELECT * FROM s; + QUERY PLAN +-------------------------------------------------------- + CTE Scan on s + CTE s + -> Append + -> Result + -> Append + -> Seq Scan on part_range_1_10000 + Filter: (id = hypo_number_one()) + -> Seq Scan on part_range_10000_20000 + Filter: (id = hypo_number_one()) + -> Seq Scan on part_range_20000_30000 + Filter: (id = hypo_number_one()) +(11 rows) + +EXPLAIN (COSTS OFF) WITH s AS (SELECT 1 UNION ALL SELECT 2 FROM hypo_part_range WHERE id = hypo_number_one()) SELECT * FROM s; + QUERY PLAN +----------------------------------------------------------------------------- + CTE Scan on s + CTE s + -> Append + -> Result + -> Append + -> Seq Scan on hypo_part_range hypo_part_range_1_10000 + Filter: (id = hypo_number_one()) + -> Seq Scan on hypo_part_range hypo_part_range_10000_20000 + Filter: (id = hypo_number_one()) + -> Seq Scan on hypo_part_range hypo_part_range_20000_30000 + Filter: (id = hypo_number_one()) +(11 rows) + +-- 6B.5 InitPlan EXPLAIN (COSTS OFF) SELECT (WITH s AS (SELECT 1 FROM part_range WHERE id = hypo_number_one()) SELECT * FROM s); QUERY PLAN ---------------------------------------------------------- @@ -1479,9 +1512,9 @@ EXPLAIN (COSTS OFF) SELECT (WITH s AS (SELECT 1 FROM hypo_part_range WHERE id = Filter: (id = hypo_number_one()) (11 rows) --- 6B.5 enable runtime partition pruning +-- 6B.6 enable runtime partition pruning SET enable_partition_pruning to true; --- 6B.6 simple case +-- 6B.7 simple case EXPLAIN (COSTS OFF) SELECT * FROM part_range WHERE id = hypo_number_one(); QUERY PLAN ------------------------------------------ @@ -1503,7 +1536,7 @@ EXPLAIN (COSTS OFF) SELECT * FROM hypo_part_range WHERE id = hypo_number_one(); Filter: (id = hypo_number_one()) (7 rows) --- 6B.7 CTE +-- 6B.8 CTE EXPLAIN (COSTS OFF) WITH s AS (SELECT * FROM part_range WHERE id = hypo_number_one()) SELECT * FROM s; QUERY PLAN -------------------------------------------------- @@ -1529,7 +1562,37 @@ EXPLAIN (COSTS OFF) WITH s AS (SELECT * FROM hypo_part_range WHERE id = hypo_num Filter: (id = hypo_number_one()) (9 rows) --- 6B.8 InitPlan +-- 6B.9 CTE with partitioning underneath an union +EXPLAIN (COSTS OFF) WITH s AS (SELECT 1 UNION ALL SELECT 2 FROM part_range WHERE id = hypo_number_one()) SELECT * FROM s; + QUERY PLAN +-------------------------------------------------------- + CTE Scan on s + CTE s + -> Append + -> Result + -> Append + Subplans Removed: 2 + -> Seq Scan on part_range_1_10000 + Filter: (id = hypo_number_one()) +(8 rows) + +EXPLAIN (COSTS OFF) WITH s AS (SELECT 1 UNION ALL SELECT 2 FROM hypo_part_range WHERE id = hypo_number_one()) SELECT * FROM s; + QUERY PLAN +----------------------------------------------------------------------------- + CTE Scan on s + CTE s + -> Append + -> Result + -> Append + -> Seq Scan on hypo_part_range hypo_part_range_1_10000 + Filter: (id = hypo_number_one()) + -> Seq Scan on hypo_part_range hypo_part_range_10000_20000 + Filter: (id = hypo_number_one()) + -> Seq Scan on hypo_part_range hypo_part_range_20000_30000 + Filter: (id = hypo_number_one()) +(11 rows) + +-- 6B.10 InitPlan EXPLAIN (COSTS OFF) SELECT (WITH s AS (SELECT 1 FROM part_range WHERE id = hypo_number_one()) SELECT * FROM s); QUERY PLAN ---------------------------------------------------------- diff --git a/hypopg.c b/hypopg.c index 072ad1d..210c1e8 100644 --- a/hypopg.c +++ b/hypopg.c @@ -111,6 +111,11 @@ static void hypo_set_rel_pathlist_hook(PlannerInfo *root, RangeTblEntry *rte); static set_rel_pathlist_hook_type prev_set_rel_pathlist_hook = NULL; #endif +#if PG_VERSION_NUM >= 110000 && PG_VERSION_NUM < 120000 +static PlannedStmt *hypo_planner_hook(Query *parse, int cursorOptions, + ParamListInfo boundParams); +static planner_hook_type prev_planner_hook = NULL; +#endif static bool hypo_query_walker(Node *node, hypoWalkerContext *context); static void hypo_CacheRelCallback(Datum arg, Oid relid); @@ -136,6 +141,10 @@ _PG_init(void) #if PG_VERSION_NUM >= 100000 && PG_VERSION_NUM < 110000 prev_set_rel_pathlist_hook = set_rel_pathlist_hook; set_rel_pathlist_hook = hypo_set_rel_pathlist_hook; +#endif +#if PG_VERSION_NUM >= 110000 && PG_VERSION_NUM < 120000 + prev_planner_hook = planner_hook; + planner_hook = hypo_planner_hook; #endif isExplain = false; hypoIndexes = NIL; @@ -180,6 +189,9 @@ _PG_fini(void) #if PG_VERSION_NUM >= 100000 && PG_VERSION_NUM < 110000 set_rel_pathlist_hook = prev_set_rel_pathlist_hook; #endif +#if PG_VERSION_NUM >= 110000 && PG_VERSION_NUM < 120000 + planner_hook = prev_planner_hook; +#endif } /*--------------------------------- @@ -725,6 +737,33 @@ hypo_set_rel_pathlist_hook(PlannerInfo *root, } #endif +#if PG_VERSION_NUM >= 110000 && PG_VERSION_NUM < 120000 +/* + * If we found partitioned tables, disable runtime partition pruning for pg11. + * This restriction will be removed for pg12+. + */ +static PlannedStmt * +hypo_planner_hook(Query *parse, int cursorOptions, + ParamListInfo boundParams) +{ + PlannedStmt *result; + hypoPlanWalkerContext hypo_context; + + if (prev_planner_hook) + result = prev_planner_hook(parse, cursorOptions, boundParams); + else + result = standard_planner(parse, cursorOptions, boundParams); + + if (HYPO_ENABLED() && hypoTables) + { + hypo_context.rtable = result->rtable; + plannedstmt_plan_walker((Node *) result, hypo_plan_walker, hypo_context); + } + + return result; +} +#endif + /* * Reset all stored entries. */ diff --git a/hypopg_table.c b/hypopg_table.c index 0df1ac7..445b01d 100644 --- a/hypopg_table.c +++ b/hypopg_table.c @@ -2642,9 +2642,196 @@ hypo_markDummyIfExcluded(PlannerInfo *root, RelOptInfo *rel, /* * TODO: re-estimate parent size just like set_append_rel_size() */ +} +#endif +#if PG_VERSION_NUM >= 110000 && PG_VERSION_NUM < 120000 + +/* + * Plan tree walking support, that can be called on a PlannedStmt or any Plan + * node. It'll call the given walker function for each Plan found and recurse + * in all of them. This is designed in a similar way to + * expression_tree_walker, refer to it for more details about the support + * routine and the rule for stopping or continuing the tree walk. + * + * BE CAREFUL: Please not that at the difference to expression_tree_walker, the + * support routine SHOULD NEVER call this function, as it would result in an + * endless loop (that should be caught by the stack depth check). + */ +bool +plannedstmt_plan_walker(Node *node, bool (*walker)(), hypoPlanWalkerContext context) +{ + ListCell *lc; + + if (node == NULL) + return false; + + /* Guard against stack overflow due to overly complex plan tree */ + check_stack_depth(); + + /* + * First, always pass the node to the walker function to ensure that it'll + * see all nodes. Note that this function should never call + * plannedstmt_plan_walker on the same node again, as it would otherwise + * cause an infinite recursion. + */ + if (walker(node, context)) + return true; + + switch (nodeTag(node)) + { + case T_PlannedStmt: + { + Plan *plan; + List *subplans; + + /* plantree */ + plan = ((PlannedStmt *) node)->planTree; + + if (plannedstmt_plan_walker((Node *) plan, walker, context)) + return true; + + /* subplans */ + subplans = ((PlannedStmt *) node)->subplans; + + if (subplans) + { + foreach(lc, subplans) + { + Node *subplan = (Node *) lfirst(lc); + + /* some subplan can be NULL */ + if (!subplan) + continue; + + if (plannedstmt_plan_walker(subplan, walker, context)) + return true; + } + } + break; + } + + case T_Append: + { + Assert(innerPlan(node) == NULL); + Assert(outerPlan(node) == NULL); + + foreach(lc, ((Append *) node)->appendplans) + { + if (plannedstmt_plan_walker((Node *) lfirst(lc), walker, + context)) + return true; + } + break; + } + + case T_MergeAppend: + { + Assert(innerPlan(node) == NULL); + Assert(outerPlan(node) == NULL); + + foreach(lc, ((MergeAppend *) node)->mergeplans) + { + if (plannedstmt_plan_walker((Node *) lfirst(lc), walker, + context)) + return true; + } + break; + } + case T_ModifyTable: + { + Assert(innerPlan(node) == NULL); + Assert(outerPlan(node) == NULL); + + foreach(lc, ((ModifyTable *) node)->plans) + { + if (plannedstmt_plan_walker((Node *) lfirst(lc), walker, + context)) + return true; + } + break; + } + + case T_SubqueryScan: + { + SubqueryScan *scan = (SubqueryScan *) node; + + Assert(innerPlan(node) == NULL); + Assert(outerPlan(node) == NULL); + + if (plannedstmt_plan_walker((Node *) scan->subplan, walker, + context)) + return true; + break; + } + + case T_BitmapAnd: + case T_BitmapOr: + { + /* + * plannedstmt_plan_walker will not recurse BitmapAnd path + * and BitmapOr path, because they can't contain Append nodes. + * The purpose of this function is finding Append nodes, so it + * will skip these unneeded nodes on a performance point of view + */ + break; + } + + default: + { + if (innerPlan(node)) + { + if (plannedstmt_plan_walker((Node *) innerPlan(node), walker, + context)) + return true; + } + if (outerPlan(node)) + { + if (plannedstmt_plan_walker((Node *) outerPlan(node), walker, + context)) + return true; + } + break; + } + } + return false; } + +/* + * To disable runtime partition pruning, we search Append node from + * PlannedStmt using plannedstmt_plan_walker() and hypo_plan_walker(). + * If we find Append node created by hypothetical partitions, we reset + * PartitionPruneInfo. +*/ +bool +hypo_plan_walker(Node *node, hypoPlanWalkerContext context) +{ + switch (nodeTag(node)) + { + case T_Append: + { + ListCell *cell; + Index rt; + RangeTblEntry *rte; + + /* check if this Append node was created by hypothetical partitions */ + foreach(cell, ((Append *) node)->partitioned_rels) + { + rt = lfirst_int(cell); + rte = list_nth_node(RangeTblEntry, context.rtable, rt-1); + if (hypo_table_oid_is_hypothetical(rte->relid)) + ((Append *) node)->part_prune_info = NULL; + } + return false; + } + + default: + return false; + } + +} + #endif #if PG_VERSION_NUM >= 110000 diff --git a/include/hypopg_table.h b/include/hypopg_table.h index 4aa6683..ed6bdcc 100644 --- a/include/hypopg_table.h +++ b/include/hypopg_table.h @@ -97,6 +97,16 @@ void hypo_injectHypotheticalPartitioning(PlannerInfo *root, void hypo_markDummyIfExcluded(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); #endif +#if PG_VERSION_NUM >= 110000 && PG_VERSION_NUM < 120000 + +typedef struct hypoPlanWalkerContext +{ + List *rtable; +}hypoPlanWalkerContext; + +bool plannedstmt_plan_walker(Node *node, bool (*walker)(), hypoPlanWalkerContext context); +bool hypo_plan_walker(Node *node, hypoPlanWalkerContext context); +#endif #endif #endif diff --git a/test/sql/hypo_table.sql b/test/sql/hypo_table.sql index 6e959ed..473e558 100644 --- a/test/sql/hypo_table.sql +++ b/test/sql/hypo_table.sql @@ -250,18 +250,24 @@ EXPLAIN (COSTS OFF) SELECT * FROM hypo_part_range WHERE id = hypo_number_one(); -- 6B.3 CTE EXPLAIN (COSTS OFF) WITH s AS (SELECT * FROM part_range WHERE id = hypo_number_one()) SELECT * FROM s; EXPLAIN (COSTS OFF) WITH s AS (SELECT * FROM hypo_part_range WHERE id = hypo_number_one()) SELECT * FROM s; --- 6B.4 InitPlan +-- 6B.4 CTE with partitioning underneath an union +EXPLAIN (COSTS OFF) WITH s AS (SELECT 1 UNION ALL SELECT 2 FROM part_range WHERE id = hypo_number_one()) SELECT * FROM s; +EXPLAIN (COSTS OFF) WITH s AS (SELECT 1 UNION ALL SELECT 2 FROM hypo_part_range WHERE id = hypo_number_one()) SELECT * FROM s; +-- 6B.5 InitPlan EXPLAIN (COSTS OFF) SELECT (WITH s AS (SELECT 1 FROM part_range WHERE id = hypo_number_one()) SELECT * FROM s); EXPLAIN (COSTS OFF) SELECT (WITH s AS (SELECT 1 FROM hypo_part_range WHERE id = hypo_number_one()) SELECT * FROM s); --- 6B.5 enable runtime partition pruning +-- 6B.6 enable runtime partition pruning SET enable_partition_pruning to true; --- 6B.6 simple case +-- 6B.7 simple case EXPLAIN (COSTS OFF) SELECT * FROM part_range WHERE id = hypo_number_one(); EXPLAIN (COSTS OFF) SELECT * FROM hypo_part_range WHERE id = hypo_number_one(); --- 6B.7 CTE +-- 6B.8 CTE EXPLAIN (COSTS OFF) WITH s AS (SELECT * FROM part_range WHERE id = hypo_number_one()) SELECT * FROM s; EXPLAIN (COSTS OFF) WITH s AS (SELECT * FROM hypo_part_range WHERE id = hypo_number_one()) SELECT * FROM s; --- 6B.8 InitPlan +-- 6B.9 CTE with partitioning underneath an union +EXPLAIN (COSTS OFF) WITH s AS (SELECT 1 UNION ALL SELECT 2 FROM part_range WHERE id = hypo_number_one()) SELECT * FROM s; +EXPLAIN (COSTS OFF) WITH s AS (SELECT 1 UNION ALL SELECT 2 FROM hypo_part_range WHERE id = hypo_number_one()) SELECT * FROM s; +-- 6B.10 InitPlan EXPLAIN (COSTS OFF) SELECT (WITH s AS (SELECT 1 FROM part_range WHERE id = hypo_number_one()) SELECT * FROM s); EXPLAIN (COSTS OFF) SELECT (WITH s AS (SELECT 1 FROM hypo_part_range WHERE id = hypo_number_one()) SELECT * FROM s);