]> granicus.if.org Git - postgresql/commitdiff
Disallow CREATE INDEX if table is already in use in current session.
authorTom Lane <tgl@sss.pgh.pa.us>
Sun, 4 Jun 2017 16:02:31 +0000 (12:02 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Sun, 4 Jun 2017 16:02:41 +0000 (12:02 -0400)
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

src/backend/bootstrap/bootparse.y
src/backend/commands/indexcmds.c
src/backend/commands/tablecmds.c
src/backend/tcop/utility.c
src/include/commands/defrem.h
src/test/regress/expected/triggers.out
src/test/regress/sql/triggers.sql

index de3695c7e05f3981119afdf9c996856b198ac33e..2e1fef03504e57a0d88ef84ead740d8576571742 100644 (file)
@@ -323,6 +323,7 @@ Boot_DeclareIndexStmt:
                                                                $4,
                                                                false,
                                                                false,
+                                                               false,
                                                                true, /* skip_build */
                                                                false);
                                        do_end();
@@ -366,6 +367,7 @@ Boot_DeclareUniqueIndexStmt:
                                                                $5,
                                                                false,
                                                                false,
+                                                               false,
                                                                true, /* skip_build */
                                                                false);
                                        do_end();
index 486179938c3e8537dc2cf631f4693a488168a53d..c0902794e98f14b5e7800135c31b6f15fb62ceaa 100644 (file)
@@ -295,6 +295,9 @@ CheckIndexCompatible(Oid oldId,
  * 'is_alter_table': this is due to an ALTER rather than a CREATE operation.
  * 'check_rights': check for CREATE rights in namespace and tablespace.  (This
  *             should be true except when ALTER is deleting/recreating an index.)
+ * 'check_not_in_use': check for table not already in use in current session.
+ *             This should be true unless caller is holding the table open, in which
+ *             case the caller had better have checked it earlier.
  * 'skip_build': make the catalog entries but leave the index file empty;
  *             it will be filled later.
  * 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
@@ -307,6 +310,7 @@ DefineIndex(Oid relationId,
                        Oid indexRelationId,
                        bool is_alter_table,
                        bool check_rights,
+                       bool check_not_in_use,
                        bool skip_build,
                        bool quiet)
 {
@@ -404,6 +408,15 @@ DefineIndex(Oid relationId,
                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                 errmsg("cannot create indexes on temporary tables of other sessions")));
 
+       /*
+        * Unless our caller vouches for having checked this already, insist that
+        * the table not be in use by our own session, either.  Otherwise we might
+        * fail to make entries in the new index (for instance, if an INSERT or
+        * UPDATE is in progress and has already made its list of target indexes).
+        */
+       if (check_not_in_use)
+               CheckTableNotInUse(rel, "CREATE INDEX");
+
        /*
         * Verify we (still) have CREATE rights in the rel's namespace.
         * (Presumably we did when the rel was created, but maybe not anymore.)
index 7959120f53eb3a17210a55a124f2f301982f3eee..b61fda9909d5d6dae53d4cce114f2c8c0b6a0171 100644 (file)
@@ -6679,6 +6679,7 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
                                                  InvalidOid,   /* no predefined OID */
                                                  true, /* is_alter_table */
                                                  check_rights,
+                                                 false,        /* check_not_in_use - we did it already */
                                                  skip_build,
                                                  quiet);
 
index 1e941fbd600276b9c99b81540b369f9ef058e642..a22fd5397e59d0fb57aead07252930ca246ec4ea 100644 (file)
@@ -1329,6 +1329,7 @@ ProcessUtilitySlow(ParseState *pstate,
                                                                        InvalidOid, /* no predefined OID */
                                                                        false,          /* is_alter_table */
                                                                        true,           /* check_rights */
+                                                                       true,           /* check_not_in_use */
                                                                        false,          /* skip_build */
                                                                        false);         /* quiet */
 
index 79f3be36e496dcadc8007d556042562b1e45ae39..5dd14b43d3b1e886a571e71ff65b2c7de52449f1 100644 (file)
@@ -27,6 +27,7 @@ extern ObjectAddress DefineIndex(Oid relationId,
                        Oid indexRelationId,
                        bool is_alter_table,
                        bool check_rights,
+                       bool check_not_in_use,
                        bool skip_build,
                        bool quiet);
 extern Oid     ReindexIndex(RangeVar *indexRelation, int options);
index 0d560fb3eed3519f09fbe77d00b20faabefa9c2d..29b8adf1e23cc85554f2acc2cbf6d346280a43bb 100644 (file)
@@ -1674,6 +1674,35 @@ drop table self_ref_trigger;
 drop function self_ref_trigger_ins_func();
 drop function self_ref_trigger_del_func();
 --
+-- Check that index creation (or DDL in general) is prohibited in a trigger
+--
+create table trigger_ddl_table (
+   col1 integer,
+   col2 integer
+);
+create function trigger_ddl_func() returns trigger as $$
+begin
+  alter table trigger_ddl_table add primary key (col1);
+  return new;
+end$$ language plpgsql;
+create trigger trigger_ddl_func before insert on trigger_ddl_table for each row
+  execute procedure trigger_ddl_func();
+insert into trigger_ddl_table values (1, 42);  -- fail
+ERROR:  cannot ALTER TABLE "trigger_ddl_table" because it is being used by active queries in this session
+CONTEXT:  SQL statement "alter table trigger_ddl_table add primary key (col1)"
+PL/pgSQL function trigger_ddl_func() line 3 at SQL statement
+create or replace function trigger_ddl_func() returns trigger as $$
+begin
+  create index on trigger_ddl_table (col2);
+  return new;
+end$$ language plpgsql;
+insert into trigger_ddl_table values (1, 42);  -- fail
+ERROR:  cannot CREATE INDEX "trigger_ddl_table" because it is being used by active queries in this session
+CONTEXT:  SQL statement "create index on trigger_ddl_table (col2)"
+PL/pgSQL function trigger_ddl_func() line 3 at SQL statement
+drop table trigger_ddl_table;
+drop function trigger_ddl_func();
+--
 -- Verify behavior of before and after triggers with INSERT...ON CONFLICT
 -- DO UPDATE
 --
index 5581fcb16485ac7ccee7fd79a936e5695841b15e..9f2ed88f20981c3f55da31c38da7d7c1d0c7bfe2 100644 (file)
@@ -1183,6 +1183,37 @@ drop table self_ref_trigger;
 drop function self_ref_trigger_ins_func();
 drop function self_ref_trigger_del_func();
 
+--
+-- Check that index creation (or DDL in general) is prohibited in a trigger
+--
+
+create table trigger_ddl_table (
+   col1 integer,
+   col2 integer
+);
+
+create function trigger_ddl_func() returns trigger as $$
+begin
+  alter table trigger_ddl_table add primary key (col1);
+  return new;
+end$$ language plpgsql;
+
+create trigger trigger_ddl_func before insert on trigger_ddl_table for each row
+  execute procedure trigger_ddl_func();
+
+insert into trigger_ddl_table values (1, 42);  -- fail
+
+create or replace function trigger_ddl_func() returns trigger as $$
+begin
+  create index on trigger_ddl_table (col2);
+  return new;
+end$$ language plpgsql;
+
+insert into trigger_ddl_table values (1, 42);  -- fail
+
+drop table trigger_ddl_table;
+drop function trigger_ddl_func();
+
 --
 -- Verify behavior of before and after triggers with INSERT...ON CONFLICT
 -- DO UPDATE