|
| 1 | +# Verify that ALTER TABLE optimizes certain operations as expected |
| 2 | + |
| 3 | +use strict; |
| 4 | +use warnings; |
| 5 | +use PostgresNode; |
| 6 | +use TestLib; |
| 7 | +use Test::More tests => 42; |
| 8 | + |
| 9 | +# Initialize a test cluster |
| 10 | +my $node = get_new_node('master'); |
| 11 | +$node->init(); |
| 12 | +# Turn message level up to DEBUG1 so that we get the messages we want to see |
| 13 | +$node->append_conf('postgresql.conf', 'client_min_messages = DEBUG1'); |
| 14 | +$node->start; |
| 15 | + |
| 16 | +# Run a SQL command and return psql's stderr (including debug messages) |
| 17 | +sub run_sql_command |
| 18 | +{ |
| 19 | + my $sql = shift; |
| 20 | + my $stderr; |
| 21 | + |
| 22 | + $node->psql( |
| 23 | + 'postgres', |
| 24 | + $sql, |
| 25 | + stderr => \$stderr, |
| 26 | + on_error_die => 1, |
| 27 | + on_error_stop => 1); |
| 28 | + return $stderr; |
| 29 | +} |
| 30 | + |
| 31 | +# Check whether result of run_sql_command shows that we did a verify pass |
| 32 | +sub is_table_verified |
| 33 | +{ |
| 34 | + my $output = shift; |
| 35 | + return index($output, 'DEBUG: verifying table') != -1; |
| 36 | +} |
| 37 | + |
| 38 | +my $output; |
| 39 | + |
| 40 | +note "test alter table set not null"; |
| 41 | + |
| 42 | +run_sql_command( |
| 43 | + 'create table atacc1 (test_a int, test_b int); |
| 44 | + insert into atacc1 values (1, 2);'); |
| 45 | + |
| 46 | +$output = run_sql_command('alter table atacc1 alter test_a set not null;'); |
| 47 | +ok(is_table_verified($output), |
| 48 | + 'column test_a without constraint will scan table'); |
| 49 | + |
| 50 | +run_sql_command( |
| 51 | + 'alter table atacc1 alter test_a drop not null; |
| 52 | + alter table atacc1 add constraint atacc1_constr_a_valid |
| 53 | + check(test_a is not null);'); |
| 54 | + |
| 55 | +# normal run will verify table data |
| 56 | +$output = run_sql_command('alter table atacc1 alter test_a set not null;'); |
| 57 | +ok(!is_table_verified($output), 'with constraint will not scan table'); |
| 58 | +ok( $output =~ |
| 59 | + m/existing constraints on column "atacc1"."test_a" are sufficient to prove that it does not contain nulls/, |
| 60 | + 'test_a proved by constraints'); |
| 61 | + |
| 62 | +run_sql_command('alter table atacc1 alter test_a drop not null;'); |
| 63 | + |
| 64 | +# we have check only for test_a column, so we need verify table for test_b |
| 65 | +$output = run_sql_command( |
| 66 | + 'alter table atacc1 alter test_b set not null, alter test_a set not null;' |
| 67 | +); |
| 68 | +ok(is_table_verified($output), 'table was scanned'); |
| 69 | +# we may miss debug message for test_a constraint because we need verify table due test_b |
| 70 | +ok( !( $output =~ |
| 71 | + m/existing constraints on column "atacc1"."test_b" are sufficient to prove that it does not contain nulls/ |
| 72 | + ), |
| 73 | + 'test_b not proved by wrong constraints'); |
| 74 | +run_sql_command( |
| 75 | + 'alter table atacc1 alter test_a drop not null, alter test_b drop not null;' |
| 76 | +); |
| 77 | + |
| 78 | +# test with both columns having check constraints |
| 79 | +run_sql_command( |
| 80 | + 'alter table atacc1 add constraint atacc1_constr_b_valid check(test_b is not null);' |
| 81 | +); |
| 82 | +$output = run_sql_command( |
| 83 | + 'alter table atacc1 alter test_b set not null, alter test_a set not null;' |
| 84 | +); |
| 85 | +ok(!is_table_verified($output), 'table was not scanned for both columns'); |
| 86 | +ok( $output =~ |
| 87 | + m/existing constraints on column "atacc1"."test_a" are sufficient to prove that it does not contain nulls/, |
| 88 | + 'test_a proved by constraints'); |
| 89 | +ok( $output =~ |
| 90 | + m/existing constraints on column "atacc1"."test_b" are sufficient to prove that it does not contain nulls/, |
| 91 | + 'test_b proved by constraints'); |
| 92 | +run_sql_command('drop table atacc1;'); |
| 93 | + |
| 94 | +note "test alter table attach partition"; |
| 95 | + |
| 96 | +run_sql_command( |
| 97 | + 'CREATE TABLE list_parted2 ( |
| 98 | + a int, |
| 99 | + b char |
| 100 | + ) PARTITION BY LIST (a); |
| 101 | + CREATE TABLE part_3_4 ( |
| 102 | + LIKE list_parted2, |
| 103 | + CONSTRAINT check_a CHECK (a IN (3)));'); |
| 104 | + |
| 105 | +# need NOT NULL to skip table scan |
| 106 | +$output = run_sql_command( |
| 107 | + 'ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);' |
| 108 | +); |
| 109 | +ok(is_table_verified($output), 'table part_3_4 scanned'); |
| 110 | + |
| 111 | +run_sql_command( |
| 112 | + 'ALTER TABLE list_parted2 DETACH PARTITION part_3_4; |
| 113 | + ALTER TABLE part_3_4 ALTER a SET NOT NULL;'); |
| 114 | + |
| 115 | +$output = run_sql_command( |
| 116 | + 'ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);' |
| 117 | +); |
| 118 | +ok(!is_table_verified($output), 'table part_3_4 not scanned'); |
| 119 | +ok( $output =~ |
| 120 | + m/partition constraint for table "part_3_4" is implied by existing constraints/, |
| 121 | + 'part_3_4 verified by existing constraints'); |
| 122 | + |
| 123 | +# test attach default partition |
| 124 | +run_sql_command( |
| 125 | + 'CREATE TABLE list_parted2_def ( |
| 126 | + LIKE list_parted2, |
| 127 | + CONSTRAINT check_a CHECK (a IN (5, 6)));'); |
| 128 | +$output = run_sql_command( |
| 129 | + 'ALTER TABLE list_parted2 ATTACH PARTITION list_parted2_def default;'); |
| 130 | +ok(!is_table_verified($output), 'table list_parted2_def not scanned'); |
| 131 | +ok( $output =~ |
| 132 | + m/partition constraint for table "list_parted2_def" is implied by existing constraints/, |
| 133 | + 'list_parted2_def verified by existing constraints'); |
| 134 | + |
| 135 | +$output = run_sql_command( |
| 136 | + 'CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66);' |
| 137 | +); |
| 138 | +ok(!is_table_verified($output), 'table list_parted2_def not scanned'); |
| 139 | +ok( $output =~ |
| 140 | + m/updated partition constraint for default partition "list_parted2_def" is implied by existing constraints/, |
| 141 | + 'updated partition constraint for default partition list_parted2_def'); |
| 142 | + |
| 143 | +# test attach another partitioned table |
| 144 | +run_sql_command( |
| 145 | + 'CREATE TABLE part_5 ( |
| 146 | + LIKE list_parted2 |
| 147 | + ) PARTITION BY LIST (b); |
| 148 | + CREATE TABLE part_5_a PARTITION OF part_5 FOR VALUES IN (\'a\'); |
| 149 | + ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IS NOT NULL AND a = 5);' |
| 150 | +); |
| 151 | +$output = run_sql_command( |
| 152 | + 'ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);'); |
| 153 | +ok(!($output =~ m/verifying table "part_5"/), 'table part_5 not scanned'); |
| 154 | +ok($output =~ m/verifying table "list_parted2_def"/, |
| 155 | + 'list_parted2_def scanned'); |
| 156 | +ok( $output =~ |
| 157 | + m/partition constraint for table "part_5" is implied by existing constraints/, |
| 158 | + 'part_5 verified by existing constraints'); |
| 159 | + |
| 160 | +run_sql_command( |
| 161 | + 'ALTER TABLE list_parted2 DETACH PARTITION part_5; |
| 162 | + ALTER TABLE part_5 DROP CONSTRAINT check_a;'); |
| 163 | + |
| 164 | +# scan should again be skipped, even though NOT NULL is now a column property |
| 165 | +run_sql_command( |
| 166 | + 'ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), |
| 167 | + ALTER a SET NOT NULL;' |
| 168 | +); |
| 169 | +$output = run_sql_command( |
| 170 | + 'ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);'); |
| 171 | +ok(!($output =~ m/verifying table "part_5"/), 'table part_5 not scanned'); |
| 172 | +ok($output =~ m/verifying table "list_parted2_def"/, |
| 173 | + 'list_parted2_def scanned'); |
| 174 | +ok( $output =~ |
| 175 | + m/partition constraint for table "part_5" is implied by existing constraints/, |
| 176 | + 'part_5 verified by existing constraints'); |
| 177 | + |
| 178 | +# Check the case where attnos of the partitioning columns in the table being |
| 179 | +# attached differs from the parent. It should not affect the constraint- |
| 180 | +# checking logic that allows to skip the scan. |
| 181 | +run_sql_command( |
| 182 | + 'CREATE TABLE part_6 ( |
| 183 | + c int, |
| 184 | + LIKE list_parted2, |
| 185 | + CONSTRAINT check_a CHECK (a IS NOT NULL AND a = 6) |
| 186 | + ); |
| 187 | + ALTER TABLE part_6 DROP c;'); |
| 188 | +$output = run_sql_command( |
| 189 | + 'ALTER TABLE list_parted2 ATTACH PARTITION part_6 FOR VALUES IN (6);'); |
| 190 | +ok(!($output =~ m/verifying table "part_6"/), 'table part_6 not scanned'); |
| 191 | +ok($output =~ m/verifying table "list_parted2_def"/, |
| 192 | + 'list_parted2_def scanned'); |
| 193 | +ok( $output =~ |
| 194 | + m/partition constraint for table "part_6" is implied by existing constraints/, |
| 195 | + 'part_6 verified by existing constraints'); |
| 196 | + |
| 197 | +# Similar to above, but the table being attached is a partitioned table |
| 198 | +# whose partition has still different attnos for the root partitioning |
| 199 | +# columns. |
| 200 | +run_sql_command( |
| 201 | + 'CREATE TABLE part_7 ( |
| 202 | + LIKE list_parted2, |
| 203 | + CONSTRAINT check_a CHECK (a IS NOT NULL AND a = 7) |
| 204 | + ) PARTITION BY LIST (b); |
| 205 | + CREATE TABLE part_7_a_null ( |
| 206 | + c int, |
| 207 | + d int, |
| 208 | + e int, |
| 209 | + LIKE list_parted2, -- a will have attnum = 4 |
| 210 | + CONSTRAINT check_b CHECK (b IS NULL OR b = \'a\'), |
| 211 | + CONSTRAINT check_a CHECK (a IS NOT NULL AND a = 7) |
| 212 | + ); |
| 213 | + ALTER TABLE part_7_a_null DROP c, DROP d, DROP e;'); |
| 214 | + |
| 215 | +$output = run_sql_command( |
| 216 | + 'ALTER TABLE part_7 ATTACH PARTITION part_7_a_null FOR VALUES IN (\'a\', null);' |
| 217 | +); |
| 218 | +ok(!is_table_verified($output), 'table not scanned'); |
| 219 | +ok( $output =~ |
| 220 | + m/partition constraint for table "part_7_a_null" is implied by existing constraints/, |
| 221 | + 'part_7_a_null verified by existing constraints'); |
| 222 | +$output = run_sql_command( |
| 223 | + 'ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);'); |
| 224 | +ok(!is_table_verified($output), 'tables not scanned'); |
| 225 | +ok( $output =~ |
| 226 | + m/partition constraint for table "part_7" is implied by existing constraints/, |
| 227 | + 'part_7 verified by existing constraints'); |
| 228 | +ok( $output =~ |
| 229 | + m/updated partition constraint for default partition "list_parted2_def" is implied by existing constraints/, |
| 230 | + 'updated partition constraint for default partition list_parted2_def'); |
| 231 | + |
| 232 | +run_sql_command( |
| 233 | + 'CREATE TABLE range_parted ( |
| 234 | + a int, |
| 235 | + b int |
| 236 | + ) PARTITION BY RANGE (a, b); |
| 237 | + CREATE TABLE range_part1 ( |
| 238 | + a int NOT NULL CHECK (a = 1), |
| 239 | + b int NOT NULL);'); |
| 240 | + |
| 241 | +$output = run_sql_command( |
| 242 | + 'ALTER TABLE range_parted ATTACH PARTITION range_part1 FOR VALUES FROM (1, 1) TO (1, 10);' |
| 243 | +); |
| 244 | +ok(is_table_verified($output), 'table range_part1 scanned'); |
| 245 | +ok( !( $output =~ |
| 246 | + m/partition constraint for table "range_part1" is implied by existing constraints/ |
| 247 | + ), |
| 248 | + 'range_part1 not verified by existing constraints'); |
| 249 | + |
| 250 | +run_sql_command( |
| 251 | + 'CREATE TABLE range_part2 ( |
| 252 | + a int NOT NULL CHECK (a = 1), |
| 253 | + b int NOT NULL CHECK (b >= 10 and b < 18) |
| 254 | +);'); |
| 255 | +$output = run_sql_command( |
| 256 | + 'ALTER TABLE range_parted ATTACH PARTITION range_part2 FOR VALUES FROM (1, 10) TO (1, 20);' |
| 257 | +); |
| 258 | +ok(!is_table_verified($output), 'table range_part2 not scanned'); |
| 259 | +ok( $output =~ |
| 260 | + m/partition constraint for table "range_part2" is implied by existing constraints/, |
| 261 | + 'range_part2 verified by existing constraints'); |
| 262 | + |
| 263 | +# If a partitioned table being created or an existing table being attached |
| 264 | +# as a partition does not have a constraint that would allow validation scan |
| 265 | +# to be skipped, but an individual partition does, then the partition's |
| 266 | +# validation scan is skipped. |
| 267 | +run_sql_command( |
| 268 | + 'CREATE TABLE quuux (a int, b text) PARTITION BY LIST (a); |
| 269 | + CREATE TABLE quuux_default PARTITION OF quuux DEFAULT PARTITION BY LIST (b); |
| 270 | + CREATE TABLE quuux_default1 PARTITION OF quuux_default ( |
| 271 | + CONSTRAINT check_1 CHECK (a IS NOT NULL AND a = 1) |
| 272 | + ) FOR VALUES IN (\'b\'); |
| 273 | + CREATE TABLE quuux1 (a int, b text);'); |
| 274 | + |
| 275 | +$output = run_sql_command( |
| 276 | + 'ALTER TABLE quuux ATTACH PARTITION quuux1 FOR VALUES IN (1);'); |
| 277 | +ok(is_table_verified($output), 'quuux1 table scanned'); |
| 278 | +ok( !( $output =~ |
| 279 | + m/partition constraint for table "quuux1" is implied by existing constraints/ |
| 280 | + ), |
| 281 | + 'quuux1 verified by existing constraints'); |
| 282 | + |
| 283 | +run_sql_command('CREATE TABLE quuux2 (a int, b text);'); |
| 284 | +$output = run_sql_command( |
| 285 | + 'ALTER TABLE quuux ATTACH PARTITION quuux2 FOR VALUES IN (2);'); |
| 286 | +ok(!($output =~ m/verifying table "quuux_default1"/), |
| 287 | + 'quuux_default1 not scanned'); |
| 288 | +ok($output =~ m/verifying table "quuux2"/, 'quuux2 scanned'); |
| 289 | +ok( $output =~ |
| 290 | + m/updated partition constraint for default partition "quuux_default1" is implied by existing constraints/, |
| 291 | + 'updated partition constraint for default partition quuux_default1'); |
| 292 | +run_sql_command('DROP TABLE quuux1, quuux2;'); |
| 293 | + |
| 294 | +# should validate for quuux1, but not for quuux2 |
| 295 | +$output = run_sql_command( |
| 296 | + 'CREATE TABLE quuux1 PARTITION OF quuux FOR VALUES IN (1);'); |
| 297 | +ok(!is_table_verified($output), 'tables not scanned'); |
| 298 | +ok( !( $output =~ |
| 299 | + m/partition constraint for table "quuux1" is implied by existing constraints/ |
| 300 | + ), |
| 301 | + 'quuux1 verified by existing constraints'); |
| 302 | +$output = run_sql_command( |
| 303 | + 'CREATE TABLE quuux2 PARTITION OF quuux FOR VALUES IN (2);'); |
| 304 | +ok(!is_table_verified($output), 'tables not scanned'); |
| 305 | +ok( $output =~ |
| 306 | + m/updated partition constraint for default partition "quuux_default1" is implied by existing constraints/, |
| 307 | + 'updated partition constraint for default partition quuux_default1'); |
| 308 | +run_sql_command('DROP TABLE quuux;'); |
| 309 | + |
| 310 | +$node->stop('fast'); |
0 commit comments