1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-07 00:36:50 +03:00

Obtain required table lock during cross-table updates, redux.

Commits 8319e5cb5 et al missed the fact that ATPostAlterTypeCleanup
contains three calls to ATPostAlterTypeParse, and the other two
also need protection against passing a relid that we don't yet
have lock on.  Add similar logic to those code paths, and add
some test cases demonstrating the need for it.

In v18 and master, the test cases demonstrate that there's a
behavioral discrepancy between stored generated columns and virtual
generated columns: we disallow changing the expression of a stored
column if it's used in any composite-type columns, but not that of
a virtual column.  Since the expression isn't actually relevant to
either sort of composite-type usage, this prohibition seems
unnecessary; but changing it is a matter for separate discussion.
For now we are just documenting the existing behavior.

Reported-by: jian he <jian.universality@gmail.com>
Author: jian he <jian.universality@gmail.com>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: CACJufxGKJtGNRRSXfwMW9SqVOPEMdP17BJ7DsBf=tNsv9pWU9g@mail.gmail.com
Backpatch-through: 13
This commit is contained in:
Tom Lane
2025-07-03 13:46:07 -04:00
parent a604affade
commit a10f21e6ce
7 changed files with 85 additions and 0 deletions

View File

@ -15487,6 +15487,14 @@ ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode)
Oid relid; Oid relid;
relid = IndexGetRelation(oldId, false); relid = IndexGetRelation(oldId, false);
/*
* As above, make sure we have lock on the index's table if it's not
* the same table.
*/
if (relid != tab->relid)
LockRelationOid(relid, AccessExclusiveLock);
ATPostAlterTypeParse(oldId, relid, InvalidOid, ATPostAlterTypeParse(oldId, relid, InvalidOid,
(char *) lfirst(def_item), (char *) lfirst(def_item),
wqueue, lockmode, tab->rewrite); wqueue, lockmode, tab->rewrite);
@ -15503,6 +15511,20 @@ ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode)
Oid relid; Oid relid;
relid = StatisticsGetRelation(oldId, false); relid = StatisticsGetRelation(oldId, false);
/*
* As above, make sure we have lock on the statistics object's table
* if it's not the same table. However, we take
* ShareUpdateExclusiveLock here, aligning with the lock level used in
* CreateStatistics and RemoveStatisticsById.
*
* CAUTION: this should be done after all cases that grab
* AccessExclusiveLock, else we risk causing deadlock due to needing
* to promote our table lock.
*/
if (relid != tab->relid)
LockRelationOid(relid, ShareUpdateExclusiveLock);
ATPostAlterTypeParse(oldId, relid, InvalidOid, ATPostAlterTypeParse(oldId, relid, InvalidOid,
(char *) lfirst(def_item), (char *) lfirst(def_item),
wqueue, lockmode, tab->rewrite); wqueue, lockmode, tab->rewrite);

View File

@ -4750,6 +4750,14 @@ create table attbl(a int);
create table atref(b attbl check ((b).a is not null)); create table atref(b attbl check ((b).a is not null));
alter table attbl alter column a type numeric; -- someday this should work alter table attbl alter column a type numeric; -- someday this should work
ERROR: cannot alter table "attbl" because column "atref.b" uses its row type ERROR: cannot alter table "attbl" because column "atref.b" uses its row type
alter table atref drop constraint atref_b_check;
create statistics atref_stat on ((b).a is not null) from atref;
alter table attbl alter column a type numeric; -- someday this should work
ERROR: cannot alter table "attbl" because column "atref.b" uses its row type
drop statistics atref_stat;
create index atref_idx on atref (((b).a));
alter table attbl alter column a type numeric; -- someday this should work
ERROR: cannot alter table "attbl" because column "atref.b" uses its row type
drop table attbl, atref; drop table attbl, atref;
/* End test case for bug #18970 */ /* End test case for bug #18970 */
-- Test that ALTER TABLE rewrite preserves a clustered index -- Test that ALTER TABLE rewrite preserves a clustered index

View File

@ -1313,6 +1313,18 @@ CREATE TABLE gtest31_1 (a int, b text GENERATED ALWAYS AS ('hello') STORED, c te
CREATE TABLE gtest31_2 (x int, y gtest31_1); CREATE TABLE gtest31_2 (x int, y gtest31_1);
ALTER TABLE gtest31_1 ALTER COLUMN b TYPE varchar; -- fails ALTER TABLE gtest31_1 ALTER COLUMN b TYPE varchar; -- fails
ERROR: cannot alter table "gtest31_1" because column "gtest31_2.y" uses its row type ERROR: cannot alter table "gtest31_1" because column "gtest31_2.y" uses its row type
-- bug #18970: these cases are unsupported, but make sure they fail cleanly
ALTER TABLE gtest31_2 ADD CONSTRAINT cc CHECK ((y).b IS NOT NULL);
ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello1');
ERROR: cannot alter table "gtest31_1" because column "gtest31_2.y" uses its row type
ALTER TABLE gtest31_2 DROP CONSTRAINT cc;
CREATE STATISTICS gtest31_2_stat ON ((y).b is not null) FROM gtest31_2;
ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello2');
ERROR: cannot alter table "gtest31_1" because column "gtest31_2.y" uses its row type
DROP STATISTICS gtest31_2_stat;
CREATE INDEX gtest31_2_y_idx ON gtest31_2(((y).b));
ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello3');
ERROR: cannot alter table "gtest31_1" because column "gtest31_2.y" uses its row type
DROP TABLE gtest31_1, gtest31_2; DROP TABLE gtest31_1, gtest31_2;
-- Check it for a partitioned table, too -- Check it for a partitioned table, too
CREATE TABLE gtest31_1 (a int, b text GENERATED ALWAYS AS ('hello') STORED, c text) PARTITION BY LIST (a); CREATE TABLE gtest31_1 (a int, b text GENERATED ALWAYS AS ('hello') STORED, c text) PARTITION BY LIST (a);

View File

@ -1283,6 +1283,15 @@ CREATE TABLE gtest31_1 (a int, b text GENERATED ALWAYS AS ('hello') VIRTUAL, c t
CREATE TABLE gtest31_2 (x int, y gtest31_1); CREATE TABLE gtest31_2 (x int, y gtest31_1);
ALTER TABLE gtest31_1 ALTER COLUMN b TYPE varchar; -- fails ALTER TABLE gtest31_1 ALTER COLUMN b TYPE varchar; -- fails
ERROR: cannot alter table "gtest31_1" because column "gtest31_2.y" uses its row type ERROR: cannot alter table "gtest31_1" because column "gtest31_2.y" uses its row type
-- bug #18970
ALTER TABLE gtest31_2 ADD CONSTRAINT cc CHECK ((y).b IS NOT NULL);
ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello1');
ALTER TABLE gtest31_2 DROP CONSTRAINT cc;
CREATE STATISTICS gtest31_2_stat ON ((y).b is not null) FROM gtest31_2;
ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello2');
DROP STATISTICS gtest31_2_stat;
CREATE INDEX gtest31_2_y_idx ON gtest31_2(((y).b));
ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello3');
DROP TABLE gtest31_1, gtest31_2; DROP TABLE gtest31_1, gtest31_2;
-- Check it for a partitioned table, too -- Check it for a partitioned table, too
CREATE TABLE gtest31_1 (a int, b text GENERATED ALWAYS AS ('hello') VIRTUAL, c text) PARTITION BY LIST (a); CREATE TABLE gtest31_1 (a int, b text GENERATED ALWAYS AS ('hello') VIRTUAL, c text) PARTITION BY LIST (a);

View File

@ -3074,6 +3074,14 @@ drop table attbl, atref;
create table attbl(a int); create table attbl(a int);
create table atref(b attbl check ((b).a is not null)); create table atref(b attbl check ((b).a is not null));
alter table attbl alter column a type numeric; -- someday this should work alter table attbl alter column a type numeric; -- someday this should work
alter table atref drop constraint atref_b_check;
create statistics atref_stat on ((b).a is not null) from atref;
alter table attbl alter column a type numeric; -- someday this should work
drop statistics atref_stat;
create index atref_idx on atref (((b).a));
alter table attbl alter column a type numeric; -- someday this should work
drop table attbl, atref; drop table attbl, atref;
/* End test case for bug #18970 */ /* End test case for bug #18970 */

View File

@ -595,6 +595,19 @@ ALTER TABLE gtest30_1 ALTER COLUMN b DROP EXPRESSION; -- error
CREATE TABLE gtest31_1 (a int, b text GENERATED ALWAYS AS ('hello') STORED, c text); CREATE TABLE gtest31_1 (a int, b text GENERATED ALWAYS AS ('hello') STORED, c text);
CREATE TABLE gtest31_2 (x int, y gtest31_1); CREATE TABLE gtest31_2 (x int, y gtest31_1);
ALTER TABLE gtest31_1 ALTER COLUMN b TYPE varchar; -- fails ALTER TABLE gtest31_1 ALTER COLUMN b TYPE varchar; -- fails
-- bug #18970: these cases are unsupported, but make sure they fail cleanly
ALTER TABLE gtest31_2 ADD CONSTRAINT cc CHECK ((y).b IS NOT NULL);
ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello1');
ALTER TABLE gtest31_2 DROP CONSTRAINT cc;
CREATE STATISTICS gtest31_2_stat ON ((y).b is not null) FROM gtest31_2;
ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello2');
DROP STATISTICS gtest31_2_stat;
CREATE INDEX gtest31_2_y_idx ON gtest31_2(((y).b));
ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello3');
DROP TABLE gtest31_1, gtest31_2; DROP TABLE gtest31_1, gtest31_2;
-- Check it for a partitioned table, too -- Check it for a partitioned table, too

View File

@ -646,6 +646,19 @@ ALTER TABLE gtest30_1 ALTER COLUMN b DROP EXPRESSION; -- error
CREATE TABLE gtest31_1 (a int, b text GENERATED ALWAYS AS ('hello') VIRTUAL, c text); CREATE TABLE gtest31_1 (a int, b text GENERATED ALWAYS AS ('hello') VIRTUAL, c text);
CREATE TABLE gtest31_2 (x int, y gtest31_1); CREATE TABLE gtest31_2 (x int, y gtest31_1);
ALTER TABLE gtest31_1 ALTER COLUMN b TYPE varchar; -- fails ALTER TABLE gtest31_1 ALTER COLUMN b TYPE varchar; -- fails
-- bug #18970
ALTER TABLE gtest31_2 ADD CONSTRAINT cc CHECK ((y).b IS NOT NULL);
ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello1');
ALTER TABLE gtest31_2 DROP CONSTRAINT cc;
CREATE STATISTICS gtest31_2_stat ON ((y).b is not null) FROM gtest31_2;
ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello2');
DROP STATISTICS gtest31_2_stat;
CREATE INDEX gtest31_2_y_idx ON gtest31_2(((y).b));
ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello3');
DROP TABLE gtest31_1, gtest31_2; DROP TABLE gtest31_1, gtest31_2;
-- Check it for a partitioned table, too -- Check it for a partitioned table, too