* a quick copyObject() call before manipulating the query tree.
*
*
- * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/backend/parser/parse_utilcmd.c,v 2.1 2007/06/23 22:12:51 tgl Exp $
+ * src/backend/parser/parse_utilcmd.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
+#include "access/genam.h"
#include "access/heapam.h"
+#include "access/reloptions.h"
+#include "catalog/dependency.h"
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/namespace.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_constraint.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_operator.h"
#include "catalog/pg_type.h"
+#include "commands/comment.h"
#include "commands/defrem.h"
#include "commands/tablecmds.h"
+#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
-#include "optimizer/clauses.h"
+#include "nodes/nodeFuncs.h"
#include "parser/analyze.h"
-#include "parser/gramparse.h"
#include "parser/parse_clause.h"
+#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
+#include "parser/parse_target.h"
#include "parser/parse_type.h"
#include "parser/parse_utilcmd.h"
+#include "parser/parser.h"
#include "rewrite/rewriteManip.h"
+#include "storage/lock.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
+#include "utils/relcache.h"
#include "utils/syscache.h"
+#include "utils/typcache.h"
/* State shared by transformCreateStmt and its subroutines */
typedef struct
{
- const char *stmtType; /* "CREATE TABLE" or "ALTER TABLE" */
+ ParseState *pstate; /* overall parser state */
+ const char *stmtType; /* "CREATE [FOREIGN] TABLE" or "ALTER TABLE" */
RangeVar *relation; /* relation to create */
Relation rel; /* opened/locked rel, if ALTER */
List *inhRelations; /* relations to inherit from */
List *ckconstraints; /* CHECK constraints */
List *fkconstraints; /* FOREIGN KEY constraints */
List *ixconstraints; /* index-creating constraints */
+ List *inh_indexes; /* cloned indexes from INCLUDING INDEXES */
List *blist; /* "before list" of things to do before
* creating the table */
List *alist; /* "after list" of things to do after creating
} CreateSchemaStmtContext;
-static void transformColumnDefinition(ParseState *pstate,
- CreateStmtContext *cxt,
+static void transformColumnDefinition(CreateStmtContext *cxt,
ColumnDef *column);
-static void transformTableConstraint(ParseState *pstate,
- CreateStmtContext *cxt,
+static void transformTableConstraint(CreateStmtContext *cxt,
Constraint *constraint);
-static void transformInhRelation(ParseState *pstate, CreateStmtContext *cxt,
+static void transformInhRelation(CreateStmtContext *cxt,
InhRelation *inhrelation);
-static void transformIndexConstraints(ParseState *pstate,
- CreateStmtContext *cxt);
-static void transformFKConstraints(ParseState *pstate,
- CreateStmtContext *cxt,
+static void transformOfType(CreateStmtContext *cxt,
+ TypeName *ofTypename);
+static char *chooseIndexName(const RangeVar *relation, IndexStmt *index_stmt);
+static IndexStmt *generateClonedIndexStmt(CreateStmtContext *cxt,
+ Relation parent_index, AttrNumber *attmap);
+static List *get_collation(Oid collation, Oid actual_datatype);
+static List *get_opclass(Oid opclass, Oid actual_datatype);
+static void transformIndexConstraints(CreateStmtContext *cxt);
+static IndexStmt *transformIndexConstraint(Constraint *constraint,
+ CreateStmtContext *cxt);
+static void transformFKConstraints(CreateStmtContext *cxt,
bool skipValidation,
bool isAddConstraint);
-static void transformConstraintAttrs(List *constraintList);
-static void transformColumnType(ParseState *pstate, ColumnDef *column);
+static void transformConstraintAttrs(CreateStmtContext *cxt,
+ List *constraintList);
+static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
static void setSchemaName(char *context_schema, char **stmt_schema_name);
List *result;
List *save_alist;
ListCell *elements;
+ Oid namespaceid;
/*
- * We must not scribble on the passed-in CreateStmt, so copy it. (This
- * is overkill, but easy.)
+ * We must not scribble on the passed-in CreateStmt, so copy it. (This is
+ * overkill, but easy.)
*/
stmt = (CreateStmt *) copyObject(stmt);
- /* Set up pstate */
+ /*
+ * Look up the creation namespace. This also checks permissions on the
+ * target namespace, so that we throw any permissions error as early as
+ * possible.
+ */
+ namespaceid = RangeVarGetAndCheckCreationNamespace(stmt->relation);
+
+ /*
+ * If the relation already exists and the user specified "IF NOT EXISTS",
+ * bail out with a NOTICE.
+ */
+ if (stmt->if_not_exists)
+ {
+ Oid existing_relid;
+
+ existing_relid = get_relname_relid(stmt->relation->relname,
+ namespaceid);
+ if (existing_relid != InvalidOid)
+ {
+ ereport(NOTICE,
+ (errcode(ERRCODE_DUPLICATE_TABLE),
+ errmsg("relation \"%s\" already exists, skipping",
+ stmt->relation->relname)));
+ return NIL;
+ }
+ }
+
+ /*
+ * If the target relation name isn't schema-qualified, make it so. This
+ * prevents some corner cases in which added-on rewritten commands might
+ * think they should apply to other relations that have the same name and
+ * are earlier in the search path. But a local temp table is effectively
+ * specified to be in pg_temp, so no need for anything extra in that case.
+ */
+ if (stmt->relation->schemaname == NULL
+ && stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+ stmt->relation->schemaname = get_namespace_name(namespaceid);
+
+ /* Set up pstate and CreateStmtContext */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
- cxt.stmtType = "CREATE TABLE";
+ cxt.pstate = pstate;
+ if (IsA(stmt, CreateForeignTableStmt))
+ cxt.stmtType = "CREATE FOREIGN TABLE";
+ else
+ cxt.stmtType = "CREATE TABLE";
cxt.relation = stmt->relation;
cxt.rel = NULL;
cxt.inhRelations = stmt->inhRelations;
cxt.ckconstraints = NIL;
cxt.fkconstraints = NIL;
cxt.ixconstraints = NIL;
+ cxt.inh_indexes = NIL;
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.hasoids = interpretOidsOption(stmt->options);
+ Assert(!stmt->ofTypename || !stmt->inhRelations); /* grammar enforces */
+
+ if (stmt->ofTypename)
+ transformOfType(&cxt, stmt->ofTypename);
+
/*
* Run through each primary element in the table creation clause. Separate
* column defs from constraints, and do preliminary analysis.
switch (nodeTag(element))
{
case T_ColumnDef:
- transformColumnDefinition(pstate, &cxt,
- (ColumnDef *) element);
+ transformColumnDefinition(&cxt, (ColumnDef *) element);
break;
case T_Constraint:
- transformTableConstraint(pstate, &cxt,
- (Constraint *) element);
- break;
-
- case T_FkConstraint:
- /* No pre-transformation needed */
- cxt.fkconstraints = lappend(cxt.fkconstraints, element);
+ transformTableConstraint(&cxt, (Constraint *) element);
break;
case T_InhRelation:
- transformInhRelation(pstate, &cxt,
- (InhRelation *) element);
+ transformInhRelation(&cxt, (InhRelation *) element);
break;
default:
/*
* Postprocess constraints that give rise to index definitions.
*/
- transformIndexConstraints(pstate, &cxt);
+ transformIndexConstraints(&cxt);
/*
* Postprocess foreign-key constraints.
*/
- transformFKConstraints(pstate, &cxt, true, false);
+ transformFKConstraints(&cxt, true, false);
/*
* Output results.
* Also used in ALTER TABLE ADD COLUMN
*/
static void
-transformColumnDefinition(ParseState *pstate, CreateStmtContext *cxt,
- ColumnDef *column)
+transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
{
bool is_serial;
bool saw_nullable;
/* Check for SERIAL pseudo-types */
is_serial = false;
- if (list_length(column->typename->names) == 1)
+ if (column->typeName
+ && list_length(column->typeName->names) == 1
+ && !column->typeName->pct_type)
{
- char *typname = strVal(linitial(column->typename->names));
+ char *typname = strVal(linitial(column->typeName->names));
- if (strcmp(typname, "serial") == 0 ||
+ if (strcmp(typname, "smallserial") == 0 ||
+ strcmp(typname, "serial2") == 0)
+ {
+ is_serial = true;
+ column->typeName->names = NIL;
+ column->typeName->typeOid = INT2OID;
+ }
+ else if (strcmp(typname, "serial") == 0 ||
strcmp(typname, "serial4") == 0)
{
is_serial = true;
- column->typename->names = NIL;
- column->typename->typeid = INT4OID;
+ column->typeName->names = NIL;
+ column->typeName->typeOid = INT4OID;
}
else if (strcmp(typname, "bigserial") == 0 ||
strcmp(typname, "serial8") == 0)
{
is_serial = true;
- column->typename->names = NIL;
- column->typename->typeid = INT8OID;
+ column->typeName->names = NIL;
+ column->typeName->typeOid = INT8OID;
}
+
+ /*
+ * We have to reject "serial[]" explicitly, because once we've set
+ * typeid, LookupTypeName won't notice arrayBounds. We don't need any
+ * special coding for serial(typmod) though.
+ */
+ if (is_serial && column->typeName->arrayBounds != NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("array of serial is not implemented"),
+ parser_errposition(cxt->pstate,
+ column->typeName->location)));
}
/* Do necessary work on the column type declaration */
- transformColumnType(pstate, column);
+ if (column->typeName)
+ transformColumnType(cxt, column);
/* Special actions for SERIAL pseudo-types */
if (is_serial)
char *sname;
char *qstring;
A_Const *snamenode;
+ TypeCast *castnode;
FuncCall *funccallnode;
CreateSeqStmt *seqstmt;
AlterSeqStmt *altseqstmt;
* TABLE.
*/
seqstmt = makeNode(CreateSeqStmt);
- seqstmt->sequence = makeRangeVar(snamespace, sname);
+ seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
seqstmt->options = NIL;
+ /*
+ * If this is ALTER ADD COLUMN, make sure the sequence will be owned
+ * by the table's owner. The current user might be someone else
+ * (perhaps a superuser, or someone who's only a member of the owning
+ * role), but the SEQUENCE OWNED BY mechanisms will bleat unless table
+ * and sequence have exactly the same owning role.
+ */
+ if (cxt->rel)
+ seqstmt->ownerId = cxt->rel->rd_rel->relowner;
+ else
+ seqstmt->ownerId = InvalidOid;
+
cxt->blist = lappend(cxt->blist, seqstmt);
/*
* done after this CREATE/ALTER TABLE.
*/
altseqstmt = makeNode(AlterSeqStmt);
- altseqstmt->sequence = makeRangeVar(snamespace, sname);
+ altseqstmt->sequence = makeRangeVar(snamespace, sname, -1);
attnamelist = list_make3(makeString(snamespace),
makeString(cxt->relation->relname),
makeString(column->colname));
snamenode = makeNode(A_Const);
snamenode->val.type = T_String;
snamenode->val.val.str = qstring;
- snamenode->typename = SystemTypeName("regclass");
+ snamenode->location = -1;
+ castnode = makeNode(TypeCast);
+ castnode->typeName = SystemTypeName("regclass");
+ castnode->arg = (Node *) snamenode;
+ castnode->location = -1;
funccallnode = makeNode(FuncCall);
funccallnode->funcname = SystemFuncName("nextval");
- funccallnode->args = list_make1(snamenode);
+ funccallnode->args = list_make1(castnode);
+ funccallnode->agg_order = NIL;
funccallnode->agg_star = false;
funccallnode->agg_distinct = false;
+ funccallnode->func_variadic = false;
+ funccallnode->over = NULL;
funccallnode->location = -1;
constraint = makeNode(Constraint);
constraint->contype = CONSTR_DEFAULT;
+ constraint->location = -1;
constraint->raw_expr = (Node *) funccallnode;
constraint->cooked_expr = NULL;
- constraint->keys = NIL;
column->constraints = lappend(column->constraints, constraint);
constraint = makeNode(Constraint);
constraint->contype = CONSTR_NOTNULL;
+ constraint->location = -1;
column->constraints = lappend(column->constraints, constraint);
}
/* Process column constraints, if any... */
- transformConstraintAttrs(column->constraints);
+ transformConstraintAttrs(cxt, column->constraints);
saw_nullable = false;
saw_default = false;
foreach(clist, column->constraints)
{
constraint = lfirst(clist);
-
- /*
- * If this column constraint is a FOREIGN KEY constraint, then we fill
- * in the current attribute's name and throw it into the list of FK
- * constraints to be processed later.
- */
- if (IsA(constraint, FkConstraint))
- {
- FkConstraint *fkconstraint = (FkConstraint *) constraint;
-
- fkconstraint->fk_attrs = list_make1(makeString(column->colname));
- cxt->fkconstraints = lappend(cxt->fkconstraints, fkconstraint);
- continue;
- }
-
Assert(IsA(constraint, Constraint));
switch (constraint->contype)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting NULL/NOT NULL declarations for column \"%s\" of table \"%s\"",
- column->colname, cxt->relation->relname)));
+ column->colname, cxt->relation->relname),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
column->is_not_null = FALSE;
saw_nullable = true;
break;
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting NULL/NOT NULL declarations for column \"%s\" of table \"%s\"",
- column->colname, cxt->relation->relname)));
+ column->colname, cxt->relation->relname),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
column->is_not_null = TRUE;
saw_nullable = true;
break;
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple default values specified for column \"%s\" of table \"%s\"",
- column->colname, cxt->relation->relname)));
- /* Note: DEFAULT NULL maps to constraint->raw_expr == NULL */
+ column->colname, cxt->relation->relname),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
column->raw_default = constraint->raw_expr;
Assert(constraint->cooked_expr == NULL);
saw_default = true;
break;
+ case CONSTR_CHECK:
+ cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
+ break;
+
case CONSTR_PRIMARY:
case CONSTR_UNIQUE:
if (constraint->keys == NIL)
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
- case CONSTR_CHECK:
- cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
+ case CONSTR_EXCLUSION:
+ /* grammar does not allow EXCLUDE as a column constraint */
+ elog(ERROR, "column exclusion constraints are not supported");
+ break;
+
+ case CONSTR_FOREIGN:
+
+ /*
+ * Fill in the current attribute's name and throw it into the
+ * list of FK constraints to be processed later.
+ */
+ constraint->fk_attrs = list_make1(makeString(column->colname));
+ cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
break;
case CONSTR_ATTR_DEFERRABLE:
* transform a Constraint node within CREATE TABLE or ALTER TABLE
*/
static void
-transformTableConstraint(ParseState *pstate, CreateStmtContext *cxt,
- Constraint *constraint)
+transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
{
switch (constraint->contype)
{
case CONSTR_PRIMARY:
case CONSTR_UNIQUE:
+ case CONSTR_EXCLUSION:
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
break;
+ case CONSTR_FOREIGN:
+ cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
+ break;
+
case CONSTR_NULL:
case CONSTR_NOTNULL:
case CONSTR_DEFAULT:
* <subtable>.
*/
static void
-transformInhRelation(ParseState *pstate, CreateStmtContext *cxt,
- InhRelation *inhRelation)
+transformInhRelation(CreateStmtContext *cxt, InhRelation *inhRelation)
{
AttrNumber parent_attno;
Relation relation;
TupleDesc tupleDesc;
TupleConstr *constr;
AclResult aclresult;
- bool including_defaults = false;
- bool including_constraints = false;
- bool including_indexes = false;
- ListCell *elem;
+ char *comment;
- relation = heap_openrv(inhRelation->relation, AccessShareLock);
+ relation = parserOpenTable(cxt->pstate, inhRelation->relation,
+ AccessShareLock);
if (relation->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
tupleDesc = RelationGetDescr(relation);
constr = tupleDesc->constr;
- foreach(elem, inhRelation->options)
- {
- int option = lfirst_int(elem);
-
- switch (option)
- {
- case CREATE_TABLE_LIKE_INCLUDING_DEFAULTS:
- including_defaults = true;
- break;
- case CREATE_TABLE_LIKE_EXCLUDING_DEFAULTS:
- including_defaults = false;
- break;
- case CREATE_TABLE_LIKE_INCLUDING_CONSTRAINTS:
- including_constraints = true;
- break;
- case CREATE_TABLE_LIKE_EXCLUDING_CONSTRAINTS:
- including_constraints = false;
- break;
- case CREATE_TABLE_LIKE_INCLUDING_INDEXES:
- including_indexes = true;
- break;
- case CREATE_TABLE_LIKE_EXCLUDING_INDEXES:
- including_indexes = false;
- break;
- default:
- elog(ERROR, "unrecognized CREATE TABLE LIKE option: %d",
- option);
- }
- }
-
- if (including_indexes)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("LIKE INCLUDING INDEXES is not implemented")));
-
/*
- * Insert the copied attributes into the cxt for the new table
- * definition.
+ * Insert the copied attributes into the cxt for the new table definition.
*/
for (parent_attno = 1; parent_attno <= tupleDesc->natts;
parent_attno++)
*/
def = makeNode(ColumnDef);
def->colname = pstrdup(attributeName);
- def->typename = makeTypeNameFromOid(attribute->atttypid,
+ def->typeName = makeTypeNameFromOid(attribute->atttypid,
attribute->atttypmod);
def->inhcount = 0;
def->is_local = true;
def->is_not_null = attribute->attnotnull;
+ def->is_from_type = false;
+ def->storage = 0;
def->raw_default = NULL;
def->cooked_default = NULL;
+ def->collClause = NULL;
+ def->collOid = attribute->attcollation;
def->constraints = NIL;
/*
/*
* Copy default, if present and the default has been requested
*/
- if (attribute->atthasdef && including_defaults)
+ if (attribute->atthasdef &&
+ (inhRelation->options & CREATE_TABLE_LIKE_DEFAULTS))
{
- char *this_default = NULL;
+ Node *this_default = NULL;
AttrDefault *attrdef;
int i;
{
if (attrdef[i].adnum == parent_attno)
{
- this_default = attrdef[i].adbin;
+ this_default = stringToNode(attrdef[i].adbin);
break;
}
}
* but it can't; so default is ready to apply to child.
*/
- def->cooked_default = pstrdup(this_default);
+ def->cooked_default = this_default;
+ }
+
+ /* Likewise, copy storage if requested */
+ if (inhRelation->options & CREATE_TABLE_LIKE_STORAGE)
+ def->storage = attribute->attstorage;
+ else
+ def->storage = 0;
+
+ /* Likewise, copy comment if requested */
+ if ((inhRelation->options & CREATE_TABLE_LIKE_COMMENTS) &&
+ (comment = GetComment(attribute->attrelid,
+ RelationRelationId,
+ attribute->attnum)) != NULL)
+ {
+ CommentStmt *stmt = makeNode(CommentStmt);
+
+ stmt->objtype = OBJECT_COLUMN;
+ stmt->objname = list_make3(makeString(cxt->relation->schemaname),
+ makeString(cxt->relation->relname),
+ makeString(def->colname));
+ stmt->objargs = NIL;
+ stmt->comment = comment;
+
+ cxt->alist = lappend(cxt->alist, stmt);
}
}
/*
- * Copy CHECK constraints if requested, being careful to adjust
- * attribute numbers
+ * Copy CHECK constraints if requested, being careful to adjust attribute
+ * numbers
*/
- if (including_constraints && tupleDesc->constr)
+ if ((inhRelation->options & CREATE_TABLE_LIKE_CONSTRAINTS) &&
+ tupleDesc->constr)
{
AttrNumber *attmap = varattnos_map_schema(tupleDesc, cxt->columns);
int ccnum;
change_varattnos_of_a_node(ccbin_node, attmap);
n->contype = CONSTR_CHECK;
- n->name = pstrdup(ccname);
+ n->location = -1;
+ n->conname = pstrdup(ccname);
n->raw_expr = NULL;
n->cooked_expr = nodeToString(ccbin_node);
- n->indexspace = NULL;
- cxt->ckconstraints = lappend(cxt->ckconstraints, (Node *) n);
+ cxt->ckconstraints = lappend(cxt->ckconstraints, n);
+
+ /* Copy comment on constraint */
+ if ((inhRelation->options & CREATE_TABLE_LIKE_COMMENTS) &&
+ (comment = GetComment(get_constraint_oid(RelationGetRelid(relation),
+ n->conname, false),
+ ConstraintRelationId,
+ 0)) != NULL)
+ {
+ CommentStmt *stmt = makeNode(CommentStmt);
+
+ stmt->objtype = OBJECT_CONSTRAINT;
+ stmt->objname = list_make3(makeString(cxt->relation->schemaname),
+ makeString(cxt->relation->relname),
+ makeString(n->conname));
+ stmt->objargs = NIL;
+ stmt->comment = comment;
+
+ cxt->alist = lappend(cxt->alist, stmt);
+ }
+ }
+ }
+
+ /*
+ * Likewise, copy indexes if requested
+ */
+ if ((inhRelation->options & CREATE_TABLE_LIKE_INDEXES) &&
+ relation->rd_rel->relhasindex)
+ {
+ AttrNumber *attmap = varattnos_map_schema(tupleDesc, cxt->columns);
+ List *parent_indexes;
+ ListCell *l;
+
+ parent_indexes = RelationGetIndexList(relation);
+
+ foreach(l, parent_indexes)
+ {
+ Oid parent_index_oid = lfirst_oid(l);
+ Relation parent_index;
+ IndexStmt *index_stmt;
+
+ parent_index = index_open(parent_index_oid, AccessShareLock);
+
+ /* Build CREATE INDEX statement to recreate the parent_index */
+ index_stmt = generateClonedIndexStmt(cxt, parent_index, attmap);
+
+ /* Copy comment on index */
+ if (inhRelation->options & CREATE_TABLE_LIKE_COMMENTS)
+ {
+ comment = GetComment(parent_index_oid, RelationRelationId, 0);
+
+ if (comment != NULL)
+ {
+ CommentStmt *stmt;
+
+ /*
+ * We have to assign the index a name now, so that we can
+ * reference it in CommentStmt.
+ */
+ if (index_stmt->idxname == NULL)
+ index_stmt->idxname = chooseIndexName(cxt->relation,
+ index_stmt);
+
+ stmt = makeNode(CommentStmt);
+ stmt->objtype = OBJECT_INDEX;
+ stmt->objname =
+ list_make2(makeString(cxt->relation->schemaname),
+ makeString(index_stmt->idxname));
+ stmt->objargs = NIL;
+ stmt->comment = comment;
+
+ cxt->alist = lappend(cxt->alist, stmt);
+ }
+ }
+
+ /* Save it in the inh_indexes list for the time being */
+ cxt->inh_indexes = lappend(cxt->inh_indexes, index_stmt);
+
+ index_close(parent_index, AccessShareLock);
}
}
heap_close(relation, NoLock);
}
+static void
+transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
+{
+ HeapTuple tuple;
+ TupleDesc tupdesc;
+ int i;
+ Oid ofTypeId;
+
+ AssertArg(ofTypename);
+
+ tuple = typenameType(NULL, ofTypename, NULL);
+ check_of_type(tuple);
+ ofTypeId = HeapTupleGetOid(tuple);
+ ofTypename->typeOid = ofTypeId; /* cached for later */
+
+ tupdesc = lookup_rowtype_tupdesc(ofTypeId, -1);
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = tupdesc->attrs[i];
+ ColumnDef *n;
+
+ if (attr->attisdropped)
+ continue;
+
+ n = makeNode(ColumnDef);
+ n->colname = pstrdup(NameStr(attr->attname));
+ n->typeName = makeTypeNameFromOid(attr->atttypid, attr->atttypmod);
+ n->inhcount = 0;
+ n->is_local = true;
+ n->is_not_null = false;
+ n->is_from_type = true;
+ n->storage = 0;
+ n->raw_default = NULL;
+ n->cooked_default = NULL;
+ n->collClause = NULL;
+ n->collOid = attr->attcollation;
+ n->constraints = NIL;
+ cxt->columns = lappend(cxt->columns, n);
+ }
+ DecrTupleDescRefCount(tupdesc);
+
+ ReleaseSysCache(tuple);
+}
+
/*
- * transformIndexConstraints
- * Handle UNIQUE and PRIMARY KEY constraints, which create indexes
+ * chooseIndexName
+ *
+ * Compute name for an index. This must match code in indexcmds.c.
+ *
+ * XXX this is inherently broken because the indexes aren't created
+ * immediately, so we fail to resolve conflicts when the same name is
+ * derived for multiple indexes. However, that's a reasonably uncommon
+ * situation, so we'll live with it for now.
*/
-static void
-transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt)
+static char *
+chooseIndexName(const RangeVar *relation, IndexStmt *index_stmt)
{
+ Oid namespaceId;
+ List *colnames;
+
+ namespaceId = RangeVarGetCreationNamespace(relation);
+ colnames = ChooseIndexColumnNames(index_stmt->indexParams);
+ return ChooseIndexName(relation->relname, namespaceId,
+ colnames, index_stmt->excludeOpNames,
+ index_stmt->primary, index_stmt->isconstraint);
+}
+
+/*
+ * Generate an IndexStmt node using information from an already existing index
+ * "source_idx". Attribute numbers should be adjusted according to attmap.
+ */
+static IndexStmt *
+generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
+ AttrNumber *attmap)
+{
+ Oid source_relid = RelationGetRelid(source_idx);
+ Form_pg_attribute *attrs = RelationGetDescr(source_idx)->attrs;
+ HeapTuple ht_idxrel;
+ HeapTuple ht_idx;
+ Form_pg_class idxrelrec;
+ Form_pg_index idxrec;
+ Form_pg_am amrec;
+ oidvector *indcollation;
+ oidvector *indclass;
IndexStmt *index;
- List *indexlist = NIL;
- ListCell *listptr;
- ListCell *l;
+ List *indexprs;
+ ListCell *indexpr_item;
+ Oid indrelid;
+ int keyno;
+ Oid keycoltype;
+ Datum datum;
+ bool isnull;
/*
- * Run through the constraints that need to generate an index. For PRIMARY
- * KEY, mark each column as NOT NULL and create an index. For UNIQUE,
- * create an index as for PRIMARY KEY, but do not insist on NOT NULL.
+ * Fetch pg_class tuple of source index. We can't use the copy in the
+ * relcache entry because it doesn't include optional fields.
*/
- foreach(listptr, cxt->ixconstraints)
+ ht_idxrel = SearchSysCache1(RELOID, ObjectIdGetDatum(source_relid));
+ if (!HeapTupleIsValid(ht_idxrel))
+ elog(ERROR, "cache lookup failed for relation %u", source_relid);
+ idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
+
+ /* Fetch pg_index tuple for source index from relcache entry */
+ ht_idx = source_idx->rd_indextuple;
+ idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
+ indrelid = idxrec->indrelid;
+
+ /* Fetch pg_am tuple for source index from relcache entry */
+ amrec = source_idx->rd_am;
+
+ /* Extract indcollation from the pg_index tuple */
+ datum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ Anum_pg_index_indcollation, &isnull);
+ Assert(!isnull);
+ indcollation = (oidvector *) DatumGetPointer(datum);
+
+ /* Extract indclass from the pg_index tuple */
+ datum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ Anum_pg_index_indclass, &isnull);
+ Assert(!isnull);
+ indclass = (oidvector *) DatumGetPointer(datum);
+
+ /* Begin building the IndexStmt */
+ index = makeNode(IndexStmt);
+ index->relation = cxt->relation;
+ index->accessMethod = pstrdup(NameStr(amrec->amname));
+ if (OidIsValid(idxrelrec->reltablespace))
+ index->tableSpace = get_tablespace_name(idxrelrec->reltablespace);
+ else
+ index->tableSpace = NULL;
+ index->indexOid = InvalidOid;
+ index->unique = idxrec->indisunique;
+ index->primary = idxrec->indisprimary;
+ index->concurrent = false;
+
+ /*
+ * We don't try to preserve the name of the source index; instead, just
+ * let DefineIndex() choose a reasonable name.
+ */
+ index->idxname = NULL;
+
+ /*
+ * If the index is marked PRIMARY or has an exclusion condition, it's
+ * certainly from a constraint; else, if it's not marked UNIQUE, it
+ * certainly isn't. If it is or might be from a constraint, we have to
+ * fetch the pg_constraint record.
+ */
+ if (index->primary || index->unique || idxrec->indisexclusion)
+ {
+ Oid constraintId = get_index_constraint(source_relid);
+
+ if (OidIsValid(constraintId))
+ {
+ HeapTuple ht_constr;
+ Form_pg_constraint conrec;
+
+ ht_constr = SearchSysCache1(CONSTROID,
+ ObjectIdGetDatum(constraintId));
+ if (!HeapTupleIsValid(ht_constr))
+ elog(ERROR, "cache lookup failed for constraint %u",
+ constraintId);
+ conrec = (Form_pg_constraint) GETSTRUCT(ht_constr);
+
+ index->isconstraint = true;
+ index->deferrable = conrec->condeferrable;
+ index->initdeferred = conrec->condeferred;
+
+ /* If it's an exclusion constraint, we need the operator names */
+ if (idxrec->indisexclusion)
+ {
+ Datum *elems;
+ int nElems;
+ int i;
+
+ Assert(conrec->contype == CONSTRAINT_EXCLUSION);
+ /* Extract operator OIDs from the pg_constraint tuple */
+ datum = SysCacheGetAttr(CONSTROID, ht_constr,
+ Anum_pg_constraint_conexclop,
+ &isnull);
+ if (isnull)
+ elog(ERROR, "null conexclop for constraint %u",
+ constraintId);
+
+ deconstruct_array(DatumGetArrayTypeP(datum),
+ OIDOID, sizeof(Oid), true, 'i',
+ &elems, NULL, &nElems);
+
+ for (i = 0; i < nElems; i++)
+ {
+ Oid operid = DatumGetObjectId(elems[i]);
+ HeapTuple opertup;
+ Form_pg_operator operform;
+ char *oprname;
+ char *nspname;
+ List *namelist;
+
+ opertup = SearchSysCache1(OPEROID,
+ ObjectIdGetDatum(operid));
+ if (!HeapTupleIsValid(opertup))
+ elog(ERROR, "cache lookup failed for operator %u",
+ operid);
+ operform = (Form_pg_operator) GETSTRUCT(opertup);
+ oprname = pstrdup(NameStr(operform->oprname));
+ /* For simplicity we always schema-qualify the op name */
+ nspname = get_namespace_name(operform->oprnamespace);
+ namelist = list_make2(makeString(nspname),
+ makeString(oprname));
+ index->excludeOpNames = lappend(index->excludeOpNames,
+ namelist);
+ ReleaseSysCache(opertup);
+ }
+ }
+
+ ReleaseSysCache(ht_constr);
+ }
+ else
+ index->isconstraint = false;
+ }
+ else
+ index->isconstraint = false;
+
+ /* Get the index expressions, if any */
+ datum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ Anum_pg_index_indexprs, &isnull);
+ if (!isnull)
+ {
+ char *exprsString;
+
+ exprsString = TextDatumGetCString(datum);
+ indexprs = (List *) stringToNode(exprsString);
+ }
+ else
+ indexprs = NIL;
+
+ /* Build the list of IndexElem */
+ index->indexParams = NIL;
+
+ indexpr_item = list_head(indexprs);
+ for (keyno = 0; keyno < idxrec->indnatts; keyno++)
{
- Constraint *constraint = lfirst(listptr);
- ListCell *keys;
IndexElem *iparam;
+ AttrNumber attnum = idxrec->indkey.values[keyno];
+ int16 opt = source_idx->rd_indoption[keyno];
- Assert(IsA(constraint, Constraint));
- Assert((constraint->contype == CONSTR_PRIMARY)
- || (constraint->contype == CONSTR_UNIQUE));
+ iparam = makeNode(IndexElem);
- index = makeNode(IndexStmt);
+ if (AttributeNumberIsValid(attnum))
+ {
+ /* Simple index column */
+ char *attname;
- index->unique = true;
- index->primary = (constraint->contype == CONSTR_PRIMARY);
- if (index->primary)
+ attname = get_relid_attribute_name(indrelid, attnum);
+ keycoltype = get_atttype(indrelid, attnum);
+
+ iparam->name = attname;
+ iparam->expr = NULL;
+ }
+ else
{
- if (cxt->pkey != NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("multiple primary keys for table \"%s\" are not allowed",
- cxt->relation->relname)));
- cxt->pkey = index;
+ /* Expressional index */
+ Node *indexkey;
- /*
- * In ALTER TABLE case, a primary index might already exist, but
- * DefineIndex will check for it.
- */
+ if (indexpr_item == NULL)
+ elog(ERROR, "too few entries in indexprs list");
+ indexkey = (Node *) lfirst(indexpr_item);
+ indexpr_item = lnext(indexpr_item);
+
+ /* OK to modify indexkey since we are working on a private copy */
+ change_varattnos_of_a_node(indexkey, attmap);
+
+ iparam->name = NULL;
+ iparam->expr = indexkey;
+
+ keycoltype = exprType(indexkey);
}
- index->isconstraint = true;
- if (constraint->name != NULL)
- index->idxname = pstrdup(constraint->name);
- else
- index->idxname = NULL; /* DefineIndex will choose name */
+ /* Copy the original index column name */
+ iparam->indexcolname = pstrdup(NameStr(attrs[keyno]->attname));
- index->relation = cxt->relation;
- index->accessMethod = DEFAULT_INDEX_TYPE;
- index->options = constraint->options;
- index->tableSpace = constraint->indexspace;
- index->indexParams = NIL;
- index->whereClause = NULL;
- index->concurrent = false;
+ /* Add the collation name, if non-default */
+ iparam->collation = get_collation(indcollation->values[keyno], keycoltype);
- /*
- * Make sure referenced keys exist. If we are making a PRIMARY KEY
- * index, also make sure they are NOT NULL, if possible. (Although we
- * could leave it to DefineIndex to mark the columns NOT NULL, it's
- * more efficient to get it right the first time.)
- */
- foreach(keys, constraint->keys)
- {
- char *key = strVal(lfirst(keys));
- bool found = false;
- ColumnDef *column = NULL;
- ListCell *columns;
+ /* Add the operator class name, if non-default */
+ iparam->opclass = get_opclass(indclass->values[keyno], keycoltype);
- foreach(columns, cxt->columns)
+ iparam->ordering = SORTBY_DEFAULT;
+ iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+
+ /* Adjust options if necessary */
+ if (amrec->amcanorder)
+ {
+ /*
+ * If it supports sort ordering, copy DESC and NULLS opts. Don't
+ * set non-default settings unnecessarily, though, so as to
+ * improve the chance of recognizing equivalence to constraint
+ * indexes.
+ */
+ if (opt & INDOPTION_DESC)
{
- column = (ColumnDef *) lfirst(columns);
- Assert(IsA(column, ColumnDef));
- if (strcmp(column->colname, key) == 0)
- {
- found = true;
- break;
- }
+ iparam->ordering = SORTBY_DESC;
+ if ((opt & INDOPTION_NULLS_FIRST) == 0)
+ iparam->nulls_ordering = SORTBY_NULLS_LAST;
}
- if (found)
+ else
{
- /* found column in the new table; force it to be NOT NULL */
- if (constraint->contype == CONSTR_PRIMARY)
- column->is_not_null = TRUE;
+ if (opt & INDOPTION_NULLS_FIRST)
+ iparam->nulls_ordering = SORTBY_NULLS_FIRST;
}
- else if (SystemAttributeByName(key, cxt->hasoids) != NULL)
- {
- /*
- * column will be a system column in the new table, so accept
- * it. System columns can't ever be null, so no need to worry
- * about PRIMARY/NOT NULL constraint.
- */
- found = true;
- }
- else if (cxt->inhRelations)
- {
- /* try inherited tables */
- ListCell *inher;
+ }
- foreach(inher, cxt->inhRelations)
- {
- RangeVar *inh = (RangeVar *) lfirst(inher);
- Relation rel;
- int count;
+ index->indexParams = lappend(index->indexParams, iparam);
+ }
- 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)));
- for (count = 0; count < rel->rd_att->natts; count++)
- {
- Form_pg_attribute inhattr = rel->rd_att->attrs[count];
- char *inhname = NameStr(inhattr->attname);
-
- if (inhattr->attisdropped)
- continue;
- if (strcmp(key, inhname) == 0)
- {
- found = true;
-
- /*
- * We currently have no easy way to force an
- * inherited column to be NOT NULL at creation, if
- * its parent wasn't so already. We leave it to
- * DefineIndex to fix things up in this case.
- */
- break;
- }
- }
- heap_close(rel, NoLock);
- if (found)
- break;
- }
- }
+ /* Copy reloptions if any */
+ datum = SysCacheGetAttr(RELOID, ht_idxrel,
+ Anum_pg_class_reloptions, &isnull);
+ if (!isnull)
+ index->options = untransformRelOptions(datum);
- /*
- * In the ALTER TABLE case, don't complain about index keys not
- * created in the command; they may well exist already.
- * DefineIndex will complain about them if not, and will also take
- * care of marking them NOT NULL.
- */
- if (!found && !cxt->isalter)
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" named in key does not exist",
- key)));
+ /* If it's a partial index, decompile and append the predicate */
+ datum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ Anum_pg_index_indpred, &isnull);
+ if (!isnull)
+ {
+ char *pred_str;
- /* Check for PRIMARY KEY(foo, foo) */
- foreach(columns, index->indexParams)
- {
- iparam = (IndexElem *) lfirst(columns);
- if (iparam->name && strcmp(key, iparam->name) == 0)
- {
- if (index->primary)
- ereport(ERROR,
- (errcode(ERRCODE_DUPLICATE_COLUMN),
- errmsg("column \"%s\" appears twice in primary key constraint",
- key)));
- else
- ereport(ERROR,
- (errcode(ERRCODE_DUPLICATE_COLUMN),
- errmsg("column \"%s\" appears twice in unique constraint",
- key)));
- }
- }
+ /* Convert text string to node tree */
+ pred_str = TextDatumGetCString(datum);
+ index->whereClause = (Node *) stringToNode(pred_str);
+ /* Adjust attribute numbers */
+ change_varattnos_of_a_node(index->whereClause, attmap);
+ }
- /* OK, add it to the index definition */
- iparam = makeNode(IndexElem);
- iparam->name = pstrdup(key);
- iparam->expr = NULL;
- iparam->opclass = NIL;
- iparam->ordering = SORTBY_DEFAULT;
- iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
- index->indexParams = lappend(index->indexParams, iparam);
+ /* Clean up */
+ ReleaseSysCache(ht_idxrel);
+
+ return index;
+}
+
+/*
+ * get_collation - fetch qualified name of a collation
+ *
+ * If collation is InvalidOid or is the default for the given actual_datatype,
+ * then the return value is NIL.
+ */
+static List *
+get_collation(Oid collation, Oid actual_datatype)
+{
+ List *result;
+ HeapTuple ht_coll;
+ Form_pg_collation coll_rec;
+ char *nsp_name;
+ char *coll_name;
+
+ if (!OidIsValid(collation))
+ return NIL; /* easy case */
+ if (collation == get_typcollation(actual_datatype))
+ return NIL; /* just let it default */
+
+ ht_coll = SearchSysCache1(COLLOID, ObjectIdGetDatum(collation));
+ if (!HeapTupleIsValid(ht_coll))
+ elog(ERROR, "cache lookup failed for collation %u", collation);
+ coll_rec = (Form_pg_collation) GETSTRUCT(ht_coll);
+
+ /* For simplicity, we always schema-qualify the name */
+ nsp_name = get_namespace_name(coll_rec->collnamespace);
+ coll_name = pstrdup(NameStr(coll_rec->collname));
+ result = list_make2(makeString(nsp_name), makeString(coll_name));
+
+ ReleaseSysCache(ht_coll);
+ return result;
+}
+
+/*
+ * get_opclass - fetch qualified name of an index operator class
+ *
+ * If the opclass is the default for the given actual_datatype, then
+ * the return value is NIL.
+ */
+static List *
+get_opclass(Oid opclass, Oid actual_datatype)
+{
+ List *result = NIL;
+ HeapTuple ht_opc;
+ Form_pg_opclass opc_rec;
+
+ ht_opc = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass));
+ if (!HeapTupleIsValid(ht_opc))
+ elog(ERROR, "cache lookup failed for opclass %u", opclass);
+ opc_rec = (Form_pg_opclass) GETSTRUCT(ht_opc);
+
+ if (GetDefaultOpClass(actual_datatype, opc_rec->opcmethod) != opclass)
+ {
+ /* For simplicity, we always schema-qualify the name */
+ char *nsp_name = get_namespace_name(opc_rec->opcnamespace);
+ char *opc_name = pstrdup(NameStr(opc_rec->opcname));
+
+ result = list_make2(makeString(nsp_name), makeString(opc_name));
+ }
+
+ ReleaseSysCache(ht_opc);
+ return result;
+}
+
+
+/*
+ * transformIndexConstraints
+ * Handle UNIQUE, PRIMARY KEY, EXCLUDE constraints, which create indexes.
+ * We also merge in any index definitions arising from
+ * LIKE ... INCLUDING INDEXES.
+ */
+static void
+transformIndexConstraints(CreateStmtContext *cxt)
+{
+ IndexStmt *index;
+ List *indexlist = NIL;
+ ListCell *lc;
+
+ /*
+ * Run through the constraints that need to generate an index. For PRIMARY
+ * KEY, mark each column as NOT NULL and create an index. For UNIQUE or
+ * EXCLUDE, create an index as for PRIMARY KEY, but do not insist on NOT
+ * NULL.
+ */
+ foreach(lc, cxt->ixconstraints)
+ {
+ Constraint *constraint = (Constraint *) lfirst(lc);
+
+ Assert(IsA(constraint, Constraint));
+ Assert(constraint->contype == CONSTR_PRIMARY ||
+ constraint->contype == CONSTR_UNIQUE ||
+ constraint->contype == CONSTR_EXCLUSION);
+
+ index = transformIndexConstraint(constraint, cxt);
+
+ indexlist = lappend(indexlist, index);
+ }
+
+ /* Add in any indexes defined by LIKE ... INCLUDING INDEXES */
+ foreach(lc, cxt->inh_indexes)
+ {
+ index = (IndexStmt *) lfirst(lc);
+
+ if (index->primary)
+ {
+ if (cxt->pkey != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("multiple primary keys for table \"%s\" are not allowed",
+ cxt->relation->relname)));
+ cxt->pkey = index;
}
indexlist = lappend(indexlist, index);
cxt->alist = list_make1(cxt->pkey);
}
- foreach(l, indexlist)
+ foreach(lc, indexlist)
{
bool keep = true;
ListCell *k;
- index = lfirst(l);
+ index = lfirst(lc);
/* if it's pkey, it's already in cxt->alist */
if (index == cxt->pkey)
{
IndexStmt *priorindex = lfirst(k);
- if (equal(index->indexParams, priorindex->indexParams))
+ if (equal(index->indexParams, priorindex->indexParams) &&
+ equal(index->whereClause, priorindex->whereClause) &&
+ equal(index->excludeOpNames, priorindex->excludeOpNames) &&
+ strcmp(index->accessMethod, priorindex->accessMethod) == 0 &&
+ index->deferrable == priorindex->deferrable &&
+ index->initdeferred == priorindex->initdeferred)
{
+ priorindex->unique |= index->unique;
+
/*
* If the prior index is as yet unnamed, and this one is
* named, then transfer the name to the prior index. This
}
/*
+ * transformIndexConstraint
+ * Transform one UNIQUE, PRIMARY KEY, or EXCLUDE constraint for
+ * transformIndexConstraints.
+ */
+static IndexStmt *
+transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
+{
+ IndexStmt *index;
+ ListCell *lc;
+
+ index = makeNode(IndexStmt);
+
+ index->unique = (constraint->contype != CONSTR_EXCLUSION);
+ index->primary = (constraint->contype == CONSTR_PRIMARY);
+ if (index->primary)
+ {
+ if (cxt->pkey != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("multiple primary keys for table \"%s\" are not allowed",
+ cxt->relation->relname),
+ parser_errposition(cxt->pstate, constraint->location)));
+ cxt->pkey = index;
+
+ /*
+ * In ALTER TABLE case, a primary index might already exist, but
+ * DefineIndex will check for it.
+ */
+ }
+ index->isconstraint = true;
+ index->deferrable = constraint->deferrable;
+ index->initdeferred = constraint->initdeferred;
+
+ if (constraint->conname != NULL)
+ index->idxname = pstrdup(constraint->conname);
+ else
+ index->idxname = NULL; /* DefineIndex will choose name */
+
+ index->relation = cxt->relation;
+ index->accessMethod = constraint->access_method ? constraint->access_method : DEFAULT_INDEX_TYPE;
+ index->options = constraint->options;
+ index->tableSpace = constraint->indexspace;
+ index->whereClause = constraint->where_clause;
+ index->indexParams = NIL;
+ index->excludeOpNames = NIL;
+ index->indexOid = InvalidOid;
+ index->concurrent = false;
+
+ /*
+ * If it's ALTER TABLE ADD CONSTRAINT USING INDEX, look up the index and
+ * verify it's usable, then extract the implied column name list. (We
+ * will not actually need the column name list at runtime, but we need it
+ * now to check for duplicate column entries below.)
+ */
+ if (constraint->indexname != NULL)
+ {
+ char *index_name = constraint->indexname;
+ Relation heap_rel = cxt->rel;
+ Oid index_oid;
+ Relation index_rel;
+ Form_pg_index index_form;
+ oidvector *indclass;
+ Datum indclassDatum;
+ bool isnull;
+ int i;
+
+ /* Grammar should not allow this with explicit column list */
+ Assert(constraint->keys == NIL);
+
+ /* Grammar should only allow PRIMARY and UNIQUE constraints */
+ Assert(constraint->contype == CONSTR_PRIMARY ||
+ constraint->contype == CONSTR_UNIQUE);
+
+ /* Must be ALTER, not CREATE, but grammar doesn't enforce that */
+ if (!cxt->isalter)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot use an existing index in CREATE TABLE"),
+ parser_errposition(cxt->pstate, constraint->location)));
+
+ /* Look for the index in the same schema as the table */
+ index_oid = get_relname_relid(index_name, RelationGetNamespace(heap_rel));
+
+ if (!OidIsValid(index_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("index \"%s\" does not exist", index_name),
+ parser_errposition(cxt->pstate, constraint->location)));
+
+ /* Open the index (this will throw an error if it is not an index) */
+ index_rel = index_open(index_oid, AccessShareLock);
+ index_form = index_rel->rd_index;
+
+ /* Check that it does not have an associated constraint already */
+ if (OidIsValid(get_index_constraint(index_oid)))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("index \"%s\" is already associated with a constraint",
+ index_name),
+ parser_errposition(cxt->pstate, constraint->location)));
+
+ /* Perform validity checks on the index */
+ if (index_form->indrelid != RelationGetRelid(heap_rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("index \"%s\" does not belong to table \"%s\"",
+ index_name, RelationGetRelationName(heap_rel)),
+ parser_errposition(cxt->pstate, constraint->location)));
+
+ if (!index_form->indisvalid)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("index \"%s\" is not valid", index_name),
+ parser_errposition(cxt->pstate, constraint->location)));
+
+ if (!index_form->indisready)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("index \"%s\" is not ready", index_name),
+ parser_errposition(cxt->pstate, constraint->location)));
+
+ if (!index_form->indisunique)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is not a unique index", index_name),
+ errdetail("Cannot create a primary key or unique constraint using such an index."),
+ parser_errposition(cxt->pstate, constraint->location)));
+
+ if (RelationGetIndexExpressions(index_rel) != NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("index \"%s\" contains expressions", index_name),
+ errdetail("Cannot create a primary key or unique constraint using such an index."),
+ parser_errposition(cxt->pstate, constraint->location)));
+
+ if (RelationGetIndexPredicate(index_rel) != NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is a partial index", index_name),
+ errdetail("Cannot create a primary key or unique constraint using such an index."),
+ parser_errposition(cxt->pstate, constraint->location)));
+
+ /*
+ * It's probably unsafe to change a deferred index to non-deferred. (A
+ * non-constraint index couldn't be deferred anyway, so this case
+ * should never occur; no need to sweat, but let's check it.)
+ */
+ if (!index_form->indimmediate && !constraint->deferrable)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is a deferrable index", index_name),
+ errdetail("Cannot create a non-deferrable constraint using a deferrable index."),
+ parser_errposition(cxt->pstate, constraint->location)));
+
+ /*
+ * Insist on it being a btree. That's the only kind that supports
+ * uniqueness at the moment anyway; but we must have an index that
+ * exactly matches what you'd get from plain ADD CONSTRAINT syntax,
+ * else dump and reload will produce a different index (breaking
+ * pg_upgrade in particular).
+ */
+ if (index_rel->rd_rel->relam != get_am_oid(DEFAULT_INDEX_TYPE, false))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("index \"%s\" is not a b-tree", index_name),
+ parser_errposition(cxt->pstate, constraint->location)));
+
+ /* Must get indclass the hard way */
+ indclassDatum = SysCacheGetAttr(INDEXRELID, index_rel->rd_indextuple,
+ Anum_pg_index_indclass, &isnull);
+ Assert(!isnull);
+ indclass = (oidvector *) DatumGetPointer(indclassDatum);
+
+ for (i = 0; i < index_form->indnatts; i++)
+ {
+ int2 attnum = index_form->indkey.values[i];
+ Form_pg_attribute attform;
+ char *attname;
+ Oid defopclass;
+
+ /*
+ * We shouldn't see attnum == 0 here, since we already rejected
+ * expression indexes. If we do, SystemAttributeDefinition will
+ * throw an error.
+ */
+ if (attnum > 0)
+ {
+ Assert(attnum <= heap_rel->rd_att->natts);
+ attform = heap_rel->rd_att->attrs[attnum - 1];
+ }
+ else
+ attform = SystemAttributeDefinition(attnum,
+ heap_rel->rd_rel->relhasoids);
+ attname = pstrdup(NameStr(attform->attname));
+
+ /*
+ * Insist on default opclass and sort options. While the index
+ * would still work as a constraint with non-default settings, it
+ * might not provide exactly the same uniqueness semantics as
+ * you'd get from a normally-created constraint; and there's also
+ * the dump/reload problem mentioned above.
+ */
+ defopclass = GetDefaultOpClass(attform->atttypid,
+ index_rel->rd_rel->relam);
+ if (indclass->values[i] != defopclass ||
+ index_rel->rd_indoption[i] != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("index \"%s\" does not have default sorting behavior", index_name),
+ errdetail("Cannot create a primary key or unique constraint using such an index."),
+ parser_errposition(cxt->pstate, constraint->location)));
+
+ constraint->keys = lappend(constraint->keys, makeString(attname));
+ }
+
+ /* Close the index relation but keep the lock */
+ relation_close(index_rel, NoLock);
+
+ index->indexOid = index_oid;
+ }
+
+ /*
+ * If it's an EXCLUDE constraint, the grammar returns a list of pairs of
+ * IndexElems and operator names. We have to break that apart into
+ * separate lists.
+ */
+ if (constraint->contype == CONSTR_EXCLUSION)
+ {
+ foreach(lc, constraint->exclusions)
+ {
+ List *pair = (List *) lfirst(lc);
+ IndexElem *elem;
+ List *opname;
+
+ Assert(list_length(pair) == 2);
+ elem = (IndexElem *) linitial(pair);
+ Assert(IsA(elem, IndexElem));
+ opname = (List *) lsecond(pair);
+ Assert(IsA(opname, List));
+
+ index->indexParams = lappend(index->indexParams, elem);
+ index->excludeOpNames = lappend(index->excludeOpNames, opname);
+ }
+
+ return index;
+ }
+
+ /*
+ * For UNIQUE and PRIMARY KEY, we just have a list of column names.
+ *
+ * Make sure referenced keys exist. If we are making a PRIMARY KEY index,
+ * also make sure they are NOT NULL, if possible. (Although we could leave
+ * it to DefineIndex to mark the columns NOT NULL, it's more efficient to
+ * get it right the first time.)
+ */
+ foreach(lc, constraint->keys)
+ {
+ char *key = strVal(lfirst(lc));
+ bool found = false;
+ ColumnDef *column = NULL;
+ ListCell *columns;
+ IndexElem *iparam;
+
+ foreach(columns, cxt->columns)
+ {
+ column = (ColumnDef *) lfirst(columns);
+ Assert(IsA(column, ColumnDef));
+ if (strcmp(column->colname, key) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ if (found)
+ {
+ /* found column in the new table; force it to be NOT NULL */
+ if (constraint->contype == CONSTR_PRIMARY)
+ column->is_not_null = TRUE;
+ }
+ else if (SystemAttributeByName(key, cxt->hasoids) != NULL)
+ {
+ /*
+ * column will be a system column in the new table, so accept it.
+ * System columns can't ever be null, so no need to worry about
+ * PRIMARY/NOT NULL constraint.
+ */
+ found = true;
+ }
+ else if (cxt->inhRelations)
+ {
+ /* try inherited tables */
+ ListCell *inher;
+
+ foreach(inher, cxt->inhRelations)
+ {
+ RangeVar *inh = (RangeVar *) lfirst(inher);
+ Relation rel;
+ int count;
+
+ 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)));
+ for (count = 0; count < rel->rd_att->natts; count++)
+ {
+ Form_pg_attribute inhattr = rel->rd_att->attrs[count];
+ char *inhname = NameStr(inhattr->attname);
+
+ if (inhattr->attisdropped)
+ continue;
+ if (strcmp(key, inhname) == 0)
+ {
+ found = true;
+
+ /*
+ * We currently have no easy way to force an inherited
+ * column to be NOT NULL at creation, if its parent
+ * wasn't so already. We leave it to DefineIndex to
+ * fix things up in this case.
+ */
+ break;
+ }
+ }
+ heap_close(rel, NoLock);
+ if (found)
+ break;
+ }
+ }
+
+ /*
+ * In the ALTER TABLE case, don't complain about index keys not
+ * created in the command; they may well exist already. DefineIndex
+ * will complain about them if not, and will also take care of marking
+ * them NOT NULL.
+ */
+ if (!found && !cxt->isalter)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" named in key does not exist", key),
+ parser_errposition(cxt->pstate, constraint->location)));
+
+ /* Check for PRIMARY KEY(foo, foo) */
+ foreach(columns, index->indexParams)
+ {
+ iparam = (IndexElem *) lfirst(columns);
+ if (iparam->name && strcmp(key, iparam->name) == 0)
+ {
+ if (index->primary)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column \"%s\" appears twice in primary key constraint",
+ key),
+ parser_errposition(cxt->pstate, constraint->location)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column \"%s\" appears twice in unique constraint",
+ key),
+ parser_errposition(cxt->pstate, constraint->location)));
+ }
+ }
+
+ /* OK, add it to the index definition */
+ iparam = makeNode(IndexElem);
+ iparam->name = pstrdup(key);
+ iparam->expr = NULL;
+ iparam->indexcolname = NULL;
+ iparam->collation = NIL;
+ iparam->opclass = NIL;
+ iparam->ordering = SORTBY_DEFAULT;
+ iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+ index->indexParams = lappend(index->indexParams, iparam);
+ }
+
+ return index;
+}
+
+/*
* transformFKConstraints
* handle FOREIGN KEY constraints
*/
static void
-transformFKConstraints(ParseState *pstate, CreateStmtContext *cxt,
+transformFKConstraints(CreateStmtContext *cxt,
bool skipValidation, bool isAddConstraint)
{
ListCell *fkclist;
/*
* If CREATE TABLE or adding a column with NULL default, we can safely
- * skip validation of the constraint.
+ * skip validation of FK constraints, and nonetheless mark them valid.
+ * (This will override any user-supplied NOT VALID flag.)
*/
if (skipValidation)
{
foreach(fkclist, cxt->fkconstraints)
{
- FkConstraint *fkconstraint = (FkConstraint *) lfirst(fkclist);
+ Constraint *constraint = (Constraint *) lfirst(fkclist);
- fkconstraint->skip_validation = true;
+ constraint->skip_validation = true;
+ constraint->initially_valid = true;
}
}
foreach(fkclist, cxt->fkconstraints)
{
- FkConstraint *fkconstraint = (FkConstraint *) lfirst(fkclist);
+ Constraint *constraint = (Constraint *) lfirst(fkclist);
AlterTableCmd *altercmd = makeNode(AlterTableCmd);
altercmd->subtype = AT_ProcessedConstraint;
altercmd->name = NULL;
- altercmd->def = (Node *) fkconstraint;
+ altercmd->def = (Node *) constraint;
alterstmt->cmds = lappend(alterstmt->cmds, altercmd);
}
}
/*
- * transformIndexStmt - parse analysis for CREATE INDEX
+ * transformIndexStmt - parse analysis for CREATE INDEX and ALTER TABLE
*
* Note: this is a no-op for an index not using either index expressions or
- * a predicate expression. There are several code paths that create indexes
+ * a predicate expression. There are several code paths that create indexes
* without bothering to call this, because they know they don't have any
* such expressions to deal with.
*/
ListCell *l;
/*
- * We must not scribble on the passed-in IndexStmt, so copy it. (This
- * is overkill, but easy.)
+ * We must not scribble on the passed-in IndexStmt, so copy it. (This is
+ * overkill, but easy.)
*/
stmt = (IndexStmt *) copyObject(stmt);
/*
- * Open the parent table with appropriate locking. We must do this
+ * Open the parent table with appropriate locking. We must do this
* because addRangeTableEntry() would acquire only AccessShareLock,
- * leaving DefineIndex() needing to do a lock upgrade with consequent
- * risk of deadlock. Make sure this stays in sync with the type of
- * lock DefineIndex() wants.
+ * leaving DefineIndex() needing to do a lock upgrade with consequent risk
+ * of deadlock. Make sure this stays in sync with the type of lock
+ * DefineIndex() wants. If we are being called by ALTER TABLE, we will
+ * already hold a higher lock.
*/
rel = heap_openrv(stmt->relation,
- (stmt->concurrent ? ShareUpdateExclusiveLock : ShareLock));
+ (stmt->concurrent ? ShareUpdateExclusiveLock : ShareLock));
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
/*
- * Put the parent table into the rtable so that the expressions can
- * refer to its fields without qualification.
+ * Put the parent table into the rtable so that the expressions can refer
+ * to its fields without qualification.
*/
rte = addRangeTableEntry(pstate, stmt->relation, NULL, false, true);
/* take care of the where clause */
if (stmt->whereClause)
+ {
stmt->whereClause = transformWhereClause(pstate,
stmt->whereClause,
"WHERE");
+ /* we have to fix its collations too */
+ assign_expr_collations(pstate, stmt->whereClause);
+ }
/* take care of any index expressions */
foreach(l, stmt->indexParams)
if (ielem->expr)
{
+ /* Extract preliminary index col name before transforming expr */
+ if (ielem->indexcolname == NULL)
+ ielem->indexcolname = FigureIndexColname(ielem->expr);
+
+ /* Now do parse transformation of the expression */
ielem->expr = transformExpr(pstate, ielem->expr);
+ /* We have to fix its collations too */
+ assign_expr_collations(pstate, ielem->expr);
+
/*
* We check only that the result type is legitimate; this is for
* consistency with what transformWhereClause() checks for the
* qualification.
*/
oldrte = addRangeTableEntryForRelation(pstate, rel,
- makeAlias("*OLD*", NIL),
+ makeAlias("old", NIL),
false, false);
newrte = addRangeTableEntryForRelation(pstate, rel,
- makeAlias("*NEW*", NIL),
+ makeAlias("new", NIL),
false, false);
/* Must override addRangeTableEntry's default access-check flags */
oldrte->requiredPerms = 0;
/* take care of the where clause */
*whereClause = transformWhereClause(pstate,
- (Node *) copyObject(stmt->whereClause),
+ (Node *) copyObject(stmt->whereClause),
"WHERE");
+ /* we have to fix its collations too */
+ assign_expr_collations(pstate, *whereClause);
if (list_length(pstate->p_rtable) != 2) /* naughty, naughty... */
ereport(ERROR,
ereport(ERROR,
(errcode(ERRCODE_GROUPING_ERROR),
errmsg("cannot use aggregate function in rule WHERE condition")));
+ if (pstate->p_hasWindowFuncs)
+ ereport(ERROR,
+ (errcode(ERRCODE_WINDOWING_ERROR),
+ errmsg("cannot use window function in rule WHERE condition")));
/*
* 'instead nothing' rules with a qualification need a query rangetable so
nothing_qry->commandType = CMD_NOTHING;
nothing_qry->rtable = pstate->p_rtable;
- nothing_qry->jointree = makeFromExpr(NIL, NULL); /* no join wanted */
+ nothing_qry->jointree = makeFromExpr(NIL, NULL); /* no join wanted */
*actions = list_make1(nothing_qry);
}
has_new;
/*
- * Since outer ParseState isn't parent of inner, have to pass
- * down the query text by hand.
+ * Since outer ParseState isn't parent of inner, have to pass down
+ * the query text by hand.
*/
sub_pstate->p_sourcetext = queryString;
* them in the joinlist.
*/
oldrte = addRangeTableEntryForRelation(sub_pstate, rel,
- makeAlias("*OLD*", NIL),
+ makeAlias("old", NIL),
false, false);
newrte = addRangeTableEntryForRelation(sub_pstate, rel,
- makeAlias("*NEW*", NIL),
+ makeAlias("new", NIL),
false, false);
oldrte->requiredPerms = 0;
newrte->requiredPerms = 0;
}
/*
+ * OLD/NEW are not allowed in WITH queries, because they would
+ * amount to outer references for the WITH, which we disallow.
+ * However, they were already in the outer rangetable when we
+ * analyzed the query, so we have to check.
+ *
+ * Note that in the INSERT...SELECT case, we need to examine the
+ * CTE lists of both top_subqry and sub_qry.
+ *
+ * Note that we aren't digging into the body of the query looking
+ * for WITHs in nested sub-SELECTs. A WITH down there can
+ * legitimately refer to OLD/NEW, because it'd be an
+ * indirect-correlated outer reference.
+ */
+ if (rangeTableEntry_used((Node *) top_subqry->cteList,
+ PRS2_OLD_VARNO, 0) ||
+ rangeTableEntry_used((Node *) sub_qry->cteList,
+ PRS2_OLD_VARNO, 0))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot refer to OLD within WITH query")));
+ if (rangeTableEntry_used((Node *) top_subqry->cteList,
+ PRS2_NEW_VARNO, 0) ||
+ rangeTableEntry_used((Node *) sub_qry->cteList,
+ PRS2_NEW_VARNO, 0))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot refer to NEW within WITH query")));
+
+ /*
* For efficiency's sake, add OLD to the rule action's jointree
* only if it was actually referenced in the statement or qual.
*
List *newcmds = NIL;
bool skipValidation = true;
AlterTableCmd *newcmd;
+ LOCKMODE lockmode;
/*
- * We must not scribble on the passed-in AlterTableStmt, so copy it.
- * (This is overkill, but easy.)
+ * We must not scribble on the passed-in AlterTableStmt, so copy it. (This
+ * is overkill, but easy.)
*/
stmt = (AlterTableStmt *) copyObject(stmt);
/*
- * Acquire exclusive lock on the target relation, which will be held
+ * Determine the appropriate lock level for this list of subcommands.
+ */
+ lockmode = AlterTableGetLockLevel(stmt->cmds);
+
+ /*
+ * Acquire appropriate lock on the target relation, which will be held
* until end of transaction. This ensures any decisions we make here
- * based on the state of the relation will still be good at execution.
- * We must get exclusive lock now because execution will; taking a lower
- * grade lock now and trying to upgrade later risks deadlock.
+ * based on the state of the relation will still be good at execution. We
+ * must get lock now because execution will later require it; taking a
+ * lower grade lock now and trying to upgrade later risks deadlock. Any
+ * new commands we add after this must not upgrade the lock level
+ * requested here.
*/
- rel = relation_openrv(stmt->relation, AccessExclusiveLock);
+ rel = relation_openrv(stmt->relation, lockmode);
- /* Set up pstate */
+ /* Set up pstate and CreateStmtContext */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
+ cxt.pstate = pstate;
cxt.stmtType = "ALTER TABLE";
cxt.relation = stmt->relation;
cxt.rel = rel;
cxt.ckconstraints = NIL;
cxt.fkconstraints = NIL;
cxt.ixconstraints = NIL;
+ cxt.inh_indexes = NIL;
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
switch (cmd->subtype)
{
case AT_AddColumn:
+ case AT_AddColumnToView:
{
ColumnDef *def = (ColumnDef *) cmd->def;
- Assert(IsA(cmd->def, ColumnDef));
- transformColumnDefinition(pstate, &cxt,
- (ColumnDef *) cmd->def);
+ Assert(IsA(def, ColumnDef));
+ transformColumnDefinition(&cxt, def);
/*
* If the column has a non-null default, we can't skip
* validation of foreign keys.
*/
- if (((ColumnDef *) cmd->def)->raw_default != NULL)
+ if (def->raw_default != NULL)
skipValidation = false;
- newcmds = lappend(newcmds, cmd);
-
- /*
- * Convert an ADD COLUMN ... NOT NULL constraint to a
- * separate command
- */
- if (def->is_not_null)
- {
- /* Remove NOT NULL from AddColumn */
- def->is_not_null = false;
-
- /* Add as a separate AlterTableCmd */
- newcmd = makeNode(AlterTableCmd);
- newcmd->subtype = AT_SetNotNull;
- newcmd->name = pstrdup(def->colname);
- newcmds = lappend(newcmds, newcmd);
- }
-
/*
* All constraints are processed in other ways. Remove the
* original list
*/
def->constraints = NIL;
+ newcmds = lappend(newcmds, cmd);
break;
}
case AT_AddConstraint:
/*
* The original AddConstraint cmd node doesn't go to newcmds
*/
-
if (IsA(cmd->def, Constraint))
- transformTableConstraint(pstate, &cxt,
- (Constraint *) cmd->def);
- else if (IsA(cmd->def, FkConstraint))
{
- cxt.fkconstraints = lappend(cxt.fkconstraints, cmd->def);
- skipValidation = false;
+ transformTableConstraint(&cxt, (Constraint *) cmd->def);
+ if (((Constraint *) cmd->def)->contype == CONSTR_FOREIGN)
+ skipValidation = false;
}
else
elog(ERROR, "unrecognized node type: %d",
/*
* transformIndexConstraints wants cxt.alist to contain only index
- * statements, so transfer anything we already have into save_alist.
+ * statements, so transfer anything we already have into save_alist
* immediately.
*/
save_alist = cxt.alist;
cxt.alist = NIL;
/* Postprocess index and FK constraints */
- transformIndexConstraints(pstate, &cxt);
+ transformIndexConstraints(&cxt);
- transformFKConstraints(pstate, &cxt, skipValidation, true);
+ transformFKConstraints(&cxt, skipValidation, true);
/*
* Push any index-creation commands into the ALTER, so that they can be
* scheduled nicely by tablecmds.c. Note that tablecmds.c assumes that
- * the IndexStmt attached to an AT_AddIndex subcommand has already been
- * through transformIndexStmt.
+ * the IndexStmt attached to an AT_AddIndex or AT_AddIndexConstraint
+ * subcommand has already been through transformIndexStmt.
*/
foreach(l, cxt.alist)
{
- Node *idxstmt = (Node *) lfirst(l);
+ IndexStmt *idxstmt = (IndexStmt *) lfirst(l);
Assert(IsA(idxstmt, IndexStmt));
+ idxstmt = transformIndexStmt(idxstmt, queryString);
newcmd = makeNode(AlterTableCmd);
- newcmd->subtype = AT_AddIndex;
- newcmd->def = (Node *) transformIndexStmt((IndexStmt *) idxstmt,
- queryString);
+ newcmd->subtype = OidIsValid(idxstmt->indexOid) ? AT_AddIndexConstraint : AT_AddIndex;
+ newcmd->def = (Node *) idxstmt;
newcmds = lappend(newcmds, newcmd);
}
cxt.alist = NIL;
* to attach constraint attributes to their primary constraint nodes
* and detect inconsistent/misplaced constraint attributes.
*
- * NOTE: currently, attributes are only supported for FOREIGN KEY primary
- * constraints, but someday they ought to be supported for other constraints.
+ * NOTE: currently, attributes are only supported for FOREIGN KEY, UNIQUE,
+ * EXCLUSION, and PRIMARY KEY constraints, but someday they ought to be
+ * supported for other constraint types.
*/
static void
-transformConstraintAttrs(List *constraintList)
+transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList)
{
- Node *lastprimarynode = NULL;
+ Constraint *lastprimarycon = NULL;
bool saw_deferrability = false;
bool saw_initially = false;
ListCell *clist;
+#define SUPPORTS_ATTRS(node) \
+ ((node) != NULL && \
+ ((node)->contype == CONSTR_PRIMARY || \
+ (node)->contype == CONSTR_UNIQUE || \
+ (node)->contype == CONSTR_EXCLUSION || \
+ (node)->contype == CONSTR_FOREIGN))
+
foreach(clist, constraintList)
{
- Node *node = lfirst(clist);
+ Constraint *con = (Constraint *) lfirst(clist);
- if (!IsA(node, Constraint))
+ if (!IsA(con, Constraint))
+ elog(ERROR, "unrecognized node type: %d",
+ (int) nodeTag(con));
+ switch (con->contype)
{
- lastprimarynode = node;
- /* reset flags for new primary node */
- saw_deferrability = false;
- saw_initially = false;
- }
- else
- {
- Constraint *con = (Constraint *) node;
+ case CONSTR_ATTR_DEFERRABLE:
+ if (!SUPPORTS_ATTRS(lastprimarycon))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("misplaced DEFERRABLE clause"),
+ parser_errposition(cxt->pstate, con->location)));
+ if (saw_deferrability)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed"),
+ parser_errposition(cxt->pstate, con->location)));
+ saw_deferrability = true;
+ lastprimarycon->deferrable = true;
+ break;
- switch (con->contype)
- {
- case CONSTR_ATTR_DEFERRABLE:
- if (lastprimarynode == NULL ||
- !IsA(lastprimarynode, FkConstraint))
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("misplaced DEFERRABLE clause")));
- if (saw_deferrability)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed")));
- saw_deferrability = true;
- ((FkConstraint *) lastprimarynode)->deferrable = true;
- break;
- case CONSTR_ATTR_NOT_DEFERRABLE:
- if (lastprimarynode == NULL ||
- !IsA(lastprimarynode, FkConstraint))
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("misplaced NOT DEFERRABLE clause")));
- if (saw_deferrability)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed")));
- saw_deferrability = true;
- ((FkConstraint *) lastprimarynode)->deferrable = false;
- if (saw_initially &&
- ((FkConstraint *) lastprimarynode)->initdeferred)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE")));
- break;
- case CONSTR_ATTR_DEFERRED:
- if (lastprimarynode == NULL ||
- !IsA(lastprimarynode, FkConstraint))
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("misplaced INITIALLY DEFERRED clause")));
- if (saw_initially)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed")));
- saw_initially = true;
- ((FkConstraint *) lastprimarynode)->initdeferred = true;
+ case CONSTR_ATTR_NOT_DEFERRABLE:
+ if (!SUPPORTS_ATTRS(lastprimarycon))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("misplaced NOT DEFERRABLE clause"),
+ parser_errposition(cxt->pstate, con->location)));
+ if (saw_deferrability)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed"),
+ parser_errposition(cxt->pstate, con->location)));
+ saw_deferrability = true;
+ lastprimarycon->deferrable = false;
+ if (saw_initially &&
+ lastprimarycon->initdeferred)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE"),
+ parser_errposition(cxt->pstate, con->location)));
+ break;
- /*
- * If only INITIALLY DEFERRED appears, assume DEFERRABLE
- */
- if (!saw_deferrability)
- ((FkConstraint *) lastprimarynode)->deferrable = true;
- else if (!((FkConstraint *) lastprimarynode)->deferrable)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE")));
- break;
- case CONSTR_ATTR_IMMEDIATE:
- if (lastprimarynode == NULL ||
- !IsA(lastprimarynode, FkConstraint))
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("misplaced INITIALLY IMMEDIATE clause")));
- if (saw_initially)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed")));
- saw_initially = true;
- ((FkConstraint *) lastprimarynode)->initdeferred = false;
- break;
- default:
- /* Otherwise it's not an attribute */
- lastprimarynode = node;
- /* reset flags for new primary node */
- saw_deferrability = false;
- saw_initially = false;
- break;
- }
+ case CONSTR_ATTR_DEFERRED:
+ if (!SUPPORTS_ATTRS(lastprimarycon))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("misplaced INITIALLY DEFERRED clause"),
+ parser_errposition(cxt->pstate, con->location)));
+ if (saw_initially)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed"),
+ parser_errposition(cxt->pstate, con->location)));
+ saw_initially = true;
+ lastprimarycon->initdeferred = true;
+
+ /*
+ * If only INITIALLY DEFERRED appears, assume DEFERRABLE
+ */
+ if (!saw_deferrability)
+ lastprimarycon->deferrable = true;
+ else if (!lastprimarycon->deferrable)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE"),
+ parser_errposition(cxt->pstate, con->location)));
+ break;
+
+ case CONSTR_ATTR_IMMEDIATE:
+ if (!SUPPORTS_ATTRS(lastprimarycon))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("misplaced INITIALLY IMMEDIATE clause"),
+ parser_errposition(cxt->pstate, con->location)));
+ if (saw_initially)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed"),
+ parser_errposition(cxt->pstate, con->location)));
+ saw_initially = true;
+ lastprimarycon->initdeferred = false;
+ break;
+
+ default:
+ /* Otherwise it's not an attribute */
+ lastprimarycon = con;
+ /* reset flags for new primary node */
+ saw_deferrability = false;
+ saw_initially = false;
+ break;
}
}
}
* Special handling of type definition for a column
*/
static void
-transformColumnType(ParseState *pstate, ColumnDef *column)
+transformColumnType(CreateStmtContext *cxt, ColumnDef *column)
{
/*
- * All we really need to do here is verify that the type is valid.
+ * All we really need to do here is verify that the type is valid,
+ * including any collation spec that might be present.
*/
- Type ctype = typenameType(pstate, column->typename);
+ Type ctype = typenameType(cxt->pstate, column->typeName, NULL);
+
+ if (column->collClause)
+ {
+ Form_pg_type typtup = (Form_pg_type) GETSTRUCT(ctype);
+
+ LookupCollation(cxt->pstate,
+ column->collClause->collname,
+ column->collClause->location);
+ /* Complain if COLLATE is applied to an uncollatable type */
+ if (!OidIsValid(typtup->typcollation))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("collations are not supported by type %s",
+ format_type_be(HeapTupleGetOid(ctype))),
+ parser_errposition(cxt->pstate,
+ column->collClause->location)));
+ }
ReleaseSysCache(ctype);
}