#include "postgres.h"
#include "access/htup_details.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 "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_type.h"
#include "parser/parse_utilcmd.h"
#include "parser/parser.h"
#include "rewrite/rewriteManip.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
Go to the source code of this file.
#define SUPPORTS_ATTRS | ( | node | ) |
((node) != NULL && \ ((node)->contype == CONSTR_PRIMARY || \ (node)->contype == CONSTR_UNIQUE || \ (node)->contype == CONSTR_EXCLUSION || \ (node)->contype == CONSTR_FOREIGN))
Referenced by transformConstraintAttrs().
static IndexStmt * generateClonedIndexStmt | ( | CreateStmtContext * | cxt, | |
Relation | source_idx, | |||
const AttrNumber * | attmap, | |||
int | attmap_length | |||
) | [static] |
Definition at line 990 of file parse_utilcmd.c.
References IndexStmt::accessMethod, Anum_pg_class_reloptions, Anum_pg_constraint_conexclop, Anum_pg_index_indclass, Anum_pg_index_indcollation, Anum_pg_index_indexprs, Anum_pg_index_indpred, Assert, AttributeNumberIsValid, IndexElem::collation, IndexStmt::concurrent, CONSTRAINT_EXCLUSION, CONSTROID, DatumGetArrayTypeP, DatumGetObjectId, DatumGetPointer, deconstruct_array(), IndexStmt::deferrable, elog, ereport, errcode(), errdetail(), errmsg(), ERROR, IndexStmt::excludeOpNames, IndexElem::expr, exprType(), get_atttype(), get_collation(), get_index_constraint(), get_namespace_name(), get_opclass(), get_relid_attribute_name(), get_tablespace_name(), GETSTRUCT, HeapTupleIsValid, i, IndexStmt::idxcomment, IndexStmt::idxname, IndexElem::indexcolname, IndexStmt::indexOid, IndexStmt::indexParams, INDEXRELID, INDOPTION_DESC, INDOPTION_NULLS_FIRST, IndexStmt::initdeferred, IndexStmt::isconstraint, lappend(), lfirst, list_head(), list_make2, lnext, makeNode, makeString(), map_variable_attnos(), IndexElem::name, NameStr, NULL, IndexElem::nulls_ordering, ObjectIdGetDatum, OidIsValid, OIDOID, IndexStmt::oldNode, IndexElem::opclass, OPEROID, IndexStmt::options, IndexElem::ordering, IndexStmt::primary, pstrdup(), RelationData::rd_am, RelationData::rd_indextuple, RelationData::rd_indoption, CreateStmtContext::relation, IndexStmt::relation, RelationGetDescr, RelationGetRelationName, RelationGetRelid, ReleaseSysCache(), RELOID, SearchSysCache1, stringToNode(), SysCacheGetAttr(), IndexStmt::tableSpace, TextDatumGetCString, IndexStmt::unique, untransformRelOptions(), oidvector::values, and IndexStmt::whereClause.
Referenced by transformTableLikeClause().
{ 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 *indexprs; ListCell *indexpr_item; Oid indrelid; int keyno; Oid keycoltype; Datum datum; bool isnull; /* * Fetch pg_class tuple of source index. We can't use the copy in the * relcache entry because it doesn't include optional fields. */ 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->excludeOpNames = NIL; index->idxcomment = NULL; index->indexOid = InvalidOid; index->oldNode = 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++) { IndexElem *iparam; AttrNumber attnum = idxrec->indkey.values[keyno]; int16 opt = source_idx->rd_indoption[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; bool found_whole_row; if (indexpr_item == NULL) elog(ERROR, "too few entries in indexprs list"); indexkey = (Node *) lfirst(indexpr_item); indexpr_item = lnext(indexpr_item); /* Adjust Vars to match new table's column numbering */ indexkey = map_variable_attnos(indexkey, 1, 0, attmap, attmap_length, &found_whole_row); /* As in transformTableLikeClause, reject whole-row variables */ if (found_whole_row) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot convert whole-row table reference"), errdetail("Index \"%s\" contains a whole-row table reference.", RelationGetRelationName(source_idx)))); iparam->name = NULL; iparam->expr = indexkey; keycoltype = exprType(indexkey); } /* 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); 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) { iparam->ordering = SORTBY_DESC; if ((opt & INDOPTION_NULLS_FIRST) == 0) iparam->nulls_ordering = SORTBY_NULLS_LAST; } else { if (opt & INDOPTION_NULLS_FIRST) iparam->nulls_ordering = SORTBY_NULLS_FIRST; } } index->indexParams = lappend(index->indexParams, iparam); } /* Copy reloptions if any */ datum = SysCacheGetAttr(RELOID, ht_idxrel, Anum_pg_class_reloptions, &isnull); if (!isnull) index->options = untransformRelOptions(datum); /* 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; Node *pred_tree; bool found_whole_row; /* Convert text string to node tree */ pred_str = TextDatumGetCString(datum); pred_tree = (Node *) stringToNode(pred_str); /* Adjust Vars to match new table's column numbering */ pred_tree = map_variable_attnos(pred_tree, 1, 0, attmap, attmap_length, &found_whole_row); /* As in transformTableLikeClause, reject whole-row variables */ if (found_whole_row) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot convert whole-row table reference"), errdetail("Index \"%s\" contains a whole-row table reference.", RelationGetRelationName(source_idx)))); index->whereClause = pred_tree; } /* Clean up */ ReleaseSysCache(ht_idxrel); return index; }
Definition at line 1295 of file parse_utilcmd.c.
References COLLOID, elog, ERROR, get_namespace_name(), get_typcollation(), GETSTRUCT, HeapTupleIsValid, list_make2, makeString(), NameStr, ObjectIdGetDatum, OidIsValid, pstrdup(), ReleaseSysCache(), and SearchSysCache1.
Referenced by generateClonedIndexStmt().
{ 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; }
Definition at line 1329 of file parse_utilcmd.c.
References CLAOID, elog, ERROR, get_namespace_name(), GetDefaultOpClass(), GETSTRUCT, HeapTupleIsValid, list_make2, makeString(), NameStr, ObjectIdGetDatum, pstrdup(), ReleaseSysCache(), and SearchSysCache1.
Referenced by generateClonedIndexStmt().
{ 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; }
static void setSchemaName | ( | char * | context_schema, | |
char ** | stmt_schema_name | |||
) | [static] |
Definition at line 2813 of file parse_utilcmd.c.
References ereport, errcode(), errmsg(), ERROR, and NULL.
Referenced by transformCreateSchemaStmt().
{ 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))); }
List* transformAlterTableStmt | ( | AlterTableStmt * | stmt, | |
const char * | queryString | |||
) |
Definition at line 2326 of file parse_utilcmd.c.
References CreateStmtContext::alist, AlterTableGetLockLevel(), Assert, AT_AddColumn, AT_AddColumnToView, AT_AddConstraint, AT_AddIndexConstraint, AT_ProcessedConstraint, CreateStmtContext::blist, CreateStmtContext::ckconstraints, AlterTableStmt::cmds, CreateStmtContext::columns, CONSTR_FOREIGN, ColumnDef::constraints, copyObject(), AlterTableCmd::def, elog, ereport, errmsg(), ERROR, CreateStmtContext::fkconstraints, CreateStmtContext::hasoids, IndexStmt::indexOid, CreateStmtContext::inh_indexes, CreateStmtContext::inhRelations, IsA, CreateStmtContext::isalter, CreateStmtContext::isforeign, CreateStmtContext::ixconstraints, lappend(), lfirst, list_concat(), make_parsestate(), makeNode, AlterTableStmt::missing_ok, nodeTag, NoLock, NOTICE, NULL, OBJECT_FOREIGN_TABLE, OidIsValid, ParseState::p_sourcetext, CreateStmtContext::pkey, CreateStmtContext::pstate, ColumnDef::raw_default, CreateStmtContext::rel, CreateStmtContext::relation, AlterTableStmt::relation, relation_close(), relation_openrv_extended(), AlterTableStmt::relkind, RangeVar::relname, CreateStmtContext::stmtType, AlterTableCmd::subtype, transformColumnDefinition(), transformFKConstraints(), transformIndexConstraints(), transformIndexStmt(), and transformTableConstraint().
Referenced by ATPostAlterTypeParse(), and ProcessUtilitySlow().
{ Relation rel; ParseState *pstate; CreateStmtContext cxt; List *result; List *save_alist; ListCell *lcmd, *l; 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.) */ stmt = (AlterTableStmt *) copyObject(stmt); /* * 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. */ rel = relation_openrv_extended(stmt->relation, lockmode, stmt->missing_ok); if (rel == NULL) { /* this message is consistent with relation_openrv */ ereport(NOTICE, (errmsg("relation \"%s\" does not exist, skipping", stmt->relation->relname))); return NIL; } /* Set up pstate and CreateStmtContext */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; cxt.pstate = pstate; if (stmt->relkind == OBJECT_FOREIGN_TABLE) { cxt.stmtType = "ALTER FOREIGN TABLE"; cxt.isforeign = true; } else { cxt.stmtType = "ALTER TABLE"; cxt.isforeign = false; } 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: case AT_AddColumnToView: { ColumnDef *def = (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 (def->raw_default != NULL) skipValidation = false; /* * 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(&cxt, (Constraint *) cmd->def); if (((Constraint *) cmd->def)->contype == CONSTR_FOREIGN) 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(&cxt); 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 or AT_AddIndexConstraint * subcommand has already been through transformIndexStmt. */ foreach(l, cxt.alist) { IndexStmt *idxstmt = (IndexStmt *) lfirst(l); Assert(IsA(idxstmt, IndexStmt)); idxstmt = transformIndexStmt(idxstmt, queryString); newcmd = makeNode(AlterTableCmd); newcmd->subtype = OidIsValid(idxstmt->indexOid) ? AT_AddIndexConstraint : AT_AddIndex; newcmd->def = (Node *) idxstmt; 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; }
static void transformColumnDefinition | ( | CreateStmtContext * | cxt, | |
ColumnDef * | column | |||
) | [static] |
Definition at line 294 of file parse_utilcmd.c.
References FuncCall::agg_distinct, FuncCall::agg_order, FuncCall::agg_star, CreateStmtContext::alist, TypeCast::arg, FuncCall::args, TypeName::arrayBounds, Assert, AlterTableCmd::behavior, CreateStmtContext::blist, ChooseRelationName(), CreateStmtContext::ckconstraints, AlterTableStmt::cmds, ColumnDef::colname, CreateStmtContext::columns, CONSTR_ATTR_DEFERRABLE, CONSTR_ATTR_DEFERRED, CONSTR_ATTR_IMMEDIATE, CONSTR_ATTR_NOT_DEFERRABLE, CONSTR_CHECK, CONSTR_DEFAULT, CONSTR_EXCLUSION, CONSTR_FOREIGN, CONSTR_NOTNULL, CONSTR_NULL, CONSTR_PRIMARY, CONSTR_UNIQUE, ColumnDef::constraints, Constraint::contype, Constraint::cooked_expr, DEBUG1, AlterTableCmd::def, DROP_RESTRICT, elog, ereport, errcode(), errmsg(), ERROR, ColumnDef::fdwoptions, Constraint::fk_attrs, CreateStmtContext::fkconstraints, FuncCall::func_variadic, FuncCall::funcname, get_namespace_name(), ColumnDef::is_not_null, IsA, CreateStmtContext::isforeign, CreateStmtContext::ixconstraints, Constraint::keys, lappend(), lfirst, linitial, list_length(), list_make1, list_make3, Constraint::location, FuncCall::location, TypeCast::location, A_Const::location, TypeName::location, makeDefElem(), makeNode, makeRangeVar(), makeString(), AlterTableCmd::missing_ok, AlterTableCmd::name, TypeName::names, NIL, NULL, AlterSeqStmt::options, CreateSeqStmt::options, FuncCall::over, CreateSeqStmt::ownerId, parser_errposition(), TypeName::pct_type, CreateStmtContext::pstate, quote_qualified_identifier(), RangeVarAdjustRelationPersistence(), RangeVarGetCreationNamespace(), ColumnDef::raw_default, Constraint::raw_expr, RelationData::rd_rel, CreateStmtContext::rel, AlterTableStmt::relation, CreateStmtContext::relation, RelationGetNamespace, AlterTableStmt::relkind, RangeVar::relname, AlterSeqStmt::sequence, CreateSeqStmt::sequence, CreateStmtContext::stmtType, Value::ValUnion::str, strVal, AlterTableCmd::subtype, SystemFuncName(), SystemTypeName(), transformColumnType(), transformConstraintAttrs(), Value::type, TypeCast::typeName, ColumnDef::typeName, TypeName::typeOid, Value::val, and A_Const::val.
Referenced by transformAlterTableStmt(), and transformCreateStmt().
{ 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 (column->typeName && list_length(column->typeName->names) == 1 && !column->typeName->pct_type) { char *typname = strVal(linitial(column->typeName->names)); 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->typeOid = INT4OID; } else if (strcmp(typname, "bigserial") == 0 || strcmp(typname, "serial8") == 0) { is_serial = true; 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 */ if (column->typeName) transformColumnType(cxt, column); /* Special actions for SERIAL pseudo-types */ if (is_serial) { Oid snamespaceid; char *snamespace; char *sname; char *qstring; A_Const *snamenode; TypeCast *castnode; 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); RangeVarAdjustRelationPersistence(cxt->relation, snamespaceid); } snamespace = get_namespace_name(snamespaceid); sname = ChooseRelationName(cxt->relation->relname, column->colname, "seq", snamespaceid); ereport(DEBUG1, (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, -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); /* * 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, -1); 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->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(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; 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(cxt, column->constraints); saw_nullable = false; saw_default = false; foreach(clist, column->constraints) { constraint = lfirst(clist); 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), parser_errposition(cxt->pstate, constraint->location))); 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), parser_errposition(cxt->pstate, constraint->location))); 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), parser_errposition(cxt->pstate, constraint->location))); column->raw_default = constraint->raw_expr; Assert(constraint->cooked_expr == NULL); saw_default = true; break; case CONSTR_CHECK: if (cxt->isforeign) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("constraints are not supported on foreign tables"), parser_errposition(cxt->pstate, constraint->location))); cxt->ckconstraints = lappend(cxt->ckconstraints, constraint); break; case CONSTR_PRIMARY: case CONSTR_UNIQUE: if (cxt->isforeign) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("constraints are not supported on foreign tables"), parser_errposition(cxt->pstate, constraint->location))); if (constraint->keys == NIL) constraint->keys = list_make1(makeString(column->colname)); cxt->ixconstraints = lappend(cxt->ixconstraints, constraint); break; case CONSTR_EXCLUSION: /* grammar does not allow EXCLUDE as a column constraint */ elog(ERROR, "column exclusion constraints are not supported"); break; case CONSTR_FOREIGN: if (cxt->isforeign) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("constraints are not supported on foreign tables"), parser_errposition(cxt->pstate, constraint->location))); /* * 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: 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; } } /* * If needed, generate ALTER FOREIGN TABLE ALTER COLUMN statement to add * per-column foreign data wrapper options to this column after creation. */ 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); } }
static void transformColumnType | ( | CreateStmtContext * | cxt, | |
ColumnDef * | column | |||
) | [static] |
Definition at line 2655 of file parse_utilcmd.c.
References ColumnDef::collClause, CollateClause::collname, ereport, errcode(), errmsg(), ERROR, format_type_be(), GETSTRUCT, HeapTupleGetOid, CollateClause::location, LookupCollation(), NULL, OidIsValid, parser_errposition(), CreateStmtContext::pstate, ReleaseSysCache(), ColumnDef::typeName, and typenameType().
Referenced by transformColumnDefinition().
{ /* * 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(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); }
static void transformConstraintAttrs | ( | CreateStmtContext * | cxt, | |
List * | constraintList | |||
) | [static] |
Definition at line 2540 of file parse_utilcmd.c.
References CONSTR_ATTR_DEFERRABLE, CONSTR_ATTR_DEFERRED, CONSTR_ATTR_IMMEDIATE, CONSTR_ATTR_NOT_DEFERRABLE, Constraint::contype, Constraint::deferrable, elog, ereport, errcode(), errmsg(), ERROR, Constraint::initdeferred, IsA, lfirst, Constraint::location, nodeTag, parser_errposition(), CreateStmtContext::pstate, and SUPPORTS_ATTRS.
Referenced by transformColumnDefinition().
{ 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) { Constraint *con = (Constraint *) lfirst(clist); if (!IsA(con, Constraint)) elog(ERROR, "unrecognized node type: %d", (int) nodeTag(con)); switch (con->contype) { 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; 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; 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; } } }
List* transformCreateSchemaStmt | ( | CreateSchemaStmt * | stmt | ) |
Definition at line 2708 of file parse_utilcmd.c.
References CreateSchemaStmt::authid, CreateSchemaStmtContext::authid, element(), elog, ERROR, CreateSchemaStmtContext::grants, CreateSchemaStmtContext::indexes, lappend(), lfirst, list_concat(), nodeTag, CreateTrigStmt::relation, IndexStmt::relation, CreateStmt::relation, CreateSchemaStmt::schemaElts, RangeVar::schemaname, CreateSchemaStmt::schemaname, CreateSchemaStmtContext::schemaname, CreateSeqStmt::sequence, CreateSchemaStmtContext::sequences, setSchemaName(), CreateSchemaStmtContext::stmtType, T_CreateSeqStmt, T_CreateStmt, T_CreateTrigStmt, T_GrantStmt, T_IndexStmt, T_ViewStmt, CreateSchemaStmtContext::tables, CreateSchemaStmtContext::triggers, ViewStmt::view, and CreateSchemaStmtContext::views.
Referenced by CreateSchemaCommand().
{ 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; }
List* transformCreateStmt | ( | CreateStmt * | stmt, | |
const char * | queryString | |||
) |
Definition at line 143 of file parse_utilcmd.c.
References CreateStmtContext::alist, Assert, CreateStmtContext::blist, CreateStmtContext::ckconstraints, CreateStmtContext::columns, CreateStmt::constraints, copyObject(), element(), elog, ereport, errcode(), errmsg(), ERROR, CreateStmtContext::fkconstraints, get_namespace_name(), CreateStmtContext::hasoids, CreateStmt::if_not_exists, CreateStmtContext::inh_indexes, CreateStmt::inhRelations, CreateStmtContext::inhRelations, interpretOidsOption(), IsA, CreateStmtContext::isalter, CreateStmtContext::isforeign, CreateStmtContext::ixconstraints, lappend(), lfirst, list_concat(), make_parsestate(), NIL, nodeTag, NoLock, NOTICE, NULL, CreateStmt::ofTypename, OidIsValid, CreateStmt::options, ParseState::p_sourcetext, CreateStmtContext::pkey, CreateStmtContext::pstate, RangeVarGetAndCheckCreationNamespace(), CreateStmtContext::rel, CreateStmtContext::relation, CreateStmt::relation, RangeVar::relname, RangeVar::relpersistence, RELPERSISTENCE_TEMP, RangeVar::schemaname, CreateStmtContext::stmtType, T_ColumnDef, T_Constraint, T_TableLikeClause, CreateStmt::tableElts, transformColumnDefinition(), transformFKConstraints(), transformIndexConstraints(), transformOfType(), transformTableConstraint(), and transformTableLikeClause().
Referenced by ProcessUtilitySlow().
{ ParseState *pstate; CreateStmtContext cxt; 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 * overkill, but easy.) */ 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. 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.pstate = pstate; if (IsA(stmt, CreateForeignTableStmt)) { cxt.stmtType = "CREATE FOREIGN TABLE"; cxt.isforeign = true; } else { cxt.stmtType = "CREATE TABLE"; cxt.isforeign = false; } 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, true); 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. */ foreach(elements, stmt->tableElts) { Node *element = lfirst(elements); switch (nodeTag(element)) { case T_ColumnDef: transformColumnDefinition(&cxt, (ColumnDef *) element); break; case T_Constraint: transformTableConstraint(&cxt, (Constraint *) element); break; case T_TableLikeClause: transformTableLikeClause(&cxt, (TableLikeClause *) 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(&cxt); /* * Postprocess foreign-key constraints. */ transformFKConstraints(&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; }
static void transformFKConstraints | ( | CreateStmtContext * | cxt, | |
bool | skipValidation, | |||
bool | isAddConstraint | |||
) | [static] |
Definition at line 1845 of file parse_utilcmd.c.
References CreateStmtContext::alist, AlterTableStmt::cmds, AlterTableCmd::def, CreateStmtContext::fkconstraints, Constraint::initially_valid, lappend(), lfirst, makeNode, AlterTableCmd::name, NIL, CreateStmtContext::relation, AlterTableStmt::relation, AlterTableStmt::relkind, Constraint::skip_validation, and AlterTableCmd::subtype.
Referenced by transformAlterTableStmt(), and transformCreateStmt().
{ ListCell *fkclist; if (cxt->fkconstraints == NIL) return; /* * If CREATE TABLE or adding a column with NULL default, we can safely * 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) { Constraint *constraint = (Constraint *) lfirst(fkclist); constraint->skip_validation = true; constraint->initially_valid = 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) { Constraint *constraint = (Constraint *) lfirst(fkclist); AlterTableCmd *altercmd = makeNode(AlterTableCmd); altercmd->subtype = AT_ProcessedConstraint; altercmd->name = NULL; altercmd->def = (Node *) constraint; alterstmt->cmds = lappend(alterstmt->cmds, altercmd); } cxt->alist = lappend(cxt->alist, alterstmt); } }
static IndexStmt * transformIndexConstraint | ( | Constraint * | constraint, | |
CreateStmtContext * | cxt | |||
) | [static] |
Definition at line 1469 of file parse_utilcmd.c.
References Constraint::access_method, IndexStmt::accessMethod, AccessShareLock, Anum_pg_index_indclass, Assert, tupleDesc::attrs, IndexElem::collation, ColumnDef::colname, CreateStmtContext::columns, IndexStmt::concurrent, Constraint::conname, CONSTR_EXCLUSION, CONSTR_PRIMARY, CONSTR_UNIQUE, Constraint::contype, DatumGetPointer, DEFAULT_INDEX_TYPE, Constraint::deferrable, IndexStmt::deferrable, ereport, errcode(), errdetail(), errmsg(), ERROR, IndexStmt::excludeOpNames, Constraint::exclusions, IndexElem::expr, get_am_oid(), get_index_constraint(), get_relname_relid(), GetDefaultOpClass(), CreateStmtContext::hasoids, heap_close, heap_openrv(), i, IndexStmt::idxcomment, IndexStmt::idxname, index_open(), IndexElem::indexcolname, IndexIsValid, Constraint::indexname, IndexStmt::indexOid, IndexStmt::indexParams, INDEXRELID, Constraint::indexspace, CreateStmtContext::inhRelations, Constraint::initdeferred, IndexStmt::initdeferred, ColumnDef::is_not_null, IsA, CreateStmtContext::isalter, IndexStmt::isconstraint, Constraint::keys, lappend(), lfirst, linitial, list_length(), Constraint::location, lsecond, makeNode, makeString(), IndexElem::name, NameStr, tupleDesc::natts, NIL, NoLock, NULL, IndexElem::nulls_ordering, OidIsValid, IndexStmt::oldNode, IndexElem::opclass, Constraint::options, IndexStmt::options, IndexElem::ordering, parser_errposition(), CreateStmtContext::pkey, IndexStmt::primary, CreateStmtContext::pstate, pstrdup(), RelationData::rd_att, RelationData::rd_index, RelationData::rd_indextuple, RelationData::rd_indoption, RelationData::rd_rel, CreateStmtContext::rel, IndexStmt::relation, CreateStmtContext::relation, relation_close(), RelationGetIndexExpressions(), RelationGetIndexPredicate(), RelationGetNamespace, RelationGetRelationName, RelationGetRelid, RELKIND_RELATION, RangeVar::relname, strVal, SysCacheGetAttr(), SystemAttributeByName(), SystemAttributeDefinition(), IndexStmt::tableSpace, IndexStmt::unique, oidvector::values, Constraint::where_clause, and IndexStmt::whereClause.
Referenced by transformIndexConstraints().
{ 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->idxcomment = NULL; index->indexOid = InvalidOid; index->oldNode = 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 (!IndexIsValid(index_form)) 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->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++) { int16 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; }
static void transformIndexConstraints | ( | CreateStmtContext * | cxt | ) | [static] |
Definition at line 1361 of file parse_utilcmd.c.
References IndexStmt::accessMethod, CreateStmtContext::alist, Assert, CONSTR_EXCLUSION, CONSTR_PRIMARY, CONSTR_UNIQUE, Constraint::contype, IndexStmt::deferrable, equal(), ereport, errcode(), errmsg(), ERROR, IndexStmt::excludeOpNames, IndexStmt::idxname, IndexStmt::indexParams, CreateStmtContext::inh_indexes, IndexStmt::initdeferred, IsA, CreateStmtContext::ixconstraints, lappend(), lfirst, list_make1, NIL, NULL, CreateStmtContext::pkey, IndexStmt::primary, CreateStmtContext::relation, RangeVar::relname, transformIndexConstraint(), IndexStmt::unique, and IndexStmt::whereClause.
Referenced by transformAlterTableStmt(), and transformCreateStmt().
{ 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); } /* * 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 SQL 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) && 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 * 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); } }
Definition at line 1912 of file parse_utilcmd.c.
References addRangeTableEntry(), addRTEtoQuery(), assign_expr_collations(), IndexStmt::concurrent, copyObject(), ereport, errcode(), errmsg(), ERROR, IndexElem::expr, EXPR_KIND_INDEX_EXPRESSION, EXPR_KIND_INDEX_PREDICATE, expression_returns_set(), FigureIndexColname(), free_parsestate(), heap_close, heap_openrv(), IndexElem::indexcolname, IndexStmt::indexParams, lfirst, list_length(), make_parsestate(), NoLock, NULL, ParseState::p_rtable, ParseState::p_sourcetext, IndexStmt::relation, ShareLock, ShareUpdateExclusiveLock, transformExpr(), transformWhereClause(), and IndexStmt::whereClause.
Referenced by ATPostAlterTypeParse(), ProcessUtilitySlow(), and transformAlterTableStmt().
{ 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. If we are being called by ALTER TABLE, we will * already hold a higher lock. */ 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, EXPR_KIND_INDEX_PREDICATE, "WHERE"); /* we have to fix its collations too */ assign_expr_collations(pstate, stmt->whereClause); } /* take care of any index expressions */ foreach(l, stmt->indexParams) { IndexElem *ielem = (IndexElem *) lfirst(l); 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, EXPR_KIND_INDEX_EXPRESSION); /* We have to fix its collations too */ assign_expr_collations(pstate, ielem->expr); /* * transformExpr() should have already rejected subqueries, * aggregates, and window functions, based on the EXPR_KIND_ for * an index expression. * * Also reject expressions returning sets; 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. (This should be dead code * now that add_missing_from is history.) */ 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; }
static void transformOfType | ( | CreateStmtContext * | cxt, | |
TypeName * | ofTypename | |||
) | [static] |
Definition at line 942 of file parse_utilcmd.c.
References AssertArg, tupleDesc::attrs, check_of_type(), ColumnDef::collClause, ColumnDef::collOid, ColumnDef::colname, CreateStmtContext::columns, ColumnDef::constraints, ColumnDef::cooked_default, DecrTupleDescRefCount(), HeapTupleGetOid, i, ColumnDef::inhcount, ColumnDef::is_from_type, ColumnDef::is_local, ColumnDef::is_not_null, lappend(), lookup_rowtype_tupdesc(), makeNode, makeTypeNameFromOid(), NameStr, tupleDesc::natts, NULL, pstrdup(), ColumnDef::raw_default, ReleaseSysCache(), ColumnDef::storage, ColumnDef::typeName, typenameType(), and TypeName::typeOid.
Referenced by transformCreateStmt().
{ 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); }
void transformRuleStmt | ( | RuleStmt * | stmt, | |
const char * | queryString, | |||
List ** | actions, | |||
Node ** | whereClause | |||
) |
Definition at line 2025 of file parse_utilcmd.c.
References AccessExclusiveLock, RuleStmt::actions, addRangeTableEntryForRelation(), addRTEtoQuery(), assign_expr_collations(), CMD_DELETE, CMD_INSERT, CMD_SELECT, CMD_UPDATE, CMD_UTILITY, Query::commandType, copyObject(), Query::cteList, elog, ereport, errcode(), errmsg(), ERROR, RuleStmt::event, EXPR_KIND_WHERE, free_parsestate(), getInsertSelectQuery(), heap_close, heap_openrv(), Query::jointree, lappend(), lfirst, list_length(), list_make1, make_parsestate(), makeAlias(), makeFromExpr(), makeNode, NIL, NoLock, NULL, ParseState::p_joinlist, ParseState::p_rtable, ParseState::p_sourcetext, PRS2_NEW_VARNO, PRS2_OLD_VARNO, rangeTableEntry_used(), RelationData::rd_rel, RuleStmt::relation, RELKIND_MATVIEW, RangeTblEntry::requiredPerms, Query::rtable, Query::setOperations, transformStmt(), transformWhereClause(), and RuleStmt::whereClause.
Referenced by DefineRule().
{ 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); if (rel->rd_rel->relkind == RELKIND_MATVIEW) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("rules on materialized views are not supported"))); /* 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), EXPR_KIND_WHERE, "WHERE"); /* we have to fix its collations too */ assign_expr_collations(pstate, *whereClause); /* this is probably dead code without add_missing_from: */ 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"))); /* * '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; } /* * 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. * * 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); }
static void transformTableConstraint | ( | CreateStmtContext * | cxt, | |
Constraint * | constraint | |||
) | [static] |
Definition at line 612 of file parse_utilcmd.c.
References CreateStmtContext::ckconstraints, CONSTR_ATTR_DEFERRABLE, CONSTR_ATTR_DEFERRED, CONSTR_ATTR_IMMEDIATE, CONSTR_ATTR_NOT_DEFERRABLE, CONSTR_CHECK, CONSTR_DEFAULT, CONSTR_EXCLUSION, CONSTR_FOREIGN, CONSTR_NOTNULL, CONSTR_NULL, CONSTR_PRIMARY, CONSTR_UNIQUE, Constraint::contype, elog, ereport, errcode(), errmsg(), ERROR, CreateStmtContext::fkconstraints, CreateStmtContext::isforeign, CreateStmtContext::ixconstraints, lappend(), Constraint::location, parser_errposition(), and CreateStmtContext::pstate.
Referenced by transformAlterTableStmt(), and transformCreateStmt().
{ if (cxt->isforeign) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("constraints are not supported on foreign tables"), parser_errposition(cxt->pstate, constraint->location))); switch (constraint->contype) { case CONSTR_PRIMARY: case CONSTR_UNIQUE: case CONSTR_EXCLUSION: cxt->ixconstraints = lappend(cxt->ixconstraints, constraint); break; case CONSTR_CHECK: 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: 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; } }
static void transformTableLikeClause | ( | CreateStmtContext * | cxt, | |
TableLikeClause * | table_like_clause | |||
) | [static] |
Definition at line 663 of file parse_utilcmd.c.
References AccessShareLock, ACL_KIND_CLASS, ACL_KIND_TYPE, ACL_SELECT, ACL_USAGE, aclcheck_error(), ACLCHECK_OK, CreateStmtContext::alist, Assert, tupleDesc::attrs, cancel_parser_errposition_callback(), constrCheck::ccbin, constrCheck::ccname, tupleConstr::check, CreateStmtContext::ckconstraints, ColumnDef::collClause, ColumnDef::collOid, ColumnDef::colname, CreateStmtContext::columns, CommentStmt::comment, Constraint::conname, tupleDesc::constr, ConstraintRelationId, ColumnDef::constraints, Constraint::contype, ColumnDef::cooked_default, Constraint::cooked_expr, CREATE_TABLE_LIKE_COMMENTS, CREATE_TABLE_LIKE_CONSTRAINTS, CREATE_TABLE_LIKE_DEFAULTS, CREATE_TABLE_LIKE_INDEXES, CREATE_TABLE_LIKE_STORAGE, tupleConstr::defval, ereport, errcode(), errdetail(), errmsg(), ERROR, generateClonedIndexStmt(), get_relation_constraint_oid(), GetComment(), GetUserId(), heap_close, i, IndexStmt::idxcomment, index_close(), index_open(), CreateStmtContext::inh_indexes, ColumnDef::inhcount, ColumnDef::is_from_type, ColumnDef::is_local, ColumnDef::is_not_null, CreateStmtContext::isforeign, lappend(), lfirst_oid, list_length(), list_make3, Constraint::location, RangeVar::location, makeNode, makeString(), makeTypeNameFromOid(), map_variable_attnos(), NameStr, tupleDesc::natts, nodeToString(), NoLock, NULL, tupleConstr::num_check, tupleConstr::num_defval, CommentStmt::objargs, CommentStmt::objname, CommentStmt::objtype, TableLikeClause::options, palloc0(), pg_class_aclcheck(), pg_type_aclcheck(), CreateStmtContext::pstate, pstrdup(), ColumnDef::raw_default, Constraint::raw_expr, RelationData::rd_rel, CreateStmtContext::relation, TableLikeClause::relation, relation_openrv(), RelationGetDescr, RelationGetIndexList(), RelationGetRelationName, RelationGetRelid, RelationRelationId, RELKIND_COMPOSITE_TYPE, RELKIND_FOREIGN_TABLE, RELKIND_MATVIEW, RELKIND_RELATION, RELKIND_VIEW, RangeVar::relname, RangeVar::schemaname, setup_parser_errposition_callback(), ColumnDef::storage, stringToNode(), and ColumnDef::typeName.
Referenced by transformCreateStmt().
{ AttrNumber parent_attno; Relation relation; TupleDesc tupleDesc; TupleConstr *constr; AttrNumber *attmap; AclResult aclresult; char *comment; ParseCallbackState pcbstate; setup_parser_errposition_callback(&pcbstate, cxt->pstate, table_like_clause->relation->location); /* we could support LIKE in many cases, but worry about it another day */ if (cxt->isforeign) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("LIKE is not supported for foreign tables"))); relation = relation_openrv(table_like_clause->relation, AccessShareLock); if (relation->rd_rel->relkind != RELKIND_RELATION && relation->rd_rel->relkind != RELKIND_VIEW && relation->rd_rel->relkind != RELKIND_MATVIEW && relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE && relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a table, view, composite type, or foreign table", RelationGetRelationName(relation)))); cancel_parser_errposition_callback(&pcbstate); /* * Check for privileges */ if (relation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE) { aclresult = pg_type_aclcheck(relation->rd_rel->reltype, GetUserId(), ACL_USAGE); if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_TYPE, RelationGetRelationName(relation)); } else { 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; /* * Initialize column number map for map_variable_attnos(). We need this * since dropped columns in the source table aren't copied, so the new * table can have different column numbers. */ attmap = (AttrNumber *) palloc0(sizeof(AttrNumber) * tupleDesc->natts); /* * 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. attmap entry is left zero. */ 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->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; /* * Add to column list */ cxt->columns = lappend(cxt->columns, def); attmap[parent_attno - 1] = list_length(cxt->columns); /* * Copy default, if present and the default has been requested */ if (attribute->atthasdef && (table_like_clause->options & CREATE_TABLE_LIKE_DEFAULTS)) { Node *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 = stringToNode(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 = this_default; } /* Likewise, copy storage if requested */ if (table_like_clause->options & CREATE_TABLE_LIKE_STORAGE) def->storage = attribute->attstorage; else def->storage = 0; /* Likewise, copy comment if requested */ if ((table_like_clause->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 so they match the child. */ if ((table_like_clause->options & CREATE_TABLE_LIKE_CONSTRAINTS) && tupleDesc->constr) { 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; Constraint *n = makeNode(Constraint); Node *ccbin_node; bool found_whole_row; ccbin_node = map_variable_attnos(stringToNode(ccbin), 1, 0, attmap, tupleDesc->natts, &found_whole_row); /* * We reject whole-row variables because the whole point of LIKE * is that the new table's rowtype might later diverge from the * parent's. So, while translation might be possible right now, * it wouldn't be possible to guarantee it would work in future. */ if (found_whole_row) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot convert whole-row table reference"), errdetail("Constraint \"%s\" contains a whole-row reference to table \"%s\".", ccname, RelationGetRelationName(relation)))); n->contype = CONSTR_CHECK; n->location = -1; n->conname = pstrdup(ccname); n->raw_expr = NULL; n->cooked_expr = nodeToString(ccbin_node); cxt->ckconstraints = lappend(cxt->ckconstraints, n); /* Copy comment on constraint */ if ((table_like_clause->options & CREATE_TABLE_LIKE_COMMENTS) && (comment = GetComment(get_relation_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 ((table_like_clause->options & CREATE_TABLE_LIKE_INDEXES) && relation->rd_rel->relhasindex) { 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, tupleDesc->natts); /* Copy comment on index, if requested */ if (table_like_clause->options & CREATE_TABLE_LIKE_COMMENTS) { comment = GetComment(parent_index_oid, RelationRelationId, 0); /* * We make use of IndexStmt's idxcomment option, so as not to * need to know now what name the index will have. */ index_stmt->idxcomment = comment; } /* 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); } } /* * 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); }