Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Propagate ALTER TABLE ... SET STORAGE to indexes
authorPeter Eisentraut <peter@eisentraut.org>
Thu, 9 Apr 2020 12:10:01 +0000 (14:10 +0200)
committerPeter Eisentraut <peter@eisentraut.org>
Fri, 8 May 2020 07:18:15 +0000 (09:18 +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 1a6ccff8f1c90a3b77553a50cf66f299d9c9a41e..b36683e74974dba4e75a85eda729fe2f9cde586e 100644 (file)
@@ -6973,6 +6973,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);
@@ -7032,6 +7033,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 9e394fcbc9c5c80d7935ed6993aacd36bfb820bd..f7a25764e5daff0c106177e9c75fe5723decf04f 100644 (file)
@@ -2164,6 +2164,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 9996d882d161d076b2b4c48fbbc20ed8ac5f3e1f..e2b9cd954d3efb420aff82ec2ef3660d70ce86b9 100644 (file)
@@ -98,7 +98,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;
@@ -112,7 +112,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 67646da2797c5f15be7ad53f93a39c0136fa404c..88e36ea32ae164891f33134d8fffba4dcbbe895c 100644 (file)
@@ -1463,6 +1463,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 69987f75e9bc1d02f05e7fde9b210f867d06078e..aaacdfe0826ac16af4ba391c33b16d68ebbbd9b3 100644 (file)
@@ -81,7 +81,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;
@@ -95,7 +95,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,