Forbid unhandled DML on hypothatical partition

This commit is contained in:
Julien Rouhaud 2018-07-30 20:07:46 +02:00
parent 357ebdb2b3
commit 0f1e6bd9c5
3 changed files with 177 additions and 15 deletions

View file

@ -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)

View file

@ -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);
}
/*

View file

@ -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;