mirror of
https://github.com/postgres/postgres.git
synced 2025-07-05 07:21:24 +03:00
Subselects in FROM clause, per ISO syntax: FROM (SELECT ...) [AS] alias.
(Don't forget that an alias is required.) Views reimplemented as expanding to subselect-in-FROM. Grouping, aggregates, DISTINCT in views actually work now (he says optimistically). No UNION support in subselects/views yet, but I have some ideas about that. Rule-related permissions checking moved out of rewriter and into executor. INITDB REQUIRED!
This commit is contained in:
@ -6,7 +6,7 @@
|
||||
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $Id: analyze.c,v 1.158 2000/09/25 12:58:46 momjian Exp $
|
||||
* $Id: analyze.c,v 1.159 2000/09/29 18:21:36 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -56,6 +56,7 @@ static void transformColumnType(ParseState *pstate, ColumnDef *column);
|
||||
static void transformFkeyCheckAttrs(FkConstraint *fkconstraint);
|
||||
|
||||
static void release_pstate_resources(ParseState *pstate);
|
||||
static FromExpr *makeFromExpr(List *fromlist, Node *quals);
|
||||
|
||||
/* kluge to return extra info from transformCreateStmt() */
|
||||
static List *extras_before;
|
||||
@ -289,6 +290,7 @@ static Query *
|
||||
transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
|
||||
{
|
||||
Query *qry = makeNode(Query);
|
||||
Node *qual;
|
||||
|
||||
qry->commandType = CMD_DELETE;
|
||||
|
||||
@ -299,17 +301,17 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
|
||||
qry->distinctClause = NIL;
|
||||
|
||||
/* fix where clause */
|
||||
qry->qual = transformWhereClause(pstate, stmt->whereClause);
|
||||
qual = transformWhereClause(pstate, stmt->whereClause);
|
||||
|
||||
/* done building the rtable */
|
||||
/* done building the range table and jointree */
|
||||
qry->rtable = pstate->p_rtable;
|
||||
qry->jointree = pstate->p_jointree;
|
||||
qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
|
||||
qry->resultRelation = refnameRangeTablePosn(pstate, stmt->relname, NULL);
|
||||
|
||||
qry->hasSubLinks = pstate->p_hasSubLinks;
|
||||
qry->hasAggs = pstate->p_hasAggs;
|
||||
if (pstate->p_hasAggs)
|
||||
parseCheckAggregates(pstate, qry);
|
||||
parseCheckAggregates(pstate, qry, qual);
|
||||
|
||||
return (Query *) qry;
|
||||
}
|
||||
@ -322,6 +324,7 @@ static Query *
|
||||
transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
|
||||
{
|
||||
Query *qry = makeNode(Query);
|
||||
Node *qual;
|
||||
List *icolumns;
|
||||
List *attrnos;
|
||||
List *attnos;
|
||||
@ -348,7 +351,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
|
||||
|
||||
qry->targetList = transformTargetList(pstate, stmt->targetList);
|
||||
|
||||
qry->qual = transformWhereClause(pstate, stmt->whereClause);
|
||||
qual = transformWhereClause(pstate, stmt->whereClause);
|
||||
|
||||
/*
|
||||
* Initial processing of HAVING clause is just like WHERE clause.
|
||||
@ -371,7 +374,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
|
||||
qry->hasSubLinks = pstate->p_hasSubLinks;
|
||||
qry->hasAggs = pstate->p_hasAggs;
|
||||
if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
|
||||
parseCheckAggregates(pstate, qry);
|
||||
parseCheckAggregates(pstate, qry, qual);
|
||||
|
||||
/*
|
||||
* The INSERT INTO ... SELECT ... could have a UNION in child, so
|
||||
@ -393,13 +396,13 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
|
||||
* 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.) Note that the INSERT target is NOT added to the
|
||||
* join tree, since we don't want to join over it.
|
||||
* joinlist, since we don't want to join over it.
|
||||
*/
|
||||
setTargetTable(pstate, stmt->relname, false, false);
|
||||
|
||||
/* now the range table will not change */
|
||||
/* now the range table and jointree will not change */
|
||||
qry->rtable = pstate->p_rtable;
|
||||
qry->jointree = pstate->p_jointree;
|
||||
qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
|
||||
qry->resultRelation = refnameRangeTablePosn(pstate, stmt->relname, NULL);
|
||||
|
||||
/* Prepare to assign non-conflicting resnos to resjunk attributes */
|
||||
@ -715,7 +718,7 @@ transformCreateStmt(ParseState *pstate, CreateStmt *stmt)
|
||||
snamenode->val.val.str = qstring;
|
||||
funccallnode = makeNode(FuncCall);
|
||||
funccallnode->funcname = "nextval";
|
||||
funccallnode->args = lcons(snamenode, NIL);
|
||||
funccallnode->args = makeList1(snamenode);
|
||||
funccallnode->agg_star = false;
|
||||
funccallnode->agg_distinct = false;
|
||||
|
||||
@ -748,7 +751,7 @@ transformCreateStmt(ParseState *pstate, CreateStmt *stmt)
|
||||
elog(NOTICE, "CREATE TABLE will create implicit sequence '%s' for SERIAL column '%s.%s'",
|
||||
sequence->seqname, stmt->relname, column->colname);
|
||||
|
||||
blist = lcons(sequence, NIL);
|
||||
blist = makeList1(sequence);
|
||||
}
|
||||
|
||||
/* Process column constraints, if any... */
|
||||
@ -776,7 +779,7 @@ transformCreateStmt(ParseState *pstate, CreateStmt *stmt)
|
||||
id->isRel = false;
|
||||
|
||||
fkconstraint = (FkConstraint *) constraint;
|
||||
fkconstraint->fk_attrs = lappend(NIL, id);
|
||||
fkconstraint->fk_attrs = makeList1(id);
|
||||
|
||||
fkconstraints = lappend(fkconstraints, constraint);
|
||||
continue;
|
||||
@ -815,7 +818,7 @@ transformCreateStmt(ParseState *pstate, CreateStmt *stmt)
|
||||
{
|
||||
key = makeNode(Ident);
|
||||
key->name = pstrdup(column->colname);
|
||||
constraint->keys = lcons(key, NIL);
|
||||
constraint->keys = makeList1(key);
|
||||
}
|
||||
dlist = lappend(dlist, constraint);
|
||||
break;
|
||||
@ -827,7 +830,7 @@ transformCreateStmt(ParseState *pstate, CreateStmt *stmt)
|
||||
{
|
||||
key = makeNode(Ident);
|
||||
key->name = pstrdup(column->colname);
|
||||
constraint->keys = lcons(key, NIL);
|
||||
constraint->keys = makeList1(key);
|
||||
}
|
||||
dlist = lappend(dlist, constraint);
|
||||
break;
|
||||
@ -1453,8 +1456,11 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt)
|
||||
newrte = addRangeTableEntry(pstate, stmt->object->relname,
|
||||
makeAttr("*NEW*", NULL),
|
||||
false, true);
|
||||
/* Must override addRangeTableEntry's default access-check flags */
|
||||
oldrte->checkForRead = false;
|
||||
newrte->checkForRead = false;
|
||||
/*
|
||||
* They must be in the jointree too for lookup purposes, but only add
|
||||
* They must be in the joinlist too for lookup purposes, but only add
|
||||
* the one(s) that are relevant for the current kind of rule. In an
|
||||
* UPDATE rule, quals must refer to OLD.field or NEW.field to be
|
||||
* unambiguous, but there's no need to be so picky for INSERT & DELETE.
|
||||
@ -1464,17 +1470,17 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt)
|
||||
switch (stmt->event)
|
||||
{
|
||||
case CMD_SELECT:
|
||||
addRTEtoJoinTree(pstate, oldrte);
|
||||
addRTEtoJoinList(pstate, oldrte);
|
||||
break;
|
||||
case CMD_UPDATE:
|
||||
addRTEtoJoinTree(pstate, oldrte);
|
||||
addRTEtoJoinTree(pstate, newrte);
|
||||
addRTEtoJoinList(pstate, oldrte);
|
||||
addRTEtoJoinList(pstate, newrte);
|
||||
break;
|
||||
case CMD_INSERT:
|
||||
addRTEtoJoinTree(pstate, newrte);
|
||||
addRTEtoJoinList(pstate, newrte);
|
||||
break;
|
||||
case CMD_DELETE:
|
||||
addRTEtoJoinTree(pstate, oldrte);
|
||||
addRTEtoJoinList(pstate, oldrte);
|
||||
break;
|
||||
default:
|
||||
elog(ERROR, "transformRuleStmt: unexpected event type %d",
|
||||
@ -1504,9 +1510,9 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt)
|
||||
|
||||
nothing_qry->commandType = CMD_NOTHING;
|
||||
nothing_qry->rtable = pstate->p_rtable;
|
||||
nothing_qry->jointree = NIL; /* no join actually wanted */
|
||||
nothing_qry->jointree = makeFromExpr(NIL, NULL); /* no join wanted */
|
||||
|
||||
stmt->actions = lappend(NIL, nothing_qry);
|
||||
stmt->actions = makeList1(nothing_qry);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1526,7 +1532,7 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt)
|
||||
* Set up OLD/NEW in the rtable for this statement. The entries
|
||||
* are marked not inFromCl because we don't want them to be
|
||||
* referred to by unqualified field names nor "*" in the rule
|
||||
* actions. We don't need to add them to the jointree for
|
||||
* actions. We don't need to add them to the joinlist for
|
||||
* qualified-name lookup, either (see qualifiedNameToVar()).
|
||||
*/
|
||||
oldrte = addRangeTableEntry(sub_pstate, stmt->object->relname,
|
||||
@ -1535,6 +1541,8 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt)
|
||||
newrte = addRangeTableEntry(sub_pstate, stmt->object->relname,
|
||||
makeAttr("*NEW*", NULL),
|
||||
false, false);
|
||||
oldrte->checkForRead = false;
|
||||
newrte->checkForRead = false;
|
||||
|
||||
/* Transform the rule action statement */
|
||||
sub_qry = transformStmt(sub_pstate, lfirst(actions));
|
||||
@ -1581,8 +1589,8 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt)
|
||||
*/
|
||||
if (has_old)
|
||||
{
|
||||
addRTEtoJoinTree(sub_pstate, oldrte);
|
||||
sub_qry->jointree = sub_pstate->p_jointree;
|
||||
addRTEtoJoinList(sub_pstate, oldrte);
|
||||
sub_qry->jointree->fromlist = sub_pstate->p_joinlist;
|
||||
}
|
||||
|
||||
lfirst(actions) = sub_qry;
|
||||
@ -1605,6 +1613,7 @@ static Query *
|
||||
transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
|
||||
{
|
||||
Query *qry = makeNode(Query);
|
||||
Node *qual;
|
||||
|
||||
qry->commandType = CMD_SELECT;
|
||||
|
||||
@ -1617,7 +1626,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
|
||||
|
||||
qry->targetList = transformTargetList(pstate, stmt->targetList);
|
||||
|
||||
qry->qual = transformWhereClause(pstate, stmt->whereClause);
|
||||
qual = transformWhereClause(pstate, stmt->whereClause);
|
||||
|
||||
/*
|
||||
* Initial processing of HAVING clause is just like WHERE clause.
|
||||
@ -1641,7 +1650,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
|
||||
qry->hasSubLinks = pstate->p_hasSubLinks;
|
||||
qry->hasAggs = pstate->p_hasAggs;
|
||||
if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
|
||||
parseCheckAggregates(pstate, qry);
|
||||
parseCheckAggregates(pstate, qry, qual);
|
||||
|
||||
/*
|
||||
* The INSERT INTO ... SELECT ... could have a UNION in child, so
|
||||
@ -1657,7 +1666,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
|
||||
qry->intersectClause = stmt->intersectClause;
|
||||
|
||||
qry->rtable = pstate->p_rtable;
|
||||
qry->jointree = pstate->p_jointree;
|
||||
qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
|
||||
|
||||
if (stmt->forUpdate != NULL)
|
||||
transformForUpdate(qry, stmt->forUpdate);
|
||||
@ -1674,6 +1683,7 @@ static Query *
|
||||
transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
|
||||
{
|
||||
Query *qry = makeNode(Query);
|
||||
Node *qual;
|
||||
List *origTargetList;
|
||||
List *tl;
|
||||
|
||||
@ -1683,22 +1693,27 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* Note: it's critical here that we process FROM before adding the
|
||||
* target table to the rtable --- otherwise, if the target is also
|
||||
* used in FROM, we'd fail to notice that it should be marked
|
||||
* checkForRead as well as checkForWrite. See setTargetTable().
|
||||
*/
|
||||
makeRangeTable(pstate, stmt->fromClause);
|
||||
setTargetTable(pstate, stmt->relname, stmt->inh, true);
|
||||
|
||||
qry->targetList = transformTargetList(pstate, stmt->targetList);
|
||||
|
||||
qry->qual = transformWhereClause(pstate, stmt->whereClause);
|
||||
qual = transformWhereClause(pstate, stmt->whereClause);
|
||||
|
||||
qry->rtable = pstate->p_rtable;
|
||||
qry->jointree = pstate->p_jointree;
|
||||
qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
|
||||
qry->resultRelation = refnameRangeTablePosn(pstate, stmt->relname, NULL);
|
||||
|
||||
qry->hasSubLinks = pstate->p_hasSubLinks;
|
||||
qry->hasAggs = pstate->p_hasAggs;
|
||||
if (pstate->p_hasAggs)
|
||||
parseCheckAggregates(pstate, qry);
|
||||
parseCheckAggregates(pstate, qry, qual);
|
||||
|
||||
/*
|
||||
* Now we are done with SELECT-like processing, and can get on with
|
||||
@ -2083,7 +2098,7 @@ A_Expr_to_Expr(Node *ptr, bool *intersect_present)
|
||||
|
||||
expr->typeOid = BOOLOID;
|
||||
expr->opType = OR_EXPR;
|
||||
expr->args = makeList(lexpr, rexpr, -1);
|
||||
expr->args = makeList2(lexpr, rexpr);
|
||||
result = (Node *) expr;
|
||||
break;
|
||||
}
|
||||
@ -2095,7 +2110,7 @@ A_Expr_to_Expr(Node *ptr, bool *intersect_present)
|
||||
|
||||
expr->typeOid = BOOLOID;
|
||||
expr->opType = AND_EXPR;
|
||||
expr->args = makeList(lexpr, rexpr, -1);
|
||||
expr->args = makeList2(lexpr, rexpr);
|
||||
result = (Node *) expr;
|
||||
break;
|
||||
}
|
||||
@ -2106,7 +2121,7 @@ A_Expr_to_Expr(Node *ptr, bool *intersect_present)
|
||||
|
||||
expr->typeOid = BOOLOID;
|
||||
expr->opType = NOT_EXPR;
|
||||
expr->args = makeList(rexpr, -1);
|
||||
expr->args = makeList1(rexpr);
|
||||
result = (Node *) expr;
|
||||
break;
|
||||
}
|
||||
@ -2122,7 +2137,7 @@ A_Expr_to_Expr(Node *ptr, bool *intersect_present)
|
||||
void
|
||||
CheckSelectForUpdate(Query *qry)
|
||||
{
|
||||
if (qry->unionClause != NULL)
|
||||
if (qry->unionClause || qry->intersectClause)
|
||||
elog(ERROR, "SELECT FOR UPDATE is not allowed with UNION/INTERSECT/EXCEPT clause");
|
||||
if (qry->distinctClause != NIL)
|
||||
elog(ERROR, "SELECT FOR UPDATE is not allowed with DISTINCT clause");
|
||||
@ -2135,61 +2150,70 @@ CheckSelectForUpdate(Query *qry)
|
||||
static void
|
||||
transformForUpdate(Query *qry, List *forUpdate)
|
||||
{
|
||||
List *rowMark = NULL;
|
||||
RowMark *newrm;
|
||||
List *rowMarks = NIL;
|
||||
List *l;
|
||||
List *rt;
|
||||
Index i;
|
||||
|
||||
CheckSelectForUpdate(qry);
|
||||
|
||||
if (lfirst(forUpdate) == NULL) /* all tables */
|
||||
if (lfirst(forUpdate) == NULL)
|
||||
{
|
||||
i = 1;
|
||||
foreach(l, qry->rtable)
|
||||
/* all tables used in query */
|
||||
i = 0;
|
||||
foreach(rt, 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;
|
||||
}
|
||||
RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
|
||||
|
||||
foreach(l, forUpdate)
|
||||
{
|
||||
char *relname = lfirst(l);
|
||||
List *l2;
|
||||
|
||||
i = 1;
|
||||
foreach(l2, qry->rtable)
|
||||
{
|
||||
if (strcmp(((RangeTblEntry *) lfirst(l2))->eref->relname, relname) == 0)
|
||||
++i;
|
||||
if (rte->subquery)
|
||||
{
|
||||
List *l3;
|
||||
|
||||
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;
|
||||
/* FOR UPDATE of subquery is propagated to subquery's rels */
|
||||
transformForUpdate(rte->subquery, makeList1(NULL));
|
||||
}
|
||||
else
|
||||
{
|
||||
rowMarks = lappendi(rowMarks, i);
|
||||
rte->checkForWrite = true;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (l2 == NULL)
|
||||
elog(ERROR, "FOR UPDATE: relation \"%s\" not found in FROM clause",
|
||||
relname);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* just the named tables */
|
||||
foreach(l, forUpdate)
|
||||
{
|
||||
char *relname = lfirst(l);
|
||||
|
||||
i = 0;
|
||||
foreach(rt, qry->rtable)
|
||||
{
|
||||
RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
|
||||
|
||||
++i;
|
||||
if (strcmp(rte->eref->relname, relname) == 0)
|
||||
{
|
||||
if (rte->subquery)
|
||||
{
|
||||
/* propagate to subquery */
|
||||
transformForUpdate(rte->subquery, makeList1(NULL));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!intMember(i, rowMarks)) /* avoid duplicates */
|
||||
rowMarks = lappendi(rowMarks, i);
|
||||
rte->checkForWrite = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (rt == NIL)
|
||||
elog(ERROR, "FOR UPDATE: relation \"%s\" not found in FROM clause",
|
||||
relname);
|
||||
}
|
||||
}
|
||||
|
||||
qry->rowMark = rowMark;
|
||||
qry->rowMarks = rowMarks;
|
||||
}
|
||||
|
||||
|
||||
@ -2452,6 +2476,17 @@ transformConstraintAttrs(List *constraintList)
|
||||
}
|
||||
}
|
||||
|
||||
/* Build a FromExpr node */
|
||||
static FromExpr *
|
||||
makeFromExpr(List *fromlist, Node *quals)
|
||||
{
|
||||
FromExpr *f = makeNode(FromExpr);
|
||||
|
||||
f->fromlist = fromlist;
|
||||
f->quals = quals;
|
||||
return f;
|
||||
}
|
||||
|
||||
/*
|
||||
* Special handling of type definition for a column
|
||||
*/
|
||||
|
Reference in New Issue
Block a user