postgresql/src/backend/optimizer/prep/preptlist.c

386 lines
11 KiB
C
Raw Normal View History

/*-------------------------------------------------------------------------
*
* preptlist.c
* Routines to preprocess the parse tree target list
*
* For INSERT and UPDATE queries, the targetlist must contain an entry for
* each attribute of the target relation in the correct order. For all query
* types, we may need to add junk tlist entries for Vars used in the RETURNING
* list and row ID information needed for SELECT FOR UPDATE locking and/or
* EvalPlanQual checking.
*
* NOTE: the rewriter's rewriteTargetListIU and rewriteTargetListUD
* routines also do preprocessing of the targetlist. The division of labor
* between here and there is a bit arbitrary and historical.
*
*
* Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
2010-09-20 22:08:53 +02:00
* src/backend/optimizer/prep/preptlist.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/heapam.h"
#include "access/sysattr.h"
1999-07-16 07:00:38 +02:00
#include "catalog/pg_type.h"
#include "nodes/makefuncs.h"
#include "optimizer/prep.h"
#include "optimizer/tlist.h"
1999-07-16 07:00:38 +02:00
#include "parser/parsetree.h"
#include "parser/parse_coerce.h"
#include "utils/rel.h"
static List *expand_targetlist(List *tlist, int command_type,
Index result_relation, List *range_table);
/*
* preprocess_targetlist
* Driver for preprocessing the parse tree targetlist.
*
* Returns the new targetlist.
*/
List *
preprocess_targetlist(PlannerInfo *root, List *tlist)
{
2005-10-15 04:49:52 +02:00
Query *parse = root->parse;
int result_relation = parse->resultRelation;
List *range_table = parse->rtable;
CmdType command_type = parse->commandType;
ListCell *lc;
/*
2001-03-22 05:01:46 +01:00
* Sanity check: if there is a result relation, it'd better be a real
* relation not a subquery. Else parser or rewriter messed up.
*/
if (result_relation)
{
RangeTblEntry *rte = rt_fetch(result_relation, range_table);
if (rte->subquery != NULL || rte->relid == InvalidOid)
elog(ERROR, "subquery cannot be result relation");
}
/*
* for heap_form_tuple to work, the targetlist must match the exact order
* of the attributes. We also need to fill in any missing attributes. -ay
* 10/94
*/
if (command_type == CMD_INSERT || command_type == CMD_UPDATE)
tlist = expand_targetlist(tlist, command_type,
result_relation, range_table);
/*
2010-02-26 03:01:40 +01:00
* Add necessary junk columns for rowmarked rels. These values are needed
* for locking of rels selected FOR UPDATE/SHARE, and to do EvalPlanQual
* rechecking. See comments for PlanRowMark in plannodes.h.
*/
foreach(lc, root->rowMarks)
{
PlanRowMark *rc = (PlanRowMark *) lfirst(lc);
Var *var;
char resname[32];
TargetEntry *tle;
/* child rels use the same junk attrs as their parents */
if (rc->rti != rc->prti)
continue;
2015-03-15 23:41:47 +01:00
if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY))
{
2015-03-15 23:41:47 +01:00
/* Need to fetch TID */
var = makeVar(rc->rti,
SelfItemPointerAttributeNumber,
TIDOID,
-1,
InvalidOid,
0);
snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId);
tle = makeTargetEntry((Expr *) var,
list_length(tlist) + 1,
pstrdup(resname),
true);
tlist = lappend(tlist, tle);
/* if parent of inheritance tree, need the tableoid too */
if (rc->isParent)
{
var = makeVar(rc->rti,
TableOidAttributeNumber,
OIDOID,
-1,
InvalidOid,
0);
snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId);
tle = makeTargetEntry((Expr *) var,
list_length(tlist) + 1,
pstrdup(resname),
true);
tlist = lappend(tlist, tle);
}
}
2015-03-15 23:41:47 +01:00
if (rc->allMarkTypes & (1 << ROW_MARK_COPY))
{
2015-03-15 23:41:47 +01:00
/* Need the whole row as a junk var */
var = makeWholeRowVar(rt_fetch(rc->rti, range_table),
rc->rti,
0,
false);
snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId);
tle = makeTargetEntry((Expr *) var,
list_length(tlist) + 1,
pstrdup(resname),
true);
tlist = lappend(tlist, tle);
}
}
/*
* If the query has a RETURNING list, add resjunk entries for any Vars
* used in RETURNING that belong to other relations. We need to do this
* to make these Vars available for the RETURNING calculation. Vars that
2006-10-04 02:30:14 +02:00
* belong to the result rel don't need to be added, because they will be
* made to refer to the actual heap tuple.
*/
if (parse->returningList && list_length(parse->rtable) > 1)
{
2006-10-04 02:30:14 +02:00
List *vars;
ListCell *l;
vars = pull_var_clause((Node *) parse->returningList,
Avoid listing ungrouped Vars in the targetlist of Agg-underneath-Window. Regular aggregate functions in combination with, or within the arguments of, window functions are OK per spec; they have the semantics that the aggregate output rows are computed and then we run the window functions over that row set. (Thus, this combination is not really useful unless there's a GROUP BY so that more than one aggregate output row is possible.) The case without GROUP BY could fail, as recently reported by Jeff Davis, because sloppy construction of the Agg node's targetlist resulted in extra references to possibly-ungrouped Vars appearing outside the aggregate function calls themselves. See the added regression test case for an example. Fixing this requires modifying the API of flatten_tlist and its underlying function pull_var_clause. I chose to make pull_var_clause's API for aggregates identical to what it was already doing for placeholders, since the useful behaviors turn out to be the same (error, report node as-is, or recurse into it). I also tightened the error checking in this area a bit: if it was ever valid to see an uplevel Var, Aggref, or PlaceHolderVar here, that was a long time ago, so complain instead of ignoring them. Backpatch into 9.1. The failure exists in 8.4 and 9.0 as well, but seeing that it only occurs in a basically-useless corner case, it doesn't seem worth the risks of changing a function API in a minor release. There might be third-party code using pull_var_clause.
2011-07-13 00:23:55 +02:00
PVC_RECURSE_AGGREGATES,
PVC_INCLUDE_PLACEHOLDERS);
foreach(l, vars)
{
Var *var = (Var *) lfirst(l);
TargetEntry *tle;
if (IsA(var, Var) &&
var->varno == result_relation)
continue; /* don't need it */
if (tlist_member((Node *) var, tlist))
continue; /* already got it */
tle = makeTargetEntry((Expr *) var,
list_length(tlist) + 1,
NULL,
true);
tlist = lappend(tlist, tle);
}
list_free(vars);
}
return tlist;
}
/*****************************************************************************
*
* TARGETLIST EXPANSION
*
*****************************************************************************/
/*
* expand_targetlist
* Given a target list as generated by the parser and a result relation,
* add targetlist entries for any missing attributes, and ensure the
* non-junk attributes appear in proper field order.
*/
static List *
expand_targetlist(List *tlist, int command_type,
Index result_relation, List *range_table)
{
List *new_tlist = NIL;
ListCell *tlist_item;
Relation rel;
int attrno,
numattrs;
tlist_item = list_head(tlist);
/*
2005-10-15 04:49:52 +02:00
* The rewriter should have already ensured that the TLEs are in correct
* order; but we have to insert TLEs for any missing attributes.
*
* Scan the tuple description in the relation's relcache entry to make
* sure we have all the user attributes in the right order. We assume
* that the rewriter already acquired at least AccessShareLock on the
* relation, so we need no lock here.
*/
rel = heap_open(getrelid(result_relation, range_table), NoLock);
numattrs = RelationGetNumberOfAttributes(rel);
for (attrno = 1; attrno <= numattrs; attrno++)
{
Form_pg_attribute att_tup = rel->rd_att->attrs[attrno - 1];
TargetEntry *new_tle = NULL;
if (tlist_item != NULL)
{
TargetEntry *old_tle = (TargetEntry *) lfirst(tlist_item);
if (!old_tle->resjunk && old_tle->resno == attrno)
{
new_tle = old_tle;
tlist_item = lnext(tlist_item);
}
}
if (new_tle == NULL)
{
/*
* Didn't find a matching tlist entry, so make one.
*
2002-09-04 22:31:48 +02:00
* For INSERT, generate a NULL constant. (We assume the rewriter
2005-10-15 04:49:52 +02:00
* would have inserted any available default value.) Also, if the
* column isn't dropped, apply any domain constraints that might
* exist --- this is to catch domain NOT NULL.
*
* For UPDATE, generate a Var reference to the existing value of
* the attribute, so that it gets copied to the new tuple. But
2005-10-15 04:49:52 +02:00
* generate a NULL for dropped columns (we want to drop any old
* values).
*
* When generating a NULL constant for a dropped column, we label
* it INT4 (any other guaranteed-to-exist datatype would do as
* well). We can't label it with the dropped column's datatype
* since that might not exist anymore. It does not really matter
* what we claim the type is, since NULL is NULL --- its
* representation is datatype-independent. This could perhaps
* confuse code comparing the finished plan to the target
* relation, however.
*/
Oid atttype = att_tup->atttypid;
int32 atttypmod = att_tup->atttypmod;
Oid attcollation = att_tup->attcollation;
Node *new_expr;
switch (command_type)
{
case CMD_INSERT:
if (!att_tup->attisdropped)
{
new_expr = (Node *) makeConst(atttype,
-1,
attcollation,
att_tup->attlen,
(Datum) 0,
2003-08-04 02:43:34 +02:00
true, /* isnull */
att_tup->attbyval);
new_expr = coerce_to_domain(new_expr,
InvalidOid, -1,
atttype,
COERCE_IMPLICIT_CAST,
-1,
false,
false);
}
else
{
/* Insert NULL for dropped column */
new_expr = (Node *) makeConst(INT4OID,
-1,
InvalidOid,
sizeof(int32),
(Datum) 0,
2003-08-04 02:43:34 +02:00
true, /* isnull */
true /* byval */ );
}
break;
case CMD_UPDATE:
if (!att_tup->attisdropped)
{
new_expr = (Node *) makeVar(result_relation,
attrno,
atttype,
atttypmod,
attcollation,
0);
}
else
{
/* Insert NULL for dropped column */
new_expr = (Node *) makeConst(INT4OID,
-1,
InvalidOid,
sizeof(int32),
(Datum) 0,
2003-08-04 02:43:34 +02:00
true, /* isnull */
true /* byval */ );
}
break;
default:
elog(ERROR, "unrecognized command_type: %d",
(int) command_type);
new_expr = NULL; /* keep compiler quiet */
break;
}
new_tle = makeTargetEntry((Expr *) new_expr,
attrno,
2002-09-04 22:31:48 +02:00
pstrdup(NameStr(att_tup->attname)),
false);
}
new_tlist = lappend(new_tlist, new_tle);
}
/*
2005-10-15 04:49:52 +02:00
* The remaining tlist entries should be resjunk; append them all to the
* end of the new tlist, making sure they have resnos higher than the last
* real attribute. (Note: although the rewriter already did such
2005-10-15 04:49:52 +02:00
* renumbering, we have to do it again here in case we are doing an UPDATE
* in a table with dropped columns, or an inheritance child table with
* extra columns.)
*/
while (tlist_item)
{
TargetEntry *old_tle = (TargetEntry *) lfirst(tlist_item);
if (!old_tle->resjunk)
elog(ERROR, "targetlist is not sorted correctly");
/* Get the resno right, but don't copy unnecessarily */
if (old_tle->resno != attrno)
{
old_tle = flatCopyTargetEntry(old_tle);
old_tle->resno = attrno;
}
new_tlist = lappend(new_tlist, old_tle);
attrno++;
tlist_item = lnext(tlist_item);
}
heap_close(rel, NoLock);
return new_tlist;
}
/*
* Locate PlanRowMark for given RT index, or return NULL if none
*
* This probably ought to be elsewhere, but there's no very good place
*/
PlanRowMark *
get_plan_rowmark(List *rowmarks, Index rtindex)
{
ListCell *l;
foreach(l, rowmarks)
{
PlanRowMark *rc = (PlanRowMark *) lfirst(l);
if (rc->rti == rtindex)
return rc;
}
return NULL;
}