postgresql/src/backend/parser/parse_clause.c
Tom Lane 7f76eab140 Rewrite parser's handling of INSERT ... SELECT so that processing
of the SELECT part of the statement is just like a plain SELECT.  All
INSERT-specific processing happens after the SELECT parsing is done.
This eliminates many problems, e.g. INSERT ... SELECT ... GROUP BY using
the wrong column labels.  Ensure that DEFAULT clauses are coerced to
the target column type, whether or not stored clause produces the right
type.  Substantial cleanup of parser's array support.
1999-07-19 00:26:20 +00:00

754 lines
19 KiB
C

/*-------------------------------------------------------------------------
*
* parse_clause.c
* handle clauses in parser
*
* Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.42 1999/07/19 00:26:19 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/heapam.h"
#include "nodes/relation.h"
#include "parse.h"
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_expr.h"
#include "parser/parse_oper.h"
#include "parser/parse_relation.h"
#include "parser/parse_target.h"
#define ORDER_CLAUSE 0
#define GROUP_CLAUSE 1
static char *clauseText[] = {"ORDER", "GROUP"};
static TargetEntry *findTargetlistEntry(ParseState *pstate, Node *node,
List *tlist, int clause);
static void parseFromClause(ParseState *pstate, List *frmList, Node **qual);
static char *transformTableEntry(ParseState *pstate, RangeVar *r);
#ifdef ENABLE_OUTER_JOINS
static Node *transformUsingClause(ParseState *pstate, List *onList,
char *lname, char *rname);
#endif
/*
* makeRangeTable -
* Build the initial range table from the FROM clause.
*/
void
makeRangeTable(ParseState *pstate, List *frmList, Node **qual)
{
/* Currently, nothing to do except this: */
parseFromClause(pstate, frmList, qual);
}
/*
* setTargetTable
* Add the target relation of INSERT or UPDATE to the range table,
* and make the special links to it in the ParseState.
*/
void
setTargetTable(ParseState *pstate, char *relname)
{
RangeTblEntry *rte;
int sublevels_up;
if ((refnameRangeTablePosn(pstate, relname, &sublevels_up) == 0)
|| (sublevels_up != 0))
rte = addRangeTableEntry(pstate, relname, relname, FALSE, FALSE);
else
rte = refnameRangeTableEntry(pstate, relname);
/* This could only happen for multi-action rules */
if (pstate->p_target_relation != NULL)
heap_close(pstate->p_target_relation);
pstate->p_target_rangetblentry = rte;
pstate->p_target_relation = heap_open(rte->relid);
/* will close relation later */
}
/*
* transformWhereClause -
* transforms the qualification and make sure it is of type Boolean
*
* Now accept an additional argument, which is a qualification derived
* from the JOIN/ON or JOIN/USING syntax.
* - thomas 1998-12-16
*/
Node *
transformWhereClause(ParseState *pstate, Node *a_expr, Node *o_expr)
{
A_Expr *expr;
Node *qual;
if ((a_expr == NULL) && (o_expr == NULL))
return NULL; /* no qualifiers */
if ((a_expr != NULL) && (o_expr != NULL))
{
A_Expr *a = makeNode(A_Expr);
a->oper = AND;
a->opname = NULL;
a->lexpr = o_expr;
a->rexpr = a_expr;
expr = a;
}
else if (o_expr != NULL)
expr = (A_Expr *) o_expr;
else
expr = (A_Expr *) a_expr;
pstate->p_in_where_clause = true;
qual = transformExpr(pstate, (Node *) expr, EXPR_COLUMN_FIRST);
pstate->p_in_where_clause = false;
if (exprType(qual) != BOOLOID)
{
elog(ERROR, "WHERE clause must return type bool, not type %s",
typeidTypeName(exprType(qual)));
}
return qual;
}
#ifdef NOT_USED
static Attr *
makeAttr(char *relname, char *attname)
{
Attr *a = makeNode(Attr);
a->relname = relname;
a->paramNo = NULL;
a->attrs = lcons(makeString(attname), NIL);
a->indirection = NULL;
return a;
}
#endif
#ifdef ENABLE_OUTER_JOINS
/* transformUsingClause()
* Take an ON or USING clause from a join expression and expand if necessary.
*/
static Node *
transformUsingClause(ParseState *pstate, List *onList, char *lname, char *rname)
{
A_Expr *expr = NULL;
List *on;
Node *qual;
foreach(on, onList)
{
qual = lfirst(on);
/*
* Ident node means it is just a column name from a real USING
* clause...
*/
if (IsA(qual, Ident))
{
Ident *i = (Ident *) qual;
Attr *lattr = makeAttr(lname, i->name);
Attr *rattr = makeAttr(rname, i->name);
A_Expr *e = makeNode(A_Expr);
e->oper = OP;
e->opname = "=";
e->lexpr = (Node *) lattr;
e->rexpr = (Node *) rattr;
if (expr != NULL)
{
A_Expr *a = makeNode(A_Expr);
a->oper = AND;
a->opname = NULL;
a->lexpr = (Node *) expr;
a->rexpr = (Node *) e;
expr = a;
}
else
expr = e;
}
/* otherwise, we have an expression from an ON clause... */
else
{
if (expr != NULL)
{
A_Expr *a = makeNode(A_Expr);
a->oper = AND;
a->opname = NULL;
a->lexpr = (Node *) expr;
a->rexpr = (Node *) qual;
expr = a;
}
else
expr = (A_Expr *) qual;
}
}
return ((Node *) transformExpr(pstate, (Node *) expr, EXPR_COLUMN_FIRST));
}
#endif
static char *
transformTableEntry(ParseState *pstate, RangeVar *r)
{
RelExpr *baserel = r->relExpr;
char *relname = baserel->relname;
char *refname = r->name;
RangeTblEntry *rte;
if (refname == NULL)
refname = relname;
/*
* marks this entry to indicate it comes from the FROM clause. In SQL,
* the target list can only refer to range variables specified in the
* from clause but we follow the more powerful POSTQUEL semantics and
* automatically generate the range variable if not specified. However
* there are times we need to know whether the entries are legitimate.
*
* eg. select * from foo f where f.x = 1; will generate wrong answer if
* we expand * to foo.x.
*/
rte = addRangeTableEntry(pstate, relname, refname, baserel->inh, TRUE);
return refname;
}
/*
* parseFromClause -
* turns the table references specified in the from-clause into a
* range table. The range table may grow as we transform the expressions
* in the target list. (Note that this happens because in POSTQUEL, we
* allow references to relations not specified in the from-clause. We
* also allow now as an extension.)
*
* The FROM clause can now contain JoinExpr nodes, which contain parsing info
* for inner and outer joins. The USING clause must be expanded into a qualification
* for an inner join at least, since that is compatible with the old syntax.
* Not sure yet how to handle outer joins, but it will become clear eventually?
* - thomas 1998-12-16
*/
static void
parseFromClause(ParseState *pstate, List *frmList, Node **qual)
{
List *fl;
if (qual != NULL)
*qual = NULL;
foreach(fl, frmList)
{
Node *n = lfirst(fl);
/*
* marks this entry to indicate it comes from the FROM clause. In
* SQL, the target list can only refer to range variables
* specified in the from clause but we follow the more powerful
* POSTQUEL semantics and automatically generate the range
* variable if not specified. However there are times we need to
* know whether the entries are legitimate.
*
* eg. select * from foo f where f.x = 1; will generate wrong answer
* if we expand * to foo.x.
*/
if (IsA(n, RangeVar))
transformTableEntry(pstate, (RangeVar *) n);
else if (IsA(n, JoinExpr))
{
JoinExpr *j = (JoinExpr *) n;
#ifdef ENABLE_OUTER_JOINS
char *lname = transformTableEntry(pstate, (RangeVar *) j->larg);
#endif
char *rname;
if (IsA((Node *) j->rarg, RangeVar))
rname = transformTableEntry(pstate, (RangeVar *) j->rarg);
else
elog(ERROR, "Nested JOINs are not yet supported");
#ifdef ENABLE_OUTER_JOINS
if (j->jointype == INNER_P)
{
/*
* This is an inner join, so rip apart the join node and
* transform into a traditional FROM list. NATURAL JOIN
* and USING clauses both change the shape of the result.
* Need to generate a list of result columns to use for
* target list expansion and validation. Not doing this
* yet though!
*/
if (IsA(j->quals, List))
j->quals = lcons(transformUsingClause(pstate, (List *) j->quals, lname, rname), NIL);
if (qual == NULL)
elog(ERROR, "JOIN/ON not supported in this context");
if (*qual == NULL)
*qual = lfirst(j->quals);
else
elog(ERROR, "Multiple JOIN/ON clauses not handled (internal error)");
/*
* if we are transforming this node back into a FROM list,
* then we will need to replace the node with two nodes.
* Will need access to the previous list item to change
* the link pointer to reference these new nodes. Try
* accumulating and returning a new list. - thomas
* 1999-01-08 Not doing this yet though!
*/
}
else if ((j->jointype == LEFT)
|| (j->jointype == RIGHT)
|| (j->jointype == FULL))
elog(ERROR, "OUTER JOIN is not implemented");
else
elog(ERROR, "Unrecognized JOIN clause; tag is %d (internal error)",
j->jointype);
#else
elog(ERROR, "JOIN expressions are not yet implemented");
#endif
}
else
elog(ERROR, "parseFromClause: unexpected FROM clause node (internal error)"
"\n\t%s", nodeToString(n));
}
}
/*
* findTargetlistEntry -
* Returns the targetlist entry matching the given (untransformed) node.
* If no matching entry exists, one is created and appended to the target
* list as a "resjunk" node.
*
* node the ORDER BY or GROUP BY expression to be matched
* tlist the existing target list (NB: this cannot be NIL, which is a
* good thing since we'd be unable to append to it...)
* clause identifies clause type for error messages.
*/
static TargetEntry *
findTargetlistEntry(ParseState *pstate, Node *node, List *tlist, int clause)
{
TargetEntry *target_result = NULL;
List *tl;
Node *expr;
/*----------
* Handle two special cases as mandated by the SQL92 spec:
*
* 1. ORDER/GROUP BY ColumnName
* For a bare identifier, we search for a matching column name
* in the existing target list. Multiple matches are an error
* unless they refer to identical values; for example,
* we allow SELECT a, a FROM table ORDER BY a
* but not SELECT a AS b, b FROM table ORDER BY b
* If no match is found, we fall through and treat the identifier
* as an expression.
*
* 2. ORDER/GROUP BY IntegerConstant
* This means to use the n'th item in the existing target list.
* Note that it would make no sense to order/group by an actual
* constant, so this does not create a conflict with our extension
* to order/group by an expression.
*
* Note that pre-existing resjunk targets must not be used in either case.
*----------
*/
if (IsA(node, Ident) && ((Ident *) node)->indirection == NIL)
{
char *name = ((Ident *) node)->name;
foreach(tl, tlist)
{
TargetEntry *tle = (TargetEntry *) lfirst(tl);
Resdom *resnode = tle->resdom;
if (!resnode->resjunk &&
strcmp(resnode->resname, name) == 0)
{
if (target_result != NULL)
{
if (! equal(target_result->expr, tle->expr))
elog(ERROR, "%s BY '%s' is ambiguous",
clauseText[clause], name);
}
else
target_result = tle;
/* Stay in loop to check for ambiguity */
}
}
if (target_result != NULL)
return target_result; /* return the first match */
}
if (IsA(node, A_Const))
{
Value *val = &((A_Const *) node)->val;
int targetlist_pos = 0;
int target_pos;
if (nodeTag(val) != T_Integer)
elog(ERROR, "Non-integer constant in %s BY", clauseText[clause]);
target_pos = intVal(val);
foreach(tl, tlist)
{
TargetEntry *tle = (TargetEntry *) lfirst(tl);
Resdom *resnode = tle->resdom;
if (!resnode->resjunk)
{
if (++targetlist_pos == target_pos)
return tle; /* return the unique match */
}
}
elog(ERROR, "%s BY position %d is not in target list",
clauseText[clause], target_pos);
}
/*
* Otherwise, we have an expression (this is a Postgres extension
* not found in SQL92). Convert the untransformed node to a
* transformed expression, and search for a match in the tlist.
* NOTE: it doesn't really matter whether there is more than one
* match. Also, we are willing to match a resjunk target here,
* though the above cases must ignore resjunk targets.
*/
expr = transformExpr(pstate, node, EXPR_COLUMN_FIRST);
foreach(tl, tlist)
{
TargetEntry *tle = (TargetEntry *) lfirst(tl);
if (equal(expr, tle->expr))
return tle;
}
/*
* If no matches, construct a new target entry which is appended to
* the end of the target list. This target is set to be resjunk =
* TRUE so that it will not be projected into the final tuple.
*/
target_result = transformTargetEntry(pstate, node, expr, NULL, true);
lappend(tlist, target_result);
return target_result;
}
/*
* transformGroupClause -
* transform a Group By clause
*
*/
List *
transformGroupClause(ParseState *pstate, List *grouplist, List *targetlist)
{
List *glist = NIL,
*gl,
*othergl;
int nextgroupref = 1;
foreach(gl, grouplist)
{
TargetEntry *restarget;
Resdom *resdom;
restarget = findTargetlistEntry(pstate, lfirst(gl),
targetlist, GROUP_CLAUSE);
resdom = restarget->resdom;
/* avoid making duplicate grouplist entries */
foreach(othergl, glist)
{
GroupClause *gcl = (GroupClause *) lfirst(othergl);
if (equal(get_groupclause_expr(gcl, targetlist),
restarget->expr))
break;
}
if (othergl == NIL) /* not in grouplist already */
{
GroupClause *grpcl = makeNode(GroupClause);
grpcl->tleGroupref = nextgroupref++;
resdom->resgroupref = grpcl->tleGroupref;
grpcl->grpOpoid = oprid(oper("<",
resdom->restype,
resdom->restype, false));
glist = lappend(glist, grpcl);
}
}
return glist;
}
/*
* transformSortClause -
* transform an Order By clause
*
*/
List *
transformSortClause(ParseState *pstate,
List *orderlist,
List *sortlist,
List *targetlist,
char *uniqueFlag)
{
List *s = NIL;
while (orderlist != NIL)
{
SortGroupBy *sortby = lfirst(orderlist);
SortClause *sortcl = makeNode(SortClause);
TargetEntry *restarget;
Resdom *resdom;
restarget = findTargetlistEntry(pstate, sortby->node,
targetlist, ORDER_CLAUSE);
sortcl->resdom = resdom = restarget->resdom;
/*
* if we have InvalidOid, then this is a NULL field and don't need
* to sort
*/
if (resdom->restype == InvalidOid)
resdom->restype = INT4OID;
sortcl->opoid = oprid(oper(sortby->useOp,
resdom->restype,
resdom->restype, false));
if (sortlist == NIL)
s = sortlist = lcons(sortcl, NIL);
else
{
List *i;
foreach(i, sortlist)
{
SortClause *scl = (SortClause *) lfirst(i);
if (scl->resdom == sortcl->resdom)
break;
}
if (i == NIL) /* not in sortlist already */
{
lnext(s) = lcons(sortcl, NIL);
s = lnext(s);
}
else
pfree(sortcl); /* get rid of this */
}
orderlist = lnext(orderlist);
}
if (uniqueFlag)
{
List *i;
if (uniqueFlag[0] == '*')
{
/*
* concatenate all elements from target list that are not
* already in the sortby list
*/
foreach(i, targetlist)
{
TargetEntry *tlelt = (TargetEntry *) lfirst(i);
s = sortlist;
while (s != NIL)
{
SortClause *sortcl = lfirst(s);
/*
* We use equal() here because we are called for UNION
* from the optimizer, and at that point, the sort
* clause resdom pointers don't match the target list
* resdom pointers
*/
if (equal(sortcl->resdom, tlelt->resdom))
break;
s = lnext(s);
}
if (s == NIL)
{
/* not a member of the sortclauses yet */
SortClause *sortcl = makeNode(SortClause);
if (tlelt->resdom->restype == InvalidOid)
tlelt->resdom->restype = INT4OID;
sortcl->resdom = tlelt->resdom;
sortcl->opoid = any_ordering_op(tlelt->resdom->restype);
sortlist = lappend(sortlist, sortcl);
}
}
}
else
{
TargetEntry *tlelt = NULL;
char *uniqueAttrName = uniqueFlag;
/* only create sort clause with the specified unique attribute */
foreach(i, targetlist)
{
tlelt = (TargetEntry *) lfirst(i);
if (strcmp(tlelt->resdom->resname, uniqueAttrName) == 0)
break;
}
if (i == NIL)
elog(ERROR, "All fields in the UNIQUE ON clause must appear in the target list");
foreach(s, sortlist)
{
SortClause *sortcl = lfirst(s);
if (sortcl->resdom == tlelt->resdom)
break;
}
if (s == NIL)
{
/* not a member of the sortclauses yet */
SortClause *sortcl = makeNode(SortClause);
sortcl->resdom = tlelt->resdom;
sortcl->opoid = any_ordering_op(tlelt->resdom->restype);
sortlist = lappend(sortlist, sortcl);
}
}
}
return sortlist;
}
/* transformUnionClause()
* Transform a UNION clause.
* Note that the union clause is actually a fully-formed select structure.
* So, it is evaluated as a select, then the resulting target fields
* are matched up to ensure correct types in the results.
* The select clause parsing is done recursively, so the unions are evaluated
* right-to-left. One might want to look at all columns from all clauses before
* trying to coerce, but unless we keep track of the call depth we won't know
* when to do this because of the recursion.
* Let's just try matching in pairs for now (right to left) and see if it works.
* - thomas 1998-05-22
*/
#ifdef NOT_USED
static List *
transformUnionClause(List *unionClause, List *targetlist)
{
List *union_list = NIL;
List *qlist,
*qlist_item;
if (unionClause)
{
/* recursion */
qlist = parse_analyze(unionClause, NULL);
foreach(qlist_item, qlist)
{
Query *query = (Query *) lfirst(qlist_item);
List *prev_target = targetlist;
List *next_target;
int prev_len = 0,
next_len = 0;
foreach(prev_target, targetlist)
if (!((TargetEntry *) lfirst(prev_target))->resdom->resjunk)
prev_len++;
foreach(next_target, query->targetList)
if (!((TargetEntry *) lfirst(next_target))->resdom->resjunk)
next_len++;
if (prev_len != next_len)
elog(ERROR, "Each UNION clause must have the same number of columns");
foreach(next_target, query->targetList)
{
Oid itype;
Oid otype;
otype = ((TargetEntry *) lfirst(prev_target))->resdom->restype;
itype = ((TargetEntry *) lfirst(next_target))->resdom->restype;
/* one or both is a NULL column? then don't convert... */
if (otype == InvalidOid)
{
/* propagate a known type forward, if available */
if (itype != InvalidOid)
((TargetEntry *) lfirst(prev_target))->resdom->restype = itype;
#if FALSE
else
{
((TargetEntry *) lfirst(prev_target))->resdom->restype = UNKNOWNOID;
((TargetEntry *) lfirst(next_target))->resdom->restype = UNKNOWNOID;
}
#endif
}
else if (itype == InvalidOid)
{
}
/* they don't match in type? then convert... */
else if (itype != otype)
{
Node *expr;
expr = ((TargetEntry *) lfirst(next_target))->expr;
expr = CoerceTargetExpr(NULL, expr, itype, otype);
if (expr == NULL)
{
elog(ERROR, "Unable to transform %s to %s"
"\n\tEach UNION clause must have compatible target types",
typeidTypeName(itype),
typeidTypeName(otype));
}
((TargetEntry *) lfirst(next_target))->expr = expr;
((TargetEntry *) lfirst(next_target))->resdom->restype = otype;
}
/* both are UNKNOWN? then evaluate as text... */
else if (itype == UNKNOWNOID)
{
((TargetEntry *) lfirst(next_target))->resdom->restype = TEXTOID;
((TargetEntry *) lfirst(prev_target))->resdom->restype = TEXTOID;
}
prev_target = lnext(prev_target);
}
union_list = lappend(union_list, query);
}
return union_list;
}
else
return NIL;
}
#endif