From f6855b58b72128db9cfeb8d648eff4b0879e32fc Mon Sep 17 00:00:00 2001 From: amit Date: Fri, 6 Jan 2017 15:33:02 +0900 Subject: [PATCH 7/7] Fix some issues with views and partitioned tables Automatically updatable views failed to handle partitioned tables. Once that's fixed, WITH CHECK OPTIONS wouldn't work correctly without them constraint expressions having been suitably converted for each partition (think map_partition_varattnos on Vars in the expressions just like with regular check constraints). Reported by: n/a Patch by: Amit Langote Reports: n/a --- src/backend/executor/execMain.c | 34 +++++++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 40 +++++++++++++++++++++++++++ src/backend/rewrite/rewriteHandler.c | 3 +- src/test/regress/expected/updatable_views.out | 24 ++++++++++++++++ src/test/regress/sql/updatable_views.sql | 19 +++++++++++++ 5 files changed, 119 insertions(+), 1 deletion(-) diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 060b036ddf..634a02bd8e 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -2000,6 +2000,40 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo, Bitmapset *insertedCols; Bitmapset *updatedCols; + /* + * In case where the tuple is routed, it's been converted + * to the partition's rowtype, which might differ from the + * root table's. We must convert it back to the root table's + * type so that it's shown correctly in the error message. + */ + if (resultRelInfo->ri_PartitionRoot) + { + HeapTuple tuple = ExecFetchSlotTuple(slot); + TupleDesc old_tupdesc = RelationGetDescr(rel); + TupleConversionMap *map; + + rel = resultRelInfo->ri_PartitionRoot; + tupdesc = RelationGetDescr(rel); + /* a reverse map */ + map = convert_tuples_by_name(old_tupdesc, tupdesc, false, + gettext_noop("could not convert row type")); + if (map != NULL) + { + tuple = do_convert_tuple(tuple, map); + ExecStoreTuple(tuple, slot, InvalidBuffer, false); + } + } + + /* + * choose the correct relation to build val_desc from the + * tuple contained in orig_slot + */ + if (resultRelInfo->ri_PartitionRoot) + { + rel = resultRelInfo->ri_PartitionRoot; + tupdesc = RelationGetDescr(rel); + } + switch (wco->kind) { /* diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index f46070fde7..5ccf2321de 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1780,6 +1780,46 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) } /* + * Build WITH CHECK OPTION constraints for each leaf partition rel. + * Note that we didn't build the returningList for each partition within + * the planner, but simple translation of the varattnos for each partition + * will suffice. This only occurs for the INSERT case; UPDATE/DELETE are + * handled above. + */ + if (node->withCheckOptionLists != NIL && mtstate->mt_num_partitions > 0) + { + List *wcoList; + + Assert(operation == CMD_INSERT); + resultRelInfo = mtstate->mt_partitions; + wcoList = linitial(node->withCheckOptionLists); + for (i = 0; i < mtstate->mt_num_partitions; i++) + { + Relation partrel = resultRelInfo->ri_RelationDesc; + List *mapped_wcoList; + List *wcoExprs = NIL; + ListCell *ll; + + /* varno = node->nominalRelation */ + mapped_wcoList = map_partition_varattnos(wcoList, + node->nominalRelation, + partrel, rel); + foreach(ll, mapped_wcoList) + { + WithCheckOption *wco = (WithCheckOption *) lfirst(ll); + ExprState *wcoExpr = ExecInitExpr((Expr *) wco->qual, + mtstate->mt_plans[i]); + + wcoExprs = lappend(wcoExprs, wcoExpr); + } + + resultRelInfo->ri_WithCheckOptions = mapped_wcoList; + resultRelInfo->ri_WithCheckOptionExprs = wcoExprs; + resultRelInfo++; + } + } + + /* * Initialize RETURNING projections if needed. */ if (node->returningLists) diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index d1ff3b20b6..d3e44fb135 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -2249,7 +2249,8 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols) if (base_rte->rtekind != RTE_RELATION || (base_rte->relkind != RELKIND_RELATION && base_rte->relkind != RELKIND_FOREIGN_TABLE && - base_rte->relkind != RELKIND_VIEW)) + base_rte->relkind != RELKIND_VIEW && + base_rte->relkind != RELKIND_PARTITIONED_TABLE)) return gettext_noop("Views that do not select from a single table or view are not automatically updatable."); if (base_rte->tablesample) diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out index 6fba613f0f..b6b61422bf 100644 --- a/src/test/regress/expected/updatable_views.out +++ b/src/test/regress/expected/updatable_views.out @@ -2465,3 +2465,27 @@ ERROR: new row violates check option for view "v1" DETAIL: Failing row contains (-1, invalid). DROP VIEW v1; DROP TABLE t1; +-- check that an auto-updatable view on a partitioned table works correctly +create table p (a int, b int) partition by range (a, b); +create table p1 (b int not null, a int not null) partition by range (b); +create table p11 (like p1); +alter table p11 drop a; +alter table p11 add a int; +alter table p11 drop a; +alter table p11 add a int not null; +alter table p1 attach partition p11 for values from (2) to (5); +alter table p attach partition p1 for values from (1, 2) to (1, 10); +create view pv as select * from p; +insert into pv values (1, 2); +select tableoid::regclass, * from p; + tableoid | a | b +----------+---+--- + p11 | 1 | 2 +(1 row) + +create view pv_wco as select * from p where a = 0 with check option; +insert into pv_wco values (1, 2); +ERROR: new row violates check option for view "pv_wco" +DETAIL: Failing row contains (1, 2). +drop view pv, pv_wco; +drop table p, p1, p11; diff --git a/src/test/regress/sql/updatable_views.sql b/src/test/regress/sql/updatable_views.sql index bb9a3a6174..cd4f5a811d 100644 --- a/src/test/regress/sql/updatable_views.sql +++ b/src/test/regress/sql/updatable_views.sql @@ -1112,3 +1112,22 @@ INSERT INTO v1 VALUES (-1, 'invalid'); -- should fail DROP VIEW v1; DROP TABLE t1; + +-- check that an auto-updatable view on a partitioned table works correctly +create table p (a int, b int) partition by range (a, b); +create table p1 (b int not null, a int not null) partition by range (b); +create table p11 (like p1); +alter table p11 drop a; +alter table p11 add a int; +alter table p11 drop a; +alter table p11 add a int not null; +alter table p1 attach partition p11 for values from (2) to (5); +alter table p attach partition p1 for values from (1, 2) to (1, 10); + +create view pv as select * from p; +insert into pv values (1, 2); +select tableoid::regclass, * from p; +create view pv_wco as select * from p where a = 0 with check option; +insert into pv_wco values (1, 2); +drop view pv, pv_wco; +drop table p, p1, p11; -- 2.11.0