diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index f55d0cad5d18c697501897ae9d18ee928a23960a..d058a44bb0d2d4570bb22f70adbc8c5a69c60439 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.155 2003/06/16 02:03:37 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.156 2003/07/03 19:07:20 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -47,7 +47,8 @@ #define EXPRKIND_QUAL 0 #define EXPRKIND_TARGET 1 #define EXPRKIND_RTFUNC 2 -#define EXPRKIND_ININFO 3 +#define EXPRKIND_LIMIT 3 +#define EXPRKIND_ININFO 4 static Node *preprocess_expression(Query *parse, Node *expr, int kind); @@ -232,6 +233,11 @@ subquery_planner(Query *parse, double tuple_fraction) parse->havingQual = preprocess_expression(parse, parse->havingQual, EXPRKIND_QUAL); + parse->limitOffset = preprocess_expression(parse, parse->limitOffset, + EXPRKIND_LIMIT); + parse->limitCount = preprocess_expression(parse, parse->limitCount, + EXPRKIND_LIMIT); + parse->in_info_list = (List *) preprocess_expression(parse, (Node *) parse->in_info_list, EXPRKIND_ININFO); diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index b3dd818e0ac3db0f5d69f60552a350881a38dd34..d35cbee92e7c263c509cfcf9a600a35cd330237c 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.145 2003/07/03 16:33:07 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.146 2003/07/03 19:07:25 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -2390,6 +2390,10 @@ query_tree_walker(Query *query, return true; if (walker(query->havingQual, context)) return true; + if (walker(query->limitOffset, context)) + return true; + if (walker(query->limitCount, context)) + return true; if (walker(query->in_info_list, context)) return true; foreach(rt, query->rtable) @@ -2863,6 +2867,8 @@ query_tree_mutator(Query *query, MUTATE(query->jointree, query->jointree, FromExpr *); MUTATE(query->setOperations, query->setOperations, Node *); MUTATE(query->havingQual, query->havingQual, Node *); + MUTATE(query->limitOffset, query->limitOffset, Node *); + MUTATE(query->limitCount, query->limitCount, Node *); MUTATE(query->in_info_list, query->in_info_list, List *); FastListInit(&newrt); foreach(rt, query->rtable) diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 99efae80721125ec78d5d97c172c64f8689df608..90818b153b8376a0d6be32d8a4bf7a03b0b5610a 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Header: /cvsroot/pgsql/src/backend/parser/analyze.c,v 1.277 2003/06/25 04:19:24 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/analyze.c,v 1.278 2003/07/03 19:07:30 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -459,7 +459,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt) qry->distinctClause = NIL; /* fix where clause */ - qual = transformWhereClause(pstate, stmt->whereClause); + qual = transformWhereClause(pstate, stmt->whereClause, "WHERE"); /* done building the range table and jointree */ qry->rtable = pstate->p_rtable; @@ -1588,7 +1588,8 @@ transformIndexStmt(ParseState *pstate, IndexStmt *stmt) /* no to join list, yes to namespace */ addRTEtoQuery(pstate, rte, false, true); - stmt->whereClause = transformWhereClause(pstate, stmt->whereClause); + stmt->whereClause = transformWhereClause(pstate, stmt->whereClause, + "WHERE"); } /* take care of any index expressions */ @@ -1699,7 +1700,8 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt, } /* take care of the where clause */ - stmt->whereClause = transformWhereClause(pstate, stmt->whereClause); + stmt->whereClause = transformWhereClause(pstate, stmt->whereClause, + "WHERE"); if (length(pstate->p_rtable) != 2) /* naughty, naughty... */ elog(ERROR, "Rule WHERE condition may not contain references to other relations"); @@ -1891,13 +1893,14 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt) markTargetListOrigins(pstate, qry->targetList); /* transform WHERE */ - qual = transformWhereClause(pstate, stmt->whereClause); + qual = transformWhereClause(pstate, stmt->whereClause, "WHERE"); /* * Initial processing of HAVING clause is just like WHERE clause. * Additional work will be done in optimizer/plan/planner.c. */ - qry->havingQual = transformWhereClause(pstate, stmt->havingClause); + qry->havingQual = transformWhereClause(pstate, stmt->havingClause, + "HAVING"); /* * Transform sorting/grouping stuff. Do ORDER BY first because both @@ -1918,8 +1921,10 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt) qry->targetList, &qry->sortClause); - qry->limitOffset = stmt->limitOffset; - qry->limitCount = stmt->limitCount; + qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset, + "OFFSET"); + qry->limitCount = transformLimitClause(pstate, stmt->limitCount, + "LIMIT"); qry->rtable = pstate->p_rtable; qry->jointree = makeFromExpr(pstate->p_joinlist, qual); @@ -2124,8 +2129,10 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt) if (tllen != length(qry->targetList)) elog(ERROR, "ORDER BY on a UNION/INTERSECT/EXCEPT result must be on one of the result columns"); - qry->limitOffset = limitOffset; - qry->limitCount = limitCount; + qry->limitOffset = transformLimitClause(pstate, limitOffset, + "OFFSET"); + qry->limitCount = transformLimitClause(pstate, limitCount, + "LIMIT"); qry->rtable = pstate->p_rtable; qry->jointree = makeFromExpr(pstate->p_joinlist, NULL); @@ -2376,7 +2383,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt) qry->targetList = transformTargetList(pstate, stmt->targetList); - qual = transformWhereClause(pstate, stmt->whereClause); + qual = transformWhereClause(pstate, stmt->whereClause, "WHERE"); qry->rtable = pstate->p_rtable; qry->jointree = makeFromExpr(pstate->p_joinlist, qual); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index d8485a2d4d7e229594a1db5f9a813cb56b3b79a4..0ef919c38ae5405b80209128dc4195dfd545d953 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.425 2003/07/03 16:33:37 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.426 2003/07/03 19:07:36 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -4585,12 +4585,13 @@ select_limit: | OFFSET select_offset_value { $$ = makeList2($2, NULL); } | LIMIT select_limit_value ',' select_offset_value - /* Disabled because it was too confusing, bjm 2002-02-18 */ - { elog(ERROR, - "LIMIT #,# syntax not supported.\n\tUse separate LIMIT and OFFSET clauses."); } + { + /* Disabled because it was too confusing, bjm 2002-02-18 */ + elog(ERROR, + "LIMIT #,# syntax not supported.\n\tUse separate LIMIT and OFFSET clauses."); + } ; - opt_select_limit: select_limit { $$ = $1; } | /* EMPTY */ @@ -4598,67 +4599,18 @@ opt_select_limit: ; select_limit_value: - Iconst - { - Const *n = makeNode(Const); - - if ($1 < 0) - elog(ERROR, "LIMIT must not be negative"); - - n->consttype = INT4OID; - n->constlen = sizeof(int4); - n->constvalue = Int32GetDatum($1); - n->constisnull = FALSE; - n->constbyval = TRUE; - $$ = (Node *)n; - } + a_expr { $$ = $1; } | ALL { /* LIMIT ALL is represented as a NULL constant */ - Const *n = makeNode(Const); - - n->consttype = INT4OID; - n->constlen = sizeof(int4); - n->constvalue = (Datum) 0; - n->constisnull = TRUE; - n->constbyval = TRUE; - $$ = (Node *)n; - } - | PARAM - { - Param *n = makeNode(Param); - - n->paramkind = PARAM_NUM; - n->paramid = $1; - n->paramtype = INT4OID; + A_Const *n = makeNode(A_Const); + n->val.type = T_Null; $$ = (Node *)n; } ; select_offset_value: - Iconst - { - Const *n = makeNode(Const); - - if ($1 < 0) - elog(ERROR, "OFFSET must not be negative"); - - n->consttype = INT4OID; - n->constlen = sizeof(int4); - n->constvalue = Int32GetDatum($1); - n->constisnull = FALSE; - n->constbyval = TRUE; - $$ = (Node *)n; - } - | PARAM - { - Param *n = makeNode(Param); - - n->paramkind = PARAM_NUM; - n->paramid = $1; - n->paramtype = INT4OID; - $$ = (Node *)n; - } + a_expr { $$ = $1; } ; /* diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index 4d08704da66ce4a90c41969f0acab55390bf771c..9094a14ae53b0f07fe9c75311aaaf5b696a39d63 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.116 2003/06/16 02:03:37 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.117 2003/07/03 19:07:45 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -317,10 +317,7 @@ transformJoinOnClause(ParseState *pstate, JoinExpr *j, save_namespace = pstate->p_namespace; pstate->p_namespace = makeList2(j->larg, j->rarg); - /* This part is just like transformWhereClause() */ - result = transformExpr(pstate, j->quals); - - result = coerce_to_boolean(pstate, result, "JOIN/ON"); + result = transformWhereClause(pstate, j->quals, "JOIN/ON"); pstate->p_namespace = save_namespace; @@ -945,10 +942,38 @@ buildMergedJoinVar(ParseState *pstate, JoinType jointype, /* * transformWhereClause - - * transforms the qualification and make sure it is of type Boolean + * Transform the qualification and make sure it is of type boolean. + * Used for WHERE and allied clauses. + * + * constructName does not affect the semantics, but is used in error messages + */ +Node * +transformWhereClause(ParseState *pstate, Node *clause, + const char *constructName) +{ + Node *qual; + + if (clause == NULL) + return NULL; + + qual = transformExpr(pstate, clause); + + qual = coerce_to_boolean(pstate, qual, constructName); + + return qual; +} + + +/* + * transformLimitClause - + * Transform the expression and make sure it is of type integer. + * Used for LIMIT and allied clauses. + * + * constructName does not affect the semantics, but is used in error messages */ Node * -transformWhereClause(ParseState *pstate, Node *clause) +transformLimitClause(ParseState *pstate, Node *clause, + const char *constructName) { Node *qual; @@ -957,7 +982,31 @@ transformWhereClause(ParseState *pstate, Node *clause) qual = transformExpr(pstate, clause); - qual = coerce_to_boolean(pstate, qual, "WHERE"); + qual = coerce_to_integer(pstate, qual, constructName); + + /* + * LIMIT can't refer to any vars or aggregates of the current query; + * we don't allow subselects either (though that case would at least + * be sensible) + */ + if (contain_vars_of_level(qual, 0)) + { + /* translator: %s is name of a SQL construct, eg LIMIT */ + elog(ERROR, "argument of %s must not contain variables", + constructName); + } + if (checkExprHasAggs(qual)) + { + /* translator: %s is name of a SQL construct, eg LIMIT */ + elog(ERROR, "argument of %s must not contain aggregates", + constructName); + } + if (contain_subplans(qual)) + { + /* translator: %s is name of a SQL construct, eg LIMIT */ + elog(ERROR, "argument of %s must not contain subselects", + constructName); + } return qual; } diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c index 977780fecc857adc8ca6a9a40bf1a2637b10fe11..1544f94cf2e602b996eab23dbe55390ddb3b5b1f 100644 --- a/src/backend/parser/parse_coerce.c +++ b/src/backend/parser/parse_coerce.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_coerce.c,v 2.102 2003/07/01 19:10:53 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_coerce.c,v 2.103 2003/07/03 19:07:48 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -564,7 +564,7 @@ coerce_to_boolean(ParseState *pstate, Node *node, if (node == NULL) { /* translator: first %s is name of a SQL construct, eg WHERE */ - elog(ERROR, "Argument of %s must be type boolean, not type %s", + elog(ERROR, "argument of %s must be type boolean, not type %s", constructName, format_type_be(inputTypeId)); } } @@ -572,7 +572,46 @@ coerce_to_boolean(ParseState *pstate, Node *node, if (expression_returns_set(node)) { /* translator: %s is name of a SQL construct, eg WHERE */ - elog(ERROR, "Argument of %s must not be a set function", + elog(ERROR, "argument of %s must not be a set function", + constructName); + } + + return node; +} + +/* coerce_to_integer() + * Coerce an argument of a construct that requires integer input + * (LIMIT, OFFSET, etc). Also check that input is not a set. + * + * Returns the possibly-transformed node tree. + * + * As with coerce_type, pstate may be NULL if no special unknown-Param + * processing is wanted. + */ +Node * +coerce_to_integer(ParseState *pstate, Node *node, + const char *constructName) +{ + Oid inputTypeId = exprType(node); + + if (inputTypeId != INT4OID) + { + node = coerce_to_target_type(pstate, node, inputTypeId, + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST); + if (node == NULL) + { + /* translator: first %s is name of a SQL construct, eg LIMIT */ + elog(ERROR, "argument of %s must be type integer, not type %s", + constructName, format_type_be(inputTypeId)); + } + } + + if (expression_returns_set(node)) + { + /* translator: %s is name of a SQL construct, eg LIMIT */ + elog(ERROR, "argument of %s must not be a set function", constructName); } diff --git a/src/include/parser/parse_clause.h b/src/include/parser/parse_clause.h index c2a3b2dc77b8068860ad0091b4884a888a173cc1..6f4f87a8b6675eefc15ab094a57e006fa8d39ac6 100644 --- a/src/include/parser/parse_clause.h +++ b/src/include/parser/parse_clause.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: parse_clause.h,v 1.32 2003/06/16 02:03:38 tgl Exp $ + * $Id: parse_clause.h,v 1.33 2003/07/03 19:07:53 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -21,7 +21,10 @@ extern int setTargetTable(ParseState *pstate, RangeVar *relation, bool inh, bool alsoSource); extern bool interpretInhOption(InhOption inhOpt); -extern Node *transformWhereClause(ParseState *pstate, Node *where); +extern Node *transformWhereClause(ParseState *pstate, Node *clause, + const char *constructName); +extern Node *transformLimitClause(ParseState *pstate, Node *clause, + const char *constructName); extern List *transformGroupClause(ParseState *pstate, List *grouplist, List *targetlist, List *sortClause); extern List *transformSortClause(ParseState *pstate, List *orderlist, diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h index 5facbc7d7d4fa43a8866718cd494f4eb8b0aa9ae..88d7b614bb9dd020bf3cf301a958c730be6933e7 100644 --- a/src/include/parser/parse_coerce.h +++ b/src/include/parser/parse_coerce.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: parse_coerce.h,v 1.52 2003/07/01 19:10:53 tgl Exp $ + * $Id: parse_coerce.h,v 1.53 2003/07/03 19:07:54 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -54,6 +54,8 @@ extern Node *coerce_to_domain(Node *arg, Oid baseTypeId, Oid typeId, extern Node *coerce_to_boolean(ParseState *pstate, Node *node, const char *constructName); +extern Node *coerce_to_integer(ParseState *pstate, Node *node, + const char *constructName); extern Oid select_common_type(List *typeids, const char *context); extern Node *coerce_to_common_type(ParseState *pstate, Node *node,