diff options
Diffstat (limited to 'src/backend/parser')
-rw-r--r-- | src/backend/parser/Makefile | 4 | ||||
-rw-r--r-- | src/backend/parser/analyze.c | 242 | ||||
-rw-r--r-- | src/backend/parser/gram.y | 383 | ||||
-rw-r--r-- | src/backend/parser/parse_agg.c | 7 | ||||
-rw-r--r-- | src/backend/parser/parse_clause.c | 993 | ||||
-rw-r--r-- | src/backend/parser/parse_expr.c | 61 | ||||
-rw-r--r-- | src/backend/parser/parse_func.c | 117 | ||||
-rw-r--r-- | src/backend/parser/parse_node.c | 59 | ||||
-rw-r--r-- | src/backend/parser/parse_relation.c | 637 | ||||
-rw-r--r-- | src/backend/parser/parse_target.c | 142 | ||||
-rw-r--r-- | src/backend/parser/parser.c | 66 | ||||
-rw-r--r-- | src/backend/parser/scan.l | 14 |
12 files changed, 1493 insertions, 1232 deletions
diff --git a/src/backend/parser/Makefile b/src/backend/parser/Makefile index 6ef084d04b6..68fed79d382 100644 --- a/src/backend/parser/Makefile +++ b/src/backend/parser/Makefile @@ -2,7 +2,7 @@ # # Makefile for parser # -# $Header: /cvsroot/pgsql/src/backend/parser/Makefile,v 1.29 2000/08/28 11:53:19 petere Exp $ +# $Header: /cvsroot/pgsql/src/backend/parser/Makefile,v 1.30 2000/09/12 21:07:00 tgl Exp $ # #------------------------------------------------------------------------- @@ -31,7 +31,7 @@ $(srcdir)/gram.c $(srcdir)/parse.h: gram.y $(srcdir)/scan.c: scan.l ifdef FLEX - $(FLEX) $(FLEXFLAGS) -o'$@' $< + $(FLEX) $(FLEXFLAGS) -Pbase_yy -o'$@' $< else @$(missing) flex $< $@ endif diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index fe21804a2c4..0165ef15c21 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -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.156 2000/08/29 04:20:44 momjian Exp $ + * $Id: analyze.c,v 1.157 2000/09/12 21:07:00 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -25,6 +25,7 @@ #include "parser/parse_relation.h" #include "parser/parse_target.h" #include "parser/parse_type.h" +#include "rewrite/rewriteManip.h" #include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/relcache.h" @@ -54,6 +55,8 @@ static void transformConstraintAttrs(List *constraintList); static void transformColumnType(ParseState *pstate, ColumnDef *column); static void transformFkeyCheckAttrs(FkConstraint *fkconstraint); +static void release_pstate_resources(ParseState *pstate); + /* kluge to return extra info from transformCreateStmt() */ static List *extras_before; static List *extras_after; @@ -71,28 +74,22 @@ List * parse_analyze(List *pl, ParseState *parentParseState) { List *result = NIL; - ParseState *pstate; - Query *parsetree; while (pl != NIL) { + ParseState *pstate = make_parsestate(parentParseState); + Query *parsetree; + extras_before = extras_after = NIL; - pstate = make_parsestate(parentParseState); parsetree = transformStmt(pstate, lfirst(pl)); - if (pstate->p_target_relation != NULL) - heap_close(pstate->p_target_relation, AccessShareLock); - pstate->p_target_relation = NULL; - pstate->p_target_rangetblentry = NULL; + release_pstate_resources(pstate); while (extras_before != NIL) { result = lappend(result, - transformStmt(pstate, lfirst(extras_before))); - if (pstate->p_target_relation != NULL) - heap_close(pstate->p_target_relation, AccessShareLock); - pstate->p_target_relation = NULL; - pstate->p_target_rangetblentry = NULL; + transformStmt(pstate, lfirst(extras_before))); + release_pstate_resources(pstate); extras_before = lnext(extras_before); } @@ -102,10 +99,7 @@ parse_analyze(List *pl, ParseState *parentParseState) { result = lappend(result, transformStmt(pstate, lfirst(extras_after))); - if (pstate->p_target_relation != NULL) - heap_close(pstate->p_target_relation, AccessShareLock); - pstate->p_target_relation = NULL; - pstate->p_target_rangetblentry = NULL; + release_pstate_resources(pstate); extras_after = lnext(extras_after); } @@ -116,6 +110,15 @@ parse_analyze(List *pl, ParseState *parentParseState) return result; } +static void +release_pstate_resources(ParseState *pstate) +{ + if (pstate->p_target_relation != NULL) + heap_close(pstate->p_target_relation, AccessShareLock); + pstate->p_target_relation = NULL; + pstate->p_target_rangetblentry = NULL; +} + /* * transformStmt - * transform a Parse tree. If it is an optimizable statement, turn it @@ -176,11 +179,11 @@ transformStmt(ParseState *pstate, Node *parseTree) Resdom *rd; id = nth(i, n->aliases); - Assert(nodeTag(id) == T_Ident); + Assert(IsA(id, Ident)); te = nth(i, targetList); - Assert(nodeTag(te) == T_TargetEntry); + Assert(IsA(te, TargetEntry)); rd = te->resdom; - Assert(nodeTag(rd) == T_Resdom); + Assert(IsA(rd, Resdom)); rd->resname = pstrdup(id->name); } } @@ -290,15 +293,17 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt) qry->commandType = CMD_DELETE; /* set up a range table */ - makeRangeTable(pstate, NULL); - setTargetTable(pstate, stmt->relname, stmt->inh); + makeRangeTable(pstate, NIL); + setTargetTable(pstate, stmt->relname, stmt->inh, true); qry->distinctClause = NIL; /* fix where clause */ qry->qual = transformWhereClause(pstate, stmt->whereClause); + /* done building the rtable */ qry->rtable = pstate->p_rtable; + qry->jointree = pstate->p_jointree; qry->resultRelation = refnameRangeTablePosn(pstate, stmt->relname, NULL); qry->hasSubLinks = pstate->p_hasSubLinks; @@ -387,12 +392,14 @@ 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.) + * the SELECT part.) Note that the INSERT target is NOT added to the + * join tree, since we don't want to join over it. */ - setTargetTable(pstate, stmt->relname, FALSE); + setTargetTable(pstate, stmt->relname, false, false); /* now the range table will not change */ qry->rtable = pstate->p_rtable; + qry->jointree = pstate->p_jointree; qry->resultRelation = refnameRangeTablePosn(pstate, stmt->relname, NULL); /* Prepare to assign non-conflicting resnos to resjunk attributes */ @@ -908,7 +915,7 @@ transformCreateStmt(ParseState *pstate, CreateStmt *stmt) while (dlist != NIL) { constraint = lfirst(dlist); - Assert(nodeTag(constraint) == T_Constraint); + Assert(IsA(constraint, Constraint)); Assert((constraint->contype == CONSTR_PRIMARY) || (constraint->contype == CONSTR_UNIQUE)); @@ -1427,17 +1434,68 @@ static Query * transformRuleStmt(ParseState *pstate, RuleStmt *stmt) { Query *qry; - Query *action; - List *actions; + RangeTblEntry *oldrte; + RangeTblEntry *newrte; qry = makeNode(Query); qry->commandType = CMD_UTILITY; + qry->utilityStmt = (Node *) stmt; + + /* + * NOTE: 'OLD' must always have a varno equal to 1 and 'NEW' + * equal to 2. Set up their RTEs in the main pstate for use + * in parsing the rule qualification. + */ + Assert(pstate->p_rtable == NIL); + oldrte = addRangeTableEntry(pstate, stmt->object->relname, + makeAttr("*OLD*", NULL), + false, true); + newrte = addRangeTableEntry(pstate, stmt->object->relname, + makeAttr("*NEW*", NULL), + false, true); + /* + * They must be in the jointree 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. + * (Note we marked the RTEs "inFromCl = true" above to allow unqualified + * references to their fields.) + */ + switch (stmt->event) + { + case CMD_SELECT: + addRTEtoJoinTree(pstate, oldrte); + break; + case CMD_UPDATE: + addRTEtoJoinTree(pstate, oldrte); + addRTEtoJoinTree(pstate, newrte); + break; + case CMD_INSERT: + addRTEtoJoinTree(pstate, newrte); + break; + case CMD_DELETE: + addRTEtoJoinTree(pstate, oldrte); + break; + default: + elog(ERROR, "transformRuleStmt: unexpected event type %d", + (int) stmt->event); + break; + } + + /* take care of the where clause */ + stmt->whereClause = transformWhereClause(pstate, stmt->whereClause); + + if (length(pstate->p_rtable) != 2) /* naughty, naughty... */ + elog(ERROR, "Rule WHERE condition may not contain references to other relations"); + + /* save info about sublinks in where clause */ + qry->hasSubLinks = pstate->p_hasSubLinks; /* - * 'instead nothing' rules with a qualification need a query a + * 'instead nothing' rules with a qualification need a query * rangetable so the rewrite handler can add the negated rule * qualification to the original query. We create a query with the new - * command type CMD_NOTHING here that is treated special by the + * command type CMD_NOTHING here that is treated specially by the * rewrite system. */ if (stmt->actions == NIL) @@ -1445,54 +1503,95 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt) Query *nothing_qry = makeNode(Query); nothing_qry->commandType = CMD_NOTHING; - - addRangeTableEntry(pstate, stmt->object->relname, - makeAttr("*OLD*", NULL), - FALSE, FALSE, FALSE); - addRangeTableEntry(pstate, stmt->object->relname, - makeAttr("*NEW*", NULL), - FALSE, FALSE, FALSE); - nothing_qry->rtable = pstate->p_rtable; + nothing_qry->jointree = NIL; /* no join actually wanted */ stmt->actions = lappend(NIL, nothing_qry); } - - actions = stmt->actions; - - /* - * transform each statment, like parse_analyze() - */ - while (actions != NIL) + else { + List *actions; /* - * NOTE: 'OLD' must always have a varno equal to 1 and 'NEW' - * equal to 2. + * transform each statement, like parse_analyze() */ - addRangeTableEntry(pstate, stmt->object->relname, - makeAttr("*OLD*", NULL), - FALSE, FALSE, FALSE); - addRangeTableEntry(pstate, stmt->object->relname, - makeAttr("*NEW*", NULL), - FALSE, FALSE, FALSE); - - pstate->p_last_resno = 1; - pstate->p_is_rule = true; /* for expand all */ - pstate->p_hasAggs = false; - - action = (Query *) lfirst(actions); - if (action->commandType != CMD_NOTHING) - lfirst(actions) = transformStmt(pstate, lfirst(actions)); - actions = lnext(actions); - } + foreach(actions, stmt->actions) + { + ParseState *sub_pstate = make_parsestate(pstate->parentParseState); + Query *sub_qry; + bool has_old, + has_new; - /* take care of the where clause */ - stmt->whereClause = transformWhereClause(pstate, stmt->whereClause); + /* + * 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 + * qualified-name lookup, either (see qualifiedNameToVar()). + */ + oldrte = addRangeTableEntry(sub_pstate, stmt->object->relname, + makeAttr("*OLD*", NULL), + false, false); + newrte = addRangeTableEntry(sub_pstate, stmt->object->relname, + makeAttr("*NEW*", NULL), + false, false); - qry->hasSubLinks = pstate->p_hasSubLinks; + /* Transform the rule action statement */ + sub_qry = transformStmt(sub_pstate, lfirst(actions)); + + /* + * Validate action's use of OLD/NEW, qual too + */ + has_old = + rangeTableEntry_used((Node *) sub_qry, PRS2_OLD_VARNO, 0) || + rangeTableEntry_used(stmt->whereClause, PRS2_OLD_VARNO, 0); + has_new = + rangeTableEntry_used((Node *) sub_qry, PRS2_NEW_VARNO, 0) || + rangeTableEntry_used(stmt->whereClause, PRS2_NEW_VARNO, 0); + + switch (stmt->event) + { + case CMD_SELECT: + if (has_old) + elog(ERROR, "ON SELECT rule may not use OLD"); + if (has_new) + elog(ERROR, "ON SELECT rule may not use NEW"); + break; + case CMD_UPDATE: + /* both are OK */ + break; + case CMD_INSERT: + if (has_old) + elog(ERROR, "ON INSERT rule may not use OLD"); + break; + case CMD_DELETE: + if (has_new) + elog(ERROR, "ON DELETE rule may not use NEW"); + break; + default: + elog(ERROR, "transformRuleStmt: unexpected event type %d", + (int) stmt->event); + break; + } + + /* + * For efficiency's sake, add OLD to the rule action's jointree + * only if it was actually referenced in the statement or qual. + * NEW is not really a relation and should never be added. + */ + if (has_old) + { + addRTEtoJoinTree(sub_pstate, oldrte); + sub_qry->jointree = sub_pstate->p_jointree; + } + + lfirst(actions) = sub_qry; + + release_pstate_resources(sub_pstate); + pfree(sub_pstate); + } + } - qry->utilityStmt = (Node *) stmt; return qry; } @@ -1558,6 +1657,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt) qry->intersectClause = stmt->intersectClause; qry->rtable = pstate->p_rtable; + qry->jointree = pstate->p_jointree; if (stmt->forUpdate != NULL) transformForUpdate(qry, stmt->forUpdate); @@ -1585,17 +1685,17 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt) * do this with REPLACE in POSTQUEL so we keep the feature. */ makeRangeTable(pstate, stmt->fromClause); - setTargetTable(pstate, stmt->relname, stmt->inh); + setTargetTable(pstate, stmt->relname, stmt->inh, true); qry->targetList = transformTargetList(pstate, stmt->targetList); qry->qual = transformWhereClause(pstate, stmt->whereClause); - qry->hasSubLinks = pstate->p_hasSubLinks; - qry->rtable = pstate->p_rtable; + qry->jointree = pstate->p_jointree; qry->resultRelation = refnameRangeTablePosn(pstate, stmt->relname, NULL); + qry->hasSubLinks = pstate->p_hasSubLinks; qry->hasAggs = pstate->p_hasAggs; if (pstate->p_hasAggs) parseCheckAggregates(pstate, qry); @@ -1689,7 +1789,7 @@ transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt) transformColumnType(pstate, (ColumnDef *) stmt->def); break; case 'C': - if (stmt->def && nodeTag(stmt->def) == T_FkConstraint) + if (stmt->def && IsA(stmt->def, FkConstraint)) { CreateTrigStmt *fk_trigger; List *fk_attr; @@ -2085,7 +2185,7 @@ transformForUpdate(Query *qry, List *forUpdate) i++; } if (l2 == NULL) - elog(ERROR, "FOR UPDATE: relation '%s' not found in FROM clause", + elog(ERROR, "FOR UPDATE: relation \"%s\" not found in FROM clause", relname); } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 7e970ab1871..301be9eb9b9 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.188 2000/09/12 05:09:44 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.189 2000/09/12 21:07:01 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -36,6 +36,7 @@ #include <ctype.h> #include "postgres.h" + #include "access/htup.h" #include "access/xact.h" #include "catalog/catname.h" @@ -77,15 +78,10 @@ static Node *makeA_Expr(int oper, char *opname, Node *lexpr, Node *rexpr); static Node *makeTypeCast(Node *arg, TypeName *typename); static Node *makeRowExpr(char *opr, List *largs, List *rargs); static void mapTargetColumns(List *source, List *target); -static void param_type_init(Oid *typev, int nargs); static bool exprIsNullConstant(Node *arg); static Node *doNegate(Node *n); static void doNegateFloat(Value *v); -/* old versions of flex define this as a macro */ -#if defined(yywrap) -#undef yywrap -#endif /* yywrap */ %} @@ -95,6 +91,7 @@ static void doNegateFloat(Value *v); char chr; char *str; bool boolean; + JoinType jtype; List *list; Node *node; Value *value; @@ -108,7 +105,6 @@ static void doNegateFloat(Value *v); JoinExpr *jexpr; IndexElem *ielem; RangeVar *range; - RelExpr *relexp; A_Indices *aind; ResTarget *target; ParamNo *paramno; @@ -194,19 +190,8 @@ static void doNegateFloat(Value *v); %type <boolean> opt_table %type <boolean> opt_chain, opt_trans -%type <jexpr> from_expr, join_clause, join_expr -%type <jexpr> join_clause_with_union, join_expr_with_union %type <node> join_outer, join_qual -%type <ival> join_type -%type <list> using_list -%type <ident> using_expr -/*** -#ifdef ENABLE_ORACLE_JOIN_SYNTAX -%type <list> oracle_list -%type <jexpr> oracle_expr -%type <boolean> oracle_outer -#endif -***/ +%type <jtype> join_type %type <list> extract_list, position_list %type <list> substr_list, substr_from, substr_for, trim_list @@ -246,8 +231,9 @@ static void doNegateFloat(Value *v); %type <attr> event_object, attr, alias_clause %type <sortgroupby> sortby %type <ielem> index_elem, func_index -%type <range> table_expr -%type <relexp> relation_expr +%type <node> table_ref +%type <jexpr> joined_table +%type <range> relation_expr %type <target> target_el, update_target_el %type <paramno> ParamNo @@ -356,6 +342,12 @@ static void doNegateFloat(Value *v); TEMP, TOAST, TRUNCATE, TRUSTED, UNLISTEN, UNTIL, VACUUM, VALID, VERBOSE, VERSION +/* The grammar thinks these are keywords, but they are not in the keywords.c + * list and so can never be entered directly. The filter in parser.c + * creates these tokens when required. + */ +%token UNIONJOIN + /* Special keywords, not in the query language - see the "lex" file */ %token <str> IDENT, FCONST, SCONST, Op %token <ival> ICONST, PARAM @@ -364,7 +356,9 @@ static void doNegateFloat(Value *v); %token OP /* precedence: lowest to highest */ -%left UNION INTERSECT EXCEPT +%left UNION EXCEPT +%left INTERSECT +%left JOIN UNIONJOIN CROSS LEFT FULL RIGHT INNER_P NATURAL %left OR %left AND %right NOT @@ -800,7 +794,7 @@ VariableSetStmt: SET ColId TO var_value n->value = $3; $$ = (Node *) n; #else - elog(ERROR, "SET NAMES is not supported."); + elog(ERROR, "SET NAMES is not supported"); #endif } ; @@ -1031,7 +1025,6 @@ AlterTableStmt: n->relname = $3; $$ = (Node *)n; } - /* ALTER TABLE <name> OWNER TO UserId */ | ALTER TABLE relation_name OWNER TO UserId { @@ -2956,7 +2949,7 @@ CreatedbStmt: CREATE DATABASE database_name WITH createdb_opt_location createdb CreatedbStmt *n; if ($5 == NULL && $6 == -1) - elog(ERROR, "CREATE DATABASE WITH requires at least one option."); + elog(ERROR, "CREATE DATABASE WITH requires at least one option"); n = makeNode(CreatedbStmt); n->dbname = $3; @@ -3465,7 +3458,7 @@ SelectStmt: select_clause sort_clause for_update_clause opt_select_limit /* This rule parses Select statements that can appear within set operations, * including UNION, INTERSECT and EXCEPT. '(' and ')' can be used to specify * the ordering of the set operations. Without '(' and ')' we want the - * operations to be left associative. + * operations to be ordered per the precedence specs at the head of this file. * * Note that sort clauses cannot be included at this level --- a sort clause * can only appear at the end of the complete Select, and it will be handled @@ -3486,10 +3479,12 @@ select_clause: '(' select_clause ')' { $$ = $1; } - | select_clause EXCEPT select_clause + | select_clause EXCEPT opt_all select_clause { $$ = (Node *)makeA_Expr(AND,NULL,$1, - makeA_Expr(NOT,NULL,NULL,$3)); + makeA_Expr(NOT,NULL,NULL,$4)); + if ($3) + elog(ERROR, "EXCEPT ALL is not implemented yet"); } | select_clause UNION opt_all select_clause { @@ -3506,9 +3501,11 @@ select_clause: '(' select_clause ')' } $$ = (Node *)makeA_Expr(OR,NULL,$1,$4); } - | select_clause INTERSECT select_clause + | select_clause INTERSECT opt_all select_clause { - $$ = (Node *)makeA_Expr(AND,NULL,$1,$3); + $$ = (Node *)makeA_Expr(AND,NULL,$1,$4); + if ($3) + elog(ERROR, "INTERSECT ALL is not implemented yet"); } ; @@ -3741,113 +3738,63 @@ update_list: OF va_list { $$ = $2; } *****************************************************************************/ from_clause: FROM from_list { $$ = $2; } -/*** -#ifdef ENABLE_ORACLE_JOIN_SYNTAX - | FROM oracle_list { $$ = $2; } -#endif -***/ - | FROM from_expr { $$ = lcons($2, NIL); } | /*EMPTY*/ { $$ = NIL; } ; -from_list: from_list ',' table_expr { $$ = lappend($1, $3); } - | table_expr { $$ = lcons($1, NIL); } - ; - -/*********** - * This results in one shift/reduce conflict, presumably due to the trailing "(+)" - * - Thomas 1999-09-20 - * -#ifdef ENABLE_ORACLE_JOIN_SYNTAX -oracle_list: oracle_expr { $$ = lcons($1, NIL); } - ; - -oracle_expr: ColId ',' ColId oracle_outer - { - elog(ERROR,"Oracle OUTER JOIN not yet supported"); - $$ = NULL; - } - | oracle_outer ColId ',' ColId - { - elog(ERROR,"Oracle OUTER JOIN not yet supported"); - $$ = NULL; - } - ; - -oracle_outer: '(' '+' ')' { $$ = TRUE; } - ; -#endif -***********/ - -from_expr: '(' join_clause_with_union ')' alias_clause - { - JoinExpr *j = $2; - j->alias = $4; - $$ = j; - } - | join_clause - { $$ = $1; } - ; - -table_expr: relation_expr alias_clause - { - $$ = makeNode(RangeVar); - $$->relExpr = $1; - $$->name = $2; - -#ifdef DISABLE_JOIN_SYNTAX - if (($2 != NULL) && ($2->attrs != NULL)) - elog(ERROR, "Column aliases in table expressions not yet supported"); -#endif - } +from_list: from_list ',' table_ref { $$ = lappend($1, $3); } + | table_ref { $$ = lcons($1, NIL); } ; -alias_clause: AS ColId '(' name_list ')' - { - $$ = makeNode(Attr); - $$->relname = $2; - $$->attrs = $4; - } - | AS ColId +/* + * table_ref is where an alias clause can be attached. Note we cannot make + * alias_clause have an empty production because that causes parse conflicts + * between table_ref := '(' joined_table ')' alias_clause + * and joined_table := '(' joined_table ')'. So, we must have the + * redundant-looking productions here instead. + */ +table_ref: relation_expr { - $$ = makeNode(Attr); - $$->relname = $2; + $$ = (Node *) $1; } - | ColId '(' name_list ')' + | relation_expr alias_clause { - $$ = makeNode(Attr); - $$->relname = $1; - $$->attrs = $3; + $1->name = $2; + $$ = (Node *) $1; } - | ColId + | '(' select_clause ')' { - $$ = makeNode(Attr); - $$->relname = $1; + RangeSubselect *n = makeNode(RangeSubselect); + n->subquery = $2; + n->name = NULL; + $$ = (Node *) n; } - | /*EMPTY*/ + | '(' select_clause ')' alias_clause { - $$ = NULL; /* no qualifiers */ + RangeSubselect *n = makeNode(RangeSubselect); + n->subquery = $2; + n->name = $4; + $$ = (Node *) n; } - ; - -/* A UNION JOIN is the same as a FULL OUTER JOIN which *omits* - * all result rows which would have matched on an INNER JOIN. - * Syntactically, must enclose the UNION JOIN in parens to avoid - * conflicts with SELECT/UNION. - */ -join_clause: join_clause join_expr + | joined_table { - $2->larg = (Node *)$1; - $$ = $2; + $$ = (Node *) $1; } - | table_expr join_expr + | '(' joined_table ')' alias_clause { - $2->larg = (Node *)$1; - $$ = $2; + $2->alias = $4; + $$ = (Node *) $2; } ; -/* This is everything but the left side of a join. +/* + * It may seem silly to separate joined_table from table_ref, but there is + * method in SQL92's madness: if you don't do it this way you get reduce- + * reduce conflicts, because it's not clear to the parser generator whether + * to expect alias_clause after ')' or not. For the same reason we must + * treat 'JOIN' and 'join_type JOIN' separately, rather than allowing + * join_type to expand to empty; if we try it, the parser generator can't + * figure out when to reduce an empty join_type right after table_ref. + * * Note that a CROSS JOIN is the same as an unqualified * INNER JOIN, and an INNER JOIN/ON has the same shape * but a qualification expression to limit membership. @@ -3855,71 +3802,122 @@ join_clause: join_clause join_expr * tables and the shape is determined by which columns are * in common. We'll collect columns during the later transformations. */ -join_expr: join_type JOIN table_expr join_qual + +joined_table: '(' joined_table ')' + { + $$ = $2; + } + | table_ref CROSS JOIN table_ref { + /* CROSS JOIN is same as unqualified inner join */ JoinExpr *n = makeNode(JoinExpr); - n->jointype = $1; - n->rarg = (Node *)$3; - n->quals = (List *)$4; + n->jointype = JOIN_INNER; + n->isNatural = FALSE; + n->larg = $1; + n->rarg = $4; + n->using = NIL; + n->quals = NULL; $$ = n; } - | NATURAL join_type JOIN table_expr + | table_ref UNIONJOIN table_ref { + /* UNION JOIN is made into 1 token to avoid shift/reduce + * conflict against regular UNION keyword. + */ JoinExpr *n = makeNode(JoinExpr); - n->jointype = $2; - n->isNatural = TRUE; - n->rarg = (Node *)$4; - n->quals = NULL; /* figure out which columns later... */ + n->jointype = JOIN_UNION; + n->isNatural = FALSE; + n->larg = $1; + n->rarg = $3; + n->using = NIL; + n->quals = NULL; $$ = n; } - | CROSS JOIN table_expr + | table_ref join_type JOIN table_ref join_qual { JoinExpr *n = makeNode(JoinExpr); - n->jointype = INNER_P; + n->jointype = $2; n->isNatural = FALSE; - n->rarg = (Node *)$3; - n->quals = NULL; + n->larg = $1; + n->rarg = $4; + if ($5 != NULL && IsA($5, List)) + n->using = (List *) $5; /* USING clause */ + else + n->quals = $5; /* ON clause */ $$ = n; } - ; - -join_clause_with_union: join_clause_with_union join_expr_with_union + | table_ref JOIN table_ref join_qual { - $2->larg = (Node *)$1; - $$ = $2; + /* letting join_type reduce to empty doesn't work */ + JoinExpr *n = makeNode(JoinExpr); + n->jointype = JOIN_INNER; + n->isNatural = FALSE; + n->larg = $1; + n->rarg = $3; + if ($4 != NULL && IsA($4, List)) + n->using = (List *) $4; /* USING clause */ + else + n->quals = $4; /* ON clause */ + $$ = n; } - | table_expr join_expr_with_union + | table_ref NATURAL join_type JOIN table_ref { - $2->larg = (Node *)$1; - $$ = $2; + JoinExpr *n = makeNode(JoinExpr); + n->jointype = $3; + n->isNatural = TRUE; + n->larg = $1; + n->rarg = $5; + n->using = NIL; /* figure out which columns later... */ + n->quals = NULL; /* fill later */ + $$ = n; } - ; - -join_expr_with_union: join_expr - { $$ = $1; } - | UNION JOIN table_expr + | table_ref NATURAL JOIN table_ref { + /* letting join_type reduce to empty doesn't work */ JoinExpr *n = makeNode(JoinExpr); - n->jointype = UNION; - n->rarg = (Node *)$3; - n->quals = NULL; + n->jointype = JOIN_INNER; + n->isNatural = TRUE; + n->larg = $1; + n->rarg = $4; + n->using = NIL; /* figure out which columns later... */ + n->quals = NULL; /* fill later */ $$ = n; + } + ; - elog(ERROR,"UNION JOIN not yet implemented"); +alias_clause: AS ColId '(' name_list ')' + { + $$ = makeNode(Attr); + $$->relname = $2; + $$->attrs = $4; + } + | AS ColId + { + $$ = makeNode(Attr); + $$->relname = $2; + } + | ColId '(' name_list ')' + { + $$ = makeNode(Attr); + $$->relname = $1; + $$->attrs = $3; + } + | ColId + { + $$ = makeNode(Attr); + $$->relname = $1; } ; -/* OUTER is just noise... */ -join_type: FULL join_outer { $$ = FULL; } - | LEFT join_outer { $$ = LEFT; } - | RIGHT join_outer { $$ = RIGHT; } - | OUTER_P { $$ = LEFT; } - | INNER_P { $$ = INNER_P; } - | /*EMPTY*/ { $$ = INNER_P; } +join_type: FULL join_outer { $$ = JOIN_FULL; } + | LEFT join_outer { $$ = JOIN_LEFT; } + | RIGHT join_outer { $$ = JOIN_RIGHT; } + | INNER_P { $$ = JOIN_INNER; } ; +/* OUTER is just noise... */ join_outer: OUTER_P { $$ = NULL; } - | /*EMPTY*/ { $$ = NULL; /* no qualifiers */ } + | /*EMPTY*/ { $$ = NULL; } ; /* JOIN qualification clauses @@ -3927,60 +3925,43 @@ join_outer: OUTER_P { $$ = NULL; } * USING ( column list ) allows only unqualified column names, * which must match between tables. * ON expr allows more general qualifications. - * - thomas 1999-01-07 + * + * We return USING as a List node, while an ON-expr will not be a List. */ -join_qual: USING '(' using_list ')' { $$ = (Node *)$3; } - | ON a_expr { $$ = (Node *)$2; } +join_qual: USING '(' name_list ')' { $$ = (Node *) $3; } + | ON a_expr { $$ = $2; } ; -using_list: using_list ',' using_expr { $$ = lappend($1, $3); } - | using_expr { $$ = lcons($1, NIL); } - ; - -using_expr: ColId - { - /* could be a column name or a relation_name */ - Ident *n = makeNode(Ident); - n->name = $1; - n->indirection = NULL; - $$ = n; - } - ; - -where_clause: WHERE a_expr { $$ = $2; } - | /*EMPTY*/ { $$ = NULL; /* no qualifiers */ } - ; relation_expr: relation_name { /* default inheritance */ - $$ = makeNode(RelExpr); + $$ = makeNode(RangeVar); $$->relname = $1; $$->inh = SQL_inheritance; + $$->name = NULL; } | relation_name '*' %prec '=' { /* inheritance query */ - $$ = makeNode(RelExpr); + $$ = makeNode(RangeVar); $$->relname = $1; $$->inh = TRUE; + $$->name = NULL; } | ONLY relation_name %prec '=' { /* no inheritance */ - $$ = makeNode(RelExpr); + $$ = makeNode(RangeVar); $$->relname = $2; $$->inh = FALSE; + $$->name = NULL; } ; -opt_array_bounds: '[' ']' opt_array_bounds - { $$ = lcons(makeInteger(-1), $3); } - | '[' Iconst ']' opt_array_bounds - { $$ = lcons(makeInteger($2), $4); } - | /*EMPTY*/ - { $$ = NIL; } +where_clause: WHERE a_expr { $$ = $2; } + | /*EMPTY*/ { $$ = NULL; /* no qualifiers */ } ; @@ -4023,6 +4004,14 @@ Typename: SimpleTypename opt_array_bounds } ; +opt_array_bounds: '[' ']' opt_array_bounds + { $$ = lcons(makeInteger(-1), $3); } + | '[' Iconst ']' opt_array_bounds + { $$ = lcons(makeInteger($2), $4); } + | /*EMPTY*/ + { $$ = NIL; } + ; + SimpleTypename: ConstTypename | ConstInterval ; @@ -6024,29 +6013,19 @@ xlateSqlType(char *name) void parser_init(Oid *typev, int nargs) { + saved_relname[0] = '\0'; QueryIsRule = FALSE; - saved_relname[0]= '\0'; - - param_type_init(typev, nargs); -} - - -/* - * param_type_init() - * - * Keep enough information around to fill out the type of param nodes - * used in postquel functions - */ -static void -param_type_init(Oid *typev, int nargs) -{ - pfunc_num_args = nargs; + /* + * Keep enough information around to fill out the type of param nodes + * used in postquel functions + */ param_type_info = typev; + pfunc_num_args = nargs; } Oid param_type(int t) { - if ((t > pfunc_num_args) || (t == 0)) + if ((t > pfunc_num_args) || (t <= 0)) return InvalidOid; return param_type_info[t - 1]; } diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c index bbc8f5c7076..955be022e4e 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_agg.c,v 1.39 2000/07/17 03:05:02 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_agg.c,v 1.40 2000/09/12 21:07:02 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -152,6 +152,11 @@ parseCheckAggregates(ParseState *pstate, Query *qry) */ if (contain_agg_clause(qry->qual)) elog(ERROR, "Aggregates not allowed in WHERE clause"); + /* + * ON-conditions in JOIN expressions are like WHERE clauses. + */ + if (contain_agg_clause((Node *) qry->jointree)) + elog(ERROR, "Aggregates not allowed in JOIN conditions"); /* * No aggregates allowed in GROUP BY clauses, either. diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index 3f874cc9643..c35b41b911b 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.65 2000/06/15 03:32:19 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.66 2000/09/12 21:07:02 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -18,7 +18,9 @@ #include "access/heapam.h" #include "optimizer/tlist.h" #include "nodes/makefuncs.h" +#include "parser/analyze.h" #include "parser/parse.h" +#include "parser/parsetree.h" #include "parser/parse_clause.h" #include "parser/parse_coerce.h" #include "parser/parse_expr.h" @@ -33,57 +35,81 @@ static char *clauseText[] = {"ORDER BY", "GROUP BY", "DISTINCT ON"}; +static void extractUniqueColumns(List *common_colnames, + List *src_colnames, List *src_colvars, + List **res_colnames, List **res_colvars); +static Node *transformUsingClause(ParseState *pstate, + List *leftVars, List *rightVars); +static RangeTblRef *transformTableEntry(ParseState *pstate, RangeVar *r); +static RangeTblRef *transformRangeSubselect(ParseState *pstate, + RangeSubselect *r); +static Node *transformFromClauseItem(ParseState *pstate, Node *n); static TargetEntry *findTargetlistEntry(ParseState *pstate, Node *node, List *tlist, int clause); -static void parseFromClause(ParseState *pstate, List *frmList); -static RangeTblEntry *transformTableEntry(ParseState *pstate, RangeVar *r); static List *addTargetToSortList(TargetEntry *tle, List *sortlist, List *targetlist, char *opname); static bool exprIsInSortList(Node *expr, List *sortList, List *targetList); -#ifndef DISABLE_OUTER_JOINS -static List *transformUsingClause(ParseState *pstate, List *using, - List *left, List *right); -#endif - /* * makeRangeTable - * Build the initial range table from the FROM clause. + * + * The range table constructed here may grow as we transform the expressions + * in the query's quals and target list. (Note that this happens because in + * POSTQUEL, we allow references to relations not specified in the + * from-clause. PostgreSQL keeps this extension to standard SQL.) + * + * Note: we assume that pstate's p_rtable and p_jointree lists were + * initialized to NIL when the pstate was created. We will add onto + * any entries already present --- this is needed for rule processing! */ void makeRangeTable(ParseState *pstate, List *frmList) { - /* Currently, nothing to do except this: */ - parseFromClause(pstate, frmList); + List *fl; + + /* + * The grammar will have produced a list of RangeVars, RangeSubselects, + * and/or JoinExprs. Transform each one, and then add it to the join tree. + */ + foreach(fl, frmList) + { + Node *n = lfirst(fl); + + n = transformFromClauseItem(pstate, n); + pstate->p_jointree = lappend(pstate->p_jointree, n); + } } /* * setTargetTable - * Add the target relation of INSERT or UPDATE to the range table, + * Add the target relation of INSERT/UPDATE/DELETE to the range table, * and make the special links to it in the ParseState. * - * Note that the target is not marked as either inFromCl or inJoinSet. + * inJoinSet says whether to add the target to the join tree. * For INSERT, we don't want the target to be joined to; it's a * destination of tuples, not a source. For UPDATE/DELETE, we do - * need to scan or join the target. This will happen without the - * inJoinSet flag because the planner's preprocess_targetlist() - * adds the destination's CTID attribute to the targetlist, and - * therefore the destination will be a referenced table even if - * there is no other use of any of its attributes. Tricky, eh? + * need to scan or join the target. */ void -setTargetTable(ParseState *pstate, char *relname, bool inh) +setTargetTable(ParseState *pstate, char *relname, bool inh, bool inJoinSet) { RangeTblEntry *rte; /* look for relname only at current nesting level... */ if (refnameRangeTablePosn(pstate, relname, NULL) == 0) - rte = addRangeTableEntry(pstate, relname, - makeAttr(relname, NULL), - inh, FALSE, FALSE); + { + rte = addRangeTableEntry(pstate, relname, NULL, inh, false); + } else + { rte = refnameRangeTableEntry(pstate, relname); + /* XXX what if pre-existing entry has wrong inh setting? */ + } + + if (inJoinSet) + addRTEtoJoinTree(pstate, rte); /* This could only happen for multi-action rules */ if (pstate->p_target_relation != NULL) @@ -95,625 +121,500 @@ setTargetTable(ParseState *pstate, char *relname, bool inh) } -static Node * -mergeInnerJoinQuals(ParseState *pstate, Node *clause) -{ - List *jquals; - - foreach(jquals, pstate->p_join_quals) - { - Node *jqual = (Node *) lfirst(jquals); - - if (clause == NULL) - clause = jqual; - else - { - A_Expr *a = makeNode(A_Expr); - - a->oper = AND; - a->opname = NULL; - a->lexpr = clause; - a->rexpr = jqual; - clause = (Node *) a; - } - } - - /* Make sure that we don't add same quals twice... */ - pstate->p_join_quals = NIL; - - return clause; -} /* mergeInnerJoinQuals() */ - /* - * transformWhereClause - - * transforms the qualification and make sure it is of type Boolean + * Extract all not-in-common columns from column lists of a source table */ -Node * -transformWhereClause(ParseState *pstate, Node *clause) -{ - Node *qual; - - if (pstate->p_join_quals != NIL) - clause = mergeInnerJoinQuals(pstate, clause); - - if (clause == NULL) - return NULL; - - pstate->p_in_where_clause = true; - qual = transformExpr(pstate, clause, 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; -} - -#ifndef DISABLE_JOIN_SYNTAX -char * - AttrString(Attr *attr); - -char * -AttrString(Attr *attr) -{ - Value *val; - - Assert(length(attr->attrs) == 1); - - val = lfirst(attr->attrs); - - Assert(IsA(val, String)); - - return strVal(val); -} - -List * - ListTableAsAttrs(ParseState *pstate, char *table); -List * -ListTableAsAttrs(ParseState *pstate, char *table) -{ - Attr *attr = expandTable(pstate, table, TRUE); - List *rlist = NIL; - List *col; - - foreach(col, attr->attrs) - { - Attr *a = makeAttr(table, strVal((Value *) lfirst(col))); - - rlist = lappend(rlist, a); - } - - return rlist; -} - -List * - makeUniqueAttrList(List *candidates, List *idents); -List * -makeUniqueAttrList(List *attrs, List *filter) +static void +extractUniqueColumns(List *common_colnames, + List *src_colnames, List *src_colvars, + List **res_colnames, List **res_colvars) { - List *result = NULL; - List *candidate; + List *new_colnames = NIL; + List *new_colvars = NIL; + List *lnames, + *lvars = src_colvars; - foreach(candidate, attrs) + foreach(lnames, src_colnames) { - List *fmember; - bool match = FALSE; - Attr *cattr = lfirst(candidate); + char *colname = strVal(lfirst(lnames)); + bool match = false; + List *cnames; - Assert(IsA(cattr, Attr)); - Assert(length(cattr->attrs) == 1); - - foreach(fmember, filter) + foreach(cnames, common_colnames) { - Attr *fattr = lfirst(fmember); - - Assert(IsA(fattr, Attr)); - Assert(length(fattr->attrs) == 1); + char *ccolname = strVal(lfirst(cnames)); - if (strcmp(strVal(lfirst(cattr->attrs)), strVal(lfirst(fattr->attrs))) == 0) + if (strcmp(colname, ccolname) == 0) { - match = TRUE; + match = true; break; } } if (!match) - result = lappend(result, cattr); - } - - return result; -} - -List * - makeAttrList(Attr *attr); - -List * -makeAttrList(Attr *attr) -{ - List *result = NULL; - - char *name = attr->relname; - List *col; - - foreach(col, attr->attrs) - { - Attr *newattr = makeAttr(name, strVal((Value *) lfirst(col))); - - result = lappend(result, newattr); - } - - return result; -} -#ifdef NOT_USED -/* ExpandAttrs() - * Take an existing attribute node and return a list of attribute nodes - * with one attribute name per node. - */ -List * -ExpandAttrs(Attr *attr) -{ - List *col; - char *relname = attr->relname; - List *rlist = NULL; - - Assert(attr != NULL); - - if ((attr->attrs == NULL) || (length(attr->attrs) <= 1)) - return lcons(attr, NIL); - - foreach(col, attr->attrs) - { - Attr *attr = lfirst(col); + { + new_colnames = lappend(new_colnames, lfirst(lnames)); + new_colvars = lappend(new_colvars, lfirst(lvars)); + } - rlist = lappend(rlist, makeAttr(relname, AttrString(attr))); + lvars = lnext(lvars); } - return rlist; + *res_colnames = new_colnames; + *res_colvars = new_colvars; } -#endif /* transformUsingClause() - * Take an ON or USING clause from a join expression and expand if necessary. - * Result is an implicitly-ANDed list of untransformed qualification clauses. + * Build a complete ON clause from a partially-transformed USING list. + * We are given lists of Var nodes representing left and right match columns. + * Result is a transformed qualification expression. */ -static List * -transformUsingClause(ParseState *pstate, List *usingList, - List *leftList, List *rightList) +static Node * +transformUsingClause(ParseState *pstate, List *leftVars, List *rightVars) { - List *result = NIL; - List *using; + Node *result = NULL; + List *lvars, + *rvars = rightVars; - foreach(using, usingList) + /* + * We cheat a little bit here by building an untransformed operator + * tree whose leaves are the already-transformed Vars. This is OK + * because transformExpr() won't complain about already-transformed + * subnodes. + */ + foreach(lvars, leftVars) { - Attr *uattr = lfirst(using); - Attr *lattr = NULL, - *rattr = NULL; - List *col; + Node *lvar = (Node *) lfirst(lvars); + Node *rvar = (Node *) lfirst(rvars); A_Expr *e; - /* - * find the first instances of this column in the shape list and - * the last table in the shape list... - */ - foreach(col, leftList) - { - Attr *attr = lfirst(col); + e = makeNode(A_Expr); + e->oper = OP; + e->opname = "="; + e->lexpr = copyObject(lvar); + e->rexpr = copyObject(rvar); - if (strcmp(AttrString(attr), AttrString(uattr)) == 0) - { - lattr = attr; - break; - } - } - foreach(col, rightList) + if (result == NULL) + result = (Node *) e; + else { - Attr *attr = lfirst(col); + A_Expr *a = makeNode(A_Expr); - if (strcmp(AttrString(attr), AttrString(uattr)) == 0) - { - rattr = attr; - break; - } + a->oper = AND; + a->opname = NULL; + a->lexpr = result; + a->rexpr = (Node *) e; + result = (Node *) a; } - Assert((lattr != NULL) && (rattr != NULL)); + rvars = lnext(rvars); + } - e = makeNode(A_Expr); - e->oper = OP; - e->opname = "="; - e->lexpr = (Node *) lattr; - e->rexpr = (Node *) rattr; + result = transformExpr(pstate, result, EXPR_COLUMN_FIRST); - result = lappend(result, e); + if (exprType(result) != BOOLOID) + { + /* This could only happen if someone defines a funny version of '=' */ + elog(ERROR, "USING clause must return type bool, not type %s", + typeidTypeName(exprType(result))); } return result; } /* transformUsingClause() */ -#endif - -static RangeTblEntry * +/* + * transformTableEntry --- transform a RangeVar (simple relation reference) + */ +static RangeTblRef * transformTableEntry(ParseState *pstate, RangeVar *r) { - RelExpr *baserel = r->relExpr; - char *relname = baserel->relname; - -#if 0 - char *refname; - List *columns; - -#endif + char *relname = r->relname; RangeTblEntry *rte; - -#if 0 - if (r->name != NULL) - refname = r->name->relname; - else - refname = NULL; - - columns = ListTableAsAttrs(pstate, relname); - - /* alias might be specified... */ - if (r->name != NULL) - { -#ifndef DISABLE_JOIN_SYNTAX - if (length(columns) > 0) - { - if (length(r->name->attrs) > 0) - { - if (length(columns) != length(r->name->attrs)) - elog(ERROR, "'%s' has %d columns but %d %s specified", - relname, length(columns), length(r->name->attrs), - ((length(r->name->attrs) != 1) ? "aliases" : "alias")); - - aliasList = nconc(aliasList, r->name->attrs); - } - else - { - r->name->attrs = columns; - - aliasList = nconc(aliasList, r->name->attrs); - } - } - else - elog(NOTICE, "transformTableEntry: column aliases not handled (internal error)"); -#else - elog(ERROR, "Column aliases not yet supported"); -#endif - } - else - { - refname = relname; - aliasList = nconc(aliasList, columns); - } -#endif - - if (r->name == NULL) - r->name = makeAttr(relname, NULL); + RangeTblRef *rtr; /* - * marks this entry to indicate it comes from the FROM clause. In SQL, + * mark 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, r->name, r->inh, true); - rte = addRangeTableEntry(pstate, relname, r->name, - baserel->inh, TRUE, TRUE); + /* + * We create a RangeTblRef, but we do not add it to the jointree here. + * makeRangeTable will do so, if we are at top level of the FROM clause. + */ + rtr = makeNode(RangeTblRef); + /* assume new rte is at end */ + rtr->rtindex = length(pstate->p_rtable); + Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable)); - return rte; -} /* transformTableEntry() */ + return rtr; +} /* - * 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 + * transformRangeSubselect --- transform a sub-SELECT appearing in FROM */ -static void -parseFromClause(ParseState *pstate, List *frmList) +static RangeTblRef * +transformRangeSubselect(ParseState *pstate, RangeSubselect *r) { - List *fl; + SelectStmt *subquery = (SelectStmt *) r->subquery; + List *parsetrees; + Query *query; - foreach(fl, frmList) - { - Node *n = lfirst(fl); + /* + * subquery node might not be SelectStmt if user wrote something like + * FROM (SELECT ... UNION SELECT ...). Our current implementation of + * UNION/INTERSECT/EXCEPT is too messy to deal with here, so punt until + * we redesign querytrees to make it more reasonable. + */ + if (subquery == NULL || !IsA(subquery, SelectStmt)) + elog(ERROR, "Set operations not yet supported in subselects in FROM"); - /* - * 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. - */ + /* + * Analyze and transform the subquery as if it were an independent + * statement (we do NOT want it to see the outer query as a parent). + */ + parsetrees = parse_analyze(lcons(subquery, NIL), NULL); - /* Plain vanilla inner join, just like we've always had? */ - if (IsA(n, RangeVar)) - transformTableEntry(pstate, (RangeVar *) n); + /* + * Check that we got something reasonable. Some of these conditions + * are probably impossible given restrictions of the grammar, but + * check 'em anyway. + */ + if (length(parsetrees) != 1) + elog(ERROR, "Unexpected parse analysis result for subselect in FROM"); + query = (Query *) lfirst(parsetrees); + if (query == NULL || !IsA(query, Query)) + elog(ERROR, "Unexpected parse analysis result for subselect in FROM"); - /* A newfangled join expression? */ - else if (IsA(n, JoinExpr)) - { -#ifndef DISABLE_JOIN_SYNTAX - RangeTblEntry *l_rte, - *r_rte; - Attr *l_name, - *r_name = NULL; - JoinExpr *j = (JoinExpr *) n; - - if (j->alias != NULL) - elog(ERROR, "JOIN table aliases are not supported"); - - /* nested join? then handle the left one first... */ - if (IsA(j->larg, JoinExpr)) - { - parseFromClause(pstate, lcons(j->larg, NIL)); - l_name = ((JoinExpr *) j->larg)->alias; - } - else - { - Assert(IsA(j->larg, RangeVar)); - l_rte = transformTableEntry(pstate, (RangeVar *) j->larg); - l_name = expandTable(pstate, l_rte->eref->relname, TRUE); - } + if (query->commandType != CMD_SELECT) + elog(ERROR, "Expected SELECT query from subselect in FROM"); + if (query->resultRelation != 0 || query->into != NULL) + elog(ERROR, "Subselect in FROM may not have SELECT INTO"); - if (IsA(j->rarg, JoinExpr)) - { - parseFromClause(pstate, lcons(j->rarg, NIL)); - l_name = ((JoinExpr *) j->larg)->alias; - } - else - { - Assert(IsA(j->rarg, RangeVar)); - r_rte = transformTableEntry(pstate, (RangeVar *) j->rarg); - r_name = expandTable(pstate, r_rte->eref->relname, TRUE); - } - /* - * Natural join does not explicitly specify columns; must - * generate columns to join. Need to run through the list of - * columns from each table or join result and match up the - * column names. Use the first table, and check every column - * in the second table for a match. - */ - if (j->isNatural) - { - List *lx, - *rx; - List *rlist = NULL; + elog(ERROR, "Subselect in FROM not done yet"); - foreach(lx, l_name->attrs) - { - Ident *id = NULL; - Value *l_col = lfirst(lx); + return NULL; +} - Assert(IsA(l_col, String)); - foreach(rx, r_name->attrs) - { - Value *r_col = lfirst(rx); +/* + * transformFromClauseItem - + * Transform a FROM-clause item, adding any required entries to the + * range table list being built in the ParseState, and return the + * transformed item ready to include in the jointree list. + * This routine can recurse to handle SQL92 JOIN expressions. + */ +static Node * +transformFromClauseItem(ParseState *pstate, Node *n) +{ + if (IsA(n, RangeVar)) + { + /* Plain relation reference */ + return (Node *) transformTableEntry(pstate, (RangeVar *) n); + } + else if (IsA(n, RangeSubselect)) + { + /* Plain relation reference */ + return (Node *) transformRangeSubselect(pstate, (RangeSubselect *) n); + } + else if (IsA(n, JoinExpr)) + { + /* A newfangled join expression */ + JoinExpr *j = (JoinExpr *) n; + List *l_colnames, + *r_colnames, + *res_colnames, + *l_colvars, + *r_colvars, + *res_colvars; - Assert(IsA(r_col, String)); + /* + * Recursively process the left and right subtrees + */ + j->larg = transformFromClauseItem(pstate, j->larg); + j->rarg = transformFromClauseItem(pstate, j->rarg); - if (strcmp(strVal(l_col), strVal(r_col)) == 0) - { - id = (Ident *) makeNode(Ident); - id->name = strVal(l_col); - break; - } - } + /* + * Extract column name and var lists from both subtrees + */ + if (IsA(j->larg, JoinExpr)) + { + /* Make a copy of the subtree's lists so we can modify! */ + l_colnames = copyObject(((JoinExpr *) j->larg)->colnames); + l_colvars = copyObject(((JoinExpr *) j->larg)->colvars); + } + else + { + RangeTblEntry *rte; - /* right column matched? then keep as join column... */ - if (id != NULL) - rlist = lappend(rlist, id); - } - j->quals = rlist; + Assert(IsA(j->larg, RangeTblRef)); + rte = rt_fetch(((RangeTblRef *) j->larg)->rtindex, + pstate->p_rtable); + expandRTE(pstate, rte, &l_colnames, &l_colvars); + /* expandRTE returns new lists, so no need for copyObject */ + } + if (IsA(j->rarg, JoinExpr)) + { + /* Make a copy of the subtree's lists so we can modify! */ + r_colnames = copyObject(((JoinExpr *) j->rarg)->colnames); + r_colvars = copyObject(((JoinExpr *) j->rarg)->colvars); + } + else + { + RangeTblEntry *rte; - printf("NATURAL JOIN columns are %s\n", nodeToString(rlist)); - } + Assert(IsA(j->rarg, RangeTblRef)); + rte = rt_fetch(((RangeTblRef *) j->rarg)->rtindex, + pstate->p_rtable); + expandRTE(pstate, rte, &r_colnames, &r_colvars); + /* expandRTE returns new lists, so no need for copyObject */ + } - if (j->jointype == INNER_P) + /* + * Natural join does not explicitly specify columns; must + * generate columns to join. Need to run through the list of + * columns from each table or join result and match up the + * column names. Use the first table, and check every column + * in the second table for a match. (We'll check that the + * matches were unique later on.) + * The result of this step is a list of column names just like an + * explicitly-written USING list. + */ + if (j->isNatural) + { + List *rlist = NIL; + List *lx, + *rx; + + Assert(j->using == NIL); /* shouldn't have USING() too */ + + foreach(lx, l_colnames) { - /* CROSS JOIN */ - if (j->quals == NULL) - printf("CROSS JOIN...\n"); + char *l_colname = strVal(lfirst(lx)); + Value *m_name = NULL; - /* - * JOIN/USING This is an inner join, so rip apart the join - * node and transform into a traditional FROM list. - * NATURAL JOIN and JOIN USING both change the shape of - * the result. Need to generate a list of result columns - * to use for target list expansion and validation. - */ - else if (IsA(j->quals, List)) + foreach(rx, r_colnames) { + char *r_colname = strVal(lfirst(rx)); - /* - * List of Ident nodes means column names from a real - * USING clause. Determine the shape of the joined - * table. - */ - List *ucols, - *ucol; - List *shape = NULL; - List *alias = NULL; - List *l_shape, - *r_shape; - - List *l_cols = makeAttrList(l_name); - List *r_cols = makeAttrList(r_name); - - printf("USING input tables are:\n %s\n %s\n", - nodeToString(l_name), nodeToString(r_name)); - - printf("USING expanded tables are:\n %s\n %s\n", - nodeToString(l_cols), nodeToString(r_cols)); - - /* Columns from the USING clause... */ - ucols = (List *) j->quals; - foreach(ucol, ucols) + if (strcmp(l_colname, r_colname) == 0) { - List *col; - Attr *l_attr = NULL, - *r_attr = NULL; - Ident *id = lfirst(ucol); - - Attr *attr = makeAttr("", id->name); - - foreach(col, l_cols) - { - attr = lfirst(col); - if (strcmp(AttrString(attr), id->name) == 0) - { - l_attr = attr; - break; - } - } - - foreach(col, r_cols) - { - attr = lfirst(col); - if (strcmp(AttrString(attr), id->name) == 0) - { - r_attr = attr; - break; - } - } - - if (l_attr == NULL) - elog(ERROR, "USING column '%s' not found in table '%s'", - id->name, l_name->relname); - if (r_attr == NULL) - elog(ERROR, "USING column '%s' not found in table '%s'", - id->name, r_name->relname); - - shape = lappend(shape, l_attr); - alias = lappend(alias, makeAttr("", AttrString(l_attr))); + m_name = makeString(l_colname); + break; } - printf("JOIN/USING join columns are %s\n", nodeToString(shape)); - - /* Remaining columns from the left side... */ - l_shape = makeUniqueAttrList(makeAttrList(l_name), shape); - - printf("JOIN/USING left columns are %s\n", nodeToString(l_shape)); - - r_shape = makeUniqueAttrList(makeAttrList(r_name), shape); + } - printf("JOIN/USING right columns are %s\n", nodeToString(r_shape)); + /* matched a right column? then keep as join column... */ + if (m_name != NULL) + rlist = lappend(rlist, m_name); + } - printf("JOIN/USING input quals are %s\n", nodeToString(j->quals)); + j->using = rlist; + } - j->quals = transformUsingClause(pstate, shape, l_cols, r_cols); + /* + * Now transform the join qualifications, if any. + */ + res_colnames = NIL; + res_colvars = NIL; - printf("JOIN/USING transformed quals are %s\n", nodeToString(j->quals)); + if (j->using) + { + /* + * JOIN/USING (or NATURAL JOIN, as transformed above). + * Transform the list into an explicit ON-condition, + * and generate a list of result columns. + */ + List *ucols = j->using; + List *l_usingvars = NIL; + List *r_usingvars = NIL; + List *ucol; - alias = nconc(nconc(alias, listCopy(l_shape)), listCopy(r_shape)); - shape = nconc(nconc(shape, l_shape), r_shape); + Assert(j->quals == NULL); /* shouldn't have ON() too */ - printf("JOIN/USING shaped table is %s\n", nodeToString(shape)); - printf("JOIN/USING alias list is %s\n", nodeToString(alias)); + foreach(ucol, ucols) + { + char *u_colname = strVal(lfirst(ucol)); + List *col; + Node *l_colvar, + *r_colvar, + *colvar; + int ndx; + int l_index = -1; + int r_index = -1; + + ndx = 0; + foreach(col, l_colnames) + { + char *l_colname = strVal(lfirst(col)); - pstate->p_shape = shape; - pstate->p_alias = alias; + if (strcmp(l_colname, u_colname) == 0) + { + if (l_index >= 0) + elog(ERROR, "Common column name \"%s\" appears more than once in left table", u_colname); + l_index = ndx; + } + ndx++; } + if (l_index < 0) + elog(ERROR, "USING column \"%s\" not found in left table", + u_colname); - /* otherwise, must be an expression from an ON clause... */ - else - j->quals = (List *) lcons(j->quals, NIL); - - /* listCopy may not be needed here --- will j->quals list - * be used again anywhere? The #ifdef'd code below may need - * it, if it ever gets used... - */ - pstate->p_join_quals = nconc(pstate->p_join_quals, - listCopy(j->quals)); - -#if 0 - if (qual == NULL) - elog(ERROR, "JOIN/ON not supported in this context"); - - printf("Table aliases are %s\n", nodeToString(*aliasList)); -#endif - -#if 0 - /* XXX this code is WRONG because j->quals is a List - * not a simple expression. Perhaps *qual - * ought also to be a List and we append to it, - * similarly to the way p_join_quals is handled above? - */ - if (*qual == NULL) + ndx = 0; + foreach(col, r_colnames) { - /* merge qualified join clauses... */ - if (j->quals != NULL) + char *r_colname = strVal(lfirst(col)); + + if (strcmp(r_colname, u_colname) == 0) { - if (*qual != NULL) - { - A_Expr *a = makeNode(A_Expr); - - a->oper = AND; - a->opname = NULL; - a->lexpr = (Node *) *qual; - a->rexpr = (Node *) j->quals; - - *qual = (Node *) a; - } - else - *qual = (Node *) j->quals; + if (r_index >= 0) + elog(ERROR, "Common column name \"%s\" appears more than once in right table", u_colname); + r_index = ndx; } + ndx++; } - else + if (r_index < 0) + elog(ERROR, "USING column \"%s\" not found in right table", + u_colname); + + l_colvar = nth(l_index, l_colvars); + l_usingvars = lappend(l_usingvars, l_colvar); + r_colvar = nth(r_index, r_colvars); + r_usingvars = lappend(r_usingvars, r_colvar); + + res_colnames = lappend(res_colnames, + nth(l_index, l_colnames)); + switch (j->jointype) { - elog(ERROR, "Multiple JOIN/ON clauses not handled (internal error)"); - *qual = lappend(*qual, j->quals); + case JOIN_INNER: + case JOIN_LEFT: + colvar = l_colvar; + break; + case JOIN_RIGHT: + colvar = r_colvar; + break; + default: + { + /* Need COALESCE(l_colvar, r_colvar) */ + CaseExpr *c = makeNode(CaseExpr); + CaseWhen *w = makeNode(CaseWhen); + A_Expr *a = makeNode(A_Expr); + + a->oper = NOTNULL; + a->lexpr = l_colvar; + w->expr = (Node *) a; + w->result = l_colvar; + c->args = lcons(w, NIL); + c->defresult = r_colvar; + colvar = transformExpr(pstate, (Node *) c, + EXPR_COLUMN_FIRST); + break; + } } -#endif - - /* - * 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! - */ + res_colvars = lappend(res_colvars, colvar); + } + j->quals = transformUsingClause(pstate, l_usingvars, r_usingvars); + } + else if (j->quals) + { + /* User-written ON-condition; transform it */ + j->quals = transformExpr(pstate, j->quals, EXPR_COLUMN_FIRST); + if (exprType(j->quals) != BOOLOID) + { + elog(ERROR, "ON clause must return type bool, not type %s", + typeidTypeName(exprType(j->quals))); } - else if ((j->jointype == LEFT) - || (j->jointype == RIGHT) - || (j->jointype == FULL)) - elog(ERROR, "OUTER JOIN is not yet supported"); - else - elog(ERROR, "Unrecognized JOIN clause; tag is %d (internal error)", - j->jointype); -#else - elog(ERROR, "JOIN expressions are not yet implemented"); -#endif + /* XXX should check that ON clause refers only to joined tbls */ } else - elog(ERROR, "parseFromClause: unexpected FROM clause node (internal error)" - "\n\t%s", nodeToString(n)); + { + /* CROSS JOIN: no quals */ + } + + /* Add remaining columns from each side to the output columns */ + extractUniqueColumns(res_colnames, + l_colnames, l_colvars, + &l_colnames, &l_colvars); + extractUniqueColumns(res_colnames, + r_colnames, r_colvars, + &r_colnames, &r_colvars); + res_colnames = nconc(res_colnames, l_colnames); + res_colvars = nconc(res_colvars, l_colvars); + res_colnames = nconc(res_colnames, r_colnames); + res_colvars = nconc(res_colvars, r_colvars); + + /* + * Process alias (AS clause), if any. + * + * The given table alias must be unique in the current nesting level, + * ie it cannot match any RTE refname or jointable alias. This is + * a bit painful to check because my own child joins are not yet in + * the pstate's jointree, so they have to be scanned separately. + */ + if (j->alias) + { + /* Check against previously created RTEs and jointree entries */ + if (refnameRangeOrJoinEntry(pstate, j->alias->relname, NULL)) + elog(ERROR, "Table name \"%s\" specified more than once", + j->alias->relname); + /* Check children */ + if (scanJoinTreeForRefname(j->larg, j->alias->relname) || + scanJoinTreeForRefname(j->rarg, j->alias->relname)) + elog(ERROR, "Table name \"%s\" specified more than once", + j->alias->relname); + /* + * If a column alias list is specified, substitute the alias + * names into my output-column list + */ + if (j->alias->attrs != NIL) + { + if (length(j->alias->attrs) != length(res_colnames)) + elog(ERROR, "Column alias list for \"%s\" has wrong number of entries (need %d)", + j->alias->relname, length(res_colnames)); + res_colnames = j->alias->attrs; + } + } + + j->colnames = res_colnames; + j->colvars = res_colvars; + + return (Node *) j; + } + else + elog(ERROR, "transformFromClauseItem: unexpected node (internal error)" + "\n\t%s", nodeToString(n)); + return NULL; /* can't get here, just keep compiler quiet */ +} + + +/* + * transformWhereClause - + * transforms the qualification and make sure it is of type Boolean + */ +Node * +transformWhereClause(ParseState *pstate, Node *clause) +{ + Node *qual; + + if (clause == NULL) + return NULL; + + qual = transformExpr(pstate, clause, EXPR_COLUMN_FIRST); + + if (exprType(qual) != BOOLOID) + { + elog(ERROR, "WHERE clause must return type bool, not type %s", + typeidTypeName(exprType(qual))); } -} /* parseFromClause() */ + return qual; +} /* @@ -786,10 +687,10 @@ findTargetlistEntry(ParseState *pstate, Node *node, List *tlist, int clause) * is a matching column. If so, fall through to let * transformExpr() do the rest. NOTE: if name could refer * ambiguously to more than one column name exposed by FROM, - * colnameRangeTableEntry will elog(ERROR). That's just what + * colnameToVar will elog(ERROR). That's just what * we want here. */ - if (colnameRangeTableEntry(pstate, name) != NULL) + if (colnameToVar(pstate, name) != NULL) name = NULL; } diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 7976f5e7795..a033ff4be22 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_expr.c,v 1.82 2000/08/08 15:42:03 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_expr.c,v 1.83 2000/09/12 21:07:02 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -157,41 +157,51 @@ transformExpr(ParseState *pstate, Node *expr, int precedence) { case OP: { - Node *lexpr = transformExpr(pstate, a->lexpr, precedence); - Node *rexpr = transformExpr(pstate, a->rexpr, precedence); + Node *lexpr = transformExpr(pstate, + a->lexpr, + precedence); + Node *rexpr = transformExpr(pstate, + a->rexpr, + precedence); result = (Node *) make_op(a->opname, lexpr, rexpr); } break; case ISNULL: { - Node *lexpr = transformExpr(pstate, a->lexpr, precedence); + Node *lexpr = transformExpr(pstate, + a->lexpr, + precedence); result = ParseFuncOrColumn(pstate, "nullvalue", lcons(lexpr, NIL), false, false, - &pstate->p_last_resno, precedence); } break; case NOTNULL: { - Node *lexpr = transformExpr(pstate, a->lexpr, precedence); + Node *lexpr = transformExpr(pstate, + a->lexpr, + precedence); result = ParseFuncOrColumn(pstate, "nonnullvalue", lcons(lexpr, NIL), false, false, - &pstate->p_last_resno, precedence); } break; case AND: { + Node *lexpr = transformExpr(pstate, + a->lexpr, + precedence); + Node *rexpr = transformExpr(pstate, + a->rexpr, + precedence); Expr *expr = makeNode(Expr); - Node *lexpr = transformExpr(pstate, a->lexpr, precedence); - Node *rexpr = transformExpr(pstate, a->rexpr, precedence); if (exprType(lexpr) != BOOLOID) elog(ERROR, "left-hand side of AND is type '%s', not '%s'", @@ -209,9 +219,13 @@ transformExpr(ParseState *pstate, Node *expr, int precedence) break; case OR: { + Node *lexpr = transformExpr(pstate, + a->lexpr, + precedence); + Node *rexpr = transformExpr(pstate, + a->rexpr, + precedence); Expr *expr = makeNode(Expr); - Node *lexpr = transformExpr(pstate, a->lexpr, precedence); - Node *rexpr = transformExpr(pstate, a->rexpr, precedence); if (exprType(lexpr) != BOOLOID) elog(ERROR, "left-hand side of OR is type '%s', not '%s'", @@ -227,8 +241,10 @@ transformExpr(ParseState *pstate, Node *expr, int precedence) break; case NOT: { + Node *rexpr = transformExpr(pstate, + a->rexpr, + precedence); Expr *expr = makeNode(Expr); - Node *rexpr = transformExpr(pstate, a->rexpr, precedence); if (exprType(rexpr) != BOOLOID) elog(ERROR, "argument to NOT is type '%s', not '%s'", @@ -254,13 +270,14 @@ transformExpr(ParseState *pstate, Node *expr, int precedence) /* transform the list of arguments */ foreach(args, fn->args) - lfirst(args) = transformExpr(pstate, (Node *) lfirst(args), precedence); + lfirst(args) = transformExpr(pstate, + (Node *) lfirst(args), + precedence); result = ParseFuncOrColumn(pstate, fn->funcname, fn->args, fn->agg_star, fn->agg_distinct, - &pstate->p_last_resno, precedence); break; } @@ -609,8 +626,7 @@ transformAttr(ParseState *pstate, Attr *att, int precedence) { Node *basenode; - basenode = ParseNestedFuncOrColumn(pstate, att, &pstate->p_last_resno, - precedence); + basenode = ParseNestedFuncOrColumn(pstate, att, precedence); return transformIndirection(pstate, basenode, att->indirection); } @@ -618,7 +634,6 @@ static Node * transformIdent(ParseState *pstate, Ident *ident, int precedence) { Node *result = NULL; - RangeTblEntry *rte; /* * try to find the ident as a relation ... but not if subscripts @@ -634,14 +649,10 @@ transformIdent(ParseState *pstate, Ident *ident, int precedence) if (result == NULL || precedence == EXPR_COLUMN_FIRST) { /* try to find the ident as a column */ - if ((rte = colnameRangeTableEntry(pstate, ident->name)) != NULL) - { - /* Convert it to a fully qualified Attr, and transform that */ - Attr *att = makeAttr(rte->eref->relname, ident->name); - - att->indirection = ident->indirection; - return transformAttr(pstate, att, precedence); - } + Node *var = colnameToVar(pstate, ident->name); + + if (var != NULL) + result = transformIndirection(pstate, var, ident->indirection); } if (result == NULL) diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index bad9401c609..1f19b1b949e 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_func.c,v 1.89 2000/08/24 03:29:05 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_func.c,v 1.90 2000/09/12 21:07:02 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -64,19 +64,20 @@ static Oid agg_select_candidate(Oid typeid, CandidateList candidates); ** a tree with of Iter and Func nodes. */ Node * -ParseNestedFuncOrColumn(ParseState *pstate, Attr *attr, int *curr_resno, int precedence) +ParseNestedFuncOrColumn(ParseState *pstate, Attr *attr, int precedence) { List *mutator_iter; Node *retval = NULL; if (attr->paramNo != NULL) { - Param *param = (Param *) transformExpr(pstate, (Node *) attr->paramNo, EXPR_RELATION_FIRST); + Param *param = (Param *) transformExpr(pstate, + (Node *) attr->paramNo, + EXPR_RELATION_FIRST); retval = ParseFuncOrColumn(pstate, strVal(lfirst(attr->attrs)), lcons(param, NIL), false, false, - curr_resno, precedence); } else @@ -88,7 +89,6 @@ ParseNestedFuncOrColumn(ParseState *pstate, Attr *attr, int *curr_resno, int pre retval = ParseFuncOrColumn(pstate, strVal(lfirst(attr->attrs)), lcons(ident, NIL), false, false, - curr_resno, precedence); } @@ -98,7 +98,6 @@ ParseNestedFuncOrColumn(ParseState *pstate, Attr *attr, int *curr_resno, int pre retval = ParseFuncOrColumn(pstate, strVal(lfirst(mutator_iter)), lcons(retval, NIL), false, false, - curr_resno, precedence); } @@ -241,17 +240,15 @@ agg_select_candidate(Oid typeid, CandidateList candidates) Node * ParseFuncOrColumn(ParseState *pstate, char *funcname, List *fargs, bool agg_star, bool agg_distinct, - int *curr_resno, int precedence) + int precedence) { Oid rettype = InvalidOid; Oid argrelid = InvalidOid; Oid funcid = InvalidOid; List *i = NIL; Node *first_arg = NULL; - char *relname = NULL; - char *refname = NULL; + char *refname; Relation rd; - Oid relid; int nargs = length(fargs); Func *funcnode; Oid oid_array[FUNC_MAX_ARGS]; @@ -283,81 +280,17 @@ ParseFuncOrColumn(ParseState *pstate, char *funcname, List *fargs, if (IsA(first_arg, Ident) && ((Ident *) first_arg)->isRel) { Ident *ident = (Ident *) first_arg; - RangeTblEntry *rte; - AttrNumber attnum; /* * first arg is a relation. This could be a projection. */ refname = ident->name; - rte = refnameRangeTableEntry(pstate, refname); - if (rte == NULL) - { - rte = addRangeTableEntry(pstate, refname, - makeAttr(refname, NULL), - FALSE, FALSE, TRUE); - warnAutoRange(pstate, refname); - } - - relname = rte->relname; - relid = rte->relid; - attnum = InvalidAttrNumber; - - /* - * If the attr isn't a set, just make a var for it. If it is - * a set, treat it like a function and drop through. Look - * through the explicit column list first, since we now allow - * column aliases. - thomas 2000-02-07 - */ - if (rte->eref->attrs != NULL) - { - List *c; - - /* - * start counting attributes/columns from one. zero is - * reserved for InvalidAttrNumber. - thomas 2000-01-27 - */ - int i = 1; - - foreach(c, rte->eref->attrs) - { - char *colname = strVal(lfirst(c)); - - /* found a match? */ - if (strcmp(colname, funcname) == 0) - { - char *basename = get_attname(relid, i); - - if (basename != NULL) - { - funcname = basename; - attnum = i; - } - - /* - * attnum was initialized to InvalidAttrNumber - * earlier, so no need to reset it if the above - * test fails. - thomas 2000-02-07 - */ - break; - } - i++; - } - if (attnum == InvalidAttrNumber) - attnum = specialAttNum(funcname); - } - else - attnum = get_attnum(relid, funcname); + retval = qualifiedNameToVar(pstate, refname, funcname, true); + if (retval) + return retval; - if (attnum != InvalidAttrNumber) - { - return (Node *) make_var(pstate, - relid, - refname, - funcname); - } - /* else drop through - attr is a set */ + /* else drop through - attr is a set or function */ } else if (ISCOMPLEX(exprType(first_arg))) { @@ -376,10 +309,7 @@ ParseFuncOrColumn(ParseState *pstate, char *funcname, List *fargs, toid = exprType(first_arg); rd = heap_openr_nofail(typeidTypeName(toid)); if (RelationIsValid(rd)) - { - relname = RelationGetRelationName(rd); heap_close(rd, NoLock); - } else elog(ERROR, "Type '%s' is not a relation type", typeidTypeName(toid)); @@ -506,17 +436,9 @@ ParseFuncOrColumn(ParseState *pstate, char *funcname, List *fargs, rte = refnameRangeTableEntry(pstate, refname); if (rte == NULL) - { - rte = addRangeTableEntry(pstate, refname, - makeAttr(refname, NULL), - FALSE, FALSE, TRUE); - warnAutoRange(pstate, refname); - } - - relname = rte->relname; + rte = addImplicitRTE(pstate, refname); - vnum = refnameRangeTablePosn(pstate, rte->eref->relname, - &sublevels_up); + vnum = RTERangeTablePosn(pstate, rte, &sublevels_up); /* * for func(relname), the param to the function is the tuple @@ -525,7 +447,8 @@ ParseFuncOrColumn(ParseState *pstate, char *funcname, List *fargs, * but has varattno == 0 to signal that the whole tuple is the * argument. */ - toid = typeTypeId(typenameType(relname)); + toid = typeTypeId(typenameType(rte->relname)); + /* replace it in the arg list */ lfirst(i) = makeVar(vnum, 0, toid, -1, sublevels_up); } @@ -666,16 +589,6 @@ ParseFuncOrColumn(ParseState *pstate, char *funcname, List *fargs, /* perform the necessary typecasting of arguments */ make_arguments(pstate, nargs, fargs, oid_array, true_oid_array); - /* - * Special checks to disallow sequence functions with side-effects - * in WHERE clauses. This is pretty much of a hack; why disallow these - * when we have no way to check for side-effects of user-defined fns? - */ - if (funcid == F_NEXTVAL && pstate->p_in_where_clause) - elog(ERROR, "Sequence function nextval is not allowed in WHERE clauses"); - if (funcid == F_SETVAL && pstate->p_in_where_clause) - elog(ERROR, "Sequence function setval is not allowed in WHERE clauses"); - expr = makeNode(Expr); expr->typeOid = rettype; expr->opType = FUNC_EXPR; diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c index 5d363ea3e69..85a56067bd2 100644 --- a/src/backend/parser/parse_node.c +++ b/src/backend/parser/parse_node.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_node.c,v 1.45 2000/08/24 03:29:05 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_node.c,v 1.46 2000/09/12 21:07:02 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -49,8 +49,8 @@ make_parsestate(ParseState *parentParseState) pstate = palloc(sizeof(ParseState)); MemSet(pstate, 0, sizeof(ParseState)); - pstate->p_last_resno = 1; pstate->parentParseState = parentParseState; + pstate->p_last_resno = 1; return pstate; } @@ -164,35 +164,44 @@ make_op(char *opname, Node *ltree, Node *rtree) /* * make_var - * Build a Var node for an attribute identified by name + * Build a Var node for an attribute identified by RTE and attrno */ Var * -make_var(ParseState *pstate, Oid relid, char *refname, - char *attrname) +make_var(ParseState *pstate, RangeTblEntry *rte, int attrno) { - HeapTuple tp; - Form_pg_attribute att_tup; int vnum, - attid; + sublevels_up; Oid vartypeid; int32 type_mod; - int sublevels_up; - - vnum = refnameRangeTablePosn(pstate, refname, &sublevels_up); - - tp = SearchSysCacheTuple(ATTNAME, - ObjectIdGetDatum(relid), - PointerGetDatum(attrname), - 0, 0); - if (!HeapTupleIsValid(tp)) - elog(ERROR, "Relation %s does not have attribute %s", - refname, attrname); - att_tup = (Form_pg_attribute) GETSTRUCT(tp); - attid = att_tup->attnum; - vartypeid = att_tup->atttypid; - type_mod = att_tup->atttypmod; - - return makeVar(vnum, attid, vartypeid, type_mod, sublevels_up); + + vnum = RTERangeTablePosn(pstate, rte, &sublevels_up); + + if (rte->relid != InvalidOid) + { + /* Plain relation RTE --- get the attribute's type info */ + HeapTuple tp; + Form_pg_attribute att_tup; + + tp = SearchSysCacheTuple(ATTNUM, + ObjectIdGetDatum(rte->relid), + Int16GetDatum(attrno), + 0, 0); + /* this shouldn't happen... */ + if (!HeapTupleIsValid(tp)) + elog(ERROR, "Relation %s does not have attribute %d", + rte->relname, attrno); + att_tup = (Form_pg_attribute) GETSTRUCT(tp); + vartypeid = att_tup->atttypid; + type_mod = att_tup->atttypmod; + } + else + { + /* Subselect RTE --- get type info from subselect's tlist */ + elog(ERROR, "make_var: subselect in FROM not implemented yet"); + vartypeid = type_mod = 0; + } + + return makeVar(vnum, attrno, vartypeid, type_mod, sublevels_up); } /* diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index 802299c8966..491cbc5ef08 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_relation.c,v 1.46 2000/08/08 15:42:04 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_relation.c,v 1.47 2000/09/12 21:07:02 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -20,14 +20,25 @@ #include "access/htup.h" #include "catalog/pg_type.h" #include "nodes/makefuncs.h" +#include "parser/parsetree.h" #include "parser/parse_coerce.h" +#include "parser/parse_expr.h" #include "parser/parse_relation.h" #include "parser/parse_type.h" +#include "rewrite/rewriteManip.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/lsyscache.h" +static Node *scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, + char *colname); +static Node *scanJoinForColumn(JoinExpr *join, char *colname, + int sublevels_up); +static List *expandNamesVars(ParseState *pstate, List *names, List *vars); +static void warnAutoRange(ParseState *pstate, char *refname); + + /* * Information defining the "system" attributes of every relation. */ @@ -65,40 +76,96 @@ static struct #define SPECIALS ((int) (sizeof(special_attr)/sizeof(special_attr[0]))) -#ifdef NOT_USED -/* refnameRangeTableEntries() - * Given refname, return a list of range table entries - * This is possible with JOIN syntax, where tables in a join - * acquire the same reference name. - * - thomas 2000-01-20 - * But at the moment we aren't carrying along a full list of - * table/column aliases, so we don't have the full mechanism - * to support outer joins in place yet. - * - thomas 2000-03-04 +/* + * refnameRangeOrJoinEntry + * Given a refname, look to see if it matches any RTE or join table. + * If so, return a pointer to the RangeTblEntry or JoinExpr. + * Optionally get its nesting depth (0 = current). If sublevels_up + * is NULL, only consider items at the current nesting level. */ - -static List * -refnameRangeTableEntries(ParseState *pstate, char *refname) +Node * +refnameRangeOrJoinEntry(ParseState *pstate, + char *refname, + int *sublevels_up) { - List *rteList = NULL; - List *temp; + if (sublevels_up) + *sublevels_up = 0; while (pstate != NULL) { + List *temp; + JoinExpr *join; + + /* + * Check the rangetable for RTEs; if no match, recursively scan + * the jointree for join tables. We assume that no duplicate + * entries have been made in any one nesting level. + */ foreach(temp, pstate->p_rtable) { RangeTblEntry *rte = lfirst(temp); if (strcmp(rte->eref->relname, refname) == 0) - rteList = lappend(rteList, rte); + return (Node *) rte; } + + join = scanJoinTreeForRefname((Node *) pstate->p_jointree, refname); + if (join) + return (Node *) join; + pstate = pstate->parentParseState; + if (sublevels_up) + (*sublevels_up)++; + else + break; } - return rteList; + return NULL; } -#endif -/* given refname, return a pointer to the range table entry */ +/* Recursively search a jointree for a joinexpr with given refname */ +JoinExpr * +scanJoinTreeForRefname(Node *jtnode, char *refname) +{ + JoinExpr *result = NULL; + + if (jtnode == NULL) + return NULL; + if (IsA(jtnode, List)) + { + List *l; + + foreach(l, (List *) jtnode) + { + result = scanJoinTreeForRefname(lfirst(l), refname); + if (result) + break; + } + } + else if (IsA(jtnode, RangeTblRef)) + { + /* ignore ... */ + } + else if (IsA(jtnode, JoinExpr)) + { + JoinExpr *j = (JoinExpr *) jtnode; + + if (j->alias && strcmp(j->alias->relname, refname) == 0) + return j; + result = scanJoinTreeForRefname(j->larg, refname); + if (! result) + result = scanJoinTreeForRefname(j->rarg, refname); + } + else + elog(ERROR, "scanJoinTreeForRefname: unexpected node type %d", + nodeTag(jtnode)); + return result; +} + +/* + * given refname, return a pointer to the range table entry. + * + * NOTE that this routine will ONLY find RTEs, not join tables. + */ RangeTblEntry * refnameRangeTableEntry(ParseState *pstate, char *refname) { @@ -118,9 +185,13 @@ refnameRangeTableEntry(ParseState *pstate, char *refname) return NULL; } -/* given refname, return RT index (starting with 1) of the relation, +/* + * given refname, return RT index (starting with 1) of the relation, * and optionally get its nesting depth (0 = current). If sublevels_up * is NULL, only consider rels at the current nesting level. + * A zero result means name not found. + * + * NOTE that this routine will ONLY find RTEs, not join tables. */ int refnameRangeTablePosn(ParseState *pstate, char *refname, int *sublevels_up) @@ -152,114 +223,264 @@ refnameRangeTablePosn(ParseState *pstate, char *refname, int *sublevels_up) } /* - * returns range entry if found, else NULL + * given an RTE, return RT index (starting with 1) of the entry, + * and optionally get its nesting depth (0 = current). If sublevels_up + * is NULL, only consider rels at the current nesting level. + * Raises error if RTE not found. */ -RangeTblEntry * -colnameRangeTableEntry(ParseState *pstate, char *colname) +int +RTERangeTablePosn(ParseState *pstate, RangeTblEntry *rte, int *sublevels_up) { - List *et; - List *rtable; - RangeTblEntry *rte_result = NULL; + int index; + List *temp; + + if (sublevels_up) + *sublevels_up = 0; while (pstate != NULL) { - if (pstate->p_is_rule) - rtable = lnext(lnext(pstate->p_rtable)); + index = 1; + foreach(temp, pstate->p_rtable) + { + if (rte == (RangeTblEntry *) lfirst(temp)) + return index; + index++; + } + pstate = pstate->parentParseState; + if (sublevels_up) + (*sublevels_up)++; else - rtable = pstate->p_rtable; + break; + } + elog(ERROR, "RTERangeTablePosn: RTE not found (internal error)"); + return 0; /* keep compiler quiet */ +} + +/* + * scanRTEForColumn + * Search the column names of a single RTE for the given name. + * If found, return an appropriate Var node, else return NULL. + * If the name proves ambiguous within this RTE, raise error. + */ +static Node * +scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, char *colname) +{ + Node *result = NULL; + int attnum = 0; + List *c; - foreach(et, rtable) + /* + * Scan the user column names (or aliases) for a match. + * Complain if multiple matches. + */ + foreach(c, rte->eref->attrs) + { + attnum++; + if (strcmp(strVal(lfirst(c)), colname) == 0) { - RangeTblEntry *rte_candidate = NULL; - RangeTblEntry *rte = lfirst(et); + if (result) + elog(ERROR, "Column reference \"%s\" is ambiguous", colname); + result = (Node *) make_var(pstate, rte, attnum); + } + } - /* only consider RTEs mentioned in FROM or UPDATE/DELETE */ - if (!rte->inFromCl && rte != pstate->p_target_rangetblentry) - continue; + /* + * If we have a unique match, return it. Note that this allows a user + * alias to override a system column name (such as OID) without error. + */ + if (result) + return result; - if (rte->eref->attrs != NULL) - { - List *c; - - foreach(c, rte->ref->attrs) - { - if (strcmp(strVal(lfirst(c)), colname) == 0) - { - if (rte_candidate != NULL) - elog(ERROR, "Column '%s' is ambiguous" - " (internal error)", colname); - rte_candidate = rte; - } - } - } + /* + * If the RTE represents a table (not a sub-select), consider system + * column names. + */ + if (rte->relid != InvalidOid) + { + attnum = specialAttNum(colname); + if (attnum != InvalidAttrNumber) + result = (Node *) make_var(pstate, rte, attnum); + } + + return result; +} +/* + * scanJoinForColumn + * Search the column names of a single join table for the given name. + * If found, return an appropriate Var node or expression, else return NULL. + * If the name proves ambiguous within this jointable, raise error. + */ +static Node * +scanJoinForColumn(JoinExpr *join, char *colname, int sublevels_up) +{ + Node *result = NULL; + int attnum = 0; + List *c; + + foreach(c, join->colnames) + { + attnum++; + if (strcmp(strVal(lfirst(c)), colname) == 0) + { + if (result) + elog(ERROR, "Column reference \"%s\" is ambiguous", colname); + result = copyObject(nth(attnum-1, join->colvars)); /* - * Even if we have an attribute list in the RTE, look for the - * column here anyway. This is the only way we will find - * implicit columns like "oid". - thomas 2000-02-07 + * If referencing an uplevel join item, we must adjust + * sublevels settings in the copied expression. */ - if ((rte_candidate == NULL) - && (get_attnum(rte->relid, colname) != InvalidAttrNumber)) - rte_candidate = rte; + if (sublevels_up > 0) + IncrementVarSublevelsUp(result, sublevels_up, 0); + } + } + return result; +} + +/* + * colnameToVar + * Search for an unqualified column name. + * If found, return the appropriate Var node (or expression). + * If not found, return NULL. If the name proves ambiguous, raise error. + */ +Node * +colnameToVar(ParseState *pstate, char *colname) +{ + Node *result = NULL; + ParseState *orig_pstate = pstate; + int levels_up = 0; - if (rte_candidate == NULL) - continue; + while (pstate != NULL) + { + List *jt; - if (rte_result != NULL) + /* + * We want to look only at top-level jointree items, and even for + * those, ignore RTEs that are marked as not inFromCl and not + * the query's target relation. + */ + foreach(jt, pstate->p_jointree) + { + Node *jtnode = (Node *) lfirst(jt); + Node *newresult = NULL; + + if (IsA(jtnode, RangeTblRef)) { - if (!pstate->p_is_insert || + int varno = ((RangeTblRef *) jtnode)->rtindex; + RangeTblEntry *rte = rt_fetch(varno, pstate->p_rtable); + + if (! rte->inFromCl && rte != pstate->p_target_rangetblentry) - elog(ERROR, "Column '%s' is ambiguous", colname); + continue; + + /* use orig_pstate here to get the right sublevels_up */ + newresult = scanRTEForColumn(orig_pstate, rte, colname); + } + else if (IsA(jtnode, JoinExpr)) + { + JoinExpr *j = (JoinExpr *) jtnode; + + newresult = scanJoinForColumn(j, colname, levels_up); } else - rte_result = rte; + elog(ERROR, "colnameToVar: unexpected node type %d", + nodeTag(jtnode)); + + if (newresult) + { + if (result) + elog(ERROR, "Column reference \"%s\" is ambiguous", + colname); + result = newresult; + } } - if (rte_result != NULL) + if (result != NULL) break; /* found */ pstate = pstate->parentParseState; + levels_up++; } - return rte_result; + + return result; } /* - * put new entry in pstate p_rtable structure, or return pointer - * if pstate null + * qualifiedNameToVar + * Search for a qualified column name (refname + column name). + * If found, return the appropriate Var node (or expression). + * If not found, return NULL. If the name proves ambiguous, raise error. + */ +Node * +qualifiedNameToVar(ParseState *pstate, char *refname, char *colname, + bool implicitRTEOK) +{ + Node *result; + Node *rteorjoin; + int sublevels_up; + + rteorjoin = refnameRangeOrJoinEntry(pstate, refname, &sublevels_up); + + if (rteorjoin == NULL) + { + if (! implicitRTEOK) + return NULL; + rteorjoin = (Node *) addImplicitRTE(pstate, refname); + sublevels_up = 0; + } + + if (IsA(rteorjoin, RangeTblEntry)) + result = scanRTEForColumn(pstate, (RangeTblEntry *) rteorjoin, + colname); + else if (IsA(rteorjoin, JoinExpr)) + result = scanJoinForColumn((JoinExpr *) rteorjoin, + colname, sublevels_up); + else + { + elog(ERROR, "qualifiedNameToVar: unexpected node type %d", + nodeTag(rteorjoin)); + result = NULL; /* keep compiler quiet */ + } + + return result; +} + +/* + * Add an entry to the pstate's range table (p_rtable), unless the + * specified refname is already present, in which case raise error. + * + * If pstate is NULL, we just build an RTE and return it without worrying + * about membership in an rtable list. */ RangeTblEntry * addRangeTableEntry(ParseState *pstate, char *relname, - Attr *ref, + Attr *alias, bool inh, - bool inFromCl, - bool inJoinSet) + bool inFromCl) { + char *refname = alias ? alias->relname : relname; Relation rel; RangeTblEntry *rte; Attr *eref; int maxattrs; - int sublevels_up; + int numaliases; int varattno; - /* Look for an existing rte, if available... */ + /* Check for conflicting RTE or jointable alias (at level 0 only) */ if (pstate != NULL) { - int rt_index = refnameRangeTablePosn(pstate, ref->relname, - &sublevels_up); + Node *rteorjoin = refnameRangeOrJoinEntry(pstate, refname, NULL); - if (rt_index != 0 && (!inFromCl || sublevels_up == 0)) - { - if (!strcmp(ref->relname, "*OLD*") || !strcmp(ref->relname, "*NEW*")) - return (RangeTblEntry *) nth(rt_index - 1, pstate->p_rtable); - elog(ERROR, "Table name '%s' specified more than once", ref->relname); - } + if (rteorjoin) + elog(ERROR, "Table name \"%s\" specified more than once", + refname); } rte = makeNode(RangeTblEntry); rte->relname = relname; - rte->ref = ref; + rte->alias = alias; /* * Get the rel's OID. This access also ensures that we have an @@ -271,30 +492,34 @@ addRangeTableEntry(ParseState *pstate, rte->relid = RelationGetRelid(rel); maxattrs = RelationGetNumberOfAttributes(rel); - eref = copyObject(ref); - if (maxattrs < length(eref->attrs)) - elog(ERROR, "Table '%s' has %d columns available but %d columns specified", - relname, maxattrs, length(eref->attrs)); + eref = alias ? copyObject(alias) : makeAttr(refname, NULL); + numaliases = length(eref->attrs); + + if (maxattrs < numaliases) + elog(ERROR, "Table \"%s\" has %d columns available but %d columns specified", + refname, maxattrs, numaliases); /* fill in any unspecified alias columns */ - for (varattno = length(eref->attrs); varattno < maxattrs; varattno++) + for (varattno = numaliases; varattno < maxattrs; varattno++) { char *attrname; attrname = pstrdup(NameStr(rel->rd_att->attrs[varattno]->attname)); eref->attrs = lappend(eref->attrs, makeString(attrname)); } - heap_close(rel, AccessShareLock); rte->eref = eref; - /* - * Flags: - this RTE should be expanded to include descendant tables, - * - this RTE is in the FROM clause, - this RTE should be included in - * the planner's final join. + heap_close(rel, AccessShareLock); + + /*---------- + * Flags: + * - this RTE should be expanded to include descendant tables, + * - this RTE is in the FROM clause, + * - this RTE should not be checked for access rights. + *---------- */ rte->inh = inh; rte->inFromCl = inFromCl; - rte->inJoinSet = inJoinSet; rte->skipAcl = false; /* always starts out false */ /* @@ -306,118 +531,184 @@ addRangeTableEntry(ParseState *pstate, return rte; } -/* expandTable() - * Populates an Attr with table name and column names - * This is similar to expandAll(), but does not create an RTE - * if it does not already exist. - * - thomas 2000-01-19 +/* + * Add the given RTE as a top-level entry in the pstate's join tree, + * unless there already is an entry for it. */ -Attr * -expandTable(ParseState *pstate, char *refname, bool getaliases) +void +addRTEtoJoinTree(ParseState *pstate, RangeTblEntry *rte) +{ + int rtindex = RTERangeTablePosn(pstate, rte, NULL); + List *jt; + RangeTblRef *rtr; + + foreach(jt, pstate->p_jointree) + { + Node *n = (Node *) lfirst(jt); + + if (IsA(n, RangeTblRef)) + { + if (rtindex == ((RangeTblRef *) n)->rtindex) + return; /* it's already being joined to */ + } + } + + /* Not present, so add it */ + rtr = makeNode(RangeTblRef); + rtr->rtindex = rtindex; + pstate->p_jointree = lappend(pstate->p_jointree, rtr); +} + +/* + * Add a POSTQUEL-style implicit RTE. + * + * We assume caller has already checked that there is no such RTE now. + */ +RangeTblEntry * +addImplicitRTE(ParseState *pstate, char *relname) { - Attr *attr; RangeTblEntry *rte; + + rte = addRangeTableEntry(pstate, relname, NULL, false, false); + addRTEtoJoinTree(pstate, rte); + warnAutoRange(pstate, relname); + + return rte; +} + +/* expandRTE() + * + * Given a rangetable entry, create lists of its column names (aliases if + * provided, else real names) and Vars for each column. Only user columns + * are considered, since this is primarily used to expand '*' and determine + * the contents of JOIN tables. + * + * If only one of the two kinds of output list is needed, pass NULL for the + * output pointer for the unwanted one. + */ +void +expandRTE(ParseState *pstate, RangeTblEntry *rte, + List **colnames, List **colvars) +{ Relation rel; int varattno, - maxattrs; + maxattrs, + rtindex, + sublevels_up; - rte = refnameRangeTableEntry(pstate, refname); + if (colnames) + *colnames = NIL; + if (colvars) + *colvars = NIL; - if (getaliases && (rte != NULL)) - return rte->eref; + /* Need the RT index of the entry for creating Vars */ + rtindex = RTERangeTablePosn(pstate, rte, &sublevels_up); - if (rte != NULL) - rel = heap_open(rte->relid, AccessShareLock); - else - rel = heap_openr(refname, AccessShareLock); - - if (rel == NULL) - elog(ERROR, "Relation '%s' not found", refname); + rel = heap_open(rte->relid, AccessShareLock); maxattrs = RelationGetNumberOfAttributes(rel); - attr = makeAttr(refname, NULL); - for (varattno = 0; varattno < maxattrs; varattno++) { - char *attrname; + Form_pg_attribute attr = rel->rd_att->attrs[varattno]; #ifdef _DROP_COLUMN_HACK__ - if (COLUMN_IS_DROPPED(rel->rd_att->attrs[varattno])) + if (COLUMN_IS_DROPPED(attr)) continue; #endif /* _DROP_COLUMN_HACK__ */ - attrname = pstrdup(NameStr(rel->rd_att->attrs[varattno]->attname)); - attr->attrs = lappend(attr->attrs, makeString(attrname)); + + if (colnames) + { + char *label; + + if (varattno < length(rte->eref->attrs)) + label = strVal(nth(varattno, rte->eref->attrs)); + else + label = NameStr(attr->attname); + *colnames = lappend(*colnames, makeString(pstrdup(label))); + } + + if (colvars) + { + Var *varnode; + + varnode = makeVar(rtindex, attr->attnum, + attr->atttypid, attr->atttypmod, + sublevels_up); + + *colvars = lappend(*colvars, varnode); + } } heap_close(rel, AccessShareLock); - - return attr; } /* - * expandAll - - * makes a list of attributes + * expandRelAttrs - + * makes a list of TargetEntry nodes for the attributes of the rel */ List * -expandAll(ParseState *pstate, char *relname, Attr *ref, int *this_resno) +expandRelAttrs(ParseState *pstate, RangeTblEntry *rte) { - List *te_list = NIL; - RangeTblEntry *rte; - Relation rel; - int varattno, - maxattrs; + List *name_list, + *var_list; - rte = refnameRangeTableEntry(pstate, ref->relname); - if (rte == NULL) - { - rte = addRangeTableEntry(pstate, relname, ref, - FALSE, FALSE, TRUE); - warnAutoRange(pstate, ref->relname); - } + expandRTE(pstate, rte, &name_list, &var_list); - rel = heap_open(rte->relid, AccessShareLock); + return expandNamesVars(pstate, name_list, var_list); +} - maxattrs = RelationGetNumberOfAttributes(rel); +/* + * expandJoinAttrs - + * makes a list of TargetEntry nodes for the attributes of the join + */ +List * +expandJoinAttrs(ParseState *pstate, JoinExpr *join, int sublevels_up) +{ + List *vars; - for (varattno = 0; varattno < maxattrs; varattno++) - { - char *attrname; - char *label; - Var *varnode; - TargetEntry *te = makeNode(TargetEntry); + vars = copyObject(join->colvars); + /* + * If referencing an uplevel join item, we must adjust + * sublevels settings in the copied expression. + */ + if (sublevels_up > 0) + IncrementVarSublevelsUp((Node *) vars, sublevels_up, 0); -#ifdef _DROP_COLUMN_HACK__ - if (COLUMN_IS_DROPPED(rel->rd_att->attrs[varattno])) - continue; -#endif /* _DROP_COLUMN_HACK__ */ - attrname = pstrdup(NameStr(rel->rd_att->attrs[varattno]->attname)); + return expandNamesVars(pstate, + copyObject(join->colnames), + vars); +} - /* - * varattno is zero-based, so check that length() is always - * greater - */ - if (length(rte->eref->attrs) > varattno) - label = pstrdup(strVal(nth(varattno, rte->eref->attrs))); - else - label = attrname; - varnode = make_var(pstate, rte->relid, relname, attrname); +/* + * expandNamesVars - + * Workhorse for "*" expansion: produce a list of targetentries + * given lists of column names (as String nodes) and var references. + */ +static List * +expandNamesVars(ParseState *pstate, List *names, List *vars) +{ + List *te_list = NIL; - /* - * Even if the elements making up a set are complex, the set - * itself is not. - */ + while (names) + { + char *label = strVal(lfirst(names)); + Node *varnode = (Node *) lfirst(vars); + TargetEntry *te = makeNode(TargetEntry); - te->resdom = makeResdom((AttrNumber) (*this_resno)++, - varnode->vartype, - varnode->vartypmod, + te->resdom = makeResdom((AttrNumber) (pstate->p_last_resno)++, + exprType(varnode), + exprTypmod(varnode), label, false); - te->expr = (Node *) varnode; + te->expr = varnode; te_list = lappend(te_list, te); + + names = lnext(names); + vars = lnext(vars); } - heap_close(rel, AccessShareLock); + Assert(vars == NIL); /* lists not same length? */ return te_list; } @@ -531,11 +822,17 @@ attnumTypeId(Relation rd, int attid) return rd->rd_att->attrs[attid - 1]->atttypid; } -void +/* + * Generate a warning about an implicit RTE, if appropriate. + * + * Our current theory on this is that we should allow "SELECT foo.*" + * but warn about a mixture of explicit and implicit RTEs. + */ +static void warnAutoRange(ParseState *pstate, char *refname) { - List *temp; bool foundInFromCl = false; + List *temp; foreach(temp, pstate->p_rtable) { @@ -548,8 +845,8 @@ warnAutoRange(ParseState *pstate, char *refname) } } if (foundInFromCl) - elog(NOTICE, "Adding missing FROM-clause entry%s for table %s", - pstate->parentParseState != NULL ? " in subquery" : "", - refname); + elog(NOTICE, "Adding missing FROM-clause entry%s for table \"%s\"", + pstate->parentParseState != NULL ? " in subquery" : "", + refname); } diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 1564f976b04..b8e1570985f 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -8,13 +8,14 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_target.c,v 1.61 2000/08/08 15:42:04 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_target.c,v 1.62 2000/09/12 21:07:02 tgl Exp $ * *------------------------------------------------------------------------- */ #include "postgres.h" #include "nodes/makefuncs.h" +#include "parser/parsetree.h" #include "parser/parse_coerce.h" #include "parser/parse_expr.h" #include "parser/parse_func.h" @@ -104,36 +105,8 @@ transformTargetList(ParseState *pstate, List *targetlist) * Target item is a single '*', expand all tables (eg. * SELECT * FROM emp) */ - if (pstate->p_shape != NULL) - { - List *s, - *a; - int i; - - Assert(length(pstate->p_shape) == length(pstate->p_alias)); - - s = pstate->p_shape; - a = pstate->p_alias; - for (i = 0; i < length(pstate->p_shape); i++) - { - TargetEntry *te; - char *colname; - Attr *shape = lfirst(s); - Attr *alias = lfirst(a); - - Assert(IsA(shape, Attr) &&IsA(alias, Attr)); - - colname = strVal(lfirst(alias->attrs)); - te = transformTargetEntry(pstate, (Node *) shape, - NULL, colname, false); - p_target = lappend(p_target, te); - s = lnext(s); - a = lnext(a); - } - } - else - p_target = nconc(p_target, - ExpandAllTables(pstate)); + p_target = nconc(p_target, + ExpandAllTables(pstate)); } else if (att->attrs != NIL && strcmp(strVal(lfirst(att->attrs)), "*") == 0) @@ -143,10 +116,30 @@ transformTargetList(ParseState *pstate, List *targetlist) * Target item is relation.*, expand that table (eg. * SELECT emp.*, dname FROM emp, dept) */ - p_target = nconc(p_target, - expandAll(pstate, att->relname, - makeAttr(att->relname, NULL), - &pstate->p_last_resno)); + Node *rteorjoin; + int sublevels_up; + + rteorjoin = refnameRangeOrJoinEntry(pstate, att->relname, + &sublevels_up); + + if (rteorjoin == NULL) + { + rteorjoin = (Node *) addImplicitRTE(pstate, att->relname); + sublevels_up = 0; + } + + if (IsA(rteorjoin, RangeTblEntry)) + p_target = nconc(p_target, + expandRelAttrs(pstate, + (RangeTblEntry *) rteorjoin)); + else if (IsA(rteorjoin, JoinExpr)) + p_target = nconc(p_target, + expandJoinAttrs(pstate, + (JoinExpr *) rteorjoin, + sublevels_up)); + else + elog(ERROR, "transformTargetList: unexpected node type %d", + nodeTag(rteorjoin)); } else { @@ -219,23 +212,12 @@ updateTargetListEntry(ParseState *pstate, */ if (indirection) { -#ifndef DISABLE_JOIN_SYNTAX - Attr *att = makeAttr(pstrdup(RelationGetRelationName(rd)), colname); - -#else - Attr *att = makeNode(Attr); - -#endif + Attr *att = makeAttr(pstrdup(RelationGetRelationName(rd)), + colname); Node *arrayBase; ArrayRef *aref; -#ifdef DISABLE_JOIN_SYNTAX - att->relname = pstrdup(RelationGetRelationName(rd)); - att->attrs = lcons(makeString(colname), NIL); -#endif - arrayBase = ParseNestedFuncOrColumn(pstate, att, - &pstate->p_last_resno, - EXPR_COLUMN_FIRST); + arrayBase = ParseNestedFuncOrColumn(pstate, att, EXPR_COLUMN_FIRST); aref = transformArraySubscripts(pstate, arrayBase, indirection, pstate->p_is_insert, @@ -401,46 +383,54 @@ checkInsertTargets(ParseState *pstate, List *cols, List **attrnos) } /* ExpandAllTables() - * Turns '*' (in the target list) into a list of attributes - * (of all relations in the range table) + * Turns '*' (in the target list) into a list of targetlist entries. + * + * tlist entries are generated for each relation appearing in the FROM list, + * which by now has been expanded into a join tree. */ static List * ExpandAllTables(ParseState *pstate) { List *target = NIL; - List *rt, - *rtable; - - rtable = pstate->p_rtable; - if (pstate->p_is_rule) - { - - /* - * skip first two entries, "*new*" and "*current*" - */ - rtable = lnext(lnext(rtable)); - } + List *jt; /* SELECT *; */ - if (rtable == NIL) + if (pstate->p_jointree == NIL) elog(ERROR, "Wildcard with no tables specified not allowed"); - foreach(rt, rtable) + foreach(jt, pstate->p_jointree) { - RangeTblEntry *rte = lfirst(rt); + Node *n = (Node *) lfirst(jt); - /* - * we only expand those listed in the from clause. (This will also - * prevent us from using the wrong table in inserts: eg. tenk2 in - * "insert into tenk2 select * from tenk1;") - */ - if (!rte->inFromCl) - continue; + if (IsA(n, RangeTblRef)) + { + RangeTblEntry *rte; - target = nconc(target, - expandAll(pstate, rte->eref->relname, rte->eref, - &pstate->p_last_resno)); + rte = rt_fetch(((RangeTblRef *) n)->rtindex, + pstate->p_rtable); + + /* + * Ignore added-on relations that were not listed in the FROM + * clause. + */ + if (!rte->inFromCl) + continue; + + target = nconc(target, expandRelAttrs(pstate, rte)); + } + else if (IsA(n, JoinExpr)) + { + /* A newfangled join expression */ + JoinExpr *j = (JoinExpr *) n; + + /* Currently, a join expr could only have come from FROM. */ + target = nconc(target, expandJoinAttrs(pstate, j, 0)); + } + else + elog(ERROR, "ExpandAllTables: unexpected node (internal error)" + "\n\t%s", nodeToString(n)); } + return target; } diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c index a7652407b73..4a6c825498a 100644 --- a/src/backend/parser/parser.c +++ b/src/backend/parser/parser.c @@ -1,32 +1,40 @@ /*------------------------------------------------------------------------- * * parser.c + * Main entry point/driver for PostgreSQL parser + * * * Portions Copyright (c) 1996-2000, PostgreSQL, Inc * Portions Copyright (c) 1994, Regents of the University of California * - * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parser.c,v 1.45 2000/04/12 17:15:27 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parser.c,v 1.46 2000/09/12 21:07:02 tgl Exp $ * *------------------------------------------------------------------------- */ #include "postgres.h" + +#include "nodes/parsenodes.h" +#include "nodes/pg_list.h" #include "parser/analyze.h" #include "parser/gramparse.h" +#include "parser/parse.h" #include "parser/parser.h" #include "parser/parse_expr.h" + #if defined(FLEX_SCANNER) extern void DeleteBuffer(void); - #endif /* FLEX_SCANNER */ char *parseString; /* the char* which holds the string to be * parsed */ List *parsetree; /* result of parsing is left here */ +static int lookahead_token; /* one-token lookahead */ +static bool have_lookahead; /* lookahead_token set? */ + #ifdef SETS_FIXED static void fixupsets(); static void define_sets(); @@ -42,11 +50,11 @@ parser(char *str, Oid *typev, int nargs) List *queryList; int yyresult; - init_io(); - - parseString = pstrdup(str); + parseString = str; parsetree = NIL; /* in case parser forgets to set it */ + have_lookahead = false; + scanner_init(); parser_init(typev, nargs); parse_expr_init(); @@ -83,6 +91,52 @@ parser(char *str, Oid *typev, int nargs) return queryList; } + +/* + * Intermediate filter between parser and base lexer (base_yylex in scan.l). + * + * The filter is needed because in some cases SQL92 requires more than one + * token lookahead. We reduce these cases to one-token lookahead by combining + * tokens here, in order to keep the grammar LR(1). + * + * Using a filter is simpler than trying to recognize multiword tokens + * directly in scan.l, because we'd have to allow for comments between the + * words ... + */ +int +yylex(void) +{ + int cur_token; + + /* Get next token --- we might already have it */ + if (have_lookahead) + { + cur_token = lookahead_token; + have_lookahead = false; + } + else + cur_token = base_yylex(); + + /* Do we need to look ahead for a possible multiword token? */ + switch (cur_token) + { + case UNION: + /* UNION JOIN must be reduced to a single UNIONJOIN token */ + lookahead_token = base_yylex(); + if (lookahead_token == JOIN) + cur_token = UNIONJOIN; + else + have_lookahead = true; + break; + + default: + break; + } + + return cur_token; +} + + #ifdef SETS_FIXED static void fixupsets(Query *parse) diff --git a/src/backend/parser/scan.l b/src/backend/parser/scan.l index f5597d1593e..5700915ad94 100644 --- a/src/backend/parser/scan.l +++ b/src/backend/parser/scan.l @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/scan.l,v 1.76 2000/08/22 13:01:20 ishii Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/scan.l,v 1.77 2000/09/12 21:07:02 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -76,7 +76,7 @@ static char *literalbuf; /* expandable buffer */ static int literallen; /* actual current length */ static int literalalloc; /* current allocated buffer size */ -static int xcdepth = 0; +static int xcdepth = 0; /* depth of nesting in slash-star comments */ #define startlit() (literalbuf[0] = '\0', literallen = 0) static void addlit(char *ytext, int yleng); @@ -510,22 +510,24 @@ other . %% -void yyerror(const char * message) +void +yyerror(const char *message) { elog(ERROR, "parser: %s at or near \"%s\"", message, yytext); } -int yywrap() +int +yywrap(void) { return(1); } /* - init_io: + scanner_init: called by postgres before any actual parsing is done */ void -init_io() +scanner_init(void) { /* it's important to set this to NULL because input()/myinput() checks the non-nullness of parseCh |