mirror of
https://github.com/zebrajr/postgres.git
synced 2025-12-06 12:20:15 +01:00
Add IGNORE NULLS/RESPECT NULLS option (null treatment clause) to lead, lag, first_value, last_value and nth_value window functions. If unspecified, the default is RESPECT NULLS which includes NULL values in any result calculation. IGNORE NULLS ignores NULL values. Built-in window functions are modified to call new API WinCheckAndInitializeNullTreatment() to indicate whether they accept IGNORE NULLS/RESPECT NULLS option or not (the API can be called by user defined window functions as well). If WinGetFuncArgInPartition's allowNullTreatment argument is true and IGNORE NULLS option is given, WinGetFuncArgInPartition() or WinGetFuncArgInFrame() will return evaluated function's argument expression on specified non NULL row (if it exists) in the partition or the frame. When IGNORE NULLS option is given, window functions need to visit and evaluate same rows over and over again to look for non null rows. To mitigate the issue, 2-bit not null information array is created while executing window functions to remember whether the row has been already evaluated to NULL or NOT NULL. If already evaluated, we could skip the evaluation work, thus we could get better performance. Author: Oliver Ford <ojford@gmail.com> Co-authored-by: Tatsuo Ishii <ishii@postgresql.org> Reviewed-by: Krasiyan Andreev <krasiyan@gmail.com> Reviewed-by: Andrew Gierth <andrew@tao11.riddles.org.uk> Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us> Reviewed-by: David Fetter <david@fetter.org> Reviewed-by: Vik Fearing <vik@postgresfriends.org> Reviewed-by: "David G. Johnston" <david.g.johnston@gmail.com> Reviewed-by: Chao Li <lic@highgo.com> Discussion: https://postgr.es/m/flat/CAGMVOdsbtRwE_4+v8zjH1d9xfovDeQAGLkP_B6k69_VoFEgX-A@mail.gmail.com
727 lines
17 KiB
C
727 lines
17 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* windowfuncs.c
|
|
* Standard window functions defined in SQL spec.
|
|
*
|
|
* Portions Copyright (c) 2000-2025, PostgreSQL Global Development Group
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/utils/adt/windowfuncs.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include "nodes/parsenodes.h"
|
|
#include "nodes/supportnodes.h"
|
|
#include "utils/fmgrprotos.h"
|
|
#include "windowapi.h"
|
|
|
|
/*
|
|
* ranking process information
|
|
*/
|
|
typedef struct rank_context
|
|
{
|
|
int64 rank; /* current rank */
|
|
} rank_context;
|
|
|
|
/*
|
|
* ntile process information
|
|
*/
|
|
typedef struct
|
|
{
|
|
int32 ntile; /* current result */
|
|
int64 rows_per_bucket; /* row number of current bucket */
|
|
int64 boundary; /* how many rows should be in the bucket */
|
|
int64 remainder; /* (total rows) % (bucket num) */
|
|
} ntile_context;
|
|
|
|
static bool rank_up(WindowObject winobj);
|
|
static Datum leadlag_common(FunctionCallInfo fcinfo,
|
|
bool forward, bool withoffset, bool withdefault);
|
|
|
|
|
|
/*
|
|
* utility routine for *_rank functions.
|
|
*/
|
|
static bool
|
|
rank_up(WindowObject winobj)
|
|
{
|
|
bool up = false; /* should rank increase? */
|
|
int64 curpos = WinGetCurrentPosition(winobj);
|
|
rank_context *context;
|
|
|
|
context = (rank_context *)
|
|
WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
|
|
|
|
if (context->rank == 0)
|
|
{
|
|
/* first call: rank of first row is always 1 */
|
|
Assert(curpos == 0);
|
|
context->rank = 1;
|
|
}
|
|
else
|
|
{
|
|
Assert(curpos > 0);
|
|
/* do current and prior tuples match by ORDER BY clause? */
|
|
if (!WinRowsArePeers(winobj, curpos - 1, curpos))
|
|
up = true;
|
|
}
|
|
|
|
/* We can advance the mark, but only *after* access to prior row */
|
|
WinSetMarkPosition(winobj, curpos);
|
|
|
|
return up;
|
|
}
|
|
|
|
|
|
/*
|
|
* row_number
|
|
* just increment up from 1 until current partition finishes.
|
|
*/
|
|
Datum
|
|
window_row_number(PG_FUNCTION_ARGS)
|
|
{
|
|
WindowObject winobj = PG_WINDOW_OBJECT();
|
|
int64 curpos = WinGetCurrentPosition(winobj);
|
|
|
|
WinCheckAndInitializeNullTreatment(winobj, false, fcinfo);
|
|
WinSetMarkPosition(winobj, curpos);
|
|
PG_RETURN_INT64(curpos + 1);
|
|
}
|
|
|
|
/*
|
|
* window_row_number_support
|
|
* prosupport function for window_row_number()
|
|
*/
|
|
Datum
|
|
window_row_number_support(PG_FUNCTION_ARGS)
|
|
{
|
|
Node *rawreq = (Node *) PG_GETARG_POINTER(0);
|
|
|
|
if (IsA(rawreq, SupportRequestWFuncMonotonic))
|
|
{
|
|
SupportRequestWFuncMonotonic *req = (SupportRequestWFuncMonotonic *) rawreq;
|
|
|
|
/* row_number() is monotonically increasing */
|
|
req->monotonic = MONOTONICFUNC_INCREASING;
|
|
PG_RETURN_POINTER(req);
|
|
}
|
|
|
|
if (IsA(rawreq, SupportRequestOptimizeWindowClause))
|
|
{
|
|
SupportRequestOptimizeWindowClause *req = (SupportRequestOptimizeWindowClause *) rawreq;
|
|
|
|
/*
|
|
* The frame options can always become "ROWS BETWEEN UNBOUNDED
|
|
* PRECEDING AND CURRENT ROW". row_number() always just increments by
|
|
* 1 with each row in the partition. Using ROWS instead of RANGE
|
|
* saves effort checking peer rows during execution.
|
|
*/
|
|
req->frameOptions = (FRAMEOPTION_NONDEFAULT |
|
|
FRAMEOPTION_ROWS |
|
|
FRAMEOPTION_START_UNBOUNDED_PRECEDING |
|
|
FRAMEOPTION_END_CURRENT_ROW);
|
|
|
|
PG_RETURN_POINTER(req);
|
|
}
|
|
|
|
PG_RETURN_POINTER(NULL);
|
|
}
|
|
|
|
/*
|
|
* rank
|
|
* Rank changes when key columns change.
|
|
* The new rank number is the current row number.
|
|
*/
|
|
Datum
|
|
window_rank(PG_FUNCTION_ARGS)
|
|
{
|
|
WindowObject winobj = PG_WINDOW_OBJECT();
|
|
rank_context *context;
|
|
bool up;
|
|
|
|
WinCheckAndInitializeNullTreatment(winobj, false, fcinfo);
|
|
up = rank_up(winobj);
|
|
context = (rank_context *)
|
|
WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
|
|
if (up)
|
|
context->rank = WinGetCurrentPosition(winobj) + 1;
|
|
|
|
PG_RETURN_INT64(context->rank);
|
|
}
|
|
|
|
/*
|
|
* window_rank_support
|
|
* prosupport function for window_rank()
|
|
*/
|
|
Datum
|
|
window_rank_support(PG_FUNCTION_ARGS)
|
|
{
|
|
Node *rawreq = (Node *) PG_GETARG_POINTER(0);
|
|
|
|
if (IsA(rawreq, SupportRequestWFuncMonotonic))
|
|
{
|
|
SupportRequestWFuncMonotonic *req = (SupportRequestWFuncMonotonic *) rawreq;
|
|
|
|
/* rank() is monotonically increasing */
|
|
req->monotonic = MONOTONICFUNC_INCREASING;
|
|
PG_RETURN_POINTER(req);
|
|
}
|
|
|
|
if (IsA(rawreq, SupportRequestOptimizeWindowClause))
|
|
{
|
|
SupportRequestOptimizeWindowClause *req = (SupportRequestOptimizeWindowClause *) rawreq;
|
|
|
|
/*
|
|
* rank() is coded in such a way that it returns "(COUNT (*) OVER
|
|
* (<opt> RANGE UNBOUNDED PRECEDING) - COUNT (*) OVER (<opt> RANGE
|
|
* CURRENT ROW) + 1)" regardless of the frame options. We'll set the
|
|
* frame options to "ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
|
|
* so they agree with what window_row_number_support() optimized the
|
|
* frame options to be. Using ROWS instead of RANGE saves from doing
|
|
* peer row checks during execution.
|
|
*/
|
|
req->frameOptions = (FRAMEOPTION_NONDEFAULT |
|
|
FRAMEOPTION_ROWS |
|
|
FRAMEOPTION_START_UNBOUNDED_PRECEDING |
|
|
FRAMEOPTION_END_CURRENT_ROW);
|
|
|
|
PG_RETURN_POINTER(req);
|
|
}
|
|
|
|
PG_RETURN_POINTER(NULL);
|
|
}
|
|
|
|
/*
|
|
* dense_rank
|
|
* Rank increases by 1 when key columns change.
|
|
*/
|
|
Datum
|
|
window_dense_rank(PG_FUNCTION_ARGS)
|
|
{
|
|
WindowObject winobj = PG_WINDOW_OBJECT();
|
|
rank_context *context;
|
|
bool up;
|
|
|
|
WinCheckAndInitializeNullTreatment(winobj, false, fcinfo);
|
|
up = rank_up(winobj);
|
|
context = (rank_context *)
|
|
WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
|
|
if (up)
|
|
context->rank++;
|
|
|
|
PG_RETURN_INT64(context->rank);
|
|
}
|
|
|
|
/*
|
|
* window_dense_rank_support
|
|
* prosupport function for window_dense_rank()
|
|
*/
|
|
Datum
|
|
window_dense_rank_support(PG_FUNCTION_ARGS)
|
|
{
|
|
Node *rawreq = (Node *) PG_GETARG_POINTER(0);
|
|
|
|
if (IsA(rawreq, SupportRequestWFuncMonotonic))
|
|
{
|
|
SupportRequestWFuncMonotonic *req = (SupportRequestWFuncMonotonic *) rawreq;
|
|
|
|
/* dense_rank() is monotonically increasing */
|
|
req->monotonic = MONOTONICFUNC_INCREASING;
|
|
PG_RETURN_POINTER(req);
|
|
}
|
|
|
|
if (IsA(rawreq, SupportRequestOptimizeWindowClause))
|
|
{
|
|
SupportRequestOptimizeWindowClause *req = (SupportRequestOptimizeWindowClause *) rawreq;
|
|
|
|
/*
|
|
* dense_rank() is unaffected by the frame options. Here we set the
|
|
* frame options to match what's done in row_number's support
|
|
* function. Using ROWS instead of RANGE (the default) saves the
|
|
* executor from having to check for peer rows.
|
|
*/
|
|
req->frameOptions = (FRAMEOPTION_NONDEFAULT |
|
|
FRAMEOPTION_ROWS |
|
|
FRAMEOPTION_START_UNBOUNDED_PRECEDING |
|
|
FRAMEOPTION_END_CURRENT_ROW);
|
|
|
|
PG_RETURN_POINTER(req);
|
|
}
|
|
|
|
PG_RETURN_POINTER(NULL);
|
|
}
|
|
|
|
/*
|
|
* percent_rank
|
|
* return fraction between 0 and 1 inclusive,
|
|
* which is described as (RK - 1) / (NR - 1), where RK is the current row's
|
|
* rank and NR is the total number of rows, per spec.
|
|
*/
|
|
Datum
|
|
window_percent_rank(PG_FUNCTION_ARGS)
|
|
{
|
|
WindowObject winobj = PG_WINDOW_OBJECT();
|
|
rank_context *context;
|
|
bool up;
|
|
int64 totalrows = WinGetPartitionRowCount(winobj);
|
|
|
|
Assert(totalrows > 0);
|
|
WinCheckAndInitializeNullTreatment(winobj, false, fcinfo);
|
|
|
|
up = rank_up(winobj);
|
|
context = (rank_context *)
|
|
WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
|
|
if (up)
|
|
context->rank = WinGetCurrentPosition(winobj) + 1;
|
|
|
|
/* return zero if there's only one row, per spec */
|
|
if (totalrows <= 1)
|
|
PG_RETURN_FLOAT8(0.0);
|
|
|
|
PG_RETURN_FLOAT8((float8) (context->rank - 1) / (float8) (totalrows - 1));
|
|
}
|
|
|
|
/*
|
|
* window_percent_rank_support
|
|
* prosupport function for window_percent_rank()
|
|
*/
|
|
Datum
|
|
window_percent_rank_support(PG_FUNCTION_ARGS)
|
|
{
|
|
Node *rawreq = (Node *) PG_GETARG_POINTER(0);
|
|
|
|
if (IsA(rawreq, SupportRequestWFuncMonotonic))
|
|
{
|
|
SupportRequestWFuncMonotonic *req = (SupportRequestWFuncMonotonic *) rawreq;
|
|
|
|
/* percent_rank() is monotonically increasing */
|
|
req->monotonic = MONOTONICFUNC_INCREASING;
|
|
PG_RETURN_POINTER(req);
|
|
}
|
|
|
|
if (IsA(rawreq, SupportRequestOptimizeWindowClause))
|
|
{
|
|
SupportRequestOptimizeWindowClause *req = (SupportRequestOptimizeWindowClause *) rawreq;
|
|
|
|
/*
|
|
* percent_rank() is unaffected by the frame options. Here we set the
|
|
* frame options to match what's done in row_number's support
|
|
* function. Using ROWS instead of RANGE (the default) saves the
|
|
* executor from having to check for peer rows.
|
|
*/
|
|
req->frameOptions = (FRAMEOPTION_NONDEFAULT |
|
|
FRAMEOPTION_ROWS |
|
|
FRAMEOPTION_START_UNBOUNDED_PRECEDING |
|
|
FRAMEOPTION_END_CURRENT_ROW);
|
|
|
|
PG_RETURN_POINTER(req);
|
|
}
|
|
|
|
PG_RETURN_POINTER(NULL);
|
|
}
|
|
|
|
|
|
/*
|
|
* cume_dist
|
|
* return fraction between 0 and 1 inclusive,
|
|
* which is described as NP / NR, where NP is the number of rows preceding or
|
|
* peers to the current row, and NR is the total number of rows, per spec.
|
|
*/
|
|
Datum
|
|
window_cume_dist(PG_FUNCTION_ARGS)
|
|
{
|
|
WindowObject winobj = PG_WINDOW_OBJECT();
|
|
rank_context *context;
|
|
bool up;
|
|
int64 totalrows = WinGetPartitionRowCount(winobj);
|
|
|
|
Assert(totalrows > 0);
|
|
WinCheckAndInitializeNullTreatment(winobj, false, fcinfo);
|
|
|
|
up = rank_up(winobj);
|
|
context = (rank_context *)
|
|
WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
|
|
if (up || context->rank == 1)
|
|
{
|
|
/*
|
|
* The current row is not peer to prior row or is just the first, so
|
|
* count up the number of rows that are peer to the current.
|
|
*/
|
|
int64 row;
|
|
|
|
context->rank = WinGetCurrentPosition(winobj) + 1;
|
|
|
|
/*
|
|
* start from current + 1
|
|
*/
|
|
for (row = context->rank; row < totalrows; row++)
|
|
{
|
|
if (!WinRowsArePeers(winobj, row - 1, row))
|
|
break;
|
|
context->rank++;
|
|
}
|
|
}
|
|
|
|
PG_RETURN_FLOAT8((float8) context->rank / (float8) totalrows);
|
|
}
|
|
|
|
/*
|
|
* window_cume_dist_support
|
|
* prosupport function for window_cume_dist()
|
|
*/
|
|
Datum
|
|
window_cume_dist_support(PG_FUNCTION_ARGS)
|
|
{
|
|
Node *rawreq = (Node *) PG_GETARG_POINTER(0);
|
|
|
|
if (IsA(rawreq, SupportRequestWFuncMonotonic))
|
|
{
|
|
SupportRequestWFuncMonotonic *req = (SupportRequestWFuncMonotonic *) rawreq;
|
|
|
|
/* cume_dist() is monotonically increasing */
|
|
req->monotonic = MONOTONICFUNC_INCREASING;
|
|
PG_RETURN_POINTER(req);
|
|
}
|
|
|
|
if (IsA(rawreq, SupportRequestOptimizeWindowClause))
|
|
{
|
|
SupportRequestOptimizeWindowClause *req = (SupportRequestOptimizeWindowClause *) rawreq;
|
|
|
|
/*
|
|
* cume_dist() is unaffected by the frame options. Here we set the
|
|
* frame options to match what's done in row_number's support
|
|
* function. Using ROWS instead of RANGE (the default) saves the
|
|
* executor from having to check for peer rows.
|
|
*/
|
|
req->frameOptions = (FRAMEOPTION_NONDEFAULT |
|
|
FRAMEOPTION_ROWS |
|
|
FRAMEOPTION_START_UNBOUNDED_PRECEDING |
|
|
FRAMEOPTION_END_CURRENT_ROW);
|
|
|
|
PG_RETURN_POINTER(req);
|
|
}
|
|
|
|
PG_RETURN_POINTER(NULL);
|
|
}
|
|
|
|
/*
|
|
* ntile
|
|
* compute an exact numeric value with scale 0 (zero),
|
|
* ranging from 1 (one) to n, per spec.
|
|
*/
|
|
Datum
|
|
window_ntile(PG_FUNCTION_ARGS)
|
|
{
|
|
WindowObject winobj = PG_WINDOW_OBJECT();
|
|
ntile_context *context;
|
|
|
|
WinCheckAndInitializeNullTreatment(winobj, false, fcinfo);
|
|
context = (ntile_context *)
|
|
WinGetPartitionLocalMemory(winobj, sizeof(ntile_context));
|
|
|
|
if (context->ntile == 0)
|
|
{
|
|
/* first call */
|
|
int64 total;
|
|
int32 nbuckets;
|
|
bool isnull;
|
|
|
|
total = WinGetPartitionRowCount(winobj);
|
|
nbuckets = DatumGetInt32(WinGetFuncArgCurrent(winobj, 0, &isnull));
|
|
|
|
/*
|
|
* per spec: If NT is the null value, then the result is the null
|
|
* value.
|
|
*/
|
|
if (isnull)
|
|
PG_RETURN_NULL();
|
|
|
|
/*
|
|
* per spec: If NT is less than or equal to 0 (zero), then an
|
|
* exception condition is raised.
|
|
*/
|
|
if (nbuckets <= 0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_NTILE),
|
|
errmsg("argument of ntile must be greater than zero")));
|
|
|
|
context->ntile = 1;
|
|
context->rows_per_bucket = 0;
|
|
context->boundary = total / nbuckets;
|
|
if (context->boundary <= 0)
|
|
context->boundary = 1;
|
|
else
|
|
{
|
|
/*
|
|
* If the total number is not divisible, add 1 row to leading
|
|
* buckets.
|
|
*/
|
|
context->remainder = total % nbuckets;
|
|
if (context->remainder != 0)
|
|
context->boundary++;
|
|
}
|
|
}
|
|
|
|
context->rows_per_bucket++;
|
|
if (context->boundary < context->rows_per_bucket)
|
|
{
|
|
/* ntile up */
|
|
if (context->remainder != 0 && context->ntile == context->remainder)
|
|
{
|
|
context->remainder = 0;
|
|
context->boundary -= 1;
|
|
}
|
|
context->ntile += 1;
|
|
context->rows_per_bucket = 1;
|
|
}
|
|
|
|
PG_RETURN_INT32(context->ntile);
|
|
}
|
|
|
|
/*
|
|
* window_ntile_support
|
|
* prosupport function for window_ntile()
|
|
*/
|
|
Datum
|
|
window_ntile_support(PG_FUNCTION_ARGS)
|
|
{
|
|
Node *rawreq = (Node *) PG_GETARG_POINTER(0);
|
|
|
|
if (IsA(rawreq, SupportRequestWFuncMonotonic))
|
|
{
|
|
SupportRequestWFuncMonotonic *req = (SupportRequestWFuncMonotonic *) rawreq;
|
|
|
|
/*
|
|
* ntile() is monotonically increasing as the number of buckets cannot
|
|
* change after the first call
|
|
*/
|
|
req->monotonic = MONOTONICFUNC_INCREASING;
|
|
PG_RETURN_POINTER(req);
|
|
}
|
|
|
|
if (IsA(rawreq, SupportRequestOptimizeWindowClause))
|
|
{
|
|
SupportRequestOptimizeWindowClause *req = (SupportRequestOptimizeWindowClause *) rawreq;
|
|
|
|
/*
|
|
* ntile() is unaffected by the frame options. Here we set the frame
|
|
* options to match what's done in row_number's support function.
|
|
* Using ROWS instead of RANGE (the default) saves the executor from
|
|
* having to check for peer rows.
|
|
*/
|
|
req->frameOptions = (FRAMEOPTION_NONDEFAULT |
|
|
FRAMEOPTION_ROWS |
|
|
FRAMEOPTION_START_UNBOUNDED_PRECEDING |
|
|
FRAMEOPTION_END_CURRENT_ROW);
|
|
|
|
PG_RETURN_POINTER(req);
|
|
}
|
|
|
|
PG_RETURN_POINTER(NULL);
|
|
}
|
|
|
|
/*
|
|
* leadlag_common
|
|
* common operation of lead() and lag()
|
|
* For lead() forward is true, whereas for lag() it is false.
|
|
* withoffset indicates we have an offset second argument.
|
|
* withdefault indicates we have a default third argument.
|
|
*/
|
|
static Datum
|
|
leadlag_common(FunctionCallInfo fcinfo,
|
|
bool forward, bool withoffset, bool withdefault)
|
|
{
|
|
WindowObject winobj = PG_WINDOW_OBJECT();
|
|
int32 offset;
|
|
bool const_offset;
|
|
Datum result;
|
|
bool isnull;
|
|
bool isout;
|
|
|
|
WinCheckAndInitializeNullTreatment(winobj, true, fcinfo);
|
|
if (withoffset)
|
|
{
|
|
offset = DatumGetInt32(WinGetFuncArgCurrent(winobj, 1, &isnull));
|
|
if (isnull)
|
|
PG_RETURN_NULL();
|
|
const_offset = get_fn_expr_arg_stable(fcinfo->flinfo, 1);
|
|
}
|
|
else
|
|
{
|
|
offset = 1;
|
|
const_offset = true;
|
|
}
|
|
|
|
result = WinGetFuncArgInPartition(winobj, 0,
|
|
(forward ? offset : -offset),
|
|
WINDOW_SEEK_CURRENT,
|
|
const_offset,
|
|
&isnull, &isout);
|
|
|
|
if (isout)
|
|
{
|
|
/*
|
|
* target row is out of the partition; supply default value if
|
|
* provided. otherwise it'll stay NULL
|
|
*/
|
|
if (withdefault)
|
|
result = WinGetFuncArgCurrent(winobj, 2, &isnull);
|
|
}
|
|
|
|
if (isnull)
|
|
PG_RETURN_NULL();
|
|
|
|
PG_RETURN_DATUM(result);
|
|
}
|
|
|
|
/*
|
|
* lag
|
|
* returns the value of VE evaluated on a row that is 1
|
|
* row before the current row within a partition,
|
|
* per spec.
|
|
*/
|
|
Datum
|
|
window_lag(PG_FUNCTION_ARGS)
|
|
{
|
|
return leadlag_common(fcinfo, false, false, false);
|
|
}
|
|
|
|
/*
|
|
* lag_with_offset
|
|
* returns the value of VE evaluated on a row that is OFFSET
|
|
* rows before the current row within a partition,
|
|
* per spec.
|
|
*/
|
|
Datum
|
|
window_lag_with_offset(PG_FUNCTION_ARGS)
|
|
{
|
|
return leadlag_common(fcinfo, false, true, false);
|
|
}
|
|
|
|
/*
|
|
* lag_with_offset_and_default
|
|
* same as lag_with_offset but accepts default value
|
|
* as its third argument.
|
|
*/
|
|
Datum
|
|
window_lag_with_offset_and_default(PG_FUNCTION_ARGS)
|
|
{
|
|
return leadlag_common(fcinfo, false, true, true);
|
|
}
|
|
|
|
/*
|
|
* lead
|
|
* returns the value of VE evaluated on a row that is 1
|
|
* row after the current row within a partition,
|
|
* per spec.
|
|
*/
|
|
Datum
|
|
window_lead(PG_FUNCTION_ARGS)
|
|
{
|
|
return leadlag_common(fcinfo, true, false, false);
|
|
}
|
|
|
|
/*
|
|
* lead_with_offset
|
|
* returns the value of VE evaluated on a row that is OFFSET
|
|
* number of rows after the current row within a partition,
|
|
* per spec.
|
|
*/
|
|
Datum
|
|
window_lead_with_offset(PG_FUNCTION_ARGS)
|
|
{
|
|
return leadlag_common(fcinfo, true, true, false);
|
|
}
|
|
|
|
/*
|
|
* lead_with_offset_and_default
|
|
* same as lead_with_offset but accepts default value
|
|
* as its third argument.
|
|
*/
|
|
Datum
|
|
window_lead_with_offset_and_default(PG_FUNCTION_ARGS)
|
|
{
|
|
return leadlag_common(fcinfo, true, true, true);
|
|
}
|
|
|
|
/*
|
|
* first_value
|
|
* return the value of VE evaluated on the first row of the
|
|
* window frame, per spec.
|
|
*/
|
|
Datum
|
|
window_first_value(PG_FUNCTION_ARGS)
|
|
{
|
|
WindowObject winobj = PG_WINDOW_OBJECT();
|
|
Datum result;
|
|
bool isnull;
|
|
|
|
WinCheckAndInitializeNullTreatment(winobj, true, fcinfo);
|
|
result = WinGetFuncArgInFrame(winobj, 0,
|
|
0, WINDOW_SEEK_HEAD, true,
|
|
&isnull, NULL);
|
|
if (isnull)
|
|
PG_RETURN_NULL();
|
|
|
|
PG_RETURN_DATUM(result);
|
|
}
|
|
|
|
/*
|
|
* last_value
|
|
* return the value of VE evaluated on the last row of the
|
|
* window frame, per spec.
|
|
*/
|
|
Datum
|
|
window_last_value(PG_FUNCTION_ARGS)
|
|
{
|
|
WindowObject winobj = PG_WINDOW_OBJECT();
|
|
Datum result;
|
|
bool isnull;
|
|
|
|
WinCheckAndInitializeNullTreatment(winobj, true, fcinfo);
|
|
result = WinGetFuncArgInFrame(winobj, 0,
|
|
0, WINDOW_SEEK_TAIL, true,
|
|
&isnull, NULL);
|
|
if (isnull)
|
|
PG_RETURN_NULL();
|
|
|
|
PG_RETURN_DATUM(result);
|
|
}
|
|
|
|
/*
|
|
* nth_value
|
|
* return the value of VE evaluated on the n-th row from the first
|
|
* row of the window frame, per spec.
|
|
*/
|
|
Datum
|
|
window_nth_value(PG_FUNCTION_ARGS)
|
|
{
|
|
WindowObject winobj = PG_WINDOW_OBJECT();
|
|
bool const_offset;
|
|
Datum result;
|
|
bool isnull;
|
|
int32 nth;
|
|
|
|
WinCheckAndInitializeNullTreatment(winobj, true, fcinfo);
|
|
nth = DatumGetInt32(WinGetFuncArgCurrent(winobj, 1, &isnull));
|
|
if (isnull)
|
|
PG_RETURN_NULL();
|
|
const_offset = get_fn_expr_arg_stable(fcinfo->flinfo, 1);
|
|
|
|
if (nth <= 0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_NTH_VALUE),
|
|
errmsg("argument of nth_value must be greater than zero")));
|
|
|
|
result = WinGetFuncArgInFrame(winobj, 0,
|
|
nth - 1, WINDOW_SEEK_HEAD, const_offset,
|
|
&isnull, NULL);
|
|
if (isnull)
|
|
PG_RETURN_NULL();
|
|
|
|
PG_RETURN_DATUM(result);
|
|
}
|