diff --git a/src/backend/executor/nodeLimit.c b/src/backend/executor/nodeLimit.c index cfb28a9fd4..47e71a221a 100644 --- a/src/backend/executor/nodeLimit.c +++ b/src/backend/executor/nodeLimit.c @@ -193,6 +193,12 @@ ExecLimit(PlanState *pstate) if (!(node->ps.state->es_top_eflags & EXEC_FLAG_BACKWARD)) (void) ExecShutdownNode(outerPlan); + /* + * The only operation from here is backward scan We have + * to move one postion forward to get previous tuple + */ + tuplestore_advance(node->tupleStore, true); + return NULL; } @@ -215,26 +221,47 @@ ExecLimit(PlanState *pstate) if (TupIsNull(slot)) { node->reachEnd = true; - node->lstate = LIMIT_SUBPLANEOF; + if (node->limitOption == LIMIT_OPTION_PER_WITH_TIES) + { + slot = node->subSlot; + tuplestore_advance(node->tupleStore, false); + if (!tuplestore_gettupleslot_heaptuple(node->tupleStore, true, true, slot)) + { + node->lstate = LIMIT_SUBPLANEOF; + tuplestore_advance(node->tupleStore, true); + return NULL; + } - /* - * The only operation from here is backward scan - * but there's no API to refetch the tuple at the - * current position. We have to move one tuple - * backward, and then we will scan forward for it - * for the first tuple and precede as usual for - * the rest - */ - tuplestore_advance(node->tupleStore, false); - return NULL; - } + ExecCopySlot(node->last_slot, slot); + node->lstate = LIMIT_WINDOWEND_TIES; + /* we'll fall through to the next case */ + } + else + { + node->reachEnd = true; + node->lstate = LIMIT_SUBPLANEOF; - tuplestore_puttupleslot(node->tupleStore, slot); + /* + * The only operation from here is backward + * scan but there's no API to refetch the + * tuple at the current position. We have to + * move one postion backward, and then we will + * scan forward for it for the first tuple and + * precede as usual for the rest + */ + tuplestore_advance(node->tupleStore, true); + return NULL; + } + } + if (node->lstate != LIMIT_WINDOWEND_TIES) + { + tuplestore_puttupleslot(node->tupleStore, slot); - cnt = tuplestore_tuple_count(node->tupleStore) + node->offset; + cnt = tuplestore_tuple_count(node->tupleStore) + node->offset; - node->count = ceil(node->percent * cnt / 100.0); - } while (node->position - node->offset >= node->count); + node->count = ceil(node->percent * cnt / 100.0); + } + } while (node->position - node->offset >= node->count && node->lstate != LIMIT_WINDOWEND_TIES); } /* @@ -255,7 +282,7 @@ ExecLimit(PlanState *pstate) */ if (!node->noCount && node->position - node->offset >= node->count - && !IsPercentOption(node->limitOption)) + && !IsPercentOption(node->limitOption) && node->lstate != LIMIT_WINDOWEND_TIES) { if (node->limitOption == LIMIT_OPTION_COUNT) { @@ -268,49 +295,45 @@ ExecLimit(PlanState *pstate) /* we'll fall through to the next case */ } } - else + else if (IsPercentOption(node->limitOption) && node->lstate != LIMIT_WINDOWEND_TIES) { - if (IsPercentOption(node->limitOption)) + if (tuplestore_gettupleslot_heaptuple(node->tupleStore, true, true, slot)) { - if (tuplestore_gettupleslot_heaptuple(node->tupleStore, true, true, slot)) - { - node->subSlot = slot; - node->position++; - break; - } - else - { - node->lstate = LIMIT_SUBPLANEOF; - return NULL; - } - - } - else - { - /* - * Get next tuple from subplan, if any. - */ - slot = ExecProcNode(outerPlan); - if (TupIsNull(slot)) - { - node->lstate = LIMIT_SUBPLANEOF; - return NULL; - } - - /* - * If WITH TIES is active, and this is the last - * in-window tuple, save it to be used in subsequent - * WINDOWEND_TIES processing. - */ - if (node->limitOption == LIMIT_OPTION_WITH_TIES && - node->position - node->offset == node->count - 1) - { - ExecCopySlot(node->last_slot, slot); - } node->subSlot = slot; node->position++; break; } + else + { + node->lstate = LIMIT_SUBPLANEOF; + return NULL; + } + } + else if (!IsPercentOption(node->limitOption) && node->lstate != LIMIT_WINDOWEND_TIES) + { + /* + * Get next tuple from subplan, if any. + */ + slot = ExecProcNode(outerPlan); + if (TupIsNull(slot)) + { + node->lstate = LIMIT_SUBPLANEOF; + return NULL; + } + + /* + * If WITH TIES is active, and this is the last in-window + * tuple, save it to be used in subsequent WINDOWEND_TIES + * processing. + */ + if (node->limitOption == LIMIT_OPTION_WITH_TIES && + node->position - node->offset == node->count - 1) + { + ExecCopySlot(node->last_slot, slot); + } + node->subSlot = slot; + node->position++; + break; } } else @@ -359,14 +382,25 @@ ExecLimit(PlanState *pstate) if (ScanDirectionIsForward(direction)) { /* - * Advance the subplan until we find the first row with - * different ORDER BY pathkeys. + * Advance the subplan or tuple store until we find the first + * row with different ORDER BY pathkeys. */ - slot = ExecProcNode(outerPlan); - if (TupIsNull(slot)) + if (node->limitOption == LIMIT_OPTION_PER_WITH_TIES) { - node->lstate = LIMIT_SUBPLANEOF; - return NULL; + if (!tuplestore_gettupleslot_heaptuple(node->tupleStore, true, true, slot)) + { + node->lstate = LIMIT_SUBPLANEOF; + return NULL; + } + } + else + { + slot = ExecProcNode(outerPlan); + if (TupIsNull(slot)) + { + node->lstate = LIMIT_SUBPLANEOF; + return NULL; + } } /* @@ -399,15 +433,30 @@ ExecLimit(PlanState *pstate) } /* - * Get previous tuple from subplan; there should be one! And - * change state-machine status. + * Get previous tuple from subplan or tuple store; there + * should be one! And change state-machine status. */ - slot = ExecProcNode(outerPlan); - if (TupIsNull(slot)) - elog(ERROR, "LIMIT subplan failed to run backwards"); - node->subSlot = slot; - node->position--; - node->lstate = LIMIT_INWINDOW; + if (node->limitOption == LIMIT_OPTION_PER_WITH_TIES) + { + if (tuplestore_gettupleslot_heaptuple(node->tupleStore, false, true, slot)) + { + node->backwardPosition++; + node->position--; + node->subSlot = slot; + node->lstate = LIMIT_INWINDOW; + } + else + elog(ERROR, "LIMIT subplan failed to run backwards"); + } + else + { + slot = ExecProcNode(outerPlan); + if (TupIsNull(slot)) + elog(ERROR, "LIMIT subplan failed to run backwards"); + node->subSlot = slot; + node->position--; + node->lstate = LIMIT_INWINDOW; + } } break; @@ -416,11 +465,12 @@ ExecLimit(PlanState *pstate) return NULL; /* - * Scan forward for the first tuple + * Scan forward for the previous tuple. there should be one! Note + * previous tuple must be in window. */ if (IsPercentOption(node->limitOption)) { - if (tuplestore_gettupleslot_heaptuple(node->tupleStore, true, true, slot)) + if (tuplestore_gettupleslot_heaptuple(node->tupleStore, false, true, slot)) { node->subSlot = slot; node->lstate = LIMIT_INWINDOW; @@ -461,6 +511,16 @@ ExecLimit(PlanState *pstate) node->subSlot = slot; node->lstate = LIMIT_INWINDOW; } + if (node->limitOption == LIMIT_OPTION_PER_WITH_TIES) + { + if (tuplestore_gettupleslot_heaptuple(node->tupleStore, false, true, slot)) + { + node->subSlot = slot; + node->lstate = LIMIT_INWINDOW; + } + else + elog(ERROR, "LIMIT subplan failed to run backwards"); + } else { /* @@ -686,7 +746,8 @@ ExecInitLimit(Limit *node, EState *estate, int eflags) /* * Initialize the equality evaluation, to detect ties. */ - if (node->limitOption == LIMIT_OPTION_WITH_TIES) + if (node->limitOption == LIMIT_OPTION_WITH_TIES + || node->limitOption == LIMIT_OPTION_PER_WITH_TIES) { TupleDesc desc; const TupleTableSlotOps *ops; diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 99278eed93..c4a81dd2ad 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -2701,7 +2701,8 @@ create_limit_plan(PlannerInfo *root, LimitPath *best_path, int flags) subplan = create_plan_recurse(root, best_path->subpath, flags); /* Extract information necessary for comparing rows for WITH TIES. */ - if (best_path->limitOption == LIMIT_OPTION_WITH_TIES) + if (best_path->limitOption == LIMIT_OPTION_WITH_TIES || + best_path->limitOption == LIMIT_OPTION_PER_WITH_TIES) { Query *parse = root->parse; ListCell *l; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index d9535f3764..94c105400c 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11576,6 +11576,14 @@ limit_clause: n->limitOption = LIMIT_OPTION_WITH_TIES; $$ = n; } + | FETCH first_or_next select_fetch_first_value PERCENT row_or_rows WITH TIES + { + SelectLimit *n = (SelectLimit *) palloc(sizeof(SelectLimit)); + n->limitOffset = NULL; + n->limitCount = $3; + n->limitOption = LIMIT_OPTION_PER_WITH_TIES; + $$ = n; + } | FETCH first_or_next row_or_rows ONLY { SelectLimit *n = (SelectLimit *) palloc(sizeof(SelectLimit)); @@ -16352,7 +16360,8 @@ insertSelectOptions(SelectStmt *stmt, ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("multiple limit options not allowed"))); - if (!stmt->sortClause && limitClause->limitOption == LIMIT_OPTION_WITH_TIES) + if (!stmt->sortClause && (limitClause->limitOption == LIMIT_OPTION_WITH_TIES + || limitClause->limitOption == LIMIT_OPTION_PER_WITH_TIES)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("WITH TIES cannot be specified without ORDER BY clause"))); diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index 4f84497928..3cbbad987d 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -1754,7 +1754,9 @@ transformLimitClause(ParseState *pstate, Node *clause, return NULL; qual = transformExpr(pstate, clause, exprKind); - if (limitOption == LIMIT_OPTION_PERCENT && strcmp(constructName, "LIMIT") == 0) + if ((limitOption == LIMIT_OPTION_PERCENT || limitOption == LIMIT_OPTION_PER_WITH_TIES) + && strcmp(constructName, "LIMIT") == 0) + qual = coerce_to_specific_type(pstate, qual, FLOAT8OID, constructName); else qual = coerce_to_specific_type(pstate, qual, INT8OID, constructName); diff --git a/src/test/regress/expected/limit.out b/src/test/regress/expected/limit.out index 57e4bb2b8b..4b6d3f10ed 100644 --- a/src/test/regress/expected/limit.out +++ b/src/test/regress/expected/limit.out @@ -435,6 +435,43 @@ fetch all in c6; 4567890123456789 | 123 (3 rows) +declare c7 cursor for select * from int8_tbl order by q1 fetch first 15 percent rows with ties; +fetch all in c7; + q1 | q2 +-----+------------------ + 123 | 456 + 123 | 4567890123456789 +(2 rows) + +fetch 1 in c7; + q1 | q2 +----+---- +(0 rows) + +fetch backward 1 in c7; + q1 | q2 +-----+------------------ + 123 | 4567890123456789 +(1 row) + +fetch backward all in c7; + q1 | q2 +-----+----- + 123 | 456 +(1 row) + +fetch backward 1 in c7; + q1 | q2 +----+---- +(0 rows) + +fetch all in c7; + q1 | q2 +-----+------------------ + 123 | 456 + 123 | 4567890123456789 +(2 rows) + rollback; -- Stress test for variable LIMIT in conjunction with bounded-heap sorting SELECT @@ -716,6 +753,80 @@ SELECT thousand 0 (2 rows) +-- +-- FETCH FIRST +-- Check the PERCENT WITH TIES clause +-- +SELECT thousand + FROM onek WHERE thousand < 5 + ORDER BY thousand FETCH FIRST 2 PERCENT ROW ONLY; + thousand +---------- + 0 +(1 row) + +SELECT thousand + FROM onek WHERE thousand < 5 + ORDER BY thousand FETCH FIRST 2 PERCENT ROWS WITH TIES; + thousand +---------- + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 +(10 rows) + +SELECT thousand + FROM onek WHERE thousand < 5 + ORDER BY thousand FETCH FIRST 21 PERCENT ROW ONLY; + thousand +---------- + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 1 +(11 rows) + +SELECT thousand + FROM onek WHERE thousand < 5 + ORDER BY thousand FETCH FIRST 21 PERCENT ROW WITH TIES; + thousand +---------- + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 +(20 rows) + -- should fail SELECT ''::text AS two, unique1, unique2, stringu1 FROM onek WHERE unique1 > 50 diff --git a/src/test/regress/sql/limit.sql b/src/test/regress/sql/limit.sql index c6e660913f..f9e3d24910 100644 --- a/src/test/regress/sql/limit.sql +++ b/src/test/regress/sql/limit.sql @@ -105,6 +105,14 @@ fetch backward all in c6; fetch backward 1 in c6; fetch all in c6; +declare c7 cursor for select * from int8_tbl order by q1 fetch first 15 percent rows with ties; +fetch all in c7; +fetch 1 in c7; +fetch backward 1 in c7; +fetch backward all in c7; +fetch backward 1 in c7; +fetch all in c7; + rollback; -- Stress test for variable LIMIT in conjunction with bounded-heap sorting @@ -197,6 +205,27 @@ SELECT thousand FROM onek WHERE thousand < 5 ORDER BY thousand FETCH FIRST 2 ROW ONLY; +-- +-- FETCH FIRST +-- Check the PERCENT WITH TIES clause +-- + +SELECT thousand + FROM onek WHERE thousand < 5 + ORDER BY thousand FETCH FIRST 2 PERCENT ROW ONLY; + +SELECT thousand + FROM onek WHERE thousand < 5 + ORDER BY thousand FETCH FIRST 2 PERCENT ROWS WITH TIES; + +SELECT thousand + FROM onek WHERE thousand < 5 + ORDER BY thousand FETCH FIRST 21 PERCENT ROW ONLY; + +SELECT thousand + FROM onek WHERE thousand < 5 + ORDER BY thousand FETCH FIRST 21 PERCENT ROW WITH TIES; + -- should fail SELECT ''::text AS two, unique1, unique2, stringu1 FROM onek WHERE unique1 > 50