summaryrefslogtreecommitdiff
path: root/src/backend/parser
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/parser')
-rw-r--r--src/backend/parser/analyze.c4
-rw-r--r--src/backend/parser/gram.y54
-rw-r--r--src/backend/parser/keywords.c3
-rw-r--r--src/backend/parser/parse_clause.c148
-rw-r--r--src/backend/parser/parser.c31
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
*