summaryrefslogtreecommitdiff
path: root/src/test/modules/test_misc/t/001_constraint_validation.pl
blob: 64766c1e33f0894a57ec5a27b4ac64847591e432 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315

# Copyright (c) 2021-2023, PostgreSQL Global Development Group

# Verify that ALTER TABLE optimizes certain operations as expected

use strict;
use warnings;
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;

# Initialize a test cluster
my $node = PostgreSQL::Test::Cluster->new('primary');
$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');

done_testing();