1
0
mirror of https://github.com/postgres/postgres.git synced 2025-11-03 09:13:20 +03:00

Restructure ALTER TABLE execution to fix assorted bugs.

We've had numerous bug reports about how (1) IF NOT EXISTS clauses in
ALTER TABLE don't behave as-expected, and (2) combining certain actions
into one ALTER TABLE doesn't work, though executing the same actions as
separate statements does.  This patch cleans up all of the cases so far
reported from the field, though there are still some oddities associated
with identity columns.

The core problem behind all of these bugs is that we do parse analysis
of ALTER TABLE subcommands too soon, before starting execution of the
statement.  The root of the bugs in group (1) is that parse analysis
schedules derived commands (such as a CREATE SEQUENCE for a serial
column) before it's known whether the IF NOT EXISTS clause should cause
a subcommand to be skipped.  The root of the bugs in group (2) is that
earlier subcommands may change the catalog state that later subcommands
need to be parsed against.

Hence, postpone parse analysis of ALTER TABLE's subcommands, and do
that one subcommand at a time, during "phase 2" of ALTER TABLE which
is the phase that does catalog rewrites.  Thus the catalog effects
of earlier subcommands are already visible when we analyze later ones.
(The sole exception is that we do parse analysis for ALTER COLUMN TYPE
subcommands during phase 1, so that their USING expressions can be
parsed against the table's original state, which is what we need.
Arguably, these bugs stem from falsely concluding that because ALTER
COLUMN TYPE must do early parse analysis, every other command subtype
can too.)

This means that ALTER TABLE itself must deal with execution of any
non-ALTER-TABLE derived statements that are generated by parse analysis.
Add a suitable entry point to utility.c to accept those recursive
calls, and create a struct to pass through the information needed by
the recursive call, rather than making the argument lists of
AlterTable() and friends even longer.

Getting this to work correctly required a little bit of fiddling
with the subcommand pass structure, in particular breaking up
AT_PASS_ADD_CONSTR into multiple passes.  But otherwise it's mostly
a pretty straightforward application of the above ideas.

Fixing the residual issues for identity columns requires refactoring of
where the dependency link from an identity column to its sequence gets
set up.  So that seems like suitable material for a separate patch,
especially since this one is pretty big already.

Discussion: https://postgr.es/m/10365.1558909428@sss.pgh.pa.us
This commit is contained in:
Tom Lane
2020-01-15 18:49:24 -05:00
parent a166d408eb
commit 1281a5c907
13 changed files with 827 additions and 267 deletions

View File

@@ -1342,17 +1342,19 @@ alter table anothertab alter column f5 type bigint;
drop table anothertab;
create table another (f1 int, f2 text);
-- test that USING expressions are parsed before column alter type / drop steps
create table another (f1 int, f2 text, f3 text);
insert into another values(1, 'one');
insert into another values(2, 'two');
insert into another values(3, 'three');
insert into another values(1, 'one', 'uno');
insert into another values(2, 'two', 'due');
insert into another values(3, 'three', 'tre');
select * from another;
alter table another
alter f1 type text using f2 || ' more',
alter f2 type bigint using f1 * 10;
alter f1 type text using f2 || ' and ' || f3 || ' more',
alter f2 type bigint using f1 * 10,
drop column f3;
select * from another;
@@ -2170,22 +2172,50 @@ ALTER TABLE ONLY test_add_column
\d test_add_column
ALTER TABLE test_add_column
ADD COLUMN c2 integer, -- fail because c2 already exists
ADD COLUMN c3 integer;
ADD COLUMN c3 integer primary key;
\d test_add_column
ALTER TABLE test_add_column
ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
ADD COLUMN c3 integer; -- fail because c3 already exists
ADD COLUMN c3 integer primary key;
\d test_add_column
ALTER TABLE test_add_column
ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
ADD COLUMN IF NOT EXISTS c3 integer; -- skipping because c3 already exists
ADD COLUMN IF NOT EXISTS c3 integer primary key; -- skipping because c3 already exists
\d test_add_column
ALTER TABLE test_add_column
ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
ADD COLUMN IF NOT EXISTS c3 integer, -- skipping because c3 already exists
ADD COLUMN c4 integer;
ADD COLUMN c4 integer REFERENCES test_add_column;
\d test_add_column
ALTER TABLE test_add_column
ADD COLUMN IF NOT EXISTS c4 integer REFERENCES test_add_column;
\d test_add_column
ALTER TABLE test_add_column
ADD COLUMN IF NOT EXISTS c5 SERIAL CHECK (c5 > 8);
\d test_add_column
ALTER TABLE test_add_column
ADD COLUMN IF NOT EXISTS c5 SERIAL CHECK (c5 > 10);
\d test_add_column*
DROP TABLE test_add_column;
\d test_add_column*
-- assorted cases with multiple ALTER TABLE steps
CREATE TABLE ataddindex(f1 INT);
INSERT INTO ataddindex VALUES (42), (43);
CREATE UNIQUE INDEX ataddindexi0 ON ataddindex(f1);
ALTER TABLE ataddindex
ADD PRIMARY KEY USING INDEX ataddindexi0,
ALTER f1 TYPE BIGINT;
\d ataddindex
DROP TABLE ataddindex;
CREATE TABLE ataddindex(f1 VARCHAR(10));
INSERT INTO ataddindex(f1) VALUES ('foo'), ('a');
ALTER TABLE ataddindex
ALTER f1 SET DATA TYPE TEXT,
ADD EXCLUDE ((f1 LIKE 'a') WITH =);
\d ataddindex
DROP TABLE ataddindex;
-- unsupported constraint types for partitioned tables
CREATE TABLE partitioned (