Vitaly Burovoy wrote: > I guess you are talking about the other thread[1]. > I'm not sure I have enough experience in Postgres hacking to start > working on it right now, but I'll have a look. > IMO the best way is to raise that topic by a letter with summary what > troubles are left there (in addition to a rebasing one). > > > [1] > http://www.postgresql.org/message-id/flat/20110707213401.ga27...@alvh.no-ip.org
Here's a newer thread: https://www.postgresql.org/message-id/1343682669-sup-2532%40alvh.no-ip.org which includes some points needing additional work. In my local tree I have a branch that maybe is not the same as the last patch I posted in that thread, because the last commit has a newer date. Here I attach what that branch has. It applies cleanly on top of 03bda4535ee119d3. I don't remember if it was in compilable state at the time I abandoned it. It needs some work to rebase, but from a quick experimental "git merge" I just ran, it's not too bad. -- Álvaro Herrera http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
diff --git a/src/backend/commands/constraint.c b/src/backend/commands/constraint.c index 8ac8373..4c9686f 100644 --- a/src/backend/commands/constraint.c +++ b/src/backend/commands/constraint.c @@ -14,12 +14,16 @@ #include "postgres.h" #include "catalog/index.h" +#include "catalog/pg_constraint.h" +#include "commands/constraint.h" #include "commands/trigger.h" #include "executor/executor.h" #include "utils/builtins.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/tqual.h" +static char *tryExtractNotNull_internal(Node *node, Relation rel); /* * unique_key_recheck - trigger function to do a deferred uniqueness check. @@ -188,3 +192,121 @@ unique_key_recheck(PG_FUNCTION_ARGS) return PointerGetDatum(NULL); } + +Constraint * +createCheckNotNullConstraint(Oid nspid, char *constraint_name, + const char *relname, const char *colname) +{ + Constraint *check = makeNode(Constraint); + ColumnRef *colref; + NullTest *nulltest; + + colref = (ColumnRef *) makeNode(ColumnRef); + colref->fields = list_make1(makeString(pstrdup(colname))); + + nulltest = (NullTest *) makeNode(NullTest); + nulltest->argisrow = false; /* FIXME -- may be bogus! */ + nulltest->nulltesttype = IS_NOT_NULL; + nulltest->arg = (Expr *) colref; + + check->contype = CONSTR_CHECK; + check->location = -1; + check->conname = constraint_name ? constraint_name : + ChooseConstraintName(relname, colname, "not_null", nspid, + NIL); + check->raw_expr = (Node *) nulltest; + check->cooked_expr = NULL; + + return check; +} + +/* + * Given a CHECK constraint, examine it and determine whether it is CHECK (col + * IS NOT NULL). If it is, return the column name for which it is. Otherwise + * return NULL. + */ +char * +tryExtractNotNullFromCheckConstr(Constraint *constr) +{ + char *retval; + + Assert(constr->contype == CONSTR_CHECK); + + if (constr->raw_expr != NULL) + { + retval = tryExtractNotNull_internal(constr->raw_expr, NULL); + if (retval != NULL) + return retval; + } + + return tryExtractNotNull_internal(stringToNode(constr->cooked_expr), NULL); +} + +/* + * As above, but use a pg_constraint row as input. + * + * tupdesc is pg_constraint's tuple descriptor, and rel is the relation the + * constraint is for. + */ +char * +tryExtractNotNullFromCatalog(HeapTuple constrTup, TupleDesc tupdesc, + Relation rel) +{ + Datum val; + bool isnull; + char *conbin; + Node *node; + + val = SysCacheGetAttr(CONSTROID, constrTup, Anum_pg_constraint_conbin, + &isnull); + if (isnull) + elog(ERROR, "null conbin for constraint %u", + HeapTupleGetOid(constrTup)); + conbin = TextDatumGetCString(val); + node = (Node *) stringToNode(conbin); + + return tryExtractNotNull_internal(node, rel); +} + +/* + * Worker for the above + */ +static char * +tryExtractNotNull_internal(Node *node, Relation rel) +{ + if (IsA(node, NullTest)) + { + NullTest *nulltest = (NullTest *) node; + + if (nulltest->nulltesttype == IS_NOT_NULL) + { + if (IsA(nulltest->arg, ColumnRef)) + { + ColumnRef *colref = (ColumnRef *) nulltest->arg; + + if (list_length(colref->fields) == 1) + return strVal(linitial(colref->fields)); + } + if (IsA(nulltest->arg, Var)) + { + Var *var = (Var *) nulltest->arg; + TupleDesc tupdesc; + + if (!RelationIsValid(rel)) + elog(ERROR, + "no relation given to extract constraint from"); + tupdesc = RelationGetDescr(rel); + return NameStr(tupdesc->attrs[var->varattno - 1]->attname); + } + } + } + + /* + * XXX Need to check a few more possible wordings of NOT NULL: + * + * - foo IS DISTINCT FROM NULL + * - NOT (foo IS NULL) + */ + + return NULL; +} diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index dc0665e..a3bff7d 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -231,6 +231,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) create->inhRelations = NIL; create->ofTypename = NULL; create->constraints = NIL; + create->notnullcols = NIL; create->options = into->options; create->oncommit = into->onCommit; create->tablespacename = into->tableSpaceName; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 00fe113..12ef1c5 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -43,6 +43,7 @@ #include "catalog/toasting.h" #include "commands/cluster.h" #include "commands/comment.h" +#include "commands/constraint.h" #include "commands/defrem.h" #include "commands/sequence.h" #include "commands/tablecmds.h" @@ -54,7 +55,6 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" -#include "nodes/parsenodes.h" #include "optimizer/clauses.h" #include "optimizer/planner.h" #include "parser/parse_clause.h" @@ -251,8 +251,9 @@ struct DropRelationCallbackState #define ATT_FOREIGN_TABLE 0x0010 static void truncate_check_rel(Relation rel); -static List *MergeAttributes(List *schema, List *supers, char relpersistence, - List **supOids, List **supconstr, int *supOidCount); +static List *MergeAttributes(List *schema, List *notnullcols, List *supers, + char relpersistence, List **supOids, List **supconstr, + int *supOidCount); static bool MergeCheckConstraint(List *constraints, char *name, Node *expr); static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel); static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel); @@ -313,8 +314,9 @@ static void add_column_collation_dependency(Oid relid, int32 attnum, Oid collid) static void ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOCKMODE lockmode); static void ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode); -static void ATExecSetNotNull(AlteredTableInfo *tab, Relation rel, - const char *colName, LOCKMODE lockmode); +static void ATExecSetNotNull(List **wqueue, AlteredTableInfo *tab, + Relation rel, char *constrname, const char *colName, + LOCKMODE lockmode); static void ATExecColumnDefault(Relation rel, const char *colName, Node *newDefault, LOCKMODE lockmode); static void ATPrepSetStatistics(Relation rel, const char *colName, @@ -341,7 +343,13 @@ static void ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel, static void ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, Constraint *constr, - bool recurse, bool recursing, LOCKMODE lockmode); + bool recurse, bool recursing, + LOCKMODE lockmode); +static void ATAddCheckConstraint_internal(List **wqueue, + AlteredTableInfo *tab, Relation rel, + Constraint *constr, + bool recurse, bool recursing, + bool check_it, LOCKMODE lockmode); static void ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, Constraint *fkconstraint, LOCKMODE lockmode); static void ATExecDropConstraint(Relation rel, const char *constrName, @@ -535,7 +543,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId) * Look up inheritance ancestors and generate relation schema, including * inherited attributes. */ - schema = MergeAttributes(schema, stmt->inhRelations, + schema = MergeAttributes(schema, stmt->notnullcols, stmt->inhRelations, stmt->relation->relpersistence, &inheritOids, &old_constraints, &parentOidCount); @@ -1288,6 +1296,8 @@ storage_name(char c) * Input arguments: * 'schema' is the column/attribute definition for the table. (It's a list * of ColumnDef's.) It is destructively changed. + * 'notnullcols' is a list of column names that have NOT NULL constraints. + * Some of these columns may already have is_not_null already set. * 'supers' is a list of names (as RangeVar nodes) of parent relations. * 'relpersistence' is a persistence type of the table. * @@ -1340,10 +1350,12 @@ storage_name(char c) *---------- */ static List * -MergeAttributes(List *schema, List *supers, char relpersistence, - List **supOids, List **supconstr, int *supOidCount) +MergeAttributes(List *schema, List *notnullcols, List *supers, + char relpersistence, List **supOids, List **supconstr, + int *supOidCount) { ListCell *entry; + ListCell *cell; List *inhSchema = NIL; List *parentOids = NIL; List *constraints = NIL; @@ -1805,6 +1817,23 @@ MergeAttributes(List *schema, List *supers, char relpersistence, } /* + * If we have NOT NULL constraint declarations, set the is_not_null bits + * in the correspoding ColumnDef elements. + */ + foreach (cell, notnullcols) + { + char *colname = lfirst(cell); + + foreach (entry, schema) + { + ColumnDef *coldef = lfirst(entry); + + if (strcmp(coldef->colname, colname) == 0) + coldef->is_not_null = true; + } + } + + /* * If we found any conflicting parent default values, check to make sure * they were overridden by the child. */ @@ -3218,7 +3247,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, ATExecDropNotNull(rel, cmd->name, lockmode); break; case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */ - ATExecSetNotNull(tab, rel, cmd->name, lockmode); + ATExecSetNotNull(wqueue, tab, rel, NULL, cmd->name, lockmode); break; case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */ ATExecSetStatistics(rel, cmd->name, cmd->def, lockmode); @@ -4835,12 +4864,13 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode) * ALTER TABLE ALTER COLUMN SET NOT NULL */ static void -ATExecSetNotNull(AlteredTableInfo *tab, Relation rel, - const char *colName, LOCKMODE lockmode) +ATExecSetNotNull(List **wqueue, AlteredTableInfo *tab, Relation rel, + char *constrname, const char *colName, LOCKMODE lockmode) { HeapTuple tuple; AttrNumber attnum; Relation attr_rel; + Constraint *newconstr; /* * lookup the attribute @@ -4880,6 +4910,20 @@ ATExecSetNotNull(AlteredTableInfo *tab, Relation rel, tab->new_notnull = true; } + /* + * We also need to add a new pg_constraint row. Use + * ATAddCheckConstraint_internal for that, but let it know that it + * doesn't need to test the constraint; we already informed it above, + * if necessary. + */ + newconstr = createCheckNotNullConstraint(rel->rd_rel->relnamespace, + constrname, + NameStr(rel->rd_rel->relname), + colName); + + ATAddCheckConstraint_internal(wqueue, tab, rel, newconstr, + true, false, false, lockmode); + heap_close(attr_rel, RowExclusiveLock); } @@ -5570,6 +5614,39 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, Constraint *constr, bool recurse, bool recursing, LOCKMODE lockmode) { + char *colname; + + /* + * If the constraint we're adding is CHECK (col IS NOT NULL), then we route + * it through ATExecSetNotNull instead of working directly with it; that + * function is responsible for getting back to us to recurse, etc. + * + * The reason for this is to get the attnotnull bit set for the column, and + * also to avoid having a second NOT NULL constraint for a column that + * might already have one. (XXX is the latter actually a desirable + * property? Consider inherited tables here.) + */ + Assert(constr->contype == CONSTR_CHECK); + + colname = tryExtractNotNullFromCheckConstr(constr); + if (colname != NULL) + { + ATExecSetNotNull(wqueue, tab, rel, constr->conname, + colname, lockmode); + return; + } + + + /* Not a single-column NOT NULL constraint -- do the regular dance */ + ATAddCheckConstraint_internal(wqueue, tab, rel, constr, recurse, + recursing, true, lockmode); +} + +static void +ATAddCheckConstraint_internal(List **wqueue, AlteredTableInfo *tab, + Relation rel, Constraint *constr, bool recurse, + bool recursing, bool check_it, LOCKMODE lockmode) +{ List *newcons; ListCell *lcon; List *children; @@ -5669,8 +5746,9 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, childtab = ATGetQueueEntry(wqueue, childrel); /* Recurse to child */ - ATAddCheckConstraint(wqueue, childtab, childrel, - constr, recurse, true, lockmode); + ATAddCheckConstraint_internal(wqueue, childtab, childrel, + constr, recurse, true, check_it, + lockmode); heap_close(childrel, NoLock); } @@ -6933,6 +7011,7 @@ ATExecDropConstraint(Relation rel, const char *constrName, while (HeapTupleIsValid(tuple = systable_getnext(scan))) { ObjectAddress conobj; + char *colName; con = (Form_pg_constraint) GETSTRUCT(tuple); @@ -6946,6 +7025,19 @@ ATExecDropConstraint(Relation rel, const char *constrName, errmsg("cannot drop inherited constraint \"%s\" of relation \"%s\"", constrName, RelationGetRelationName(rel)))); + /* + * If it's a CHECK constraint, verify whether it is NOT NULL. + * If it is, then we may need to unset the attnotnull bit as well. + */ + colName = tryExtractNotNullFromCatalog(tuple, + RelationGetDescr(conrel), + rel); + if (colName != NULL) + { + /* do something! */ + elog(NOTICE, "colname is %s", colName); + } + is_no_inherit_constraint = con->connoinherit; /* diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index accda01..7700cdf 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -37,6 +37,7 @@ #include "catalog/pg_operator.h" #include "catalog/pg_type.h" #include "commands/comment.h" +#include "commands/constraint.h" #include "commands/defrem.h" #include "commands/tablecmds.h" #include "commands/tablespace.h" @@ -75,6 +76,7 @@ typedef struct List *ckconstraints; /* CHECK constraints */ List *fkconstraints; /* FOREIGN KEY constraints */ List *ixconstraints; /* index-creating constraints */ + List *notnulls; /* list of column names declared NOT NULL */ List *inh_indexes; /* cloned indexes from INCLUDING INDEXES */ List *blist; /* "before list" of things to do before * creating the table */ @@ -100,6 +102,8 @@ typedef struct static void transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column); +static Constraint *transformNotNullConstraint(CreateStmtContext *cxt, + Constraint *constraint, ColumnDef *column); static void transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint); static void transformTableLikeClause(CreateStmtContext *cxt, @@ -205,6 +209,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString) cxt.ckconstraints = NIL; cxt.fkconstraints = NIL; cxt.ixconstraints = NIL; + cxt.notnulls = NIL; cxt.inh_indexes = NIL; cxt.blist = NIL; cxt.alist = NIL; @@ -269,6 +274,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString) */ stmt->tableElts = cxt.columns; stmt->constraints = cxt.ckconstraints; + stmt->notnullcols = cxt.notnulls; result = lappend(cxt.blist, stmt); result = list_concat(result, cxt.alist); @@ -471,6 +477,9 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) foreach(clist, column->constraints) { + Constraint *newckconstr; + char *colname; + constraint = lfirst(clist); Assert(IsA(constraint, Constraint)); @@ -489,6 +498,11 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) break; case CONSTR_NOTNULL: + /* + * For NOT NULL declarations, we need to mark the column as + * not nullable; and furthermore we need to create a new + * CHECK constraint for this. + */ if (saw_nullable && !column->is_not_null) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), @@ -497,6 +511,9 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) parser_errposition(cxt->pstate, constraint->location))); column->is_not_null = TRUE; + newckconstr = transformNotNullConstraint(cxt, constraint, + column); + cxt->ckconstraints = lappend(cxt->ckconstraints, newckconstr); saw_nullable = true; break; @@ -515,6 +532,21 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) case CONSTR_CHECK: cxt->ckconstraints = lappend(cxt->ckconstraints, constraint); + /* + * If there is a CHECK (foo IS NOT NULL) constraint + * declaration, we check the column name used in the + * constraint. If it's the same name as the column being + * defined, simply set the is_not_null flag in the column + * definition; otherwise remember the column name for later. + */ + colname = tryExtractNotNullFromCheckConstr(constraint); + if (colname != NULL) + { + if (strcmp(colname, column->colname) == 0) + column->is_not_null = true; + else + cxt->notnulls = lappend(cxt->notnulls, colname); + } break; case CONSTR_PRIMARY: @@ -586,6 +618,8 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) static void transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint) { + char *colname; + switch (constraint->contype) { case CONSTR_PRIMARY: @@ -596,6 +630,9 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint) case CONSTR_CHECK: cxt->ckconstraints = lappend(cxt->ckconstraints, constraint); + colname = tryExtractNotNullFromCheckConstr(constraint); + if (colname != NULL) + cxt->notnulls = lappend(cxt->notnulls, colname); break; case CONSTR_FOREIGN: @@ -621,6 +658,30 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint) } /* + * Given a NOT NULL column declaration, transform it into a new Constraint node + * representing the equivalent CHECK (col) IS NOT NULL. + */ +static Constraint * +transformNotNullConstraint(CreateStmtContext *cxt, Constraint *constraint, + ColumnDef *column) +{ + Constraint *check; + Oid nspid; + + if (cxt->rel) + nspid = RelationGetNamespace(cxt->rel); + else + nspid = RangeVarGetCreationNamespace(cxt->relation); + + check = createCheckNotNullConstraint(nspid, + NULL, + cxt->relation->relname, + column->colname); + + return check; +} + +/* * transformTableLikeClause * * Change the LIKE <srctable> portion of a CREATE TABLE statement into @@ -2343,6 +2404,7 @@ transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString) cxt.ckconstraints = NIL; cxt.fkconstraints = NIL; cxt.ixconstraints = NIL; + cxt.notnulls = NIL; cxt.inh_indexes = NIL; cxt.blist = NIL; cxt.alist = NIL; diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 09ca6dd..95bb2d5 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -5729,7 +5729,35 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) resetPQExpBuffer(q); - if (fout->remoteVersion >= 90200) + if (fout->remoteVersion >= 90300) + { + /* + * In 9.3, NOT NULL constraints are in pg_constraint and will be + * dumped as table constraints, so it's unnecessary to dump them + * here. + */ + appendPQExpBuffer(q, "SELECT a.attnum, a.attname, a.atttypmod, " + "a.attstattarget, a.attstorage, t.typstorage, " + "false as attnotnull, a.atthasdef, a.attisdropped, " + "a.attlen, a.attalign, a.attislocal, " + "pg_catalog.format_type(t.oid,a.atttypmod) AS atttypname, " + "array_to_string(a.attoptions, ', ') AS attoptions, " + "CASE WHEN a.attcollation <> t.typcollation " + "THEN a.attcollation ELSE 0 END AS attcollation, " + "pg_catalog.array_to_string(ARRAY(" + "SELECT pg_catalog.quote_ident(option_name) || " + "' ' || pg_catalog.quote_literal(option_value) " + "FROM pg_catalog.pg_options_to_table(attfdwoptions) " + "ORDER BY option_name" + "), E',\n ') AS attfdwoptions " + "FROM pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_type t " + "ON a.atttypid = t.oid " + "WHERE a.attrelid = '%u'::pg_catalog.oid " + "AND a.attnum > 0::pg_catalog.int2 " + "ORDER BY a.attrelid, a.attnum", + tbinfo->dobj.catId.oid); + } + else if (fout->remoteVersion >= 90200) { /* * attfdwoptions is new in 9.2. diff --git a/src/include/commands/constraint.h b/src/include/commands/constraint.h new file mode 100644 index 0000000..99799b4 --- /dev/null +++ b/src/include/commands/constraint.h @@ -0,0 +1,29 @@ +/*------------------------------------------------------------------------- + * + * constraint.h + * PostgreSQL CONSTRAINT support declarations + * + * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/include/commands/constraint.h + * + *------------------------------------------------------------------------- + */ +#ifndef CONSTRAINT_H +#define CONSTRAINT_H + +#include "nodes/parsenodes.h" +#include "utils/relcache.h" + +extern Constraint *createCheckNotNullConstraint(Oid nspid, + char *constraint_name, const char *relname, + const char *colname); + +extern char *tryExtractNotNullFromCheckConstr(Constraint *constr); + +extern char *tryExtractNotNullFromCatalog(HeapTuple constrTup, + TupleDesc tupdesc, Relation rel); + +#endif /* CONSTRAINT_H */ diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index f433166..bfc4cf5 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1439,10 +1439,11 @@ typedef struct VariableShowStmt * Create Table Statement * * NOTE: in the raw gram.y output, ColumnDef and Constraint nodes are - * intermixed in tableElts, and constraints is NIL. After parse analysis, - * tableElts contains just ColumnDefs, and constraints contains just - * Constraint nodes (in fact, only CONSTR_CHECK nodes, in the present - * implementation). + * intermixed in tableElts, and constraints and notnullcols are NIL. After + * parse analysis, tableElts contains just ColumnDefs, notnullcols has been + * filled with not-nullable column names from various sources, and constraints + * contains just Constraint nodes (in fact, only CONSTR_CHECK nodes, in the + * present implementation). * ---------------------- */ @@ -1455,6 +1456,7 @@ typedef struct CreateStmt * inhRelation) */ TypeName *ofTypename; /* OF typename */ List *constraints; /* constraints (list of Constraint nodes) */ + List *notnullcols; /* list of column names with NOT NULL */ List *options; /* options from WITH clause */ OnCommitAction oncommit; /* what do we do at COMMIT? */ char *tablespacename; /* table space to use, or NULL */
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers