diff options
| author | Tom Lane <tgl@sss.pgh.pa.us> | 1999-10-07 04:23:24 +0000 |
|---|---|---|
| committer | Tom Lane <tgl@sss.pgh.pa.us> | 1999-10-07 04:23:24 +0000 |
| commit | 3eb1c8227751aecede58e742a13b07127a7e2652 (patch) | |
| tree | c2f606088b1536e385811667c7f032552b89afa6 /src/backend/parser | |
| parent | 4040fcfa78065882aa16895906ff8398aaaf1c23 (diff) | |
| download | postgresql-3eb1c8227751aecede58e742a13b07127a7e2652.tar.gz | |
Fix planner and rewriter to follow SQL semantics for tables that are
mentioned in FROM but not elsewhere in the query: such tables should be
joined over anyway. Aside from being more standards-compliant, this allows
removal of some very ugly hacks for COUNT(*) processing. Also, allow
HAVING clause without aggregate functions, since SQL does. Clean up
CREATE RULE statement-list syntax the same way Bruce just fixed the
main stmtmulti production.
CAUTION: addition of a field to RangeTblEntry nodes breaks stored rules;
you will have to initdb if you have any rules.
Diffstat (limited to 'src/backend/parser')
| -rw-r--r-- | src/backend/parser/analyze.c | 32 | ||||
| -rw-r--r-- | src/backend/parser/gram.y | 102 | ||||
| -rw-r--r-- | src/backend/parser/parse_agg.c | 94 | ||||
| -rw-r--r-- | src/backend/parser/parse_clause.c | 17 | ||||
| -rw-r--r-- | src/backend/parser/parse_func.c | 8 | ||||
| -rw-r--r-- | src/backend/parser/parse_relation.c | 39 |
6 files changed, 125 insertions, 167 deletions
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 4bcf79a727..08c209b2a5 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -5,7 +5,7 @@ * * Copyright (c) 1994, Regents of the University of California * - * $Id: analyze.c,v 1.120 1999/10/03 23:55:30 tgl Exp $ + * $Id: analyze.c,v 1.121 1999/10/07 04:23:11 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -298,19 +298,10 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt) qry->hasSubLinks = pstate->p_hasSubLinks; qry->hasAggs = pstate->p_hasAggs; - if (pstate->p_hasAggs || qry->groupClause) + if (pstate->p_hasAggs || qry->groupClause || qry->havingQual) parseCheckAggregates(pstate, qry); /* - * If there is a havingQual but there are no aggregates, then there is - * something wrong with the query because HAVING must contain - * aggregates in its expressions! Otherwise the query could have been - * formulated using the WHERE clause. - */ - if (qry->havingQual && ! qry->hasAggs) - elog(ERROR, "SELECT/HAVING requires aggregates to be valid"); - - /* * The INSERT INTO ... SELECT ... could have a UNION in child, so * unionClause may be false */ @@ -961,9 +952,9 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt) nothing_qry->commandType = CMD_NOTHING; addRangeTableEntry(pstate, stmt->object->relname, "*CURRENT*", - FALSE, FALSE); + FALSE, FALSE, FALSE); addRangeTableEntry(pstate, stmt->object->relname, "*NEW*", - FALSE, FALSE); + FALSE, FALSE, FALSE); nothing_qry->rtable = pstate->p_rtable; @@ -983,9 +974,9 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt) * equal to 2. */ addRangeTableEntry(pstate, stmt->object->relname, "*CURRENT*", - FALSE, FALSE); + FALSE, FALSE, FALSE); addRangeTableEntry(pstate, stmt->object->relname, "*NEW*", - FALSE, FALSE); + FALSE, FALSE, FALSE); pstate->p_last_resno = 1; pstate->p_is_rule = true; /* for expand all */ @@ -1048,19 +1039,10 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt) qry->hasSubLinks = pstate->p_hasSubLinks; qry->hasAggs = pstate->p_hasAggs; - if (pstate->p_hasAggs || qry->groupClause) + if (pstate->p_hasAggs || qry->groupClause || qry->havingQual) parseCheckAggregates(pstate, qry); /* - * If there is a havingQual but there are no aggregates, then there is - * something wrong with the query because HAVING must contain - * aggregates in its expressions! Otherwise the query could have been - * formulated using the WHERE clause. - */ - if (qry->havingQual && ! qry->hasAggs) - elog(ERROR, "SELECT/HAVING requires aggregates to be valid"); - - /* * The INSERT INTO ... SELECT ... could have a UNION in child, so * unionClause may be false */ diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 006e545c3a..a88b66c970 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.107 1999/10/05 18:14:31 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.108 1999/10/07 04:23:12 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -72,7 +72,6 @@ static char *xlateSqlType(char *); static Node *makeA_Expr(int oper, char *opname, Node *lexpr, Node *rexpr); static Node *makeRowExpr(char *opr, List *largs, List *rargs); static void mapTargetColumns(List *source, List *target); -static char *fmtId(char *rawid); static void param_type_init(Oid *typev, int nargs); static Node *doNegate(Node *n); @@ -130,7 +129,7 @@ Oid param_type(int t); /* used in parse_expr.c */ UpdateStmt, InsertStmt, select_clause, SelectStmt, NotifyStmt, DeleteStmt, ClusterStmt, ExplainStmt, VariableSetStmt, VariableShowStmt, VariableResetStmt, CreateUserStmt, AlterUserStmt, DropUserStmt, RuleActionStmt, - ConstraintsSetStmt, + RuleActionStmtOrEmpty, ConstraintsSetStmt, %type <str> opt_database1, opt_database2, location, encoding @@ -165,7 +164,7 @@ Oid param_type(int t); /* used in parse_expr.c */ result, relation_name_list, OptTableElementList, OptInherit, definition, opt_with, func_args, func_args_list, func_as, - oper_argtypes, RuleActionList, RuleActionBlock, RuleActionMulti, + oper_argtypes, RuleActionList, RuleActionMulti, opt_column_list, columnList, opt_va_list, va_list, sort_clause, sortby_list, index_params, index_list, name_list, from_clause, from_expr, table_list, opt_array_bounds, @@ -374,17 +373,18 @@ stmtblock: stmtmulti { parsetree = $1; } ; +/* the thrashing around here is to discard "empty" statements... */ stmtmulti: stmtmulti ';' stmt - { if ($3 != (Node *)NIL) + { if ($3 != (Node *)NULL) $$ = lappend($1, $3); else $$ = $1; } | stmt - { if ($1 != (Node *)NIL) + { if ($1 != (Node *)NULL) $$ = lcons($1,NIL); else - $$ = (Node *)NIL; + $$ = NIL; } ; @@ -433,7 +433,7 @@ stmt : AddAttrStmt | VariableResetStmt | ConstraintsSetStmt | /*EMPTY*/ - { $$ = (Node *)NIL; } + { $$ = (Node *)NULL; } ; /***************************************************************************** @@ -930,7 +930,7 @@ ColConstraint: CONSTRAINT name ColConstraintElem { Constraint *n = (Constraint *)$3; - if (n != NULL) n->name = fmtId($2); + if (n != NULL) n->name = $2; $$ = $3; } | ColConstraintElem @@ -1024,7 +1024,7 @@ ColConstraintElem: CHECK '(' a_expr ')' TableConstraint: CONSTRAINT name ConstraintElem { Constraint *n = (Constraint *)$3; - if (n != NULL) n->name = fmtId($2); + if (n != NULL) n->name = $2; $$ = $3; } | ConstraintElem @@ -2034,20 +2034,23 @@ RuleStmt: CREATE RULE name AS RuleActionList: NOTHING { $$ = NIL; } | SelectStmt { $$ = lcons($1, NIL); } | RuleActionStmt { $$ = lcons($1, NIL); } - | '[' RuleActionBlock ']' { $$ = $2; } - | '(' RuleActionBlock ')' { $$ = $2; } - ; - -RuleActionBlock: RuleActionMulti { $$ = $1; } - | RuleActionStmt { $$ = lcons($1, NIL); } + | '[' RuleActionMulti ']' { $$ = $2; } + | '(' RuleActionMulti ')' { $$ = $2; } ; -RuleActionMulti: RuleActionMulti RuleActionStmt - { $$ = lappend($1, $2); } - | RuleActionMulti RuleActionStmt ';' - { $$ = lappend($1, $2); } - | RuleActionStmt ';' - { $$ = lcons($1, NIL); } +/* the thrashing around here is to discard "empty" statements... */ +RuleActionMulti: RuleActionMulti ';' RuleActionStmtOrEmpty + { if ($3 != (Node *)NULL) + $$ = lappend($1, $3); + else + $$ = $1; + } + | RuleActionStmtOrEmpty + { if ($1 != (Node *)NULL) + $$ = lcons($1,NIL); + else + $$ = NIL; + } ; RuleActionStmt: InsertStmt @@ -2056,6 +2059,11 @@ RuleActionStmt: InsertStmt | NotifyStmt ; +RuleActionStmtOrEmpty: RuleActionStmt + | /*EMPTY*/ + { $$ = (Node *)NULL; } + ; + event_object: relation_name '.' attr_name { $$ = makeNode(Attr); @@ -3676,12 +3684,25 @@ a_expr: attr { $$ = makeA_Expr(OP, $2, $1, NULL); } | func_name '(' '*' ')' { - /* cheap hack for aggregate (eg. count) */ + /* + * For now, we transform AGGREGATE(*) into AGGREGATE(1). + * + * This does the right thing for COUNT(*) (in fact, + * any certainly-non-null expression would do for COUNT), + * and there are no other aggregates in SQL92 that accept + * '*' as parameter. + * + * XXX really, the '*' ought to be transformed to some + * special construct that wouldn't be acceptable as the + * input of a non-aggregate function, in case the given + * func_name matches a plain function. This would also + * support a possible extension to let user-defined + * aggregates do something special with '*' as input. + */ FuncCall *n = makeNode(FuncCall); A_Const *star = makeNode(A_Const); - - star->val.type = T_String; - star->val.val.str = ""; + star->val.type = T_Integer; + star->val.val.ival = 1; n->funcname = $1; n->args = lcons(star, NIL); $$ = (Node *)n; @@ -5265,30 +5286,6 @@ void parser_init(Oid *typev, int nargs) } -/* fmtId() - * Check input string for non-lowercase/non-numeric characters. - * Returns either input string or input surrounded by double quotes. - */ -static char * -fmtId(char *rawid) -{ - static char *cp; - - for (cp = rawid; *cp != '\0'; cp++) - if (! (islower(*cp) || isdigit(*cp) || (*cp == '_'))) break; - - if (*cp != '\0') { - cp = palloc(strlen(rawid)+3); - strcpy(cp,"\""); - strcat(cp,rawid); - strcat(cp,"\""); - } else { - cp = rawid; - }; - - return cp; -} - /* * param_type_init() * @@ -5313,6 +5310,11 @@ Oid param_type(int t) * The optimizer doesn't like '-' 4 for index use. It only checks for * Var '=' Const. It wants an integer of -4, so we try to merge the * minus into the constant. + * + * This code is no longer essential as of 10/1999, since the optimizer + * now has a constant-subexpression simplifier. However, we can save + * a few cycles throughout the parse and rewrite stages if we collapse + * the minus into the constant sooner rather than later... */ static Node *doNegate(Node *n) { diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c index a500782297..5c87d5bc85 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_agg.c,v 1.28 1999/08/21 03:48:55 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_agg.c,v 1.29 1999/10/07 04:23:12 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -129,8 +129,8 @@ parseCheckAggregates(ParseState *pstate, Query *qry) List *groupClauses = NIL; List *tl; - /* This should only be called if we found aggregates or grouping */ - Assert(pstate->p_hasAggs || qry->groupClause); + /* This should only be called if we found aggregates, GROUP, or HAVING */ + Assert(pstate->p_hasAggs || qry->groupClause || qry->havingQual); /* * Aggregates must never appear in WHERE clauses. (Note this check @@ -161,6 +161,15 @@ parseCheckAggregates(ParseState *pstate, Query *qry) } /* + * The expression specified in the HAVING clause can only contain + * aggregates, group columns and functions thereof. As with WHERE, + * we want to point the finger at HAVING before the target list. + */ + if (!exprIsAggOrGroupCol(qry->havingQual, groupClauses)) + elog(ERROR, + "Illegal use of aggregates or non-group column in HAVING clause"); + + /* * The target list can only contain aggregates, group columns and * functions thereof. */ @@ -173,14 +182,6 @@ parseCheckAggregates(ParseState *pstate, Query *qry) "Illegal use of aggregates or non-group column in target list"); } - /* - * The expression specified in the HAVING clause has the same - * restriction as those in the target list. - */ - if (!exprIsAggOrGroupCol(qry->havingQual, groupClauses)) - elog(ERROR, - "Illegal use of aggregates or non-group column in HAVING clause"); - /* Release the list storage (but not the pointed-to expressions!) */ freeList(groupClauses); } @@ -190,12 +191,12 @@ Aggref * ParseAgg(ParseState *pstate, char *aggname, Oid basetype, List *target, int precedence) { + HeapTuple theAggTuple; + Form_pg_aggregate aggform; Oid fintype; - Oid vartype; Oid xfn1; - Form_pg_aggregate aggform; + Oid vartype; Aggref *aggref; - HeapTuple theAggTuple; bool usenulls = false; theAggTuple = SearchSysCacheTuple(AGGNAME, @@ -206,66 +207,19 @@ ParseAgg(ParseState *pstate, char *aggname, Oid basetype, elog(ERROR, "Aggregate %s does not exist", aggname); /* - * We do a major hack for count(*) here. - * - * Count(*) poses several problems. First, we need a field that is - * guaranteed to be in the range table, and unique. Using a constant - * causes the optimizer to properly remove the aggragate from any - * elements of the query. Using just 'oid', which can not be null, in - * the parser fails on: + * There used to be a really ugly hack for count(*) here. * - * select count(*) from tab1, tab2 -- oid is not unique select - * count(*) from viewtable -- views don't have real oids + * It's gone. Now, the grammar transforms count(*) into count(1), + * which does the right thing. (It didn't use to do the right thing, + * because the optimizer had the wrong ideas about semantics of queries + * without explicit variables. Fixed as of Oct 1999 --- tgl.) * - * So, for an aggregate with parameter '*', we use the first valid range - * table entry, and pick the first column from the table. We set a - * flag to count nulls, because we could have nulls in that column. - * - * It's an ugly job, but someone has to do it. bjm 1998/1/18 + * Since "1" never evaluates as null, we currently have no need of + * the "usenulls" flag, but it should be kept around; in fact, we should + * extend the pg_aggregate table to let usenulls be specified as an + * attribute of user-defined aggregates. */ - if (nodeTag(lfirst(target)) == T_Const) - { - Const *con = (Const *) lfirst(target); - - if (con->consttype == UNKNOWNOID && VARSIZE(con->constvalue) == VARHDRSZ) - { - Attr *attr = makeNode(Attr); - List *rtable, - *rlist; - RangeTblEntry *first_valid_rte; - - Assert(lnext(target) == NULL); - - if (pstate->p_is_rule) - rtable = lnext(lnext(pstate->p_rtable)); - else - rtable = pstate->p_rtable; - - first_valid_rte = NULL; - foreach(rlist, rtable) - { - RangeTblEntry *rte = lfirst(rlist); - - /* only entries on outer(non-function?) scope */ - if (!rte->inFromCl && rte != pstate->p_target_rangetblentry) - continue; - - first_valid_rte = rte; - break; - } - if (first_valid_rte == NULL) - elog(ERROR, "Can't find column to do aggregate(*) on."); - - attr->relname = first_valid_rte->refname; - attr->attrs = lcons(makeString( - get_attname(first_valid_rte->relid, 1)), NIL); - - lfirst(target) = transformExpr(pstate, (Node *) attr, precedence); - usenulls = true; - } - } - aggform = (Form_pg_aggregate) GETSTRUCT(theAggTuple); fintype = aggform->aggfinaltype; xfn1 = aggform->aggtransfn1; diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index bae53ebbd8..81f3f88669 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.45 1999/09/18 19:07:12 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.46 1999/10/07 04:23:12 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -60,6 +60,15 @@ makeRangeTable(ParseState *pstate, List *frmList, Node **qual) * setTargetTable * Add the target relation of INSERT or UPDATE 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. + * 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? */ void setTargetTable(ParseState *pstate, char *relname) @@ -69,7 +78,8 @@ setTargetTable(ParseState *pstate, char *relname) if ((refnameRangeTablePosn(pstate, relname, &sublevels_up) == 0) || (sublevels_up != 0)) - rte = addRangeTableEntry(pstate, relname, relname, FALSE, FALSE); + rte = addRangeTableEntry(pstate, relname, relname, + FALSE, FALSE, FALSE); else rte = refnameRangeTableEntry(pstate, relname); @@ -230,7 +240,8 @@ transformTableEntry(ParseState *pstate, RangeVar *r) * we expand * to foo.x. */ - rte = addRangeTableEntry(pstate, relname, refname, baserel->inh, TRUE); + rte = addRangeTableEntry(pstate, relname, refname, + baserel->inh, TRUE, TRUE); return refname; } diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index ca56e59204..c6f9610699 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_func.c,v 1.59 1999/09/29 18:16:04 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_func.c,v 1.60 1999/10/07 04:23:12 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -274,7 +274,8 @@ ParseFuncOrColumn(ParseState *pstate, char *funcname, List *fargs, rte = refnameRangeTableEntry(pstate, refname); if (rte == NULL) { - rte = addRangeTableEntry(pstate, refname, refname,FALSE, FALSE); + rte = addRangeTableEntry(pstate, refname, refname, + FALSE, FALSE, TRUE); #ifdef WARN_FROM elog(NOTICE,"Adding missing FROM-clause entry%s for table %s", pstate->parentParseState != NULL ? " in subquery" : "", @@ -437,7 +438,8 @@ ParseFuncOrColumn(ParseState *pstate, char *funcname, List *fargs, rte = refnameRangeTableEntry(pstate, refname); if (rte == NULL) { - rte = addRangeTableEntry(pstate, refname, refname,FALSE, FALSE); + rte = addRangeTableEntry(pstate, refname, refname, + FALSE, FALSE, TRUE); #ifdef WARN_FROM elog(NOTICE,"Adding missing FROM-clause entry%s for table %s", pstate->parentParseState != NULL ? " in subquery" : "", diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index e664615e6d..fcbd6fedd2 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_relation.c,v 1.31 1999/09/29 18:16:04 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_relation.c,v 1.32 1999/10/07 04:23:12 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -144,7 +144,7 @@ colnameRangeTableEntry(ParseState *pstate, char *colname) { RangeTblEntry *rte = lfirst(et); - /* only entries on outer(non-function?) scope */ + /* only consider RTEs mentioned in FROM or UPDATE/DELETE */ if (!rte->inFromCl && rte != pstate->p_target_rangetblentry) continue; @@ -178,45 +178,51 @@ addRangeTableEntry(ParseState *pstate, char *relname, char *refname, bool inh, - bool inFromCl) + bool inFromCl, + bool inJoinSet) { Relation relation; - RangeTblEntry *rte = makeNode(RangeTblEntry); + RangeTblEntry *rte; int sublevels_up; if (pstate != NULL) { - if (refnameRangeTablePosn(pstate, refname, &sublevels_up) != 0 && - (!inFromCl || sublevels_up == 0)) + int rt_index = refnameRangeTablePosn(pstate, refname, + &sublevels_up); + + if (rt_index != 0 && (!inFromCl || sublevels_up == 0)) { if (!strcmp(refname, "*CURRENT*") || !strcmp(refname, "*NEW*")) - { - int rt_index = refnameRangeTablePosn(pstate, refname, &sublevels_up); - return (RangeTblEntry *) nth(rt_index - 1, pstate->p_rtable); - } elog(ERROR, "Table name '%s' specified more than once", refname); } } + rte = makeNode(RangeTblEntry); + rte->relname = pstrdup(relname); rte->refname = pstrdup(refname); + /* Get the rel's OID. This access also ensures that we have an + * up-to-date relcache entry for the rel. We don't need to keep + * it open, however. + */ relation = heap_openr(relname, AccessShareLock); rte->relid = RelationGetRelid(relation); heap_close(relation, AccessShareLock); /* - * Flags - zero or more from inheritance,union,version or recursive - * (transitive closure) [we don't support them all -- ay 9/94 ] + * 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. */ rte->inh = inh; - - /* RelOID */ rte->inFromCl = inFromCl; + rte->inJoinSet = inJoinSet; + rte->skipAcl = false; /* always starts out false */ /* - * close the relation we're done with it for now. + * Add completed RTE to range table list. */ if (pstate != NULL) pstate->p_rtable = lappend(pstate->p_rtable, rte); @@ -240,7 +246,8 @@ expandAll(ParseState *pstate, char *relname, char *refname, int *this_resno) rte = refnameRangeTableEntry(pstate, refname); if (rte == NULL) { - rte = addRangeTableEntry(pstate, relname, refname, FALSE, FALSE); + rte = addRangeTableEntry(pstate, relname, refname, + FALSE, FALSE, TRUE); #ifdef WARN_FROM elog(NOTICE,"Adding missing FROM-clause entry%s for table %s", pstate->parentParseState != NULL ? " in subquery" : "", |
