Avoid using INFO elevel for what are fundamentally debug messages.

Commit 6f6b99d13 stuck an INFO message into the fast path for
checking partition constraints, for no very good reason except
that it made it easy for the regression tests to verify that
that path was taken.  Assorted later patches did likewise,
increasing the unsuppressable-chatter level from ALTER TABLE
even more.  This isn't good for the user experience, so let's
drop these messages down to DEBUG1 where they belong.  So as
not to have a loss of test coverage, create a TAP test that
runs the relevant queries with client_min_messages = DEBUG1
and greps for the expected messages.

This testing method is a bit brute-force --- in particular,
it duplicates the execution of a fair amount of the core
create_table and alter_table tests.  We experimented with
other solutions, but running any significant amount of
standard testing with client_min_messages = DEBUG1 seems
to have a lot of output-stability pitfalls, cf commits
bbb96c370 and 5655565c0.  Possibly at some point we'll look
into whether we can reduce the amount of test duplication.

Backpatch into v12, because some of these messages are new
in v12 and we don't really want to ship it that way.

Sergei Kornilov

Discussion: https://postgr.es/m/81911511895540@web58j.yandex.ru
Discussion: https://postgr.es/m/4859321552643736@myt5-02b80404fd9e.qloud-c.yandex.net
This commit is contained in:
Tom Lane 2019-09-07 19:03:11 -04:00
parent ca70bdaefe
commit db43831899
10 changed files with 340 additions and 20 deletions

View File

@ -15396,11 +15396,11 @@ QueuePartitionConstraintValidation(List **wqueue, Relation scanrel,
if (PartConstraintImpliedByRelConstraint(scanrel, partConstraint))
{
if (!validate_default)
ereport(INFO,
ereport(DEBUG1,
(errmsg("partition constraint for table \"%s\" is implied by existing constraints",
RelationGetRelationName(scanrel))));
else
ereport(INFO,
ereport(DEBUG1,
(errmsg("updated partition constraint for default partition \"%s\" is implied by existing constraints",
RelationGetRelationName(scanrel))));
return;

View File

@ -1253,7 +1253,7 @@ check_default_partition_contents(Relation parent, Relation default_rel,
*/
if (PartConstraintImpliedByRelConstraint(default_rel, def_part_constraints))
{
ereport(INFO,
ereport(DEBUG1,
(errmsg("updated partition constraint for default partition \"%s\" is implied by existing constraints",
RelationGetRelationName(default_rel))));
return;
@ -1304,7 +1304,7 @@ check_default_partition_contents(Relation parent, Relation default_rel,
if (PartConstraintImpliedByRelConstraint(part_rel,
def_part_constraints))
{
ereport(INFO,
ereport(DEBUG1,
(errmsg("updated partition constraint for default partition \"%s\" is implied by existing constraints",
RelationGetRelationName(part_rel))));

View File

@ -14,6 +14,7 @@ SUBDIRS = \
test_extensions \
test_ginpostinglist \
test_integerset \
test_misc \
test_parser \
test_pg_dump \
test_predtest \

4
src/test/modules/test_misc/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
# Generated subdirectories
/log/
/results/
/tmp_check/

View File

@ -0,0 +1,14 @@
# src/test/modules/test_misc/Makefile
TAP_TESTS = 1
ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)
else
subdir = src/test/modules/test_misc
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
include $(top_srcdir)/contrib/contrib-global.mk
endif

View File

@ -0,0 +1,4 @@
This directory doesn't actually contain any extension module.
What it is is a home for otherwise-unclassified TAP tests that exercise core
server features. We might equally well have called it, say, src/test/misc.

View File

@ -0,0 +1,310 @@
# Verify that ALTER TABLE optimizes certain operations as expected
use strict;
use warnings;
use PostgresNode;
use TestLib;
use Test::More tests => 42;
# Initialize a test cluster
my $node = get_new_node('master');
$node->init();
# Turn message level up to DEBUG1 so that we get the messages we want to see
$node->append_conf('postgresql.conf', 'client_min_messages = DEBUG1');
$node->start;
# Run a SQL command and return psql's stderr (including debug messages)
sub run_sql_command
{
my $sql = shift;
my $stderr;
$node->psql(
'postgres',
$sql,
stderr => \$stderr,
on_error_die => 1,
on_error_stop => 1);
return $stderr;
}
# Check whether result of run_sql_command shows that we did a verify pass
sub is_table_verified
{
my $output = shift;
return index($output, 'DEBUG: verifying table') != -1;
}
my $output;
note "test alter table set not null";
run_sql_command(
'create table atacc1 (test_a int, test_b int);
insert into atacc1 values (1, 2);');
$output = run_sql_command('alter table atacc1 alter test_a set not null;');
ok(is_table_verified($output),
'column test_a without constraint will scan table');
run_sql_command(
'alter table atacc1 alter test_a drop not null;
alter table atacc1 add constraint atacc1_constr_a_valid
check(test_a is not null);');
# normal run will verify table data
$output = run_sql_command('alter table atacc1 alter test_a set not null;');
ok(!is_table_verified($output), 'with constraint will not scan table');
ok( $output =~
m/existing constraints on column "atacc1"."test_a" are sufficient to prove that it does not contain nulls/,
'test_a proved by constraints');
run_sql_command('alter table atacc1 alter test_a drop not null;');
# we have check only for test_a column, so we need verify table for test_b
$output = run_sql_command(
'alter table atacc1 alter test_b set not null, alter test_a set not null;'
);
ok(is_table_verified($output), 'table was scanned');
# we may miss debug message for test_a constraint because we need verify table due test_b
ok( !( $output =~
m/existing constraints on column "atacc1"."test_b" are sufficient to prove that it does not contain nulls/
),
'test_b not proved by wrong constraints');
run_sql_command(
'alter table atacc1 alter test_a drop not null, alter test_b drop not null;'
);
# test with both columns having check constraints
run_sql_command(
'alter table atacc1 add constraint atacc1_constr_b_valid check(test_b is not null);'
);
$output = run_sql_command(
'alter table atacc1 alter test_b set not null, alter test_a set not null;'
);
ok(!is_table_verified($output), 'table was not scanned for both columns');
ok( $output =~
m/existing constraints on column "atacc1"."test_a" are sufficient to prove that it does not contain nulls/,
'test_a proved by constraints');
ok( $output =~
m/existing constraints on column "atacc1"."test_b" are sufficient to prove that it does not contain nulls/,
'test_b proved by constraints');
run_sql_command('drop table atacc1;');
note "test alter table attach partition";
run_sql_command(
'CREATE TABLE list_parted2 (
a int,
b char
) PARTITION BY LIST (a);
CREATE TABLE part_3_4 (
LIKE list_parted2,
CONSTRAINT check_a CHECK (a IN (3)));');
# need NOT NULL to skip table scan
$output = run_sql_command(
'ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);'
);
ok(is_table_verified($output), 'table part_3_4 scanned');
run_sql_command(
'ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
ALTER TABLE part_3_4 ALTER a SET NOT NULL;');
$output = run_sql_command(
'ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);'
);
ok(!is_table_verified($output), 'table part_3_4 not scanned');
ok( $output =~
m/partition constraint for table "part_3_4" is implied by existing constraints/,
'part_3_4 verified by existing constraints');
# test attach default partition
run_sql_command(
'CREATE TABLE list_parted2_def (
LIKE list_parted2,
CONSTRAINT check_a CHECK (a IN (5, 6)));');
$output = run_sql_command(
'ALTER TABLE list_parted2 ATTACH PARTITION list_parted2_def default;');
ok(!is_table_verified($output), 'table list_parted2_def not scanned');
ok( $output =~
m/partition constraint for table "list_parted2_def" is implied by existing constraints/,
'list_parted2_def verified by existing constraints');
$output = run_sql_command(
'CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66);'
);
ok(!is_table_verified($output), 'table list_parted2_def not scanned');
ok( $output =~
m/updated partition constraint for default partition "list_parted2_def" is implied by existing constraints/,
'updated partition constraint for default partition list_parted2_def');
# test attach another partitioned table
run_sql_command(
'CREATE TABLE part_5 (
LIKE list_parted2
) PARTITION BY LIST (b);
CREATE TABLE part_5_a PARTITION OF part_5 FOR VALUES IN (\'a\');
ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IS NOT NULL AND a = 5);'
);
$output = run_sql_command(
'ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);');
ok(!($output =~ m/verifying table "part_5"/), 'table part_5 not scanned');
ok($output =~ m/verifying table "list_parted2_def"/,
'list_parted2_def scanned');
ok( $output =~
m/partition constraint for table "part_5" is implied by existing constraints/,
'part_5 verified by existing constraints');
run_sql_command(
'ALTER TABLE list_parted2 DETACH PARTITION part_5;
ALTER TABLE part_5 DROP CONSTRAINT check_a;');
# scan should again be skipped, even though NOT NULL is now a column property
run_sql_command(
'ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)),
ALTER a SET NOT NULL;'
);
$output = run_sql_command(
'ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);');
ok(!($output =~ m/verifying table "part_5"/), 'table part_5 not scanned');
ok($output =~ m/verifying table "list_parted2_def"/,
'list_parted2_def scanned');
ok( $output =~
m/partition constraint for table "part_5" is implied by existing constraints/,
'part_5 verified by existing constraints');
# Check the case where attnos of the partitioning columns in the table being
# attached differs from the parent. It should not affect the constraint-
# checking logic that allows to skip the scan.
run_sql_command(
'CREATE TABLE part_6 (
c int,
LIKE list_parted2,
CONSTRAINT check_a CHECK (a IS NOT NULL AND a = 6)
);
ALTER TABLE part_6 DROP c;');
$output = run_sql_command(
'ALTER TABLE list_parted2 ATTACH PARTITION part_6 FOR VALUES IN (6);');
ok(!($output =~ m/verifying table "part_6"/), 'table part_6 not scanned');
ok($output =~ m/verifying table "list_parted2_def"/,
'list_parted2_def scanned');
ok( $output =~
m/partition constraint for table "part_6" is implied by existing constraints/,
'part_6 verified by existing constraints');
# Similar to above, but the table being attached is a partitioned table
# whose partition has still different attnos for the root partitioning
# columns.
run_sql_command(
'CREATE TABLE part_7 (
LIKE list_parted2,
CONSTRAINT check_a CHECK (a IS NOT NULL AND a = 7)
) PARTITION BY LIST (b);
CREATE TABLE part_7_a_null (
c int,
d int,
e int,
LIKE list_parted2, -- a will have attnum = 4
CONSTRAINT check_b CHECK (b IS NULL OR b = \'a\'),
CONSTRAINT check_a CHECK (a IS NOT NULL AND a = 7)
);
ALTER TABLE part_7_a_null DROP c, DROP d, DROP e;');
$output = run_sql_command(
'ALTER TABLE part_7 ATTACH PARTITION part_7_a_null FOR VALUES IN (\'a\', null);'
);
ok(!is_table_verified($output), 'table not scanned');
ok( $output =~
m/partition constraint for table "part_7_a_null" is implied by existing constraints/,
'part_7_a_null verified by existing constraints');
$output = run_sql_command(
'ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);');
ok(!is_table_verified($output), 'tables not scanned');
ok( $output =~
m/partition constraint for table "part_7" is implied by existing constraints/,
'part_7 verified by existing constraints');
ok( $output =~
m/updated partition constraint for default partition "list_parted2_def" is implied by existing constraints/,
'updated partition constraint for default partition list_parted2_def');
run_sql_command(
'CREATE TABLE range_parted (
a int,
b int
) PARTITION BY RANGE (a, b);
CREATE TABLE range_part1 (
a int NOT NULL CHECK (a = 1),
b int NOT NULL);');
$output = run_sql_command(
'ALTER TABLE range_parted ATTACH PARTITION range_part1 FOR VALUES FROM (1, 1) TO (1, 10);'
);
ok(is_table_verified($output), 'table range_part1 scanned');
ok( !( $output =~
m/partition constraint for table "range_part1" is implied by existing constraints/
),
'range_part1 not verified by existing constraints');
run_sql_command(
'CREATE TABLE range_part2 (
a int NOT NULL CHECK (a = 1),
b int NOT NULL CHECK (b >= 10 and b < 18)
);');
$output = run_sql_command(
'ALTER TABLE range_parted ATTACH PARTITION range_part2 FOR VALUES FROM (1, 10) TO (1, 20);'
);
ok(!is_table_verified($output), 'table range_part2 not scanned');
ok( $output =~
m/partition constraint for table "range_part2" is implied by existing constraints/,
'range_part2 verified by existing constraints');
# If a partitioned table being created or an existing table being attached
# as a partition does not have a constraint that would allow validation scan
# to be skipped, but an individual partition does, then the partition's
# validation scan is skipped.
run_sql_command(
'CREATE TABLE quuux (a int, b text) PARTITION BY LIST (a);
CREATE TABLE quuux_default PARTITION OF quuux DEFAULT PARTITION BY LIST (b);
CREATE TABLE quuux_default1 PARTITION OF quuux_default (
CONSTRAINT check_1 CHECK (a IS NOT NULL AND a = 1)
) FOR VALUES IN (\'b\');
CREATE TABLE quuux1 (a int, b text);');
$output = run_sql_command(
'ALTER TABLE quuux ATTACH PARTITION quuux1 FOR VALUES IN (1);');
ok(is_table_verified($output), 'quuux1 table scanned');
ok( !( $output =~
m/partition constraint for table "quuux1" is implied by existing constraints/
),
'quuux1 verified by existing constraints');
run_sql_command('CREATE TABLE quuux2 (a int, b text);');
$output = run_sql_command(
'ALTER TABLE quuux ATTACH PARTITION quuux2 FOR VALUES IN (2);');
ok(!($output =~ m/verifying table "quuux_default1"/),
'quuux_default1 not scanned');
ok($output =~ m/verifying table "quuux2"/, 'quuux2 scanned');
ok( $output =~
m/updated partition constraint for default partition "quuux_default1" is implied by existing constraints/,
'updated partition constraint for default partition quuux_default1');
run_sql_command('DROP TABLE quuux1, quuux2;');
# should validate for quuux1, but not for quuux2
$output = run_sql_command(
'CREATE TABLE quuux1 PARTITION OF quuux FOR VALUES IN (1);');
ok(!is_table_verified($output), 'tables not scanned');
ok( !( $output =~
m/partition constraint for table "quuux1" is implied by existing constraints/
),
'quuux1 verified by existing constraints');
$output = run_sql_command(
'CREATE TABLE quuux2 PARTITION OF quuux FOR VALUES IN (2);');
ok(!is_table_verified($output), 'tables not scanned');
ok( $output =~
m/updated partition constraint for default partition "quuux_default1" is implied by existing constraints/,
'updated partition constraint for default partition quuux_default1');
run_sql_command('DROP TABLE quuux;');
$node->stop('fast');

View File

@ -3633,11 +3633,9 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
ALTER TABLE part_3_4 ALTER a SET NOT NULL;
ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
INFO: partition constraint for table "part_3_4" is implied by existing constraints
-- check if default partition scan skipped
ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66);
INFO: updated partition constraint for default partition "list_parted2_def" is implied by existing constraints
-- check validation when attaching range partitions
CREATE TABLE range_parted (
a int,
@ -3662,7 +3660,6 @@ CREATE TABLE part2 (
b int NOT NULL CHECK (b >= 10 AND b < 18)
);
ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
INFO: partition constraint for table "part2" is implied by existing constraints
-- Create default partition
CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
-- Only one default partition is allowed, hence, following should give error
@ -3690,13 +3687,11 @@ ERROR: partition constraint is violated by some row
DELETE FROM part_5_a WHERE a NOT IN (3);
ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IS NOT NULL AND a = 5);
ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
INFO: partition constraint for table "part_5" is implied by existing constraints
ALTER TABLE list_parted2 DETACH PARTITION part_5;
ALTER TABLE part_5 DROP CONSTRAINT check_a;
-- scan should again be skipped, even though NOT NULL is now a column property
ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
INFO: partition constraint for table "part_5" is implied by existing constraints
-- Check the case where attnos of the partitioning columns in the table being
-- attached differs from the parent. It should not affect the constraint-
-- checking logic that allows to skip the scan.
@ -3707,7 +3702,6 @@ CREATE TABLE part_6 (
);
ALTER TABLE part_6 DROP c;
ALTER TABLE list_parted2 ATTACH PARTITION part_6 FOR VALUES IN (6);
INFO: partition constraint for table "part_6" is implied by existing constraints
-- Similar to above, but the table being attached is a partitioned table
-- whose partition has still different attnos for the root partitioning
-- columns.
@ -3725,10 +3719,7 @@ CREATE TABLE part_7_a_null (
);
ALTER TABLE part_7_a_null DROP c, DROP d, DROP e;
ALTER TABLE part_7 ATTACH PARTITION part_7_a_null FOR VALUES IN ('a', null);
INFO: partition constraint for table "part_7_a_null" is implied by existing constraints
ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
INFO: partition constraint for table "part_7" is implied by existing constraints
INFO: updated partition constraint for default partition "list_parted2_def" is implied by existing constraints
-- Same example, but check this time that the constraint correctly detects
-- violating rows
ALTER TABLE list_parted2 DETACH PARTITION part_7;
@ -3742,7 +3733,6 @@ SELECT tableoid::regclass, a, b FROM part_7 order by a;
(2 rows)
ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
INFO: updated partition constraint for default partition "list_parted2_def" is implied by existing constraints
ERROR: partition constraint is violated by some row
-- check that leaf partitions of default partition are scanned when
-- attaching a partitioned table.
@ -3779,12 +3769,10 @@ CREATE TABLE quuux1 (a int, b text);
ALTER TABLE quuux ATTACH PARTITION quuux1 FOR VALUES IN (1); -- validate!
CREATE TABLE quuux2 (a int, b text);
ALTER TABLE quuux ATTACH PARTITION quuux2 FOR VALUES IN (2); -- skip validation
INFO: updated partition constraint for default partition "quuux_default1" is implied by existing constraints
DROP TABLE quuux1, quuux2;
-- should validate for quuux1, but not for quuux2
CREATE TABLE quuux1 PARTITION OF quuux FOR VALUES IN (1);
CREATE TABLE quuux2 PARTITION OF quuux FOR VALUES IN (2);
INFO: updated partition constraint for default partition "quuux_default1" is implied by existing constraints
DROP TABLE quuux;
-- check validation when attaching hash partitions
-- Use hand-rolled hash functions and operator class to get predictable result
@ -4019,7 +4007,6 @@ delete from defpart_attach_test_d where a = 1;
alter table defpart_attach_test_d add check (a > 1);
-- should be attached successfully and without needing to be scanned
alter table defpart_attach_test attach partition defpart_attach_test_d default;
INFO: partition constraint for table "defpart_attach_test_d" is implied by existing constraints
-- check that attaching a partition correctly reports any rows in the default
-- partition that should not be there for the new partition to be attached
-- successfully

View File

@ -1163,7 +1163,6 @@ alter table defcheck_def drop c;
alter table defcheck attach partition defcheck_def default;
alter table defcheck_def add check (b <= 0 and b is not null);
create table defcheck_1 partition of defcheck for values in (1, null);
INFO: updated partition constraint for default partition "defcheck_def" is implied by existing constraints
-- test that complex default partition constraints are enforced correctly
insert into defcheck_def values (0, 0);
create table defcheck_0 partition of defcheck for values in (0);

View File

@ -47,8 +47,9 @@ my @contrib_excludes = (
'jsonb_plperl', 'jsonb_plpython',
'ltree_plpython', 'pgcrypto',
'sepgsql', 'brin',
'test_extensions', 'test_pg_dump',
'snapshot_too_old', 'unsafe_tests');
'test_extensions', 'test_misc',
'test_pg_dump', 'snapshot_too_old',
'unsafe_tests');
# Set of variables for frontend modules
my $frontend_defines = { 'initdb' => 'FRONTEND' };