diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index 95e5ddbc9d..f3158050d9 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -3,40 +3,40 @@ * preptlist.c * Routines to preprocess the parse tree target list * + * This module takes care of altering the query targetlist as needed for + * INSERT, UPDATE, and DELETE queries. For INSERT and UPDATE queries, + * the targetlist must contain an entry for each attribute of the target + * relation in the correct order. For both UPDATE and DELETE queries, + * we need a junk targetlist entry holding the CTID attribute --- the + * executor relies on this to find the tuple to be replaced/deleted. + * + * * Copyright (c) 1994, Regents of the University of California * - * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/prep/preptlist.c,v 1.31 1999/08/22 20:14:51 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/prep/preptlist.c,v 1.32 1999/10/30 23:06:32 tgl Exp $ * *------------------------------------------------------------------------- */ + #include "postgres.h" +#include "access/heapam.h" #include "catalog/pg_type.h" #include "nodes/makefuncs.h" -#include "optimizer/clauses.h" #include "optimizer/prep.h" #include "parser/parsetree.h" #include "utils/lsyscache.h" -#include "utils/syscache.h" -static List *expand_targetlist(List *tlist, Oid relid, int command_type, - Index result_relation); -static List *replace_matching_resname(List *new_tlist, - List *old_tlist); -static List *new_relation_targetlist(Oid relid, Index rt_index, - NodeTag node_type); + +static List *expand_targetlist(List *tlist, int command_type, + Index result_relation, List *range_table); /* * preprocess_targetlist * Driver for preprocessing the parse tree targetlist. * - * 1. Deal with appends and replaces by filling missing attributes - * in the target list. - * 2. Reset operator OIDs to the appropriate regproc ids. - * * Returns the new targetlist. */ List * @@ -45,53 +45,49 @@ preprocess_targetlist(List *tlist, Index result_relation, List *range_table) { - Oid relid = InvalidOid; - List *expanded_tlist; - List *t_list; - - if (result_relation >= 1 && command_type != CMD_SELECT) - relid = getrelid(result_relation, range_table); - /* * for heap_formtuple to work, the targetlist must match the exact - * order of the attributes. We also need to fill in the missing - * attributes here. -ay 10/94 + * order of the attributes. We also need to fill in any missing + * attributes. -ay 10/94 */ - expanded_tlist = expand_targetlist(tlist, relid, command_type, result_relation); + if (command_type == CMD_INSERT || command_type == CMD_UPDATE) + tlist = expand_targetlist(tlist, command_type, + result_relation, range_table); - t_list = copyObject(expanded_tlist); - - /* ------------------ - * for "replace" or "delete" queries, add ctid of the result - * relation into the target list so that the ctid can get - * propogate through the execution and in the end ExecReplace() - * will find the right tuple to replace or delete. This - * extra field will be removed in ExecReplace(). - * For convinient, we append this extra field to the end of - * the target list. - * ------------------ + /* + * for "update" and "delete" queries, add ctid of the result + * relation into the target list so that the ctid will propagate + * through execution and ExecutePlan() will be able to identify + * the right tuple to replace or delete. This extra field is + * marked "junk" so that it is not stored back into the tuple. */ if (command_type == CMD_UPDATE || command_type == CMD_DELETE) { - TargetEntry *ctid; Resdom *resdom; Var *var; - resdom = makeResdom(length(t_list) + 1, + resdom = makeResdom(length(tlist) + 1, TIDOID, -1, - "ctid", + pstrdup("ctid"), 0, 0, true); - var = makeVar(result_relation, -1, TIDOID, -1, 0); + var = makeVar(result_relation, SelfItemPointerAttributeNumber, + TIDOID, -1, 0); - ctid = makeTargetEntry(resdom, (Node *) var); - t_list = lappend(t_list, ctid); + /* For an UPDATE, expand_targetlist already created a fresh tlist. + * For DELETE, better do a listCopy so that we don't destructively + * modify the original tlist (is this really necessary?). + */ + if (command_type == CMD_DELETE) + tlist = listCopy(tlist); + + tlist = lappend(tlist, makeTargetEntry(resdom, (Node *) var)); } - return t_list; + return tlist; } /***************************************************************************** @@ -103,201 +99,98 @@ preprocess_targetlist(List *tlist, /* * expand_targetlist * Given a target list as generated by the parser and a result relation, - * add targetlist entries for the attributes which have not been used. - * - * XXX This code is only supposed to work with unnested relations. - * - * 'tlist' is the original target list - * 'relid' is the relid of the result relation - * 'command' is the update command - * - * Returns the expanded target list, sorted in resno order. + * add targetlist entries for any missing attributes, and order the + * non-junk attributes in proper field order. */ static List * -expand_targetlist(List *tlist, - Oid relid, - int command_type, - Index result_relation) +expand_targetlist(List *tlist, int command_type, + Index result_relation, List *range_table) { - NodeTag node_type = T_Invalid; - - switch (command_type) - { - case CMD_INSERT: - node_type = (NodeTag) T_Const; - break; - case CMD_UPDATE: - node_type = (NodeTag) T_Var; - break; - } - - if (node_type != T_Invalid) - { - List *ntlist = new_relation_targetlist(relid, - result_relation, - node_type); - - return replace_matching_resname(ntlist, tlist); - } - else - return tlist; - -} - - -static List * -replace_matching_resname(List *new_tlist, List *old_tlist) -{ - List *temp, - *i; - List *t_list = NIL; - - foreach(i, new_tlist) - { - TargetEntry *new_tle = (TargetEntry *) lfirst(i); - TargetEntry *matching_old_tl = NULL; - - foreach(temp, old_tlist) - { - TargetEntry *old_tle = (TargetEntry *) lfirst(temp); - - if (!strcmp(old_tle->resdom->resname, - new_tle->resdom->resname)) - { - matching_old_tl = old_tle; - break; - } - } - - if (matching_old_tl) - { - /* XXX safe to modify old resdom? */ - matching_old_tl->resdom->resno = new_tle->resdom->resno; - t_list = lappend(t_list, matching_old_tl); - } - else - t_list = lappend(t_list, new_tle); - } + int old_tlist_len = length(tlist); + List *new_tlist = NIL; + bool *tlistentry_used; + Relation rel; + int attrno, + numattrs, + old_tlist_index; + List *temp; /* - * It is possible that 'old_tlist' has some negative attributes (i.e. - * negative resnos). This only happens if this is a replace/append - * command and we explicitly specify a system attribute. Of course - * this is not a very good idea if this is a user query, but on the - * other hand the rule manager uses this mechanism to replace rule - * locks. - * - * So, copy all these entries to the end of the target list and set their - * 'resjunk' value to true to show that these are special attributes - * and have to be treated specially by the executor! + * Keep a map of which tlist items we have transferred to new list. + * +1 here keeps palloc from complaining if old_tlist_len=0. */ - foreach(temp, old_tlist) + tlistentry_used = (bool *) palloc((old_tlist_len+1) * sizeof(bool)); + memset(tlistentry_used, 0, (old_tlist_len+1) * sizeof(bool)); + + /* + * Scan the tuple description in the relation's relcache entry to make + * sure we have all the user attributes in the right order. + */ + rel = heap_open(getrelid(result_relation, range_table), AccessShareLock); + + numattrs = RelationGetNumberOfAttributes(rel); + + for (attrno = 1; attrno <= numattrs; attrno++) { - TargetEntry *old_tle, - *new_tl; + Form_pg_attribute att_tup = rel->rd_att->attrs[attrno-1]; + char *attrname = att_tup->attname.data; + TargetEntry *new_tle = NULL; - old_tle = lfirst(temp); - if (old_tle->resdom->resno < 0) - { - Resdom *newresdom; - - newresdom = (Resdom *) copyObject((Node *) old_tle->resdom); - newresdom->resno = length(t_list) + 1; - newresdom->resjunk = true; - new_tl = makeTargetEntry(newresdom, old_tle->expr); - t_list = lappend(t_list, new_tl); - } /* - * Also it is possible that the parser or rewriter added some junk - * attributes to hold ORDER/GROUP BY expressions which are not part of - * the result attributes. We can simply identify them by looking - * at the ressortgroupref in the TLE's resdom, which is a unique - * number telling which TLE belongs to which Sort/GroupClause. + * We match targetlist entries to attributes using the resname. */ - else if (old_tle->resdom->ressortgroupref > 0) + old_tlist_index = 0; + foreach(temp, tlist) { - bool already_there = false; + TargetEntry *old_tle = (TargetEntry *) lfirst(temp); + Resdom *resdom = old_tle->resdom; - /* - * Check if the tle is already in the new list - */ - foreach(i, t_list) + if (! tlistentry_used[old_tlist_index] && + strcmp(resdom->resname, attrname) == 0 && + ! resdom->resjunk) { - TargetEntry *new_tle = (TargetEntry *) lfirst(i); - - if (new_tle->resdom->ressortgroupref == - old_tle->resdom->ressortgroupref) + /* + * We can recycle the old TLE+resdom if right resno; else + * make a new one to avoid modifying the old tlist structure. + * (Is preserving old tlist actually necessary?) + */ + if (resdom->resno == attrno) { - already_there = true; - break; + new_tle = old_tle; } + else + { + resdom = (Resdom *) copyObject((Node *) resdom); + resdom->resno = attrno; + new_tle = makeTargetEntry(resdom, old_tle->expr); + } + tlistentry_used[old_tlist_index] = true; + break; } + old_tlist_index++; + } + if (new_tle == NULL) + { /* - * If not, add it and make sure it is now a junk attribute + * Didn't find a matching tlist entry, so make one. + * + * For INSERT, generate a constant of the default value for + * the attribute type, or NULL if no default value. + * + * For UPDATE, generate a Var reference to the existing value + * of the attribute, so that it gets copied to the new tuple. */ - if (!already_there) + Oid atttype = att_tup->atttypid; + int32 atttypmod = att_tup->atttypmod; + + switch (command_type) { - Resdom *newresdom; - - newresdom = (Resdom *) copyObject((Node *) old_tle->resdom); - newresdom->resno = length(t_list) + 1; - newresdom->resjunk = true; - new_tl = makeTargetEntry(newresdom, old_tle->expr); - t_list = lappend(t_list, new_tl); - } - } - } - - return t_list; -} - -/* - * new_relation_targetlist - * Generate a targetlist for the relation with relation OID 'relid' - * and rangetable index 'rt_index'. - * - * Returns the new targetlist. - */ -static List * -new_relation_targetlist(Oid relid, Index rt_index, NodeTag node_type) -{ - List *t_list = NIL; - int natts = get_relnatts(relid); - AttrNumber attno; - - for (attno = 1; attno <= natts; attno++) - { - HeapTuple tp; - Form_pg_attribute att_tup; - char *attname; - Oid atttype; - int32 atttypmod; - bool attisset; - - tp = SearchSysCacheTuple(ATTNUM, - ObjectIdGetDatum(relid), - UInt16GetDatum(attno), - 0, 0); - if (! HeapTupleIsValid(tp)) - { - elog(ERROR, "new_relation_targetlist: no attribute tuple %u %d", - relid, attno); - } - att_tup = (Form_pg_attribute) GETSTRUCT(tp); - attname = pstrdup(att_tup->attname.data); - atttype = att_tup->atttypid; - atttypmod = att_tup->atttypmod; - attisset = att_tup->attisset; - - switch (node_type) - { - case T_Const: /* INSERT command */ + case CMD_INSERT: { Datum typedefault = get_typdefault(atttype); int typlen; Const *temp_const; - TargetEntry *temp_tle; if (typedefault == PointerGetDatum(NULL)) typlen = 0; @@ -308,7 +201,7 @@ new_relation_targetlist(Oid relid, Index rt_index, NodeTag node_type) * any set attribute is the size of the OID used to * represent it. */ - if (attisset) + if (att_tup->attisset) typlen = get_typlen(OIDOID); else typlen = get_typlen(atttype); @@ -322,40 +215,69 @@ new_relation_targetlist(Oid relid, Index rt_index, NodeTag node_type) false, /* not a set */ false); - temp_tle = makeTargetEntry(makeResdom(attno, - atttype, - -1, - attname, - 0, - (Oid) 0, - false), - (Node *) temp_const); - t_list = lappend(t_list, temp_tle); + new_tle = makeTargetEntry(makeResdom(attrno, + atttype, + -1, + pstrdup(attrname), + 0, + (Oid) 0, + false), + (Node *) temp_const); break; } - case T_Var: /* UPDATE command */ + case CMD_UPDATE: { Var *temp_var; - TargetEntry *temp_tle; - temp_var = makeVar(rt_index, attno, atttype, + temp_var = makeVar(result_relation, attrno, atttype, atttypmod, 0); - temp_tle = makeTargetEntry(makeResdom(attno, - atttype, - atttypmod, - attname, - 0, - (Oid) 0, - false), - (Node *) temp_var); - t_list = lappend(t_list, temp_tle); + new_tle = makeTargetEntry(makeResdom(attrno, + atttype, + atttypmod, + pstrdup(attrname), + 0, + (Oid) 0, + false), + (Node *) temp_var); break; } - default: /* do nothing */ - break; + default: + elog(ERROR, "expand_targetlist: unexpected command_type"); + break; + } } + + new_tlist = lappend(new_tlist, new_tle); } - return t_list; + /* + * Copy all unprocessed tlist entries to the end of the new tlist, + * making sure they are marked resjunk = true. Typical junk entries + * include ORDER BY or GROUP BY expressions (are these actually possible + * in an INSERT or UPDATE?), system attribute references, etc. + */ + old_tlist_index = 0; + foreach(temp, tlist) + { + TargetEntry *old_tle = (TargetEntry *) lfirst(temp); + + if (! tlistentry_used[old_tlist_index]) + { + Resdom *resdom; + + resdom = (Resdom *) copyObject((Node *) old_tle->resdom); + resdom->resno = attrno++; + resdom->resjunk = true; + new_tlist = lappend(new_tlist, + makeTargetEntry(resdom, old_tle->expr)); + } + old_tlist_index++; + } + + heap_close(rel, AccessShareLock); + + pfree(tlistentry_used); + + return new_tlist; }