diff options
Diffstat (limited to 'src/backend/parser')
| -rw-r--r-- | src/backend/parser/analyze.c | 4 | ||||
| -rw-r--r-- | src/backend/parser/gram.y | 54 | ||||
| -rw-r--r-- | src/backend/parser/keywords.c | 3 | ||||
| -rw-r--r-- | src/backend/parser/parse_clause.c | 148 | ||||
| -rw-r--r-- | src/backend/parser/parser.c | 31 |
5 files changed, 179 insertions, 61 deletions
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 270332812b7..f4b566cb6de 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.354 2007/01/05 22:19:33 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.355 2007/01/09 02:14:13 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1629,6 +1629,8 @@ transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt) iparam->name = pstrdup(key); iparam->expr = NULL; iparam->opclass = NIL; + iparam->ordering = SORTBY_DEFAULT; + iparam->nulls_ordering = SORTBY_NULLS_DEFAULT; index->indexParams = lappend(index->indexParams, iparam); } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index e3eae427ded..6abe0d6795a 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.572 2007/01/05 22:19:33 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.573 2007/01/09 02:14:14 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -175,7 +175,7 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args) simple_select values_clause %type <node> alter_column_default opclass_item alter_using -%type <ival> add_drop +%type <ival> add_drop opt_asc_desc opt_nulls_order %type <node> alter_table_cmd alter_rel_cmd %type <list> alter_table_cmds alter_rel_cmds @@ -397,7 +397,7 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args) KEY - LANCOMPILER LANGUAGE LARGE_P LAST_P LEADING LEAST LEFT LEVEL + LANCOMPILER LANGUAGE LARGE_P LAST_P LEADING LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOGIN_P @@ -405,7 +405,7 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args) NAME_P NAMES NATIONAL NATURAL NCHAR NEW NEXT NO NOCREATEDB NOCREATEROLE NOCREATEUSER NOINHERIT NOLOGIN_P NONE NOSUPERUSER - NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF NUMERIC + NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF NULLS_P NUMERIC OBJECT_P OF OFF OFFSET OIDS OLD ON ONLY OPERATOR OPTION OR ORDER OUT_P OUTER_P OVERLAPS OVERLAY OWNED OWNER @@ -449,7 +449,7 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args) * list and so can never be entered directly. The filter in parser.c * creates these tokens when required. */ -%token WITH_CASCADED WITH_LOCAL WITH_CHECK +%token NULLS_FIRST NULLS_LAST WITH_CASCADED WITH_LOCAL WITH_CHECK /* Special token types, not actually keywords - see the "lex" file */ %token <str> IDENT FCONST SCONST BCONST XCONST Op @@ -3712,26 +3712,32 @@ index_params: index_elem { $$ = list_make1($1); } * expressions in parens. For backwards-compatibility reasons, we allow * an expression that's just a function call to be written without parens. */ -index_elem: ColId opt_class +index_elem: ColId opt_class opt_asc_desc opt_nulls_order { $$ = makeNode(IndexElem); $$->name = $1; $$->expr = NULL; $$->opclass = $2; + $$->ordering = $3; + $$->nulls_ordering = $4; } - | func_expr opt_class + | func_expr opt_class opt_asc_desc opt_nulls_order { $$ = makeNode(IndexElem); $$->name = NULL; $$->expr = $1; $$->opclass = $2; + $$->ordering = $3; + $$->nulls_ordering = $4; } - | '(' a_expr ')' opt_class + | '(' a_expr ')' opt_class opt_asc_desc opt_nulls_order { $$ = makeNode(IndexElem); $$->name = NULL; $$->expr = $2; $$->opclass = $4; + $$->ordering = $5; + $$->nulls_ordering = $6; } ; @@ -3740,6 +3746,17 @@ opt_class: any_name { $$ = $1; } | /*EMPTY*/ { $$ = NIL; } ; +opt_asc_desc: ASC { $$ = SORTBY_ASC; } + | DESC { $$ = SORTBY_DESC; } + | /*EMPTY*/ { $$ = SORTBY_DEFAULT; } + ; + +opt_nulls_order: NULLS_FIRST { $$ = SORTBY_NULLS_FIRST; } + | NULLS_LAST { $$ = SORTBY_NULLS_LAST; } + | /*EMPTY*/ { $$ = SORTBY_NULLS_DEFAULT; } + ; + + /***************************************************************************** * * QUERY: @@ -5810,32 +5827,36 @@ sortby_list: | sortby_list ',' sortby { $$ = lappend($1, $3); } ; -sortby: a_expr USING qual_all_Op +sortby: a_expr USING qual_all_Op opt_nulls_order { $$ = makeNode(SortBy); $$->node = $1; - $$->sortby_kind = SORTBY_USING; + $$->sortby_dir = SORTBY_USING; + $$->sortby_nulls = $4; $$->useOp = $3; } - | a_expr ASC + | a_expr ASC opt_nulls_order { $$ = makeNode(SortBy); $$->node = $1; - $$->sortby_kind = SORTBY_ASC; + $$->sortby_dir = SORTBY_ASC; + $$->sortby_nulls = $3; $$->useOp = NIL; } - | a_expr DESC + | a_expr DESC opt_nulls_order { $$ = makeNode(SortBy); $$->node = $1; - $$->sortby_kind = SORTBY_DESC; + $$->sortby_dir = SORTBY_DESC; + $$->sortby_nulls = $3; $$->useOp = NIL; } - | a_expr + | a_expr opt_nulls_order { $$ = makeNode(SortBy); $$->node = $1; - $$->sortby_kind = SORTBY_ASC; /* default */ + $$->sortby_dir = SORTBY_DEFAULT; + $$->sortby_nulls = $2; $$->useOp = NIL; } ; @@ -8613,6 +8634,7 @@ unreserved_keyword: | NOTHING | NOTIFY | NOWAIT + | NULLS_P | OBJECT_P | OF | OIDS diff --git a/src/backend/parser/keywords.c b/src/backend/parser/keywords.c index e6a4f9a7ebc..e1592736f0d 100644 --- a/src/backend/parser/keywords.c +++ b/src/backend/parser/keywords.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.180 2007/01/05 22:19:33 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.181 2007/01/09 02:14:14 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -242,6 +242,7 @@ static const ScanKeyword ScanKeywords[] = { {"nowait", NOWAIT}, {"null", NULL_P}, {"nullif", NULLIF}, + {"nulls", NULLS_P}, {"numeric", NUMERIC}, {"object", OBJECT_P}, {"of", OF}, diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index 663273df48b..6db3fce8377 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.161 2007/01/05 22:19:33 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.162 2007/01/09 02:14:14 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -33,6 +33,7 @@ #include "parser/parse_target.h" #include "rewrite/rewriteManip.h" #include "utils/guc.h" +#include "utils/lsyscache.h" #define ORDER_CLAUSE 0 @@ -1305,13 +1306,15 @@ findTargetlistEntry(ParseState *pstate, Node *node, List **tlist, int clause) } static GroupClause * -make_group_clause(TargetEntry *tle, List *targetlist, Oid sortop) +make_group_clause(TargetEntry *tle, List *targetlist, + Oid sortop, bool nulls_first) { GroupClause *result; result = makeNode(GroupClause); result->tleSortGroupRef = assignSortGroupRef(tle, targetlist); result->sortop = sortop; + result->nulls_first = nulls_first; return result; } @@ -1380,8 +1383,9 @@ transformGroupClause(ParseState *pstate, List *grouplist, tle_list = list_delete_cell(tle_list, tl, prev); - /* Use the sort clause's sorting operator */ - gc = make_group_clause(tle, *targetlist, sc->sortop); + /* Use the sort clause's sorting information */ + gc = make_group_clause(tle, *targetlist, + sc->sortop, sc->nulls_first); result = lappend(result, gc); found = true; break; @@ -1408,12 +1412,18 @@ transformGroupClause(ParseState *pstate, List *grouplist, GroupClause *gc; Oid sort_op; - /* avoid making duplicate grouplist entries */ - if (targetIsInSortList(tle, result)) + /* + * Avoid making duplicate grouplist entries. Note that we don't + * enforce a particular sortop here. Along with the copying of sort + * information above, this means that if you write something like + * "GROUP BY foo ORDER BY foo USING <<<", the GROUP BY operation + * silently takes on the equality semantics implied by the ORDER BY. + */ + if (targetIsInSortList(tle, InvalidOid, result)) continue; sort_op = ordering_oper_opid(exprType((Node *) tle->expr)); - gc = make_group_clause(tle, *targetlist, sort_op); + gc = make_group_clause(tle, *targetlist, sort_op, false); result = lappend(result, gc); } @@ -1447,7 +1457,8 @@ transformSortClause(ParseState *pstate, sortlist = addTargetToSortList(pstate, tle, sortlist, *targetlist, - sortby->sortby_kind, + sortby->sortby_dir, + sortby->sortby_nulls, sortby->useOp, resolveUnknown); } @@ -1553,7 +1564,9 @@ transformDistinctClause(ParseState *pstate, List *distinctlist, { *sortClause = addTargetToSortList(pstate, tle, *sortClause, *targetlist, - SORTBY_ASC, NIL, true); + SORTBY_DEFAULT, + SORTBY_NULLS_DEFAULT, + NIL, true); /* * Probably, the tle should always have been added at the end @@ -1601,8 +1614,9 @@ addAllTargetsToSortList(ParseState *pstate, List *sortlist, if (!tle->resjunk) sortlist = addTargetToSortList(pstate, tle, sortlist, targetlist, - SORTBY_ASC, NIL, - resolveUnknown); + SORTBY_DEFAULT, + SORTBY_NULLS_DEFAULT, + NIL, resolveUnknown); } return sortlist; } @@ -1610,8 +1624,7 @@ addAllTargetsToSortList(ParseState *pstate, List *sortlist, /* * addTargetToSortList * If the given targetlist entry isn't already in the ORDER BY list, - * add it to the end of the list, using the sortop with given name - * or the default sort operator if opname == NIL. + * add it to the end of the list, using the given sort ordering info. * * If resolveUnknown is TRUE, convert TLEs of type UNKNOWN to TEXT. If not, * do nothing (which implies the search for a sort operator will fail). @@ -1623,49 +1636,89 @@ addAllTargetsToSortList(ParseState *pstate, List *sortlist, List * addTargetToSortList(ParseState *pstate, TargetEntry *tle, List *sortlist, List *targetlist, - int sortby_kind, List *sortby_opname, - bool resolveUnknown) + SortByDir sortby_dir, SortByNulls sortby_nulls, + List *sortby_opname, bool resolveUnknown) { + Oid restype = exprType((Node *) tle->expr); + Oid sortop; + Oid cmpfunc; + bool reverse; + + /* if tlist item is an UNKNOWN literal, change it to TEXT */ + if (restype == UNKNOWNOID && resolveUnknown) + { + tle->expr = (Expr *) coerce_type(pstate, (Node *) tle->expr, + restype, TEXTOID, -1, + COERCION_IMPLICIT, + COERCE_IMPLICIT_CAST); + restype = TEXTOID; + } + + /* determine the sortop */ + switch (sortby_dir) + { + case SORTBY_DEFAULT: + case SORTBY_ASC: + sortop = ordering_oper_opid(restype); + reverse = false; + break; + case SORTBY_DESC: + sortop = reverse_ordering_oper_opid(restype); + reverse = true; + break; + case SORTBY_USING: + Assert(sortby_opname != NIL); + sortop = compatible_oper_opid(sortby_opname, + restype, + restype, + false); + /* + * Verify it's a valid ordering operator, and determine + * whether to consider it like ASC or DESC. + */ + if (!get_op_compare_function(sortop, &cmpfunc, &reverse)) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("operator %s is not a valid ordering operator", + strVal(llast(sortby_opname))), + errhint("Ordering operators must be \"<\" or \">\" members of btree operator families."))); + break; + default: + elog(ERROR, "unrecognized sortby_dir: %d", sortby_dir); + sortop = InvalidOid; /* keep compiler quiet */ + reverse = false; + break; + } + /* avoid making duplicate sortlist entries */ - if (!targetIsInSortList(tle, sortlist)) + if (!targetIsInSortList(tle, sortop, sortlist)) { SortClause *sortcl = makeNode(SortClause); - Oid restype = exprType((Node *) tle->expr); - - /* if tlist item is an UNKNOWN literal, change it to TEXT */ - if (restype == UNKNOWNOID && resolveUnknown) - { - tle->expr = (Expr *) coerce_type(pstate, (Node *) tle->expr, - restype, TEXTOID, -1, - COERCION_IMPLICIT, - COERCE_IMPLICIT_CAST); - restype = TEXTOID; - } sortcl->tleSortGroupRef = assignSortGroupRef(tle, targetlist); - switch (sortby_kind) + sortcl->sortop = sortop; + + switch (sortby_nulls) { - case SORTBY_ASC: - sortcl->sortop = ordering_oper_opid(restype); + case SORTBY_NULLS_DEFAULT: + /* NULLS FIRST is default for DESC; other way for ASC */ + sortcl->nulls_first = reverse; break; - case SORTBY_DESC: - sortcl->sortop = reverse_ordering_oper_opid(restype); + case SORTBY_NULLS_FIRST: + sortcl->nulls_first = true; break; - case SORTBY_USING: - Assert(sortby_opname != NIL); - sortcl->sortop = compatible_oper_opid(sortby_opname, - restype, - restype, - false); + case SORTBY_NULLS_LAST: + sortcl->nulls_first = false; break; default: - elog(ERROR, "unrecognized sortby_kind: %d", sortby_kind); + elog(ERROR, "unrecognized sortby_nulls: %d", sortby_nulls); break; } sortlist = lappend(sortlist, sortcl); } + return sortlist; } @@ -1701,13 +1754,23 @@ assignSortGroupRef(TargetEntry *tle, List *tlist) /* * targetIsInSortList * Is the given target item already in the sortlist? + * If sortop is not InvalidOid, also test for a match to the sortop. + * + * It is not an oversight that this function ignores the nulls_first flag. + * We check sortop when determining if an ORDER BY item is redundant with + * earlier ORDER BY items, because it's conceivable that "ORDER BY + * foo USING <, foo USING <<<" is not redundant, if <<< distinguishes + * values that < considers equal. We need not check nulls_first + * however, because a lower-order column with the same sortop but + * opposite nulls direction is redundant. Also, we can consider + * ORDER BY foo ASC, foo DESC redundant, so check for a commutator match. * * Works for both SortClause and GroupClause lists. Note that the main * reason we need this routine (and not just a quick test for nonzeroness * of ressortgroupref) is that a TLE might be in only one of the lists. */ bool -targetIsInSortList(TargetEntry *tle, List *sortList) +targetIsInSortList(TargetEntry *tle, Oid sortop, List *sortList) { Index ref = tle->ressortgroupref; ListCell *l; @@ -1720,7 +1783,10 @@ targetIsInSortList(TargetEntry *tle, List *sortList) { SortClause *scl = (SortClause *) lfirst(l); - if (scl->tleSortGroupRef == ref) + if (scl->tleSortGroupRef == ref && + (sortop == InvalidOid || + sortop == scl->sortop || + sortop == get_commutator(scl->sortop))) return true; } return false; diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c index c007613cc4e..b9c0b9a9853 100644 --- a/src/backend/parser/parser.c +++ b/src/backend/parser/parser.c @@ -14,7 +14,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parser.c,v 1.70 2007/01/06 19:14:17 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parser.c,v 1.71 2007/01/09 02:14:14 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -97,8 +97,35 @@ filtered_base_yylex(void) /* Do we need to look ahead for a possible multiword token? */ switch (cur_token) { - case WITH: + case NULLS_P: + /* + * NULLS FIRST and NULLS LAST must be reduced to one token + */ + cur_yylval = base_yylval; + cur_yylloc = base_yylloc; + next_token = base_yylex(); + switch (next_token) + { + case FIRST_P: + cur_token = NULLS_FIRST; + break; + case LAST_P: + cur_token = NULLS_LAST; + break; + default: + /* save the lookahead token for next time */ + lookahead_token = next_token; + lookahead_yylval = base_yylval; + lookahead_yylloc = base_yylloc; + have_lookahead = true; + /* and back up the output info to cur_token */ + base_yylval = cur_yylval; + base_yylloc = cur_yylloc; + break; + } + break; + case WITH: /* * WITH CASCADED, LOCAL, or CHECK must be reduced to one token * |
