1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-17 06:41:09 +03:00

Support ORDER BY ... NULLS FIRST/LAST, and add ASC/DESC/NULLS FIRST/NULLS LAST

per-column options for btree indexes.  The planner's support for this is still
pretty rudimentary; it does not yet know how to plan mergejoins with
nondefault ordering options.  The documentation is pretty rudimentary, too.
I'll work on improving that stuff later.

Note incompatible change from prior behavior: ORDER BY ... USING will now be
rejected if the operator is not a less-than or greater-than member of some
btree opclass.  This prevents less-than-sane behavior if an operator that
doesn't actually define a proper sort ordering is selected.
This commit is contained in:
Tom Lane
2007-01-09 02:14:16 +00:00
parent 3a32ba2f3f
commit 4431758229
65 changed files with 1476 additions and 593 deletions

View File

@ -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);
}

View File

@ -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

View File

@ -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},

View File

@ -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;

View File

@ -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
*