* preptlist.c
* Routines to preprocess the parse tree target list
*
- * Copyright (c) 1994, Regents of the University of California
+ * 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 *
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);
-
- 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.
- * ------------------
+ if (command_type == CMD_INSERT || command_type == CMD_UPDATE)
+ tlist = expand_targetlist(tlist, command_type,
+ result_relation, range_table);
+
+ /*
+ * 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);
+
+ /* 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);
- ctid = makeTargetEntry(resdom, (Node *) var);
- t_list = lappend(t_list, ctid);
+ tlist = lappend(tlist, makeTargetEntry(resdom, (Node *) var));
}
- return t_list;
+ return 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)
-{
- 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)
+expand_targetlist(List *tlist, int command_type,
+ Index result_relation, List *range_table)
{
- 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);
+ int old_tlist_len = length(tlist);
+ List *new_tlist = NIL;
+ bool *tlistentry_used;
+ Relation rel;
+ int attrno,
+ numattrs,
+ old_tlist_index;
+ List *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);
- }
+ /*
+ * Keep a map of which tlist items we have transferred to new list.
+ * +1 here keeps palloc from complaining if old_tlist_len=0.
+ */
+ tlistentry_used = (bool *) palloc((old_tlist_len+1) * sizeof(bool));
+ memset(tlistentry_used, 0, (old_tlist_len+1) * sizeof(bool));
/*
- * 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!
+ * Scan the tuple description in the relation's relcache entry to make
+ * sure we have all the user attributes in the right order.
*/
- foreach(temp, old_tlist)
- {
- TargetEntry *old_tle,
- *new_tl;
+ rel = heap_open(getrelid(result_relation, range_table), AccessShareLock);
- old_tle = lfirst(temp);
- if (old_tle->resdom->resno < 0)
- {
- Resdom *newresdom;
+ numattrs = RelationGetNumberOfAttributes(rel);
+
+ for (attrno = 1; attrno <= numattrs; attrno++)
+ {
+ Form_pg_attribute att_tup = rel->rd_att->attrs[attrno-1];
+ char *attrname = att_tup->attname.data;
+ TargetEntry *new_tle = NULL;
- 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;
}
-
- /*
- * If not, add it and make sure it is now a junk attribute
- */
- if (!already_there)
- {
- 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);
- }
+ old_tlist_index++;
}
- }
- 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))
+ if (new_tle == NULL)
{
- 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;
+ /*
+ * 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.
+ */
+ Oid atttype = att_tup->atttypid;
+ int32 atttypmod = att_tup->atttypmod;
- switch (node_type)
- {
- case T_Const: /* INSERT command */
+ switch (command_type)
+ {
+ case CMD_INSERT:
{
Datum typedefault = get_typdefault(atttype);
int typlen;
Const *temp_const;
- TargetEntry *temp_tle;
if (typedefault == PointerGetDatum(NULL))
typlen = 0;
* 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);
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);
+ }
+
+ /*
+ * 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++;
}
- return t_list;
+ heap_close(rel, AccessShareLock);
+
+ pfree(tlistentry_used);
+
+ return new_tlist;
}