Allow specifying STORAGE attribute for a new table
authorPeter Eisentraut <peter@eisentraut.org>
Wed, 13 Jul 2022 10:21:45 +0000 (12:21 +0200)
committerPeter Eisentraut <peter@eisentraut.org>
Wed, 13 Jul 2022 10:21:45 +0000 (12:21 +0200)
Previously, the STORAGE specification was only available in ALTER
TABLE.  This makes it available in CREATE TABLE as well.

Also make the code and the documentation for STORAGE and COMPRESSION
attributes consistent.

Author: Teodor Sigaev <teodor@sigaev.ru>
Author: Aleksander Alekseev <aleksander@timescale.com>
Reviewed-by: Peter Eisentraut <peter.eisentraut@enterprisedb.com>
Reviewed-by: wenjing zeng <wjzeng2012@gmail.com>
Reviewed-by: Matthias van de Meent <boekewurm+postgres@gmail.com>
Reviewed-by: Kyotaro Horiguchi <horikyota.ntt@gmail.com>
Discussion: https://postgr.es/m/de83407a-ae3d-a8e1-a788-920eb334f25b@sigaev.ru

doc/src/sgml/ref/alter_table.sgml
doc/src/sgml/ref/create_table.sgml
src/backend/commands/tablecmds.c
src/backend/parser/gram.y
src/include/nodes/parsenodes.h
src/test/regress/expected/alter_table.out
src/test/regress/sql/alter_table.sql

index a3c62bf056eac47f56c2cac1fad6d325264de5a7..f0f912a56c5fa6327427b77380d169b1246736d7 100644 (file)
@@ -367,7 +367,7 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
 
    <varlistentry>
     <term>
-     <literal>SET STORAGE</literal>
+     <literal>SET STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN }</literal>
      <indexterm>
       <primary>TOAST</primary>
       <secondary>per-column storage settings</secondary>
index 6c9918b0a1eb283b0bc5aa3522e71a67f5ba7648..6bbf15ed1a48b2efbf27b79f306ce8cc818958dc 100644 (file)
@@ -22,7 +22,7 @@ PostgreSQL documentation
  <refsynopsisdiv>
 <synopsis>
 CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name</replaceable> ( [
-  { <replaceable class="parameter">column_name</replaceable> <replaceable class="parameter">data_type</replaceable> [ COMPRESSION <replaceable>compression_method</replaceable> ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="parameter">column_constraint</replaceable> [ ... ] ]
+  { <replaceable class="parameter">column_name</replaceable> <replaceable class="parameter">data_type</replaceable> [ STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN } ] [ COMPRESSION <replaceable>compression_method</replaceable> ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="parameter">column_constraint</replaceable> [ ... ] ]
     | <replaceable>table_constraint</replaceable>
     | LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ] }
     [, ... ]
@@ -297,6 +297,33 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term>
+     <literal>STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN }</literal>
+     <indexterm>
+      <primary>TOAST</primary>
+      <secondary>per-column storage settings</secondary>
+     </indexterm>
+    </term>
+    <listitem>
+     <para>
+      This form sets the storage mode for the column. This controls whether this
+      column is held inline or in a secondary <acronym>TOAST</acronym> table,
+      and whether the data should be compressed or not. <literal>PLAIN</literal>
+      must be used for fixed-length values such as <type>integer</type> and is
+      inline, uncompressed. <literal>MAIN</literal> is for inline, compressible
+      data. <literal>EXTERNAL</literal> is for external, uncompressed data, and
+      <literal>EXTENDED</literal> is for external, compressed data.
+      <literal>EXTENDED</literal> is the default for most data types that
+      support non-<literal>PLAIN</literal> storage. Use of
+      <literal>EXTERNAL</literal> will make substring operations on very large
+      <type>text</type> and <type>bytea</type> values run faster, at the penalty
+      of increased storage space. See <xref linkend="storage-toast"/> for more
+      information.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>COMPRESSION <replaceable class="parameter">compression_method</replaceable></literal></term>
     <listitem>
index 701bd73f5eeb4de3bf32ff03bf9e3b9456ac9af1..f2947ea9b4947f5edad025cc463dd3dba12fc4bf 100644 (file)
@@ -593,7 +593,7 @@ static void ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKM
 static void ATExecGenericOptions(Relation rel, List *options);
 static void ATExecSetRowSecurity(Relation rel, bool rls);
 static void ATExecForceNoForceRowSecurity(Relation rel, bool force_rls);
-static ObjectAddress ATExecSetCompression(AlteredTableInfo *tab, Relation rel,
+static ObjectAddress ATExecSetCompression(Relation rel,
                                          const char *column, Node *newValue, LOCKMODE lockmode);
 
 static void index_copy_data(Relation rel, RelFileLocator newrlocator);
@@ -633,6 +633,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
 static char GetAttributeCompression(Oid atttypid, char *compression);
+static char GetAttributeStorage(Oid atttypid, const char *storagemode);
 
 
 /* ----------------------------------------------------------------
@@ -931,6 +932,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
        if (colDef->compression)
            attr->attcompression = GetAttributeCompression(attr->atttypid,
                                                           colDef->compression);
+
+       if (colDef->storage_name)
+           attr->attstorage = GetAttributeStorage(attr->atttypid, colDef->storage_name);
    }
 
    /*
@@ -4963,8 +4967,8 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
        case AT_SetStorage:     /* ALTER COLUMN SET STORAGE */
            address = ATExecSetStorage(rel, cmd->name, cmd->def, lockmode);
            break;
-       case AT_SetCompression:
-           address = ATExecSetCompression(tab, rel, cmd->name, cmd->def,
+       case AT_SetCompression: /* ALTER COLUMN SET COMPRESSION */
+           address = ATExecSetCompression(rel, cmd->name, cmd->def,
                                           lockmode);
            break;
        case AT_DropColumn:     /* DROP COLUMN */
@@ -6820,7 +6824,10 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
    attribute.atttypmod = typmod;
    attribute.attbyval = tform->typbyval;
    attribute.attalign = tform->typalign;
-   attribute.attstorage = tform->typstorage;
+   if (colDef->storage_name)
+       attribute.attstorage = GetAttributeStorage(typeOid, colDef->storage_name);
+   else
+       attribute.attstorage = tform->typstorage;
    attribute.attcompression = GetAttributeCompression(typeOid,
                                                       colDef->compression);
    attribute.attnotnull = colDef->is_not_null;
@@ -8263,33 +8270,12 @@ SetIndexStorageProperties(Relation rel, Relation attrelation,
 static ObjectAddress
 ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE lockmode)
 {
-   char       *storagemode;
-   char        newstorage;
    Relation    attrelation;
    HeapTuple   tuple;
    Form_pg_attribute attrtuple;
    AttrNumber  attnum;
    ObjectAddress address;
 
-   storagemode = strVal(newValue);
-
-   if (pg_strcasecmp(storagemode, "plain") == 0)
-       newstorage = TYPSTORAGE_PLAIN;
-   else if (pg_strcasecmp(storagemode, "external") == 0)
-       newstorage = TYPSTORAGE_EXTERNAL;
-   else if (pg_strcasecmp(storagemode, "extended") == 0)
-       newstorage = TYPSTORAGE_EXTENDED;
-   else if (pg_strcasecmp(storagemode, "main") == 0)
-       newstorage = TYPSTORAGE_MAIN;
-   else
-   {
-       ereport(ERROR,
-               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                errmsg("invalid storage type \"%s\"",
-                       storagemode)));
-       newstorage = 0;         /* keep compiler quiet */
-   }
-
    attrelation = table_open(AttributeRelationId, RowExclusiveLock);
 
    tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
@@ -8308,17 +8294,7 @@ ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE loc
                 errmsg("cannot alter system column \"%s\"",
                        colName)));
 
-   /*
-    * safety check: do not allow toasted storage modes unless column datatype
-    * is TOAST-aware.
-    */
-   if (newstorage == TYPSTORAGE_PLAIN || TypeIsToastable(attrtuple->atttypid))
-       attrtuple->attstorage = newstorage;
-   else
-       ereport(ERROR,
-               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                errmsg("column data type %s can only have storage PLAIN",
-                       format_type_be(attrtuple->atttypid))));
+   attrtuple->attstorage = GetAttributeStorage(attrtuple->atttypid, strVal(newValue));
 
    CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
 
@@ -8326,17 +8302,17 @@ ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE loc
                              RelationGetRelid(rel),
                              attrtuple->attnum);
 
-   heap_freetuple(tuple);
-
    /*
     * Apply the change to indexes as well (only for simple index columns,
     * matching behavior of index.c ConstructTupleDescriptor()).
     */
    SetIndexStorageProperties(rel, attrelation, attnum,
-                             true, newstorage,
+                             true, attrtuple->attstorage,
                              false, 0,
                              lockmode);
 
+   heap_freetuple(tuple);
+
    table_close(attrelation, RowExclusiveLock);
 
    ObjectAddressSubSet(address, RelationRelationId,
@@ -16156,8 +16132,7 @@ ATExecGenericOptions(Relation rel, List *options)
  * Return value is the address of the modified column
  */
 static ObjectAddress
-ATExecSetCompression(AlteredTableInfo *tab,
-                    Relation rel,
+ATExecSetCompression(Relation rel,
                     const char *column,
                     Node *newValue,
                     LOCKMODE lockmode)
@@ -19287,3 +19262,38 @@ GetAttributeCompression(Oid atttypid, char *compression)
 
    return cmethod;
 }
+
+/*
+ * resolve column storage specification
+ */
+static char
+GetAttributeStorage(Oid atttypid, const char *storagemode)
+{
+   char        cstorage = 0;
+
+   if (pg_strcasecmp(storagemode, "plain") == 0)
+       cstorage = TYPSTORAGE_PLAIN;
+   else if (pg_strcasecmp(storagemode, "external") == 0)
+       cstorage = TYPSTORAGE_EXTERNAL;
+   else if (pg_strcasecmp(storagemode, "extended") == 0)
+       cstorage = TYPSTORAGE_EXTENDED;
+   else if (pg_strcasecmp(storagemode, "main") == 0)
+       cstorage = TYPSTORAGE_MAIN;
+   else
+       ereport(ERROR,
+               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                errmsg("invalid storage type \"%s\"",
+                       storagemode)));
+
+   /*
+    * safety check: do not allow toasted storage modes unless column datatype
+    * is TOAST-aware.
+    */
+   if (!(cstorage == TYPSTORAGE_PLAIN || TypeIsToastable(atttypid)))
+       ereport(ERROR,
+               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                errmsg("column data type %s can only have storage PLAIN",
+                       format_type_be(atttypid))));
+
+   return cstorage;
+}
index 0523013f537cea89f56cfad581deb64e294caf57..c018140afe412385b4bfc979562fc5fde4d677ad 100644 (file)
@@ -595,7 +595,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <node>   TableConstraint TableLikeClause
 %type <ival>   TableLikeOptionList TableLikeOption
-%type <str>        column_compression opt_column_compression
+%type <str>        column_compression opt_column_compression column_storage opt_column_storage
 %type <list>   ColQualList
 %type <node>   ColConstraint ColConstraintElem ConstraintAttr
 %type <ival>   key_match
@@ -2537,13 +2537,13 @@ alter_table_cmd:
                    $$ = (Node *) n;
                }
            /* ALTER TABLE <name> ALTER [COLUMN] <colname> SET STORAGE <storagemode> */
-           | ALTER opt_column ColId SET STORAGE ColId
+           | ALTER opt_column ColId SET column_storage
                {
                    AlterTableCmd *n = makeNode(AlterTableCmd);
 
                    n->subtype = AT_SetStorage;
                    n->name = $3;
-                   n->def = (Node *) makeString($6);
+                   n->def = (Node *) makeString($5);
                    $$ = (Node *) n;
                }
            /* ALTER TABLE <name> ALTER [COLUMN] <colname> SET COMPRESSION <cm> */
@@ -3778,13 +3778,14 @@ TypedTableElement:
            | TableConstraint                   { $$ = $1; }
        ;
 
-columnDef: ColId Typename opt_column_compression create_generic_options ColQualList
+columnDef: ColId Typename opt_column_storage opt_column_compression create_generic_options ColQualList
                {
                    ColumnDef *n = makeNode(ColumnDef);
 
                    n->colname = $1;
                    n->typeName = $2;
-                   n->compression = $3;
+                   n->storage_name = $3;
+                   n->compression = $4;
                    n->inhcount = 0;
                    n->is_local = true;
                    n->is_not_null = false;
@@ -3793,8 +3794,8 @@ columnDef:    ColId Typename opt_column_compression create_generic_options ColQualL
                    n->raw_default = NULL;
                    n->cooked_default = NULL;
                    n->collOid = InvalidOid;
-                   n->fdwoptions = $4;
-                   SplitColQualList($5, &n->constraints, &n->collClause,
+                   n->fdwoptions = $5;
+                   SplitColQualList($6, &n->constraints, &n->collClause,
                                     yyscanner);
                    n->location = @1;
                    $$ = (Node *) n;
@@ -3851,6 +3852,15 @@ opt_column_compression:
            | /*EMPTY*/                             { $$ = NULL; }
        ;
 
+column_storage:
+           STORAGE ColId                           { $$ = $2; }
+       ;
+
+opt_column_storage:
+           column_storage                          { $$ = $1; }
+           | /*EMPTY*/                             { $$ = NULL; }
+       ;
+
 ColQualList:
            ColQualList ColConstraint               { $$ = lappend($1, $2); }
            | /*EMPTY*/                             { $$ = NIL; }
index e2ad761768557affd1249e858759c2640b98da34..3605cea4c7215b8f43e1b9ec7e4485b76eae4541 100644 (file)
@@ -695,6 +695,7 @@ typedef struct ColumnDef
    bool        is_not_null;    /* NOT NULL constraint specified? */
    bool        is_from_type;   /* column definition came from table type */
    char        storage;        /* attstorage setting, or 0 for default */
+   char       *storage_name;   /* attstorage setting name or NULL for default */
    Node       *raw_default;    /* default value (untransformed parse tree) */
    Node       *cooked_default; /* default value (transformed expr tree) */
    char        identity;       /* attidentity setting */
index 5ede56d9b5547cb5ede13a6ce61444aaa18adfb8..e3dac1699cfb7cea90101a2f91a17fb1f74f2e3b 100644 (file)
@@ -2244,7 +2244,7 @@ alter table recur1 add column f2 int;
 alter table recur1 alter column f2 type recur2; -- fails
 ERROR:  composite type recur1 cannot be made a member of itself
 -- SET STORAGE may need to add a TOAST table
-create table test_storage (a text);
+create table test_storage (a text, c text storage plain);
 alter table test_storage alter a set storage plain;
 alter table test_storage add b int default 0; -- rewrite table to remove its TOAST table
 alter table test_storage alter a set storage extended; -- re-add TOAST table
@@ -2256,6 +2256,9 @@ where oid = 'test_storage'::regclass;
  t
 (1 row)
 
+-- check STORAGE correctness
+create table test_storage_failed (a text, b int storage extended);
+ERROR:  column data type integer can only have storage PLAIN
 -- 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;
@@ -2264,6 +2267,7 @@ alter table test_storage alter column a set storage external;
  Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
 --------+---------+-----------+----------+---------+----------+--------------+-------------
  a      | text    |           |          |         | external |              | 
+ c      | text    |           |          |         | plain    |              | 
  b      | integer |           |          | 0       | plain    |              | 
 Indexes:
     "test_storage_idx" btree (b, a)
index 52001e3135d058b4d28aafef49afba64f3580e59..e7013f5e154404526cd5b920104b77e19ae7dc65 100644 (file)
@@ -1527,7 +1527,7 @@ alter table recur1 add column f2 int;
 alter table recur1 alter column f2 type recur2; -- fails
 
 -- SET STORAGE may need to add a TOAST table
-create table test_storage (a text);
+create table test_storage (a text, c text storage plain);
 alter table test_storage alter a set storage plain;
 alter table test_storage add b int default 0; -- rewrite table to remove its TOAST table
 alter table test_storage alter a set storage extended; -- re-add TOAST table
@@ -1536,6 +1536,9 @@ select reltoastrelid <> 0 as has_toast_table
 from pg_class
 where oid = 'test_storage'::regclass;
 
+-- check STORAGE correctness
+create table test_storage_failed (a text, b int storage extended);
+
 -- 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;