mirror of
https://github.com/HypoPG/hypopg
synced 2026-05-24 01:28:51 +00:00
Forbid unhandled DML on hypothatical partition
This commit is contained in:
parent
357ebdb2b3
commit
0f1e6bd9c5
3 changed files with 177 additions and 15 deletions
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
91
hypopg.c
91
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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue