Copy storage parameters on CREATE TABLE LIKE/INHERITS
Here is a patch to copy column storage parameters and reloptions on
CREATE TABLE LIKE, which I proposed at:
[HACKERS] Uncopied parameters on CREATE TABLE LIKE
http://archives.postgresql.org/message-id/20080724145954.E572.52131E4D@oss.ntt.co.jp
Column storage parameters (by ALTER COLUMN SET STORAGE) and table
storage parameters (by ALTER TABLE SET (...) ) are copied from template
tables on LIKE or parent tables on INHERITS (1. and 2. at above e-mail).
The patch does not include copying of comments (3.) for now.
It also includes new regression tests and rough documentation.
When template or parent tables have non-default settings,
they are copied into a new table automatically on CREATE TABLE.
If CREATE TABLE statement has own storage parameter definition,
they overwrites inherited settings.
Basically, this patch does nothing if the base tables have only
default settings, but there is an exception -- WITH OIDS. In 8.3,
we inherits WITH OIDS configuration on INHERITS, but not on LIKE.
I'd like to modify the behavior to inherit OIDS configuration
because inheritance would be expected in various contexts.
I did so in the patch, in that a new table will have OIDS if
at least one of its template tables in LIKE has OIDS.
Comments welcome.
Regards,
---
ITAGAKI Takahiro
NTT Open Source Software Center
Attachments:
inherits-parameters.patchapplication/octet-stream; name=inherits-parameters.patchDownload
diff -cpr HEAD/doc/src/sgml/ref/create_table.sgml inherits-parameters/doc/src/sgml/ref/create_table.sgml
*** HEAD/doc/src/sgml/ref/create_table.sgml Tue Jul 29 14:19:30 2008
--- inherits-parameters/doc/src/sgml/ref/create_table.sgml Tue Jul 29 14:20:01 2008
*************** and <replaceable class="PARAMETER">table
*** 229,234 ****
--- 229,237 ----
will always be chosen for it.
</para>
+ <para>
+ Storage parameters are also copied from parent tables.
+ </para>
<!--
<para>
<productname>PostgreSQL</> automatically allows the
*************** and <replaceable class="PARAMETER">table
*** 251,257 ****
<para>
The <literal>LIKE</literal> clause specifies a table from which
the new table automatically copies all column names, their data types,
! and their not-null constraints.
</para>
<para>
Unlike <literal>INHERITS</literal>, the new table and original table
--- 254,260 ----
<para>
The <literal>LIKE</literal> clause specifies a table from which
the new table automatically copies all column names, their data types,
! their not-null constraints, and their storage parameters.
</para>
<para>
Unlike <literal>INHERITS</literal>, the new table and original table
diff -cpr HEAD/src/backend/access/common/reloptions.c inherits-parameters/src/backend/access/common/reloptions.c
*** HEAD/src/backend/access/common/reloptions.c Tue Jul 29 14:19:30 2008
--- inherits-parameters/src/backend/access/common/reloptions.c Tue Jul 29 14:20:01 2008
***************
*** 24,29 ****
--- 24,30 ----
#include "utils/guc.h"
#include "utils/rel.h"
+ static DefElem *searchDefList(List *defList, const char *name);
/*
* Transform a relation options list (list of DefElem) into the text array
*************** transformRelOptions(Datum oldOptions, Li
*** 73,93 ****
for (i = 0; i < noldoptions; i++)
{
! text *oldoption = DatumGetTextP(oldoptions[i]);
! char *text_str = VARDATA(oldoption);
! int text_len = VARSIZE(oldoption) - VARHDRSZ;
!
! /* Search for a match in defList */
! foreach(cell, defList)
! {
! DefElem *def = lfirst(cell);
! int kw_len = strlen(def->defname);
!
! if (text_len > kw_len && text_str[kw_len] == '=' &&
! pg_strncasecmp(text_str, def->defname, kw_len) == 0)
! break;
! }
! if (!cell)
{
/* No match, so keep old option */
astate = accumArrayResult(astate, oldoptions[i],
--- 74,84 ----
for (i = 0; i < noldoptions; i++)
{
! char *name = TextDatumGetCString(oldoptions[i]);
! char *sep = strchr(name, '=');
! if (sep)
! *sep = '\0';
! if (searchDefList(defList, name) == NULL)
{
/* No match, so keep old option */
astate = accumArrayResult(astate, oldoptions[i],
*************** untransformRelOptions(Datum options)
*** 194,199 ****
--- 185,220 ----
return result;
}
+ /*
+ * Merge two reloptions. Options in defList2 are merged into defList1.
+ * defList1 is modified destructively.
+ */
+ List *
+ mergeRelOptions(List *defList1, List *defList2, bool hasoids)
+ {
+ ListCell *cell;
+ DefElem *def;
+
+ foreach(cell, defList2)
+ {
+ def = lfirst(cell);
+
+ if (pg_strcasecmp(def->defname, "oids") == 0)
+ continue;
+
+ /* Merge if defList1 does not have the option. */
+ if (searchDefList(defList1, def->defname) == NULL)
+ defList1 = lappend(defList1, def);
+ }
+
+ /* Overwrite oids option. */
+ if ((def = searchDefList(defList1, "oids")) == NULL)
+ defList1 = lappend(defList1, defWithOids(hasoids));
+ else if (defGetBoolean(def) != hasoids)
+ def->arg = (Node *) makeInteger(hasoids);
+
+ return defList1;
+ }
/*
* Interpret reloptions that are given in text-array format.
*************** index_reloptions(RegProcedure amoptions,
*** 378,381 ****
--- 399,418 ----
return NULL;
return DatumGetByteaP(result);
+ }
+
+ /* Search for a match in defList */
+ static DefElem *
+ searchDefList(List *defList, const char *name)
+ {
+ ListCell *cell;
+
+ foreach(cell, defList)
+ {
+ DefElem *def = lfirst(cell);
+ if (pg_strcasecmp(name, def->defname) == 0)
+ return def;
+ }
+
+ return NULL;
}
diff -cpr HEAD/src/backend/parser/parse_utilcmd.c inherits-parameters/src/backend/parser/parse_utilcmd.c
*** HEAD/src/backend/parser/parse_utilcmd.c Tue Jul 29 14:19:33 2008
--- inherits-parameters/src/backend/parser/parse_utilcmd.c Tue Jul 29 14:20:01 2008
*************** typedef struct
*** 65,70 ****
--- 65,71 ----
List *inhRelations; /* relations to inherit from */
bool isalter; /* true if altering existing table */
bool hasoids; /* does relation have an OID column? */
+ List *options; /* reloptions specified in WITH (...) */
List *columns; /* ColumnDef items */
List *ckconstraints; /* CHECK constraints */
List *fkconstraints; /* FOREIGN KEY constraints */
*************** static void transformFKConstraints(Parse
*** 114,119 ****
--- 115,123 ----
static void transformConstraintAttrs(List *constraintList);
static void transformColumnType(ParseState *pstate, ColumnDef *column);
static void setSchemaName(char *context_schema, char **stmt_schema_name);
+ static void mergeOptions(CreateStmtContext *cxt, Relation rel);
+ static AlterTableStmt *appendSetStorageCmd(AlterTableStmt *stmt,
+ RangeVar *rel, Form_pg_attribute attr);
/*
*************** transformCreateStmt(CreateStmt *stmt, co
*** 138,143 ****
--- 142,148 ----
List *result;
List *save_alist;
ListCell *elements;
+ AlterTableStmt *alterstmt = NULL;
/*
* We must not scribble on the passed-in CreateStmt, so copy it. (This is
*************** transformCreateStmt(CreateStmt *stmt, co
*** 177,182 ****
--- 182,188 ----
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.hasoids = interpretOidsOption(stmt->options);
+ cxt.options = mergeRelOptions(stmt->options, NIL, cxt.hasoids);
/*
* Run through each primary element in the table creation clause. Separate
*************** transformCreateStmt(CreateStmt *stmt, co
*** 215,220 ****
--- 221,257 ----
}
}
+ /* Copy reloptions and column storage parameters. */
+ foreach(elements, cxt.inhRelations)
+ {
+ RangeVar *inh = lfirst(elements);
+ Relation rel;
+ TupleDesc tupleDesc;
+ int i;
+
+ Assert(IsA(inh, RangeVar));
+ rel = heap_openrv(inh, AccessShareLock);
+ if (rel->rd_rel->relkind != RELKIND_RELATION)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("inherited relation \"%s\" is not a table",
+ inh->relname)));
+
+ /* Merge reloptions in inherited tables. */
+ mergeOptions(&cxt, rel);
+
+ /* Copy column storage parameter. */
+ tupleDesc = RelationGetDescr(rel);
+ for (i = 0; i < tupleDesc->natts; i++)
+ alterstmt = appendSetStorageCmd(alterstmt, cxt.relation,
+ tupleDesc->attrs[i]);
+
+ heap_close(rel, NoLock);
+ }
+
+ if (alterstmt)
+ cxt.alist = lappend(cxt.alist, alterstmt);
+
/*
* transformIndexConstraints wants cxt.alist to contain only index
* statements, so transfer anything we already have into save_alist.
*************** transformCreateStmt(CreateStmt *stmt, co
*** 239,244 ****
--- 276,282 ----
*/
stmt->tableElts = cxt.columns;
stmt->constraints = cxt.ckconstraints;
+ stmt->options = cxt.options;
result = lappend(cxt.blist, stmt);
result = list_concat(result, cxt.alist);
*************** transformInhRelation(ParseState *pstate,
*** 545,550 ****
--- 583,589 ----
bool including_constraints = false;
bool including_indexes = false;
ListCell *elem;
+ AlterTableStmt *alterstmt = NULL;
relation = heap_openrv(inhRelation->relation, AccessShareLock);
*************** transformInhRelation(ParseState *pstate,
*** 663,670 ****
--- 702,715 ----
def->cooked_default = pstrdup(this_default);
}
+
+ /* Copy column storage parameter. */
+ alterstmt = appendSetStorageCmd(alterstmt, cxt->relation, attribute);
}
+ if (alterstmt)
+ cxt->alist = lappend(cxt->alist, alterstmt);
+
/*
* Copy CHECK constraints if requested, being careful to adjust attribute
* numbers
*************** transformInhRelation(ParseState *pstate,
*** 721,726 ****
--- 766,774 ----
}
}
+ /* Merge reloptions and oids */
+ mergeOptions(cxt, relation);
+
/*
* Close the parent rel, but keep our AccessShareLock on it until xact
* commit. That will prevent someone else from deleting or ALTERing the
*************** setSchemaName(char *context_schema, char
*** 2105,2108 ****
--- 2153,2242 ----
errmsg("CREATE specifies a schema (%s) "
"different from the one being created (%s)",
*stmt_schema_name, context_schema)));
+ }
+
+ /*
+ * mergeOptions
+ * Merge rel's reloptions and oids into cxt->options.
+ */
+ static void
+ mergeOptions(CreateStmtContext *cxt, Relation rel)
+ {
+ List *options = NIL;
+ Oid relid = RelationGetRelid(rel);
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+
+ /* The new relation should have oids if any of parents have oids. */
+ if (rel->rd_rel->relhasoids)
+ cxt->hasoids = true;
+
+ /* Copy reloptions if any */
+ tuple = SearchSysCache(RELOID, ObjectIdGetDatum(relid), 0, 0, 0);
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ datum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_reloptions, &isnull);
+ if (!isnull)
+ options = untransformRelOptions(datum);
+ ReleaseSysCache(tuple);
+
+ /* Do merge. */
+ cxt->options = mergeRelOptions(cxt->options, options, cxt->hasoids);
+ }
+
+ /*
+ * Append ALTER COLUMN SET STORAGE command to stmt if needed.
+ */
+ static AlterTableStmt *
+ appendSetStorageCmd(AlterTableStmt *stmt, RangeVar *rel, Form_pg_attribute attr)
+ {
+ char *storage;
+ AlterTableCmd *altercmd;
+
+ /* Ignore dropped columns. */
+ if (attr->attisdropped)
+ return stmt;
+
+ /* No command needed if the default storage paramater is used. */
+ if (attr->attstorage == get_typstorage(attr->atttypid))
+ return stmt;
+
+ switch (attr->attstorage)
+ {
+ case 'p':
+ storage = "plain";
+ break;
+ case 'm':
+ storage = "main";
+ break;
+ case 'x':
+ storage = "extended";
+ break;
+ case 'e':
+ storage = "external";
+ break;
+ default:
+ elog(ERROR, "unexpected storage type: %c", attr->attstorage);
+ storage = NULL;
+ break;
+ }
+
+ altercmd = makeNode(AlterTableCmd);
+ altercmd->subtype = AT_SetStorage;
+ altercmd->name = pstrdup(NameStr(attr->attname));
+ altercmd->def = (Node *) makeString(storage);
+
+ /* SET STORAGE commands are merged into one AlterTableStmt. */
+ if (stmt == NULL)
+ {
+ stmt = makeNode(AlterTableStmt);
+ stmt->relation = rel;
+ stmt->cmds = NIL;
+ stmt->relkind = OBJECT_TABLE;
+ }
+ stmt->cmds = lappend(stmt->cmds, altercmd);
+
+ return stmt;
}
diff -cpr HEAD/src/include/access/reloptions.h inherits-parameters/src/include/access/reloptions.h
*** HEAD/src/include/access/reloptions.h Tue Jul 29 14:19:40 2008
--- inherits-parameters/src/include/access/reloptions.h Tue Jul 29 14:20:01 2008
*************** extern Datum transformRelOptions(Datum o
*** 25,30 ****
--- 25,32 ----
extern List *untransformRelOptions(Datum options);
+ extern List *mergeRelOptions(List *defList1, List *defList2, bool hasoids);
+
extern void parseRelOptions(Datum options, int numkeywords,
const char *const * keywords,
char **values, bool validate);
diff -cpr HEAD/src/test/regress/expected/inherit.out inherits-parameters/src/test/regress/expected/inherit.out
*** HEAD/src/test/regress/expected/inherit.out Tue Jul 29 14:19:45 2008
--- inherits-parameters/src/test/regress/expected/inherit.out Tue Jul 29 14:20:01 2008
*************** drop table pp1 cascade;
*** 904,906 ****
--- 904,958 ----
NOTICE: drop cascades to 2 other objects
DETAIL: drop cascades to table cc1
drop cascades to table cc2
+ -- Test inheritance with WITH-options
+ CREATE TABLE table_with_oids (a text) WITH (oids);
+ CREATE TABLE table_with_opts (b text) WITH (fillfactor=50);
+ ALTER TABLE table_with_opts ALTER COLUMN b SET STORAGE main;
+ CREATE TABLE table_inh () INHERITS (table_with_oids, table_with_opts);
+ SELECT relname, relhasoids, reloptions FROM pg_class WHERE oid = 'table_inh'::regclass;
+ relname | relhasoids | reloptions
+ -----------+------------+-----------------
+ table_inh | t | {fillfactor=50}
+ (1 row)
+
+ \d+ table_inh
+ Table "public.table_inh"
+ Column | Type | Modifiers | Storage | Description
+ --------+------+-----------+----------+-------------
+ a | text | | extended |
+ b | text | | main |
+ Inherits: table_with_oids,
+ table_with_opts
+ Has OIDs: yes
+
+ CREATE TABLE table_like1 (LIKE table_with_oids, LIKE table_with_opts);
+ SELECT relname, relhasoids, reloptions FROM pg_class WHERE oid = 'table_like1'::regclass;
+ relname | relhasoids | reloptions
+ -------------+------------+-----------------
+ table_like1 | t | {fillfactor=50}
+ (1 row)
+
+ \d+ table_like1
+ Table "public.table_like1"
+ Column | Type | Modifiers | Storage | Description
+ --------+------+-----------+----------+-------------
+ a | text | | extended |
+ b | text | | main |
+ Has OIDs: yes
+
+ CREATE TABLE table_like2 (LIKE table_with_opts, LIKE table_with_oids) WITH (fillfactor=75);
+ SELECT relname, relhasoids, reloptions FROM pg_class WHERE oid = 'table_like2'::regclass;
+ relname | relhasoids | reloptions
+ -------------+------------+-----------------
+ table_like2 | t | {fillfactor=75}
+ (1 row)
+
+ \d+ table_like2
+ Table "public.table_like2"
+ Column | Type | Modifiers | Storage | Description
+ --------+------+-----------+----------+-------------
+ b | text | | main |
+ a | text | | extended |
+ Has OIDs: yes
+
+ DROP TABLE table_with_oids, table_with_opts, table_inh, table_like1, table_like2 CASCADE;
diff -cpr HEAD/src/test/regress/sql/inherit.sql inherits-parameters/src/test/regress/sql/inherit.sql
*** HEAD/src/test/regress/sql/inherit.sql Tue Jul 29 14:19:45 2008
--- inherits-parameters/src/test/regress/sql/inherit.sql Tue Jul 29 14:20:01 2008
*************** create table cc2(f4 float) inherits(pp1,
*** 276,278 ****
--- 276,293 ----
alter table pp1 add column a2 int check (a2 > 0);
\d cc2
drop table pp1 cascade;
+
+ -- Test inheritance with WITH-options
+ CREATE TABLE table_with_oids (a text) WITH (oids);
+ CREATE TABLE table_with_opts (b text) WITH (fillfactor=50);
+ ALTER TABLE table_with_opts ALTER COLUMN b SET STORAGE main;
+ CREATE TABLE table_inh () INHERITS (table_with_oids, table_with_opts);
+ SELECT relname, relhasoids, reloptions FROM pg_class WHERE oid = 'table_inh'::regclass;
+ \d+ table_inh
+ CREATE TABLE table_like1 (LIKE table_with_oids, LIKE table_with_opts);
+ SELECT relname, relhasoids, reloptions FROM pg_class WHERE oid = 'table_like1'::regclass;
+ \d+ table_like1
+ CREATE TABLE table_like2 (LIKE table_with_opts, LIKE table_with_oids) WITH (fillfactor=75);
+ SELECT relname, relhasoids, reloptions FROM pg_class WHERE oid = 'table_like2'::regclass;
+ \d+ table_like2
+ DROP TABLE table_with_oids, table_with_opts, table_inh, table_like1, table_like2 CASCADE;
On Wed, Jul 30, 2008 at 04:45:47PM +0900, ITAGAKI Takahiro wrote:
Here is a patch to copy column storage parameters and reloptions on
CREATE TABLE LIKE, which I proposed at:
[HACKERS] Uncopied parameters on CREATE TABLE LIKE
http://archives.postgresql.org/message-id/20080724145954.E572.52131E4D@oss.ntt.co.jpColumn storage parameters (by ALTER COLUMN SET STORAGE) and table
storage parameters (by ALTER TABLE SET (...) ) are copied from template
tables on LIKE or parent tables on INHERITS (1. and 2. at above e-mail).
The patch does not include copying of comments (3.) for now.
It also includes new regression tests and rough documentation.When template or parent tables have non-default settings,
they are copied into a new table automatically on CREATE TABLE.
If CREATE TABLE statement has own storage parameter definition,
they overwrites inherited settings.
I'd like to have the ability to copy these parameters, but not to have it
be automatic. Some of my clients applications commonly use CREATE TABLE
LIKE to get empty work tables to populate and update before adding the
data to the main table (which may have non-default settings). The automatic
behaviour may be undesirable for this use.
-dg
--
David Gould daveg@sonic.net 510 536 1443 510 282 0869
If simplicity worked, the world would be overrun with insects.
daveg <daveg@sonic.net> writes:
On Wed, Jul 30, 2008 at 04:45:47PM +0900, ITAGAKI Takahiro wrote:
Here is a patch to copy column storage parameters and reloptions on
CREATE TABLE LIKE, which I proposed at:
I'd like to have the ability to copy these parameters, but not to have it
be automatic.
There are a couple of other things that need to be thought about:
* LIKE is intended to copy a table as a *portion* of another table;
for example it adds the source table's columns to whatever was specified
directly in CREATE TABLE, plus possibly other LIKE invocations. For
example
regression=# create table t1 (f1 int);
CREATE TABLE
regression=# create table t2 (f2 text);
CREATE TABLE
regression=# create table t3 (f3 float, like t1, like t2);
CREATE TABLE
regression=# \d t3
Table "public.t3"
Column | Type | Modifiers
--------+------------------+-----------
f3 | double precision |
f1 | integer |
f2 | text |
I think therefore that having LIKE copy anything "global" to a table,
such as tablespace or reloptions, is fundamentally wrongheaded. What
will you do about conflicts? The same is true for inheritance cases,
since a table can inherit from multiple parents.
* LIKE INCLUDING INDEXES, on the other hand, copies indexes as units,
so it's sensible that it should (and already does) copy the tablespace
and reloptions of those indexes. This leads to a disconnect between
the table-level and index-level behavior, which I believe is what is
bothering Itagaki-san. I'm not sure what to do about it but making
them behave the same is not necessarily a good answer.
* It does make sense to copy per-column storage parameters, if you
consider those as part of the column definitions. However, I'm not
clear on how we'd "merge" storage parameters when the same column
appears from multiple parents.
regards, tom lane
Tom Lane <tgl@sss.pgh.pa.us> wrote:
There are a couple of other things that need to be thought about:
* LIKE is intended to copy a table as a *portion* of another table;
You're absolutely right. I just thought it's a *better default* behavior
because LIKE and INHERITS are often used in single inheritance, but
in definition they declare columns, not whole tables.
In other words, we don't have an ability of copying tables as units...
However, I think we'd better to have an ability to copy reloptions easily.
There were requests about adding a configuration parameter to modify
default fillfactor. I think reloption-cloning would be a solution about it.
How about adding a new WITH-LIKE syntax?
CREATE TABLE newtbl ( ... ) WITH (LIKE "template-table")
It is expanded to an option array as below:
SELECT 'WITH (' || array_to_string(
array_append(reloptions, 'OIDS=' || relhasoids), ',') || ')'
FROM pg_class
WHERE oid = "template-table";
I think therefore that having LIKE copy anything "global" to a table,
such as tablespace or reloptions, is fundamentally wrongheaded. What
will you do about conflicts? The same is true for inheritance cases,
since a table can inherit from multiple parents.
Currently I uses the first setting found in multiple tables
and directly specified options have the highest priority.
For example, setting are used in order of [A] -> [B] -> [C].
Conflicted parameters are ignored silently for now.
CREATE TABLE newtbl (LIKE [B], LIKE [C]) WITH ([A])
Regards,
---
ITAGAKI Takahiro
NTT Open Source Software Center