mirror of
https://github.com/postgres/postgres.git
synced 2025-11-29 23:43:17 +03:00
Support window functions a la SQL:2008.
Hitoshi Harada, with some kibitzing from Heikki and Tom.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# Makefile for utils/adt
|
||||
#
|
||||
# $PostgreSQL: pgsql/src/backend/utils/adt/Makefile,v 1.70 2008/11/03 20:17:20 adunstan Exp $
|
||||
# $PostgreSQL: pgsql/src/backend/utils/adt/Makefile,v 1.71 2008/12/28 18:53:59 tgl Exp $
|
||||
#
|
||||
|
||||
subdir = src/backend/utils/adt
|
||||
@@ -29,7 +29,7 @@ OBJS = acl.o arrayfuncs.o array_userfuncs.o arrayutils.o bool.o \
|
||||
tsginidx.o tsgistidx.o tsquery.o tsquery_cleanup.o tsquery_gist.o \
|
||||
tsquery_op.o tsquery_rewrite.o tsquery_util.o tsrank.o \
|
||||
tsvector.o tsvector_op.o tsvector_parser.o \
|
||||
txid.o uuid.o xml.o
|
||||
txid.o uuid.o windowfuncs.o xml.o
|
||||
|
||||
like.o: like.c like_match.c
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* Copyright (c) 2003-2008, PostgreSQL Global Development Group
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/array_userfuncs.c,v 1.26 2008/11/14 02:09:51 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/array_userfuncs.c,v 1.27 2008/12/28 18:53:59 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@@ -475,6 +475,7 @@ Datum
|
||||
array_agg_transfn(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Oid arg1_typeid = get_fn_expr_argtype(fcinfo->flinfo, 1);
|
||||
MemoryContext aggcontext;
|
||||
ArrayBuildState *state;
|
||||
Datum elem;
|
||||
|
||||
@@ -483,8 +484,16 @@ array_agg_transfn(PG_FUNCTION_ARGS)
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("could not determine input data type")));
|
||||
|
||||
/* cannot be called directly because of internal-type argument */
|
||||
Assert(fcinfo->context && IsA(fcinfo->context, AggState));
|
||||
if (fcinfo->context && IsA(fcinfo->context, AggState))
|
||||
aggcontext = ((AggState *) fcinfo->context)->aggcontext;
|
||||
else if (fcinfo->context && IsA(fcinfo->context, WindowAggState))
|
||||
aggcontext = ((WindowAggState *) fcinfo->context)->wincontext;
|
||||
else
|
||||
{
|
||||
/* cannot be called directly because of internal-type argument */
|
||||
elog(ERROR, "array_agg_transfn called in non-aggregate context");
|
||||
aggcontext = NULL; /* keep compiler quiet */
|
||||
}
|
||||
|
||||
state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
|
||||
elem = PG_ARGISNULL(1) ? (Datum) 0 : PG_GETARG_DATUM(1);
|
||||
@@ -492,7 +501,7 @@ array_agg_transfn(PG_FUNCTION_ARGS)
|
||||
elem,
|
||||
PG_ARGISNULL(1),
|
||||
arg1_typeid,
|
||||
((AggState *) fcinfo->context)->aggcontext);
|
||||
aggcontext);
|
||||
|
||||
/*
|
||||
* The transition type for array_agg() is declared to be "internal",
|
||||
@@ -506,14 +515,28 @@ array_agg_transfn(PG_FUNCTION_ARGS)
|
||||
Datum
|
||||
array_agg_finalfn(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Datum result;
|
||||
ArrayBuildState *state;
|
||||
int dims[1];
|
||||
int lbs[1];
|
||||
|
||||
/* cannot be called directly because of internal-type argument */
|
||||
Assert(fcinfo->context && IsA(fcinfo->context, AggState));
|
||||
Assert(fcinfo->context &&
|
||||
(IsA(fcinfo->context, AggState) ||
|
||||
IsA(fcinfo->context, WindowAggState)));
|
||||
|
||||
if (PG_ARGISNULL(0))
|
||||
PG_RETURN_NULL(); /* returns null iff no input values */
|
||||
|
||||
state = (ArrayBuildState *) PG_GETARG_POINTER(0);
|
||||
PG_RETURN_ARRAYTYPE_P(makeArrayResult(state, CurrentMemoryContext));
|
||||
|
||||
dims[0] = state->nelems;
|
||||
lbs[0] = 1;
|
||||
|
||||
/* Release working state if regular aggregate, but not if window agg */
|
||||
result = makeMdArrayResult(state, 1, dims, lbs,
|
||||
CurrentMemoryContext,
|
||||
IsA(fcinfo->context, AggState));
|
||||
|
||||
PG_RETURN_DATUM(result);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/arrayfuncs.c,v 1.150 2008/11/14 00:51:46 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/arrayfuncs.c,v 1.151 2008/12/28 18:53:59 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@@ -4208,7 +4208,7 @@ makeArrayResult(ArrayBuildState *astate,
|
||||
dims[0] = astate->nelems;
|
||||
lbs[0] = 1;
|
||||
|
||||
return makeMdArrayResult(astate, 1, dims, lbs, rcontext);
|
||||
return makeMdArrayResult(astate, 1, dims, lbs, rcontext, true);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -4219,13 +4219,15 @@ makeArrayResult(ArrayBuildState *astate,
|
||||
*
|
||||
* astate is working state (not NULL)
|
||||
* rcontext is where to construct result
|
||||
* release is true if okay to release working state
|
||||
*/
|
||||
Datum
|
||||
makeMdArrayResult(ArrayBuildState *astate,
|
||||
int ndims,
|
||||
int *dims,
|
||||
int *lbs,
|
||||
MemoryContext rcontext)
|
||||
MemoryContext rcontext,
|
||||
bool release)
|
||||
{
|
||||
ArrayType *result;
|
||||
MemoryContext oldcontext;
|
||||
@@ -4246,7 +4248,8 @@ makeMdArrayResult(ArrayBuildState *astate,
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
|
||||
/* Clean up all the junk */
|
||||
MemoryContextDelete(astate->mcontext);
|
||||
if (release)
|
||||
MemoryContextDelete(astate->mcontext);
|
||||
|
||||
return PointerGetDatum(result);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/float.c,v 1.157 2008/05/09 21:31:23 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/float.c,v 1.158 2008/12/28 18:53:59 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@@ -1765,7 +1765,9 @@ float8_accum(PG_FUNCTION_ARGS)
|
||||
* parameter in-place to reduce palloc overhead. Otherwise we construct a
|
||||
* new array with the updated transition data and return it.
|
||||
*/
|
||||
if (fcinfo->context && IsA(fcinfo->context, AggState))
|
||||
if (fcinfo->context &&
|
||||
(IsA(fcinfo->context, AggState) ||
|
||||
IsA(fcinfo->context, WindowAggState)))
|
||||
{
|
||||
transvalues[0] = N;
|
||||
transvalues[1] = sumX;
|
||||
@@ -1818,7 +1820,9 @@ float4_accum(PG_FUNCTION_ARGS)
|
||||
* parameter in-place to reduce palloc overhead. Otherwise we construct a
|
||||
* new array with the updated transition data and return it.
|
||||
*/
|
||||
if (fcinfo->context && IsA(fcinfo->context, AggState))
|
||||
if (fcinfo->context &&
|
||||
(IsA(fcinfo->context, AggState) ||
|
||||
IsA(fcinfo->context, WindowAggState)))
|
||||
{
|
||||
transvalues[0] = N;
|
||||
transvalues[1] = sumX;
|
||||
@@ -2035,7 +2039,9 @@ float8_regr_accum(PG_FUNCTION_ARGS)
|
||||
* parameter in-place to reduce palloc overhead. Otherwise we construct a
|
||||
* new array with the updated transition data and return it.
|
||||
*/
|
||||
if (fcinfo->context && IsA(fcinfo->context, AggState))
|
||||
if (fcinfo->context &&
|
||||
(IsA(fcinfo->context, AggState) ||
|
||||
IsA(fcinfo->context, WindowAggState)))
|
||||
{
|
||||
transvalues[0] = N;
|
||||
transvalues[1] = sumX;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/int8.c,v 1.71 2008/10/05 23:18:37 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/int8.c,v 1.72 2008/12/28 18:53:59 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@@ -666,7 +666,9 @@ int8inc(PG_FUNCTION_ARGS)
|
||||
* as incorrect, so just ifdef it out.)
|
||||
*/
|
||||
#ifndef USE_FLOAT8_BYVAL /* controls int8 too */
|
||||
if (fcinfo->context && IsA(fcinfo->context, AggState))
|
||||
if (fcinfo->context &&
|
||||
(IsA(fcinfo->context, AggState) ||
|
||||
IsA(fcinfo->context, WindowAggState)))
|
||||
{
|
||||
int64 *arg = (int64 *) PG_GETARG_POINTER(0);
|
||||
int64 result;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* Copyright (c) 1998-2008, PostgreSQL Global Development Group
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/numeric.c,v 1.114 2008/05/09 21:31:23 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/numeric.c,v 1.115 2008/12/28 18:53:59 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@@ -2611,7 +2611,9 @@ int2_sum(PG_FUNCTION_ARGS)
|
||||
* as incorrect, so just ifdef it out.)
|
||||
*/
|
||||
#ifndef USE_FLOAT8_BYVAL /* controls int8 too */
|
||||
if (fcinfo->context && IsA(fcinfo->context, AggState))
|
||||
if (fcinfo->context &&
|
||||
(IsA(fcinfo->context, AggState) ||
|
||||
IsA(fcinfo->context, WindowAggState)))
|
||||
{
|
||||
int64 *oldsum = (int64 *) PG_GETARG_POINTER(0);
|
||||
|
||||
@@ -2660,7 +2662,9 @@ int4_sum(PG_FUNCTION_ARGS)
|
||||
* as incorrect, so just ifdef it out.)
|
||||
*/
|
||||
#ifndef USE_FLOAT8_BYVAL /* controls int8 too */
|
||||
if (fcinfo->context && IsA(fcinfo->context, AggState))
|
||||
if (fcinfo->context &&
|
||||
(IsA(fcinfo->context, AggState) ||
|
||||
IsA(fcinfo->context, WindowAggState)))
|
||||
{
|
||||
int64 *oldsum = (int64 *) PG_GETARG_POINTER(0);
|
||||
|
||||
@@ -2753,7 +2757,9 @@ int2_avg_accum(PG_FUNCTION_ARGS)
|
||||
* parameter in-place to reduce palloc overhead. Otherwise we need to make
|
||||
* a copy of it before scribbling on it.
|
||||
*/
|
||||
if (fcinfo->context && IsA(fcinfo->context, AggState))
|
||||
if (fcinfo->context &&
|
||||
(IsA(fcinfo->context, AggState) ||
|
||||
IsA(fcinfo->context, WindowAggState)))
|
||||
transarray = PG_GETARG_ARRAYTYPE_P(0);
|
||||
else
|
||||
transarray = PG_GETARG_ARRAYTYPE_P_COPY(0);
|
||||
@@ -2781,7 +2787,9 @@ int4_avg_accum(PG_FUNCTION_ARGS)
|
||||
* parameter in-place to reduce palloc overhead. Otherwise we need to make
|
||||
* a copy of it before scribbling on it.
|
||||
*/
|
||||
if (fcinfo->context && IsA(fcinfo->context, AggState))
|
||||
if (fcinfo->context &&
|
||||
(IsA(fcinfo->context, AggState) ||
|
||||
IsA(fcinfo->context, WindowAggState)))
|
||||
transarray = PG_GETARG_ARRAYTYPE_P(0);
|
||||
else
|
||||
transarray = PG_GETARG_ARRAYTYPE_P_COPY(0);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.290 2008/12/19 05:04:35 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.291 2008/12/28 18:53:59 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@@ -81,6 +81,8 @@ typedef struct
|
||||
{
|
||||
StringInfo buf; /* output buffer to append to */
|
||||
List *namespaces; /* List of deparse_namespace nodes */
|
||||
List *windowClause; /* Current query level's WINDOW clause */
|
||||
List *windowTList; /* targetlist for resolving WINDOW clause */
|
||||
int prettyFlags; /* enabling of pretty-print functions */
|
||||
int indentLevel; /* current indent level for prettyprint */
|
||||
bool varprefix; /* TRUE to print prefixes on Vars */
|
||||
@@ -167,6 +169,11 @@ static void get_setop_query(Node *setOp, Query *query,
|
||||
static Node *get_rule_sortgroupclause(SortGroupClause *srt, List *tlist,
|
||||
bool force_colno,
|
||||
deparse_context *context);
|
||||
static void get_rule_orderby(List *orderList, List *targetList,
|
||||
bool force_colno, deparse_context *context);
|
||||
static void get_rule_windowclause(Query *query, deparse_context *context);
|
||||
static void get_rule_windowspec(WindowClause *wc, List *targetList,
|
||||
deparse_context *context);
|
||||
static void push_plan(deparse_namespace *dpns, Plan *subplan);
|
||||
static char *get_variable(Var *var, int levelsup, bool showstar,
|
||||
deparse_context *context);
|
||||
@@ -183,6 +190,7 @@ static void get_oper_expr(OpExpr *expr, deparse_context *context);
|
||||
static void get_func_expr(FuncExpr *expr, deparse_context *context,
|
||||
bool showimplicit);
|
||||
static void get_agg_expr(Aggref *aggref, deparse_context *context);
|
||||
static void get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context);
|
||||
static void get_coercion_expr(Node *arg, deparse_context *context,
|
||||
Oid resulttype, int32 resulttypmod,
|
||||
Node *parentNode);
|
||||
@@ -1854,6 +1862,8 @@ deparse_expression_pretty(Node *expr, List *dpcontext,
|
||||
initStringInfo(&buf);
|
||||
context.buf = &buf;
|
||||
context.namespaces = dpcontext;
|
||||
context.windowClause = NIL;
|
||||
context.windowTList = NIL;
|
||||
context.varprefix = forceprefix;
|
||||
context.prettyFlags = prettyFlags;
|
||||
context.indentLevel = startIndent;
|
||||
@@ -2085,6 +2095,8 @@ make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
|
||||
|
||||
context.buf = buf;
|
||||
context.namespaces = list_make1(&dpns);
|
||||
context.windowClause = NIL;
|
||||
context.windowTList = NIL;
|
||||
context.varprefix = (list_length(query->rtable) != 1);
|
||||
context.prettyFlags = prettyFlags;
|
||||
context.indentLevel = PRETTYINDENT_STD;
|
||||
@@ -2228,6 +2240,8 @@ get_query_def(Query *query, StringInfo buf, List *parentnamespace,
|
||||
|
||||
context.buf = buf;
|
||||
context.namespaces = lcons(&dpns, list_copy(parentnamespace));
|
||||
context.windowClause = NIL;
|
||||
context.windowTList = NIL;
|
||||
context.varprefix = (parentnamespace != NIL ||
|
||||
list_length(query->rtable) != 1);
|
||||
context.prettyFlags = prettyFlags;
|
||||
@@ -2392,13 +2406,20 @@ get_select_query_def(Query *query, deparse_context *context,
|
||||
TupleDesc resultDesc)
|
||||
{
|
||||
StringInfo buf = context->buf;
|
||||
List *save_windowclause;
|
||||
List *save_windowtlist;
|
||||
bool force_colno;
|
||||
const char *sep;
|
||||
ListCell *l;
|
||||
|
||||
/* Insert the WITH clause if given */
|
||||
get_with_clause(query, context);
|
||||
|
||||
/* Set up context for possible window functions */
|
||||
save_windowclause = context->windowClause;
|
||||
context->windowClause = query->windowClause;
|
||||
save_windowtlist = context->windowTList;
|
||||
context->windowTList = query->targetList;
|
||||
|
||||
/*
|
||||
* If the Query node has a setOperations tree, then it's the top level of
|
||||
* a UNION/INTERSECT/EXCEPT query; only the WITH, ORDER BY and LIMIT
|
||||
@@ -2421,48 +2442,8 @@ get_select_query_def(Query *query, deparse_context *context,
|
||||
{
|
||||
appendContextKeyword(context, " ORDER BY ",
|
||||
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
|
||||
sep = "";
|
||||
foreach(l, query->sortClause)
|
||||
{
|
||||
SortGroupClause *srt = (SortGroupClause *) lfirst(l);
|
||||
Node *sortexpr;
|
||||
Oid sortcoltype;
|
||||
TypeCacheEntry *typentry;
|
||||
|
||||
appendStringInfoString(buf, sep);
|
||||
sortexpr = get_rule_sortgroupclause(srt, query->targetList,
|
||||
force_colno, context);
|
||||
sortcoltype = exprType(sortexpr);
|
||||
/* See whether operator is default < or > for datatype */
|
||||
typentry = lookup_type_cache(sortcoltype,
|
||||
TYPECACHE_LT_OPR | TYPECACHE_GT_OPR);
|
||||
if (srt->sortop == typentry->lt_opr)
|
||||
{
|
||||
/* ASC is default, so emit nothing for it */
|
||||
if (srt->nulls_first)
|
||||
appendStringInfo(buf, " NULLS FIRST");
|
||||
}
|
||||
else if (srt->sortop == typentry->gt_opr)
|
||||
{
|
||||
appendStringInfo(buf, " DESC");
|
||||
/* DESC defaults to NULLS FIRST */
|
||||
if (!srt->nulls_first)
|
||||
appendStringInfo(buf, " NULLS LAST");
|
||||
}
|
||||
else
|
||||
{
|
||||
appendStringInfo(buf, " USING %s",
|
||||
generate_operator_name(srt->sortop,
|
||||
sortcoltype,
|
||||
sortcoltype));
|
||||
/* be specific to eliminate ambiguity */
|
||||
if (srt->nulls_first)
|
||||
appendStringInfo(buf, " NULLS FIRST");
|
||||
else
|
||||
appendStringInfo(buf, " NULLS LAST");
|
||||
}
|
||||
sep = ", ";
|
||||
}
|
||||
get_rule_orderby(query->sortClause, query->targetList,
|
||||
force_colno, context);
|
||||
}
|
||||
|
||||
/* Add the LIMIT clause if given */
|
||||
@@ -2500,6 +2481,9 @@ get_select_query_def(Query *query, deparse_context *context,
|
||||
if (rc->noWait)
|
||||
appendStringInfo(buf, " NOWAIT");
|
||||
}
|
||||
|
||||
context->windowClause = save_windowclause;
|
||||
context->windowTList = save_windowtlist;
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -2603,6 +2587,10 @@ get_basic_select_query(Query *query, deparse_context *context,
|
||||
-PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
|
||||
get_rule_expr(query->havingQual, context, false);
|
||||
}
|
||||
|
||||
/* Add the WINDOW clause if needed */
|
||||
if (query->windowClause != NIL)
|
||||
get_rule_windowclause(query, context);
|
||||
}
|
||||
|
||||
/* ----------
|
||||
@@ -2807,6 +2795,143 @@ get_rule_sortgroupclause(SortGroupClause *srt, List *tlist, bool force_colno,
|
||||
return expr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Display an ORDER BY list.
|
||||
*/
|
||||
static void
|
||||
get_rule_orderby(List *orderList, List *targetList,
|
||||
bool force_colno, deparse_context *context)
|
||||
{
|
||||
StringInfo buf = context->buf;
|
||||
const char *sep;
|
||||
ListCell *l;
|
||||
|
||||
sep = "";
|
||||
foreach(l, orderList)
|
||||
{
|
||||
SortGroupClause *srt = (SortGroupClause *) lfirst(l);
|
||||
Node *sortexpr;
|
||||
Oid sortcoltype;
|
||||
TypeCacheEntry *typentry;
|
||||
|
||||
appendStringInfoString(buf, sep);
|
||||
sortexpr = get_rule_sortgroupclause(srt, targetList,
|
||||
force_colno, context);
|
||||
sortcoltype = exprType(sortexpr);
|
||||
/* See whether operator is default < or > for datatype */
|
||||
typentry = lookup_type_cache(sortcoltype,
|
||||
TYPECACHE_LT_OPR | TYPECACHE_GT_OPR);
|
||||
if (srt->sortop == typentry->lt_opr)
|
||||
{
|
||||
/* ASC is default, so emit nothing for it */
|
||||
if (srt->nulls_first)
|
||||
appendStringInfo(buf, " NULLS FIRST");
|
||||
}
|
||||
else if (srt->sortop == typentry->gt_opr)
|
||||
{
|
||||
appendStringInfo(buf, " DESC");
|
||||
/* DESC defaults to NULLS FIRST */
|
||||
if (!srt->nulls_first)
|
||||
appendStringInfo(buf, " NULLS LAST");
|
||||
}
|
||||
else
|
||||
{
|
||||
appendStringInfo(buf, " USING %s",
|
||||
generate_operator_name(srt->sortop,
|
||||
sortcoltype,
|
||||
sortcoltype));
|
||||
/* be specific to eliminate ambiguity */
|
||||
if (srt->nulls_first)
|
||||
appendStringInfo(buf, " NULLS FIRST");
|
||||
else
|
||||
appendStringInfo(buf, " NULLS LAST");
|
||||
}
|
||||
sep = ", ";
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Display a WINDOW clause.
|
||||
*
|
||||
* Note that the windowClause list might contain only anonymous window
|
||||
* specifications, in which case we should print nothing here.
|
||||
*/
|
||||
static void
|
||||
get_rule_windowclause(Query *query, deparse_context *context)
|
||||
{
|
||||
StringInfo buf = context->buf;
|
||||
const char *sep;
|
||||
ListCell *l;
|
||||
|
||||
sep = NULL;
|
||||
foreach(l, query->windowClause)
|
||||
{
|
||||
WindowClause *wc = (WindowClause *) lfirst(l);
|
||||
|
||||
if (wc->name == NULL)
|
||||
continue; /* ignore anonymous windows */
|
||||
|
||||
if (sep == NULL)
|
||||
appendContextKeyword(context, " WINDOW ",
|
||||
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
|
||||
else
|
||||
appendStringInfoString(buf, sep);
|
||||
|
||||
appendStringInfo(buf, "%s AS ", quote_identifier(wc->name));
|
||||
|
||||
get_rule_windowspec(wc, query->targetList, context);
|
||||
|
||||
sep = ", ";
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Display a window definition
|
||||
*/
|
||||
static void
|
||||
get_rule_windowspec(WindowClause *wc, List *targetList,
|
||||
deparse_context *context)
|
||||
{
|
||||
StringInfo buf = context->buf;
|
||||
bool needspace = false;
|
||||
const char *sep;
|
||||
ListCell *l;
|
||||
|
||||
appendStringInfoChar(buf, '(');
|
||||
if (wc->refname)
|
||||
{
|
||||
appendStringInfoString(buf, quote_identifier(wc->refname));
|
||||
needspace = true;
|
||||
}
|
||||
/* partitions are always inherited, so only print if no refname */
|
||||
if (wc->partitionClause && !wc->refname)
|
||||
{
|
||||
if (needspace)
|
||||
appendStringInfoChar(buf, ' ');
|
||||
appendStringInfoString(buf, "PARTITION BY ");
|
||||
sep = "";
|
||||
foreach(l, wc->partitionClause)
|
||||
{
|
||||
SortGroupClause *grp = (SortGroupClause *) lfirst(l);
|
||||
|
||||
appendStringInfoString(buf, sep);
|
||||
get_rule_sortgroupclause(grp, targetList,
|
||||
false, context);
|
||||
sep = ", ";
|
||||
}
|
||||
needspace = true;
|
||||
}
|
||||
if (wc->orderClause && !wc->copiedOrder)
|
||||
{
|
||||
if (needspace)
|
||||
appendStringInfoChar(buf, ' ');
|
||||
appendStringInfoString(buf, "ORDER BY ");
|
||||
get_rule_orderby(wc->orderClause, targetList, false, context);
|
||||
needspace = true;
|
||||
}
|
||||
appendStringInfoChar(buf, ')');
|
||||
}
|
||||
|
||||
/* ----------
|
||||
* get_insert_query_def - Parse back an INSERT parsetree
|
||||
* ----------
|
||||
@@ -3801,6 +3926,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
|
||||
case T_XmlExpr:
|
||||
case T_NullIfExpr:
|
||||
case T_Aggref:
|
||||
case T_WindowFunc:
|
||||
case T_FuncExpr:
|
||||
/* function-like: name(..) or name[..] */
|
||||
return true;
|
||||
@@ -3916,6 +4042,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
|
||||
case T_XmlExpr: /* own parentheses */
|
||||
case T_NullIfExpr: /* other separators */
|
||||
case T_Aggref: /* own parentheses */
|
||||
case T_WindowFunc: /* own parentheses */
|
||||
case T_CaseExpr: /* other separators */
|
||||
return true;
|
||||
default:
|
||||
@@ -3965,6 +4092,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
|
||||
case T_XmlExpr: /* own parentheses */
|
||||
case T_NullIfExpr: /* other separators */
|
||||
case T_Aggref: /* own parentheses */
|
||||
case T_WindowFunc: /* own parentheses */
|
||||
case T_CaseExpr: /* other separators */
|
||||
return true;
|
||||
default:
|
||||
@@ -4093,6 +4221,10 @@ get_rule_expr(Node *node, deparse_context *context,
|
||||
get_agg_expr((Aggref *) node, context);
|
||||
break;
|
||||
|
||||
case T_WindowFunc:
|
||||
get_windowfunc_expr((WindowFunc *) node, context);
|
||||
break;
|
||||
|
||||
case T_ArrayRef:
|
||||
{
|
||||
ArrayRef *aref = (ArrayRef *) node;
|
||||
@@ -4999,13 +5131,13 @@ get_func_expr(FuncExpr *expr, deparse_context *context,
|
||||
* Normal function: display as proname(args). First we need to extract
|
||||
* the argument datatypes.
|
||||
*/
|
||||
if (list_length(expr->args) > FUNC_MAX_ARGS)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
|
||||
errmsg("too many arguments")));
|
||||
nargs = 0;
|
||||
foreach(l, expr->args)
|
||||
{
|
||||
if (nargs >= FUNC_MAX_ARGS)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
|
||||
errmsg("too many arguments")));
|
||||
argtypes[nargs] = exprType((Node *) lfirst(l));
|
||||
nargs++;
|
||||
}
|
||||
@@ -5036,13 +5168,13 @@ get_agg_expr(Aggref *aggref, deparse_context *context)
|
||||
int nargs;
|
||||
ListCell *l;
|
||||
|
||||
if (list_length(aggref->args) > FUNC_MAX_ARGS)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
|
||||
errmsg("too many arguments")));
|
||||
nargs = 0;
|
||||
foreach(l, aggref->args)
|
||||
{
|
||||
if (nargs >= FUNC_MAX_ARGS)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
|
||||
errmsg("too many arguments")));
|
||||
argtypes[nargs] = exprType((Node *) lfirst(l));
|
||||
nargs++;
|
||||
}
|
||||
@@ -5059,6 +5191,64 @@ get_agg_expr(Aggref *aggref, deparse_context *context)
|
||||
appendStringInfoChar(buf, ')');
|
||||
}
|
||||
|
||||
/*
|
||||
* get_windowfunc_expr - Parse back a WindowFunc node
|
||||
*/
|
||||
static void
|
||||
get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context)
|
||||
{
|
||||
StringInfo buf = context->buf;
|
||||
Oid argtypes[FUNC_MAX_ARGS];
|
||||
int nargs;
|
||||
ListCell *l;
|
||||
|
||||
if (list_length(wfunc->args) > FUNC_MAX_ARGS)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
|
||||
errmsg("too many arguments")));
|
||||
nargs = 0;
|
||||
foreach(l, wfunc->args)
|
||||
{
|
||||
argtypes[nargs] = exprType((Node *) lfirst(l));
|
||||
nargs++;
|
||||
}
|
||||
|
||||
appendStringInfo(buf, "%s(%s",
|
||||
generate_function_name(wfunc->winfnoid,
|
||||
nargs, argtypes, NULL), "");
|
||||
/* winstar can be set only in zero-argument aggregates */
|
||||
if (wfunc->winstar)
|
||||
appendStringInfoChar(buf, '*');
|
||||
else
|
||||
get_rule_expr((Node *) wfunc->args, context, true);
|
||||
appendStringInfoString(buf, ") OVER ");
|
||||
|
||||
foreach(l, context->windowClause)
|
||||
{
|
||||
WindowClause *wc = (WindowClause *) lfirst(l);
|
||||
|
||||
if (wc->winref == wfunc->winref)
|
||||
{
|
||||
if (wc->name)
|
||||
appendStringInfoString(buf, quote_identifier(wc->name));
|
||||
else
|
||||
get_rule_windowspec(wc, context->windowTList, context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (l == NULL)
|
||||
{
|
||||
if (context->windowClause)
|
||||
elog(ERROR, "could not find window clause for winref %u",
|
||||
wfunc->winref);
|
||||
/*
|
||||
* In EXPLAIN, we don't have window context information available,
|
||||
* so we have to settle for this:
|
||||
*/
|
||||
appendStringInfoString(buf, "(?)");
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------
|
||||
* get_coercion_expr
|
||||
*
|
||||
@@ -6089,7 +6279,9 @@ generate_function_name(Oid funcid, int nargs, Oid *argtypes,
|
||||
NIL, nargs, argtypes, false, true,
|
||||
&p_funcid, &p_rettype,
|
||||
&p_retset, &p_nvargs, &p_true_typeids, NULL);
|
||||
if ((p_result == FUNCDETAIL_NORMAL || p_result == FUNCDETAIL_AGGREGATE) &&
|
||||
if ((p_result == FUNCDETAIL_NORMAL ||
|
||||
p_result == FUNCDETAIL_AGGREGATE ||
|
||||
p_result == FUNCDETAIL_WINDOWFUNC) &&
|
||||
p_funcid == funcid)
|
||||
nspname = NULL;
|
||||
else
|
||||
|
||||
475
src/backend/utils/adt/windowfuncs.c
Normal file
475
src/backend/utils/adt/windowfuncs.c
Normal file
@@ -0,0 +1,475 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* windowfuncs.c
|
||||
* Standard window functions defined in SQL spec.
|
||||
*
|
||||
* Portions Copyright (c) 2000-2008, PostgreSQL Global Development Group
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/windowfuncs.c,v 1.1 2008/12/28 18:53:59 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
#include "postgres.h"
|
||||
|
||||
#include "utils/builtins.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* acccess 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);
|
||||
|
||||
WinSetMarkPosition(winobj, curpos);
|
||||
PG_RETURN_INT64(curpos + 1);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
up = rank_up(winobj);
|
||||
context = (rank_context *)
|
||||
WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
|
||||
if (up)
|
||||
context->rank = WinGetCurrentPosition(winobj) + 1;
|
||||
|
||||
PG_RETURN_INT64(context->rank);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
up = rank_up(winobj);
|
||||
context = (rank_context *)
|
||||
WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
|
||||
if (up)
|
||||
context->rank++;
|
||||
|
||||
PG_RETURN_INT64(context->rank);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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);
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
/*
|
||||
* cume_dist
|
||||
* return fraction betweeen 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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
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 evelulated 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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/fmgr/fmgr.c,v 1.122 2008/08/25 22:42:34 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/utils/fmgr/fmgr.c,v 1.123 2008/12/28 18:53:59 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@@ -2218,6 +2218,7 @@ pg_detoast_datum_packed(struct varlena * datum)
|
||||
*
|
||||
* These are needed by polymorphic functions, which accept multiple possible
|
||||
* input types and need help from the parser to know what they've got.
|
||||
* Also, some functions might be interested in whether a parameter is constant.
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
@@ -2288,6 +2289,8 @@ get_call_expr_argtype(Node *expr, int argnum)
|
||||
args = list_make1(((ArrayCoerceExpr *) expr)->arg);
|
||||
else if (IsA(expr, NullIfExpr))
|
||||
args = ((NullIfExpr *) expr)->args;
|
||||
else if (IsA(expr, WindowFunc))
|
||||
args = ((WindowFunc *) expr)->args;
|
||||
else
|
||||
return InvalidOid;
|
||||
|
||||
@@ -2310,3 +2313,73 @@ get_call_expr_argtype(Node *expr, int argnum)
|
||||
|
||||
return argtype;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find out whether a specific function argument is constant for the
|
||||
* duration of a query
|
||||
*
|
||||
* Returns false if information is not available
|
||||
*/
|
||||
bool
|
||||
get_fn_expr_arg_stable(FmgrInfo *flinfo, int argnum)
|
||||
{
|
||||
/*
|
||||
* can't return anything useful if we have no FmgrInfo or if its fn_expr
|
||||
* node has not been initialized
|
||||
*/
|
||||
if (!flinfo || !flinfo->fn_expr)
|
||||
return false;
|
||||
|
||||
return get_call_expr_arg_stable(flinfo->fn_expr, argnum);
|
||||
}
|
||||
|
||||
/*
|
||||
* Find out whether a specific function argument is constant for the
|
||||
* duration of a query, but working from the calling expression tree
|
||||
*
|
||||
* Returns false if information is not available
|
||||
*/
|
||||
bool
|
||||
get_call_expr_arg_stable(Node *expr, int argnum)
|
||||
{
|
||||
List *args;
|
||||
Node *arg;
|
||||
|
||||
if (expr == NULL)
|
||||
return false;
|
||||
|
||||
if (IsA(expr, FuncExpr))
|
||||
args = ((FuncExpr *) expr)->args;
|
||||
else if (IsA(expr, OpExpr))
|
||||
args = ((OpExpr *) expr)->args;
|
||||
else if (IsA(expr, DistinctExpr))
|
||||
args = ((DistinctExpr *) expr)->args;
|
||||
else if (IsA(expr, ScalarArrayOpExpr))
|
||||
args = ((ScalarArrayOpExpr *) expr)->args;
|
||||
else if (IsA(expr, ArrayCoerceExpr))
|
||||
args = list_make1(((ArrayCoerceExpr *) expr)->arg);
|
||||
else if (IsA(expr, NullIfExpr))
|
||||
args = ((NullIfExpr *) expr)->args;
|
||||
else if (IsA(expr, WindowFunc))
|
||||
args = ((WindowFunc *) expr)->args;
|
||||
else
|
||||
return false;
|
||||
|
||||
if (argnum < 0 || argnum >= list_length(args))
|
||||
return false;
|
||||
|
||||
arg = (Node *) list_nth(args, argnum);
|
||||
|
||||
/*
|
||||
* Either a true Const or an external Param will have a value that
|
||||
* doesn't change during the execution of the query. In future we
|
||||
* might want to consider other cases too, e.g. now().
|
||||
*/
|
||||
if (IsA(arg, Const))
|
||||
return true;
|
||||
if (IsA(arg, Param) &&
|
||||
((Param *) arg)->paramkind == PARAM_EXTERN)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/sort/tuplestore.c,v 1.44 2008/12/27 17:39:00 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/utils/sort/tuplestore.c,v 1.45 2008/12/28 18:53:59 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@@ -1148,6 +1148,19 @@ tuplestore_trim(Tuplestorestate *state)
|
||||
state->truncated = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* tuplestore_in_memory
|
||||
*
|
||||
* Returns true if the tuplestore has not spilled to disk.
|
||||
*
|
||||
* XXX exposing this is a violation of modularity ... should get rid of it.
|
||||
*/
|
||||
bool
|
||||
tuplestore_in_memory(Tuplestorestate *state)
|
||||
{
|
||||
return (state->status == TSS_INMEM);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Tape interface routines
|
||||
|
||||
Reference in New Issue
Block a user