diff --git a/expected/hypo_table.out b/expected/hypo_table.out index ad08b71..aebd2ef 100644 --- a/expected/hypo_table.out +++ b/expected/hypo_table.out @@ -1314,3 +1314,82 @@ EXPLAIN (COSTS OFF) SELECT * FROM hypo_part_range WHERE id = 42; Filter: (id = 42) (3 rows) +-- no UPDATE/DELETE test +-- ===================== +-- simple UPDATE and DELETE on hypothetically partitioned table +EXPLAIN (COSTS OFF) UPDATE hypo_part_range set id = id; +ERROR: hypopg: UPDATE and DELETE on hypothetical partitioned tables are not supported +EXPLAIN DELETE FROM hypo_part_range WHERE id = 42; +ERROR: hypopg: UPDATE and DELETE on hypothetical partitioned tables are not supported +-- UPDATE and DELETE on hypothetically partitioned table inside CTE +EXPLAIN (COSTS OFF) WITH s AS (UPDATE hypo_part_range set id = id returning *) SELECT 1; +ERROR: hypopg: UPDATE and DELETE on hypothetical partitioned tables are not supported +EXPLAIN (COSTS OFF) WITH s AS (DELETE FROM hypo_part_range WHERE id = 42 returning *) SELECT 1; +ERROR: hypopg: UPDATE and DELETE on hypothetical partitioned tables are not supported +-- UPDATE and DELETE involving hypothetically partitioned table, but on regular +-- tables +CREATE TABLE foo(id integer); +-- UPDATE on non hypothetically partitioned table but having a hypothetically +-- partitioned table joined +EXPLAIN (COSTS OFF) WITH s AS (UPDATE foo SET id = 0 from hypo_part_range WHERE foo.id = hypo_part_range.id AND hypo_part_range.id > 25000 RETURNING *) SELECT 1; + QUERY PLAN +----------------------------------------------------------------------------------------- + Result + CTE s + -> Update on foo + -> Hash Join + Hash Cond: (foo.id = hypo_part_range_20000_30000.id) + -> Seq Scan on foo + -> Hash + -> Append + -> Seq Scan on hypo_part_range hypo_part_range_20000_30000 + Filter: (id > 25000) +(10 rows) + +-- same but with real table +EXPLAIN (COSTS OFF) WITH s AS (UPDATE foo SET id = 0 from part_range WHERE foo.id = part_range.id AND part_range.id > 25000 RETURNING *) SELECT 1; + QUERY PLAN +-------------------------------------------------------------------- + Result + CTE s + -> Update on foo + -> Hash Join + Hash Cond: (foo.id = part_range_20000_30000.id) + -> Seq Scan on foo + -> Hash + -> Append + -> Seq Scan on part_range_20000_30000 + Filter: (id > 25000) +(10 rows) + +-- DELETE on non hypothetically partitioned table but having a hypothetically +-- partitioned table joined +EXPLAIN (COSTS OFF) WITH s AS (DELETE FROM foo USING hypo_part_range WHERE foo.id = hypo_part_range.id AND hypo_part_range.id = 42 RETURNING *) SELECT 1; + QUERY PLAN +------------------------------------------------------------------------------- + Result + CTE s + -> Delete on foo + -> Nested Loop + -> Append + -> Seq Scan on hypo_part_range hypo_part_range_1_10000 + Filter: (id = 42) + -> Seq Scan on foo + Filter: (id = 42) +(9 rows) + +-- same but with real table +EXPLAIN (COSTS OFF) WITH s AS (DELETE FROM foo USING part_range WHERE foo.id = part_range.id AND part_range.id = 42 RETURNING *) SELECT 1; + QUERY PLAN +---------------------------------------------------------- + Result + CTE s + -> Delete on foo + -> Nested Loop + -> Append + -> Seq Scan on part_range_1_10000 + Filter: (id = 42) + -> Seq Scan on foo + Filter: (id = 42) +(9 rows) + diff --git a/hypopg.c b/hypopg.c index bfe2430..17d9a13 100644 --- a/hypopg.c +++ b/hypopg.c @@ -26,6 +26,7 @@ #endif #include "utils/inval.h" #include "utils/lsyscache.h" +#include "parser/parsetree.h" #include "utils/selfuncs.h" #include "utils/syscache.h" @@ -40,6 +41,11 @@ PG_MODULE_MAGIC; /*--- Macros ---*/ #define HYPO_ENABLED() (isExplain && hypo_is_enabled) +typedef struct hypoWalkerContext +{ + bool explain_found; +} hypoWalkerContext; + /*--- Variables exported ---*/ bool isExplain; @@ -99,7 +105,7 @@ static bool hypo_get_relation_stats_hook(PlannerInfo *root, VariableStatData *vardata); static get_relation_stats_hook_type prev_get_relation_stats_hook = NULL; -static bool hypo_query_walker(Node *node); +static bool hypo_query_walker(Node *node, hypoWalkerContext *context); static void hypo_CacheRelCallback(Datum arg, Oid relid); void @@ -218,14 +224,17 @@ hypo_utility_hook( DestReceiver *dest, char *completionTag) { - isExplain = query_or_expression_tree_walker( + hypoWalkerContext hypo_context = { 0 }; + + hypo_query_walker( #if PG_VERSION_NUM >= 100000 (Node *) pstmt, #else parsetree, #endif - hypo_query_walker, - NULL, 0); + &hypo_context); + + isExplain = hypo_context.explain_found; /* * Process pending invalidation. For now, just do it if the current query @@ -279,36 +288,88 @@ hypo_utility_hook( * i.e. an EXPLAIN, no ANALYZE */ static bool -hypo_query_walker(Node *parsetree) +hypo_query_walker(Node *node, hypoWalkerContext *context) { - if (parsetree == NULL) + if (node == NULL) return false; -#if PG_VERSION_NUM >= 100000 - parsetree = ((PlannedStmt *) parsetree)->utilityStmt; - if (parsetree == NULL) - return false; -#endif - switch (nodeTag(parsetree)) + switch (nodeTag(node)) { + case T_PlannedStmt: + { + Node *stmt = ((PlannedStmt *) node)->utilityStmt; + return query_or_expression_tree_walker(stmt, hypo_query_walker, + context, QTW_IGNORE_RANGE_TABLE); + } case T_ExplainStmt: { + ExplainStmt *stmt = (ExplainStmt *) node; ListCell *lc; - foreach(lc, ((ExplainStmt *) parsetree)->options) + foreach(lc, stmt->options) { DefElem *opt = (DefElem *) lfirst(lc); if (strcmp(opt->defname, "analyze") == 0) return false; } + + context->explain_found = true; +#if PG_VERSION_NUM >= 100000 + /* + * No point in looking for unhandled command type if there are + * no hypothetical partitions + */ + if (!hypoTables) + return true; + + return hypo_query_walker(stmt->query, context); +#else + return true; +#endif } - return true; break; +#if PG_VERSION_NUM >= 100000 + case T_Query: + { + Query *query = (Query *) node; + + Assert(context->explain_found); + + if (context->explain_found && + (query->commandType == CMD_UPDATE || + query->commandType == CMD_DELETE) + ) + { + RangeTblEntry *rte = rt_fetch(query->resultRelation, + query->rtable); + + if (hypo_table_oid_is_hypothetical(rte->relid)) + elog(ERROR, "hypopg: UPDATE and DELETE on hypothetically" + " partitioned tables are not supported"); + } + + if (query->cteList) + { + ListCell *lc; + + foreach(lc, query->cteList) + { + CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc); + hypo_query_walker(cte->ctequery, context); + } + } + return query_or_expression_tree_walker(node, hypo_query_walker, + context, QTW_IGNORE_RANGE_TABLE); + } + break; +#endif default: return false; } - return false; + + return query_or_expression_tree_walker(node, hypo_query_walker, context, + QTW_IGNORE_RANGE_TABLE); } /* diff --git a/test/sql/hypo_table.sql b/test/sql/hypo_table.sql index ca178d3..0f35d69 100644 --- a/test/sql/hypo_table.sql +++ b/test/sql/hypo_table.sql @@ -240,3 +240,25 @@ SELECT tablename FROM hypopg_add_partition('hypo_part_range_10000_20000', 'PARTI SELECT tablename FROM hypopg_add_partition('hypo_part_range_20000_30000', 'PARTITION OF hypo_part_range FOR VALUES FROM (20000) TO (30000)'); SELECT tablename FROM hypopg_add_partition('hypo_part_range_1_10000', 'PARTITION OF hypo_part_range FOR VALUES FROM (1) TO (10000)'); EXPLAIN (COSTS OFF) SELECT * FROM hypo_part_range WHERE id = 42; + +-- no UPDATE/DELETE test +-- ===================== +-- simple UPDATE and DELETE on hypothetically partitioned table +EXPLAIN (COSTS OFF) UPDATE hypo_part_range set id = id; +EXPLAIN DELETE FROM hypo_part_range WHERE id = 42; +-- UPDATE and DELETE on hypothetically partitioned table inside CTE +EXPLAIN (COSTS OFF) WITH s AS (UPDATE hypo_part_range set id = id returning *) SELECT 1; +EXPLAIN (COSTS OFF) WITH s AS (DELETE FROM hypo_part_range WHERE id = 42 returning *) SELECT 1; +-- UPDATE and DELETE involving hypothetically partitioned table, but on regular +-- tables +CREATE TABLE foo(id integer); +-- UPDATE on non hypothetically partitioned table but having a hypothetically +-- partitioned table joined +EXPLAIN (COSTS OFF) WITH s AS (UPDATE foo SET id = 0 from hypo_part_range WHERE foo.id = hypo_part_range.id AND hypo_part_range.id > 25000 RETURNING *) SELECT 1; +-- same but with real table +EXPLAIN (COSTS OFF) WITH s AS (UPDATE foo SET id = 0 from part_range WHERE foo.id = part_range.id AND part_range.id > 25000 RETURNING *) SELECT 1; +-- DELETE on non hypothetically partitioned table but having a hypothetically +-- partitioned table joined +EXPLAIN (COSTS OFF) WITH s AS (DELETE FROM foo USING hypo_part_range WHERE foo.id = hypo_part_range.id AND hypo_part_range.id = 42 RETURNING *) SELECT 1; +-- same but with real table +EXPLAIN (COSTS OFF) WITH s AS (DELETE FROM foo USING part_range WHERE foo.id = part_range.id AND part_range.id = 42 RETURNING *) SELECT 1;