/*------------------------------------------------------------------------- * * analyze.c * transform the parse tree into a query tree * * Copyright (c) 1994, Regents of the University of California * * $Id: analyze.c,v 1.129 2000/01/15 02:59:31 petere Exp $ * *------------------------------------------------------------------------- */ #include "postgres.h" #include "access/heapam.h" #include "catalog/catname.h" #include "catalog/pg_index.h" #include "catalog/pg_type.h" #include "nodes/makefuncs.h" #include "parse.h" #include "parser/analyze.h" #include "parser/parse_agg.h" #include "parser/parse_clause.h" #include "parser/parse_relation.h" #include "parser/parse_target.h" #include "utils/builtins.h" static Query *transformStmt(ParseState *pstate, Node *stmt); static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt); static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt); static Query *transformIndexStmt(ParseState *pstate, IndexStmt *stmt); static Query *transformExtendStmt(ParseState *pstate, ExtendStmt *stmt); static Query *transformRuleStmt(ParseState *query, RuleStmt *stmt); static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt); static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt); static Query *transformCursorStmt(ParseState *pstate, SelectStmt *stmt); static Query *transformCreateStmt(ParseState *pstate, CreateStmt *stmt); static void transformForUpdate(Query *qry, List *forUpdate); static void transformFkeyGetPrimaryKey(FkConstraint *fkconstraint); void CheckSelectForUpdate(Query *qry); /* kluge to return extra info from transformCreateStmt() */ static List *extras_before; static List *extras_after; /* * parse_analyze - * analyze a list of parse trees and transform them if necessary. * * Returns a list of transformed parse trees. Optimizable statements are * all transformed to Query while the rest stays the same. * */ List * parse_analyze(List *pl, ParseState *parentParseState) { List *result = NIL; ParseState *pstate; Query *parsetree; while (pl != NIL) { extras_before = extras_after = NIL; pstate = make_parsestate(parentParseState); parsetree = transformStmt(pstate, lfirst(pl)); if (pstate->p_target_relation != NULL) heap_close(pstate->p_target_relation, AccessShareLock); pstate->p_target_relation = NULL; pstate->p_target_rangetblentry = NULL; while (extras_before != NIL) { result = lappend(result, transformStmt(pstate, lfirst(extras_before))); if (pstate->p_target_relation != NULL) heap_close(pstate->p_target_relation, AccessShareLock); pstate->p_target_relation = NULL; pstate->p_target_rangetblentry = NULL; extras_before = lnext(extras_before); } result = lappend(result, parsetree); while (extras_after != NIL) { result = lappend(result, transformStmt(pstate, lfirst(extras_after))); if (pstate->p_target_relation != NULL) heap_close(pstate->p_target_relation, AccessShareLock); pstate->p_target_relation = NULL; pstate->p_target_rangetblentry = NULL; extras_after = lnext(extras_after); } pfree(pstate); pl = lnext(pl); } return result; } /* * transformStmt - * transform a Parse tree. If it is an optimizable statement, turn it * into a Query tree. */ static Query * transformStmt(ParseState *pstate, Node *parseTree) { Query *result = NULL; switch (nodeTag(parseTree)) { /*------------------------ * Non-optimizable statements *------------------------ */ case T_CreateStmt: result = transformCreateStmt(pstate, (CreateStmt *) parseTree); break; case T_IndexStmt: result = transformIndexStmt(pstate, (IndexStmt *) parseTree); break; case T_ExtendStmt: result = transformExtendStmt(pstate, (ExtendStmt *) parseTree); break; case T_RuleStmt: result = transformRuleStmt(pstate, (RuleStmt *) parseTree); break; case T_ViewStmt: { ViewStmt *n = (ViewStmt *) parseTree; n->query = (Query *) transformStmt(pstate, (Node *) n->query); result = makeNode(Query); result->commandType = CMD_UTILITY; result->utilityStmt = (Node *) n; } break; case T_VacuumStmt: { MemoryContext oldcontext; /* * make sure that this Query is allocated in TopMemory * context because vacuum spans transactions and we don't * want to lose the vacuum Query due to end-of-transaction * free'ing */ oldcontext = MemoryContextSwitchTo(TopMemoryContext); result = makeNode(Query); result->commandType = CMD_UTILITY; result->utilityStmt = (Node *) parseTree; MemoryContextSwitchTo(oldcontext); } break; case T_ExplainStmt: { ExplainStmt *n = (ExplainStmt *) parseTree; result = makeNode(Query); result->commandType = CMD_UTILITY; n->query = transformStmt(pstate, (Node *) n->query); result->utilityStmt = (Node *) parseTree; } break; /*------------------------ * Optimizable statements *------------------------ */ case T_InsertStmt: result = transformInsertStmt(pstate, (InsertStmt *) parseTree); break; case T_DeleteStmt: result = transformDeleteStmt(pstate, (DeleteStmt *) parseTree); break; case T_UpdateStmt: result = transformUpdateStmt(pstate, (UpdateStmt *) parseTree); break; case T_SelectStmt: if (!((SelectStmt *) parseTree)->portalname) { result = transformSelectStmt(pstate, (SelectStmt *) parseTree); result->limitOffset = ((SelectStmt *) parseTree)->limitOffset; result->limitCount = ((SelectStmt *) parseTree)->limitCount; } else result = transformCursorStmt(pstate, (SelectStmt *) parseTree); break; default: /* * other statments don't require any transformation-- just * return the original parsetree, yea! */ result = makeNode(Query); result->commandType = CMD_UTILITY; result->utilityStmt = (Node *) parseTree; break; } return result; } /* * transformDeleteStmt - * transforms a Delete Statement */ static Query * transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt) { Query *qry = makeNode(Query); qry->commandType = CMD_DELETE; /* set up a range table */ makeRangeTable(pstate, NULL, NULL); setTargetTable(pstate, stmt->relname); qry->uniqueFlag = NULL; /* fix where clause */ qry->qual = transformWhereClause(pstate, stmt->whereClause, NULL); qry->rtable = pstate->p_rtable; qry->resultRelation = refnameRangeTablePosn(pstate, stmt->relname, NULL); qry->hasSubLinks = pstate->p_hasSubLinks; qry->hasAggs = pstate->p_hasAggs; if (pstate->p_hasAggs) parseCheckAggregates(pstate, qry); return (Query *) qry; } /* * transformInsertStmt - * transform an Insert Statement */ static Query * transformInsertStmt(ParseState *pstate, InsertStmt *stmt) { Query *qry = makeNode(Query); Node *fromQual; List *icolumns; List *attrnos; List *attnos; int numuseratts; List *tl; TupleDesc rd_att; qry->commandType = CMD_INSERT; pstate->p_is_insert = true; /*---------- * Initial processing steps are just like SELECT, which should not * be surprising, since we may be handling an INSERT ... SELECT. * It is important that we finish processing all the SELECT subclauses * before we start doing any INSERT-specific processing; otherwise * the behavior of SELECT within INSERT might be different from a * stand-alone SELECT. (Indeed, Postgres up through 6.5 had bugs of * just that nature...) *---------- */ /* set up a range table --- note INSERT target is not in it yet */ makeRangeTable(pstate, stmt->fromClause, &fromQual); qry->uniqueFlag = stmt->unique; qry->targetList = transformTargetList(pstate, stmt->targetList); qry->qual = transformWhereClause(pstate, stmt->whereClause, fromQual); /* Initial processing of HAVING clause is just like WHERE clause. * Additional work will be done in optimizer/plan/planner.c. */ qry->havingQual = transformWhereClause(pstate, stmt->havingClause, NULL); qry->groupClause = transformGroupClause(pstate, stmt->groupClause, qry->targetList); /* An InsertStmt has no sortClause, but we still call * transformSortClause because it also handles uniqueFlag. */ qry->sortClause = transformSortClause(pstate, NIL, qry->targetList, qry->uniqueFlag); qry->hasSubLinks = pstate->p_hasSubLinks; qry->hasAggs = pstate->p_hasAggs; if (pstate->p_hasAggs || qry->groupClause || qry->havingQual) parseCheckAggregates(pstate, qry); /* * The INSERT INTO ... SELECT ... could have a UNION in child, so * unionClause may be false */ qry->unionall = stmt->unionall; /* * Just hand through the unionClause and intersectClause. We will * handle it in the function Except_Intersect_Rewrite() */ qry->unionClause = stmt->unionClause; qry->intersectClause = stmt->intersectClause; /* * Now we are done with SELECT-like processing, and can get on with * transforming the target list to match the INSERT target columns. * * In particular, it's time to add the INSERT target to the rangetable. * (We didn't want it there until now since it shouldn't be visible in * the SELECT part.) */ setTargetTable(pstate, stmt->relname); /* now the range table will not change */ qry->rtable = pstate->p_rtable; qry->resultRelation = refnameRangeTablePosn(pstate, stmt->relname, NULL); /* Prepare to assign non-conflicting resnos to resjunk attributes */ if (pstate->p_last_resno <= pstate->p_target_relation->rd_rel->relnatts) pstate->p_last_resno = pstate->p_target_relation->rd_rel->relnatts + 1; /* Validate stmt->cols list, or build default list if no list given */ icolumns = checkInsertTargets(pstate, stmt->cols, &attrnos); /* Prepare non-junk columns for assignment to target table */ numuseratts = 0; attnos = attrnos; foreach(tl, qry->targetList) { TargetEntry *tle = (TargetEntry *) lfirst(tl); Resdom *resnode = tle->resdom; Ident *id; if (resnode->resjunk) { /* Resjunk nodes need no additional processing, but be sure they * have names and resnos that do not match any target columns; * else rewriter or planner might get confused. */ resnode->resname = "?resjunk?"; resnode->resno = (AttrNumber) pstate->p_last_resno++; continue; } if (icolumns == NIL || attnos == NIL) elog(ERROR, "INSERT has more expressions than target columns"); id = (Ident *) lfirst(icolumns); updateTargetListEntry(pstate, tle, id->name, lfirsti(attnos), id->indirection); numuseratts++; icolumns = lnext(icolumns); attnos = lnext(attnos); } /* * It is possible that the targetlist has fewer entries than were in * the columns list. We do not consider this an error (perhaps we * should, if the columns list was explictly given?). We must truncate * the attrnos list to only include the attrs actually provided, * else we will fail to apply defaults for them below. */ if (icolumns != NIL) attrnos = ltruncate(numuseratts, attrnos); /* * Add targetlist items to assign DEFAULT values to any columns that * have defaults and were not assigned to by the user. * * XXX wouldn't it make more sense to do this further downstream, * after the rule rewriter? */ rd_att = pstate->p_target_relation->rd_att; if (rd_att->constr && rd_att->constr->num_defval > 0) { Form_pg_attribute *att = rd_att->attrs; AttrDefault *defval = rd_att->constr->defval; int ndef = rd_att->constr->num_defval; while (--ndef >= 0) { AttrNumber attrno = defval[ndef].adnum; Form_pg_attribute thisatt = att[attrno - 1]; TargetEntry *te; if (intMember((int) attrno, attrnos)) continue; /* there was a user-specified value */ /* * No user-supplied value, so add a targetentry with DEFAULT expr * and correct data for the target column. */ te = makeTargetEntry( makeResdom(attrno, thisatt->atttypid, thisatt->atttypmod, pstrdup(NameStr(thisatt->attname)), 0, 0, false), stringToNode(defval[ndef].adbin)); qry->targetList = lappend(qry->targetList, te); /* * Make sure the value is coerced to the target column type * (might not be right type if it's not a constant!) */ updateTargetListEntry(pstate, te, te->resdom->resname, attrno, NIL); } } if (stmt->forUpdate != NULL) transformForUpdate(qry, stmt->forUpdate); /* in case of subselects in default clauses... */ qry->hasSubLinks = pstate->p_hasSubLinks; return (Query *) qry; } /* * makeObjectName() * * Create a name for an implicitly created index, sequence, constraint, etc. * * The parameters are: the original table name, the original field name, and * a "type" string (such as "seq" or "pkey"). The field name and/or type * can be NULL if not relevant. * * The result is a palloc'd string. * * The basic result we want is "name1_name2_type", omitting "_name2" or * "_type" when those parameters are NULL. However, we must generate * a name with less than NAMEDATALEN characters! So, we truncate one or * both names if necessary to make a short-enough string. The type part * is never truncated (so it had better be reasonably short). * * To reduce the probability of collisions, we might someday add more * smarts to this routine, like including some "hash" characters computed * from the truncated characters. Currently it seems best to keep it simple, * so that the generated names are easily predictable by a person. */ static char * makeObjectName(char *name1, char *name2, char *typename) { char *name; int overhead = 0; /* chars needed for type and underscores */ int availchars; /* chars available for name(s) */ int name1chars; /* chars allocated to name1 */ int name2chars; /* chars allocated to name2 */ int ndx; name1chars = strlen(name1); if (name2) { name2chars = strlen(name2); overhead++; /* allow for separating underscore */ } else name2chars = 0; if (typename) overhead += strlen(typename) + 1; availchars = NAMEDATALEN-1 - overhead; /* If we must truncate, preferentially truncate the longer name. * This logic could be expressed without a loop, but it's simple and * obvious as a loop. */ while (name1chars + name2chars > availchars) { if (name1chars > name2chars) name1chars--; else name2chars--; } /* Now construct the string using the chosen lengths */ name = palloc(name1chars + name2chars + overhead + 1); strncpy(name, name1, name1chars); ndx = name1chars; if (name2) { name[ndx++] = '_'; strncpy(name+ndx, name2, name2chars); ndx += name2chars; } if (typename) { name[ndx++] = '_'; strcpy(name+ndx, typename); } else name[ndx] = '\0'; return name; } static char * CreateIndexName(char *table_name, char *column_name, char *label, List *indices) { int pass = 0; char *iname = NULL; List *ilist; char typename[NAMEDATALEN]; /* The type name for makeObjectName is label, or labelN if that's * necessary to prevent collisions among multiple indexes for the same * table. Note there is no check for collisions with already-existing * indexes; this ought to be rethought someday. */ strcpy(typename, label); for (;;) { iname = makeObjectName(table_name, column_name, typename); foreach(ilist, indices) { IndexStmt *index = lfirst(ilist); if (strcasecmp(iname, index->idxname) == 0) break; } /* ran through entire list? then no name conflict found so done */ if (ilist == NIL) break; /* the last one conflicted, so try a new name component */ pfree(iname); sprintf(typename, "%s%d", label, ++pass); } return iname; } /* * transformCreateStmt - * transforms the "create table" statement * 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 */ static Query * transformCreateStmt(ParseState *pstate, CreateStmt *stmt) { Query *q; List *elements; Node *element; List *columns; List *dlist; ColumnDef *column; List *constraints, *clist; Constraint *constraint; List *fkconstraints, /* List of FOREIGN KEY constraints to */ *fkclist; /* add finally */ FkConstraint *fkconstraint; List *keys; Ident *key; List *blist = NIL; /* "before list" of things to do before * creating the table */ List *ilist = NIL; /* "index list" of things to do after * creating the table */ IndexStmt *index, *pkey = NULL; IndexElem *iparam; q = makeNode(Query); q->commandType = CMD_UTILITY; fkconstraints = NIL; constraints = stmt->constraints; columns = NIL; dlist = NIL; /* * Run through each primary element in the table creation clause */ foreach(elements, stmt->tableElts) { element = lfirst(elements); switch (nodeTag(element)) { case T_ColumnDef: column = (ColumnDef *) element; columns = lappend(columns, column); /* Special case SERIAL type? */ if (column->is_sequence) { char *sname; char *qstring; A_Const *snamenode; FuncCall *funccallnode; CreateSeqStmt *sequence; sname = makeObjectName(stmt->relname, column->colname, "seq"); /* * Create an expression tree representing the function * call nextval('"sequencename"') */ qstring = palloc(strlen(sname) + 2 + 1); sprintf(qstring, "\"%s\"", sname); snamenode = makeNode(A_Const); snamenode->val.type = T_String; snamenode->val.val.str = qstring; funccallnode = makeNode(FuncCall); funccallnode->funcname = "nextval"; funccallnode->args = lcons(snamenode, NIL); funccallnode->agg_star = false; funccallnode->agg_distinct = false; constraint = makeNode(Constraint); constraint->contype = CONSTR_DEFAULT; constraint->name = sname; constraint->raw_expr = (Node *) funccallnode; constraint->cooked_expr = NULL; constraint->keys = NULL; column->constraints = lappend(column->constraints, constraint); constraint = makeNode(Constraint); constraint->contype = CONSTR_UNIQUE; constraint->name = makeObjectName(stmt->relname, column->colname, "key"); column->constraints = lappend(column->constraints, constraint); sequence = makeNode(CreateSeqStmt); sequence->seqname = pstrdup(sname); sequence->options = NIL; elog(NOTICE, "CREATE TABLE will create implicit sequence '%s' for SERIAL column '%s.%s'", sequence->seqname, stmt->relname, column->colname); blist = lcons(sequence, NIL); } /* Process column constraints, if any... */ foreach(clist, column->constraints) { constraint = lfirst(clist); /* ---------- * If this column constraint is a FOREIGN KEY * constraint, then we fill in the current attributes * name and throw it into the list of FK constraints * to be processed later. * ---------- */ if (nodeTag(constraint) == T_FkConstraint) { Ident *id = makeNode(Ident); id->name = column->colname; id->indirection = NIL; id->isRel = false; fkconstraint = (FkConstraint *)constraint; fkconstraint->fk_attrs = lappend(NIL, id); fkconstraints = lappend(fkconstraints, constraint); continue; } switch (constraint->contype) { case CONSTR_NULL: /* * We should mark this explicitly, so we * can tell if NULL and NOT NULL are both * specified */ if (column->is_not_null) elog(ERROR, "CREATE TABLE/(NOT) NULL conflicting declaration" " for '%s.%s'", stmt->relname, column->colname); column->is_not_null = FALSE; break; case CONSTR_NOTNULL: if (column->is_not_null) elog(ERROR, "CREATE TABLE/NOT NULL already specified" " for '%s.%s'", stmt->relname, column->colname); column->is_not_null = TRUE; break; case CONSTR_DEFAULT: if (column->raw_default != NULL) elog(ERROR, "CREATE TABLE/DEFAULT multiple values specified" " for '%s.%s'", stmt->relname, column->colname); column->raw_default = constraint->raw_expr; Assert(constraint->cooked_expr == NULL); break; case CONSTR_PRIMARY: if (constraint->name == NULL) constraint->name = makeObjectName(stmt->relname, NULL, "pkey"); if (constraint->keys == NIL) constraint->keys = lappend(constraint->keys, column); dlist = lappend(dlist, constraint); break; case CONSTR_UNIQUE: if (constraint->name == NULL) constraint->name = makeObjectName(stmt->relname, column->colname, "key"); if (constraint->keys == NIL) constraint->keys = lappend(constraint->keys, column); dlist = lappend(dlist, constraint); break; case CONSTR_CHECK: if (constraint->name == NULL) constraint->name = makeObjectName(stmt->relname, column->colname, NULL); constraints = lappend(constraints, constraint); break; default: elog(ERROR, "parser: unrecognized constraint (internal error)"); break; } } break; case T_Constraint: constraint = (Constraint *) element; switch (constraint->contype) { case CONSTR_PRIMARY: if (constraint->name == NULL) constraint->name = makeObjectName(stmt->relname, NULL, "pkey"); dlist = lappend(dlist, constraint); break; case CONSTR_UNIQUE: dlist = lappend(dlist, constraint); break; case CONSTR_CHECK: constraints = lappend(constraints, constraint); break; case CONSTR_NOTNULL: case CONSTR_DEFAULT: elog(ERROR, "parser: illegal context for constraint (internal error)"); break; default: elog(ERROR, "parser: unrecognized constraint (internal error)"); break; } break; case T_FkConstraint: /* ---------- * Table level FOREIGN KEY constraints are already complete. * Just remember for later. * ---------- */ fkconstraints = lappend(fkconstraints, element); break; default: elog(ERROR, "parser: unrecognized node (internal error)"); } } stmt->tableElts = columns; stmt->constraints = constraints; /* Now run through the "deferred list" to complete the query transformation. * For PRIMARY KEYs, mark each column as NOT NULL and create an index. * For UNIQUE, create an index as for PRIMARY KEYS, but do not insist on NOT NULL. * * Note that this code does not currently look for all possible redundant cases * and either ignore or stop with warning. The create might fail later when * names for indices turn out to be duplicated, or a user might have specified * extra useless indices which might hurt performance. - thomas 1997-12-08 */ while (dlist != NIL) { constraint = lfirst(dlist); Assert(nodeTag(constraint) == T_Constraint); Assert((constraint->contype == CONSTR_PRIMARY) || (constraint->contype == CONSTR_UNIQUE)); index = makeNode(IndexStmt); index->unique = TRUE; index->primary = (constraint->contype == CONSTR_PRIMARY ? TRUE : FALSE); if (index->primary) { if (pkey != NULL) elog(ERROR, "CREATE TABLE/PRIMARY KEY multiple primary keys" " for table '%s' are not allowed", stmt->relname); pkey = (IndexStmt *) index; } if (constraint->name != NULL) index->idxname = pstrdup(constraint->name); else if (constraint->contype == CONSTR_PRIMARY) index->idxname = makeObjectName(stmt->relname, NULL, "pkey"); else index->idxname = NULL; index->relname = stmt->relname; index->accessMethod = "btree"; index->indexParams = NIL; index->withClause = NIL; index->whereClause = NULL; keys = constraint->keys; while (keys != NIL) { key = lfirst(keys); columns = stmt->tableElts; column = NULL; while (columns != NIL) { column = lfirst(columns); if (strcasecmp(column->colname, key->name) == 0) break; else column = NULL; columns = lnext(columns); } if (column == NULL) elog(ERROR, "CREATE TABLE column '%s' in key does not exist", key->name); if (constraint->contype == CONSTR_PRIMARY) column->is_not_null = TRUE; iparam = makeNode(IndexElem); iparam->name = pstrdup(column->colname); iparam->args = NIL; iparam->class = NULL; iparam->typename = NULL; index->indexParams = lappend(index->indexParams, iparam); if (index->idxname == NULL) index->idxname = CreateIndexName(stmt->relname, iparam->name, "key", ilist); keys = lnext(keys); } if (index->idxname == NULL) /* should not happen */ elog(ERROR, "CREATE TABLE: failed to make implicit index name"); ilist = lappend(ilist, index); dlist = lnext(dlist); } /* OK, now finally, if there is a primary key, then make sure that there aren't any redundant * unique indices defined on columns. This can arise if someone specifies UNIQUE explicitly * or if a SERIAL column was defined along with a table PRIMARY KEY constraint. * - thomas 1999-05-11 */ if (pkey != NULL) { dlist = ilist; ilist = NIL; while (dlist != NIL) { List *pcols, *icols; int plen, ilen; int keep = TRUE; index = lfirst(dlist); pcols = pkey->indexParams; icols = index->indexParams; plen = length(pcols); ilen = length(icols); /* Not the same as the primary key? Then we should look... */ if ((index != pkey) && (ilen == plen)) { keep = FALSE; while ((pcols != NIL) && (icols != NIL)) { IndexElem *pcol = lfirst(pcols); IndexElem *icol = lfirst(icols); char *pname = pcol->name; char *iname = icol->name; /* different names? then no match... */ if (strcmp(iname, pname) != 0) { keep = TRUE; break; } pcols = lnext(pcols); icols = lnext(icols); } } if (keep) ilist = lappend(ilist, index); dlist = lnext(dlist); } } dlist = ilist; while (dlist != NIL) { index = lfirst(dlist); elog(NOTICE, "CREATE TABLE/%s will create implicit index '%s' for table '%s'", (index->primary ? "PRIMARY KEY" : "UNIQUE"), index->idxname, stmt->relname); dlist = lnext(dlist); } q->utilityStmt = (Node *) stmt; extras_before = blist; extras_after = ilist; /* * Now process the FOREIGN KEY constraints and add appropriate * queries to the extras_after statements list. * */ if (fkconstraints != NIL) { CreateTrigStmt *fk_trigger; List *fk_attr; List *pk_attr; Ident *id; elog(NOTICE, "CREATE TABLE will create implicit trigger(s) for FOREIGN KEY check(s)"); foreach (fkclist, fkconstraints) { fkconstraint = (FkConstraint *)lfirst(fkclist); /* * If the constraint has no name, set it to * */ if (fkconstraint->constr_name == NULL) fkconstraint->constr_name = ""; /* * If the attribute list for the referenced table was * omitted, lookup for the definition of the primary key * */ if (fkconstraint->fk_attrs != NIL && fkconstraint->pk_attrs == NIL) transformFkeyGetPrimaryKey(fkconstraint); /* * Build a CREATE CONSTRAINT TRIGGER statement for the CHECK * action. * */ fk_trigger = (CreateTrigStmt *)makeNode(CreateTrigStmt); fk_trigger->trigname = fkconstraint->constr_name; fk_trigger->relname = stmt->relname; fk_trigger->funcname = "RI_FKey_check_ins"; fk_trigger->before = false; fk_trigger->row = true; fk_trigger->actions[0] = 'i'; fk_trigger->actions[1] = 'u'; fk_trigger->actions[2] = '\0'; fk_trigger->lang = NULL; fk_trigger->text = NULL; fk_trigger->attr = NIL; fk_trigger->when = NULL; fk_trigger->isconstraint = true; fk_trigger->deferrable = fkconstraint->deferrable; fk_trigger->initdeferred = fkconstraint->initdeferred; fk_trigger->constrrelname = fkconstraint->pktable_name; fk_trigger->args = NIL; fk_trigger->args = lappend(fk_trigger->args, fkconstraint->constr_name); fk_trigger->args = lappend(fk_trigger->args, stmt->relname); fk_trigger->args = lappend(fk_trigger->args, fkconstraint->pktable_name); fk_trigger->args = lappend(fk_trigger->args, fkconstraint->match_type); fk_attr = fkconstraint->fk_attrs; pk_attr = fkconstraint->pk_attrs; if (length(fk_attr) != length(pk_attr)) { elog(NOTICE, "Illegal FOREIGN KEY definition REFERENCES \"%s\"", fkconstraint->pktable_name); elog(ERROR, "number of key attributes in referenced table must be equal to foreign key"); } while (fk_attr != NIL) { id = (Ident *)lfirst(fk_attr); fk_trigger->args = lappend(fk_trigger->args, id->name); id = (Ident *)lfirst(pk_attr); fk_trigger->args = lappend(fk_trigger->args, id->name); fk_attr = lnext(fk_attr); pk_attr = lnext(pk_attr); } extras_after = lappend(extras_after, (Node *)fk_trigger); /* * Build a CREATE CONSTRAINT TRIGGER statement for the * ON DELETE action fired on the PK table !!! * */ fk_trigger = (CreateTrigStmt *)makeNode(CreateTrigStmt); fk_trigger->trigname = fkconstraint->constr_name; fk_trigger->relname = fkconstraint->pktable_name; switch ((fkconstraint->actions & FKCONSTR_ON_DELETE_MASK) >> FKCONSTR_ON_DELETE_SHIFT) { case FKCONSTR_ON_KEY_NOACTION: fk_trigger->funcname = "RI_FKey_noaction_del"; break; case FKCONSTR_ON_KEY_RESTRICT: fk_trigger->funcname = "RI_FKey_restrict_del"; break; case FKCONSTR_ON_KEY_CASCADE: fk_trigger->funcname = "RI_FKey_cascade_del"; break; case FKCONSTR_ON_KEY_SETNULL: fk_trigger->funcname = "RI_FKey_setnull_del"; break; case FKCONSTR_ON_KEY_SETDEFAULT: fk_trigger->funcname = "RI_FKey_setdefault_del"; break; default: elog(ERROR, "Only one ON DELETE action can be specified for FOREIGN KEY constraint"); break; } fk_trigger->before = false; fk_trigger->row = true; fk_trigger->actions[0] = 'd'; fk_trigger->actions[1] = '\0'; fk_trigger->lang = NULL; fk_trigger->text = NULL; fk_trigger->attr = NIL; fk_trigger->when = NULL; fk_trigger->isconstraint = true; fk_trigger->deferrable = fkconstraint->deferrable; fk_trigger->initdeferred = fkconstraint->initdeferred; fk_trigger->constrrelname = stmt->relname; fk_trigger->args = NIL; fk_trigger->args = lappend(fk_trigger->args, fkconstraint->constr_name); fk_trigger->args = lappend(fk_trigger->args, stmt->relname); fk_trigger->args = lappend(fk_trigger->args, fkconstraint->pktable_name); fk_trigger->args = lappend(fk_trigger->args, fkconstraint->match_type); fk_attr = fkconstraint->fk_attrs; pk_attr = fkconstraint->pk_attrs; while (fk_attr != NIL) { id = (Ident *)lfirst(fk_attr); fk_trigger->args = lappend(fk_trigger->args, id->name); id = (Ident *)lfirst(pk_attr); fk_trigger->args = lappend(fk_trigger->args, id->name); fk_attr = lnext(fk_attr); pk_attr = lnext(pk_attr); } extras_after = lappend(extras_after, (Node *)fk_trigger); /* * Build a CREATE CONSTRAINT TRIGGER statement for the * ON UPDATE action fired on the PK table !!! * */ fk_trigger = (CreateTrigStmt *)makeNode(CreateTrigStmt); fk_trigger->trigname = fkconstraint->constr_name; fk_trigger->relname = fkconstraint->pktable_name; switch ((fkconstraint->actions & FKCONSTR_ON_UPDATE_MASK) >> FKCONSTR_ON_UPDATE_SHIFT) { case FKCONSTR_ON_KEY_NOACTION: fk_trigger->funcname = "RI_FKey_noaction_upd"; break; case FKCONSTR_ON_KEY_RESTRICT: fk_trigger->funcname = "RI_FKey_restrict_upd"; break; case FKCONSTR_ON_KEY_CASCADE: fk_trigger->funcname = "RI_FKey_cascade_upd"; break; case FKCONSTR_ON_KEY_SETNULL: fk_trigger->funcname = "RI_FKey_setnull_upd"; break; case FKCONSTR_ON_KEY_SETDEFAULT: fk_trigger->funcname = "RI_FKey_setdefault_upd"; break; default: elog(ERROR, "Only one ON UPDATE action can be specified for FOREIGN KEY constraint"); break; } fk_trigger->before = false; fk_trigger->row = true; fk_trigger->actions[0] = 'u'; fk_trigger->actions[1] = '\0'; fk_trigger->lang = NULL; fk_trigger->text = NULL; fk_trigger->attr = NIL; fk_trigger->when = NULL; fk_trigger->isconstraint = true; fk_trigger->deferrable = fkconstraint->deferrable; fk_trigger->initdeferred = fkconstraint->initdeferred; fk_trigger->constrrelname = stmt->relname; fk_trigger->args = NIL; fk_trigger->args = lappend(fk_trigger->args, fkconstraint->constr_name); fk_trigger->args = lappend(fk_trigger->args, stmt->relname); fk_trigger->args = lappend(fk_trigger->args, fkconstraint->pktable_name); fk_trigger->args = lappend(fk_trigger->args, fkconstraint->match_type); fk_attr = fkconstraint->fk_attrs; pk_attr = fkconstraint->pk_attrs; while (fk_attr != NIL) { id = (Ident *)lfirst(fk_attr); fk_trigger->args = lappend(fk_trigger->args, id->name); id = (Ident *)lfirst(pk_attr); fk_trigger->args = lappend(fk_trigger->args, id->name); fk_attr = lnext(fk_attr); pk_attr = lnext(pk_attr); } extras_after = lappend(extras_after, (Node *)fk_trigger); } } return q; } /* transformCreateStmt() */ /* * transformIndexStmt - * transforms the qualification of the index statement */ static Query * transformIndexStmt(ParseState *pstate, IndexStmt *stmt) { Query *qry; qry = makeNode(Query); qry->commandType = CMD_UTILITY; /* take care of the where clause */ stmt->whereClause = transformWhereClause(pstate, stmt->whereClause, NULL); qry->hasSubLinks = pstate->p_hasSubLinks; stmt->rangetable = pstate->p_rtable; qry->utilityStmt = (Node *) stmt; return qry; } /* * transformExtendStmt - * transform the qualifications of the Extend Index Statement * */ static Query * transformExtendStmt(ParseState *pstate, ExtendStmt *stmt) { Query *qry; qry = makeNode(Query); qry->commandType = CMD_UTILITY; /* take care of the where clause */ stmt->whereClause = transformWhereClause(pstate, stmt->whereClause, NULL); qry->hasSubLinks = pstate->p_hasSubLinks; stmt->rangetable = pstate->p_rtable; qry->utilityStmt = (Node *) stmt; return qry; } /* * transformRuleStmt - * transform a Create Rule Statement. The actions is a list of parse * trees which is transformed into a list of query trees. */ static Query * transformRuleStmt(ParseState *pstate, RuleStmt *stmt) { Query *qry; Query *action; List *actions; qry = makeNode(Query); qry->commandType = CMD_UTILITY; /* * 'instead nothing' rules with a qualification need a query a * 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 special by the * rewrite system. */ if (stmt->actions == NIL) { Query *nothing_qry = makeNode(Query); nothing_qry->commandType = CMD_NOTHING; addRangeTableEntry(pstate, stmt->object->relname, "*CURRENT*", FALSE, FALSE, FALSE); addRangeTableEntry(pstate, stmt->object->relname, "*NEW*", FALSE, FALSE, FALSE); nothing_qry->rtable = pstate->p_rtable; stmt->actions = lappend(NIL, nothing_qry); } actions = stmt->actions; /* * transform each statment, like parse_analyze() */ while (actions != NIL) { /* * NOTE: 'CURRENT' must always have a varno equal to 1 and 'NEW' * equal to 2. */ addRangeTableEntry(pstate, stmt->object->relname, "*CURRENT*", FALSE, FALSE, FALSE); addRangeTableEntry(pstate, stmt->object->relname, "*NEW*", FALSE, FALSE, FALSE); pstate->p_last_resno = 1; pstate->p_is_rule = true; /* for expand all */ pstate->p_hasAggs = false; action = (Query *) lfirst(actions); if (action->commandType != CMD_NOTHING) lfirst(actions) = transformStmt(pstate, lfirst(actions)); actions = lnext(actions); } /* take care of the where clause */ stmt->whereClause = transformWhereClause(pstate, stmt->whereClause, NULL); qry->hasSubLinks = pstate->p_hasSubLinks; qry->utilityStmt = (Node *) stmt; return qry; } /* * transformSelectStmt - * transforms a Select Statement * */ static Query * transformSelectStmt(ParseState *pstate, SelectStmt *stmt) { Query *qry = makeNode(Query); Node *fromQual; qry->commandType = CMD_SELECT; /* set up a range table */ makeRangeTable(pstate, stmt->fromClause, &fromQual); qry->uniqueFlag = stmt->unique; qry->into = stmt->into; qry->isTemp = stmt->istemp; qry->isPortal = FALSE; qry->targetList = transformTargetList(pstate, stmt->targetList); qry->qual = transformWhereClause(pstate, stmt->whereClause, fromQual); /* Initial processing of HAVING clause is just like WHERE clause. * Additional work will be done in optimizer/plan/planner.c. */ qry->havingQual = transformWhereClause(pstate, stmt->havingClause, NULL); qry->groupClause = transformGroupClause(pstate, stmt->groupClause, qry->targetList); qry->sortClause = transformSortClause(pstate, stmt->sortClause, qry->targetList, qry->uniqueFlag); qry->hasSubLinks = pstate->p_hasSubLinks; qry->hasAggs = pstate->p_hasAggs; if (pstate->p_hasAggs || qry->groupClause || qry->havingQual) parseCheckAggregates(pstate, qry); /* * The INSERT INTO ... SELECT ... could have a UNION in child, so * unionClause may be false */ qry->unionall = stmt->unionall; /* * Just hand through the unionClause and intersectClause. We will * handle it in the function Except_Intersect_Rewrite() */ qry->unionClause = stmt->unionClause; qry->intersectClause = stmt->intersectClause; qry->rtable = pstate->p_rtable; if (stmt->forUpdate != NULL) transformForUpdate(qry, stmt->forUpdate); return (Query *) qry; } /* * transformUpdateStmt - * transforms an update statement * */ static Query * transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt) { Query *qry = makeNode(Query); List *origTargetList; List *tl; qry->commandType = CMD_UPDATE; pstate->p_is_update = true; /* * the FROM clause is non-standard SQL syntax. We used to be able to * do this with REPLACE in POSTQUEL so we keep the feature. */ makeRangeTable(pstate, stmt->fromClause, NULL); setTargetTable(pstate, stmt->relname); qry->targetList = transformTargetList(pstate, stmt->targetList); qry->qual = transformWhereClause(pstate, stmt->whereClause, NULL); qry->hasSubLinks = pstate->p_hasSubLinks; qry->rtable = pstate->p_rtable; qry->resultRelation = refnameRangeTablePosn(pstate, stmt->relname, NULL); qry->hasAggs = pstate->p_hasAggs; if (pstate->p_hasAggs) parseCheckAggregates(pstate, qry); /* * Now we are done with SELECT-like processing, and can get on with * transforming the target list to match the UPDATE target columns. */ /* Prepare to assign non-conflicting resnos to resjunk attributes */ if (pstate->p_last_resno <= pstate->p_target_relation->rd_rel->relnatts) pstate->p_last_resno = pstate->p_target_relation->rd_rel->relnatts + 1; /* Prepare non-junk columns for assignment to target table */ origTargetList = stmt->targetList; foreach(tl, qry->targetList) { TargetEntry *tle = (TargetEntry *) lfirst(tl); Resdom *resnode = tle->resdom; ResTarget *origTarget; if (resnode->resjunk) { /* Resjunk nodes need no additional processing, but be sure they * have names and resnos that do not match any target columns; * else rewriter or planner might get confused. */ resnode->resname = "?resjunk?"; resnode->resno = (AttrNumber) pstate->p_last_resno++; continue; } if (origTargetList == NIL) elog(ERROR, "UPDATE target count mismatch --- internal error"); origTarget = (ResTarget *) lfirst(origTargetList); updateTargetListEntry(pstate, tle, origTarget->name, attnameAttNum(pstate->p_target_relation, origTarget->name), origTarget->indirection); origTargetList = lnext(origTargetList); } if (origTargetList != NIL) elog(ERROR, "UPDATE target count mismatch --- internal error"); return (Query *) qry; } /* * transformCursorStmt - * transform a Create Cursor Statement * */ static Query * transformCursorStmt(ParseState *pstate, SelectStmt *stmt) { Query *qry; qry = transformSelectStmt(pstate, stmt); qry->into = stmt->portalname; qry->isTemp = stmt->istemp; qry->isPortal = TRUE; qry->isBinary = stmt->binary; /* internal portal */ return qry; } /* This function steps through the tree * built up by the select_w_o_sort rule * and builds a list of all SelectStmt Nodes found * The built up list is handed back in **select_list. * If one of the SelectStmt Nodes has the 'unionall' flag * set to true *unionall_present hands back 'true' */ void create_select_list(Node *ptr, List **select_list, bool *unionall_present) { if (IsA(ptr, SelectStmt)) { *select_list = lappend(*select_list, ptr); if (((SelectStmt *) ptr)->unionall == TRUE) *unionall_present = TRUE; return; } /* Recursively call for all arguments. A NOT expr has no lexpr! */ if (((A_Expr *) ptr)->lexpr != NULL) create_select_list(((A_Expr *) ptr)->lexpr, select_list, unionall_present); create_select_list(((A_Expr *) ptr)->rexpr, select_list, unionall_present); } /* Changes the A_Expr Nodes to Expr Nodes and exchanges ANDs and ORs. * The reason for the exchange is easy: We implement INTERSECTs and EXCEPTs * by rewriting these queries to semantically equivalent queries that use * IN and NOT IN subselects. To be able to use all three operations * (UNIONs INTERSECTs and EXCEPTs) in one complex query we have to * translate the queries into Disjunctive Normal Form (DNF). Unfortunately * there is no function 'dnfify' but there is a function 'cnfify' * which produces DNF when we exchange ANDs and ORs before calling * 'cnfify' and exchange them back in the result. * * If an EXCEPT or INTERSECT is present *intersect_present * hands back 'true' */ Node * A_Expr_to_Expr(Node *ptr, bool *intersect_present) { Node *result = NULL; switch (nodeTag(ptr)) { case T_A_Expr: { A_Expr *a = (A_Expr *) ptr; switch (a->oper) { case AND: { Expr *expr = makeNode(Expr); Node *lexpr = A_Expr_to_Expr(((A_Expr *) ptr)->lexpr, intersect_present); Node *rexpr = A_Expr_to_Expr(((A_Expr *) ptr)->rexpr, intersect_present); *intersect_present = TRUE; expr->typeOid = BOOLOID; expr->opType = OR_EXPR; expr->args = makeList(lexpr, rexpr, -1); result = (Node *) expr; break; } case OR: { Expr *expr = makeNode(Expr); Node *lexpr = A_Expr_to_Expr(((A_Expr *) ptr)->lexpr, intersect_present); Node *rexpr = A_Expr_to_Expr(((A_Expr *) ptr)->rexpr, intersect_present); expr->typeOid = BOOLOID; expr->opType = AND_EXPR; expr->args = makeList(lexpr, rexpr, -1); result = (Node *) expr; break; } case NOT: { Expr *expr = makeNode(Expr); Node *rexpr = A_Expr_to_Expr(((A_Expr *) ptr)->rexpr, intersect_present); expr->typeOid = BOOLOID; expr->opType = NOT_EXPR; expr->args = makeList(rexpr, -1); result = (Node *) expr; break; } } break; } default: result = ptr; } return result; } void CheckSelectForUpdate(Query *qry) { if (qry->unionClause != NULL) elog(ERROR, "SELECT FOR UPDATE is not allowed with UNION/INTERSECT/EXCEPT clause"); if (qry->uniqueFlag != NULL) elog(ERROR, "SELECT FOR UPDATE is not allowed with DISTINCT clause"); if (qry->groupClause != NULL) elog(ERROR, "SELECT FOR UPDATE is not allowed with GROUP BY clause"); if (qry->hasAggs) elog(ERROR, "SELECT FOR UPDATE is not allowed with AGGREGATE"); } static void transformForUpdate(Query *qry, List *forUpdate) { List *rowMark = NULL; RowMark *newrm; List *l; Index i; CheckSelectForUpdate(qry); if (lfirst(forUpdate) == NULL) /* all tables */ { i = 1; foreach(l, qry->rtable) { newrm = makeNode(RowMark); newrm->rti = i++; newrm->info = ROW_MARK_FOR_UPDATE | ROW_ACL_FOR_UPDATE; rowMark = lappend(rowMark, newrm); } qry->rowMark = nconc(qry->rowMark, rowMark); return; } foreach(l, forUpdate) { List *l2; List *l3; i = 1; foreach(l2, qry->rtable) { if (strcmp(((RangeTblEntry *) lfirst(l2))->refname, lfirst(l)) == 0) { foreach(l3, rowMark) { if (((RowMark *) lfirst(l3))->rti == i) /* duplicate */ break; } if (l3 == NULL) { newrm = makeNode(RowMark); newrm->rti = i; newrm->info = ROW_MARK_FOR_UPDATE | ROW_ACL_FOR_UPDATE; rowMark = lappend(rowMark, newrm); } break; } i++; } if (l2 == NULL) elog(ERROR, "FOR UPDATE: relation %s not found in FROM clause", strVal(lfirst(l))); } qry->rowMark = rowMark; return; } /* * transformFkeyGetPrimaryKey - * * Try to find the primary key attributes of a referenced table if * the column list in the REFERENCES specification was omitted. * */ static void transformFkeyGetPrimaryKey(FkConstraint *fkconstraint) { Relation pkrel; Form_pg_attribute *pkrel_attrs; Relation indexRd; HeapScanDesc indexSd; ScanKeyData key; HeapTuple indexTup; Form_pg_index indexStruct = NULL; Ident *pkattr; int pkattno; int i; /* ---------- * Open the referenced table and get the attributes list * ---------- */ pkrel = heap_openr(fkconstraint->pktable_name, AccessShareLock); if (pkrel == NULL) elog(ERROR, "referenced table \"%s\" not found", fkconstraint->pktable_name); pkrel_attrs = pkrel->rd_att->attrs; /* ---------- * Open pg_index and begin a scan for all indices defined on * the referenced table * ---------- */ indexRd = heap_openr(IndexRelationName, AccessShareLock); ScanKeyEntryInitialize(&key, 0, Anum_pg_index_indrelid, F_OIDEQ, ObjectIdGetDatum(pkrel->rd_id)); indexSd = heap_beginscan(indexRd, /* scan desc */ false, /* scan backward flag */ SnapshotNow, /* NOW snapshot */ 1, /* number scan keys */ &key); /* scan keys */ /* ---------- * Fetch the index with indisprimary == true * ---------- */ while (HeapTupleIsValid(indexTup = heap_getnext(indexSd, 0))) { indexStruct = (Form_pg_index) GETSTRUCT(indexTup); if (indexStruct->indisprimary) { break; } } /* ---------- * Check that we found it * ---------- */ if (!HeapTupleIsValid(indexTup)) elog(ERROR, "PRIMARY KEY for referenced table \"%s\" not found", fkconstraint->pktable_name); /* ---------- * Now build the list of PK attributes from the indkey definition * using the attribute names of the PK relation descriptor * ---------- */ for (i = 0; i < INDEX_MAX_KEYS && indexStruct->indkey[i] != 0; i++) { pkattno = indexStruct->indkey[i]; pkattr = (Ident *)makeNode(Ident); pkattr->name = nameout(&(pkrel_attrs[pkattno - 1]->attname)); pkattr->indirection = NIL; pkattr->isRel = false; fkconstraint->pk_attrs = lappend(fkconstraint->pk_attrs, pkattr); } /* ---------- * End index scan and close relations * ---------- */ heap_endscan(indexSd); heap_close(indexRd, AccessShareLock); heap_close(pkrel, AccessShareLock); }