Propagate ALTER TABLE ... SET STORAGE to indexes
authorPeter Eisentraut <[email protected]>
Thu, 9 Apr 2020 12:10:01 +0000 (14:10 +0200)
committerPeter Eisentraut <[email protected]>
Fri, 8 May 2020 06:39:17 +0000 (08:39 +0200)
When creating a new index, the attstorage setting of the table column
is copied to regular (non-expression) index columns.  But a later
ALTER TABLE ... SET STORAGE is not propagated to indexes, thus
creating an inconsistent and undumpable state.

Discussion: https://www.postgresql.org/message-id/flat/9765d72b-37c0-06f5-e349-2a580aafd989%402ndquadrant.com

contrib/test_decoding/expected/toast.out
contrib/test_decoding/sql/toast.sql
src/backend/commands/tablecmds.c
src/test/regress/expected/alter_table.out
src/test/regress/expected/vacuum.out
src/test/regress/sql/alter_table.sql
src/test/regress/sql/vacuum.sql

index 91a9a1e86d6c630d3b4dd12efe4f75eef090a492..75c4d22d8013136ed0019746a29b15d236ced6e8 100644 (file)
@@ -305,6 +305,10 @@ ALTER TABLE toasted_several REPLICA IDENTITY FULL;
 ALTER TABLE toasted_several ALTER COLUMN toasted_key SET STORAGE EXTERNAL;
 ALTER TABLE toasted_several ALTER COLUMN toasted_col1 SET STORAGE EXTERNAL;
 ALTER TABLE toasted_several ALTER COLUMN toasted_col2 SET STORAGE EXTERNAL;
+-- Change the storage of the index back to EXTENDED, separately from
+-- the table.  This is currently not doable via DDL, but it is
+-- supported internally.
+UPDATE pg_attribute SET attstorage = 'x' WHERE attrelid = 'toasted_several_pkey'::regclass AND attname = 'toasted_key';
 INSERT INTO toasted_several(toasted_key) VALUES(repeat('9876543210', 10000));
 SELECT pg_column_size(toasted_key) > 2^16 FROM toasted_several;
  ?column? 
index ef11d2bfff999a3ed195c2dca7ae2192634cc752..016c3ab78424986ff80e4089cc9a5a277c49ad8f 100644 (file)
@@ -279,6 +279,11 @@ ALTER TABLE toasted_several ALTER COLUMN toasted_key SET STORAGE EXTERNAL;
 ALTER TABLE toasted_several ALTER COLUMN toasted_col1 SET STORAGE EXTERNAL;
 ALTER TABLE toasted_several ALTER COLUMN toasted_col2 SET STORAGE EXTERNAL;
 
+-- Change the storage of the index back to EXTENDED, separately from
+-- the table.  This is currently not doable via DDL, but it is
+-- supported internally.
+UPDATE pg_attribute SET attstorage = 'x' WHERE attrelid = 'toasted_several_pkey'::regclass AND attname = 'toasted_key';
+
 INSERT INTO toasted_several(toasted_key) VALUES(repeat('9876543210', 10000));
 SELECT pg_column_size(toasted_key) > 2^16 FROM toasted_several;
 
index 5745cd648a68be1f56144fd50d4c66beb3a0f42e..2fba83504b62647a78c5ef26683783a0b0332eab 100644 (file)
@@ -7383,6 +7383,7 @@ ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE loc
        Form_pg_attribute attrtuple;
        AttrNumber      attnum;
        ObjectAddress address;
+       ListCell   *lc;
 
        Assert(IsA(newValue, String));
        storagemode = strVal(newValue);
@@ -7442,6 +7443,52 @@ ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE loc
 
        heap_freetuple(tuple);
 
+       /*
+        * Apply the change to indexes as well (only for simple index columns,
+        * matching behavior of index.c ConstructTupleDescriptor()).
+        */
+       foreach(lc, RelationGetIndexList(rel))
+       {
+               Oid         indexoid = lfirst_oid(lc);
+               Relation    indrel;
+               AttrNumber      indattnum = 0;
+
+               indrel = index_open(indexoid, lockmode);
+
+               for (int i = 0; i < indrel->rd_index->indnatts; i++)
+               {
+                       if (indrel->rd_index->indkey.values[i] == attnum)
+                       {
+                               indattnum = i + 1;
+                               break;
+                       }
+               }
+
+               if (indattnum == 0)
+               {
+                       index_close(indrel, lockmode);
+                       continue;
+               }
+
+               tuple = SearchSysCacheCopyAttNum(RelationGetRelid(indrel), indattnum);
+
+               if (HeapTupleIsValid(tuple))
+               {
+                       attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
+                       attrtuple->attstorage = newstorage;
+
+                       CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
+
+                       InvokeObjectPostAlterHook(RelationRelationId,
+                                                                         RelationGetRelid(rel),
+                                                                         attrtuple->attnum);
+
+                       heap_freetuple(tuple);
+               }
+
+               index_close(indrel, lockmode);
+       }
+
        table_close(attrelation, RowExclusiveLock);
 
        ObjectAddressSubSet(address, RelationRelationId,
index f343f9b42ea46e54ee6f43cc6ed84a5692ab6030..99ff6973bc8e805332884fd21efcc8f4bdfd6fea 100644 (file)
@@ -2172,6 +2172,26 @@ where oid = 'test_storage'::regclass;
  t
 (1 row)
 
+-- test that SET STORAGE propagates to index correctly
+create index test_storage_idx on test_storage (b, a);
+alter table test_storage alter column a set storage external;
+\d+ test_storage
+                                Table "public.test_storage"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a      | text    |           |          |         | external |              | 
+ b      | integer |           |          | 0       | plain    |              | 
+Indexes:
+    "test_storage_idx" btree (b, a)
+
+\d+ test_storage_idx
+                Index "public.test_storage_idx"
+ Column |  Type   | Key? | Definition | Storage  | Stats target 
+--------+---------+------+------------+----------+--------------
+ b      | integer | yes  | b          | plain    | 
+ a      | text    | yes  | a          | external | 
+btree, for table "public.test_storage"
+
 -- ALTER COLUMN TYPE with a check constraint and a child table (bug #13779)
 CREATE TABLE test_inh_check (a float check (a > 10.2), b float);
 CREATE TABLE test_inh_check_child() INHERITS(test_inh_check);
index 736c2f60d20f0486048f48ee3a8b0b48337922cb..3fccb183c0fd4789a2eb616053b1b18b4146ae77 100644 (file)
@@ -134,7 +134,7 @@ CREATE TABLE no_index_cleanup (i INT PRIMARY KEY, t TEXT);
 CREATE INDEX no_index_cleanup_idx ON no_index_cleanup(t);
 ALTER TABLE no_index_cleanup ALTER COLUMN t SET STORAGE EXTERNAL;
 INSERT INTO no_index_cleanup(i, t) VALUES (generate_series(1,30),
-    repeat('1234567890',300));
+    repeat('1234567890',269));
 -- index cleanup option is ignored if VACUUM FULL
 VACUUM (INDEX_CLEANUP TRUE, FULL TRUE) no_index_cleanup;
 VACUUM (FULL TRUE) no_index_cleanup;
@@ -148,7 +148,7 @@ ALTER TABLE no_index_cleanup SET (vacuum_index_cleanup = true);
 VACUUM no_index_cleanup;
 -- Parameter is set for both the parent table and its toast relation.
 INSERT INTO no_index_cleanup(i, t) VALUES (generate_series(31,60),
-    repeat('1234567890',300));
+    repeat('1234567890',269));
 DELETE FROM no_index_cleanup WHERE i < 45;
 -- Only toast index is cleaned up.
 ALTER TABLE no_index_cleanup SET (vacuum_index_cleanup = false,
index fb5c9139d1529bbed20d926f875e65419368a67b..b45ba2a322bc9d88fdec3607e52c30fae3c37619 100644 (file)
@@ -1472,6 +1472,12 @@ select reltoastrelid <> 0 as has_toast_table
 from pg_class
 where oid = 'test_storage'::regclass;
 
+-- test that SET STORAGE propagates to index correctly
+create index test_storage_idx on test_storage (b, a);
+alter table test_storage alter column a set storage external;
+\d+ test_storage
+\d+ test_storage_idx
+
 -- ALTER COLUMN TYPE with a check constraint and a child table (bug #13779)
 CREATE TABLE test_inh_check (a float check (a > 10.2), b float);
 CREATE TABLE test_inh_check_child() INHERITS(test_inh_check);
index 84dee8c8169e164e00916906052d223390922b36..c7b5f96f6bdfb518725aafe02bfc62088056885f 100644 (file)
@@ -115,7 +115,7 @@ CREATE TABLE no_index_cleanup (i INT PRIMARY KEY, t TEXT);
 CREATE INDEX no_index_cleanup_idx ON no_index_cleanup(t);
 ALTER TABLE no_index_cleanup ALTER COLUMN t SET STORAGE EXTERNAL;
 INSERT INTO no_index_cleanup(i, t) VALUES (generate_series(1,30),
-    repeat('1234567890',300));
+    repeat('1234567890',269));
 -- index cleanup option is ignored if VACUUM FULL
 VACUUM (INDEX_CLEANUP TRUE, FULL TRUE) no_index_cleanup;
 VACUUM (FULL TRUE) no_index_cleanup;
@@ -129,7 +129,7 @@ ALTER TABLE no_index_cleanup SET (vacuum_index_cleanup = true);
 VACUUM no_index_cleanup;
 -- Parameter is set for both the parent table and its toast relation.
 INSERT INTO no_index_cleanup(i, t) VALUES (generate_series(31,60),
-    repeat('1234567890',300));
+    repeat('1234567890',269));
 DELETE FROM no_index_cleanup WHERE i < 45;
 -- Only toast index is cleaned up.
 ALTER TABLE no_index_cleanup SET (vacuum_index_cleanup = false,