postgresql/src/test/regress/expected/insert.out

416 lines
17 KiB
Plaintext
Raw Normal View History

2002-04-05 13:56:55 +02:00
--
-- insert with DEFAULT in the target_list
--
create table inserttest (col1 int4, col2 int4 NOT NULL, col3 text default 'testing');
insert into inserttest (col1, col2, col3) values (DEFAULT, DEFAULT, DEFAULT);
ERROR: null value in column "col2" violates not-null constraint
DETAIL: Failing row contains (null, null, testing).
2002-04-05 13:56:55 +02:00
insert into inserttest (col2, col3) values (3, DEFAULT);
insert into inserttest (col1, col2, col3) values (DEFAULT, 5, DEFAULT);
insert into inserttest values (DEFAULT, 5, 'test');
insert into inserttest values (DEFAULT, 7);
select * from inserttest;
col1 | col2 | col3
------+------+---------
| 3 | testing
| 5 | testing
| 5 | test
| 7 | testing
(4 rows)
--
-- insert with similar expression / target_list values (all fail)
--
insert into inserttest (col1, col2, col3) values (DEFAULT, DEFAULT);
ERROR: INSERT has more target columns than expressions
LINE 1: insert into inserttest (col1, col2, col3) values (DEFAULT, D...
^
insert into inserttest (col1, col2, col3) values (1, 2);
ERROR: INSERT has more target columns than expressions
LINE 1: insert into inserttest (col1, col2, col3) values (1, 2);
^
insert into inserttest (col1) values (1, 2);
ERROR: INSERT has more expressions than target columns
LINE 1: insert into inserttest (col1) values (1, 2);
^
insert into inserttest (col1) values (DEFAULT, DEFAULT);
ERROR: INSERT has more expressions than target columns
LINE 1: insert into inserttest (col1) values (DEFAULT, DEFAULT);
^
select * from inserttest;
col1 | col2 | col3
------+------+---------
| 3 | testing
| 5 | testing
| 5 | test
| 7 | testing
(4 rows)
--
-- VALUES test
--
insert into inserttest values(10, 20, '40'), (-1, 2, DEFAULT),
((select 2), (select i from (values(3)) as foo (i)), 'values are fun!');
select * from inserttest;
col1 | col2 | col3
------+------+-----------------
| 3 | testing
| 5 | testing
| 5 | test
| 7 | testing
10 | 20 | 40
-1 | 2 | testing
2 | 3 | values are fun!
(7 rows)
--
-- TOASTed value test
--
insert into inserttest values(30, 50, repeat('x', 10000));
select col1, col2, char_length(col3) from inserttest;
col1 | col2 | char_length
------+------+-------------
| 3 | 7
| 5 | 7
| 5 | 4
| 7 | 7
10 | 20 | 2
-1 | 2 | 7
2 | 3 | 15
30 | 50 | 10000
(8 rows)
2002-04-05 13:56:55 +02:00
drop table inserttest;
Make INSERT-from-multiple-VALUES-rows handle targetlist indirection better. Previously, if an INSERT with multiple rows of VALUES had indirection (array subscripting or field selection) in its target-columns list, the parser handled that by applying transformAssignedExpr() to each element of each VALUES row independently. This led to having ArrayRef assignment nodes or FieldStore nodes in each row of the VALUES RTE. That works for simple cases, but in bug #14265 Nuri Boardman points out that it fails if there are multiple assignments to elements/fields of the same target column. For such cases to work, rewriteTargetListIU() has to nest the ArrayRefs or FieldStores together to produce a single expression to be assigned to the column. But it failed to find them in the top-level targetlist and issued an error about "multiple assignments to same column". We could possibly fix this by teaching the rewriter to apply rewriteTargetListIU to each VALUES row separately, but that would be messy (it would change the output rowtype of the VALUES RTE, for example) and inefficient. Instead, let's fix the parser so that the VALUES RTE outputs are just the user-specified values, cast to the right type if necessary, and then the ArrayRefs or FieldStores are applied in the top-level targetlist to Vars representing the RTE's outputs. This is the same parsetree representation already used for similar cases with INSERT/SELECT syntax, so it allows simplifications in ruleutils.c, which no longer needs to treat INSERT-from-multiple-VALUES as its own special case. This implementation works by applying transformAssignedExpr to the VALUES entries as before, and then stripping off any ArrayRefs or FieldStores it adds. With lots of VALUES rows it would be noticeably more efficient to not add those nodes in the first place. But that's just an optimization not a bug fix, and there doesn't seem to be any good way to do it without significant refactoring. (A non-invasive answer would be to apply transformAssignedExpr + stripping to just the first VALUES row, and then just forcibly cast remaining rows to the same data types exposed in the first row. But this way would lead to different, not-INSERT-specific errors being reported in casting failure cases, so it doesn't seem very nice.) So leave that for later; this patch at least isn't making the per-row parsing work worse, and it does make the finished parsetree smaller, saving rewriter and planner work. Catversion bump because stored rules containing such INSERTs would need to change. Because of that, no back-patch, even though this is a very long-standing bug. Report: <20160727005725.7438.26021@wrigleys.postgresql.org> Discussion: <9578.1469645245@sss.pgh.pa.us>
2016-08-03 22:37:03 +02:00
--
-- check indirection (field/array assignment), cf bug #14265
--
-- these tests are aware that transformInsertStmt has 3 separate code paths
--
create type insert_test_type as (if1 int, if2 text[]);
create table inserttest (f1 int, f2 int[],
f3 insert_test_type, f4 insert_test_type[]);
insert into inserttest (f2[1], f2[2]) values (1,2);
insert into inserttest (f2[1], f2[2]) values (3,4), (5,6);
insert into inserttest (f2[1], f2[2]) select 7,8;
insert into inserttest (f2[1], f2[2]) values (1,default); -- not supported
ERROR: cannot set an array element to DEFAULT
LINE 1: insert into inserttest (f2[1], f2[2]) values (1,default);
^
insert into inserttest (f3.if1, f3.if2) values (1,array['foo']);
insert into inserttest (f3.if1, f3.if2) values (1,'{foo}'), (2,'{bar}');
insert into inserttest (f3.if1, f3.if2) select 3, '{baz,quux}';
insert into inserttest (f3.if1, f3.if2) values (1,default); -- not supported
ERROR: cannot set a subfield to DEFAULT
LINE 1: insert into inserttest (f3.if1, f3.if2) values (1,default);
^
insert into inserttest (f3.if2[1], f3.if2[2]) values ('foo', 'bar');
insert into inserttest (f3.if2[1], f3.if2[2]) values ('foo', 'bar'), ('baz', 'quux');
insert into inserttest (f3.if2[1], f3.if2[2]) select 'bear', 'beer';
insert into inserttest (f4[1].if2[1], f4[1].if2[2]) values ('foo', 'bar');
insert into inserttest (f4[1].if2[1], f4[1].if2[2]) values ('foo', 'bar'), ('baz', 'quux');
insert into inserttest (f4[1].if2[1], f4[1].if2[2]) select 'bear', 'beer';
select * from inserttest;
f1 | f2 | f3 | f4
----+-------+------------------+------------------------
| {1,2} | |
| {3,4} | |
| {5,6} | |
| {7,8} | |
| | (1,{foo}) |
| | (1,{foo}) |
| | (2,{bar}) |
| | (3,"{baz,quux}") |
| | (,"{foo,bar}") |
| | (,"{foo,bar}") |
| | (,"{baz,quux}") |
| | (,"{bear,beer}") |
| | | {"(,\"{foo,bar}\")"}
| | | {"(,\"{foo,bar}\")"}
| | | {"(,\"{baz,quux}\")"}
| | | {"(,\"{bear,beer}\")"}
(16 rows)
-- also check reverse-listing
create table inserttest2 (f1 bigint, f2 text);
create rule irule1 as on insert to inserttest2 do also
insert into inserttest (f3.if2[1], f3.if2[2])
values (new.f1,new.f2);
create rule irule2 as on insert to inserttest2 do also
insert into inserttest (f4[1].if1, f4[1].if2[2])
values (1,'fool'),(new.f1,new.f2);
create rule irule3 as on insert to inserttest2 do also
insert into inserttest (f4[1].if1, f4[1].if2[2])
select new.f1, new.f2;
\d+ inserttest2
Table "public.inserttest2"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
--------+--------+-----------+----------+---------+----------+--------------+-------------
f1 | bigint | | | | plain | |
f2 | text | | | | extended | |
Make INSERT-from-multiple-VALUES-rows handle targetlist indirection better. Previously, if an INSERT with multiple rows of VALUES had indirection (array subscripting or field selection) in its target-columns list, the parser handled that by applying transformAssignedExpr() to each element of each VALUES row independently. This led to having ArrayRef assignment nodes or FieldStore nodes in each row of the VALUES RTE. That works for simple cases, but in bug #14265 Nuri Boardman points out that it fails if there are multiple assignments to elements/fields of the same target column. For such cases to work, rewriteTargetListIU() has to nest the ArrayRefs or FieldStores together to produce a single expression to be assigned to the column. But it failed to find them in the top-level targetlist and issued an error about "multiple assignments to same column". We could possibly fix this by teaching the rewriter to apply rewriteTargetListIU to each VALUES row separately, but that would be messy (it would change the output rowtype of the VALUES RTE, for example) and inefficient. Instead, let's fix the parser so that the VALUES RTE outputs are just the user-specified values, cast to the right type if necessary, and then the ArrayRefs or FieldStores are applied in the top-level targetlist to Vars representing the RTE's outputs. This is the same parsetree representation already used for similar cases with INSERT/SELECT syntax, so it allows simplifications in ruleutils.c, which no longer needs to treat INSERT-from-multiple-VALUES as its own special case. This implementation works by applying transformAssignedExpr to the VALUES entries as before, and then stripping off any ArrayRefs or FieldStores it adds. With lots of VALUES rows it would be noticeably more efficient to not add those nodes in the first place. But that's just an optimization not a bug fix, and there doesn't seem to be any good way to do it without significant refactoring. (A non-invasive answer would be to apply transformAssignedExpr + stripping to just the first VALUES row, and then just forcibly cast remaining rows to the same data types exposed in the first row. But this way would lead to different, not-INSERT-specific errors being reported in casting failure cases, so it doesn't seem very nice.) So leave that for later; this patch at least isn't making the per-row parsing work worse, and it does make the finished parsetree smaller, saving rewriter and planner work. Catversion bump because stored rules containing such INSERTs would need to change. Because of that, no back-patch, even though this is a very long-standing bug. Report: <20160727005725.7438.26021@wrigleys.postgresql.org> Discussion: <9578.1469645245@sss.pgh.pa.us>
2016-08-03 22:37:03 +02:00
Rules:
irule1 AS
ON INSERT TO inserttest2 DO INSERT INTO inserttest (f3.if2[1], f3.if2[2])
VALUES (new.f1, new.f2)
irule2 AS
ON INSERT TO inserttest2 DO INSERT INTO inserttest (f4[1].if1, f4[1].if2[2]) VALUES (1,'fool'::text), (new.f1,new.f2)
irule3 AS
ON INSERT TO inserttest2 DO INSERT INTO inserttest (f4[1].if1, f4[1].if2[2]) SELECT new.f1,
new.f2
drop table inserttest2;
drop table inserttest;
drop type insert_test_type;
Implement table partitioning. Table partitioning is like table inheritance and reuses much of the existing infrastructure, but there are some important differences. The parent is called a partitioned table and is always empty; it may not have indexes or non-inherited constraints, since those make no sense for a relation with no data of its own. The children are called partitions and contain all of the actual data. Each partition has an implicit partitioning constraint. Multiple inheritance is not allowed, and partitioning and inheritance can't be mixed. Partitions can't have extra columns and may not allow nulls unless the parent does. Tuples inserted into the parent are automatically routed to the correct partition, so tuple-routing ON INSERT triggers are not needed. Tuple routing isn't yet supported for partitions which are foreign tables, and it doesn't handle updates that cross partition boundaries. Currently, tables can be range-partitioned or list-partitioned. List partitioning is limited to a single column, but range partitioning can involve multiple columns. A partitioning "column" can be an expression. Because table partitioning is less general than table inheritance, it is hoped that it will be easier to reason about properties of partitions, and therefore that this will serve as a better foundation for a variety of possible optimizations, including query planner optimizations. The tuple routing based which this patch does based on the implicit partitioning constraints is an example of this, but it seems likely that many other useful optimizations are also possible. Amit Langote, reviewed and tested by Robert Haas, Ashutosh Bapat, Amit Kapila, Rajkumar Raghuwanshi, Corey Huinker, Jaime Casanova, Rushabh Lathia, Erik Rijkers, among others. Minor revisions by me.
2016-12-07 19:17:43 +01:00
-- direct partition inserts should check partition bound constraint
create table range_parted (
a text,
b int
) partition by range (a, (b+0));
create table part1 partition of range_parted for values from ('a', 1) to ('a', 10);
create table part2 partition of range_parted for values from ('a', 10) to ('a', 20);
create table part3 partition of range_parted for values from ('b', 1) to ('b', 10);
create table part4 partition of range_parted for values from ('b', 10) to ('b', 20);
-- fail
insert into part1 values ('a', 11);
ERROR: new row for relation "part1" violates partition constraint
DETAIL: Failing row contains (a, 11).
insert into part1 values ('b', 1);
ERROR: new row for relation "part1" violates partition constraint
DETAIL: Failing row contains (b, 1).
-- ok
insert into part1 values ('a', 1);
-- fail
insert into part4 values ('b', 21);
ERROR: new row for relation "part4" violates partition constraint
DETAIL: Failing row contains (b, 21).
insert into part4 values ('a', 10);
ERROR: new row for relation "part4" violates partition constraint
DETAIL: Failing row contains (a, 10).
-- ok
insert into part4 values ('b', 10);
-- fail (partition key a has a NOT NULL constraint)
insert into part1 values (null);
ERROR: null value in column "a" violates not-null constraint
DETAIL: Failing row contains (null, null).
-- fail (expression key (b+0) cannot be null either)
insert into part1 values (1);
ERROR: new row for relation "part1" violates partition constraint
DETAIL: Failing row contains (1, null).
create table list_parted (
a text,
b int
) partition by list (lower(a));
create table part_aa_bb partition of list_parted FOR VALUES IN ('aa', 'bb');
create table part_cc_dd partition of list_parted FOR VALUES IN ('cc', 'dd');
create table part_null partition of list_parted FOR VALUES IN (null);
-- fail
insert into part_aa_bb values ('cc', 1);
ERROR: new row for relation "part_aa_bb" violates partition constraint
DETAIL: Failing row contains (cc, 1).
insert into part_aa_bb values ('AAa', 1);
ERROR: new row for relation "part_aa_bb" violates partition constraint
DETAIL: Failing row contains (AAa, 1).
insert into part_aa_bb values (null);
ERROR: new row for relation "part_aa_bb" violates partition constraint
DETAIL: Failing row contains (null, null).
-- ok
insert into part_cc_dd values ('cC', 1);
insert into part_null values (null, 0);
-- check in case of multi-level partitioned table
create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
-- fail
insert into part_ee_ff1 values ('EE', 11);
ERROR: new row for relation "part_ee_ff1" violates partition constraint
DETAIL: Failing row contains (EE, 11).
-- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
insert into part_ee_ff1 values ('cc', 1);
ERROR: new row for relation "part_ee_ff1" violates partition constraint
DETAIL: Failing row contains (cc, 1).
-- ok
insert into part_ee_ff1 values ('ff', 1);
insert into part_ee_ff2 values ('ff', 11);
-- Check tuple routing for partitioned tables
-- fail
insert into range_parted values ('a', 0);
ERROR: no partition of relation "range_parted" found for row
DETAIL: Partition key of the failing row contains (a, (b + 0)) = (a, 0).
Implement table partitioning. Table partitioning is like table inheritance and reuses much of the existing infrastructure, but there are some important differences. The parent is called a partitioned table and is always empty; it may not have indexes or non-inherited constraints, since those make no sense for a relation with no data of its own. The children are called partitions and contain all of the actual data. Each partition has an implicit partitioning constraint. Multiple inheritance is not allowed, and partitioning and inheritance can't be mixed. Partitions can't have extra columns and may not allow nulls unless the parent does. Tuples inserted into the parent are automatically routed to the correct partition, so tuple-routing ON INSERT triggers are not needed. Tuple routing isn't yet supported for partitions which are foreign tables, and it doesn't handle updates that cross partition boundaries. Currently, tables can be range-partitioned or list-partitioned. List partitioning is limited to a single column, but range partitioning can involve multiple columns. A partitioning "column" can be an expression. Because table partitioning is less general than table inheritance, it is hoped that it will be easier to reason about properties of partitions, and therefore that this will serve as a better foundation for a variety of possible optimizations, including query planner optimizations. The tuple routing based which this patch does based on the implicit partitioning constraints is an example of this, but it seems likely that many other useful optimizations are also possible. Amit Langote, reviewed and tested by Robert Haas, Ashutosh Bapat, Amit Kapila, Rajkumar Raghuwanshi, Corey Huinker, Jaime Casanova, Rushabh Lathia, Erik Rijkers, among others. Minor revisions by me.
2016-12-07 19:17:43 +01:00
-- ok
insert into range_parted values ('a', 1);
insert into range_parted values ('a', 10);
-- fail
insert into range_parted values ('a', 20);
ERROR: no partition of relation "range_parted" found for row
DETAIL: Partition key of the failing row contains (a, (b + 0)) = (a, 20).
Implement table partitioning. Table partitioning is like table inheritance and reuses much of the existing infrastructure, but there are some important differences. The parent is called a partitioned table and is always empty; it may not have indexes or non-inherited constraints, since those make no sense for a relation with no data of its own. The children are called partitions and contain all of the actual data. Each partition has an implicit partitioning constraint. Multiple inheritance is not allowed, and partitioning and inheritance can't be mixed. Partitions can't have extra columns and may not allow nulls unless the parent does. Tuples inserted into the parent are automatically routed to the correct partition, so tuple-routing ON INSERT triggers are not needed. Tuple routing isn't yet supported for partitions which are foreign tables, and it doesn't handle updates that cross partition boundaries. Currently, tables can be range-partitioned or list-partitioned. List partitioning is limited to a single column, but range partitioning can involve multiple columns. A partitioning "column" can be an expression. Because table partitioning is less general than table inheritance, it is hoped that it will be easier to reason about properties of partitions, and therefore that this will serve as a better foundation for a variety of possible optimizations, including query planner optimizations. The tuple routing based which this patch does based on the implicit partitioning constraints is an example of this, but it seems likely that many other useful optimizations are also possible. Amit Langote, reviewed and tested by Robert Haas, Ashutosh Bapat, Amit Kapila, Rajkumar Raghuwanshi, Corey Huinker, Jaime Casanova, Rushabh Lathia, Erik Rijkers, among others. Minor revisions by me.
2016-12-07 19:17:43 +01:00
-- ok
insert into range_parted values ('b', 1);
insert into range_parted values ('b', 10);
-- fail (partition key (b+0) is null)
insert into range_parted values ('a');
ERROR: range partition key of row contains null
select tableoid::regclass, * from range_parted;
tableoid | a | b
----------+---+----
part1 | a | 1
part1 | a | 1
part2 | a | 10
part3 | b | 1
part4 | b | 10
part4 | b | 10
(6 rows)
-- ok
insert into list_parted values (null, 1);
insert into list_parted (a) values ('aA');
-- fail (partition of part_ee_ff not found in both cases)
insert into list_parted values ('EE', 0);
ERROR: no partition of relation "part_ee_ff" found for row
DETAIL: Partition key of the failing row contains (b) = (0).
Implement table partitioning. Table partitioning is like table inheritance and reuses much of the existing infrastructure, but there are some important differences. The parent is called a partitioned table and is always empty; it may not have indexes or non-inherited constraints, since those make no sense for a relation with no data of its own. The children are called partitions and contain all of the actual data. Each partition has an implicit partitioning constraint. Multiple inheritance is not allowed, and partitioning and inheritance can't be mixed. Partitions can't have extra columns and may not allow nulls unless the parent does. Tuples inserted into the parent are automatically routed to the correct partition, so tuple-routing ON INSERT triggers are not needed. Tuple routing isn't yet supported for partitions which are foreign tables, and it doesn't handle updates that cross partition boundaries. Currently, tables can be range-partitioned or list-partitioned. List partitioning is limited to a single column, but range partitioning can involve multiple columns. A partitioning "column" can be an expression. Because table partitioning is less general than table inheritance, it is hoped that it will be easier to reason about properties of partitions, and therefore that this will serve as a better foundation for a variety of possible optimizations, including query planner optimizations. The tuple routing based which this patch does based on the implicit partitioning constraints is an example of this, but it seems likely that many other useful optimizations are also possible. Amit Langote, reviewed and tested by Robert Haas, Ashutosh Bapat, Amit Kapila, Rajkumar Raghuwanshi, Corey Huinker, Jaime Casanova, Rushabh Lathia, Erik Rijkers, among others. Minor revisions by me.
2016-12-07 19:17:43 +01:00
insert into part_ee_ff values ('EE', 0);
ERROR: no partition of relation "part_ee_ff" found for row
DETAIL: Partition key of the failing row contains (b) = (0).
Implement table partitioning. Table partitioning is like table inheritance and reuses much of the existing infrastructure, but there are some important differences. The parent is called a partitioned table and is always empty; it may not have indexes or non-inherited constraints, since those make no sense for a relation with no data of its own. The children are called partitions and contain all of the actual data. Each partition has an implicit partitioning constraint. Multiple inheritance is not allowed, and partitioning and inheritance can't be mixed. Partitions can't have extra columns and may not allow nulls unless the parent does. Tuples inserted into the parent are automatically routed to the correct partition, so tuple-routing ON INSERT triggers are not needed. Tuple routing isn't yet supported for partitions which are foreign tables, and it doesn't handle updates that cross partition boundaries. Currently, tables can be range-partitioned or list-partitioned. List partitioning is limited to a single column, but range partitioning can involve multiple columns. A partitioning "column" can be an expression. Because table partitioning is less general than table inheritance, it is hoped that it will be easier to reason about properties of partitions, and therefore that this will serve as a better foundation for a variety of possible optimizations, including query planner optimizations. The tuple routing based which this patch does based on the implicit partitioning constraints is an example of this, but it seems likely that many other useful optimizations are also possible. Amit Langote, reviewed and tested by Robert Haas, Ashutosh Bapat, Amit Kapila, Rajkumar Raghuwanshi, Corey Huinker, Jaime Casanova, Rushabh Lathia, Erik Rijkers, among others. Minor revisions by me.
2016-12-07 19:17:43 +01:00
-- ok
insert into list_parted values ('EE', 1);
insert into part_ee_ff values ('EE', 10);
select tableoid::regclass, * from list_parted;
tableoid | a | b
-------------+----+----
part_aa_bb | aA |
part_cc_dd | cC | 1
part_null | | 0
part_null | | 1
part_ee_ff1 | ff | 1
part_ee_ff1 | EE | 1
part_ee_ff2 | ff | 11
part_ee_ff2 | EE | 10
(8 rows)
-- some more tests to exercise tuple-routing with multi-level partitioning
create table part_gg partition of list_parted for values in ('gg') partition by range (b);
create table part_gg1 partition of part_gg for values from (unbounded) to (1);
create table part_gg2 partition of part_gg for values from (1) to (10) partition by range (b);
create table part_gg2_1 partition of part_gg2 for values from (1) to (5);
create table part_gg2_2 partition of part_gg2 for values from (5) to (10);
create table part_ee_ff3 partition of part_ee_ff for values from (20) to (30) partition by range (b);
create table part_ee_ff3_1 partition of part_ee_ff3 for values from (20) to (25);
create table part_ee_ff3_2 partition of part_ee_ff3 for values from (25) to (30);
truncate list_parted;
insert into list_parted values ('aa'), ('cc');
insert into list_parted select 'Ff', s.a from generate_series(1, 29) s(a);
insert into list_parted select 'gg', s.a from generate_series(1, 9) s(a);
insert into list_parted (b) values (1);
select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_parted group by 1, 2 order by 1;
tableoid | a | min_b | max_b
---------------+----+-------+-------
part_aa_bb | aa | |
part_cc_dd | cc | |
part_ee_ff1 | Ff | 1 | 9
part_ee_ff2 | Ff | 10 | 19
part_ee_ff3_1 | Ff | 20 | 24
part_ee_ff3_2 | Ff | 25 | 29
part_gg2_1 | gg | 1 | 4
part_gg2_2 | gg | 5 | 9
part_null | | 1 | 1
(9 rows)
Implement table partitioning. Table partitioning is like table inheritance and reuses much of the existing infrastructure, but there are some important differences. The parent is called a partitioned table and is always empty; it may not have indexes or non-inherited constraints, since those make no sense for a relation with no data of its own. The children are called partitions and contain all of the actual data. Each partition has an implicit partitioning constraint. Multiple inheritance is not allowed, and partitioning and inheritance can't be mixed. Partitions can't have extra columns and may not allow nulls unless the parent does. Tuples inserted into the parent are automatically routed to the correct partition, so tuple-routing ON INSERT triggers are not needed. Tuple routing isn't yet supported for partitions which are foreign tables, and it doesn't handle updates that cross partition boundaries. Currently, tables can be range-partitioned or list-partitioned. List partitioning is limited to a single column, but range partitioning can involve multiple columns. A partitioning "column" can be an expression. Because table partitioning is less general than table inheritance, it is hoped that it will be easier to reason about properties of partitions, and therefore that this will serve as a better foundation for a variety of possible optimizations, including query planner optimizations. The tuple routing based which this patch does based on the implicit partitioning constraints is an example of this, but it seems likely that many other useful optimizations are also possible. Amit Langote, reviewed and tested by Robert Haas, Ashutosh Bapat, Amit Kapila, Rajkumar Raghuwanshi, Corey Huinker, Jaime Casanova, Rushabh Lathia, Erik Rijkers, among others. Minor revisions by me.
2016-12-07 19:17:43 +01:00
-- cleanup
drop table range_parted, list_parted;
-- more tests for certain multi-level partitioning scenarios
create table mlparted (a int, b int) partition by range (a, b);
create table mlparted1 (b int not null, a int not null) partition by range ((b+0));
create table mlparted11 (like mlparted1);
alter table mlparted11 drop a;
alter table mlparted11 add a int;
alter table mlparted11 drop a;
alter table mlparted11 add a int not null;
-- attnum for key attribute 'a' is different in mlparted, mlparted1, and mlparted11
select attrelid::regclass, attname, attnum
from pg_attribute
where attname = 'a'
and (attrelid = 'mlparted'::regclass
or attrelid = 'mlparted1'::regclass
or attrelid = 'mlparted11'::regclass)
order by attrelid::regclass::text;
attrelid | attname | attnum
------------+---------+--------
mlparted | a | 1
mlparted1 | a | 2
mlparted11 | a | 4
(3 rows)
alter table mlparted1 attach partition mlparted11 for values from (2) to (5);
alter table mlparted attach partition mlparted1 for values from (1, 2) to (1, 10);
-- check that "(1, 2)" is correctly routed to mlparted11.
insert into mlparted values (1, 2);
select tableoid::regclass, * from mlparted;
tableoid | a | b
------------+---+---
mlparted11 | 1 | 2
(1 row)
-- check that proper message is shown after failure to route through mlparted1
insert into mlparted (a, b) values (1, 5);
ERROR: no partition of relation "mlparted1" found for row
DETAIL: Partition key of the failing row contains ((b + 0)) = (5).
truncate mlparted;
alter table mlparted add constraint check_b check (b = 3);
-- check that correct input row is shown when constraint check_b fails on mlparted11
-- after "(1, 2)" is routed to it
insert into mlparted values (1, 2);
ERROR: new row for relation "mlparted11" violates check constraint "check_b"
DETAIL: Failing row contains (1, 2).
-- check that inserting into an internal partition successfully results in
-- checking its partition constraint before inserting into the leaf partition
-- selected by tuple-routing
insert into mlparted1 (a, b) values (2, 3);
ERROR: new row for relation "mlparted11" violates partition constraint
DETAIL: Failing row contains (3, 2).
-- check that RETURNING works correctly with tuple-routing
alter table mlparted drop constraint check_b;
create table mlparted12 partition of mlparted1 for values from (5) to (10);
create table mlparted2 (b int not null, a int not null);
alter table mlparted attach partition mlparted2 for values from (1, 10) to (1, 20);
create table mlparted3 partition of mlparted for values from (1, 20) to (1, 30);
create table mlparted4 (like mlparted);
alter table mlparted4 drop a;
alter table mlparted4 add a int not null;
alter table mlparted attach partition mlparted4 for values from (1, 30) to (1, 40);
with ins (a, b, c) as
(insert into mlparted (b, a) select s.a, 1 from generate_series(2, 39) s(a) returning tableoid::regclass, *)
select a, b, min(c), max(c) from ins group by a, b order by 1;
a | b | min | max
------------+---+-----+-----
mlparted11 | 1 | 2 | 4
mlparted12 | 1 | 5 | 9
mlparted2 | 1 | 10 | 19
mlparted3 | 1 | 20 | 29
mlparted4 | 1 | 30 | 39
(5 rows)
-- check that message shown after failure to find a partition shows the
-- appropriate key description (or none) in various situations
create table key_desc (a int, b int) partition by list ((a+0));
create table key_desc_1 partition of key_desc for values in (1) partition by range (b);
create user someone_else;
grant select (a) on key_desc_1 to someone_else;
grant insert on key_desc to someone_else;
set role someone_else;
-- no key description is shown
insert into key_desc values (1, 1);
ERROR: no partition of relation "key_desc_1" found for row
reset role;
grant select (b) on key_desc_1 to someone_else;
set role someone_else;
-- key description (b)=(1) is now shown
insert into key_desc values (1, 1);
ERROR: no partition of relation "key_desc_1" found for row
DETAIL: Partition key of the failing row contains (b) = (1).
-- key description is not shown if key contains expression
insert into key_desc values (2, 1);
ERROR: no partition of relation "key_desc" found for row
reset role;
revoke all on key_desc from someone_else;
revoke all on key_desc_1 from someone_else;
drop role someone_else;
drop table key_desc, key_desc_1;