window-func-fixes-v2.patch
text/x-diff
Filename: window-func-fixes-v2.patch
Type: text/x-diff
Part: 0
Patch
Format: unified
Series: patch v2
| File | + | − |
|---|---|---|
| src/backend/executor/nodeWindowAgg.c | 0 | 0 |
| src/backend/optimizer/plan/createplan.c | 0 | 0 |
| src/backend/optimizer/plan/planner.c | 0 | 0 |
| src/backend/parser/parse_clause.c | 0 | 0 |
| src/test/regress/expected/window.out | 0 | 0 |
| src/test/regress/sql/window.sql | 0 | 0 |
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 968d5d3..0db193b 100644
*** a/src/backend/executor/nodeWindowAgg.c
--- b/src/backend/executor/nodeWindowAgg.c
*************** begin_partition(WindowAggState *winstate
*** 1079,1084 ****
--- 1079,1085 ----
{
WindowAgg *node = (WindowAgg *) winstate->ss.ps.plan;
PlanState *outerPlan = outerPlanState(winstate);
+ int frameOptions = winstate->frameOptions;
int numfuncs = winstate->numfuncs;
int i;
*************** begin_partition(WindowAggState *winstate
*** 1143,1150 ****
* If the frame head is potentially movable, or we have an EXCLUSION
* clause, we might need to restart aggregation ...
*/
! if (!(winstate->frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING) ||
! (winstate->frameOptions & FRAMEOPTION_EXCLUSION))
{
/* ... so create a mark pointer to track the frame head */
agg_winobj->markptr = tuplestore_alloc_read_pointer(winstate->buffer, 0);
--- 1144,1151 ----
* If the frame head is potentially movable, or we have an EXCLUSION
* clause, we might need to restart aggregation ...
*/
! if (!(frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING) ||
! (frameOptions & FRAMEOPTION_EXCLUSION))
{
/* ... so create a mark pointer to track the frame head */
agg_winobj->markptr = tuplestore_alloc_read_pointer(winstate->buffer, 0);
*************** begin_partition(WindowAggState *winstate
*** 1182,1202 ****
/*
* If we are in RANGE or GROUPS mode, then determining frame boundaries
! * requires physical access to the frame endpoint rows, except in
* degenerate cases. We create read pointers to point to those rows, to
* simplify access and ensure that the tuplestore doesn't discard the
! * endpoint rows prematurely. (Must match logic in update_frameheadpos
! * and update_frametailpos.)
*/
winstate->framehead_ptr = winstate->frametail_ptr = -1; /* if not used */
! if ((winstate->frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS)) &&
! node->ordNumCols != 0)
{
! if (!(winstate->frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING))
winstate->framehead_ptr =
tuplestore_alloc_read_pointer(winstate->buffer, 0);
! if (!(winstate->frameOptions & FRAMEOPTION_END_UNBOUNDED_FOLLOWING))
winstate->frametail_ptr =
tuplestore_alloc_read_pointer(winstate->buffer, 0);
}
--- 1183,1206 ----
/*
* If we are in RANGE or GROUPS mode, then determining frame boundaries
! * requires physical access to the frame endpoint rows, except in certain
* degenerate cases. We create read pointers to point to those rows, to
* simplify access and ensure that the tuplestore doesn't discard the
! * endpoint rows prematurely. (Must create pointers in exactly the same
! * cases that update_frameheadpos and update_frametailpos need them.)
*/
winstate->framehead_ptr = winstate->frametail_ptr = -1; /* if not used */
! if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
{
! if (((frameOptions & FRAMEOPTION_START_CURRENT_ROW) &&
! node->ordNumCols != 0) ||
! (frameOptions & FRAMEOPTION_START_OFFSET))
winstate->framehead_ptr =
tuplestore_alloc_read_pointer(winstate->buffer, 0);
! if (((frameOptions & FRAMEOPTION_END_CURRENT_ROW) &&
! node->ordNumCols != 0) ||
! (frameOptions & FRAMEOPTION_END_OFFSET))
winstate->frametail_ptr =
tuplestore_alloc_read_pointer(winstate->buffer, 0);
}
*************** begin_partition(WindowAggState *winstate
*** 1210,1217 ****
*/
winstate->grouptail_ptr = -1;
! if ((winstate->frameOptions & (FRAMEOPTION_EXCLUDE_GROUP |
! FRAMEOPTION_EXCLUDE_TIES)) &&
node->ordNumCols != 0)
{
winstate->grouptail_ptr =
--- 1214,1221 ----
*/
winstate->grouptail_ptr = -1;
! if ((frameOptions & (FRAMEOPTION_EXCLUDE_GROUP |
! FRAMEOPTION_EXCLUDE_TIES)) &&
node->ordNumCols != 0)
{
winstate->grouptail_ptr =
*************** update_frameheadpos(WindowAggState *wins
*** 1563,1568 ****
--- 1567,1575 ----
bool sub,
less;
+ /* We must have an ordering column */
+ Assert(node->ordNumCols == 1);
+
/* Precompute flags for in_range checks */
if (frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING)
sub = true; /* subtract startOffset from current row */
*************** update_frameheadpos(WindowAggState *wins
*** 1643,1648 ****
--- 1650,1660 ----
* framehead_slot, and advance as necessary. Note that if we
* reach end of partition, we will leave frameheadpos = end+1 and
* framehead_slot empty.
+ *
+ * Note: it's possible to have ordNumCols == 0, in which case
+ * there is only one peer group and we could in principle do this
+ * without the laborious per-row tests. Doesn't seem worth
+ * expending extra code on such a silly case, though.
*/
int64 offset = DatumGetInt64(winstate->startOffsetValue);
int64 minheadgroup;
*************** update_frametailpos(WindowAggState *wins
*** 1814,1819 ****
--- 1826,1834 ----
bool sub,
less;
+ /* We must have an ordering column */
+ Assert(node->ordNumCols == 1);
+
/* Precompute flags for in_range checks */
if (frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING)
sub = true; /* subtract endOffset from current row */
*************** update_frametailpos(WindowAggState *wins
*** 1894,1899 ****
--- 1909,1919 ----
* of the last-known frame tail row in frametail_slot, and advance
* as necessary. Note that if we reach end of partition, we will
* leave frametailpos = end+1 and frametail_slot empty.
+ *
+ * Note: it's possible to have ordNumCols == 0, in which case
+ * there is only one peer group and we could in principle do this
+ * without the laborious per-row tests. Doesn't seem worth
+ * expending extra code on such a silly case, though.
*/
int64 offset = DatumGetInt64(winstate->endOffsetValue);
int64 maxtailgroup;
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 2318,2333 ****
winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc);
/*
! * create frame head and tail slots only if needed (must match logic in
! * update_frameheadpos and update_frametailpos)
*/
winstate->framehead_slot = winstate->frametail_slot = NULL;
if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
{
! if (!(frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING))
winstate->framehead_slot = ExecInitExtraTupleSlot(estate, scanDesc);
! if (!(frameOptions & FRAMEOPTION_END_UNBOUNDED_FOLLOWING))
winstate->frametail_slot = ExecInitExtraTupleSlot(estate, scanDesc);
}
--- 2338,2358 ----
winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc);
/*
! * create frame head and tail slots only if needed (must create slots in
! * exactly the same cases that update_frameheadpos and update_frametailpos
! * need them)
*/
winstate->framehead_slot = winstate->frametail_slot = NULL;
if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
{
! if (((frameOptions & FRAMEOPTION_START_CURRENT_ROW) &&
! node->ordNumCols != 0) ||
! (frameOptions & FRAMEOPTION_START_OFFSET))
winstate->framehead_slot = ExecInitExtraTupleSlot(estate, scanDesc);
! if (((frameOptions & FRAMEOPTION_END_CURRENT_ROW) &&
! node->ordNumCols != 0) ||
! (frameOptions & FRAMEOPTION_END_OFFSET))
winstate->frametail_slot = ExecInitExtraTupleSlot(estate, scanDesc);
}
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index cf82b70..4186e20 100644
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
*************** static WindowAgg *create_windowagg_plan(
*** 107,121 ****
static SetOp *create_setop_plan(PlannerInfo *root, SetOpPath *best_path,
int flags);
static RecursiveUnion *create_recursiveunion_plan(PlannerInfo *root, RecursiveUnionPath *best_path);
- static void get_column_info_for_window(PlannerInfo *root, WindowClause *wc,
- List *tlist,
- int numSortCols, AttrNumber *sortColIdx,
- int *partNumCols,
- AttrNumber **partColIdx,
- Oid **partOperators,
- int *ordNumCols,
- AttrNumber **ordColIdx,
- Oid **ordOperators);
static LockRows *create_lockrows_plan(PlannerInfo *root, LockRowsPath *best_path,
int flags);
static ModifyTable *create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path);
--- 107,112 ----
*************** create_windowagg_plan(PlannerInfo *root,
*** 2160,2178 ****
{
WindowAgg *plan;
WindowClause *wc = best_path->winclause;
Plan *subplan;
List *tlist;
- int numsortkeys;
- AttrNumber *sortColIdx;
- Oid *sortOperators;
- Oid *collations;
- bool *nullsFirst;
int partNumCols;
AttrNumber *partColIdx;
Oid *partOperators;
int ordNumCols;
AttrNumber *ordColIdx;
Oid *ordOperators;
/*
* WindowAgg can project, so no need to be terribly picky about child
--- 2151,2167 ----
{
WindowAgg *plan;
WindowClause *wc = best_path->winclause;
+ int numPart = list_length(wc->partitionClause);
+ int numOrder = list_length(wc->orderClause);
Plan *subplan;
List *tlist;
int partNumCols;
AttrNumber *partColIdx;
Oid *partOperators;
int ordNumCols;
AttrNumber *ordColIdx;
Oid *ordOperators;
+ ListCell *lc;
/*
* WindowAgg can project, so no need to be terribly picky about child
*************** create_windowagg_plan(PlannerInfo *root,
*** 2183,2214 ****
tlist = build_path_tlist(root, &best_path->path);
/*
! * We shouldn't need to actually sort, but it's convenient to use
! * prepare_sort_from_pathkeys to identify the input's sort columns.
*/
! subplan = prepare_sort_from_pathkeys(subplan,
! best_path->winpathkeys,
! NULL,
! NULL,
! false,
! &numsortkeys,
! &sortColIdx,
! &sortOperators,
! &collations,
! &nullsFirst);
! /* Now deconstruct that into partition and ordering portions */
! get_column_info_for_window(root,
! wc,
! subplan->targetlist,
! numsortkeys,
! sortColIdx,
! &partNumCols,
! &partColIdx,
! &partOperators,
! &ordNumCols,
! &ordColIdx,
! &ordOperators);
/* And finally we can make the WindowAgg node */
plan = make_windowagg(tlist,
--- 2172,2214 ----
tlist = build_path_tlist(root, &best_path->path);
/*
! * Convert SortGroupClause lists into arrays of attr indexes and equality
! * operators, as wanted by executor. (Note: in principle, it's possible
! * to drop some of the sort columns, if they were proved redundant by
! * pathkey logic. However, it doesn't seem worth going out of our way to
! * optimize such cases. In any case, we must *not* remove the ordering
! * column for RANGE OFFSET cases, as the executor needs that for in_range
! * tests even if it's known to be equal to some partitioning column.)
*/
! partColIdx = (AttrNumber *) palloc(sizeof(AttrNumber) * numPart);
! partOperators = (Oid *) palloc(sizeof(Oid) * numPart);
! partNumCols = 0;
! foreach(lc, wc->partitionClause)
! {
! SortGroupClause *sgc = (SortGroupClause *) lfirst(lc);
! TargetEntry *tle = get_sortgroupclause_tle(sgc, subplan->targetlist);
!
! Assert(OidIsValid(sgc->eqop));
! partColIdx[partNumCols] = tle->resno;
! partOperators[partNumCols] = sgc->eqop;
! partNumCols++;
! }
!
! ordColIdx = (AttrNumber *) palloc(sizeof(AttrNumber) * numOrder);
! ordOperators = (Oid *) palloc(sizeof(Oid) * numOrder);
!
! ordNumCols = 0;
! foreach(lc, wc->orderClause)
! {
! SortGroupClause *sgc = (SortGroupClause *) lfirst(lc);
! TargetEntry *tle = get_sortgroupclause_tle(sgc, subplan->targetlist);
!
! Assert(OidIsValid(sgc->eqop));
! ordColIdx[ordNumCols] = tle->resno;
! ordOperators[ordNumCols] = sgc->eqop;
! ordNumCols++;
! }
/* And finally we can make the WindowAgg node */
plan = make_windowagg(tlist,
*************** create_windowagg_plan(PlannerInfo *root,
*** 2235,2346 ****
}
/*
- * get_column_info_for_window
- * Get the partitioning/ordering column numbers and equality operators
- * for a WindowAgg node.
- *
- * This depends on the behavior of planner.c's make_pathkeys_for_window!
- *
- * We are given the target WindowClause and an array of the input column
- * numbers associated with the resulting pathkeys. In the easy case, there
- * are the same number of pathkey columns as partitioning + ordering columns
- * and we just have to copy some data around. However, it's possible that
- * some of the original partitioning + ordering columns were eliminated as
- * redundant during the transformation to pathkeys. (This can happen even
- * though the parser gets rid of obvious duplicates. A typical scenario is a
- * window specification "PARTITION BY x ORDER BY y" coupled with a clause
- * "WHERE x = y" that causes the two sort columns to be recognized as
- * redundant.) In that unusual case, we have to work a lot harder to
- * determine which keys are significant.
- *
- * The method used here is a bit brute-force: add the sort columns to a list
- * one at a time and note when the resulting pathkey list gets longer. But
- * it's a sufficiently uncommon case that a faster way doesn't seem worth
- * the amount of code refactoring that'd be needed.
- */
- static void
- get_column_info_for_window(PlannerInfo *root, WindowClause *wc, List *tlist,
- int numSortCols, AttrNumber *sortColIdx,
- int *partNumCols,
- AttrNumber **partColIdx,
- Oid **partOperators,
- int *ordNumCols,
- AttrNumber **ordColIdx,
- Oid **ordOperators)
- {
- int numPart = list_length(wc->partitionClause);
- int numOrder = list_length(wc->orderClause);
-
- if (numSortCols == numPart + numOrder)
- {
- /* easy case */
- *partNumCols = numPart;
- *partColIdx = sortColIdx;
- *partOperators = extract_grouping_ops(wc->partitionClause);
- *ordNumCols = numOrder;
- *ordColIdx = sortColIdx + numPart;
- *ordOperators = extract_grouping_ops(wc->orderClause);
- }
- else
- {
- List *sortclauses;
- List *pathkeys;
- int scidx;
- ListCell *lc;
-
- /* first, allocate what's certainly enough space for the arrays */
- *partNumCols = 0;
- *partColIdx = (AttrNumber *) palloc(numPart * sizeof(AttrNumber));
- *partOperators = (Oid *) palloc(numPart * sizeof(Oid));
- *ordNumCols = 0;
- *ordColIdx = (AttrNumber *) palloc(numOrder * sizeof(AttrNumber));
- *ordOperators = (Oid *) palloc(numOrder * sizeof(Oid));
- sortclauses = NIL;
- pathkeys = NIL;
- scidx = 0;
- foreach(lc, wc->partitionClause)
- {
- SortGroupClause *sgc = (SortGroupClause *) lfirst(lc);
- List *new_pathkeys;
-
- sortclauses = lappend(sortclauses, sgc);
- new_pathkeys = make_pathkeys_for_sortclauses(root,
- sortclauses,
- tlist);
- if (list_length(new_pathkeys) > list_length(pathkeys))
- {
- /* this sort clause is actually significant */
- (*partColIdx)[*partNumCols] = sortColIdx[scidx++];
- (*partOperators)[*partNumCols] = sgc->eqop;
- (*partNumCols)++;
- pathkeys = new_pathkeys;
- }
- }
- foreach(lc, wc->orderClause)
- {
- SortGroupClause *sgc = (SortGroupClause *) lfirst(lc);
- List *new_pathkeys;
-
- sortclauses = lappend(sortclauses, sgc);
- new_pathkeys = make_pathkeys_for_sortclauses(root,
- sortclauses,
- tlist);
- if (list_length(new_pathkeys) > list_length(pathkeys))
- {
- /* this sort clause is actually significant */
- (*ordColIdx)[*ordNumCols] = sortColIdx[scidx++];
- (*ordOperators)[*ordNumCols] = sgc->eqop;
- (*ordNumCols)++;
- pathkeys = new_pathkeys;
- }
- }
- /* complain if we didn't eat exactly the right number of sort cols */
- if (scidx != numSortCols)
- elog(ERROR, "failed to deconstruct sort operators into partitioning/ordering operators");
- }
- }
-
- /*
* create_setop_plan
*
* Create a SetOp plan for 'best_path' and (recursively) plans
--- 2235,2240 ----
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index fd45c97..2c31b57 100644
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
*************** make_window_input_target(PlannerInfo *ro
*** 5466,5473 ****
* The required ordering is first the PARTITION keys, then the ORDER keys.
* In the future we might try to implement windowing using hashing, in which
* case the ordering could be relaxed, but for now we always sort.
- *
- * Caution: if you change this, see createplan.c's get_column_info_for_window!
*/
static List *
make_pathkeys_for_window(PlannerInfo *root, WindowClause *wc,
--- 5466,5471 ----
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index e147880..cfd4b91 100644
*** a/src/backend/parser/parse_clause.c
--- b/src/backend/parser/parse_clause.c
*************** transformWindowDefinitions(ParseState *p
*** 2795,2800 ****
--- 2795,2810 ----
wc->inRangeNullsFirst = sortcl->nulls_first;
}
+ /* Per spec, GROUPS mode requires an ORDER BY clause */
+ if (wc->frameOptions & FRAMEOPTION_GROUPS)
+ {
+ if (wc->orderClause == NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WINDOWING_ERROR),
+ errmsg("GROUPS mode requires an ORDER BY clause"),
+ parser_errposition(pstate, windef->location)));
+ }
+
/* Process frame offset expressions */
wc->startOffset = transformFrameOffset(pstate, wc->frameOptions,
rangeopfamily, rangeopcintype,
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 85d81e7..562006a 100644
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
*************** SELECT count(*) OVER (PARTITION BY four)
*** 2834,2839 ****
--- 2834,2934 ----
-------
(0 rows)
+ -- check some degenerate cases
+ create temp table t1 (f1 int, f2 int8);
+ insert into t1 values (1,1),(1,2),(2,2);
+ select f1, sum(f1) over (partition by f1
+ range between 1 preceding and 1 following)
+ from t1 where f1 = f2; -- error, must have order by
+ ERROR: RANGE with offset PRECEDING/FOLLOWING requires exactly one ORDER BY column
+ LINE 1: select f1, sum(f1) over (partition by f1
+ ^
+ explain (costs off)
+ select f1, sum(f1) over (partition by f1 order by f2
+ range between 1 preceding and 1 following)
+ from t1 where f1 = f2;
+ QUERY PLAN
+ ---------------------------------
+ WindowAgg
+ -> Sort
+ Sort Key: f1
+ -> Seq Scan on t1
+ Filter: (f1 = f2)
+ (5 rows)
+
+ select f1, sum(f1) over (partition by f1 order by f2
+ range between 1 preceding and 1 following)
+ from t1 where f1 = f2;
+ f1 | sum
+ ----+-----
+ 1 | 1
+ 2 | 2
+ (2 rows)
+
+ select f1, sum(f1) over (partition by f1, f1 order by f2
+ range between 2 preceding and 1 preceding)
+ from t1 where f1 = f2;
+ f1 | sum
+ ----+-----
+ 1 |
+ 2 |
+ (2 rows)
+
+ select f1, sum(f1) over (partition by f1, f2 order by f2
+ range between 1 following and 2 following)
+ from t1 where f1 = f2;
+ f1 | sum
+ ----+-----
+ 1 |
+ 2 |
+ (2 rows)
+
+ select f1, sum(f1) over (partition by f1
+ groups between 1 preceding and 1 following)
+ from t1 where f1 = f2; -- error, must have order by
+ ERROR: GROUPS mode requires an ORDER BY clause
+ LINE 1: select f1, sum(f1) over (partition by f1
+ ^
+ explain (costs off)
+ select f1, sum(f1) over (partition by f1 order by f2
+ groups between 1 preceding and 1 following)
+ from t1 where f1 = f2;
+ QUERY PLAN
+ ---------------------------------
+ WindowAgg
+ -> Sort
+ Sort Key: f1
+ -> Seq Scan on t1
+ Filter: (f1 = f2)
+ (5 rows)
+
+ select f1, sum(f1) over (partition by f1 order by f2
+ groups between 1 preceding and 1 following)
+ from t1 where f1 = f2;
+ f1 | sum
+ ----+-----
+ 1 | 1
+ 2 | 2
+ (2 rows)
+
+ select f1, sum(f1) over (partition by f1, f1 order by f2
+ groups between 2 preceding and 1 preceding)
+ from t1 where f1 = f2;
+ f1 | sum
+ ----+-----
+ 1 |
+ 2 |
+ (2 rows)
+
+ select f1, sum(f1) over (partition by f1, f2 order by f2
+ groups between 1 following and 2 following)
+ from t1 where f1 = f2;
+ f1 | sum
+ ----+-----
+ 1 |
+ 2 |
+ (2 rows)
+
-- ordering by a non-integer constant is allowed
SELECT rank() OVER (ORDER BY length('abc'));
rank
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 051b50b..e2943a3 100644
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
*************** WINDOW w AS (ORDER BY x groups between 1
*** 795,800 ****
--- 795,838 ----
-- with UNION
SELECT count(*) OVER (PARTITION BY four) FROM (SELECT * FROM tenk1 UNION ALL SELECT * FROM tenk2)s LIMIT 0;
+ -- check some degenerate cases
+ create temp table t1 (f1 int, f2 int8);
+ insert into t1 values (1,1),(1,2),(2,2);
+
+ select f1, sum(f1) over (partition by f1
+ range between 1 preceding and 1 following)
+ from t1 where f1 = f2; -- error, must have order by
+ explain (costs off)
+ select f1, sum(f1) over (partition by f1 order by f2
+ range between 1 preceding and 1 following)
+ from t1 where f1 = f2;
+ select f1, sum(f1) over (partition by f1 order by f2
+ range between 1 preceding and 1 following)
+ from t1 where f1 = f2;
+ select f1, sum(f1) over (partition by f1, f1 order by f2
+ range between 2 preceding and 1 preceding)
+ from t1 where f1 = f2;
+ select f1, sum(f1) over (partition by f1, f2 order by f2
+ range between 1 following and 2 following)
+ from t1 where f1 = f2;
+
+ select f1, sum(f1) over (partition by f1
+ groups between 1 preceding and 1 following)
+ from t1 where f1 = f2; -- error, must have order by
+ explain (costs off)
+ select f1, sum(f1) over (partition by f1 order by f2
+ groups between 1 preceding and 1 following)
+ from t1 where f1 = f2;
+ select f1, sum(f1) over (partition by f1 order by f2
+ groups between 1 preceding and 1 following)
+ from t1 where f1 = f2;
+ select f1, sum(f1) over (partition by f1, f1 order by f2
+ groups between 2 preceding and 1 preceding)
+ from t1 where f1 = f2;
+ select f1, sum(f1) over (partition by f1, f2 order by f2
+ groups between 1 following and 2 following)
+ from t1 where f1 = f2;
+
-- ordering by a non-integer constant is allowed
SELECT rank() OVER (ORDER BY length('abc'));