1
0
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:
Tom Lane
2008-12-28 18:54:01 +00:00
parent 38e9348282
commit 95b07bc7f5
92 changed files with 6720 additions and 321 deletions

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);

View File

@@ -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

View 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);
}

View File

@@ -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;
}

View File

@@ -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