diff --git a/doc/src/sgml/ref/alter_type.sgml b/doc/src/sgml/ref/alter_type.sgml
index 67be1dd568..03a6a59468 100644
--- a/doc/src/sgml/ref/alter_type.sgml
+++ b/doc/src/sgml/ref/alter_type.sgml
@@ -30,6 +30,7 @@ ALTER TYPE name RENAME TO name SET SCHEMA new_schema
ALTER TYPE name ADD VALUE [ IF NOT EXISTS ] new_enum_value [ { BEFORE | AFTER } neighbor_enum_value ]
ALTER TYPE name RENAME VALUE existing_enum_value TO new_enum_value
+ALTER TYPE name SET STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN }
where action is one of:
@@ -97,6 +98,38 @@ ALTER TYPE name RENAME VALUE
+
+
+ SET STORAGE
+
+ TOAST
+ per-type storage settings
+
+
+
+
+ This form sets the storage mode for a data type, controlling whether the
+ values are held inline or in a secondary TOAST table,
+ and whether the data should be compressed or not.
+ PLAIN must be used for fixed-length values such as
+ integer and is inline, uncompressed. MAIN
+ is for inline, compressible data. EXTERNAL is for
+ external, uncompressed data, and EXTENDED is for
+ external, compressed data. EXTENDED is the default
+ for most data types that support non-PLAIN storage.
+ Use of EXTERNAL will make substring operations on
+ very large text and bytea values run faster,
+ at the penalty of increased storage space. Note that
+ SET STORAGE doesn't itself change anything in the
+ tables, it just sets the strategy to be used by tables created in the
+ future. See for more information.
+ Note that this merely modifies the default for a data type, but each
+ attribute specifies it's own strategy that overrides this value.
+ See for how to change that.
+
+
+
+
SET SCHEMA
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 1cb84182b0..d021b1d272 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -1042,3 +1042,48 @@ AlterObjectOwner_internal(Relation rel, Oid objectId, Oid new_ownerId)
InvokeObjectPostAlterHook(classId, objectId, 0);
}
+
+/*
+ * Executes an ALTER TYPE / SET STORAGE statement. At the moment this is
+ * supported only for OBJECT_TYPE nad OBJECT_DOMAIN.
+ */
+ObjectAddress
+ExecAlterTypeStorageStmt(AlterTypeStorageStmt *stmt)
+{
+ char *storagemode;
+ char newstorage;
+
+ Assert(IsA(stmt->newstorage, String));
+ storagemode = strVal(stmt->newstorage);
+
+ if (pg_strcasecmp(storagemode, "plain") == 0)
+ newstorage = 'p';
+ else if (pg_strcasecmp(storagemode, "external") == 0)
+ newstorage = 'e';
+ else if (pg_strcasecmp(storagemode, "extended") == 0)
+ newstorage = 'x';
+ else if (pg_strcasecmp(storagemode, "main") == 0)
+ newstorage = 'm';
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid storage type \"%s\"",
+ storagemode)));
+ newstorage = 0; /* keep compiler quiet */
+ }
+
+ switch (stmt->objectType)
+ {
+ case OBJECT_TYPE:
+ case OBJECT_DOMAIN: /* same as TYPE */
+ return AlterTypeStorage(castNode(List, stmt->object),
+ newstorage, stmt->objectType);
+ break;
+
+ default:
+ elog(ERROR, "unrecognized AlterTypeStorageStmt type: %d",
+ (int) stmt->objectType);
+ return InvalidObjectAddress; /* keep compiler happy */
+ }
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 52097363fd..3909ed6b72 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -41,6 +41,7 @@
#include "catalog/heap.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_attribute.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -3394,6 +3395,187 @@ AlterTypeOwner(List *names, Oid newOwnerId, ObjectType objecttype)
return address;
}
+static bool
+type_has_toasted_attributes(Oid typeoid)
+{
+ Relation attrel;
+ SysScanDesc scan;
+ ScanKeyData key[1];
+ HeapTuple tuple;
+ bool found = false; /* no TOAST-ed attributes found */
+
+ /*
+ * Must scan pg_attribute. Right now, it is a seqscan because there is
+ * no available index on atttypid.
+ */
+
+ attrel = table_open(AttributeRelationId, AccessShareLock);
+
+ /* Use the index to scan only attributes of the target relation */
+ ScanKeyInit(&key[0],
+ Anum_pg_attribute_atttypid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(typeoid));
+
+ scan = systable_beginscan(attrel, InvalidOid, true,
+ NULL, 1, key);
+
+ /* There should be at most one matching tuple, but we loop anyway */
+ while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ {
+ Form_pg_attribute attr = (Form_pg_attribute) GETSTRUCT(tuple);
+
+ if (attr->attstorage != 'p')
+ {
+ found = true;
+ break;
+ }
+ }
+
+ /* Clean up after the scan */
+ systable_endscan(scan);
+ table_close(attrel, AccessShareLock);
+
+ return found;
+}
+
+/*
+ * Change the storage of a type.
+ */
+ObjectAddress
+AlterTypeStorage(List *names, char newStorage, ObjectType objecttype)
+{
+ TypeName *typename;
+ Oid typeOid;
+ Relation rel;
+ HeapTuple tup;
+ HeapTuple newtup;
+ Form_pg_type typTup;
+ ObjectAddress address;
+
+ rel = table_open(TypeRelationId, RowExclusiveLock);
+
+ /* Make a TypeName so we can use standard type lookup machinery */
+ typename = makeTypeNameFromNameList(names);
+
+ /* Use LookupTypeName here so that shell types can be processed */
+ tup = LookupTypeName(NULL, typename, NULL, false);
+ if (tup == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("type \"%s\" does not exist",
+ TypeNameToString(typename))));
+ typeOid = typeTypeId(tup);
+
+ /* Copy the syscache entry so we can scribble on it below */
+ newtup = heap_copytuple(tup);
+ ReleaseSysCache(tup);
+ tup = newtup;
+ typTup = (Form_pg_type) GETSTRUCT(tup);
+
+ /* Don't allow ALTER DOMAIN on a type */
+ if (objecttype == OBJECT_DOMAIN && typTup->typtype != TYPTYPE_DOMAIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("%s is not a domain",
+ format_type_be(typeOid))));
+
+ /*
+ * If it's a composite type, we need to check that it really is a
+ * free-standing composite type, and not a table's rowtype. We want people
+ * to use ALTER TABLE not ALTER TYPE for that case.
+ *
+ * XXX Maybe this should be allowed?
+ */
+ if (typTup->typtype == TYPTYPE_COMPOSITE &&
+ get_rel_relkind(typTup->typrelid) != RELKIND_COMPOSITE_TYPE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("%s is a table's row type",
+ format_type_be(typeOid)),
+ errhint("Use ALTER TABLE instead.")));
+
+ /*
+ * don't allow direct alteration of array types, either
+ *
+ * XXX Maybe this should be allowed?
+ */
+ if (OidIsValid(typTup->typelem) &&
+ get_array_type(typTup->typelem) == typeOid)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot alter array type %s",
+ format_type_be(typeOid)),
+ errhint("You can alter type %s, which will alter the array type as well.",
+ format_type_be(typTup->typelem))));
+
+ /*
+ * If the new storage is the same as the existing storage, consider the
+ * command to have succeeded.
+ */
+ if (typTup->typstorage != newStorage)
+ {
+ Datum repl_val[Natts_pg_type];
+ bool repl_null[Natts_pg_type];
+ bool repl_repl[Natts_pg_type];
+
+ /* Check the user has right to do ALTER TYPE */
+ if (!pg_type_ownercheck(typTup->oid, GetUserId()))
+ aclcheck_error_type(ACLCHECK_NOT_OWNER, typTup->oid);
+
+ /*
+ * Verify the transition to the new storage value is allowed given the
+ * other type parameters (especially length) and existing attributes
+ * using the type.
+ */
+
+ /* Only varlena types can be toasted */
+ if (newStorage != 'p' && typTup->typlen != -1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("fixed-size types must have storage PLAIN")));
+
+ /*
+ * We must not allow switching to PLAIN when there are attributes with
+ * this type using some other storage value. PLAIN may indicate the
+ * type code does not expect the values to be TOAST-ed, so allowing
+ * that at the attribute level might lead to crashes.
+ *
+ * Any other transition is correct, because it makes the type TOAST
+ * aware. But it's allowed to restrict it at the attribute level, or
+ * enable/disable compression.
+ *
+ * So maybe
+ *
+ * XXX Maybe this could also support CASCADE mode, i.e. we'd set the
+ * storage strategy for all attributes using the data type.
+ */
+ if (newStorage == 'p' && type_has_toasted_attributes(typeOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("can't set storage to PLAIN when there are attributes with non-PLAIN storage values")));
+
+ /* do the actual change */
+ memset(repl_null, false, sizeof(repl_null));
+ memset(repl_repl, false, sizeof(repl_repl));
+
+ repl_repl[Anum_pg_type_typstorage - 1] = true;
+ repl_val[Anum_pg_type_typstorage - 1] = CharGetDatum(newStorage);
+
+ tup = heap_modify_tuple(tup, RelationGetDescr(rel), repl_val, repl_null,
+ repl_repl);
+
+ CatalogTupleUpdate(rel, &tup->t_self, tup);
+ }
+
+ ObjectAddressSet(address, TypeRelationId, typeOid);
+
+ /* Clean up */
+ table_close(rel, RowExclusiveLock);
+
+ return address;
+}
+
/*
* AlterTypeOwner_oid - change type owner unconditionally
*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 96e7fdbcfe..3249a67eca 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -253,7 +253,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
AlterTblSpcStmt AlterExtensionStmt AlterExtensionContentsStmt AlterForeignTableStmt
AlterCompositeTypeStmt AlterUserMappingStmt
AlterRoleStmt AlterRoleSetStmt AlterPolicyStmt AlterStatsStmt
- AlterDefaultPrivilegesStmt DefACLAction
+ AlterDefaultPrivilegesStmt AlterTypeStorageStmt DefACLAction
AnalyzeStmt CallStmt ClosePortalStmt ClusterStmt CommentStmt
ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
CreateDomainStmt CreateExtensionStmt CreateGroupStmt CreateOpClassStmt
@@ -847,6 +847,7 @@ stmt :
| AlterObjectSchemaStmt
| AlterOwnerStmt
| AlterOperatorStmt
+ | AlterTypeStorageStmt
| AlterPolicyStmt
| AlterSeqStmt
| AlterSystemStmt
@@ -9566,6 +9567,31 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
;
+/*****************************************************************************
+ *
+ * ALTER TTYPE name SET STORAGE storage
+ *
+ *****************************************************************************/
+
+AlterTypeStorageStmt:
+ ALTER TYPE_P any_name SET STORAGE ColId
+ {
+ AlterTypeStorageStmt *n = makeNode(AlterTypeStorageStmt);
+ n->objectType = OBJECT_TYPE;
+ n->object = (Node *) $3;
+ n->newstorage = (Node *) makeString($6);
+ $$ = (Node *)n;
+ }
+ | ALTER DOMAIN_P any_name SET STORAGE ColId
+ {
+ AlterTypeStorageStmt *n = makeNode(AlterTypeStorageStmt);
+ n->objectType = OBJECT_DOMAIN;
+ n->object = (Node *) $3;
+ n->newstorage = (Node *) makeString($6);
+ $$ = (Node *)n;
+ }
+ ;
+
/*****************************************************************************
*
* CREATE PUBLICATION name [ FOR TABLE ] [ WITH options ]
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index bb85b5e52a..3f3da70b88 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -158,6 +158,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
case T_AlterSeqStmt:
case T_AlterStatsStmt:
case T_AlterSubscriptionStmt:
+ case T_AlterTypeStorageStmt:
case T_AlterTSConfigurationStmt:
case T_AlterTSDictionaryStmt:
case T_AlterTableMoveAllStmt:
@@ -1045,6 +1046,19 @@ standard_ProcessUtility(PlannedStmt *pstmt,
}
break;
+ case T_AlterTypeStorageStmt:
+ {
+ AlterTypeStorageStmt *stmt = (AlterTypeStorageStmt *) parsetree;
+
+ if (EventTriggerSupportsObjectType(stmt->objectType))
+ ProcessUtilitySlow(pstate, pstmt, queryString,
+ context, params, queryEnv,
+ dest, completionTag);
+ else
+ ExecAlterTypeStorageStmt(stmt);
+ }
+ break;
+
case T_CommentStmt:
{
CommentStmt *stmt = (CommentStmt *) parsetree;
@@ -1723,6 +1737,10 @@ ProcessUtilitySlow(ParseState *pstate,
address = AlterOperator((AlterOperatorStmt *) parsetree);
break;
+ case T_AlterTypeStorageStmt:
+ address = ExecAlterTypeStorageStmt((AlterTypeStorageStmt *) parsetree);
+ break;
+
case T_CommentStmt:
address = CommentObject((CommentStmt *) parsetree);
break;
@@ -2575,6 +2593,10 @@ CreateCommandTag(Node *parsetree)
tag = AlterObjectTypeCommandTag(((AlterOwnerStmt *) parsetree)->objectType);
break;
+ case T_AlterTypeStorageStmt:
+ tag = "ALTER TYPE";
+ break;
+
case T_AlterTableMoveAllStmt:
tag = AlterObjectTypeCommandTag(((AlterTableMoveAllStmt *) parsetree)->objtype);
break;
@@ -3264,6 +3286,10 @@ GetCommandLogLevel(Node *parsetree)
lev = LOGSTMT_DDL;
break;
+ case T_AlterTypeStorageStmt:
+ lev = LOGSTMT_DDL;
+ break;
+
case T_AlterTableMoveAllStmt:
case T_AlterTableStmt:
lev = LOGSTMT_DDL;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b6b08d0ccb..9a98306b06 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2150,7 +2150,7 @@ psql_completion(const char *text, int start, int end)
else if (Matches("ALTER", "TYPE", MatchAny))
COMPLETE_WITH("ADD ATTRIBUTE", "ADD VALUE", "ALTER ATTRIBUTE",
"DROP ATTRIBUTE",
- "OWNER TO", "RENAME", "SET SCHEMA");
+ "OWNER TO", "RENAME", "SET SCHEMA", "SET STORAGE");
/* complete ALTER TYPE ADD with actions */
else if (Matches("ALTER", "TYPE", MatchAny, "ADD"))
COMPLETE_WITH("ATTRIBUTE", "VALUE");
diff --git a/src/include/commands/alter.h b/src/include/commands/alter.h
index f9d6ba13c9..c55a1da0af 100644
--- a/src/include/commands/alter.h
+++ b/src/include/commands/alter.h
@@ -31,5 +31,6 @@ extern Oid AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
extern ObjectAddress ExecAlterOwnerStmt(AlterOwnerStmt *stmt);
extern void AlterObjectOwner_internal(Relation catalog, Oid objectId,
Oid new_ownerId);
+extern ObjectAddress ExecAlterTypeStorageStmt(AlterTypeStorageStmt *stmt);
#endif /* ALTER_H */
diff --git a/src/include/commands/typecmds.h b/src/include/commands/typecmds.h
index fc18d64347..1f45fc670a 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -54,4 +54,6 @@ extern Oid AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid,
bool errorOnTableType,
ObjectAddresses *objsMoved);
+extern ObjectAddress AlterTypeStorage(List *names, char newStorage, ObjectType objecttype);
+
#endif /* TYPECMDS_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index baced7eec0..2eb227efdc 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -380,6 +380,7 @@ typedef enum NodeTag
T_AlterObjectSchemaStmt,
T_AlterOwnerStmt,
T_AlterOperatorStmt,
+ T_AlterTypeStorageStmt,
T_DropOwnedStmt,
T_ReassignOwnedStmt,
T_CompositeTypeStmt,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index da0706add5..4d40acc097 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2959,6 +2959,18 @@ typedef struct AlterOwnerStmt
RoleSpec *newowner; /* the new owner */
} AlterOwnerStmt;
+/* ----------------------
+ * Alter Type Storage Statement
+ * ----------------------
+ */
+typedef struct AlterTypeStorageStmt
+{
+ NodeTag type;
+ ObjectType objectType; /* OBJECT_TYPE or OBJECT_DOMAIN */
+ Node *object;
+ Node *newstorage; /* the new storage */
+} AlterTypeStorageStmt;
+
/* ----------------------
* Alter Operator Set Restrict, Join