Skip to content

Commit 0d18852

Browse files
committed
Disallow CREATE INDEX if table is already in use in current session.
If we allow this, whatever outer command has the table open will not know about the new index and may fail to update it as needed, as shown in a report from Laurenz Albe. We already had such a prohibition in place for ALTER TABLE, but the CREATE INDEX syntax missed the check. Fixing it requires an API change for DefineIndex(), which conceivably would break third-party extensions if we were to back-patch it. Given how long this problem has existed without being noticed, fixing it in the back branches doesn't seem worth that risk. Discussion: https://postgr.es/m/A737B7A37273E048B164557ADEF4A58B53A4DC9A@ntex2010i.host.magwien.gv.at
1 parent 55a70a0 commit 0d18852

File tree

7 files changed

+78
-0
lines changed

7 files changed

+78
-0
lines changed

src/backend/bootstrap/bootparse.y

+2
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ Boot_DeclareIndexStmt:
323323
$4,
324324
false,
325325
false,
326+
false,
326327
true, /* skip_build */
327328
false);
328329
do_end();
@@ -366,6 +367,7 @@ Boot_DeclareUniqueIndexStmt:
366367
$5,
367368
false,
368369
false,
370+
false,
369371
true, /* skip_build */
370372
false);
371373
do_end();

src/backend/commands/indexcmds.c

+13
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,9 @@ CheckIndexCompatible(Oid oldId,
295295
* 'is_alter_table': this is due to an ALTER rather than a CREATE operation.
296296
* 'check_rights': check for CREATE rights in namespace and tablespace. (This
297297
* should be true except when ALTER is deleting/recreating an index.)
298+
* 'check_not_in_use': check for table not already in use in current session.
299+
* This should be true unless caller is holding the table open, in which
300+
* case the caller had better have checked it earlier.
298301
* 'skip_build': make the catalog entries but leave the index file empty;
299302
* it will be filled later.
300303
* 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
@@ -307,6 +310,7 @@ DefineIndex(Oid relationId,
307310
Oid indexRelationId,
308311
bool is_alter_table,
309312
bool check_rights,
313+
bool check_not_in_use,
310314
bool skip_build,
311315
bool quiet)
312316
{
@@ -404,6 +408,15 @@ DefineIndex(Oid relationId,
404408
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
405409
errmsg("cannot create indexes on temporary tables of other sessions")));
406410

411+
/*
412+
* Unless our caller vouches for having checked this already, insist that
413+
* the table not be in use by our own session, either. Otherwise we might
414+
* fail to make entries in the new index (for instance, if an INSERT or
415+
* UPDATE is in progress and has already made its list of target indexes).
416+
*/
417+
if (check_not_in_use)
418+
CheckTableNotInUse(rel, "CREATE INDEX");
419+
407420
/*
408421
* Verify we (still) have CREATE rights in the rel's namespace.
409422
* (Presumably we did when the rel was created, but maybe not anymore.)

src/backend/commands/tablecmds.c

+1
Original file line numberDiff line numberDiff line change
@@ -6679,6 +6679,7 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
66796679
InvalidOid, /* no predefined OID */
66806680
true, /* is_alter_table */
66816681
check_rights,
6682+
false, /* check_not_in_use - we did it already */
66826683
skip_build,
66836684
quiet);
66846685

src/backend/tcop/utility.c

+1
Original file line numberDiff line numberDiff line change
@@ -1329,6 +1329,7 @@ ProcessUtilitySlow(ParseState *pstate,
13291329
InvalidOid, /* no predefined OID */
13301330
false, /* is_alter_table */
13311331
true, /* check_rights */
1332+
true, /* check_not_in_use */
13321333
false, /* skip_build */
13331334
false); /* quiet */
13341335

src/include/commands/defrem.h

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ extern ObjectAddress DefineIndex(Oid relationId,
2727
Oid indexRelationId,
2828
bool is_alter_table,
2929
bool check_rights,
30+
bool check_not_in_use,
3031
bool skip_build,
3132
bool quiet);
3233
extern Oid ReindexIndex(RangeVar *indexRelation, int options);

src/test/regress/expected/triggers.out

+29
Original file line numberDiff line numberDiff line change
@@ -1674,6 +1674,35 @@ drop table self_ref_trigger;
16741674
drop function self_ref_trigger_ins_func();
16751675
drop function self_ref_trigger_del_func();
16761676
--
1677+
-- Check that index creation (or DDL in general) is prohibited in a trigger
1678+
--
1679+
create table trigger_ddl_table (
1680+
col1 integer,
1681+
col2 integer
1682+
);
1683+
create function trigger_ddl_func() returns trigger as $$
1684+
begin
1685+
alter table trigger_ddl_table add primary key (col1);
1686+
return new;
1687+
end$$ language plpgsql;
1688+
create trigger trigger_ddl_func before insert on trigger_ddl_table for each row
1689+
execute procedure trigger_ddl_func();
1690+
insert into trigger_ddl_table values (1, 42); -- fail
1691+
ERROR: cannot ALTER TABLE "trigger_ddl_table" because it is being used by active queries in this session
1692+
CONTEXT: SQL statement "alter table trigger_ddl_table add primary key (col1)"
1693+
PL/pgSQL function trigger_ddl_func() line 3 at SQL statement
1694+
create or replace function trigger_ddl_func() returns trigger as $$
1695+
begin
1696+
create index on trigger_ddl_table (col2);
1697+
return new;
1698+
end$$ language plpgsql;
1699+
insert into trigger_ddl_table values (1, 42); -- fail
1700+
ERROR: cannot CREATE INDEX "trigger_ddl_table" because it is being used by active queries in this session
1701+
CONTEXT: SQL statement "create index on trigger_ddl_table (col2)"
1702+
PL/pgSQL function trigger_ddl_func() line 3 at SQL statement
1703+
drop table trigger_ddl_table;
1704+
drop function trigger_ddl_func();
1705+
--
16771706
-- Verify behavior of before and after triggers with INSERT...ON CONFLICT
16781707
-- DO UPDATE
16791708
--

src/test/regress/sql/triggers.sql

+31
Original file line numberDiff line numberDiff line change
@@ -1183,6 +1183,37 @@ drop table self_ref_trigger;
11831183
drop function self_ref_trigger_ins_func();
11841184
drop function self_ref_trigger_del_func();
11851185

1186+
--
1187+
-- Check that index creation (or DDL in general) is prohibited in a trigger
1188+
--
1189+
1190+
create table trigger_ddl_table (
1191+
col1 integer,
1192+
col2 integer
1193+
);
1194+
1195+
create function trigger_ddl_func() returns trigger as $$
1196+
begin
1197+
alter table trigger_ddl_table add primary key (col1);
1198+
return new;
1199+
end$$ language plpgsql;
1200+
1201+
create trigger trigger_ddl_func before insert on trigger_ddl_table for each row
1202+
execute procedure trigger_ddl_func();
1203+
1204+
insert into trigger_ddl_table values (1, 42); -- fail
1205+
1206+
create or replace function trigger_ddl_func() returns trigger as $$
1207+
begin
1208+
create index on trigger_ddl_table (col2);
1209+
return new;
1210+
end$$ language plpgsql;
1211+
1212+
insert into trigger_ddl_table values (1, 42); -- fail
1213+
1214+
drop table trigger_ddl_table;
1215+
drop function trigger_ddl_func();
1216+
11861217
--
11871218
-- Verify behavior of before and after triggers with INSERT...ON CONFLICT
11881219
-- DO UPDATE

0 commit comments

Comments
 (0)