Commit 158b7bc6 authored by Alvaro Herrera's avatar Alvaro Herrera

Ignore whole-rows in INSERT/CONFLICT with partitioned tables

We had an Assert() preventing whole-row expressions from being used in
the SET clause of INSERT ON CONFLICT, but it seems unnecessary, given
some tests, so remove it.  Add a new test to exercise the case.

Still at ExecInitPartitionInfo, we used map_partition_varattnos (which
constructs an attribute map, then calls map_variable_attnos) using
the same two relations many times in different expressions and with
different parameters.  Constructing the map over and over is a waste.
To avoid this repeated work, construct the map once, and use
map_variable_attnos() directly instead.

Author: Amit Langote, per comments by me (Álvaro)
Discussion: https://postgr.es/m/20180326142016.m4st5e34chrzrknk@alvherre.pgsql
parent 3a2d6365
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
#include "nodes/makefuncs.h" #include "nodes/makefuncs.h"
#include "partitioning/partbounds.h" #include "partitioning/partbounds.h"
#include "partitioning/partprune.h" #include "partitioning/partprune.h"
#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h" #include "utils/lsyscache.h"
#include "utils/partcache.h" #include "utils/partcache.h"
#include "utils/rel.h" #include "utils/rel.h"
...@@ -307,8 +308,12 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, ...@@ -307,8 +308,12 @@ ExecInitPartitionInfo(ModifyTableState *mtstate,
ModifyTable *node = (ModifyTable *) mtstate->ps.plan; ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
Relation rootrel = resultRelInfo->ri_RelationDesc, Relation rootrel = resultRelInfo->ri_RelationDesc,
partrel; partrel;
Relation firstResultRel = mtstate->resultRelInfo[0].ri_RelationDesc;
ResultRelInfo *leaf_part_rri; ResultRelInfo *leaf_part_rri;
MemoryContext oldContext; MemoryContext oldContext;
AttrNumber *part_attnos = NULL;
bool found_whole_row;
bool equalTupdescs;
/* /*
* We locked all the partitions in ExecSetupPartitionTupleRouting * We locked all the partitions in ExecSetupPartitionTupleRouting
...@@ -356,6 +361,10 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, ...@@ -356,6 +361,10 @@ ExecInitPartitionInfo(ModifyTableState *mtstate,
(node != NULL && (node != NULL &&
node->onConflictAction != ONCONFLICT_NONE)); node->onConflictAction != ONCONFLICT_NONE));
/* if tuple descs are identical, we don't need to map the attrs */
equalTupdescs = equalTupleDescs(RelationGetDescr(partrel),
RelationGetDescr(firstResultRel));
/* /*
* Build WITH CHECK OPTION constraints for the partition. Note that we * Build WITH CHECK OPTION constraints for the partition. Note that we
* didn't build the withCheckOptionList for partitions within the planner, * didn't build the withCheckOptionList for partitions within the planner,
...@@ -369,7 +378,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, ...@@ -369,7 +378,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate,
List *wcoExprs = NIL; List *wcoExprs = NIL;
ListCell *ll; ListCell *ll;
int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex; int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
Relation firstResultRel = mtstate->resultRelInfo[0].ri_RelationDesc;
/* /*
* In the case of INSERT on a partitioned table, there is only one * In the case of INSERT on a partitioned table, there is only one
...@@ -397,8 +405,22 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, ...@@ -397,8 +405,22 @@ ExecInitPartitionInfo(ModifyTableState *mtstate,
/* /*
* Convert Vars in it to contain this partition's attribute numbers. * Convert Vars in it to contain this partition's attribute numbers.
*/ */
wcoList = map_partition_varattnos(wcoList, firstVarno, if (!equalTupdescs)
partrel, firstResultRel, NULL); {
part_attnos =
convert_tuples_by_name_map(RelationGetDescr(partrel),
RelationGetDescr(firstResultRel),
gettext_noop("could not convert row type"));
wcoList = (List *)
map_variable_attnos((Node *) wcoList,
firstVarno, 0,
part_attnos,
RelationGetDescr(firstResultRel)->natts,
RelationGetForm(partrel)->reltype,
&found_whole_row);
/* We ignore the value of found_whole_row. */
}
foreach(ll, wcoList) foreach(ll, wcoList)
{ {
WithCheckOption *wco = castNode(WithCheckOption, lfirst(ll)); WithCheckOption *wco = castNode(WithCheckOption, lfirst(ll));
...@@ -425,7 +447,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, ...@@ -425,7 +447,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate,
ExprContext *econtext; ExprContext *econtext;
List *returningList; List *returningList;
int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex; int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
Relation firstResultRel = mtstate->resultRelInfo[0].ri_RelationDesc;
/* See the comment above for WCO lists. */ /* See the comment above for WCO lists. */
Assert((node->operation == CMD_INSERT && Assert((node->operation == CMD_INSERT &&
...@@ -443,12 +464,26 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, ...@@ -443,12 +464,26 @@ ExecInitPartitionInfo(ModifyTableState *mtstate,
*/ */
returningList = linitial(node->returningLists); returningList = linitial(node->returningLists);
if (!equalTupdescs)
{
/* /*
* Convert Vars in it to contain this partition's attribute numbers. * Convert Vars in it to contain this partition's attribute numbers.
*/ */
returningList = map_partition_varattnos(returningList, firstVarno, if (part_attnos == NULL)
partrel, firstResultRel, part_attnos =
NULL); convert_tuples_by_name_map(RelationGetDescr(partrel),
RelationGetDescr(firstResultRel),
gettext_noop("could not convert row type"));
returningList = (List *)
map_variable_attnos((Node *) returningList,
firstVarno, 0,
part_attnos,
RelationGetDescr(firstResultRel)->natts,
RelationGetForm(partrel)->reltype,
&found_whole_row);
/* We ignore the value of found_whole_row. */
}
leaf_part_rri->ri_returningList = returningList; leaf_part_rri->ri_returningList = returningList;
/* /*
...@@ -473,7 +508,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, ...@@ -473,7 +508,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate,
{ {
TupleConversionMap *map = proute->parent_child_tupconv_maps[partidx]; TupleConversionMap *map = proute->parent_child_tupconv_maps[partidx];
int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex; int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
Relation firstResultRel = mtstate->resultRelInfo[0].ri_RelationDesc;
TupleDesc partrelDesc = RelationGetDescr(partrel); TupleDesc partrelDesc = RelationGetDescr(partrel);
ExprContext *econtext = mtstate->ps.ps_ExprContext; ExprContext *econtext = mtstate->ps.ps_ExprContext;
ListCell *lc; ListCell *lc;
...@@ -549,17 +583,33 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, ...@@ -549,17 +583,33 @@ ExecInitPartitionInfo(ModifyTableState *mtstate,
* target relation (firstVarno). * target relation (firstVarno).
*/ */
onconflset = (List *) copyObject((Node *) node->onConflictSet); onconflset = (List *) copyObject((Node *) node->onConflictSet);
onconflset = if (!equalTupdescs)
map_partition_varattnos(onconflset, INNER_VAR, partrel, {
firstResultRel, &found_whole_row); if (part_attnos == NULL)
Assert(!found_whole_row); part_attnos =
onconflset = convert_tuples_by_name_map(RelationGetDescr(partrel),
map_partition_varattnos(onconflset, firstVarno, partrel, RelationGetDescr(firstResultRel),
firstResultRel, &found_whole_row); gettext_noop("could not convert row type"));
Assert(!found_whole_row); onconflset = (List *)
map_variable_attnos((Node *) onconflset,
INNER_VAR, 0,
part_attnos,
RelationGetDescr(firstResultRel)->natts,
RelationGetForm(partrel)->reltype,
&found_whole_row);
/* We ignore the value of found_whole_row. */
onconflset = (List *)
map_variable_attnos((Node *) onconflset,
firstVarno, 0,
part_attnos,
RelationGetDescr(firstResultRel)->natts,
RelationGetForm(partrel)->reltype,
&found_whole_row);
/* We ignore the value of found_whole_row. */
/* Finally, adjust this tlist to match the partition. */ /* Finally, adjust this tlist to match the partition. */
onconflset = adjust_partition_tlist(onconflset, map); onconflset = adjust_partition_tlist(onconflset, map);
}
/* /*
* Build UPDATE SET's projection info. The user of this * Build UPDATE SET's projection info. The user of this
...@@ -587,14 +637,25 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, ...@@ -587,14 +637,25 @@ ExecInitPartitionInfo(ModifyTableState *mtstate,
List *clause; List *clause;
clause = copyObject((List *) node->onConflictWhere); clause = copyObject((List *) node->onConflictWhere);
clause = map_partition_varattnos(clause, INNER_VAR, if (!equalTupdescs)
partrel, firstResultRel, {
clause = (List *)
map_variable_attnos((Node *) clause,
INNER_VAR, 0,
part_attnos,
RelationGetDescr(firstResultRel)->natts,
RelationGetForm(partrel)->reltype,
&found_whole_row); &found_whole_row);
Assert(!found_whole_row); /* We ignore the value of found_whole_row. */
clause = map_partition_varattnos(clause, firstVarno, clause = (List *)
partrel, firstResultRel, map_variable_attnos((Node *) clause,
firstVarno, 0,
part_attnos,
RelationGetDescr(firstResultRel)->natts,
RelationGetForm(partrel)->reltype,
&found_whole_row); &found_whole_row);
Assert(!found_whole_row); /* We ignore the value of found_whole_row. */
}
leaf_part_rri->ri_onConflict->oc_WhereClause = leaf_part_rri->ri_onConflict->oc_WhereClause =
ExecInitQual((List *) clause, &mtstate->ps); ExecInitQual((List *) clause, &mtstate->ps);
} }
......
...@@ -884,4 +884,20 @@ insert into parted_conflict values (40, 'forty'); ...@@ -884,4 +884,20 @@ insert into parted_conflict values (40, 'forty');
insert into parted_conflict_1 values (40, 'cuarenta') insert into parted_conflict_1 values (40, 'cuarenta')
on conflict (a) do update set b = excluded.b; on conflict (a) do update set b = excluded.b;
ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification
-- test whole-row Vars in ON CONFLICT expressions
create unique index on parted_conflict (a, b);
alter table parted_conflict add c int;
truncate parted_conflict;
insert into parted_conflict values (50, 'cincuenta', 1);
insert into parted_conflict values (50, 'cincuenta', 2)
on conflict (a, b) do update set (a, b, c) = row(excluded.*)
where parted_conflict = (50, text 'cincuenta', 1) and
excluded = (50, text 'cincuenta', 2);
-- should see (50, 'cincuenta', 2)
select * from parted_conflict order by a;
a | b | c
----+-----------+---
50 | cincuenta | 2
(1 row)
drop table parted_conflict; drop table parted_conflict;
...@@ -559,3 +559,21 @@ insert into parted_conflict values (40, 'forty'); ...@@ -559,3 +559,21 @@ insert into parted_conflict values (40, 'forty');
insert into parted_conflict_1 values (40, 'cuarenta') insert into parted_conflict_1 values (40, 'cuarenta')
on conflict (a) do update set b = excluded.b; on conflict (a) do update set b = excluded.b;
drop table parted_conflict; drop table parted_conflict;
-- test whole-row Vars in ON CONFLICT expressions
create table parted_conflict (a int, b text, c int) partition by range (a);
create table parted_conflict_1 (drp text, c int, a int, b text);
alter table parted_conflict_1 drop column drp;
create unique index on parted_conflict (a, b);
alter table parted_conflict attach partition parted_conflict_1 for values from (0) to (1000);
truncate parted_conflict;
insert into parted_conflict values (50, 'cincuenta', 1);
insert into parted_conflict values (50, 'cincuenta', 2)
on conflict (a, b) do update set (a, b, c) = row(excluded.*)
where parted_conflict = (50, text 'cincuenta', 1) and
excluded = (50, text 'cincuenta', 2);
-- should see (50, 'cincuenta', 2)
select * from parted_conflict order by a;
drop table parted_conflict;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment