* a quick copyObject() call before manipulating the query tree.
*
*
- * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/backend/parser/parse_utilcmd.c,v 2.43 2010/08/18 18:35:20 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 "nodes/nodeFuncs.h"
#include "parser/analyze.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_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/rel.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 */
} 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,
- InhRelation *inhrelation);
-static void transformOfType(ParseState *pstate, CreateStmtContext *cxt,
+static void transformTableLikeClause(CreateStmtContext *cxt,
+ TableLikeClause *table_like_clause);
+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(ParseState *pstate,
- CreateStmtContext *cxt);
+static void transformIndexConstraints(CreateStmtContext *cxt);
static IndexStmt *transformIndexConstraint(Constraint *constraint,
CreateStmtContext *cxt);
-static void transformFKConstraints(ParseState *pstate,
- CreateStmtContext *cxt,
+static void transformFKConstraints(CreateStmtContext *cxt,
bool skipValidation,
bool isAddConstraint);
-static void transformConstraintAttrs(ParseState *pstate, 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;
+ Oid existing_relid;
/*
* We must not scribble on the passed-in CreateStmt, so copy it. (This is
*/
stmt = (CreateStmt *) copyObject(stmt);
+ /*
+ * Look up the creation namespace. This also checks permissions on the
+ * target namespace, locks it against concurrent drops, checks for a
+ * preexisting relation in that namespace with the same name, and updates
+ * stmt->relation->relpersistence if the select namespace is temporary.
+ */
+ namespaceid =
+ RangeVarGetAndCheckCreationNamespace(stmt->relation, NoLock,
+ &existing_relid);
+
+ /*
+ * If the relation already exists and the user specified "IF NOT EXISTS",
+ * bail out with a NOTICE.
+ */
+ if (stmt->if_not_exists && OidIsValid(existing_relid))
+ {
+ 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. "istemp" is equivalent to a
- * specification of pg_temp, so no need for anything extra in that case.
+ * 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->istemp)
- {
- Oid namespaceid = RangeVarGetCreationNamespace(stmt->relation);
-
+ if (stmt->relation->schemaname == NULL
+ && stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
stmt->relation->schemaname = get_namespace_name(namespaceid);
- }
- /* Set up pstate */
+ /* 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;
Assert(!stmt->ofTypename || !stmt->inhRelations); /* grammar enforces */
if (stmt->ofTypename)
- transformOfType(pstate, &cxt, stmt->ofTypename);
+ transformOfType(&cxt, stmt->ofTypename);
/*
* Run through each primary element in the table creation clause. Separate
switch (nodeTag(element))
{
case T_ColumnDef:
- transformColumnDefinition(pstate, &cxt,
- (ColumnDef *) element);
+ transformColumnDefinition(&cxt, (ColumnDef *) element);
break;
case T_Constraint:
- transformTableConstraint(pstate, &cxt,
- (Constraint *) element);
+ transformTableConstraint(&cxt, (Constraint *) element);
break;
- case T_InhRelation:
- transformInhRelation(pstate, &cxt,
- (InhRelation *) element);
+ case T_TableLikeClause:
+ transformTableLikeClause(&cxt, (TableLikeClause *) 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;
{
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;
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("array of serial is not implemented"),
- parser_errposition(pstate, column->typeName->location)));
+ parser_errposition(cxt->pstate,
+ column->typeName->location)));
}
/* Do necessary work on the column type declaration */
if (column->typeName)
- transformColumnType(pstate, column);
+ transformColumnType(cxt, column);
/* Special actions for SERIAL pseudo-types */
if (is_serial)
if (cxt->rel)
snamespaceid = RelationGetNamespace(cxt->rel);
else
+ {
snamespaceid = RangeVarGetCreationNamespace(cxt->relation);
+ RangeVarAdjustRelationPersistence(cxt->relation, snamespaceid);
+ }
snamespace = get_namespace_name(snamespaceid);
sname = ChooseRelationName(cxt->relation->relname,
column->colname,
* 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.
+ * 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;
}
/* Process column constraints, if any... */
- transformConstraintAttrs(pstate, column->constraints);
+ transformConstraintAttrs(cxt, column->constraints);
saw_nullable = false;
saw_default = false;
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting NULL/NOT NULL declarations for column \"%s\" of table \"%s\"",
column->colname, cxt->relation->relname),
- parser_errposition(pstate,
+ parser_errposition(cxt->pstate,
constraint->location)));
column->is_not_null = FALSE;
saw_nullable = true;
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting NULL/NOT NULL declarations for column \"%s\" of table \"%s\"",
column->colname, cxt->relation->relname),
- parser_errposition(pstate,
+ parser_errposition(cxt->pstate,
constraint->location)));
column->is_not_null = TRUE;
saw_nullable = true;
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple default values specified for column \"%s\" of table \"%s\"",
column->colname, cxt->relation->relname),
- parser_errposition(pstate,
+ parser_errposition(cxt->pstate,
constraint->location)));
column->raw_default = constraint->raw_expr;
Assert(constraint->cooked_expr == NULL);
break;
}
}
+
+ /*
+ * Generate ALTER FOREIGN TABLE ALTER COLUMN statement which adds
+ * per-column foreign data wrapper options for this column.
+ */
+ if (column->fdwoptions != NIL)
+ {
+ AlterTableStmt *stmt;
+ AlterTableCmd *cmd;
+
+ cmd = makeNode(AlterTableCmd);
+ cmd->subtype = AT_AlterColumnGenericOptions;
+ cmd->name = column->colname;
+ cmd->def = (Node *) column->fdwoptions;
+ cmd->behavior = DROP_RESTRICT;
+ cmd->missing_ok = false;
+
+ stmt = makeNode(AlterTableStmt);
+ stmt->relation = cxt->relation;
+ stmt->cmds = NIL;
+ stmt->relkind = OBJECT_FOREIGN_TABLE;
+ stmt->cmds = lappend(stmt->cmds, cmd);
+
+ cxt->alist = lappend(cxt->alist, stmt);
+ }
}
/*
* 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)
{
}
/*
- * transformInhRelation
+ * transformTableLikeClause
*
- * Change the LIKE <subtable> portion of a CREATE TABLE statement into
+ * Change the LIKE <srctable> portion of a CREATE TABLE statement into
* column definitions which recreate the user defined column portions of
- * <subtable>.
+ * <srctable>.
*/
static void
-transformInhRelation(ParseState *pstate, CreateStmtContext *cxt,
- InhRelation *inhRelation)
+transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_clause)
{
AttrNumber parent_attno;
Relation relation;
AclResult aclresult;
char *comment;
- relation = parserOpenTable(pstate, inhRelation->relation, AccessShareLock);
+ relation = parserOpenTable(cxt->pstate, table_like_clause->relation,
+ AccessShareLock);
- if (relation->rd_rel->relkind != RELKIND_RELATION)
+ if (relation->rd_rel->relkind != RELKIND_RELATION
+ && relation->rd_rel->relkind != RELKIND_VIEW
+ && relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("inherited relation \"%s\" is not a table",
- inhRelation->relation->relname)));
+ errmsg("LIKE source relation \"%s\" is not a table, view, or foreign table",
+ table_like_clause->relation->relname)));
/*
- * Check for SELECT privilages
+ * Check for SELECT privileges
*/
aclresult = pg_class_aclcheck(RelationGetRelid(relation), GetUserId(),
ACL_SELECT);
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 &&
- (inhRelation->options & CREATE_TABLE_LIKE_DEFAULTS))
+ (table_like_clause->options & CREATE_TABLE_LIKE_DEFAULTS))
{
Node *this_default = NULL;
AttrDefault *attrdef;
}
/* Likewise, copy storage if requested */
- if (inhRelation->options & CREATE_TABLE_LIKE_STORAGE)
+ if (table_like_clause->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) &&
+ if ((table_like_clause->options & CREATE_TABLE_LIKE_COMMENTS) &&
(comment = GetComment(attribute->attrelid,
RelationRelationId,
attribute->attnum)) != NULL)
* Copy CHECK constraints if requested, being careful to adjust attribute
* numbers
*/
- if ((inhRelation->options & CREATE_TABLE_LIKE_CONSTRAINTS) &&
+ if ((table_like_clause->options & CREATE_TABLE_LIKE_CONSTRAINTS) &&
tupleDesc->constr)
{
AttrNumber *attmap = varattnos_map_schema(tupleDesc, cxt->columns);
cxt->ckconstraints = lappend(cxt->ckconstraints, n);
/* Copy comment on constraint */
- if ((inhRelation->options & CREATE_TABLE_LIKE_COMMENTS) &&
+ if ((table_like_clause->options & CREATE_TABLE_LIKE_COMMENTS) &&
(comment = GetComment(get_constraint_oid(RelationGetRelid(relation),
- n->conname, false),
+ n->conname, false),
ConstraintRelationId,
0)) != NULL)
{
/*
* Likewise, copy indexes if requested
*/
- if ((inhRelation->options & CREATE_TABLE_LIKE_INDEXES) &&
+ if ((table_like_clause->options & CREATE_TABLE_LIKE_INDEXES) &&
relation->rd_rel->relhasindex)
{
AttrNumber *attmap = varattnos_map_schema(tupleDesc, cxt->columns);
index_stmt = generateClonedIndexStmt(cxt, parent_index, attmap);
/* Copy comment on index */
- if (inhRelation->options & CREATE_TABLE_LIKE_COMMENTS)
+ if (table_like_clause->options & CREATE_TABLE_LIKE_COMMENTS)
{
comment = GetComment(parent_index_oid, RelationRelationId, 0);
}
static void
-transformOfType(ParseState *pstate, CreateStmtContext *cxt, TypeName *ofTypename)
+transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
{
HeapTuple tuple;
- Form_pg_type typ;
TupleDesc tupdesc;
int i;
Oid ofTypeId;
AssertArg(ofTypename);
tuple = typenameType(NULL, ofTypename, NULL);
- typ = (Form_pg_type) GETSTRUCT(tuple);
+ check_of_type(tuple);
ofTypeId = HeapTupleGetOid(tuple);
ofTypename->typeOid = ofTypeId; /* cached for later */
- if (typ->typtype != TYPTYPE_COMPOSITE)
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("type %s is not a composite type",
- format_type_be(ofTypeId))));
-
tupdesc = lookup_rowtype_tupdesc(ofTypeId, -1);
for (i = 0; i < tupdesc->natts; i++)
{
Form_pg_attribute attr = tupdesc->attrs[i];
- ColumnDef *n = makeNode(ColumnDef);
+ ColumnDef *n;
+
+ if (attr->attisdropped)
+ continue;
+ n = makeNode(ColumnDef);
n->colname = pstrdup(NameStr(attr->attname));
n->typeName = makeTypeNameFromOid(attr->atttypid, attr->atttypmod);
- n->constraints = NULL;
+ 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);
Form_pg_class idxrelrec;
Form_pg_index idxrec;
Form_pg_am amrec;
+ oidvector *indcollation;
oidvector *indclass;
IndexStmt *index;
List *indexprs;
/* 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);
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;
* 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 || idxrelrec->relhasexclusion)
+ if (index->primary || index->unique || idxrec->indisexclusion)
{
Oid constraintId = get_index_constraint(source_relid);
index->initdeferred = conrec->condeferred;
/* If it's an exclusion constraint, we need the operator names */
- if (idxrelrec->relhasexclusion)
+ if (idxrec->indisexclusion)
{
Datum *elems;
int nElems;
/* Copy the original index column name */
iparam->indexcolname = pstrdup(NameStr(attrs[keyno]->attname));
+ /* Add the collation name, if non-default */
+ iparam->collation = get_collation(indcollation->values[keyno], keycoltype);
+
/* Add the operator class name, if non-default */
iparam->opclass = get_opclass(indclass->values[keyno], keycoltype);
}
/*
- * get_opclass - fetch name of an index operator class
+ * 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;
- List *result = NIL;
ht_opc = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass));
if (!HeapTupleIsValid(ht_opc))
* LIKE ... INCLUDING INDEXES.
*/
static void
-transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt)
+transformIndexConstraints(CreateStmtContext *cxt)
{
IndexStmt *index;
List *indexlist = NIL;
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("multiple primary keys for table \"%s\" are not allowed",
- cxt->relation->relname)));
+ cxt->relation->relname),
+ parser_errposition(cxt->pstate, constraint->location)));
cxt->pkey = index;
/*
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 btree", 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
if (!found && !cxt->isalter)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" named in key does not exist",
- key)));
+ 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)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
errmsg("column \"%s\" appears twice in primary key constraint",
- key)));
+ key),
+ parser_errposition(cxt->pstate, constraint->location)));
else
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
errmsg("column \"%s\" appears twice in unique constraint",
- key)));
+ key),
+ parser_errposition(cxt->pstate, constraint->location)));
}
}
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;
* 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)
{
Constraint *constraint = (Constraint *) lfirst(fkclist);
constraint->skip_validation = true;
+ constraint->initially_valid = 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)
/* 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
*whereClause = transformWhereClause(pstate,
(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,
break;
}
+ /*
+ * 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.
stmt = (AlterTableStmt *) copyObject(stmt);
/*
- * Assign the appropriate lock level for this list of subcommands.
+ * 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
- * 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.
+ * 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 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, 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;
ColumnDef *def = (ColumnDef *) cmd->def;
Assert(IsA(def, ColumnDef));
- transformColumnDefinition(pstate, &cxt, def);
+ transformColumnDefinition(&cxt, def);
/*
* If the column has a non-null default, we can't skip
*/
if (IsA(cmd->def, Constraint))
{
- transformTableConstraint(pstate, &cxt,
- (Constraint *) cmd->def);
+ transformTableConstraint(&cxt, (Constraint *) cmd->def);
if (((Constraint *) cmd->def)->contype == CONSTR_FOREIGN)
skipValidation = false;
}
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;
* and detect inconsistent/misplaced constraint attributes.
*
* NOTE: currently, attributes are only supported for FOREIGN KEY, UNIQUE,
- * and PRIMARY KEY constraints, but someday they ought to be supported
- * for other constraint types.
+ * EXCLUSION, and PRIMARY KEY constraints, but someday they ought to be
+ * supported for other constraint types.
*/
static void
-transformConstraintAttrs(ParseState *pstate, List *constraintList)
+transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList)
{
Constraint *lastprimarycon = NULL;
bool saw_deferrability = false;
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced DEFERRABLE clause"),
- parser_errposition(pstate, con->location)));
+ 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(pstate, con->location)));
+ parser_errposition(cxt->pstate, con->location)));
saw_deferrability = true;
lastprimarycon->deferrable = true;
break;
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced NOT DEFERRABLE clause"),
- parser_errposition(pstate, con->location)));
+ 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(pstate, con->location)));
+ parser_errposition(cxt->pstate, con->location)));
saw_deferrability = true;
lastprimarycon->deferrable = false;
if (saw_initially &&
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE"),
- parser_errposition(pstate, con->location)));
+ parser_errposition(cxt->pstate, con->location)));
break;
case CONSTR_ATTR_DEFERRED:
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced INITIALLY DEFERRED clause"),
- parser_errposition(pstate, con->location)));
+ 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(pstate, con->location)));
+ parser_errposition(cxt->pstate, con->location)));
saw_initially = true;
lastprimarycon->initdeferred = true;
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE"),
- parser_errposition(pstate, con->location)));
+ parser_errposition(cxt->pstate, con->location)));
break;
case CONSTR_ATTR_IMMEDIATE:
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced INITIALLY IMMEDIATE clause"),
- parser_errposition(pstate, con->location)));
+ 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(pstate, con->location)));
+ parser_errposition(cxt->pstate, con->location)));
saw_initially = true;
lastprimarycon->initdeferred = 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, NULL);
+ 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);
}