/*------------------------------------------------------------------------- * * parse_utilcmd.c * Perform parse analysis work for various utility commands * * Formerly we did this work during parse_analyze() in analyze.c. However * that is fairly unsafe in the presence of querytree caching, since any * database state that we depend on in making the transformations might be * obsolete by the time the utility command is executed; and utility commands * have no infrastructure for holding locks or rechecking plan validity. * Hence these functions are now called at the start of execution of their * respective utility commands. * * NOTE: in general we must avoid scribbling on the passed-in raw parse * tree, since it might be in a plan cache. The simplest solution is * a quick copyObject() call before manipulating the query tree. * * * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * $PostgreSQL: pgsql/src/backend/parser/parse_utilcmd.c,v 2.6 2007/11/15 21:14:37 momjian Exp $ * *------------------------------------------------------------------------- */ #include "postgres.h" #include "access/genam.h" #include "access/heapam.h" #include "catalog/heap.h" #include "catalog/index.h" #include "catalog/namespace.h" #include "catalog/pg_opclass.h" #include "catalog/pg_type.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 "parser/analyze.h" #include "parser/gramparse.h" #include "parser/parse_clause.h" #include "parser/parse_expr.h" #include "parser/parse_relation.h" #include "parser/parse_type.h" #include "parser/parse_utilcmd.h" #include "rewrite/rewriteManip.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/relcache.h" #include "utils/syscache.h" /* State shared by transformCreateStmt and its subroutines */ typedef struct { const char *stmtType; /* "CREATE TABLE" or "ALTER TABLE" */ RangeVar *relation; /* relation to create */ Relation rel; /* opened/locked rel, if ALTER */ List *inhRelations; /* relations to inherit from */ bool isalter; /* true if altering existing table */ bool hasoids; /* does relation have an OID column? */ List *columns; /* ColumnDef items */ 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 * the table */ IndexStmt *pkey; /* PRIMARY KEY index, if any */ } CreateStmtContext; /* State shared by transformCreateSchemaStmt and its subroutines */ typedef struct { const char *stmtType; /* "CREATE SCHEMA" or "ALTER SCHEMA" */ char *schemaname; /* name of schema */ char *authid; /* owner of schema */ List *sequences; /* CREATE SEQUENCE items */ List *tables; /* CREATE TABLE items */ List *views; /* CREATE VIEW items */ List *indexes; /* CREATE INDEX items */ List *triggers; /* CREATE TRIGGER items */ List *grants; /* GRANT items */ } CreateSchemaStmtContext; static void transformColumnDefinition(ParseState *pstate, CreateStmtContext *cxt, ColumnDef *column); static void transformTableConstraint(ParseState *pstate, CreateStmtContext *cxt, Constraint *constraint); static void transformInhRelation(ParseState *pstate, CreateStmtContext *cxt, InhRelation *inhrelation); static IndexStmt *generateClonedIndexStmt(CreateStmtContext *cxt, Relation parent_index, AttrNumber *attmap); static List *get_opclass(Oid opclass, Oid actual_datatype); static void transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt); static IndexStmt *transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt); static void transformFKConstraints(ParseState *pstate, CreateStmtContext *cxt, bool skipValidation, bool isAddConstraint); static void transformConstraintAttrs(List *constraintList); static void transformColumnType(ParseState *pstate, ColumnDef *column); static void setSchemaName(char *context_schema, char **stmt_schema_name); /* * transformCreateStmt - * parse analysis for CREATE TABLE * * Returns a List of utility commands to be done in sequence. One of these * will be the transformed CreateStmt, but there may be additional actions * to be done before and after the actual DefineRelation() call. * * SQL92 allows constraints to be scattered all over, so thumb through * the columns and collect all constraints into one place. * If there are any implied indices (e.g. UNIQUE or PRIMARY KEY) * then expand those into multiple IndexStmt blocks. * - thomas 1997-12-02 */ List * transformCreateStmt(CreateStmt *stmt, const char *queryString) { ParseState *pstate; CreateStmtContext cxt; List *result; List *save_alist; ListCell *elements; /* * We must not scribble on the passed-in CreateStmt, so copy it. (This is * overkill, but easy.) */ stmt = (CreateStmt *) copyObject(stmt); /* * 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. */ if (stmt->relation->schemaname == NULL && !stmt->relation->istemp) { Oid namespaceid = RangeVarGetCreationNamespace(stmt->relation); stmt->relation->schemaname = get_namespace_name(namespaceid); } /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; cxt.stmtType = "CREATE TABLE"; cxt.relation = stmt->relation; cxt.rel = NULL; cxt.inhRelations = stmt->inhRelations; cxt.isalter = false; cxt.columns = NIL; 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); /* * Run through each primary element in the table creation clause. Separate * column defs from constraints, and do preliminary analysis. */ foreach(elements, stmt->tableElts) { Node *element = lfirst(elements); switch (nodeTag(element)) { case T_ColumnDef: transformColumnDefinition(pstate, &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); break; case T_InhRelation: transformInhRelation(pstate, &cxt, (InhRelation *) element); break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(element)); break; } } /* * transformIndexConstraints wants cxt.alist to contain only index * statements, so transfer anything we already have into save_alist. */ save_alist = cxt.alist; cxt.alist = NIL; Assert(stmt->constraints == NIL); /* * Postprocess constraints that give rise to index definitions. */ transformIndexConstraints(pstate, &cxt); /* * Postprocess foreign-key constraints. */ transformFKConstraints(pstate, &cxt, true, false); /* * Output results. */ stmt->tableElts = cxt.columns; stmt->constraints = cxt.ckconstraints; result = lappend(cxt.blist, stmt); result = list_concat(result, cxt.alist); result = list_concat(result, save_alist); return result; } /* * transformColumnDefinition - * transform a single ColumnDef within CREATE TABLE * Also used in ALTER TABLE ADD COLUMN */ static void transformColumnDefinition(ParseState *pstate, CreateStmtContext *cxt, ColumnDef *column) { bool is_serial; bool saw_nullable; bool saw_default; Constraint *constraint; ListCell *clist; cxt->columns = lappend(cxt->columns, column); /* Check for SERIAL pseudo-types */ is_serial = false; if (list_length(column->typename->names) == 1) { char *typname = strVal(linitial(column->typename->names)); if (strcmp(typname, "serial") == 0 || strcmp(typname, "serial4") == 0) { is_serial = true; column->typename->names = NIL; column->typename->typeid = INT4OID; } else if (strcmp(typname, "bigserial") == 0 || strcmp(typname, "serial8") == 0) { is_serial = true; column->typename->names = NIL; column->typename->typeid = INT8OID; } } /* Do necessary work on the column type declaration */ transformColumnType(pstate, column); /* Special actions for SERIAL pseudo-types */ if (is_serial) { Oid snamespaceid; char *snamespace; char *sname; char *qstring; A_Const *snamenode; FuncCall *funccallnode; CreateSeqStmt *seqstmt; AlterSeqStmt *altseqstmt; List *attnamelist; /* * Determine namespace and name to use for the sequence. * * Although we use ChooseRelationName, it's not guaranteed that the * selected sequence name won't conflict; given sufficiently long * field names, two different serial columns in the same table could * be assigned the same sequence name, and we'd not notice since we * aren't creating the sequence quite yet. In practice this seems * quite unlikely to be a problem, especially since few people would * need two serial columns in one table. */ if (cxt->rel) snamespaceid = RelationGetNamespace(cxt->rel); else snamespaceid = RangeVarGetCreationNamespace(cxt->relation); snamespace = get_namespace_name(snamespaceid); sname = ChooseRelationName(cxt->relation->relname, column->colname, "seq", snamespaceid); ereport(NOTICE, (errmsg("%s will create implicit sequence \"%s\" for serial column \"%s.%s\"", cxt->stmtType, sname, cxt->relation->relname, column->colname))); /* * Build a CREATE SEQUENCE command to create the sequence object, and * add it to the list of things to be done before this CREATE/ALTER * TABLE. */ seqstmt = makeNode(CreateSeqStmt); seqstmt->sequence = makeRangeVar(snamespace, sname); seqstmt->options = NIL; cxt->blist = lappend(cxt->blist, seqstmt); /* * Build an ALTER SEQUENCE ... OWNED BY command to mark the sequence * as owned by this column, and add it to the list of things to be * done after this CREATE/ALTER TABLE. */ altseqstmt = makeNode(AlterSeqStmt); altseqstmt->sequence = makeRangeVar(snamespace, sname); attnamelist = list_make3(makeString(snamespace), makeString(cxt->relation->relname), makeString(column->colname)); altseqstmt->options = list_make1(makeDefElem("owned_by", (Node *) attnamelist)); cxt->alist = lappend(cxt->alist, altseqstmt); /* * Create appropriate constraints for SERIAL. We do this in full, * rather than shortcutting, so that we will detect any conflicting * constraints the user wrote (like a different DEFAULT). * * Create an expression tree representing the function call * nextval('sequencename'). We cannot reduce the raw tree to cooked * form until after the sequence is created, but there's no need to do * so. */ qstring = quote_qualified_identifier(snamespace, sname); snamenode = makeNode(A_Const); snamenode->val.type = T_String; snamenode->val.val.str = qstring; snamenode->typename = SystemTypeName("regclass"); funccallnode = makeNode(FuncCall); funccallnode->funcname = SystemFuncName("nextval"); funccallnode->args = list_make1(snamenode); funccallnode->agg_star = false; funccallnode->agg_distinct = false; funccallnode->location = -1; constraint = makeNode(Constraint); constraint->contype = CONSTR_DEFAULT; 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; column->constraints = lappend(column->constraints, constraint); } /* Process column constraints, if any... */ transformConstraintAttrs(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) { case CONSTR_NULL: if (saw_nullable && column->is_not_null) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting NULL/NOT NULL declarations for column \"%s\" of table \"%s\"", column->colname, cxt->relation->relname))); column->is_not_null = FALSE; saw_nullable = true; break; case CONSTR_NOTNULL: if (saw_nullable && !column->is_not_null) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting NULL/NOT NULL declarations for column \"%s\" of table \"%s\"", column->colname, cxt->relation->relname))); column->is_not_null = TRUE; saw_nullable = true; break; case CONSTR_DEFAULT: if (saw_default) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("multiple default values specified for column \"%s\" of table \"%s\"", column->colname, cxt->relation->relname))); column->raw_default = constraint->raw_expr; Assert(constraint->cooked_expr == NULL); saw_default = true; break; case CONSTR_PRIMARY: case CONSTR_UNIQUE: if (constraint->keys == NIL) constraint->keys = list_make1(makeString(column->colname)); cxt->ixconstraints = lappend(cxt->ixconstraints, constraint); break; case CONSTR_CHECK: cxt->ckconstraints = lappend(cxt->ckconstraints, constraint); break; case CONSTR_ATTR_DEFERRABLE: case CONSTR_ATTR_NOT_DEFERRABLE: case CONSTR_ATTR_DEFERRED: case CONSTR_ATTR_IMMEDIATE: /* transformConstraintAttrs took care of these */ break; default: elog(ERROR, "unrecognized constraint type: %d", constraint->contype); break; } } } /* * transformTableConstraint * transform a Constraint node within CREATE TABLE or ALTER TABLE */ static void transformTableConstraint(ParseState *pstate, CreateStmtContext *cxt, Constraint *constraint) { switch (constraint->contype) { case CONSTR_PRIMARY: case CONSTR_UNIQUE: cxt->ixconstraints = lappend(cxt->ixconstraints, constraint); break; case CONSTR_CHECK: cxt->ckconstraints = lappend(cxt->ckconstraints, constraint); break; case CONSTR_NULL: case CONSTR_NOTNULL: case CONSTR_DEFAULT: case CONSTR_ATTR_DEFERRABLE: case CONSTR_ATTR_NOT_DEFERRABLE: case CONSTR_ATTR_DEFERRED: case CONSTR_ATTR_IMMEDIATE: elog(ERROR, "invalid context for constraint type %d", constraint->contype); break; default: elog(ERROR, "unrecognized constraint type: %d", constraint->contype); break; } } /* * transformInhRelation * * Change the LIKE portion of a CREATE TABLE statement into * column definitions which recreate the user defined column portions of * . */ static void transformInhRelation(ParseState *pstate, 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; relation = heap_openrv(inhRelation->relation, AccessShareLock); if (relation->rd_rel->relkind != RELKIND_RELATION) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("inherited relation \"%s\" is not a table", inhRelation->relation->relname))); /* * Check for SELECT privilages */ aclresult = pg_class_aclcheck(RelationGetRelid(relation), GetUserId(), ACL_SELECT); if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_CLASS, RelationGetRelationName(relation)); 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); } } /* * Insert the copied attributes into the cxt for the new table definition. */ for (parent_attno = 1; parent_attno <= tupleDesc->natts; parent_attno++) { Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1]; char *attributeName = NameStr(attribute->attname); ColumnDef *def; /* * Ignore dropped columns in the parent. */ if (attribute->attisdropped) continue; /* * Create a new column, which is marked as NOT inherited. * * For constraints, ONLY the NOT NULL constraint is inherited by the * new column definition per SQL99. */ def = makeNode(ColumnDef); def->colname = pstrdup(attributeName); def->typename = makeTypeNameFromOid(attribute->atttypid, attribute->atttypmod); def->inhcount = 0; def->is_local = true; def->is_not_null = attribute->attnotnull; def->raw_default = NULL; def->cooked_default = NULL; def->constraints = NIL; /* * Add to column list */ cxt->columns = lappend(cxt->columns, def); /* * Copy default, if present and the default has been requested */ if (attribute->atthasdef && including_defaults) { char *this_default = NULL; AttrDefault *attrdef; int i; /* Find default in constraint structure */ Assert(constr != NULL); attrdef = constr->defval; for (i = 0; i < constr->num_defval; i++) { if (attrdef[i].adnum == parent_attno) { this_default = attrdef[i].adbin; break; } } Assert(this_default != NULL); /* * If default expr could contain any vars, we'd need to fix 'em, * but it can't; so default is ready to apply to child. */ def->cooked_default = pstrdup(this_default); } } /* * Copy CHECK constraints if requested, being careful to adjust attribute * numbers */ if (including_constraints && tupleDesc->constr) { AttrNumber *attmap = varattnos_map_schema(tupleDesc, cxt->columns); int ccnum; for (ccnum = 0; ccnum < tupleDesc->constr->num_check; ccnum++) { char *ccname = tupleDesc->constr->check[ccnum].ccname; char *ccbin = tupleDesc->constr->check[ccnum].ccbin; Node *ccbin_node = stringToNode(ccbin); Constraint *n = makeNode(Constraint); change_varattnos_of_a_node(ccbin_node, attmap); n->contype = CONSTR_CHECK; n->name = pstrdup(ccname); n->raw_expr = NULL; n->cooked_expr = nodeToString(ccbin_node); n->indexspace = NULL; cxt->ckconstraints = lappend(cxt->ckconstraints, (Node *) n); } } if (including_indexes && relation->rd_rel->relhasindex) { AttrNumber *attmap; List *parent_indexes; ListCell *l; attmap = varattnos_map_schema(tupleDesc, cxt->columns); 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); /* Add the new IndexStmt to the create context */ cxt->inh_indexes = lappend(cxt->inh_indexes, index_stmt); /* Keep our lock on the index till xact commit */ index_close(parent_index, NoLock); } } /* * Close the parent rel, but keep our AccessShareLock on it until xact * commit. That will prevent someone else from deleting or ALTERing the * parent before the child is committed. */ heap_close(relation, NoLock); } /* * Generate an IndexStmt entry using information from an already * existing index "source_idx". * * Note: Much of this functionality is cribbed from pg_get_indexdef. */ static IndexStmt * generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx, AttrNumber *attmap) { HeapTuple ht_idx; HeapTuple ht_idxrel; HeapTuple ht_am; Form_pg_index idxrec; Form_pg_class idxrelrec; Form_pg_am amrec; List *indexprs = NIL; ListCell *indexpr_item; Oid indrelid; Oid source_relid; int keyno; Oid keycoltype; Datum indclassDatum; Datum indoptionDatum; bool isnull; oidvector *indclass; int2vector *indoption; IndexStmt *index; Datum reloptions; source_relid = RelationGetRelid(source_idx); /* Fetch pg_index tuple for source index */ ht_idx = SearchSysCache(INDEXRELID, ObjectIdGetDatum(source_relid), 0, 0, 0); if (!HeapTupleIsValid(ht_idx)) elog(ERROR, "cache lookup failed for index %u", source_relid); idxrec = (Form_pg_index) GETSTRUCT(ht_idx); Assert(source_relid == idxrec->indexrelid); indrelid = idxrec->indrelid; index = makeNode(IndexStmt); index->unique = idxrec->indisunique; index->concurrent = false; index->primary = idxrec->indisprimary; index->relation = cxt->relation; index->isconstraint = false; /* * We don't try to preserve the name of the source index; instead, just * let DefineIndex() choose a reasonable name. */ index->idxname = NULL; /* Must get indclass and indoption the hard way */ indclassDatum = SysCacheGetAttr(INDEXRELID, ht_idx, Anum_pg_index_indclass, &isnull); Assert(!isnull); indclass = (oidvector *) DatumGetPointer(indclassDatum); indoptionDatum = SysCacheGetAttr(INDEXRELID, ht_idx, Anum_pg_index_indoption, &isnull); Assert(!isnull); indoption = (int2vector *) DatumGetPointer(indoptionDatum); /* Fetch pg_class tuple of source index */ ht_idxrel = SearchSysCache(RELOID, ObjectIdGetDatum(source_relid), 0, 0, 0); if (!HeapTupleIsValid(ht_idxrel)) elog(ERROR, "cache lookup failed for relation %u", source_relid); /* * Store the reloptions for later use by this new index */ reloptions = SysCacheGetAttr(RELOID, ht_idxrel, Anum_pg_class_reloptions, &isnull); if (!isnull) index->src_options = flatten_reloptions(source_relid); idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel); /* Fetch pg_am tuple for the index's access method */ ht_am = SearchSysCache(AMOID, ObjectIdGetDatum(idxrelrec->relam), 0, 0, 0); if (!HeapTupleIsValid(ht_am)) elog(ERROR, "cache lookup failed for access method %u", idxrelrec->relam); amrec = (Form_pg_am) GETSTRUCT(ht_am); index->accessMethod = pstrdup(NameStr(amrec->amname)); /* Get the index expressions, if any */ if (!heap_attisnull(ht_idx, Anum_pg_index_indexprs)) { Datum exprsDatum; bool isnull; char *exprsString; exprsDatum = SysCacheGetAttr(INDEXRELID, ht_idx, Anum_pg_index_indexprs, &isnull); exprsString = DatumGetCString(DirectFunctionCall1(textout, exprsDatum)); Assert(!isnull); indexprs = (List *) stringToNode(exprsString); } indexpr_item = list_head(indexprs); for (keyno = 0; keyno < idxrec->indnatts; keyno++) { IndexElem *iparam; AttrNumber attnum = idxrec->indkey.values[keyno]; int16 opt = indoption->values[keyno]; iparam = makeNode(IndexElem); if (AttributeNumberIsValid(attnum)) { /* Simple index column */ char *attname; attname = get_relid_attribute_name(indrelid, attnum); keycoltype = get_atttype(indrelid, attnum); iparam->name = attname; iparam->expr = NULL; } else { /* Expressional index */ Node *indexkey; if (indexpr_item == NULL) elog(ERROR, "too few entries in indexprs list"); indexkey = (Node *) lfirst(indexpr_item); change_varattnos_of_a_node(indexkey, attmap); iparam->name = NULL; iparam->expr = indexkey; indexpr_item = lnext(indexpr_item); keycoltype = exprType(indexkey); } /* Add the operator class name, if non-default */ iparam->opclass = get_opclass(indclass->values[keyno], keycoltype); iparam->ordering = SORTBY_DEFAULT; iparam->nulls_ordering = SORTBY_NULLS_DEFAULT; /* Adjust options if necessary */ if (amrec->amcanorder) { /* If it supports sort ordering, report DESC and NULLS opts */ if (opt & INDOPTION_DESC) iparam->ordering = SORTBY_DESC; if (opt & INDOPTION_NULLS_FIRST) iparam->nulls_ordering = SORTBY_NULLS_FIRST; } index->indexParams = lappend(index->indexParams, iparam); } /* Use the same tablespace as the source index */ index->tableSpace = get_tablespace_name(source_idx->rd_node.spcNode); /* If it's a partial index, decompile and append the predicate */ if (!heap_attisnull(ht_idx, Anum_pg_index_indpred)) { Datum pred_datum; bool isnull; char *pred_str; /* Convert text string to node tree */ pred_datum = SysCacheGetAttr(INDEXRELID, ht_idx, Anum_pg_index_indpred, &isnull); Assert(!isnull); pred_str = DatumGetCString(DirectFunctionCall1(textout, pred_datum)); index->whereClause = (Node *) stringToNode(pred_str); change_varattnos_of_a_node(index->whereClause, attmap); } /* Clean up */ ReleaseSysCache(ht_idx); ReleaseSysCache(ht_idxrel); ReleaseSysCache(ht_am); return index; } /* * get_opclass - fetch 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) { HeapTuple ht_opc; Form_pg_opclass opc_rec; List *result = NIL; ht_opc = SearchSysCache(CLAOID, ObjectIdGetDatum(opclass), 0, 0, 0); if (!HeapTupleIsValid(ht_opc)) elog(ERROR, "cache lookup failed for opclass %u", opclass); opc_rec = (Form_pg_opclass) GETSTRUCT(ht_opc); if (!OidIsValid(actual_datatype) || GetDefaultOpClass(actual_datatype, opc_rec->opcmethod) != opclass) { char *nsp_name = get_namespace_name(opc_rec->opcnamespace); char *opc_name = NameStr(opc_rec->opcname); result = list_make2(makeString(nsp_name), makeString(opc_name)); } ReleaseSysCache(ht_opc); return result; } /* * transformIndexConstraints * Handle UNIQUE and PRIMARY KEY constraints, which create * indexes. We also merge index definitions arising from * LIKE ... INCLUDING INDEXES. */ static void transformIndexConstraints(ParseState *pstate, 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, * create an index as for PRIMARY KEY, but do not insist on NOT NULL. */ foreach(lc, cxt->ixconstraints) { Constraint *constraint = (Constraint *) lfirst(lc); index = transformIndexConstraint(constraint, cxt); indexlist = lappend(indexlist, index); } /* * Scan the index list and remove any redundant index specifications. This * can happen if, for instance, the user writes UNIQUE PRIMARY KEY. A * strict reading of SQL92 would suggest raising an error instead, but * that strikes me as too anal-retentive. - tgl 2001-02-14 * * XXX in ALTER TABLE case, it'd be nice to look for duplicate * pre-existing indexes, too. */ Assert(cxt->alist == NIL); if (cxt->pkey != NULL) { /* Make sure we keep the PKEY index in preference to others... */ cxt->alist = list_make1(cxt->pkey); } foreach(lc, indexlist) { bool keep = true; ListCell *k; index = lfirst(lc); /* if it's pkey, it's already in cxt->alist */ if (index == cxt->pkey) continue; foreach(k, cxt->alist) { IndexStmt *priorindex = lfirst(k); if (equal(index->indexParams, priorindex->indexParams)) { /* * If the prior index is as yet unnamed, and this one is * named, then transfer the name to the prior index. This * ensures that if we have named and unnamed constraints, * we'll use (at least one of) the names for the index. */ if (priorindex->idxname == NULL) priorindex->idxname = index->idxname; keep = false; break; } } if (keep) cxt->alist = lappend(cxt->alist, index); } /* Copy indexes defined by LIKE ... INCLUDING INDEXES */ foreach(lc, cxt->inh_indexes) { index = (IndexStmt *) lfirst(lc); if (index->primary) { if (cxt->pkey) ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("multiple primary keys for table \"%s\" are not allowed", cxt->relation->relname))); cxt->pkey = index; } cxt->alist = lappend(cxt->alist, index); } } static IndexStmt * transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt) { IndexStmt *index; ListCell *keys; IndexElem *iparam; Assert(constraint->contype == CONSTR_PRIMARY || constraint->contype == CONSTR_UNIQUE); index = makeNode(IndexStmt); index->unique = true; 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))); cxt->pkey = index; /* * In ALTER TABLE case, a primary index might already exist, but * DefineIndex will check for it. */ } index->isconstraint = true; if (constraint->name != NULL) index->idxname = pstrdup(constraint->name); else index->idxname = NULL; /* DefineIndex will choose name */ 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; /* * 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; 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))); /* 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))); } } /* 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); } return index; } /* * transformFKConstraints * handle FOREIGN KEY constraints */ static void transformFKConstraints(ParseState *pstate, CreateStmtContext *cxt, bool skipValidation, bool isAddConstraint) { ListCell *fkclist; if (cxt->fkconstraints == NIL) return; /* * If CREATE TABLE or adding a column with NULL default, we can safely * skip validation of the constraint. */ if (skipValidation) { foreach(fkclist, cxt->fkconstraints) { FkConstraint *fkconstraint = (FkConstraint *) lfirst(fkclist); fkconstraint->skip_validation = true; } } /* * For CREATE TABLE or ALTER TABLE ADD COLUMN, gin up an ALTER TABLE ADD * CONSTRAINT command to execute after the basic command is complete. (If * called from ADD CONSTRAINT, that routine will add the FK constraints to * its own subcommand list.) * * Note: the ADD CONSTRAINT command must also execute after any index * creation commands. Thus, this should run after * transformIndexConstraints, so that the CREATE INDEX commands are * already in cxt->alist. */ if (!isAddConstraint) { AlterTableStmt *alterstmt = makeNode(AlterTableStmt); alterstmt->relation = cxt->relation; alterstmt->cmds = NIL; alterstmt->relkind = OBJECT_TABLE; foreach(fkclist, cxt->fkconstraints) { FkConstraint *fkconstraint = (FkConstraint *) lfirst(fkclist); AlterTableCmd *altercmd = makeNode(AlterTableCmd); altercmd->subtype = AT_ProcessedConstraint; altercmd->name = NULL; altercmd->def = (Node *) fkconstraint; alterstmt->cmds = lappend(alterstmt->cmds, altercmd); } cxt->alist = lappend(cxt->alist, alterstmt); } } /* * transformIndexStmt - parse analysis for CREATE INDEX * * 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 * without bothering to call this, because they know they don't have any * such expressions to deal with. */ IndexStmt * transformIndexStmt(IndexStmt *stmt, const char *queryString) { Relation rel; ParseState *pstate; RangeTblEntry *rte; ListCell *l; /* * 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 * 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. */ rel = heap_openrv(stmt->relation, (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. */ rte = addRangeTableEntry(pstate, stmt->relation, NULL, false, true); /* no to join list, yes to namespaces */ addRTEtoQuery(pstate, rte, false, true, true); /* take care of the where clause */ if (stmt->whereClause) stmt->whereClause = transformWhereClause(pstate, stmt->whereClause, "WHERE"); /* take care of any index expressions */ foreach(l, stmt->indexParams) { IndexElem *ielem = (IndexElem *) lfirst(l); if (ielem->expr) { ielem->expr = transformExpr(pstate, ielem->expr); /* * We check only that the result type is legitimate; this is for * consistency with what transformWhereClause() checks for the * predicate. DefineIndex() will make more checks. */ if (expression_returns_set(ielem->expr)) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("index expression cannot return a set"))); } } /* * Check that only the base rel is mentioned. */ if (list_length(pstate->p_rtable) != 1) ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), errmsg("index expressions and predicates can refer only to the table being indexed"))); free_parsestate(pstate); /* Close relation, but keep the lock */ heap_close(rel, NoLock); return stmt; } /* * transformRuleStmt - * transform a CREATE RULE Statement. The action is a list of parse * trees which is transformed into a list of query trees, and we also * transform the WHERE clause if any. * * actions and whereClause are output parameters that receive the * transformed results. * * Note that we must not scribble on the passed-in RuleStmt, so we do * copyObject() on the actions and WHERE clause. */ void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause) { Relation rel; ParseState *pstate; RangeTblEntry *oldrte; RangeTblEntry *newrte; /* * To avoid deadlock, make sure the first thing we do is grab * AccessExclusiveLock on the target relation. This will be needed by * DefineQueryRewrite(), and we don't want to grab a lesser lock * beforehand. */ rel = heap_openrv(stmt->relation, AccessExclusiveLock); /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; /* * NOTE: 'OLD' must always have a varno equal to 1 and 'NEW' equal to 2. * Set up their RTEs in the main pstate for use in parsing the rule * qualification. */ oldrte = addRangeTableEntryForRelation(pstate, rel, makeAlias("*OLD*", NIL), false, false); newrte = addRangeTableEntryForRelation(pstate, rel, makeAlias("*NEW*", NIL), false, false); /* Must override addRangeTableEntry's default access-check flags */ oldrte->requiredPerms = 0; newrte->requiredPerms = 0; /* * They must be in the namespace too for lookup purposes, but only add the * one(s) that are relevant for the current kind of rule. In an UPDATE * rule, quals must refer to OLD.field or NEW.field to be unambiguous, but * there's no need to be so picky for INSERT & DELETE. We do not add them * to the joinlist. */ switch (stmt->event) { case CMD_SELECT: addRTEtoQuery(pstate, oldrte, false, true, true); break; case CMD_UPDATE: addRTEtoQuery(pstate, oldrte, false, true, true); addRTEtoQuery(pstate, newrte, false, true, true); break; case CMD_INSERT: addRTEtoQuery(pstate, newrte, false, true, true); break; case CMD_DELETE: addRTEtoQuery(pstate, oldrte, false, true, true); break; default: elog(ERROR, "unrecognized event type: %d", (int) stmt->event); break; } /* take care of the where clause */ *whereClause = transformWhereClause(pstate, (Node *) copyObject(stmt->whereClause), "WHERE"); if (list_length(pstate->p_rtable) != 2) /* naughty, naughty... */ ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("rule WHERE condition cannot contain references to other relations"))); /* aggregates not allowed (but subselects are okay) */ if (pstate->p_hasAggs) ereport(ERROR, (errcode(ERRCODE_GROUPING_ERROR), errmsg("cannot use aggregate function in rule WHERE condition"))); /* * 'instead nothing' rules with a qualification need a query rangetable so * the rewrite handler can add the negated rule qualification to the * original query. We create a query with the new command type CMD_NOTHING * here that is treated specially by the rewrite system. */ if (stmt->actions == NIL) { Query *nothing_qry = makeNode(Query); nothing_qry->commandType = CMD_NOTHING; nothing_qry->rtable = pstate->p_rtable; nothing_qry->jointree = makeFromExpr(NIL, NULL); /* no join wanted */ *actions = list_make1(nothing_qry); } else { ListCell *l; List *newactions = NIL; /* * transform each statement, like parse_sub_analyze() */ foreach(l, stmt->actions) { Node *action = (Node *) lfirst(l); ParseState *sub_pstate = make_parsestate(NULL); Query *sub_qry, *top_subqry; bool has_old, has_new; /* * Since outer ParseState isn't parent of inner, have to pass down * the query text by hand. */ sub_pstate->p_sourcetext = queryString; /* * Set up OLD/NEW in the rtable for this statement. The entries * are added only to relnamespace, not varnamespace, because we * don't want them to be referred to by unqualified field names * nor "*" in the rule actions. We decide later whether to put * them in the joinlist. */ oldrte = addRangeTableEntryForRelation(sub_pstate, rel, makeAlias("*OLD*", NIL), false, false); newrte = addRangeTableEntryForRelation(sub_pstate, rel, makeAlias("*NEW*", NIL), false, false); oldrte->requiredPerms = 0; newrte->requiredPerms = 0; addRTEtoQuery(sub_pstate, oldrte, false, true, false); addRTEtoQuery(sub_pstate, newrte, false, true, false); /* Transform the rule action statement */ top_subqry = transformStmt(sub_pstate, (Node *) copyObject(action)); /* * We cannot support utility-statement actions (eg NOTIFY) with * nonempty rule WHERE conditions, because there's no way to make * the utility action execute conditionally. */ if (top_subqry->commandType == CMD_UTILITY && *whereClause != NULL) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("rules with WHERE conditions can only have SELECT, INSERT, UPDATE, or DELETE actions"))); /* * If the action is INSERT...SELECT, OLD/NEW have been pushed down * into the SELECT, and that's what we need to look at. (Ugly * kluge ... try to fix this when we redesign querytrees.) */ sub_qry = getInsertSelectQuery(top_subqry, NULL); /* * If the sub_qry is a setop, we cannot attach any qualifications * to it, because the planner won't notice them. This could * perhaps be relaxed someday, but for now, we may as well reject * such a rule immediately. */ if (sub_qry->setOperations != NULL && *whereClause != NULL) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("conditional UNION/INTERSECT/EXCEPT statements are not implemented"))); /* * Validate action's use of OLD/NEW, qual too */ has_old = rangeTableEntry_used((Node *) sub_qry, PRS2_OLD_VARNO, 0) || rangeTableEntry_used(*whereClause, PRS2_OLD_VARNO, 0); has_new = rangeTableEntry_used((Node *) sub_qry, PRS2_NEW_VARNO, 0) || rangeTableEntry_used(*whereClause, PRS2_NEW_VARNO, 0); switch (stmt->event) { case CMD_SELECT: if (has_old) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("ON SELECT rule cannot use OLD"))); if (has_new) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("ON SELECT rule cannot use NEW"))); break; case CMD_UPDATE: /* both are OK */ break; case CMD_INSERT: if (has_old) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("ON INSERT rule cannot use OLD"))); break; case CMD_DELETE: if (has_new) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("ON DELETE rule cannot use NEW"))); break; default: elog(ERROR, "unrecognized event type: %d", (int) stmt->event); break; } /* * For efficiency's sake, add OLD to the rule action's jointree * only if it was actually referenced in the statement or qual. * * For INSERT, NEW is not really a relation (only a reference to * the to-be-inserted tuple) and should never be added to the * jointree. * * For UPDATE, we treat NEW as being another kind of reference to * OLD, because it represents references to *transformed* tuples * of the existing relation. It would be wrong to enter NEW * separately in the jointree, since that would cause a double * join of the updated relation. It's also wrong to fail to make * a jointree entry if only NEW and not OLD is mentioned. */ if (has_old || (has_new && stmt->event == CMD_UPDATE)) { /* * If sub_qry is a setop, manipulating its jointree will do no * good at all, because the jointree is dummy. (This should be * a can't-happen case because of prior tests.) */ if (sub_qry->setOperations != NULL) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("conditional UNION/INTERSECT/EXCEPT statements are not implemented"))); /* hack so we can use addRTEtoQuery() */ sub_pstate->p_rtable = sub_qry->rtable; sub_pstate->p_joinlist = sub_qry->jointree->fromlist; addRTEtoQuery(sub_pstate, oldrte, true, false, false); sub_qry->jointree->fromlist = sub_pstate->p_joinlist; } newactions = lappend(newactions, top_subqry); free_parsestate(sub_pstate); } *actions = newactions; } free_parsestate(pstate); /* Close relation, but keep the exclusive lock */ heap_close(rel, NoLock); } /* * transformAlterTableStmt - * parse analysis for ALTER TABLE * * Returns a List of utility commands to be done in sequence. One of these * will be the transformed AlterTableStmt, but there may be additional actions * to be done before and after the actual AlterTable() call. */ List * transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString) { Relation rel; ParseState *pstate; CreateStmtContext cxt; List *result; List *save_alist; ListCell *lcmd, *l; List *newcmds = NIL; bool skipValidation = true; AlterTableCmd *newcmd; /* * 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 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. */ rel = relation_openrv(stmt->relation, AccessExclusiveLock); /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; cxt.stmtType = "ALTER TABLE"; cxt.relation = stmt->relation; cxt.rel = rel; cxt.inhRelations = NIL; cxt.isalter = true; cxt.hasoids = false; /* need not be right */ cxt.columns = NIL; cxt.ckconstraints = NIL; cxt.fkconstraints = NIL; cxt.ixconstraints = NIL; cxt.inh_indexes = NIL; cxt.blist = NIL; cxt.alist = NIL; cxt.pkey = NULL; /* * The only subtypes that currently require parse transformation handling * are ADD COLUMN and ADD CONSTRAINT. These largely re-use code from * CREATE TABLE. */ foreach(lcmd, stmt->cmds) { AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd); switch (cmd->subtype) { case AT_AddColumn: { ColumnDef *def = (ColumnDef *) cmd->def; Assert(IsA(cmd->def, ColumnDef)); transformColumnDefinition(pstate, &cxt, (ColumnDef *) cmd->def); /* * If the column has a non-null default, we can't skip * validation of foreign keys. */ if (((ColumnDef *) cmd->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; 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; } else elog(ERROR, "unrecognized node type: %d", (int) nodeTag(cmd->def)); break; case AT_ProcessedConstraint: /* * Already-transformed ADD CONSTRAINT, so just make it look * like the standard case. */ cmd->subtype = AT_AddConstraint; newcmds = lappend(newcmds, cmd); break; default: newcmds = lappend(newcmds, cmd); break; } } /* * transformIndexConstraints wants cxt.alist to contain only index * 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); transformFKConstraints(pstate, &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. */ foreach(l, cxt.alist) { Node *idxstmt = (Node *) lfirst(l); Assert(IsA(idxstmt, IndexStmt)); newcmd = makeNode(AlterTableCmd); newcmd->subtype = AT_AddIndex; newcmd->def = (Node *) transformIndexStmt((IndexStmt *) idxstmt, queryString); newcmds = lappend(newcmds, newcmd); } cxt.alist = NIL; /* Append any CHECK or FK constraints to the commands list */ foreach(l, cxt.ckconstraints) { newcmd = makeNode(AlterTableCmd); newcmd->subtype = AT_AddConstraint; newcmd->def = (Node *) lfirst(l); newcmds = lappend(newcmds, newcmd); } foreach(l, cxt.fkconstraints) { newcmd = makeNode(AlterTableCmd); newcmd->subtype = AT_AddConstraint; newcmd->def = (Node *) lfirst(l); newcmds = lappend(newcmds, newcmd); } /* Close rel but keep lock */ relation_close(rel, NoLock); /* * Output results. */ stmt->cmds = newcmds; result = lappend(cxt.blist, stmt); result = list_concat(result, cxt.alist); result = list_concat(result, save_alist); return result; } /* * Preprocess a list of column constraint clauses * 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. */ static void transformConstraintAttrs(List *constraintList) { Node *lastprimarynode = NULL; bool saw_deferrability = false; bool saw_initially = false; ListCell *clist; foreach(clist, constraintList) { Node *node = lfirst(clist); if (!IsA(node, Constraint)) { lastprimarynode = node; /* reset flags for new primary node */ saw_deferrability = false; saw_initially = false; } else { Constraint *con = (Constraint *) node; 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; /* * 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; } } } } /* * Special handling of type definition for a column */ static void transformColumnType(ParseState *pstate, ColumnDef *column) { /* * All we really need to do here is verify that the type is valid. */ Type ctype = typenameType(pstate, column->typename, NULL); ReleaseSysCache(ctype); } /* * transformCreateSchemaStmt - * analyzes the CREATE SCHEMA statement * * Split the schema element list into individual commands and place * them in the result list in an order such that there are no forward * references (e.g. GRANT to a table created later in the list). Note * that the logic we use for determining forward references is * presently quite incomplete. * * SQL92 also allows constraints to make forward references, so thumb through * the table columns and move forward references to a posterior alter-table * command. * * The result is a list of parse nodes that still need to be analyzed --- * but we can't analyze the later commands until we've executed the earlier * ones, because of possible inter-object references. * * Note: this breaks the rules a little bit by modifying schema-name fields * within passed-in structs. However, the transformation would be the same * if done over, so it should be all right to scribble on the input to this * extent. */ List * transformCreateSchemaStmt(CreateSchemaStmt *stmt) { CreateSchemaStmtContext cxt; List *result; ListCell *elements; cxt.stmtType = "CREATE SCHEMA"; cxt.schemaname = stmt->schemaname; cxt.authid = stmt->authid; cxt.sequences = NIL; cxt.tables = NIL; cxt.views = NIL; cxt.indexes = NIL; cxt.triggers = NIL; cxt.grants = NIL; /* * Run through each schema element in the schema element list. Separate * statements by type, and do preliminary analysis. */ foreach(elements, stmt->schemaElts) { Node *element = lfirst(elements); switch (nodeTag(element)) { case T_CreateSeqStmt: { CreateSeqStmt *elp = (CreateSeqStmt *) element; setSchemaName(cxt.schemaname, &elp->sequence->schemaname); cxt.sequences = lappend(cxt.sequences, element); } break; case T_CreateStmt: { CreateStmt *elp = (CreateStmt *) element; setSchemaName(cxt.schemaname, &elp->relation->schemaname); /* * XXX todo: deal with constraints */ cxt.tables = lappend(cxt.tables, element); } break; case T_ViewStmt: { ViewStmt *elp = (ViewStmt *) element; setSchemaName(cxt.schemaname, &elp->view->schemaname); /* * XXX todo: deal with references between views */ cxt.views = lappend(cxt.views, element); } break; case T_IndexStmt: { IndexStmt *elp = (IndexStmt *) element; setSchemaName(cxt.schemaname, &elp->relation->schemaname); cxt.indexes = lappend(cxt.indexes, element); } break; case T_CreateTrigStmt: { CreateTrigStmt *elp = (CreateTrigStmt *) element; setSchemaName(cxt.schemaname, &elp->relation->schemaname); cxt.triggers = lappend(cxt.triggers, element); } break; case T_GrantStmt: cxt.grants = lappend(cxt.grants, element); break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(element)); } } result = NIL; result = list_concat(result, cxt.sequences); result = list_concat(result, cxt.tables); result = list_concat(result, cxt.views); result = list_concat(result, cxt.indexes); result = list_concat(result, cxt.triggers); result = list_concat(result, cxt.grants); return result; } /* * setSchemaName * Set or check schema name in an element of a CREATE SCHEMA command */ static void setSchemaName(char *context_schema, char **stmt_schema_name) { if (*stmt_schema_name == NULL) *stmt_schema_name = context_schema; else if (strcmp(context_schema, *stmt_schema_name) != 0) ereport(ERROR, (errcode(ERRCODE_INVALID_SCHEMA_DEFINITION), errmsg("CREATE specifies a schema (%s) " "different from the one being created (%s)", *stmt_schema_name, context_schema))); }