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:
@ -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);
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user