1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-03 20:02:46 +03:00

Support FETCH FIRST WITH TIES

WITH TIES is an option to the FETCH FIRST N ROWS clause (the SQL
standard's spelling of LIMIT), where you additionally get rows that
compare equal to the last of those N rows by the columns in the
mandatory ORDER BY clause.

There was a proposal by Andrew Gierth to implement this functionality in
a more powerful way that would yield more features, but the other patch
had not been finished at this time, so we decided to use this one for
now in the spirit of incremental development.

Author: Surafel Temesgen <surafel3000@gmail.com>
Reviewed-by: Álvaro Herrera <alvherre@alvh.no-ip.org>
Reviewed-by: Tomas Vondra <tomas.vondra@2ndquadrant.com>
Discussion: https://postgr.es/m/CALAY4q9ky7rD_A4vf=FVQvCGngm3LOes-ky0J6euMrg=_Se+ag@mail.gmail.com
Discussion: https://postgr.es/m/87o8wvz253.fsf@news-spur.riddles.org.uk
This commit is contained in:
Alvaro Herrera
2020-04-07 16:22:13 -04:00
parent 26a944cf29
commit 357889eb17
25 changed files with 616 additions and 75 deletions

View File

@ -1281,9 +1281,12 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
/* transform LIMIT */
qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset,
EXPR_KIND_OFFSET, "OFFSET");
EXPR_KIND_OFFSET, "OFFSET",
stmt->limitOption);
qry->limitCount = transformLimitClause(pstate, stmt->limitCount,
EXPR_KIND_LIMIT, "LIMIT");
EXPR_KIND_LIMIT, "LIMIT",
stmt->limitOption);
qry->limitOption = stmt->limitOption;
/* transform window clauses after we have seen all window functions */
qry->windowClause = transformWindowDefinitions(pstate,
@ -1524,9 +1527,12 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
false /* allow SQL92 rules */ );
qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset,
EXPR_KIND_OFFSET, "OFFSET");
EXPR_KIND_OFFSET, "OFFSET",
stmt->limitOption);
qry->limitCount = transformLimitClause(pstate, stmt->limitCount,
EXPR_KIND_LIMIT, "LIMIT");
EXPR_KIND_LIMIT, "LIMIT",
stmt->limitOption);
qry->limitOption = stmt->limitOption;
if (stmt->lockingClause)
ereport(ERROR,
@ -1775,9 +1781,12 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
exprLocation(list_nth(qry->targetList, tllen)))));
qry->limitOffset = transformLimitClause(pstate, limitOffset,
EXPR_KIND_OFFSET, "OFFSET");
EXPR_KIND_OFFSET, "OFFSET",
stmt->limitOption);
qry->limitCount = transformLimitClause(pstate, limitCount,
EXPR_KIND_LIMIT, "LIMIT");
EXPR_KIND_LIMIT, "LIMIT",
stmt->limitOption);
qry->limitOption = stmt->limitOption;
qry->rtable = pstate->p_rtable;
qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);

View File

@ -127,6 +127,14 @@ typedef struct ImportQual
List *table_names;
} ImportQual;
/* Private struct for the result of opt_select_limit production */
typedef struct SelectLimit
{
Node *limitOffset;
Node *limitCount;
LimitOption limitOption;
} SelectLimit;
/* ConstraintAttributeSpec yields an integer bitmask of these flags: */
#define CAS_NOT_DEFERRABLE 0x01
#define CAS_DEFERRABLE 0x02
@ -164,7 +172,7 @@ static List *makeOrderedSetArgs(List *directargs, List *orderedargs,
core_yyscan_t yyscanner);
static void insertSelectOptions(SelectStmt *stmt,
List *sortClause, List *lockingClause,
Node *limitOffset, Node *limitCount,
SelectLimit *limitClause,
WithClause *withClause,
core_yyscan_t yyscanner);
static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
@ -242,6 +250,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
struct SelectLimit *selectlimit;
}
%type <node> stmt schema_stmt
@ -374,6 +383,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> import_qualification_type
%type <importqual> import_qualification
%type <node> vacuum_relation
%type <selectlimit> opt_select_limit select_limit limit_clause
%type <list> stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition
@ -394,8 +404,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
target_list opt_target_list insert_column_list set_target_list
set_clause_list set_clause
def_list operator_def_list indirection opt_indirection
reloption_list group_clause TriggerFuncArgs select_limit
opt_select_limit opclass_item_list opclass_drop_list
reloption_list group_clause TriggerFuncArgs opclass_item_list opclass_drop_list
opclass_purpose opt_opfamily transaction_mode_list_or_empty
OptTableFuncElementList TableFuncElementList opt_type_modifiers
prep_type_clause
@ -458,7 +467,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
comment_type_any_name comment_type_name
security_label_type_any_name security_label_type_name
%type <node> fetch_args limit_clause select_limit_value
%type <node> fetch_args select_limit_value
offset_clause select_offset_value
select_fetch_first_value I_or_F_const
%type <ival> row_or_rows first_or_next
@ -11378,14 +11387,14 @@ select_no_parens:
| select_clause sort_clause
{
insertSelectOptions((SelectStmt *) $1, $2, NIL,
NULL, NULL, NULL,
NULL, NULL,
yyscanner);
$$ = $1;
}
| select_clause opt_sort_clause for_locking_clause opt_select_limit
{
insertSelectOptions((SelectStmt *) $1, $2, $3,
list_nth($4, 0), list_nth($4, 1),
$4,
NULL,
yyscanner);
$$ = $1;
@ -11393,7 +11402,7 @@ select_no_parens:
| select_clause opt_sort_clause select_limit opt_for_locking_clause
{
insertSelectOptions((SelectStmt *) $1, $2, $4,
list_nth($3, 0), list_nth($3, 1),
$3,
NULL,
yyscanner);
$$ = $1;
@ -11401,7 +11410,7 @@ select_no_parens:
| with_clause select_clause
{
insertSelectOptions((SelectStmt *) $2, NULL, NIL,
NULL, NULL,
NULL,
$1,
yyscanner);
$$ = $2;
@ -11409,7 +11418,7 @@ select_no_parens:
| with_clause select_clause sort_clause
{
insertSelectOptions((SelectStmt *) $2, $3, NIL,
NULL, NULL,
NULL,
$1,
yyscanner);
$$ = $2;
@ -11417,7 +11426,7 @@ select_no_parens:
| with_clause select_clause opt_sort_clause for_locking_clause opt_select_limit
{
insertSelectOptions((SelectStmt *) $2, $3, $4,
list_nth($5, 0), list_nth($5, 1),
$5,
$1,
yyscanner);
$$ = $2;
@ -11425,7 +11434,7 @@ select_no_parens:
| with_clause select_clause opt_sort_clause select_limit opt_for_locking_clause
{
insertSelectOptions((SelectStmt *) $2, $3, $5,
list_nth($4, 0), list_nth($4, 1),
$4,
$1,
yyscanner);
$$ = $2;
@ -11719,20 +11728,44 @@ sortby: a_expr USING qual_all_Op opt_nulls_order
select_limit:
limit_clause offset_clause { $$ = list_make2($2, $1); }
| offset_clause limit_clause { $$ = list_make2($1, $2); }
| limit_clause { $$ = list_make2(NULL, $1); }
| offset_clause { $$ = list_make2($1, NULL); }
limit_clause offset_clause
{
$$ = $1;
($$)->limitOffset = $2;
}
| offset_clause limit_clause
{
$$ = $2;
($$)->limitOffset = $1;
}
| limit_clause
{
$$ = $1;
}
| offset_clause
{
SelectLimit *n = (SelectLimit *) palloc(sizeof(SelectLimit));
n->limitOffset = $1;
n->limitCount = NULL;
n->limitOption = LIMIT_OPTION_COUNT;
$$ = n;
}
;
opt_select_limit:
select_limit { $$ = $1; }
| /* EMPTY */ { $$ = list_make2(NULL,NULL); }
| /* EMPTY */ { $$ = NULL; }
;
limit_clause:
LIMIT select_limit_value
{ $$ = $2; }
{
SelectLimit *n = (SelectLimit *) palloc(sizeof(SelectLimit));
n->limitOffset = NULL;
n->limitCount = $2;
n->limitOption = LIMIT_OPTION_COUNT;
$$ = n;
}
| LIMIT select_limit_value ',' select_offset_value
{
/* Disabled because it was too confusing, bjm 2002-02-18 */
@ -11750,9 +11783,29 @@ limit_clause:
* we can see the ONLY token in the lookahead slot.
*/
| FETCH first_or_next select_fetch_first_value row_or_rows ONLY
{ $$ = $3; }
{
SelectLimit *n = (SelectLimit *) palloc(sizeof(SelectLimit));
n->limitOffset = NULL;
n->limitCount = $3;
n->limitOption = LIMIT_OPTION_COUNT;
$$ = n;
}
| FETCH first_or_next select_fetch_first_value row_or_rows WITH TIES
{
SelectLimit *n = (SelectLimit *) palloc(sizeof(SelectLimit));
n->limitOffset = NULL;
n->limitCount = $3;
n->limitOption = LIMIT_OPTION_WITH_TIES;
$$ = n;
}
| FETCH first_or_next row_or_rows ONLY
{ $$ = makeIntConst(1, -1); }
{
SelectLimit *n = (SelectLimit *) palloc(sizeof(SelectLimit));
n->limitOffset = NULL;
n->limitCount = makeIntConst(1, -1);
n->limitOption = LIMIT_OPTION_COUNT;
$$ = n;
}
;
offset_clause:
@ -16047,7 +16100,7 @@ makeOrderedSetArgs(List *directargs, List *orderedargs,
static void
insertSelectOptions(SelectStmt *stmt,
List *sortClause, List *lockingClause,
Node *limitOffset, Node *limitCount,
SelectLimit *limitClause,
WithClause *withClause,
core_yyscan_t yyscanner)
{
@ -16068,23 +16121,35 @@ insertSelectOptions(SelectStmt *stmt,
}
/* We can handle multiple locking clauses, though */
stmt->lockingClause = list_concat(stmt->lockingClause, lockingClause);
if (limitOffset)
if (limitClause && limitClause->limitOffset)
{
if (stmt->limitOffset)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple OFFSET clauses not allowed"),
parser_errposition(exprLocation(limitOffset))));
stmt->limitOffset = limitOffset;
parser_errposition(exprLocation(limitClause->limitOffset))));
stmt->limitOffset = limitClause->limitOffset;
}
if (limitCount)
if (limitClause && limitClause->limitCount)
{
if (stmt->limitCount)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple LIMIT clauses not allowed"),
parser_errposition(exprLocation(limitCount))));
stmt->limitCount = limitCount;
parser_errposition(exprLocation(limitClause->limitCount))));
stmt->limitCount = limitClause->limitCount;
}
if (limitClause && limitClause->limitOption != LIMIT_OPTION_DEFAULT)
{
if (stmt->limitOption)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple limit options not allowed")));
if (!stmt->sortClause && limitClause->limitOption == LIMIT_OPTION_WITH_TIES)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("WITH TIES options can not be specified without ORDER BY clause")));
stmt->limitOption = limitClause->limitOption;
}
if (withClause)
{

View File

@ -1745,7 +1745,8 @@ transformWhereClause(ParseState *pstate, Node *clause,
*/
Node *
transformLimitClause(ParseState *pstate, Node *clause,
ParseExprKind exprKind, const char *constructName)
ParseExprKind exprKind, const char *constructName,
LimitOption limitOption)
{
Node *qual;
@ -1759,6 +1760,18 @@ transformLimitClause(ParseState *pstate, Node *clause,
/* LIMIT can't refer to any variables of the current query */
checkExprIsVarFree(pstate, qual, constructName);
/*
* Don't allow NULLs in FETCH FIRST .. WITH TIES. This test is ugly and
* extremely simplistic, in that you can pass a NULL anyway by hiding it
* inside an expression -- but this protects ruleutils against emitting an
* unadorned NULL that's not accepted back by the grammar.
*/
if (exprKind == EXPR_KIND_LIMIT && limitOption == LIMIT_OPTION_WITH_TIES &&
IsA(clause, A_Const) && ((A_Const *) clause)->val.type == T_Null)
ereport(ERROR,
(errcode(ERRCODE_INVALID_ROW_COUNT_IN_LIMIT_CLAUSE),
errmsg("row count cannot be NULL in FETCH FIRST ... WITH TIES clause")));
return qual;
}