poc - possibility to write window function in PL languages
Hi
I wrote a proof concept for the support window function from plpgsql.
Window function API - functions named WinFuncArg* are polymorphic and it is
not easy to wrap these functions for usage from SQL level. I wrote an
enhancement of the GET statement - for this case GET WINDOW_CONTEXT, that
allows safe and fast access to the result of these functions.
Custom variant of row_number can look like:
create or replace function pl_row_number()
returns bigint as $$
declare pos int8;
begin
pos := get_current_position(windowobject);
pos := pos + 1;
perform set_mark_position(windowobject, pos);
return pos;
end
$$
language plpgsql window;
Custom variant of lag function can look like:
create or replace function pl_lag(numeric)
returns numeric as $$
declare
v numeric;
begin
perform get_input_value_in_partition(windowobject, 1, -1, 'seek_current',
false);
get pg_window_context v = PG_INPUT_VALUE;
return v;
end;
$$ language plpgsql window;
Custom window functions can be used for generating missing data in time
series
create table test_missing_values(id int, v integer);
insert into test_missing_values
values(1,10),(2,11),(3,12),(4,null),(5,null),(6,15),(7,16);
create or replace function pl_pcontext_test(numeric)
returns numeric as $$
declare
n numeric;
v numeric;
begin
perform get_input_value_for_row(windowobject, 1);
get pg_window_context v = PG_INPUT_VALUE;
if v is null then
v := get_partition_context_value(windowobject, null::numeric);
else
perform set_partition_context_value(windowobject, v);
end if;
return v;
end
$$
language plpgsql window;
select id, v, pl_pcontext_test(v) over (order by id) from
test_missing_values;
id | v | pl_pcontext_test.
----+----+------------------
1 | 10 | 10
2 | 11 | 11
3 | 12 | 12
4 | | 12
5 | | 12
6 | 15 | 15
7 | 16 | 16
(7 rows)
I think about another variant for WinFuncArg functions where polymorphic
argument is used similarly like in get_partition_context_value - this patch
is prototype, but it works and I think so support of custom window
functions in PL languages is possible and probably useful.
Comments, notes, ideas, objections?
Regards
Pavel
Attachments:
plpgsql-window-function-support.patchtext/x-patch; charset=US-ASCII; name=plpgsql-window-function-support.patchDownload
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ba5a23ac25..fdc364c05e 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1418,6 +1418,18 @@ LANGUAGE internal
STRICT IMMUTABLE PARALLEL SAFE
AS 'unicode_is_normalized';
+CREATE OR REPLACE FUNCTION
+ get_partition_context_value(windowobjectproxy, anyelement, int4 DEFAULT NULL)
+ RETURNS anyelement
+LANGUAGE internal
+AS 'windowobject_get_partition_context_value';
+
+CREATE OR REPLACE FUNCTION
+ set_partition_context_value(windowobjectproxy, anyelement, int4 DEFAULT NULL)
+ RETURNS void
+LANGUAGE internal
+AS 'windowobject_set_partition_context_value';
+
--
-- The default permissions for functions mean that anyone can execute them.
-- A number of functions shouldn't be executable by just anyone, but rather
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index 3d6b2f9093..ebb2a16572 100644
--- a/src/backend/utils/adt/pseudotypes.c
+++ b/src/backend/utils/adt/pseudotypes.c
@@ -334,6 +334,17 @@ pg_node_tree_send(PG_FUNCTION_ARGS)
PSEUDOTYPE_DUMMY_IO_FUNCS(pg_ddl_command);
PSEUDOTYPE_DUMMY_BINARY_IO_FUNCS(pg_ddl_command);
+/*
+ * windowobjectproxy
+ *
+ * This type is pointer to WindowObjectProxyData. It is communication
+ * mechanism between PL environment and WinFuncArgs functions. Due
+ * performance reason I prefer using indirect result processing against
+ * using function returning polymorphic composite value. The indirect
+ * mechanism is implemented with proxy object represented by type
+ * WindowObjectProxyData.
+ */
+PSEUDOTYPE_DUMMY_IO_FUNCS(windowobjectproxy);
/*
* Dummy I/O functions for various other pseudotypes.
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index f0c8ae686d..f18495b228 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -14,6 +14,8 @@
#include "postgres.h"
#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/lsyscache.h"
#include "windowapi.h"
/*
@@ -35,6 +37,20 @@ typedef struct
int64 remainder; /* (total rows) % (bucket num) */
} ntile_context;
+#define PROXY_CONTEXT_MAGIC 19730715
+
+typedef struct
+{
+ int magic;
+ Oid typid;
+ int16 typlen;
+ bool typbyval;
+ int allocsize;
+ bool isnull;
+ Datum value;
+ char data[FLEXIBLE_ARRAY_MEMBER];
+} proxy_context;
+
static bool rank_up(WindowObject winobj);
static Datum leadlag_common(FunctionCallInfo fcinfo,
bool forward, bool withoffset, bool withdefault);
@@ -472,3 +488,485 @@ window_nth_value(PG_FUNCTION_ARGS)
PG_RETURN_DATUM(result);
}
+
+/*
+ * High level access function. These functions are wrappers for windows API
+ * for PL languages based on usage WindowObjectProxy.
+ */
+Datum
+windowobject_get_current_position(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int64 pos;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ wop->isvalid = false;
+
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ pos = WinGetCurrentPosition(winobj);
+
+ PG_RETURN_INT64(pos);
+}
+
+Datum
+windowobject_set_mark_position(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int64 pos;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ wop->isvalid = false;
+
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ pos = PG_GETARG_INT64(1);
+
+ WinSetMarkPosition(winobj, pos);
+
+ PG_RETURN_VOID();
+}
+
+Datum
+windowobject_get_partition_rowcount(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int64 rc;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ wop->isvalid = false;
+
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ rc = WinGetPartitionRowCount(winobj);
+
+ PG_RETURN_INT64(rc);
+}
+
+Datum
+windowobject_rows_are_peers(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int64 pos1,
+ pos2;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ wop->isvalid = false;
+
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ pos1 = PG_GETARG_INT64(1);
+ pos2 = PG_GETARG_INT64(2);
+
+ PG_RETURN_BOOL(WinRowsArePeers(winobj, pos1, pos2));
+}
+
+#define SEEK_CURRENT_STR "seek_current"
+#define SEEK_HEAD_STR "seek_head"
+#define SEEK_TAIL_STR "seek_tail"
+
+#define STRLEN(s) (sizeof(s) - 1)
+
+static int
+get_seek_type(text *seektype)
+{
+ char *str;
+ int len;
+ int result;
+
+ str = VARDATA_ANY(seektype);
+ len = VARSIZE_ANY_EXHDR(seektype);
+
+ if (len == STRLEN(SEEK_CURRENT_STR) && strncmp(str, SEEK_CURRENT_STR, len) == 0)
+ result = WINDOW_SEEK_CURRENT;
+ else if (len == STRLEN(SEEK_HEAD_STR) && strncmp(str, SEEK_HEAD_STR, len) == 0)
+ result = WINDOW_SEEK_HEAD;
+ else if (len == STRLEN(SEEK_TAIL_STR) && strncmp(str, SEEK_TAIL_STR, len) == 0)
+ result = WINDOW_SEEK_TAIL;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("seek type value must be \"seek_current\", \"seek_head\" or \"seek_tail\"")));
+
+ return result;
+}
+
+/*
+ * Value should be copied to persistent memory context.
+ * GET PG_WINDOW_CONTEXT is second statement, and we should
+ * not to lost data.
+ */
+static void
+copy_datum_to_winobj_proxy(WindowObjectProxy wop,
+ int argno,
+ Datum value,
+ bool isnull)
+{
+ MemoryContext oldcxt;
+
+ if (argno != wop->last_argno)
+ {
+ Oid argtypid = get_fn_expr_argtype(wop->fcinfo->flinfo, argno);
+
+ argtypid = getBaseType(argtypid);
+ get_typlenbyval(argtypid, &wop->typlen, &wop->typbyval);
+ wop->last_argno = argno;
+ }
+
+ if (wop->freeval)
+ {
+ pfree(DatumGetPointer(wop->value));
+ wop->freeval = false;
+ }
+
+ if (!isnull)
+ {
+ oldcxt = MemoryContextSwitchTo(wop->proxy_cxt);
+
+ wop->value = datumCopy(value, wop->typbyval, wop->typlen);
+ wop->freeval = !wop->typbyval;
+ wop->isnull = false;
+
+ MemoryContextSwitchTo(oldcxt);
+ }
+ else
+ wop->isnull = true;
+
+ wop->argno = argno;
+ wop->isvalid = true;
+}
+
+Datum
+windowobject_get_func_arg_partition(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int argno;
+ int relpos;
+ int seektype;
+ bool set_mark;
+ Datum value;
+ bool isnull;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ wop->isvalid = false;
+ wop->argno = -1;
+
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ argno = PG_GETARG_INT32(1);
+ if (argno < 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("arg number less than one")));
+
+ argno -= 1;
+
+ relpos = PG_GETARG_INT32(2);
+ seektype = get_seek_type(PG_GETARG_TEXT_P(3));
+ set_mark = PG_GETARG_BOOL(4);
+
+ value = WinGetFuncArgInPartition(winobj,
+ argno,
+ relpos,
+ seektype,
+ set_mark,
+ &isnull,
+ &wop->isout);
+
+ copy_datum_to_winobj_proxy(wop, argno, value, isnull);
+
+ PG_RETURN_VOID();
+}
+
+Datum
+windowobject_get_func_arg_frame(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int argno;
+ int relpos;
+ int seektype;
+ bool set_mark;
+ Datum value;
+ bool isnull;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ wop->isvalid = false;
+ wop->argno = -1;
+
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ argno = PG_GETARG_INT32(1);
+ relpos = PG_GETARG_INT32(2);
+ seektype = get_seek_type(PG_GETARG_TEXT_P(3));
+ set_mark = PG_GETARG_BOOL(4);
+
+ if (argno < 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("arg number less than one")));
+
+ argno -= 1;
+
+ value = WinGetFuncArgInFrame(winobj,
+ argno,
+ relpos,
+ seektype,
+ set_mark,
+ &isnull,
+ &wop->isout);
+
+ copy_datum_to_winobj_proxy(wop, argno, value, isnull);
+
+ PG_RETURN_VOID();
+}
+
+Datum
+windowobject_get_func_arg_current(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int argno;
+ Datum value;
+ bool isnull;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ wop->isvalid = false;
+ wop->argno = -1;
+
+ winobj = wop->winobj;
+
+ Assert(WindowObjectIsValid(winobj));
+
+ argno = PG_GETARG_INT32(1);
+
+ if (argno < 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("arg number less than one")));
+
+ argno -= 1;
+
+ value = WinGetFuncArgCurrent(winobj, argno, &isnull);
+ copy_datum_to_winobj_proxy(wop, argno, value, isnull);
+
+ wop->isout = false;
+
+ PG_RETURN_VOID();
+}
+
+static void
+copy_datum_to_partition_context(proxy_context *pcontext,
+ Datum value,
+ bool isnull)
+{
+ if (!isnull)
+ {
+ if (pcontext->typbyval)
+ pcontext->value = value;
+ else if (pcontext->typlen == -1)
+ {
+ struct varlena *s = (struct varlena *) DatumGetPointer(value);
+
+ memcpy(pcontext->data, s, VARSIZE_ANY(s));
+ pcontext->value = PointerGetDatum(pcontext->data);
+ }
+ else
+ {
+ memcpy(pcontext->data, DatumGetPointer(value), pcontext->typlen);
+ pcontext->value = PointerGetDatum(pcontext->data);
+ }
+
+ pcontext->isnull = false;
+ }
+ else
+ {
+ pcontext->value = (Datum) 0;
+ pcontext->isnull = true;
+ }
+}
+
+/*
+ * Returns estimated size of windowobject partition context
+ */
+static int
+estimate_partition_context_size(Datum value,
+ bool isnull,
+ int16 typlen,
+ int16 minsize,
+ int *realsize)
+{
+ if(typlen != -1)
+ {
+ if (typlen < sizeof(Datum))
+ {
+ *realsize = offsetof(proxy_context, data);
+
+ return *realsize;
+ }
+
+ if (typlen > 1024)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("size of value is greather than limit (1024 bytes)")));
+
+ *realsize = offsetof(proxy_context, data) + typlen;
+
+ return *realsize;
+ }
+ else
+ {
+ if (!isnull)
+ {
+ int size = VARSIZE_ANY_EXHDR(DatumGetPointer(value));
+
+ if (size > 1024)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("size of value is greather than limit (1024 bytes)")));
+
+ *realsize = size;
+
+ size += size / 3;
+
+ return offsetof(proxy_context, data)
+ + MAXALIGN(size > minsize ? size : minsize);
+ }
+ else
+ {
+ /* by default we allocate 30 bytes */
+ *realsize = 0;
+
+ return offsetof(proxy_context, data) + MAXALIGN(minsize);
+ }
+ }
+}
+
+#define VARLENA_MINSIZE 32
+
+static proxy_context *
+get_partition_context(FunctionCallInfo fcinfo, bool write_mode)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ Oid typid;
+ int16 typlen;
+ bool typbyval;
+ Datum value = (Datum) 0;
+ bool isnull = true;
+ int allocsize;
+ int minsize;
+ int realsize;
+ proxy_context *pcontext;
+
+ if (PG_ARGISNULL(0))
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("windowobject is NULL")));
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ if (PG_ARGISNULL(2))
+ minsize = VARLENA_MINSIZE;
+ else
+ minsize = PG_GETARG_INT32(2);
+
+ if (!PG_ARGISNULL(1))
+ {
+ value = PG_GETARG_DATUM(1);
+ isnull = false;
+ }
+
+ typid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ if (!OidIsValid(typid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot detect type of context value")));
+
+ typid = getBaseType(typid);
+ get_typlenbyval(typid, &typlen, &typbyval);
+
+ Assert(typlen != -2);
+
+ allocsize = estimate_partition_context_size(value,
+ isnull,
+ typlen,
+ minsize,
+ &realsize);
+
+ pcontext = (proxy_context *) WinGetPartitionLocalMemory(winobj, allocsize);
+
+ /* fresh pcontext has zeroed memory */
+ Assert(pcontext->magic == 0 || pcontext->magic == PROXY_CONTEXT_MAGIC);
+
+ if (pcontext->allocsize > 0)
+ {
+ if (realsize > pcontext->allocsize)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("the value cannot be saved to allocated buffer"),
+ errhint("Try to increase the minsize argument.")));
+
+ if (pcontext->typid != typid)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("partition context was initialized for different type")));
+
+ if (write_mode)
+ copy_datum_to_partition_context(pcontext, value, isnull);
+
+ }
+ else
+ {
+ pcontext->magic = PROXY_CONTEXT_MAGIC;
+ pcontext->typid = typid;
+ pcontext->typlen = typlen;
+ pcontext->typbyval = typbyval;
+ pcontext->allocsize = allocsize;
+
+ copy_datum_to_partition_context(pcontext, value, isnull);
+ }
+
+ return pcontext;
+}
+
+Datum
+windowobject_set_partition_context_value(PG_FUNCTION_ARGS)
+{
+ (void) get_partition_context(fcinfo, true);
+
+ PG_RETURN_VOID();
+}
+
+Datum
+windowobject_get_partition_context_value(PG_FUNCTION_ARGS)
+{
+ proxy_context *pcontext;
+
+ pcontext = get_partition_context(fcinfo, false);
+
+ if (pcontext->isnull)
+ PG_RETURN_NULL();
+
+ PG_RETURN_DATUM(pcontext->value);
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 27989971db..0393bd66c4 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7130,6 +7130,12 @@
{ oid => '2305', descr => 'I/O',
proname => 'internal_out', prorettype => 'cstring', proargtypes => 'internal',
prosrc => 'internal_out' },
+{ oid => '9553', descr => 'I/O',
+ proname => 'windowobjectproxy_in', proisstrict => 'f', prorettype => 'windowobjectproxy',
+ proargtypes => 'cstring', prosrc => 'windowobjectproxy_in' },
+{ oid => '9554', descr => 'I/O',
+ proname => 'windowobjectproxy_out', prorettype => 'cstring', proargtypes => 'windowobjectproxy',
+ prosrc => 'windowobjectproxy_out' },
{ oid => '2312', descr => 'I/O',
proname => 'anyelement_in', prorettype => 'anyelement',
proargtypes => 'cstring', prosrc => 'anyelement_in' },
@@ -9742,7 +9748,36 @@
{ oid => '3114', descr => 'fetch the Nth row value',
proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
-
+{ oid => '9555', descr => 'get current position from window object',
+ proname => 'get_current_position', prokind => 'f', prorettype => 'int8',
+ proargtypes => 'windowobjectproxy', prosrc => 'windowobject_get_current_position' },
+{ oid => '9556', descr => 'set current position in window object',
+ proname => 'set_mark_position', prokind => 'f', prorettype => 'void',
+ proargtypes => 'windowobjectproxy int8', prosrc => 'windowobject_set_mark_position' },
+{ oid => '9560', descr => 'get partition row count',
+ proname => 'get_partition_rowcount', prokind => 'f', prorettype => 'int8',
+ proargtypes => 'windowobjectproxy', prosrc => 'windowobject_get_partition_rowcount' },
+{ oid => '9561', descr => 'returns true if two positions are peers',
+ proname => 'rows_are_peers', prokind => 'f', prorettype => 'bool',
+ proargtypes => 'windowobjectproxy int8 int8', prosrc => 'windowobject_rows_are_peers' },
+{ oid => '9562', descr => 'returns argument of window function against to partition',
+ proname => 'get_input_value_in_partition', prokind => 'f', prorettype => 'void',
+ proargtypes => 'windowobjectproxy int4 int4 text bool', prosrc => 'windowobject_get_func_arg_partition' },
+{ oid => '9563', descr => 'returns argument of window function against to frame',
+ proname => 'get_input_value_in_frame', prokind => 'f', prorettype => 'void',
+ proargtypes => 'windowobjectproxy int4 int4 text bool', prosrc => 'windowobject_get_func_arg_frame' },
+{ oid => '9564', descr => 'returns argument of window function against to current row',
+ proname => 'get_input_value_for_row', prokind => 'f', prorettype => 'void',
+ proargtypes => 'windowobjectproxy int4', prosrc => 'windowobject_get_func_arg_current' },
+{ oid => '9565', descr => 'returns a value stored in a partition context',
+ proname => 'get_partition_context_value', prokind => 'f', prorettype => 'anyelement',
+ proargtypes => 'windowobjectproxy anyelement int4',
+ prosrc => 'windowobject_get_partition_context_value', proisstrict => 'f' },
+{ oid => '9566', descr => 'store a value to partition context',
+ proname => 'set_partition_context_value', prokind => 'f', prorettype => 'void',
+ proargtypes => 'windowobjectproxy anyelement int4',
+ prosrc => 'windowobject_set_partition_context_value',
+ proisstrict => 'f' },
# functions for range types
{ oid => '3832', descr => 'I/O',
proname => 'anyrange_in', provolatile => 's', prorettype => 'anyrange',
diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat
index b2cec07416..78b166ecb1 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -560,6 +560,12 @@
typtype => 'p', typcategory => 'P', typinput => 'internal_in',
typoutput => 'internal_out', typreceive => '-', typsend => '-',
typalign => 'ALIGNOF_POINTER' },
+{ oid => '9552',
+ descr => 'pseudo-type representing an pointer to WindowObjectProxy structure',
+ typname => 'windowobjectproxy', typlen => '-1', typbyval => 't',
+ typtype => 'p', typcategory => 'P', typinput => 'windowobjectproxy_in',
+ typoutput => 'windowobjectproxy_out', typreceive => '-', typsend => '-',
+ typalign => 'd' },
{ oid => '2283', descr => 'pseudo-type representing a polymorphic base type',
typname => 'anyelement', typlen => '4', typbyval => 't', typtype => 'p',
typcategory => 'P', typinput => 'anyelement_in',
diff --git a/src/include/windowapi.h b/src/include/windowapi.h
index e8c9fc54d8..0ec6b82412 100644
--- a/src/include/windowapi.h
+++ b/src/include/windowapi.h
@@ -36,6 +36,33 @@
/* this struct is private in nodeWindowAgg.c */
typedef struct WindowObjectData *WindowObject;
+/*
+ * This type is used as proxy between PL variants of WinFuncArg
+ * functions and PL environment.
+ */
+typedef struct WindowObjectProxyData
+{
+ int32 vl_len; /* varlena header */
+ WindowObject winobj;
+
+ bool isout;
+ int argno;
+ bool isvalid;
+
+ Datum value;
+ bool isnull;
+ bool freeval;
+
+ int last_argno;
+ int16 typlen;
+ bool typbyval;
+
+ MemoryContext proxy_cxt;
+ FunctionCallInfo fcinfo;
+} WindowObjectProxyData;
+
+typedef WindowObjectProxyData *WindowObjectProxy;
+
#define PG_WINDOW_OBJECT() ((WindowObject) fcinfo->context)
#define WindowObjectIsValid(winobj) \
diff --git a/src/pl/plpgsql/src/Makefile b/src/pl/plpgsql/src/Makefile
index 193df8a010..ba6edfd42e 100644
--- a/src/pl/plpgsql/src/Makefile
+++ b/src/pl/plpgsql/src/Makefile
@@ -34,7 +34,7 @@ REGRESS_OPTS = --dbname=$(PL_TESTDB)
REGRESS = plpgsql_call plpgsql_control plpgsql_copy plpgsql_domain \
plpgsql_record plpgsql_cache plpgsql_simple plpgsql_transaction \
- plpgsql_trap plpgsql_trigger plpgsql_varprops
+ plpgsql_trap plpgsql_trigger plpgsql_varprops plpgsql_window
# where to find gen_keywordlist.pl and subsidiary files
TOOLSDIR = $(top_srcdir)/src/tools
diff --git a/src/pl/plpgsql/src/expected/plpgsql_window.out b/src/pl/plpgsql/src/expected/plpgsql_window.out
new file mode 100644
index 0000000000..9ee8189eb5
--- /dev/null
+++ b/src/pl/plpgsql/src/expected/plpgsql_window.out
@@ -0,0 +1,237 @@
+create or replace function pl_row_number()
+returns bigint as $$
+declare pos int8;
+begin
+ pos := get_current_position(windowobject);
+ pos := pos + 1;
+ perform set_mark_position(windowobject, pos);
+ return pos;
+end
+$$
+language plpgsql window;
+select pl_row_number() over (), v from (values(10),(20),(30)) v(v);
+ pl_row_number | v
+---------------+----
+ 1 | 10
+ 2 | 20
+ 3 | 30
+(3 rows)
+
+create or replace function pl_round_value(numeric)
+returns int as $$
+declare
+ num numeric;
+begin
+ perform get_input_value_for_row(windowobject, 1);
+ get pg_window_context num = PG_INPUT_VALUE;
+ return round(num);
+end
+$$ language plpgsql window;
+select pl_round_value(v) over(order by v desc) from generate_series(0.1, 1.0, 0.1) g(v);
+ pl_round_value
+----------------
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+ 0
+ 0
+ 0
+ 0
+(10 rows)
+
+select pl_round_value(v + 1) over(order by v desc) from generate_series(0.1, 1.0, 0.1) g(v);
+ pl_round_value
+----------------
+ 2
+ 2
+ 2
+ 2
+ 2
+ 2
+ 1
+ 1
+ 1
+ 1
+(10 rows)
+
+create table test_table(v numeric);
+insert into test_table values(1),(3),(6),(6),(8),(7),(6),(5),(4);
+create or replace function pl_lag(numeric)
+returns numeric as $$
+declare
+ v numeric;
+begin
+ perform get_input_value_in_partition(windowobject, 1, -1, 'seek_current', false);
+ get pg_window_context v = PG_INPUT_VALUE;
+ return v;
+end;
+$$ language plpgsql window;
+select pl_lag(v) over (), lag(v) over () from test_table;
+ pl_lag | lag
+--------+-----
+ |
+ 1 | 1
+ 3 | 3
+ 6 | 6
+ 6 | 6
+ 8 | 8
+ 7 | 7
+ 6 | 6
+ 5 | 5
+(9 rows)
+
+drop table test_table;
+create table test_table(v integer);
+insert into test_table values(1),(3),(6),(6),(8),(7),(6),(5),(4);
+select pl_lag(v) over (), lag(v) over () from test_table;
+ pl_lag | lag
+--------+-----
+ |
+ 1 | 1
+ 3 | 3
+ 6 | 6
+ 6 | 6
+ 8 | 8
+ 7 | 7
+ 6 | 6
+ 5 | 5
+(9 rows)
+
+create or replace function pl_moving_avg(numeric)
+returns numeric as $$
+declare
+ s numeric default 0.0;
+ v numeric;
+ c numeric default 0.0;
+begin
+ -- look before
+ perform get_input_value_in_partition(windowobject, 1, -1, 'seek_current', false);
+ get pg_window_context v = PG_INPUT_VALUE;
+ if v is not null then
+ s := s + v;
+ c := c + 1.0;
+ end if;
+
+ -- look after
+ perform get_input_value_in_partition(windowobject, 1, 0, 'seek_current', false);
+ get pg_window_context v = PG_INPUT_VALUE;
+ if v is not null then
+ s := s + v;
+ c := c + 1.0;
+ end if;
+
+ perform get_input_value_in_partition(windowobject, 1, 1, 'seek_current', false);
+ get pg_window_context v = PG_INPUT_VALUE;
+ if v is not null then
+ s := s + v;
+ c := c + 1.0;
+ end if;
+
+ return trim_scale(s / c);
+end
+$$ language plpgsql window;
+select pl_moving_avg(v) over (), v from test_table;
+ pl_moving_avg | v
+--------------------+---
+ 2 | 1
+ 3.3333333333333333 | 3
+ 5 | 6
+ 6.6666666666666667 | 6
+ 7 | 8
+ 7 | 7
+ 6 | 6
+ 5 | 5
+ 4.5 | 4
+(9 rows)
+
+create or replace function pl_lag_polymorphic(anyelement)
+returns anyelement as $$
+declare
+ v $0%type;
+begin
+ perform get_input_value_in_partition(windowobject, 1, -1, 'seek_current', false);
+ get pg_window_context v = PG_INPUT_VALUE;
+ return v;
+end;
+$$ language plpgsql window;
+select pl_lag_polymorphic(v) over (), lag(v) over () from test_table;
+ pl_lag_polymorphic | lag
+--------------------+-----
+ |
+ 1 | 1
+ 3 | 3
+ 6 | 6
+ 6 | 6
+ 8 | 8
+ 7 | 7
+ 6 | 6
+ 5 | 5
+(9 rows)
+
+create or replace function pl_pcontext_test(numeric)
+returns numeric as $$
+declare
+ n numeric;
+ v numeric;
+begin
+ n := get_partition_context_value(windowobject, null::numeric);
+
+ perform get_input_value_for_row(windowobject, 1);
+ get pg_window_context v = PG_INPUT_VALUE;
+
+ perform set_partition_context_value(windowobject, v);
+ return n;
+end
+$$
+language plpgsql window;
+select v, pl_pcontext_test(v) over () from generate_series(0.1, 1.0, 0.1) g(v);
+ v | pl_pcontext_test
+-----+------------------
+ 0.1 |
+ 0.2 | 0.1
+ 0.3 | 0.2
+ 0.4 | 0.3
+ 0.5 | 0.4
+ 0.6 | 0.5
+ 0.7 | 0.6
+ 0.8 | 0.7
+ 0.9 | 0.8
+ 1.0 | 0.9
+(10 rows)
+
+create table test_missing_values(id int, v integer);
+insert into test_missing_values values(1,10),(2,11),(3,12),(4,null),(5,null),(6,15),(7,16);
+create or replace function pl_pcontext_test(numeric)
+returns numeric as $$
+declare
+ n numeric;
+ v numeric;
+begin
+ perform get_input_value_for_row(windowobject, 1);
+ get pg_window_context v = PG_INPUT_VALUE;
+
+ if v is null then
+ v := get_partition_context_value(windowobject, null::numeric);
+ else
+ perform set_partition_context_value(windowobject, v);
+ end if;
+
+ return v;
+end
+$$
+language plpgsql window;
+select id, v, pl_pcontext_test(v) over (order by id) from test_missing_values;
+ id | v | pl_pcontext_test
+----+----+------------------
+ 1 | 10 | 10
+ 2 | 11 | 11
+ 3 | 12 | 12
+ 4 | | 12
+ 5 | | 12
+ 6 | 15 | 15
+ 7 | 16 | 16
+(7 rows)
+
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index e7f4a5f291..ee4bbb560e 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -582,6 +582,41 @@ do_compile(FunctionCallInfo fcinfo,
true);
}
+ if (function->fn_prokind == PROKIND_WINDOW)
+ {
+ PLpgSQL_type *dtype;
+ PLpgSQL_var *var;
+
+ /*
+ * Add the promise variable windowobject with windowobjectproxy type
+ *
+ * Pseudotypes are disallowed for custom variables. It is checked
+ * in plpgsql_build_variable, so instead call this function, build
+ * promise variable here.
+ */
+
+ dtype = plpgsql_build_datatype(WINDOWOBJECTPROXYOID,
+ -1,
+ function->fn_input_collation,
+ NULL);
+
+ /* this should be pseudotype */
+ Assert(dtype->ttype == PLPGSQL_TTYPE_PSEUDO);
+
+ var = palloc0(sizeof(PLpgSQL_var));
+
+ var->dtype = PLPGSQL_DTYPE_PROMISE;
+ var->promise = PLPGSQL_PROMISE_WINDOWOBJECT;
+
+ var->refname = pstrdup("windowobject");
+ var->datatype = dtype;
+
+ plpgsql_adddatum((PLpgSQL_datum *) var);
+ plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR,
+ var->dno,
+ var->refname);
+ }
+
ReleaseSysCache(typeTup);
break;
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index d4a3d58daa..25858ed815 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -320,6 +320,8 @@ static int exec_stmt_rollback(PLpgSQL_execstate *estate,
PLpgSQL_stmt_rollback *stmt);
static int exec_stmt_set(PLpgSQL_execstate *estate,
PLpgSQL_stmt_set *stmt);
+static int exec_stmt_getwincxt(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_getwincxt *stmt);
static void plpgsql_estate_setup(PLpgSQL_execstate *estate,
PLpgSQL_function *func,
@@ -593,6 +595,42 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo,
*/
exec_set_found(&estate, false);
+ /*
+ * Initialize promise winobject
+ */
+ if (func->fn_prokind == PROKIND_WINDOW)
+ {
+ /* fcinfo is available in this function too */
+ WindowObjectProxy wop;
+ MemoryContext oldcontext;
+
+ oldcontext = MemoryContextSwitchTo(estate.datum_context);
+
+ wop = palloc(sizeof(WindowObjectProxyData));
+ SET_VARSIZE(wop, sizeof(WindowObjectProxy));
+
+ wop->winobj = PG_WINDOW_OBJECT();
+
+ Assert(WindowObjectIsValid(wop->winobj));
+
+ wop->value = (Datum) 0;
+ wop->isnull = true;
+ wop->isout = false;
+ wop->freeval = false;
+ wop->argno = -1;
+ wop->isvalid = false;
+ wop->last_argno = -1;
+
+ wop->proxy_cxt = estate.datum_context;
+ wop->fcinfo = fcinfo;
+
+ MemoryContextSwitchTo(oldcontext);
+
+ estate.winobjproxy = wop;
+ }
+ else
+ estate.winobjproxy = NULL;
+
/*
* Let the instrumentation plugin peek at this function
*/
@@ -915,6 +953,11 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
plpgsql_estate_setup(&estate, func, NULL, NULL, NULL);
estate.trigdata = trigdata;
+ /*
+ * Trigger function cannot be WINDOW function
+ */
+ estate.winobjproxy = NULL;
+
/*
* Setup error traceback support for ereport()
*/
@@ -1293,11 +1336,13 @@ copy_plpgsql_datums(PLpgSQL_execstate *estate,
PLpgSQL_datum *indatum = indatums[i];
PLpgSQL_datum *outdatum;
+
/* This must agree with plpgsql_finish_datums on what is copiable */
switch (indatum->dtype)
{
case PLPGSQL_DTYPE_VAR:
case PLPGSQL_DTYPE_PROMISE:
+
outdatum = (PLpgSQL_datum *) ws_next;
memcpy(outdatum, indatum, sizeof(PLpgSQL_var));
ws_next += MAXALIGN(sizeof(PLpgSQL_var));
@@ -1486,6 +1531,17 @@ plpgsql_fulfill_promise(PLpgSQL_execstate *estate,
assign_text_var(estate, var, GetCommandTagName(estate->evtrigdata->tag));
break;
+ case PLPGSQL_PROMISE_WINDOWOBJECT:
+ if (!estate->winobjproxy)
+ elog(ERROR, "windowobject promise is not in a window function");
+
+ assign_simple_var(estate,
+ var,
+ PointerGetDatum(estate->winobjproxy),
+ false,
+ false);
+ break;
+
default:
elog(ERROR, "unrecognized promise type: %d", var->promise);
}
@@ -1995,6 +2051,10 @@ exec_stmts(PLpgSQL_execstate *estate, List *stmts)
rc = exec_stmt_getdiag(estate, (PLpgSQL_stmt_getdiag *) stmt);
break;
+ case PLPGSQL_STMT_GETWINCXT:
+ rc = exec_stmt_getwincxt(estate, (PLpgSQL_stmt_getwincxt *) stmt);
+ break;
+
case PLPGSQL_STMT_IF:
rc = exec_stmt_if(estate, (PLpgSQL_stmt_if *) stmt);
break;
@@ -2486,6 +2546,72 @@ exec_stmt_getdiag(PLpgSQL_execstate *estate, PLpgSQL_stmt_getdiag *stmt)
return PLPGSQL_RC_OK;
}
+/* ----------
+ * exec_stmt_getwincxt
+ *
+ * Inside this statement we can do safe cast from window_result type
+ * to some target variable. We should to ensure so target variable
+ * has same datatype as result type. Then we can safely copy
+ * referenced datum to target variable.
+ *
+ * ----------
+ */
+static int
+exec_stmt_getwincxt(PLpgSQL_execstate *estate, PLpgSQL_stmt_getwincxt *stmt)
+{
+ WindowObjectProxy wop;
+ ListCell *lc;
+
+ wop = estate->winobjproxy;
+
+ if (!wop)
+ elog(ERROR, "GET WINDOWS_CONTEXT is not in windows function");
+
+
+ if (!wop->isvalid)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("windows context has not valid data")));
+
+ foreach(lc, stmt->items)
+ {
+ PLpgSQL_wincxt_item *item = (PLpgSQL_wincxt_item *) lfirst(lc);
+
+ if (item->kind == PLPGSQL_GETWINCXT_INPUT_VALUE)
+ {
+ if (wop->argno >= 0)
+ exec_assign_value(estate,
+ estate->datums[item->target],
+ wop->value,
+ wop->isnull,
+ get_fn_expr_argtype(wop->fcinfo->flinfo, wop->argno),
+ -1);
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("windows context has not requested data")));
+ }
+ else if (item->kind == PLPGSQL_GETWINCXT_IS_OUT_OF_INPUT)
+ {
+ if (wop->argno >= 0)
+ exec_assign_value(estate,
+ estate->datums[item->target],
+ BoolGetDatum(wop->isout),
+ false,
+ BOOLOID,
+ -1);
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("windows context has not requested data")));
+ }
+ else
+ elog(ERROR, "unsupported PLpgSQL_getwincxt_kind");
+ }
+
+ return PLPGSQL_RC_OK;
+}
+
/* ----------
* exec_stmt_if Evaluate a bool expression and
* execute the true or false body
diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c
index ee60ced583..7080b5c6da 100644
--- a/src/pl/plpgsql/src/pl_funcs.c
+++ b/src/pl/plpgsql/src/pl_funcs.c
@@ -274,6 +274,8 @@ plpgsql_stmt_typename(PLpgSQL_stmt *stmt)
case PLPGSQL_STMT_GETDIAG:
return ((PLpgSQL_stmt_getdiag *) stmt)->is_stacked ?
"GET STACKED DIAGNOSTICS" : "GET DIAGNOSTICS";
+ case PLPGSQL_STMT_GETWINCXT:
+ return "GET PG_WINDOW_CONTEXT";
case PLPGSQL_STMT_OPEN:
return "OPEN";
case PLPGSQL_STMT_FETCH:
@@ -363,6 +365,7 @@ static void free_execsql(PLpgSQL_stmt_execsql *stmt);
static void free_dynexecute(PLpgSQL_stmt_dynexecute *stmt);
static void free_dynfors(PLpgSQL_stmt_dynfors *stmt);
static void free_getdiag(PLpgSQL_stmt_getdiag *stmt);
+static void free_getwincxt(PLpgSQL_stmt_getwincxt *stmt);
static void free_open(PLpgSQL_stmt_open *stmt);
static void free_fetch(PLpgSQL_stmt_fetch *stmt);
static void free_close(PLpgSQL_stmt_close *stmt);
@@ -439,6 +442,9 @@ free_stmt(PLpgSQL_stmt *stmt)
case PLPGSQL_STMT_GETDIAG:
free_getdiag((PLpgSQL_stmt_getdiag *) stmt);
break;
+ case PLPGSQL_STMT_GETWINCXT:
+ free_getwincxt((PLpgSQL_stmt_getwincxt *) stmt);
+ break;
case PLPGSQL_STMT_OPEN:
free_open((PLpgSQL_stmt_open *) stmt);
break;
@@ -723,6 +729,11 @@ free_getdiag(PLpgSQL_stmt_getdiag *stmt)
{
}
+static void
+free_getwincxt(PLpgSQL_stmt_getwincxt *stmt)
+{
+}
+
static void
free_expr(PLpgSQL_expr *expr)
{
@@ -820,6 +831,7 @@ static void dump_execsql(PLpgSQL_stmt_execsql *stmt);
static void dump_dynexecute(PLpgSQL_stmt_dynexecute *stmt);
static void dump_dynfors(PLpgSQL_stmt_dynfors *stmt);
static void dump_getdiag(PLpgSQL_stmt_getdiag *stmt);
+static void dump_getwincxt(PLpgSQL_stmt_getwincxt *stmt);
static void dump_open(PLpgSQL_stmt_open *stmt);
static void dump_fetch(PLpgSQL_stmt_fetch *stmt);
static void dump_cursor_direction(PLpgSQL_stmt_fetch *stmt);
@@ -907,6 +919,9 @@ dump_stmt(PLpgSQL_stmt *stmt)
case PLPGSQL_STMT_GETDIAG:
dump_getdiag((PLpgSQL_stmt_getdiag *) stmt);
break;
+ case PLPGSQL_STMT_GETWINCXT:
+ dump_getwincxt((PLpgSQL_stmt_getwincxt *) stmt);
+ break;
case PLPGSQL_STMT_OPEN:
dump_open((PLpgSQL_stmt_open *) stmt);
break;
@@ -1614,6 +1629,41 @@ dump_getdiag(PLpgSQL_stmt_getdiag *stmt)
printf("\n");
}
+static const char *
+getwincxt_kindname(PLpgSQL_getwincxt_kind kind)
+{
+ switch (kind)
+ {
+ case PLPGSQL_GETWINCXT_INPUT_VALUE:
+ return "PG_INPUT_VALUE";
+ case PLPGSQL_GETWINCXT_IS_OUT_OF_INPUT:
+ return "PG_IS_OUT_OF_INPUT";
+ default:
+ elog(ERROR, "unknown PLpgSQL_getwincxt_kind value");
+ }
+}
+
+static void
+dump_getwincxt(PLpgSQL_stmt_getwincxt *stmt)
+{
+ ListCell *lc;
+
+ dump_ind();
+
+ printf("GET PG_WINDOW_CONTEXT ");
+ foreach(lc, stmt->items)
+ {
+ PLpgSQL_wincxt_item *item = (PLpgSQL_wincxt_item *) lfirst(lc);
+
+ if (lc != list_head(stmt->items))
+ printf(", ");
+
+ printf("{var %d} = %s", item->target,
+ getwincxt_kindname(item->kind));
+ }
+ printf("\n");
+}
+
static void
dump_expr(PLpgSQL_expr *expr)
{
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 5a7e1a4444..143b8a86ab 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -166,6 +166,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
PLpgSQL_diag_item *diagitem;
PLpgSQL_stmt_fetch *fetch;
PLpgSQL_case_when *casewhen;
+ PLpgSQL_wincxt_item *wincxtitem;
}
%type <declhdr> decl_sect
@@ -201,6 +202,9 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
%type <stmt> stmt_open stmt_fetch stmt_move stmt_close stmt_null
%type <stmt> stmt_commit stmt_rollback stmt_set
%type <stmt> stmt_case stmt_foreach_a
+%type <stmt> stmt_getwincxt
+%type <list> get_wincxt_items
+%type <wincxtitem> get_wincxt_item
%type <list> proc_exceptions
%type <exception_block> exception_sect
@@ -325,6 +329,9 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
%token <keyword> K_PG_EXCEPTION_CONTEXT
%token <keyword> K_PG_EXCEPTION_DETAIL
%token <keyword> K_PG_EXCEPTION_HINT
+%token <keyword> K_PG_INPUT_VALUE
+%token <keyword> K_PG_IS_OUT_OF_INPUT
+%token <keyword> K_PG_WINDOW_CONTEXT
%token <keyword> K_PRINT_STRICT_PARAMS
%token <keyword> K_PRIOR
%token <keyword> K_QUERY
@@ -886,6 +893,8 @@ proc_stmt : pl_block ';'
{ $$ = $1; }
| stmt_getdiag
{ $$ = $1; }
+ | stmt_getwincxt
+ { $$ = $1; }
| stmt_open
{ $$ = $1; }
| stmt_fetch
@@ -1153,6 +1162,56 @@ assign_var : T_DATUM
}
;
+stmt_getwincxt : K_GET K_PG_WINDOW_CONTEXT get_wincxt_items ';'
+ {
+ PLpgSQL_stmt_getwincxt *new = palloc0(sizeof(PLpgSQL_stmt_getwincxt));
+
+ new->cmd_type = PLPGSQL_STMT_GETWINCXT;
+
+ /* Allow this statement only inside window function */
+ if (plpgsql_curr_compile->fn_prokind != PROKIND_WINDOW)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("GET WINDOW_FUNCTION_ARGUMENT statement can be used only in WINDOW function")));
+
+ new->lineno = plpgsql_location_to_lineno(@1);
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->items = $3;
+
+ $$ = (PLpgSQL_stmt *) new;
+ }
+ ;
+
+get_wincxt_items : get_wincxt_items ',' get_wincxt_item
+ {
+ $$ = lappend($1, $3);
+ }
+ | get_wincxt_item
+ {
+ $$ = list_make1($1);
+ }
+ ;
+
+get_wincxt_item: T_DATUM '=' K_PG_INPUT_VALUE
+ {
+ PLpgSQL_wincxt_item *item = palloc(sizeof(PLpgSQL_wincxt_item));
+
+ check_assignable($1.datum, @1);
+
+ item->target = $1.datum->dno;
+ item->kind = PLPGSQL_GETWINCXT_INPUT_VALUE;
+ }
+ | T_DATUM '=' K_PG_IS_OUT_OF_INPUT
+ {
+ PLpgSQL_wincxt_item *item = palloc(sizeof(PLpgSQL_wincxt_item));
+
+ check_assignable($1.datum, @1);
+
+ item->target = $1.datum->dno;
+ item->kind = PLPGSQL_GETWINCXT_IS_OUT_OF_INPUT;
+ }
+ ;
+
stmt_if : K_IF expr_until_then proc_sect stmt_elsifs stmt_else K_END K_IF ';'
{
PLpgSQL_stmt_if *new;
diff --git a/src/pl/plpgsql/src/pl_unreserved_kwlist.h b/src/pl/plpgsql/src/pl_unreserved_kwlist.h
index 99b3cf7d8a..67cb8b7203 100644
--- a/src/pl/plpgsql/src/pl_unreserved_kwlist.h
+++ b/src/pl/plpgsql/src/pl_unreserved_kwlist.h
@@ -84,6 +84,9 @@ PG_KEYWORD("pg_datatype_name", K_PG_DATATYPE_NAME)
PG_KEYWORD("pg_exception_context", K_PG_EXCEPTION_CONTEXT)
PG_KEYWORD("pg_exception_detail", K_PG_EXCEPTION_DETAIL)
PG_KEYWORD("pg_exception_hint", K_PG_EXCEPTION_HINT)
+PG_KEYWORD("pg_input_value", K_PG_INPUT_VALUE)
+PG_KEYWORD("pg_is_out_of_input", K_PG_IS_OUT_OF_INPUT)
+PG_KEYWORD("pg_window_context", K_PG_WINDOW_CONTEXT)
PG_KEYWORD("print_strict_params", K_PRINT_STRICT_PARAMS)
PG_KEYWORD("prior", K_PRIOR)
PG_KEYWORD("query", K_QUERY)
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 0c3d30fb13..16c898c4ef 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -23,6 +23,8 @@
#include "utils/expandedrecord.h"
#include "utils/typcache.h"
+#include "windowapi.h"
+
/**********************************************************************
* Definitions
@@ -84,7 +86,8 @@ typedef enum PLpgSQL_promise_type
PLPGSQL_PROMISE_TG_NARGS,
PLPGSQL_PROMISE_TG_ARGV,
PLPGSQL_PROMISE_TG_EVENT,
- PLPGSQL_PROMISE_TG_TAG
+ PLPGSQL_PROMISE_TG_TAG,
+ PLPGSQL_PROMISE_WINDOWOBJECT
} PLpgSQL_promise_type;
/*
@@ -122,6 +125,7 @@ typedef enum PLpgSQL_stmt_type
PLPGSQL_STMT_DYNEXECUTE,
PLPGSQL_STMT_DYNFORS,
PLPGSQL_STMT_GETDIAG,
+ PLPGSQL_STMT_GETWINCXT,
PLPGSQL_STMT_OPEN,
PLPGSQL_STMT_FETCH,
PLPGSQL_STMT_CLOSE,
@@ -162,6 +166,15 @@ typedef enum PLpgSQL_getdiag_kind
PLPGSQL_GETDIAG_SCHEMA_NAME
} PLpgSQL_getdiag_kind;
+/*
+ * GET WINDOW_CONTEXT information items
+ */
+typedef enum PLpgSQL_getwincxt_kind
+{
+ PLPGSQL_GETWINCXT_INPUT_VALUE,
+ PLPGSQL_GETWINCXT_IS_OUT_OF_INPUT
+} PLpgSQL_getwincxt_kind;
+
/*
* RAISE statement options
*/
@@ -612,6 +625,26 @@ typedef struct PLpgSQL_stmt_getdiag
List *diag_items; /* List of PLpgSQL_diag_item */
} PLpgSQL_stmt_getdiag;
+/*
+ * GET PG_WINDOW_CONTEXT item
+ */
+typedef struct PLpgSQL_wincxt_item
+{
+ PLpgSQL_getwincxt_kind kind; /* id for diagnostic value desired */
+ int target; /* where to assign it */
+} PLpgSQL_wincxt_item;
+
+/*
+ * GET PG_WINDOW_CONTEXT statement
+ */
+typedef struct PLpgSQL_stmt_getwincxt
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ List *items;
+} PLpgSQL_stmt_getwincxt;
+
/*
* IF statement
*/
@@ -1049,6 +1082,9 @@ typedef struct PLpgSQL_execstate
TriggerData *trigdata; /* if regular trigger, data about firing */
EventTriggerData *evtrigdata; /* if event trigger, data about firing */
+ WindowObjectProxy winobjproxy; /* for window function we need proxy
+ * object between PL and WinFucArg funcions */
+
Datum retval;
bool retisnull;
Oid rettype; /* type of current retval */
diff --git a/src/pl/plpgsql/src/sql/plpgsql_window.sql b/src/pl/plpgsql/src/sql/plpgsql_window.sql
new file mode 100644
index 0000000000..f2f275bd1a
--- /dev/null
+++ b/src/pl/plpgsql/src/sql/plpgsql_window.sql
@@ -0,0 +1,144 @@
+create or replace function pl_row_number()
+returns bigint as $$
+declare pos int8;
+begin
+ pos := get_current_position(windowobject);
+ pos := pos + 1;
+ perform set_mark_position(windowobject, pos);
+ return pos;
+end
+$$
+language plpgsql window;
+
+select pl_row_number() over (), v from (values(10),(20),(30)) v(v);
+
+create or replace function pl_round_value(numeric)
+returns int as $$
+declare
+ num numeric;
+begin
+ perform get_input_value_for_row(windowobject, 1);
+ get pg_window_context num = PG_INPUT_VALUE;
+ return round(num);
+end
+$$ language plpgsql window;
+
+select pl_round_value(v) over(order by v desc) from generate_series(0.1, 1.0, 0.1) g(v);
+
+select pl_round_value(v + 1) over(order by v desc) from generate_series(0.1, 1.0, 0.1) g(v);
+
+create table test_table(v numeric);
+insert into test_table values(1),(3),(6),(6),(8),(7),(6),(5),(4);
+
+create or replace function pl_lag(numeric)
+returns numeric as $$
+declare
+ v numeric;
+begin
+ perform get_input_value_in_partition(windowobject, 1, -1, 'seek_current', false);
+ get pg_window_context v = PG_INPUT_VALUE;
+ return v;
+end;
+$$ language plpgsql window;
+
+select pl_lag(v) over (), lag(v) over () from test_table;
+
+drop table test_table;
+
+create table test_table(v integer);
+insert into test_table values(1),(3),(6),(6),(8),(7),(6),(5),(4);
+
+select pl_lag(v) over (), lag(v) over () from test_table;
+
+create or replace function pl_moving_avg(numeric)
+returns numeric as $$
+declare
+ s numeric default 0.0;
+ v numeric;
+ c numeric default 0.0;
+begin
+ -- look before
+ perform get_input_value_in_partition(windowobject, 1, -1, 'seek_current', false);
+ get pg_window_context v = PG_INPUT_VALUE;
+ if v is not null then
+ s := s + v;
+ c := c + 1.0;
+ end if;
+
+ -- look after
+ perform get_input_value_in_partition(windowobject, 1, 0, 'seek_current', false);
+ get pg_window_context v = PG_INPUT_VALUE;
+ if v is not null then
+ s := s + v;
+ c := c + 1.0;
+ end if;
+
+ perform get_input_value_in_partition(windowobject, 1, 1, 'seek_current', false);
+ get pg_window_context v = PG_INPUT_VALUE;
+ if v is not null then
+ s := s + v;
+ c := c + 1.0;
+ end if;
+
+ return trim_scale(s / c);
+end
+$$ language plpgsql window;
+
+select pl_moving_avg(v) over (), v from test_table;
+
+create or replace function pl_lag_polymorphic(anyelement)
+returns anyelement as $$
+declare
+ v $0%type;
+begin
+ perform get_input_value_in_partition(windowobject, 1, -1, 'seek_current', false);
+ get pg_window_context v = PG_INPUT_VALUE;
+ return v;
+end;
+$$ language plpgsql window;
+
+select pl_lag_polymorphic(v) over (), lag(v) over () from test_table;
+
+create or replace function pl_pcontext_test(numeric)
+returns numeric as $$
+declare
+ n numeric;
+ v numeric;
+begin
+ n := get_partition_context_value(windowobject, null::numeric);
+
+ perform get_input_value_for_row(windowobject, 1);
+ get pg_window_context v = PG_INPUT_VALUE;
+
+ perform set_partition_context_value(windowobject, v);
+ return n;
+end
+$$
+language plpgsql window;
+
+select v, pl_pcontext_test(v) over () from generate_series(0.1, 1.0, 0.1) g(v);
+
+create table test_missing_values(id int, v integer);
+insert into test_missing_values values(1,10),(2,11),(3,12),(4,null),(5,null),(6,15),(7,16);
+
+create or replace function pl_pcontext_test(numeric)
+returns numeric as $$
+declare
+ n numeric;
+ v numeric;
+begin
+ perform get_input_value_for_row(windowobject, 1);
+ get pg_window_context v = PG_INPUT_VALUE;
+
+ if v is null then
+ v := get_partition_context_value(windowobject, null::numeric);
+ else
+ perform set_partition_context_value(windowobject, v);
+ end if;
+
+ return v;
+end
+$$
+language plpgsql window;
+
+select id, v, pl_pcontext_test(v) over (order by id) from test_missing_values;
Hi
I simplified access to results of winfuncargs functions by proxy type
"typedvalue". This type can hold any Datum value, and allows fast cast to
basic buildin types or it can use (slower) generic cast functions. It is
used in cooperation with a plpgsql assign statement that can choose the
correct cast implicitly. When the winfuncarg function returns a value of
the same type, that is expected by the variable on the left side of the
assign statement, then (for basic types), the value is just copied without
casts. With this proxy type is not necessary to have special statement for
assigning returned value from winfuncargs functions, so source code of
window function in plpgsql looks intuitive to me.
Example - implementation of "lag" function in plpgsql
create or replace function pl_lag(numeric)
returns numeric as $$
declare v numeric;
begin
v := get_input_value_in_partition(windowobject, 1, -1, 'seek_current',
false);
return v;
end;
$$ language plpgsql window;
I think this code is usable, and I assign this patch to commitfest.
Regards
Pavel
Attachments:
plpgsql-window-function-support.patchtext/x-patch; charset=US-ASCII; name=plpgsql-window-function-support.patchDownload
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ba5a23ac25..fdc364c05e 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1418,6 +1418,18 @@ LANGUAGE internal
STRICT IMMUTABLE PARALLEL SAFE
AS 'unicode_is_normalized';
+CREATE OR REPLACE FUNCTION
+ get_partition_context_value(windowobjectproxy, anyelement, int4 DEFAULT NULL)
+ RETURNS anyelement
+LANGUAGE internal
+AS 'windowobject_get_partition_context_value';
+
+CREATE OR REPLACE FUNCTION
+ set_partition_context_value(windowobjectproxy, anyelement, int4 DEFAULT NULL)
+ RETURNS void
+LANGUAGE internal
+AS 'windowobject_set_partition_context_value';
+
--
-- The default permissions for functions mean that anyone can execute them.
-- A number of functions shouldn't be executable by just anyone, but rather
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 5d2aca8cfe..1974cfd7e6 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -101,6 +101,7 @@ OBJS = \
tsvector.o \
tsvector_op.o \
tsvector_parser.o \
+ typedvalue.o \
uuid.o \
varbit.o \
varchar.o \
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index 3d6b2f9093..ebb2a16572 100644
--- a/src/backend/utils/adt/pseudotypes.c
+++ b/src/backend/utils/adt/pseudotypes.c
@@ -334,6 +334,17 @@ pg_node_tree_send(PG_FUNCTION_ARGS)
PSEUDOTYPE_DUMMY_IO_FUNCS(pg_ddl_command);
PSEUDOTYPE_DUMMY_BINARY_IO_FUNCS(pg_ddl_command);
+/*
+ * windowobjectproxy
+ *
+ * This type is pointer to WindowObjectProxyData. It is communication
+ * mechanism between PL environment and WinFuncArgs functions. Due
+ * performance reason I prefer using indirect result processing against
+ * using function returning polymorphic composite value. The indirect
+ * mechanism is implemented with proxy object represented by type
+ * WindowObjectProxyData.
+ */
+PSEUDOTYPE_DUMMY_IO_FUNCS(windowobjectproxy);
/*
* Dummy I/O functions for various other pseudotypes.
diff --git a/src/backend/utils/adt/typedvalue.c b/src/backend/utils/adt/typedvalue.c
new file mode 100644
index 0000000000..d2d5dc34c0
--- /dev/null
+++ b/src/backend/utils/adt/typedvalue.c
@@ -0,0 +1,630 @@
+/*-------------------------------------------------------------------------
+ *
+ * typedvalue.c
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/typedvalue.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "catalog/pg_type.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/lsyscache.h"
+#include "utils/numeric.h"
+#include "utils/typedvalue.h"
+#include "fmgr.h"
+
+
+static Datum
+TypedValueGetDatum(TypedValue tv)
+{
+ if (tv->typbyval)
+ return *((Datum *) tv->data);
+ else
+ return PointerGetDatum(tv->data);
+}
+
+Datum
+typedvalue_in(PG_FUNCTION_ARGS)
+{
+ char *str = PG_GETARG_CSTRING(0);
+ int len;
+ Size size;
+ TypedValue tv;
+ text *txt;
+
+ len = strlen(str);
+
+ size = MAXALIGN(offsetof(TypedValueData, data) + len + VARHDRSZ);
+
+ tv = (TypedValue) palloc(size);
+ SET_VARSIZE(tv, size);
+
+ txt = (text *) tv->data;
+
+ SET_VARSIZE(txt, VARHDRSZ + len);
+ memcpy(VARDATA(txt), str, len);
+
+ tv->typid = TEXTOID;
+ tv->typbyval = false;
+ tv->typlen = -1;
+
+ PG_RETURN_POINTER(tv);
+}
+
+Datum
+typedvalue_out(PG_FUNCTION_ARGS)
+{
+ Oid typOutput;
+ bool isVarlena;
+ char *str;
+ TypedValue tv;
+ Datum value;
+
+ tv = (TypedValue) PG_GETARG_POINTER(0);
+
+ getTypeOutputInfo(tv->typid, &typOutput, &isVarlena);
+
+ value = TypedValueGetDatum(tv);
+
+ str = OidOutputFunctionCall(typOutput, value);
+
+ PG_RETURN_CSTRING(str);
+}
+
+Datum
+makeTypedValue(Datum value, Oid typid, int16 typlen, bool typbyval)
+{
+ TypedValue tv;
+ Size size;
+ Size copy_bytes = 0;
+
+ if (typbyval)
+ size = MAXALIGN(offsetof(TypedValueData, data) + sizeof(Datum));
+ else
+ {
+ if (typlen != -1)
+ {
+ size = MAXALIGN(offsetof(TypedValueData, data) + typlen);
+ copy_bytes = typlen;
+ }
+ else
+ {
+ copy_bytes = VARSIZE_ANY((struct varlena *) DatumGetPointer(value));
+ size = MAXALIGN(offsetof(TypedValueData, data) + copy_bytes);
+ }
+ }
+
+ tv = (TypedValue) palloc(size);
+
+ SET_VARSIZE(tv, size);
+
+ tv->typid = typid;
+ tv->typlen = typlen;
+ tv->typbyval = typbyval;
+
+ if (typbyval)
+ *((Datum *) tv->data) = value;
+ else
+ memcpy(tv->data, DatumGetPointer(value), copy_bytes);
+
+ return PointerGetDatum(tv);
+}
+
+static Datum
+parse_value(Datum textval, Oid targettid)
+{
+ Oid typinput,
+ typioparam;
+ char *str;
+ Datum result;
+
+ getTypeInputInfo(targettid, &typinput, &typioparam);
+ str = TextDatumGetCString(textval);
+ result = OidInputFunctionCall(typinput, str,
+ typioparam, -1);
+ pfree(str);
+ return result;
+}
+
+static Datum
+generic_cast(Datum value, Oid typid, Oid target_typid)
+{
+ return OidFunctionCall1(get_cast_oid(typid,
+ target_typid,
+ false),
+ value);
+}
+
+Datum
+typedvalue_to_numeric(PG_FUNCTION_ARGS)
+{
+ TypedValue tv;
+ Datum value;
+
+ tv = (TypedValue) PG_GETARG_POINTER(0);
+ value = TypedValueGetDatum(tv);
+
+ switch (tv->typid)
+ {
+ case INT4OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(int4_numeric, value));
+
+ case INT8OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(int8_numeric, value));
+
+ case NUMERICOID:
+ PG_RETURN_DATUM(value);
+
+ case FLOAT4OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(float4_numeric, value));
+
+ case FLOAT8OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(float8_numeric, value));
+
+ case TEXTOID:
+ case BPCHAROID:
+ PG_RETURN_DATUM(parse_value(value, NUMERICOID));
+
+ default:
+ /* slower generic cast */
+ PG_RETURN_DATUM(generic_cast(value, tv->typid, NUMERICOID));
+ }
+}
+
+Datum
+typedvalue_to_int8(PG_FUNCTION_ARGS)
+{
+ TypedValue tv;
+ Datum value;
+
+ tv = (TypedValue) PG_GETARG_POINTER(0);
+ value = TypedValueGetDatum(tv);
+
+ switch (tv->typid)
+ {
+ case INT4OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(int48, value));
+
+ case BOOLOID:
+ {
+ int32 i = DatumGetInt32(DirectFunctionCall1(bool_int4, value));
+
+ PG_RETURN_INT64((int64) i);
+ }
+
+ case INT8OID:
+ PG_RETURN_DATUM(value);
+
+ case NUMERICOID:
+ PG_RETURN_DATUM(DirectFunctionCall1(numeric_int8, value));
+
+ case FLOAT4OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(ftoi8, value));
+
+ case FLOAT8OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(dtoi8, value));
+
+ case TEXTOID:
+ case BPCHAROID:
+ PG_RETURN_DATUM(parse_value(value, INT8OID));
+
+ default:
+ /* slower generic cast */
+ PG_RETURN_DATUM(generic_cast(value, tv->typid, INT8OID));
+ }
+}
+
+Datum
+typedvalue_to_int4(PG_FUNCTION_ARGS)
+{
+ TypedValue tv;
+ Datum value;
+
+ tv = (TypedValue) PG_GETARG_POINTER(0);
+ value = TypedValueGetDatum(tv);
+
+ switch (tv->typid)
+ {
+ case INT4OID:
+ PG_RETURN_DATUM(value);
+
+ case BOOLOID:
+ PG_RETURN_DATUM(DirectFunctionCall1(bool_int4, value));
+
+ case INT8OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(int84, value));
+
+ case NUMERICOID:
+ PG_RETURN_DATUM(DirectFunctionCall1(numeric_int4, value));
+
+ case FLOAT4OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(ftoi4, value));
+
+ case FLOAT8OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(dtoi4, value));
+
+ case TEXTOID:
+ case BPCHAROID:
+ PG_RETURN_DATUM(parse_value(value, INT4OID));
+
+ default:
+ /* slower generic cast */
+ PG_RETURN_DATUM(generic_cast(value, tv->typid, INT4OID));
+ }
+}
+
+Datum
+typedvalue_to_float8(PG_FUNCTION_ARGS)
+{
+ TypedValue tv;
+ Datum value;
+
+ tv = (TypedValue) PG_GETARG_POINTER(0);
+ value = TypedValueGetDatum(tv);
+
+ switch (tv->typid)
+ {
+ case INT4OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(i4tod, value));
+
+ case INT8OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(i8tod, value));
+
+ case NUMERICOID:
+ PG_RETURN_DATUM(DirectFunctionCall1(numeric_float8, value));
+
+ case FLOAT4OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(ftod, value));
+
+ case FLOAT8OID:
+ PG_RETURN_DATUM(value);
+
+ case TEXTOID:
+ case BPCHAROID:
+ PG_RETURN_DATUM(parse_value(value, FLOAT8OID));
+
+ default:
+ /* slower generic cast */
+ PG_RETURN_DATUM(generic_cast(value, tv->typid, FLOAT8OID));
+ }
+
+
+ PG_RETURN_NULL();
+}
+
+Datum
+typedvalue_to_float4(PG_FUNCTION_ARGS)
+{
+ TypedValue tv;
+ Datum value;
+
+ tv = (TypedValue) PG_GETARG_POINTER(0);
+ value = TypedValueGetDatum(tv);
+
+ switch (tv->typid)
+ {
+ case INT4OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(i4tof, value));
+
+ case INT8OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(i8tof, value));
+
+ case NUMERICOID:
+ PG_RETURN_DATUM(DirectFunctionCall1(numeric_float4, value));
+
+ case FLOAT4OID:
+ PG_RETURN_DATUM(value);
+
+ case FLOAT8OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(dtof, value));
+
+ case TEXTOID:
+ case BPCHAROID:
+ PG_RETURN_DATUM(parse_value(value, FLOAT4OID));
+
+ default:
+ /* slower generic cast */
+ PG_RETURN_DATUM(generic_cast(value, tv->typid, FLOAT4OID));
+ }
+}
+
+Datum
+typedvalue_to_date(PG_FUNCTION_ARGS)
+{
+ TypedValue tv;
+ Datum value;
+
+ tv = (TypedValue) PG_GETARG_POINTER(0);
+ value = TypedValueGetDatum(tv);
+
+ switch (tv->typid)
+ {
+ case DATEOID:
+ PG_RETURN_DATUM(value);
+
+ case TIMESTAMPOID:
+ PG_RETURN_DATUM(DirectFunctionCall1(timestamp_date, value));
+
+ case TIMESTAMPTZOID:
+ PG_RETURN_DATUM(DirectFunctionCall1(timestamptz_date, value));
+
+ case TEXTOID:
+ case BPCHAROID:
+ PG_RETURN_DATUM(parse_value(value, FLOAT4OID));
+
+ default:
+ /* slower generic cast */
+ PG_RETURN_DATUM(generic_cast(value, tv->typid, DATEOID));
+ }
+}
+
+Datum
+typedvalue_to_timestamp(PG_FUNCTION_ARGS)
+{
+ TypedValue tv;
+ Datum value;
+
+ tv = (TypedValue) PG_GETARG_POINTER(0);
+ value = TypedValueGetDatum(tv);
+
+ switch (tv->typid)
+ {
+ case DATEOID:
+ PG_RETURN_DATUM(DirectFunctionCall1(date_timestamp, value));
+
+ case TIMESTAMPOID:
+ PG_RETURN_DATUM(value);
+
+ case TIMESTAMPTZOID:
+ PG_RETURN_DATUM(DirectFunctionCall1(timestamptz_timestamp, value));
+
+ case TEXTOID:
+ case BPCHAROID:
+ PG_RETURN_DATUM(parse_value(value, FLOAT4OID));
+
+ default:
+ /* slower generic cast */
+ PG_RETURN_DATUM(generic_cast(value, tv->typid, TIMESTAMPOID));
+ }
+}
+
+Datum
+typedvalue_to_timestamptz(PG_FUNCTION_ARGS)
+{
+ TypedValue tv;
+ Datum value;
+
+ tv = (TypedValue) PG_GETARG_POINTER(0);
+ value = TypedValueGetDatum(tv);
+
+ switch (tv->typid)
+ {
+ case DATEOID:
+ PG_RETURN_DATUM(DirectFunctionCall1(date_timestamptz, value));
+
+ case TIMESTAMPOID:
+ PG_RETURN_DATUM(DirectFunctionCall1(timestamp_timestamptz, value));
+
+ case TIMESTAMPTZOID:
+ PG_RETURN_DATUM(value);
+
+ case TEXTOID:
+ case BPCHAROID:
+ PG_RETURN_DATUM(parse_value(value, FLOAT4OID));
+
+ default:
+ /* slower generic cast */
+ PG_RETURN_DATUM(generic_cast(value, tv->typid, TIMESTAMPTZOID));
+ }
+}
+
+Datum
+typedvalue_to_interval(PG_FUNCTION_ARGS)
+{
+ TypedValue tv;
+ Datum value;
+
+ tv = (TypedValue) PG_GETARG_POINTER(0);
+ value = TypedValueGetDatum(tv);
+
+ switch (tv->typid)
+ {
+ case INTERVALOID:
+ PG_RETURN_DATUM(value);
+
+ case TEXTOID:
+ case BPCHAROID:
+ PG_RETURN_DATUM(parse_value(value, BOOLOID));
+
+ default:
+ /* slower generic cast */
+ PG_RETURN_DATUM(generic_cast(value, tv->typid, INTERVALOID));
+ }
+}
+
+Datum
+typedvalue_to_text(PG_FUNCTION_ARGS)
+{
+ Oid typOutput;
+ bool isVarlena;
+ char *str;
+ TypedValue tv;
+ Datum value;
+ text *txt;
+
+ tv = (TypedValue) PG_GETARG_POINTER(0);
+
+ value = TypedValueGetDatum(tv);
+
+ if (tv->typid == TEXTOID || tv->typid == BPCHAROID)
+ {
+ PG_RETURN_DATUM(datumCopy(value,
+ tv->typbyval,
+ tv->typlen));
+ }
+
+ getTypeOutputInfo(tv->typid, &typOutput, &isVarlena);
+
+ value = TypedValueGetDatum(tv);
+ str = OidOutputFunctionCall(typOutput, value);
+ txt = cstring_to_text(str);
+
+ PG_RETURN_TEXT_P(txt);
+}
+
+Datum
+typedvalue_to_bpchar(PG_FUNCTION_ARGS)
+{
+ return typedvalue_to_text(fcinfo);
+}
+
+Datum
+typedvalue_to_bool(PG_FUNCTION_ARGS)
+{
+ TypedValue tv;
+ Datum value;
+
+ tv = (TypedValue) PG_GETARG_POINTER(0);
+ value = TypedValueGetDatum(tv);
+
+ switch (tv->typid)
+ {
+ case BOOLOID:
+ PG_RETURN_DATUM(value);
+
+ case INT4OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(int4_bool, value));
+
+ case INT8OID:
+ {
+ int64 i = DatumGetInt64(value);
+
+ PG_RETURN_DATUM(DirectFunctionCall1(int4_bool, Int32GetDatum((int32) i)));
+ }
+
+ case TEXTOID:
+ case BPCHAROID:
+ PG_RETURN_DATUM(parse_value(value, BOOLOID));
+
+ default:
+ /* slower generic cast */
+ PG_RETURN_DATUM(generic_cast(value, tv->typid, BOOLOID));
+ }
+}
+
+Datum
+numeric_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ NUMERICOID,
+ -1,
+ false));
+}
+
+Datum
+int8_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ INT8OID,
+ 8,
+ true));
+}
+
+Datum
+int4_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ INT4OID,
+ 4,
+ true));
+}
+
+Datum
+float8_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ FLOAT8OID,
+ 8,
+ true));
+}
+
+Datum
+float4_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ FLOAT4OID,
+ 4,
+ true));
+}
+
+
+Datum
+date_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ DATEOID,
+ 4,
+ true));
+}
+
+Datum
+timestamp_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ TIMESTAMPOID,
+ 8,
+ true));
+}
+
+Datum
+timestamptz_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ TIMESTAMPTZOID,
+ 8,
+ true));
+}
+
+Datum
+interval_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ INTERVALOID,
+ 16,
+ false));
+}
+
+Datum
+text_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ TEXTOID,
+ -1,
+ false));
+}
+
+Datum
+bpchar_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ BPCHAROID,
+ -1,
+ false));
+}
+
+Datum
+bool_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ BOOLOID,
+ 1,
+ true));
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index f0c8ae686d..e6bee14899 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -14,6 +14,9 @@
#include "postgres.h"
#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/lsyscache.h"
+#include "utils/typedvalue.h"
#include "windowapi.h"
/*
@@ -35,6 +38,20 @@ typedef struct
int64 remainder; /* (total rows) % (bucket num) */
} ntile_context;
+#define PROXY_CONTEXT_MAGIC 19730715
+
+typedef struct
+{
+ int magic;
+ Oid typid;
+ int16 typlen;
+ bool typbyval;
+ int allocsize;
+ bool isnull;
+ Datum value;
+ char data[FLEXIBLE_ARRAY_MEMBER];
+} proxy_context;
+
static bool rank_up(WindowObject winobj);
static Datum leadlag_common(FunctionCallInfo fcinfo,
bool forward, bool withoffset, bool withdefault);
@@ -472,3 +489,467 @@ window_nth_value(PG_FUNCTION_ARGS)
PG_RETURN_DATUM(result);
}
+
+/*
+ * High level access function. These functions are wrappers for windows API
+ * for PL languages based on usage WindowObjectProxy.
+ */
+Datum
+windowobject_get_current_position(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int64 pos;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ pos = WinGetCurrentPosition(winobj);
+
+ PG_RETURN_INT64(pos);
+}
+
+Datum
+windowobject_set_mark_position(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int64 pos;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ pos = PG_GETARG_INT64(1);
+
+ WinSetMarkPosition(winobj, pos);
+
+ PG_RETURN_VOID();
+}
+
+Datum
+windowobject_get_partition_rowcount(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int64 rc;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ rc = WinGetPartitionRowCount(winobj);
+
+ PG_RETURN_INT64(rc);
+}
+
+Datum
+windowobject_rows_are_peers(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int64 pos1,
+ pos2;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ pos1 = PG_GETARG_INT64(1);
+ pos2 = PG_GETARG_INT64(2);
+
+ PG_RETURN_BOOL(WinRowsArePeers(winobj, pos1, pos2));
+}
+
+#define SEEK_CURRENT_STR "seek_current"
+#define SEEK_HEAD_STR "seek_head"
+#define SEEK_TAIL_STR "seek_tail"
+
+#define STRLEN(s) (sizeof(s) - 1)
+
+static int
+get_seek_type(text *seektype)
+{
+ char *str;
+ int len;
+ int result;
+
+ str = VARDATA_ANY(seektype);
+ len = VARSIZE_ANY_EXHDR(seektype);
+
+ if (len == STRLEN(SEEK_CURRENT_STR) && strncmp(str, SEEK_CURRENT_STR, len) == 0)
+ result = WINDOW_SEEK_CURRENT;
+ else if (len == STRLEN(SEEK_HEAD_STR) && strncmp(str, SEEK_HEAD_STR, len) == 0)
+ result = WINDOW_SEEK_HEAD;
+ else if (len == STRLEN(SEEK_TAIL_STR) && strncmp(str, SEEK_TAIL_STR, len) == 0)
+ result = WINDOW_SEEK_TAIL;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("seek type value must be \"seek_current\", \"seek_head\" or \"seek_tail\"")));
+
+ return result;
+}
+
+static Oid
+wop_funcarg_info(WindowObjectProxy wop,
+ int argno,
+ int16 *typlen,
+ bool *typbyval)
+{
+ WindowObjectProxyMutable *mutable_data = wop->mutable_data;
+
+ if (argno != mutable_data->last_argno)
+ {
+ Oid argtypid = get_fn_expr_argtype(wop->fcinfo->flinfo, argno);
+
+ mutable_data->typid = getBaseType(argtypid);
+ get_typlenbyval(mutable_data->typid,
+ &mutable_data->typlen,
+ &mutable_data->typbyval);
+ mutable_data->last_argno = argno;
+ }
+
+ *typlen = mutable_data->typlen;
+ *typbyval = mutable_data->typbyval;
+
+ return mutable_data->typid;
+}
+
+Datum
+windowobject_get_func_arg_partition(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int argno;
+ int relpos;
+ int seektype;
+ bool set_mark;
+ Datum value;
+ bool isnull;
+ Oid typid;
+ int16 typlen;
+ bool typbyval;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ argno = PG_GETARG_INT32(1);
+
+ if (argno < 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("arg number less than one")));
+
+ argno -= 1;
+
+ relpos = PG_GETARG_INT32(2);
+ seektype = get_seek_type(PG_GETARG_TEXT_P(3));
+ set_mark = PG_GETARG_BOOL(4);
+
+ value = WinGetFuncArgInPartition(winobj,
+ argno,
+ relpos,
+ seektype,
+ set_mark,
+ &isnull,
+ &wop->mutable_data->isout);
+
+ if (isnull)
+ PG_RETURN_NULL();
+
+ typid = wop_funcarg_info(wop, argno, &typlen, &typbyval);
+
+ PG_RETURN_DATUM(makeTypedValue(value, typid, typlen, typbyval));
+}
+
+Datum
+windowobject_get_func_arg_frame(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int argno;
+ int relpos;
+ int seektype;
+ bool set_mark;
+ Datum value;
+ bool isnull;
+ Oid typid;
+ int16 typlen;
+ bool typbyval;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ argno = PG_GETARG_INT32(1);
+ relpos = PG_GETARG_INT32(2);
+ seektype = get_seek_type(PG_GETARG_TEXT_P(3));
+ set_mark = PG_GETARG_BOOL(4);
+
+ if (argno < 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("arg number less than one")));
+
+ argno -= 1;
+
+ value = WinGetFuncArgInFrame(winobj,
+ argno,
+ relpos,
+ seektype,
+ set_mark,
+ &isnull,
+ &wop->mutable_data->isout);
+
+ if (isnull)
+ PG_RETURN_NULL();
+
+ typid = wop_funcarg_info(wop, argno, &typlen, &typbyval);
+
+ PG_RETURN_DATUM(makeTypedValue(value, typid, typlen, typbyval));
+}
+
+Datum
+windowobject_get_func_arg_current(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int argno;
+ Datum value;
+ bool isnull;
+ Oid typid;
+ int16 typlen;
+ bool typbyval;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ winobj = wop->winobj;
+
+ Assert(WindowObjectIsValid(winobj));
+
+ argno = PG_GETARG_INT32(1);
+
+ if (argno < 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("arg number less than one")));
+
+ argno -= 1;
+
+ value = WinGetFuncArgCurrent(winobj, argno, &isnull);
+
+ wop->mutable_data->isout = false;
+
+ if (isnull)
+ PG_RETURN_NULL();
+
+ typid = wop_funcarg_info(wop, argno, &typlen, &typbyval);
+ PG_RETURN_DATUM(makeTypedValue(value, typid, typlen, typbyval));
+}
+
+static void
+copy_datum_to_partition_context(proxy_context *pcontext,
+ Datum value,
+ bool isnull)
+{
+ if (!isnull)
+ {
+ if (pcontext->typbyval)
+ pcontext->value = value;
+ else if (pcontext->typlen == -1)
+ {
+ struct varlena *s = (struct varlena *) DatumGetPointer(value);
+
+ memcpy(pcontext->data, s, VARSIZE_ANY(s));
+ pcontext->value = PointerGetDatum(pcontext->data);
+ }
+ else
+ {
+ memcpy(pcontext->data, DatumGetPointer(value), pcontext->typlen);
+ pcontext->value = PointerGetDatum(pcontext->data);
+ }
+
+ pcontext->isnull = false;
+ }
+ else
+ {
+ pcontext->value = (Datum) 0;
+ pcontext->isnull = true;
+ }
+}
+
+/*
+ * Returns estimated size of windowobject partition context
+ */
+static int
+estimate_partition_context_size(Datum value,
+ bool isnull,
+ int16 typlen,
+ int16 minsize,
+ int *realsize)
+{
+ if(typlen != -1)
+ {
+ if (typlen < sizeof(Datum))
+ {
+ *realsize = offsetof(proxy_context, data);
+
+ return *realsize;
+ }
+
+ if (typlen > 1024)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("size of value is greather than limit (1024 bytes)")));
+
+ *realsize = offsetof(proxy_context, data) + typlen;
+
+ return *realsize;
+ }
+ else
+ {
+ if (!isnull)
+ {
+ int size = VARSIZE_ANY_EXHDR(DatumGetPointer(value));
+
+ if (size > 1024)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("size of value is greather than limit (1024 bytes)")));
+
+ *realsize = size;
+
+ size += size / 3;
+
+ return offsetof(proxy_context, data)
+ + MAXALIGN(size > minsize ? size : minsize);
+ }
+ else
+ {
+ /* by default we allocate 30 bytes */
+ *realsize = 0;
+
+ return offsetof(proxy_context, data) + MAXALIGN(minsize);
+ }
+ }
+}
+
+#define VARLENA_MINSIZE 32
+
+static proxy_context *
+get_partition_context(FunctionCallInfo fcinfo, bool write_mode)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ Oid typid;
+ int16 typlen;
+ bool typbyval;
+ Datum value = (Datum) 0;
+ bool isnull = true;
+ int allocsize;
+ int minsize;
+ int realsize;
+ proxy_context *pcontext;
+
+ if (PG_ARGISNULL(0))
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("windowobject is NULL")));
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ if (PG_ARGISNULL(2))
+ minsize = VARLENA_MINSIZE;
+ else
+ minsize = PG_GETARG_INT32(2);
+
+ if (!PG_ARGISNULL(1))
+ {
+ value = PG_GETARG_DATUM(1);
+ isnull = false;
+ }
+
+ typid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ if (!OidIsValid(typid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot detect type of context value")));
+
+ typid = getBaseType(typid);
+ get_typlenbyval(typid, &typlen, &typbyval);
+
+ Assert(typlen != -2);
+
+ allocsize = estimate_partition_context_size(value,
+ isnull,
+ typlen,
+ minsize,
+ &realsize);
+
+ pcontext = (proxy_context *) WinGetPartitionLocalMemory(winobj, allocsize);
+
+ /* fresh pcontext has zeroed memory */
+ Assert(pcontext->magic == 0 || pcontext->magic == PROXY_CONTEXT_MAGIC);
+
+ if (pcontext->allocsize > 0)
+ {
+ if (realsize > pcontext->allocsize)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("the value cannot be saved to allocated buffer"),
+ errhint("Try to increase the minsize argument.")));
+
+ if (pcontext->typid != typid)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("partition context was initialized for different type")));
+
+ if (write_mode)
+ copy_datum_to_partition_context(pcontext, value, isnull);
+
+ }
+ else
+ {
+ pcontext->magic = PROXY_CONTEXT_MAGIC;
+ pcontext->typid = typid;
+ pcontext->typlen = typlen;
+ pcontext->typbyval = typbyval;
+ pcontext->allocsize = allocsize;
+
+ copy_datum_to_partition_context(pcontext, value, isnull);
+ }
+
+ return pcontext;
+}
+
+Datum
+windowobject_set_partition_context_value(PG_FUNCTION_ARGS)
+{
+ (void) get_partition_context(fcinfo, true);
+
+ PG_RETURN_VOID();
+}
+
+Datum
+windowobject_get_partition_context_value(PG_FUNCTION_ARGS)
+{
+ proxy_context *pcontext;
+
+ pcontext = get_partition_context(fcinfo, false);
+
+ if (pcontext->isnull)
+ PG_RETURN_NULL();
+
+ PG_RETURN_DATUM(pcontext->value);
+}
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index 5a58f50fbb..597de4daa9 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -530,4 +530,56 @@
{ castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
castcontext => 'e', castmethod => 'f' },
+# Allow explicit coercions between typedvalue and other types
+
+
+{ castsource => 'typedvalue', casttarget => 'numeric', castfunc => 'to_numeric(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'int8', castfunc => 'to_int8(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'int4', castfunc => 'to_int4(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'float8', castfunc => 'to_float8(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'float4', castfunc => 'to_float4(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'date', castfunc => 'to_date(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'timestamp', castfunc => 'to_timestamp(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'timestamptz', castfunc => 'to_timestamptz(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'interval', castfunc => 'to_interval(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'text', castfunc => 'to_text(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'varchar', castfunc => 'to_varchar(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'bool', castfunc => 'to_bool(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'numeric', casttarget => 'typedvalue', castfunc => 'to_typedvalue(numeric)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'int8', casttarget => 'typedvalue', castfunc => 'to_typedvalue(int8)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'int4', casttarget => 'typedvalue', castfunc => 'to_typedvalue(int4)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'float8', casttarget => 'typedvalue', castfunc => 'to_typedvalue(float8)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'float4', casttarget => 'typedvalue', castfunc => 'to_typedvalue(float4)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'date', casttarget => 'typedvalue', castfunc => 'to_typedvalue(date)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'timestamp', casttarget => 'typedvalue', castfunc => 'to_typedvalue(timestamp)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'timestamptz', casttarget => 'typedvalue', castfunc => 'to_typedvalue(timestamptz)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'interval', casttarget => 'typedvalue', castfunc => 'to_typedvalue(interval)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'text', casttarget => 'typedvalue', castfunc => 'to_typedvalue(text)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'varchar', casttarget => 'typedvalue', castfunc => 'to_typedvalue(varchar)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'bool', casttarget => 'typedvalue', castfunc => 'to_typedvalue(bool)',
+ castcontext => 'e', castmethod => 'f' },
+
]
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 27989971db..213d7c5440 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7130,6 +7130,18 @@
{ oid => '2305', descr => 'I/O',
proname => 'internal_out', prorettype => 'cstring', proargtypes => 'internal',
prosrc => 'internal_out' },
+{ oid => '9554', descr => 'I/O',
+ proname => 'windowobjectproxy_in', proisstrict => 'f', prorettype => 'windowobjectproxy',
+ proargtypes => 'cstring', prosrc => 'windowobjectproxy_in' },
+{ oid => '9555', descr => 'I/O',
+ proname => 'windowobjectproxy_out', prorettype => 'cstring', proargtypes => 'windowobjectproxy',
+ prosrc => 'windowobjectproxy_out' },
+{ oid => '9556', descr => 'I/O',
+ proname => 'typedvalue_in', proisstrict => 'f', prorettype => 'typedvalue',
+ proargtypes => 'cstring', prosrc => 'typedvalue_in' },
+{ oid => '9557', descr => 'I/O',
+ proname => 'typedvalue_out', prorettype => 'cstring', proargtypes => 'typedvalue',
+ prosrc => 'typedvalue_out' },
{ oid => '2312', descr => 'I/O',
proname => 'anyelement_in', prorettype => 'anyelement',
proargtypes => 'cstring', prosrc => 'anyelement_in' },
@@ -7219,6 +7231,80 @@
prorettype => 'cstring', proargtypes => 'anycompatiblerange',
prosrc => 'anycompatiblerange_out' },
+# typedvalue cast functions
+{ oid => '9567', descr => 'format typedvalue to numeric',
+ proname => 'to_numeric', provolatile => 's', prorettype => 'numeric',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_numeric' },
+{ oid => '9568', descr => 'format typedvalue to int8',
+ proname => 'to_int8', provolatile => 's', prorettype => 'int8',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_int8' },
+{ oid => '9569', descr => 'format typedvalue to int4',
+ proname => 'to_int4', provolatile => 's', prorettype => 'int4',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_int4' },
+{ oid => '9570', descr => 'format typedvalue to float8',
+ proname => 'to_float8', provolatile => 's', prorettype => 'float8',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_float8' },
+{ oid => '9571', descr => 'format typedvalue to float4',
+ proname => 'to_float4', provolatile => 's', prorettype => 'float4',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_float4' },
+{ oid => '9572', descr => 'format typedvalue to date',
+ proname => 'to_date', provolatile => 's', prorettype => 'date',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_date' },
+{ oid => '9573', descr => 'format typedvalue to timestamp',
+ proname => 'to_timestamp', provolatile => 's', prorettype => 'timestamp',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_timestamp' },
+{ oid => '9574', descr => 'format typedvalue to timestamptz',
+ proname => 'to_timestamptz', provolatile => 's', prorettype => 'timestamptz',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_timestamptz' },
+{ oid => '9575', descr => 'format typedvalue to interval',
+ proname => 'to_interval', provolatile => 's', prorettype => 'interval',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_interval' },
+{ oid => '9576', descr => 'format typedvalue to text',
+ proname => 'to_text', provolatile => 's', prorettype => 'text',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_text' },
+{ oid => '9577', descr => 'format typedvalue to varchar',
+ proname => 'to_varchar', provolatile => 's', prorettype => 'varchar',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_bpchar' },
+{ oid => '9578', descr => 'format numeric to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'numeric', prosrc => 'numeric_to_typedvalue' },
+{ oid => '9579', descr => 'format int8 to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'int8', prosrc => 'int8_to_typedvalue' },
+{ oid => '9580', descr => 'format int4 to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'int4', prosrc => 'int4_to_typedvalue' },
+{ oid => '9581', descr => 'format float8 to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'float8', prosrc => 'float8_to_typedvalue' },
+{ oid => '9582', descr => 'format float4 to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'float4', prosrc => 'float4_to_typedvalue' },
+{ oid => '9583', descr => 'format date to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'date', prosrc => 'date_to_typedvalue' },
+{ oid => '9584', descr => 'format timestamp to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'timestamp', prosrc => 'timestamp_to_typedvalue' },
+{ oid => '9585', descr => 'format timestamptz to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'timestamptz', prosrc => 'timestamptz_to_typedvalue' },
+{ oid => '9586', descr => 'format interval to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'interval', prosrc => 'interval_to_typedvalue' },
+{ oid => '9587', descr => 'format text to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'text', prosrc => 'text_to_typedvalue' },
+{ oid => '9588', descr => 'format varchar to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'varchar', prosrc => 'bpchar_to_typedvalue' },
+{ oid => '9589', descr => 'format bool to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'bool', prosrc => 'bool_to_typedvalue' },
+{ oid => '9590', descr => 'format typedvalue to bool',
+ proname => 'to_bool', provolatile => 's', prorettype => 'bool',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_bool' },
+
# tablesample method handlers
{ oid => '3313', descr => 'BERNOULLI tablesample method handler',
proname => 'bernoulli', provolatile => 'v', prorettype => 'tsm_handler',
@@ -9742,6 +9828,35 @@
{ oid => '3114', descr => 'fetch the Nth row value',
proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '9558', descr => 'get current position from window object',
+ proname => 'get_current_position', prokind => 'f', prorettype => 'int8',
+ proargtypes => 'windowobjectproxy', prosrc => 'windowobject_get_current_position' },
+{ oid => '9559', descr => 'set current position in window object',
+ proname => 'set_mark_position', prokind => 'f', prorettype => 'void',
+ proargtypes => 'windowobjectproxy int8', prosrc => 'windowobject_set_mark_position' },
+{ oid => '9560', descr => 'get partition row count',
+ proname => 'get_partition_rowcount', prokind => 'f', prorettype => 'int8',
+ proargtypes => 'windowobjectproxy', prosrc => 'windowobject_get_partition_rowcount' },
+{ oid => '9561', descr => 'returns true if two positions are peers',
+ proname => 'rows_are_peers', prokind => 'f', prorettype => 'bool',
+ proargtypes => 'windowobjectproxy int8 int8', prosrc => 'windowobject_rows_are_peers' },
+{ oid => '9562', descr => 'returns argument of window function against to partition',
+ proname => 'get_input_value_in_partition', prokind => 'f', prorettype => 'typedvalue',
+ proargtypes => 'windowobjectproxy int4 int4 text bool', prosrc => 'windowobject_get_func_arg_partition' },
+{ oid => '9563', descr => 'returns argument of window function against to frame',
+ proname => 'get_input_value_in_frame', prokind => 'f', prorettype => 'typedvalue',
+ proargtypes => 'windowobjectproxy int4 int4 text bool', prosrc => 'windowobject_get_func_arg_frame' },
+{ oid => '9564', descr => 'returns argument of window function against to current row',
+ proname => 'get_input_value_for_row', prokind => 'f', prorettype => 'typedvalue',
+ proargtypes => 'windowobjectproxy int4', prosrc => 'windowobject_get_func_arg_current' },
+{ oid => '9565', descr => 'returns a value stored in a partition context',
+ proname => 'get_partition_context_value', prokind => 'f', prorettype => 'anyelement',
+ proargtypes => 'windowobjectproxy anyelement int4',
+ prosrc => 'windowobject_get_partition_context_value', proisstrict => 'f' },
+{ oid => '9566', descr => 'store a value to partition context',
+ proname => 'set_partition_context_value', prokind => 'f', prorettype => 'void',
+ proargtypes => 'windowobjectproxy anyelement int4',
+ prosrc => 'windowobject_set_partition_context_value', proisstrict => 'f' },
# functions for range types
{ oid => '3832', descr => 'I/O',
diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat
index b2cec07416..5ad4ddfd59 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -560,6 +560,17 @@
typtype => 'p', typcategory => 'P', typinput => 'internal_in',
typoutput => 'internal_out', typreceive => '-', typsend => '-',
typalign => 'ALIGNOF_POINTER' },
+{ oid => '9552',
+ descr => 'pseudo-type representing an pointer to WindowObjectProxy structure',
+ typname => 'windowobjectproxy', typlen => '-1', typbyval => 'f',
+ typtype => 'p', typcategory => 'P', typinput => 'windowobjectproxy_in',
+ typoutput => 'windowobjectproxy_out', typreceive => '-', typsend => '-',
+ typalign => 'i', typstorage => 'p' },
+{ oid => '9553',
+ descr => 'type that can hold any scalar value with necessary meta',
+ typname => 'typedvalue', typtype => 'b', typlen => '-1', typbyval => 'f', typcategory => 'X',
+ typispreferred => 'f', typinput => 'typedvalue_in', typoutput => 'typedvalue_out',
+ typreceive => '-', typsend => '-', typalign => 'i', typstorage => 'x' },
{ oid => '2283', descr => 'pseudo-type representing a polymorphic base type',
typname => 'anyelement', typlen => '4', typbyval => 't', typtype => 'p',
typcategory => 'P', typinput => 'anyelement_in',
diff --git a/src/include/utils/typedvalue.h b/src/include/utils/typedvalue.h
new file mode 100644
index 0000000000..ce573b0374
--- /dev/null
+++ b/src/include/utils/typedvalue.h
@@ -0,0 +1,28 @@
+/*-------------------------------------------------------------------------
+ *
+ * typedvalue.h
+ * Declarations for typedvalue data type support.
+ *
+ * Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ *
+ * src/include/utils/typedvalue.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef __TYPEDVALUE_H__
+#define __TYPEDVALUE_H__
+
+typedef struct
+{
+ int32 vl_len; /* varlena header */
+ Oid typid;
+ bool typbyval;
+ int16 typlen;
+ char data[FLEXIBLE_ARRAY_MEMBER];
+} TypedValueData;
+
+typedef TypedValueData *TypedValue;
+
+extern Datum makeTypedValue(Datum value, Oid typid, int16 typlen, bool typbyval);
+
+#endif
\ No newline at end of file
diff --git a/src/include/windowapi.h b/src/include/windowapi.h
index e8c9fc54d8..a4b8504f78 100644
--- a/src/include/windowapi.h
+++ b/src/include/windowapi.h
@@ -36,6 +36,34 @@
/* this struct is private in nodeWindowAgg.c */
typedef struct WindowObjectData *WindowObject;
+typedef struct WindowObjectProxyMutable
+{
+ /* true when request on winfuncarg doesn't return data */
+ bool isout;
+
+ /* cache for type related data of Window arguments */
+ int last_argno;
+ Oid typid;
+ int16 typlen;
+ bool typbyval;
+} WindowObjectProxyMutable;
+
+/*
+ * This type is used as proxy between PL variants of WinFuncArg
+ * functions and PL environment. The variables of windowobjectproxy
+ * type can be copied, so mutable content should be elsewhere.
+ */
+typedef struct WindowObjectProxyData
+{
+ int32 vl_len; /* varlena header */
+
+ WindowObject winobj;
+ FunctionCallInfo fcinfo;
+ WindowObjectProxyMutable *mutable_data;
+} WindowObjectProxyData;
+
+typedef WindowObjectProxyData *WindowObjectProxy;
+
#define PG_WINDOW_OBJECT() ((WindowObject) fcinfo->context)
#define WindowObjectIsValid(winobj) \
diff --git a/src/pl/plpgsql/src/Makefile b/src/pl/plpgsql/src/Makefile
index 193df8a010..ba6edfd42e 100644
--- a/src/pl/plpgsql/src/Makefile
+++ b/src/pl/plpgsql/src/Makefile
@@ -34,7 +34,7 @@ REGRESS_OPTS = --dbname=$(PL_TESTDB)
REGRESS = plpgsql_call plpgsql_control plpgsql_copy plpgsql_domain \
plpgsql_record plpgsql_cache plpgsql_simple plpgsql_transaction \
- plpgsql_trap plpgsql_trigger plpgsql_varprops
+ plpgsql_trap plpgsql_trigger plpgsql_varprops plpgsql_window
# where to find gen_keywordlist.pl and subsidiary files
TOOLSDIR = $(top_srcdir)/src/tools
diff --git a/src/pl/plpgsql/src/expected/plpgsql_window.out b/src/pl/plpgsql/src/expected/plpgsql_window.out
new file mode 100644
index 0000000000..b9cb015ecc
--- /dev/null
+++ b/src/pl/plpgsql/src/expected/plpgsql_window.out
@@ -0,0 +1,228 @@
+create or replace function pl_row_number()
+returns bigint as $$
+declare pos int8;
+begin
+ pos := get_current_position(windowobject);
+ pos := pos + 1;
+ perform set_mark_position(windowobject, pos);
+ return pos;
+end
+$$
+language plpgsql window;
+select pl_row_number() over (), v from (values(10),(20),(30)) v(v);
+ pl_row_number | v
+---------------+----
+ 1 | 10
+ 2 | 20
+ 3 | 30
+(3 rows)
+
+create or replace function pl_round_value(numeric)
+returns int as $$
+declare
+ num numeric;
+begin
+ num := get_input_value_for_row(windowobject, 1);
+ return round(num);
+end
+$$ language plpgsql window;
+select pl_round_value(v) over(order by v desc) from generate_series(0.1, 1.0, 0.1) g(v);
+ pl_round_value
+----------------
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+ 0
+ 0
+ 0
+ 0
+(10 rows)
+
+select pl_round_value(v + 1) over(order by v desc) from generate_series(0.1, 1.0, 0.1) g(v);
+ pl_round_value
+----------------
+ 2
+ 2
+ 2
+ 2
+ 2
+ 2
+ 1
+ 1
+ 1
+ 1
+(10 rows)
+
+create table test_table(v numeric);
+insert into test_table values(1),(3),(6),(6),(8),(7),(6),(5),(4);
+create or replace function pl_lag(numeric)
+returns numeric as $$
+declare
+ v numeric;
+begin
+ v := get_input_value_in_partition(windowobject, 1, -1, 'seek_current', false);
+ return v;
+end;
+$$ language plpgsql window;
+select pl_lag(v) over (), lag(v) over () from test_table;
+ pl_lag | lag
+--------+-----
+ |
+ 1 | 1
+ 3 | 3
+ 6 | 6
+ 6 | 6
+ 8 | 8
+ 7 | 7
+ 6 | 6
+ 5 | 5
+(9 rows)
+
+drop table test_table;
+create table test_table(v integer);
+insert into test_table values(1),(3),(6),(6),(8),(7),(6),(5),(4);
+select pl_lag(v) over (), lag(v) over () from test_table;
+ pl_lag | lag
+--------+-----
+ |
+ 1 | 1
+ 3 | 3
+ 6 | 6
+ 6 | 6
+ 8 | 8
+ 7 | 7
+ 6 | 6
+ 5 | 5
+(9 rows)
+
+create or replace function pl_moving_avg(numeric)
+returns numeric as $$
+declare
+ s numeric default 0.0;
+ v numeric;
+ c numeric default 0.0;
+begin
+ -- look before
+ v := get_input_value_in_partition(windowobject, 1, -1, 'seek_current', false);
+ if v is not null then
+ s := s + v;
+ c := c + 1.0;
+ end if;
+
+ -- look after
+ v := get_input_value_in_partition(windowobject, 1, 0, 'seek_current', false);
+ if v is not null then
+ s := s + v;
+ c := c + 1.0;
+ end if;
+
+ v := get_input_value_in_partition(windowobject, 1, 1, 'seek_current', false);
+ if v is not null then
+ s := s + v;
+ c := c + 1.0;
+ end if;
+
+ return trim_scale(s / c);
+end
+$$ language plpgsql window;
+select pl_moving_avg(v) over (), v from test_table;
+ pl_moving_avg | v
+--------------------+---
+ 2 | 1
+ 3.3333333333333333 | 3
+ 5 | 6
+ 6.6666666666666667 | 6
+ 7 | 8
+ 7 | 7
+ 6 | 6
+ 5 | 5
+ 4.5 | 4
+(9 rows)
+
+create or replace function pl_lag_polymorphic(anyelement)
+returns anyelement as $$
+declare
+ v $0%type;
+begin
+ v := get_input_value_in_partition(windowobject, 1, -1, 'seek_current', false);
+ return v;
+end;
+$$ language plpgsql window;
+select pl_lag_polymorphic(v) over (), lag(v) over () from test_table;
+ pl_lag_polymorphic | lag
+--------------------+-----
+ |
+ 1 | 1
+ 3 | 3
+ 6 | 6
+ 6 | 6
+ 8 | 8
+ 7 | 7
+ 6 | 6
+ 5 | 5
+(9 rows)
+
+create or replace function pl_pcontext_test(numeric)
+returns numeric as $$
+declare
+ n numeric;
+ v numeric;
+begin
+ n := get_partition_context_value(windowobject, null::numeric);
+ v := get_input_value_for_row(windowobject, 1);
+ perform set_partition_context_value(windowobject, v);
+
+ return n;
+end
+$$
+language plpgsql window;
+select v, pl_pcontext_test(v) over () from generate_series(0.1, 1.0, 0.1) g(v);
+ v | pl_pcontext_test
+-----+------------------
+ 0.1 |
+ 0.2 | 0.1
+ 0.3 | 0.2
+ 0.4 | 0.3
+ 0.5 | 0.4
+ 0.6 | 0.5
+ 0.7 | 0.6
+ 0.8 | 0.7
+ 0.9 | 0.8
+ 1.0 | 0.9
+(10 rows)
+
+create table test_missing_values(id int, v integer);
+insert into test_missing_values values(1,10),(2,11),(3,12),(4,null),(5,null),(6,15),(7,16);
+create or replace function pl_pcontext_test(numeric)
+returns numeric as $$
+declare
+ n numeric;
+ v numeric;
+begin
+ v := get_input_value_for_row(windowobject, 1);
+
+ if v is null then
+ v := get_partition_context_value(windowobject, null::numeric);
+ else
+ perform set_partition_context_value(windowobject, v);
+ end if;
+
+ return v;
+end
+$$
+language plpgsql window;
+select id, v, pl_pcontext_test(v) over (order by id) from test_missing_values;
+ id | v | pl_pcontext_test
+----+----+------------------
+ 1 | 10 | 10
+ 2 | 11 | 11
+ 3 | 12 | 12
+ 4 | | 12
+ 5 | | 12
+ 6 | 15 | 15
+ 7 | 16 | 16
+(7 rows)
+
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index e7f4a5f291..ee4bbb560e 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -582,6 +582,41 @@ do_compile(FunctionCallInfo fcinfo,
true);
}
+ if (function->fn_prokind == PROKIND_WINDOW)
+ {
+ PLpgSQL_type *dtype;
+ PLpgSQL_var *var;
+
+ /*
+ * Add the promise variable windowobject with windowobjectproxy type
+ *
+ * Pseudotypes are disallowed for custom variables. It is checked
+ * in plpgsql_build_variable, so instead call this function, build
+ * promise variable here.
+ */
+
+ dtype = plpgsql_build_datatype(WINDOWOBJECTPROXYOID,
+ -1,
+ function->fn_input_collation,
+ NULL);
+
+ /* this should be pseudotype */
+ Assert(dtype->ttype == PLPGSQL_TTYPE_PSEUDO);
+
+ var = palloc0(sizeof(PLpgSQL_var));
+
+ var->dtype = PLPGSQL_DTYPE_PROMISE;
+ var->promise = PLPGSQL_PROMISE_WINDOWOBJECT;
+
+ var->refname = pstrdup("windowobject");
+ var->datatype = dtype;
+
+ plpgsql_adddatum((PLpgSQL_datum *) var);
+ plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR,
+ var->dno,
+ var->refname);
+ }
+
ReleaseSysCache(typeTup);
break;
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index d4a3d58daa..e2af053fda 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -593,6 +593,39 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo,
*/
exec_set_found(&estate, false);
+ /*
+ * Initialize promise winobject
+ */
+ if (func->fn_prokind == PROKIND_WINDOW)
+ {
+ /* fcinfo is available in this function too */
+ WindowObjectProxy wop;
+ WindowObjectProxyMutable *mutable_data;
+ MemoryContext oldcontext;
+
+ oldcontext = MemoryContextSwitchTo(estate.datum_context);
+
+ wop = palloc(sizeof(WindowObjectProxyData));
+ SET_VARSIZE(wop, sizeof(WindowObjectProxyData));
+
+ wop->winobj = PG_WINDOW_OBJECT();
+
+ Assert(WindowObjectIsValid(wop->winobj));
+
+ mutable_data = palloc0(sizeof(WindowObjectProxyMutable));
+ mutable_data->isout = false;
+ mutable_data->last_argno = -1;
+
+ wop->mutable_data = mutable_data;
+ wop->fcinfo = fcinfo;
+
+ MemoryContextSwitchTo(oldcontext);
+
+ estate.winobjproxy = wop;
+ }
+ else
+ estate.winobjproxy = NULL;
+
/*
* Let the instrumentation plugin peek at this function
*/
@@ -915,6 +948,11 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
plpgsql_estate_setup(&estate, func, NULL, NULL, NULL);
estate.trigdata = trigdata;
+ /*
+ * Trigger function cannot be WINDOW function
+ */
+ estate.winobjproxy = NULL;
+
/*
* Setup error traceback support for ereport()
*/
@@ -1293,11 +1331,13 @@ copy_plpgsql_datums(PLpgSQL_execstate *estate,
PLpgSQL_datum *indatum = indatums[i];
PLpgSQL_datum *outdatum;
+
/* This must agree with plpgsql_finish_datums on what is copiable */
switch (indatum->dtype)
{
case PLPGSQL_DTYPE_VAR:
case PLPGSQL_DTYPE_PROMISE:
+
outdatum = (PLpgSQL_datum *) ws_next;
memcpy(outdatum, indatum, sizeof(PLpgSQL_var));
ws_next += MAXALIGN(sizeof(PLpgSQL_var));
@@ -1486,6 +1526,17 @@ plpgsql_fulfill_promise(PLpgSQL_execstate *estate,
assign_text_var(estate, var, GetCommandTagName(estate->evtrigdata->tag));
break;
+ case PLPGSQL_PROMISE_WINDOWOBJECT:
+ if (!estate->winobjproxy)
+ elog(ERROR, "windowobject promise is not in a window function");
+
+ assign_simple_var(estate,
+ var,
+ PointerGetDatum(estate->winobjproxy),
+ false,
+ false);
+ break;
+
default:
elog(ERROR, "unrecognized promise type: %d", var->promise);
}
@@ -2475,6 +2526,17 @@ exec_stmt_getdiag(PLpgSQL_execstate *estate, PLpgSQL_stmt_getdiag *stmt)
}
break;
+ case PLPGSQL_GETDIAG_VALUE_IS_OUT:
+ {
+ if (!estate->winobjproxy)
+ elog(ERROR, "function is not a window function");
+
+ exec_assign_value(estate, var,
+ BoolGetDatum(estate->winobjproxy->mutable_data->isout),
+ false, BOOLOID, -1);
+ }
+ break;
+
default:
elog(ERROR, "unrecognized diagnostic item kind: %d",
diag_item->kind);
diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c
index ee60ced583..992763c735 100644
--- a/src/pl/plpgsql/src/pl_funcs.c
+++ b/src/pl/plpgsql/src/pl_funcs.c
@@ -321,6 +321,8 @@ plpgsql_getdiag_kindname(PLpgSQL_getdiag_kind kind)
return "CONSTRAINT_NAME";
case PLPGSQL_GETDIAG_DATATYPE_NAME:
return "PG_DATATYPE_NAME";
+ case PLPGSQL_GETDIAG_VALUE_IS_OUT:
+ return "PG_VALUE_IS_OUT";
case PLPGSQL_GETDIAG_MESSAGE_TEXT:
return "MESSAGE_TEXT";
case PLPGSQL_GETDIAG_TABLE_NAME:
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 5a7e1a4444..823bbd288b 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -325,6 +325,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
%token <keyword> K_PG_EXCEPTION_CONTEXT
%token <keyword> K_PG_EXCEPTION_DETAIL
%token <keyword> K_PG_EXCEPTION_HINT
+%token <keyword> K_PG_VALUE_IS_OUT
%token <keyword> K_PRINT_STRICT_PARAMS
%token <keyword> K_PRIOR
%token <keyword> K_QUERY
@@ -1081,6 +1082,9 @@ getdiag_item :
else if (tok_is_keyword(tok, &yylval,
K_PG_EXCEPTION_CONTEXT, "pg_exception_context"))
$$ = PLPGSQL_GETDIAG_ERROR_CONTEXT;
+ else if (tok_is_keyword(tok, &yylval,
+ K_PG_VALUE_IS_OUT, "pg_value_is_out"))
+ $$ = PLPGSQL_GETDIAG_VALUE_IS_OUT;
else if (tok_is_keyword(tok, &yylval,
K_COLUMN_NAME, "column_name"))
$$ = PLPGSQL_GETDIAG_COLUMN_NAME;
diff --git a/src/pl/plpgsql/src/pl_unreserved_kwlist.h b/src/pl/plpgsql/src/pl_unreserved_kwlist.h
index 99b3cf7d8a..d27b7dfb85 100644
--- a/src/pl/plpgsql/src/pl_unreserved_kwlist.h
+++ b/src/pl/plpgsql/src/pl_unreserved_kwlist.h
@@ -84,6 +84,7 @@ PG_KEYWORD("pg_datatype_name", K_PG_DATATYPE_NAME)
PG_KEYWORD("pg_exception_context", K_PG_EXCEPTION_CONTEXT)
PG_KEYWORD("pg_exception_detail", K_PG_EXCEPTION_DETAIL)
PG_KEYWORD("pg_exception_hint", K_PG_EXCEPTION_HINT)
+PG_KEYWORD("pg_value_is_out", K_PG_VALUE_IS_OUT)
PG_KEYWORD("print_strict_params", K_PRINT_STRICT_PARAMS)
PG_KEYWORD("prior", K_PRIOR)
PG_KEYWORD("query", K_QUERY)
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 0c3d30fb13..4c1ed7cbf7 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -23,6 +23,8 @@
#include "utils/expandedrecord.h"
#include "utils/typcache.h"
+#include "windowapi.h"
+
/**********************************************************************
* Definitions
@@ -84,7 +86,8 @@ typedef enum PLpgSQL_promise_type
PLPGSQL_PROMISE_TG_NARGS,
PLPGSQL_PROMISE_TG_ARGV,
PLPGSQL_PROMISE_TG_EVENT,
- PLPGSQL_PROMISE_TG_TAG
+ PLPGSQL_PROMISE_TG_TAG,
+ PLPGSQL_PROMISE_WINDOWOBJECT
} PLpgSQL_promise_type;
/*
@@ -159,7 +162,8 @@ typedef enum PLpgSQL_getdiag_kind
PLPGSQL_GETDIAG_DATATYPE_NAME,
PLPGSQL_GETDIAG_MESSAGE_TEXT,
PLPGSQL_GETDIAG_TABLE_NAME,
- PLPGSQL_GETDIAG_SCHEMA_NAME
+ PLPGSQL_GETDIAG_SCHEMA_NAME,
+ PLPGSQL_GETDIAG_VALUE_IS_OUT
} PLpgSQL_getdiag_kind;
/*
@@ -612,6 +616,17 @@ typedef struct PLpgSQL_stmt_getdiag
List *diag_items; /* List of PLpgSQL_diag_item */
} PLpgSQL_stmt_getdiag;
+/*
+ * GET PG_WINDOW_CONTEXT statement
+ */
+typedef struct PLpgSQL_stmt_getwincxt
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ List *items;
+} PLpgSQL_stmt_getwincxt;
+
/*
* IF statement
*/
@@ -1049,6 +1064,8 @@ typedef struct PLpgSQL_execstate
TriggerData *trigdata; /* if regular trigger, data about firing */
EventTriggerData *evtrigdata; /* if event trigger, data about firing */
+ WindowObjectProxy winobjproxy; /* for window function we need proxy
+ * object between PL and WinFucArg funcions */
Datum retval;
bool retisnull;
Oid rettype; /* type of current retval */
diff --git a/src/pl/plpgsql/src/sql/plpgsql_window.sql b/src/pl/plpgsql/src/sql/plpgsql_window.sql
new file mode 100644
index 0000000000..6a3cab39a2
--- /dev/null
+++ b/src/pl/plpgsql/src/sql/plpgsql_window.sql
@@ -0,0 +1,135 @@
+create or replace function pl_row_number()
+returns bigint as $$
+declare pos int8;
+begin
+ pos := get_current_position(windowobject);
+ pos := pos + 1;
+ perform set_mark_position(windowobject, pos);
+ return pos;
+end
+$$
+language plpgsql window;
+
+select pl_row_number() over (), v from (values(10),(20),(30)) v(v);
+
+create or replace function pl_round_value(numeric)
+returns int as $$
+declare
+ num numeric;
+begin
+ num := get_input_value_for_row(windowobject, 1);
+ return round(num);
+end
+$$ language plpgsql window;
+
+select pl_round_value(v) over(order by v desc) from generate_series(0.1, 1.0, 0.1) g(v);
+
+select pl_round_value(v + 1) over(order by v desc) from generate_series(0.1, 1.0, 0.1) g(v);
+
+create table test_table(v numeric);
+insert into test_table values(1),(3),(6),(6),(8),(7),(6),(5),(4);
+
+create or replace function pl_lag(numeric)
+returns numeric as $$
+declare
+ v numeric;
+begin
+ v := get_input_value_in_partition(windowobject, 1, -1, 'seek_current', false);
+ return v;
+end;
+$$ language plpgsql window;
+
+select pl_lag(v) over (), lag(v) over () from test_table;
+
+drop table test_table;
+
+create table test_table(v integer);
+insert into test_table values(1),(3),(6),(6),(8),(7),(6),(5),(4);
+
+select pl_lag(v) over (), lag(v) over () from test_table;
+
+create or replace function pl_moving_avg(numeric)
+returns numeric as $$
+declare
+ s numeric default 0.0;
+ v numeric;
+ c numeric default 0.0;
+begin
+ -- look before
+ v := get_input_value_in_partition(windowobject, 1, -1, 'seek_current', false);
+ if v is not null then
+ s := s + v;
+ c := c + 1.0;
+ end if;
+
+ -- look after
+ v := get_input_value_in_partition(windowobject, 1, 0, 'seek_current', false);
+ if v is not null then
+ s := s + v;
+ c := c + 1.0;
+ end if;
+
+ v := get_input_value_in_partition(windowobject, 1, 1, 'seek_current', false);
+ if v is not null then
+ s := s + v;
+ c := c + 1.0;
+ end if;
+
+ return trim_scale(s / c);
+end
+$$ language plpgsql window;
+
+select pl_moving_avg(v) over (), v from test_table;
+
+create or replace function pl_lag_polymorphic(anyelement)
+returns anyelement as $$
+declare
+ v $0%type;
+begin
+ v := get_input_value_in_partition(windowobject, 1, -1, 'seek_current', false);
+ return v;
+end;
+$$ language plpgsql window;
+
+select pl_lag_polymorphic(v) over (), lag(v) over () from test_table;
+
+create or replace function pl_pcontext_test(numeric)
+returns numeric as $$
+declare
+ n numeric;
+ v numeric;
+begin
+ n := get_partition_context_value(windowobject, null::numeric);
+ v := get_input_value_for_row(windowobject, 1);
+ perform set_partition_context_value(windowobject, v);
+
+ return n;
+end
+$$
+language plpgsql window;
+
+select v, pl_pcontext_test(v) over () from generate_series(0.1, 1.0, 0.1) g(v);
+
+create table test_missing_values(id int, v integer);
+insert into test_missing_values values(1,10),(2,11),(3,12),(4,null),(5,null),(6,15),(7,16);
+
+create or replace function pl_pcontext_test(numeric)
+returns numeric as $$
+declare
+ n numeric;
+ v numeric;
+begin
+ v := get_input_value_for_row(windowobject, 1);
+
+ if v is null then
+ v := get_partition_context_value(windowobject, null::numeric);
+ else
+ perform set_partition_context_value(windowobject, v);
+ end if;
+
+ return v;
+end
+$$
+language plpgsql window;
+
+select id, v, pl_pcontext_test(v) over (order by id) from test_missing_values;
st 26. 8. 2020 v 17:06 odesílatel Pavel Stehule <pavel.stehule@gmail.com>
napsal:
Hi
I simplified access to results of winfuncargs functions by proxy type
"typedvalue". This type can hold any Datum value, and allows fast cast to
basic buildin types or it can use (slower) generic cast functions. It is
used in cooperation with a plpgsql assign statement that can choose the
correct cast implicitly. When the winfuncarg function returns a value of
the same type, that is expected by the variable on the left side of the
assign statement, then (for basic types), the value is just copied without
casts. With this proxy type is not necessary to have special statement for
assigning returned value from winfuncargs functions, so source code of
window function in plpgsql looks intuitive to me.Example - implementation of "lag" function in plpgsql
create or replace function pl_lag(numeric)
returns numeric as $$
declare v numeric;
begin
v := get_input_value_in_partition(windowobject, 1, -1, 'seek_current',
false);
return v;
end;
$$ language plpgsql window;I think this code is usable, and I assign this patch to commitfest.
Regards
Pavel
fix regress tests and some doc
Attachments:
plpgsql-window-function-20200828.patchtext/x-patch; charset=US-ASCII; name=plpgsql-window-function-20200828.patchDownload
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index 815912666d..5b4d5bbac4 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -4568,6 +4568,99 @@ CREATE EVENT TRIGGER snitch ON ddl_command_start EXECUTE FUNCTION snitch();
</sect1>
+ <sect1 id="plpgsql-window">
+ <title>Window Functions</title>
+
+ <indexterm zone="plpgsql-window">
+ <primary>window</primary>
+ <secondary>in PL/pgSQL</secondary>
+ </indexterm>
+
+ <para>
+ <application>PL/pgSQL</application> can be used to define window
+ functions. A window function is created with the <command>CREATE FUNCTION</command>
+ command with clause <literal>WINDOW</literal>. The specific feature of
+ this functions is a possibility to two special storages with
+ sorted values of window function arguments and store with stored
+ one value of any type for currently processed partition (of window
+ function).
+ </para>
+
+ <para>
+ Access to both storages is done with special internal variable
+ <varname>WINDOWOBJECT</varname>. This variable is declared implicitly,
+ and it is available only in window functions.
+
+<programlisting>
+CREATE OR REPLACE FUNCTION plpgsql_rownum() RETURNS int8
+LANGUAGE plpgsql WINDOW
+AS $$
+DECLARE pos int8
+BEGIN
+ pos := get_current_position(WINDOWOBJECT);
+ pos := pos + 1;
+ PERFORM set_mark_position(WINDOWOBJECT, pos);
+RETURN pos;
+$$;
+
+SELECT plpgsql_rownum() OVER (), * FROM tab;
+</programlisting>
+ </para>
+
+ <para>
+ The arguments of window function cannot be accessed directly. The special
+ functions should be used. With these functions we can choose a scope of
+ buffered arguments, we can choose a wanted position against first, current, or
+ last row. The implementation of <function>lag</function> can looks like
+ (the window functions in plpgsql can use polymorphic types too):
+
+<programlisting>
+CREATE OR REPLACE FUNCTION plpgsql_lag(anyelement) RETURNS anyelement
+LANGUAGE plpgsql WINDOW
+AS $$
+BEGIN
+ RETURN
+ get_input_value_in_partition(WINDOWOBJECT,
+ 1, -1,
+ 'seek_current',
+ false);
+END;
+$$;
+
+SELECT v, plpgsql_lag(v) FROM generate_series(1, 10) g(v);
+</programlisting>
+
+ </para>
+
+ <para>
+ Second buffer that can be used in window function is a buffer for one value
+ assigned to partition. The content of this buffer can be read by function
+ <function>get_partition_context_value</function> or modified by function
+ <function>set_partition_context_value</function>. Next function replaces
+ missing values by previous non <literal>NULL</literal> value:
+
+<programlisting>
+CREATE OR REPLACE FUNCTION plpgsql_replace_missing(numeric) RETURNS numeric
+LANGUAGE plpgsql WINDOW
+AS $$
+DECLATE
+ v numeric;
+BEGIN
+ v := get_input_value_for_row(WINDOWOBJECT, 1);
+ IF v IS NULL THEN
+ v := get_partition_context_value(WINDOWOBJECT, NULL::numeric);
+ ELSE
+ PERFORM set_partition_context_value(WINDOWOBJECT, v);
+ END IF;
+ RETURN v;
+END;
+$$;
+</programlisting>
+
+ </para>
+
+ </sect1>
+
<sect1 id="plpgsql-implementation">
<title><application>PL/pgSQL</application> under the Hood</title>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index a2d61302f9..f926f2e386 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1421,6 +1421,18 @@ LANGUAGE internal
STRICT IMMUTABLE PARALLEL SAFE
AS 'unicode_is_normalized';
+CREATE OR REPLACE FUNCTION
+ get_partition_context_value(windowobjectproxy, anyelement, int4 DEFAULT NULL)
+ RETURNS anyelement
+LANGUAGE internal
+AS 'windowobject_get_partition_context_value';
+
+CREATE OR REPLACE FUNCTION
+ set_partition_context_value(windowobjectproxy, anyelement, int4 DEFAULT NULL)
+ RETURNS void
+LANGUAGE internal
+AS 'windowobject_set_partition_context_value';
+
--
-- The default permissions for functions mean that anyone can execute them.
-- A number of functions shouldn't be executable by just anyone, but rather
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 54d5c37947..84da7222d9 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -102,6 +102,7 @@ OBJS = \
tsvector.o \
tsvector_op.o \
tsvector_parser.o \
+ typedvalue.o \
uuid.o \
varbit.o \
varchar.o \
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index 3d6b2f9093..ebb2a16572 100644
--- a/src/backend/utils/adt/pseudotypes.c
+++ b/src/backend/utils/adt/pseudotypes.c
@@ -334,6 +334,17 @@ pg_node_tree_send(PG_FUNCTION_ARGS)
PSEUDOTYPE_DUMMY_IO_FUNCS(pg_ddl_command);
PSEUDOTYPE_DUMMY_BINARY_IO_FUNCS(pg_ddl_command);
+/*
+ * windowobjectproxy
+ *
+ * This type is pointer to WindowObjectProxyData. It is communication
+ * mechanism between PL environment and WinFuncArgs functions. Due
+ * performance reason I prefer using indirect result processing against
+ * using function returning polymorphic composite value. The indirect
+ * mechanism is implemented with proxy object represented by type
+ * WindowObjectProxyData.
+ */
+PSEUDOTYPE_DUMMY_IO_FUNCS(windowobjectproxy);
/*
* Dummy I/O functions for various other pseudotypes.
diff --git a/src/backend/utils/adt/typedvalue.c b/src/backend/utils/adt/typedvalue.c
new file mode 100644
index 0000000000..d2d5dc34c0
--- /dev/null
+++ b/src/backend/utils/adt/typedvalue.c
@@ -0,0 +1,630 @@
+/*-------------------------------------------------------------------------
+ *
+ * typedvalue.c
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/typedvalue.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "catalog/pg_type.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/lsyscache.h"
+#include "utils/numeric.h"
+#include "utils/typedvalue.h"
+#include "fmgr.h"
+
+
+static Datum
+TypedValueGetDatum(TypedValue tv)
+{
+ if (tv->typbyval)
+ return *((Datum *) tv->data);
+ else
+ return PointerGetDatum(tv->data);
+}
+
+Datum
+typedvalue_in(PG_FUNCTION_ARGS)
+{
+ char *str = PG_GETARG_CSTRING(0);
+ int len;
+ Size size;
+ TypedValue tv;
+ text *txt;
+
+ len = strlen(str);
+
+ size = MAXALIGN(offsetof(TypedValueData, data) + len + VARHDRSZ);
+
+ tv = (TypedValue) palloc(size);
+ SET_VARSIZE(tv, size);
+
+ txt = (text *) tv->data;
+
+ SET_VARSIZE(txt, VARHDRSZ + len);
+ memcpy(VARDATA(txt), str, len);
+
+ tv->typid = TEXTOID;
+ tv->typbyval = false;
+ tv->typlen = -1;
+
+ PG_RETURN_POINTER(tv);
+}
+
+Datum
+typedvalue_out(PG_FUNCTION_ARGS)
+{
+ Oid typOutput;
+ bool isVarlena;
+ char *str;
+ TypedValue tv;
+ Datum value;
+
+ tv = (TypedValue) PG_GETARG_POINTER(0);
+
+ getTypeOutputInfo(tv->typid, &typOutput, &isVarlena);
+
+ value = TypedValueGetDatum(tv);
+
+ str = OidOutputFunctionCall(typOutput, value);
+
+ PG_RETURN_CSTRING(str);
+}
+
+Datum
+makeTypedValue(Datum value, Oid typid, int16 typlen, bool typbyval)
+{
+ TypedValue tv;
+ Size size;
+ Size copy_bytes = 0;
+
+ if (typbyval)
+ size = MAXALIGN(offsetof(TypedValueData, data) + sizeof(Datum));
+ else
+ {
+ if (typlen != -1)
+ {
+ size = MAXALIGN(offsetof(TypedValueData, data) + typlen);
+ copy_bytes = typlen;
+ }
+ else
+ {
+ copy_bytes = VARSIZE_ANY((struct varlena *) DatumGetPointer(value));
+ size = MAXALIGN(offsetof(TypedValueData, data) + copy_bytes);
+ }
+ }
+
+ tv = (TypedValue) palloc(size);
+
+ SET_VARSIZE(tv, size);
+
+ tv->typid = typid;
+ tv->typlen = typlen;
+ tv->typbyval = typbyval;
+
+ if (typbyval)
+ *((Datum *) tv->data) = value;
+ else
+ memcpy(tv->data, DatumGetPointer(value), copy_bytes);
+
+ return PointerGetDatum(tv);
+}
+
+static Datum
+parse_value(Datum textval, Oid targettid)
+{
+ Oid typinput,
+ typioparam;
+ char *str;
+ Datum result;
+
+ getTypeInputInfo(targettid, &typinput, &typioparam);
+ str = TextDatumGetCString(textval);
+ result = OidInputFunctionCall(typinput, str,
+ typioparam, -1);
+ pfree(str);
+ return result;
+}
+
+static Datum
+generic_cast(Datum value, Oid typid, Oid target_typid)
+{
+ return OidFunctionCall1(get_cast_oid(typid,
+ target_typid,
+ false),
+ value);
+}
+
+Datum
+typedvalue_to_numeric(PG_FUNCTION_ARGS)
+{
+ TypedValue tv;
+ Datum value;
+
+ tv = (TypedValue) PG_GETARG_POINTER(0);
+ value = TypedValueGetDatum(tv);
+
+ switch (tv->typid)
+ {
+ case INT4OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(int4_numeric, value));
+
+ case INT8OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(int8_numeric, value));
+
+ case NUMERICOID:
+ PG_RETURN_DATUM(value);
+
+ case FLOAT4OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(float4_numeric, value));
+
+ case FLOAT8OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(float8_numeric, value));
+
+ case TEXTOID:
+ case BPCHAROID:
+ PG_RETURN_DATUM(parse_value(value, NUMERICOID));
+
+ default:
+ /* slower generic cast */
+ PG_RETURN_DATUM(generic_cast(value, tv->typid, NUMERICOID));
+ }
+}
+
+Datum
+typedvalue_to_int8(PG_FUNCTION_ARGS)
+{
+ TypedValue tv;
+ Datum value;
+
+ tv = (TypedValue) PG_GETARG_POINTER(0);
+ value = TypedValueGetDatum(tv);
+
+ switch (tv->typid)
+ {
+ case INT4OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(int48, value));
+
+ case BOOLOID:
+ {
+ int32 i = DatumGetInt32(DirectFunctionCall1(bool_int4, value));
+
+ PG_RETURN_INT64((int64) i);
+ }
+
+ case INT8OID:
+ PG_RETURN_DATUM(value);
+
+ case NUMERICOID:
+ PG_RETURN_DATUM(DirectFunctionCall1(numeric_int8, value));
+
+ case FLOAT4OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(ftoi8, value));
+
+ case FLOAT8OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(dtoi8, value));
+
+ case TEXTOID:
+ case BPCHAROID:
+ PG_RETURN_DATUM(parse_value(value, INT8OID));
+
+ default:
+ /* slower generic cast */
+ PG_RETURN_DATUM(generic_cast(value, tv->typid, INT8OID));
+ }
+}
+
+Datum
+typedvalue_to_int4(PG_FUNCTION_ARGS)
+{
+ TypedValue tv;
+ Datum value;
+
+ tv = (TypedValue) PG_GETARG_POINTER(0);
+ value = TypedValueGetDatum(tv);
+
+ switch (tv->typid)
+ {
+ case INT4OID:
+ PG_RETURN_DATUM(value);
+
+ case BOOLOID:
+ PG_RETURN_DATUM(DirectFunctionCall1(bool_int4, value));
+
+ case INT8OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(int84, value));
+
+ case NUMERICOID:
+ PG_RETURN_DATUM(DirectFunctionCall1(numeric_int4, value));
+
+ case FLOAT4OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(ftoi4, value));
+
+ case FLOAT8OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(dtoi4, value));
+
+ case TEXTOID:
+ case BPCHAROID:
+ PG_RETURN_DATUM(parse_value(value, INT4OID));
+
+ default:
+ /* slower generic cast */
+ PG_RETURN_DATUM(generic_cast(value, tv->typid, INT4OID));
+ }
+}
+
+Datum
+typedvalue_to_float8(PG_FUNCTION_ARGS)
+{
+ TypedValue tv;
+ Datum value;
+
+ tv = (TypedValue) PG_GETARG_POINTER(0);
+ value = TypedValueGetDatum(tv);
+
+ switch (tv->typid)
+ {
+ case INT4OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(i4tod, value));
+
+ case INT8OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(i8tod, value));
+
+ case NUMERICOID:
+ PG_RETURN_DATUM(DirectFunctionCall1(numeric_float8, value));
+
+ case FLOAT4OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(ftod, value));
+
+ case FLOAT8OID:
+ PG_RETURN_DATUM(value);
+
+ case TEXTOID:
+ case BPCHAROID:
+ PG_RETURN_DATUM(parse_value(value, FLOAT8OID));
+
+ default:
+ /* slower generic cast */
+ PG_RETURN_DATUM(generic_cast(value, tv->typid, FLOAT8OID));
+ }
+
+
+ PG_RETURN_NULL();
+}
+
+Datum
+typedvalue_to_float4(PG_FUNCTION_ARGS)
+{
+ TypedValue tv;
+ Datum value;
+
+ tv = (TypedValue) PG_GETARG_POINTER(0);
+ value = TypedValueGetDatum(tv);
+
+ switch (tv->typid)
+ {
+ case INT4OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(i4tof, value));
+
+ case INT8OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(i8tof, value));
+
+ case NUMERICOID:
+ PG_RETURN_DATUM(DirectFunctionCall1(numeric_float4, value));
+
+ case FLOAT4OID:
+ PG_RETURN_DATUM(value);
+
+ case FLOAT8OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(dtof, value));
+
+ case TEXTOID:
+ case BPCHAROID:
+ PG_RETURN_DATUM(parse_value(value, FLOAT4OID));
+
+ default:
+ /* slower generic cast */
+ PG_RETURN_DATUM(generic_cast(value, tv->typid, FLOAT4OID));
+ }
+}
+
+Datum
+typedvalue_to_date(PG_FUNCTION_ARGS)
+{
+ TypedValue tv;
+ Datum value;
+
+ tv = (TypedValue) PG_GETARG_POINTER(0);
+ value = TypedValueGetDatum(tv);
+
+ switch (tv->typid)
+ {
+ case DATEOID:
+ PG_RETURN_DATUM(value);
+
+ case TIMESTAMPOID:
+ PG_RETURN_DATUM(DirectFunctionCall1(timestamp_date, value));
+
+ case TIMESTAMPTZOID:
+ PG_RETURN_DATUM(DirectFunctionCall1(timestamptz_date, value));
+
+ case TEXTOID:
+ case BPCHAROID:
+ PG_RETURN_DATUM(parse_value(value, FLOAT4OID));
+
+ default:
+ /* slower generic cast */
+ PG_RETURN_DATUM(generic_cast(value, tv->typid, DATEOID));
+ }
+}
+
+Datum
+typedvalue_to_timestamp(PG_FUNCTION_ARGS)
+{
+ TypedValue tv;
+ Datum value;
+
+ tv = (TypedValue) PG_GETARG_POINTER(0);
+ value = TypedValueGetDatum(tv);
+
+ switch (tv->typid)
+ {
+ case DATEOID:
+ PG_RETURN_DATUM(DirectFunctionCall1(date_timestamp, value));
+
+ case TIMESTAMPOID:
+ PG_RETURN_DATUM(value);
+
+ case TIMESTAMPTZOID:
+ PG_RETURN_DATUM(DirectFunctionCall1(timestamptz_timestamp, value));
+
+ case TEXTOID:
+ case BPCHAROID:
+ PG_RETURN_DATUM(parse_value(value, FLOAT4OID));
+
+ default:
+ /* slower generic cast */
+ PG_RETURN_DATUM(generic_cast(value, tv->typid, TIMESTAMPOID));
+ }
+}
+
+Datum
+typedvalue_to_timestamptz(PG_FUNCTION_ARGS)
+{
+ TypedValue tv;
+ Datum value;
+
+ tv = (TypedValue) PG_GETARG_POINTER(0);
+ value = TypedValueGetDatum(tv);
+
+ switch (tv->typid)
+ {
+ case DATEOID:
+ PG_RETURN_DATUM(DirectFunctionCall1(date_timestamptz, value));
+
+ case TIMESTAMPOID:
+ PG_RETURN_DATUM(DirectFunctionCall1(timestamp_timestamptz, value));
+
+ case TIMESTAMPTZOID:
+ PG_RETURN_DATUM(value);
+
+ case TEXTOID:
+ case BPCHAROID:
+ PG_RETURN_DATUM(parse_value(value, FLOAT4OID));
+
+ default:
+ /* slower generic cast */
+ PG_RETURN_DATUM(generic_cast(value, tv->typid, TIMESTAMPTZOID));
+ }
+}
+
+Datum
+typedvalue_to_interval(PG_FUNCTION_ARGS)
+{
+ TypedValue tv;
+ Datum value;
+
+ tv = (TypedValue) PG_GETARG_POINTER(0);
+ value = TypedValueGetDatum(tv);
+
+ switch (tv->typid)
+ {
+ case INTERVALOID:
+ PG_RETURN_DATUM(value);
+
+ case TEXTOID:
+ case BPCHAROID:
+ PG_RETURN_DATUM(parse_value(value, BOOLOID));
+
+ default:
+ /* slower generic cast */
+ PG_RETURN_DATUM(generic_cast(value, tv->typid, INTERVALOID));
+ }
+}
+
+Datum
+typedvalue_to_text(PG_FUNCTION_ARGS)
+{
+ Oid typOutput;
+ bool isVarlena;
+ char *str;
+ TypedValue tv;
+ Datum value;
+ text *txt;
+
+ tv = (TypedValue) PG_GETARG_POINTER(0);
+
+ value = TypedValueGetDatum(tv);
+
+ if (tv->typid == TEXTOID || tv->typid == BPCHAROID)
+ {
+ PG_RETURN_DATUM(datumCopy(value,
+ tv->typbyval,
+ tv->typlen));
+ }
+
+ getTypeOutputInfo(tv->typid, &typOutput, &isVarlena);
+
+ value = TypedValueGetDatum(tv);
+ str = OidOutputFunctionCall(typOutput, value);
+ txt = cstring_to_text(str);
+
+ PG_RETURN_TEXT_P(txt);
+}
+
+Datum
+typedvalue_to_bpchar(PG_FUNCTION_ARGS)
+{
+ return typedvalue_to_text(fcinfo);
+}
+
+Datum
+typedvalue_to_bool(PG_FUNCTION_ARGS)
+{
+ TypedValue tv;
+ Datum value;
+
+ tv = (TypedValue) PG_GETARG_POINTER(0);
+ value = TypedValueGetDatum(tv);
+
+ switch (tv->typid)
+ {
+ case BOOLOID:
+ PG_RETURN_DATUM(value);
+
+ case INT4OID:
+ PG_RETURN_DATUM(DirectFunctionCall1(int4_bool, value));
+
+ case INT8OID:
+ {
+ int64 i = DatumGetInt64(value);
+
+ PG_RETURN_DATUM(DirectFunctionCall1(int4_bool, Int32GetDatum((int32) i)));
+ }
+
+ case TEXTOID:
+ case BPCHAROID:
+ PG_RETURN_DATUM(parse_value(value, BOOLOID));
+
+ default:
+ /* slower generic cast */
+ PG_RETURN_DATUM(generic_cast(value, tv->typid, BOOLOID));
+ }
+}
+
+Datum
+numeric_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ NUMERICOID,
+ -1,
+ false));
+}
+
+Datum
+int8_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ INT8OID,
+ 8,
+ true));
+}
+
+Datum
+int4_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ INT4OID,
+ 4,
+ true));
+}
+
+Datum
+float8_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ FLOAT8OID,
+ 8,
+ true));
+}
+
+Datum
+float4_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ FLOAT4OID,
+ 4,
+ true));
+}
+
+
+Datum
+date_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ DATEOID,
+ 4,
+ true));
+}
+
+Datum
+timestamp_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ TIMESTAMPOID,
+ 8,
+ true));
+}
+
+Datum
+timestamptz_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ TIMESTAMPTZOID,
+ 8,
+ true));
+}
+
+Datum
+interval_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ INTERVALOID,
+ 16,
+ false));
+}
+
+Datum
+text_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ TEXTOID,
+ -1,
+ false));
+}
+
+Datum
+bpchar_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ BPCHAROID,
+ -1,
+ false));
+}
+
+Datum
+bool_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ BOOLOID,
+ 1,
+ true));
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index f0c8ae686d..e6bee14899 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -14,6 +14,9 @@
#include "postgres.h"
#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/lsyscache.h"
+#include "utils/typedvalue.h"
#include "windowapi.h"
/*
@@ -35,6 +38,20 @@ typedef struct
int64 remainder; /* (total rows) % (bucket num) */
} ntile_context;
+#define PROXY_CONTEXT_MAGIC 19730715
+
+typedef struct
+{
+ int magic;
+ Oid typid;
+ int16 typlen;
+ bool typbyval;
+ int allocsize;
+ bool isnull;
+ Datum value;
+ char data[FLEXIBLE_ARRAY_MEMBER];
+} proxy_context;
+
static bool rank_up(WindowObject winobj);
static Datum leadlag_common(FunctionCallInfo fcinfo,
bool forward, bool withoffset, bool withdefault);
@@ -472,3 +489,467 @@ window_nth_value(PG_FUNCTION_ARGS)
PG_RETURN_DATUM(result);
}
+
+/*
+ * High level access function. These functions are wrappers for windows API
+ * for PL languages based on usage WindowObjectProxy.
+ */
+Datum
+windowobject_get_current_position(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int64 pos;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ pos = WinGetCurrentPosition(winobj);
+
+ PG_RETURN_INT64(pos);
+}
+
+Datum
+windowobject_set_mark_position(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int64 pos;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ pos = PG_GETARG_INT64(1);
+
+ WinSetMarkPosition(winobj, pos);
+
+ PG_RETURN_VOID();
+}
+
+Datum
+windowobject_get_partition_rowcount(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int64 rc;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ rc = WinGetPartitionRowCount(winobj);
+
+ PG_RETURN_INT64(rc);
+}
+
+Datum
+windowobject_rows_are_peers(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int64 pos1,
+ pos2;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ pos1 = PG_GETARG_INT64(1);
+ pos2 = PG_GETARG_INT64(2);
+
+ PG_RETURN_BOOL(WinRowsArePeers(winobj, pos1, pos2));
+}
+
+#define SEEK_CURRENT_STR "seek_current"
+#define SEEK_HEAD_STR "seek_head"
+#define SEEK_TAIL_STR "seek_tail"
+
+#define STRLEN(s) (sizeof(s) - 1)
+
+static int
+get_seek_type(text *seektype)
+{
+ char *str;
+ int len;
+ int result;
+
+ str = VARDATA_ANY(seektype);
+ len = VARSIZE_ANY_EXHDR(seektype);
+
+ if (len == STRLEN(SEEK_CURRENT_STR) && strncmp(str, SEEK_CURRENT_STR, len) == 0)
+ result = WINDOW_SEEK_CURRENT;
+ else if (len == STRLEN(SEEK_HEAD_STR) && strncmp(str, SEEK_HEAD_STR, len) == 0)
+ result = WINDOW_SEEK_HEAD;
+ else if (len == STRLEN(SEEK_TAIL_STR) && strncmp(str, SEEK_TAIL_STR, len) == 0)
+ result = WINDOW_SEEK_TAIL;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("seek type value must be \"seek_current\", \"seek_head\" or \"seek_tail\"")));
+
+ return result;
+}
+
+static Oid
+wop_funcarg_info(WindowObjectProxy wop,
+ int argno,
+ int16 *typlen,
+ bool *typbyval)
+{
+ WindowObjectProxyMutable *mutable_data = wop->mutable_data;
+
+ if (argno != mutable_data->last_argno)
+ {
+ Oid argtypid = get_fn_expr_argtype(wop->fcinfo->flinfo, argno);
+
+ mutable_data->typid = getBaseType(argtypid);
+ get_typlenbyval(mutable_data->typid,
+ &mutable_data->typlen,
+ &mutable_data->typbyval);
+ mutable_data->last_argno = argno;
+ }
+
+ *typlen = mutable_data->typlen;
+ *typbyval = mutable_data->typbyval;
+
+ return mutable_data->typid;
+}
+
+Datum
+windowobject_get_func_arg_partition(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int argno;
+ int relpos;
+ int seektype;
+ bool set_mark;
+ Datum value;
+ bool isnull;
+ Oid typid;
+ int16 typlen;
+ bool typbyval;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ argno = PG_GETARG_INT32(1);
+
+ if (argno < 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("arg number less than one")));
+
+ argno -= 1;
+
+ relpos = PG_GETARG_INT32(2);
+ seektype = get_seek_type(PG_GETARG_TEXT_P(3));
+ set_mark = PG_GETARG_BOOL(4);
+
+ value = WinGetFuncArgInPartition(winobj,
+ argno,
+ relpos,
+ seektype,
+ set_mark,
+ &isnull,
+ &wop->mutable_data->isout);
+
+ if (isnull)
+ PG_RETURN_NULL();
+
+ typid = wop_funcarg_info(wop, argno, &typlen, &typbyval);
+
+ PG_RETURN_DATUM(makeTypedValue(value, typid, typlen, typbyval));
+}
+
+Datum
+windowobject_get_func_arg_frame(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int argno;
+ int relpos;
+ int seektype;
+ bool set_mark;
+ Datum value;
+ bool isnull;
+ Oid typid;
+ int16 typlen;
+ bool typbyval;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ argno = PG_GETARG_INT32(1);
+ relpos = PG_GETARG_INT32(2);
+ seektype = get_seek_type(PG_GETARG_TEXT_P(3));
+ set_mark = PG_GETARG_BOOL(4);
+
+ if (argno < 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("arg number less than one")));
+
+ argno -= 1;
+
+ value = WinGetFuncArgInFrame(winobj,
+ argno,
+ relpos,
+ seektype,
+ set_mark,
+ &isnull,
+ &wop->mutable_data->isout);
+
+ if (isnull)
+ PG_RETURN_NULL();
+
+ typid = wop_funcarg_info(wop, argno, &typlen, &typbyval);
+
+ PG_RETURN_DATUM(makeTypedValue(value, typid, typlen, typbyval));
+}
+
+Datum
+windowobject_get_func_arg_current(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int argno;
+ Datum value;
+ bool isnull;
+ Oid typid;
+ int16 typlen;
+ bool typbyval;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ winobj = wop->winobj;
+
+ Assert(WindowObjectIsValid(winobj));
+
+ argno = PG_GETARG_INT32(1);
+
+ if (argno < 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("arg number less than one")));
+
+ argno -= 1;
+
+ value = WinGetFuncArgCurrent(winobj, argno, &isnull);
+
+ wop->mutable_data->isout = false;
+
+ if (isnull)
+ PG_RETURN_NULL();
+
+ typid = wop_funcarg_info(wop, argno, &typlen, &typbyval);
+ PG_RETURN_DATUM(makeTypedValue(value, typid, typlen, typbyval));
+}
+
+static void
+copy_datum_to_partition_context(proxy_context *pcontext,
+ Datum value,
+ bool isnull)
+{
+ if (!isnull)
+ {
+ if (pcontext->typbyval)
+ pcontext->value = value;
+ else if (pcontext->typlen == -1)
+ {
+ struct varlena *s = (struct varlena *) DatumGetPointer(value);
+
+ memcpy(pcontext->data, s, VARSIZE_ANY(s));
+ pcontext->value = PointerGetDatum(pcontext->data);
+ }
+ else
+ {
+ memcpy(pcontext->data, DatumGetPointer(value), pcontext->typlen);
+ pcontext->value = PointerGetDatum(pcontext->data);
+ }
+
+ pcontext->isnull = false;
+ }
+ else
+ {
+ pcontext->value = (Datum) 0;
+ pcontext->isnull = true;
+ }
+}
+
+/*
+ * Returns estimated size of windowobject partition context
+ */
+static int
+estimate_partition_context_size(Datum value,
+ bool isnull,
+ int16 typlen,
+ int16 minsize,
+ int *realsize)
+{
+ if(typlen != -1)
+ {
+ if (typlen < sizeof(Datum))
+ {
+ *realsize = offsetof(proxy_context, data);
+
+ return *realsize;
+ }
+
+ if (typlen > 1024)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("size of value is greather than limit (1024 bytes)")));
+
+ *realsize = offsetof(proxy_context, data) + typlen;
+
+ return *realsize;
+ }
+ else
+ {
+ if (!isnull)
+ {
+ int size = VARSIZE_ANY_EXHDR(DatumGetPointer(value));
+
+ if (size > 1024)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("size of value is greather than limit (1024 bytes)")));
+
+ *realsize = size;
+
+ size += size / 3;
+
+ return offsetof(proxy_context, data)
+ + MAXALIGN(size > minsize ? size : minsize);
+ }
+ else
+ {
+ /* by default we allocate 30 bytes */
+ *realsize = 0;
+
+ return offsetof(proxy_context, data) + MAXALIGN(minsize);
+ }
+ }
+}
+
+#define VARLENA_MINSIZE 32
+
+static proxy_context *
+get_partition_context(FunctionCallInfo fcinfo, bool write_mode)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ Oid typid;
+ int16 typlen;
+ bool typbyval;
+ Datum value = (Datum) 0;
+ bool isnull = true;
+ int allocsize;
+ int minsize;
+ int realsize;
+ proxy_context *pcontext;
+
+ if (PG_ARGISNULL(0))
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("windowobject is NULL")));
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ if (PG_ARGISNULL(2))
+ minsize = VARLENA_MINSIZE;
+ else
+ minsize = PG_GETARG_INT32(2);
+
+ if (!PG_ARGISNULL(1))
+ {
+ value = PG_GETARG_DATUM(1);
+ isnull = false;
+ }
+
+ typid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ if (!OidIsValid(typid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot detect type of context value")));
+
+ typid = getBaseType(typid);
+ get_typlenbyval(typid, &typlen, &typbyval);
+
+ Assert(typlen != -2);
+
+ allocsize = estimate_partition_context_size(value,
+ isnull,
+ typlen,
+ minsize,
+ &realsize);
+
+ pcontext = (proxy_context *) WinGetPartitionLocalMemory(winobj, allocsize);
+
+ /* fresh pcontext has zeroed memory */
+ Assert(pcontext->magic == 0 || pcontext->magic == PROXY_CONTEXT_MAGIC);
+
+ if (pcontext->allocsize > 0)
+ {
+ if (realsize > pcontext->allocsize)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("the value cannot be saved to allocated buffer"),
+ errhint("Try to increase the minsize argument.")));
+
+ if (pcontext->typid != typid)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("partition context was initialized for different type")));
+
+ if (write_mode)
+ copy_datum_to_partition_context(pcontext, value, isnull);
+
+ }
+ else
+ {
+ pcontext->magic = PROXY_CONTEXT_MAGIC;
+ pcontext->typid = typid;
+ pcontext->typlen = typlen;
+ pcontext->typbyval = typbyval;
+ pcontext->allocsize = allocsize;
+
+ copy_datum_to_partition_context(pcontext, value, isnull);
+ }
+
+ return pcontext;
+}
+
+Datum
+windowobject_set_partition_context_value(PG_FUNCTION_ARGS)
+{
+ (void) get_partition_context(fcinfo, true);
+
+ PG_RETURN_VOID();
+}
+
+Datum
+windowobject_get_partition_context_value(PG_FUNCTION_ARGS)
+{
+ proxy_context *pcontext;
+
+ pcontext = get_partition_context(fcinfo, false);
+
+ if (pcontext->isnull)
+ PG_RETURN_NULL();
+
+ PG_RETURN_DATUM(pcontext->value);
+}
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index 5a58f50fbb..597de4daa9 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -530,4 +530,56 @@
{ castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
castcontext => 'e', castmethod => 'f' },
+# Allow explicit coercions between typedvalue and other types
+
+
+{ castsource => 'typedvalue', casttarget => 'numeric', castfunc => 'to_numeric(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'int8', castfunc => 'to_int8(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'int4', castfunc => 'to_int4(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'float8', castfunc => 'to_float8(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'float4', castfunc => 'to_float4(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'date', castfunc => 'to_date(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'timestamp', castfunc => 'to_timestamp(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'timestamptz', castfunc => 'to_timestamptz(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'interval', castfunc => 'to_interval(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'text', castfunc => 'to_text(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'varchar', castfunc => 'to_varchar(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'bool', castfunc => 'to_bool(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'numeric', casttarget => 'typedvalue', castfunc => 'to_typedvalue(numeric)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'int8', casttarget => 'typedvalue', castfunc => 'to_typedvalue(int8)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'int4', casttarget => 'typedvalue', castfunc => 'to_typedvalue(int4)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'float8', casttarget => 'typedvalue', castfunc => 'to_typedvalue(float8)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'float4', casttarget => 'typedvalue', castfunc => 'to_typedvalue(float4)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'date', casttarget => 'typedvalue', castfunc => 'to_typedvalue(date)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'timestamp', casttarget => 'typedvalue', castfunc => 'to_typedvalue(timestamp)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'timestamptz', casttarget => 'typedvalue', castfunc => 'to_typedvalue(timestamptz)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'interval', casttarget => 'typedvalue', castfunc => 'to_typedvalue(interval)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'text', casttarget => 'typedvalue', castfunc => 'to_typedvalue(text)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'varchar', casttarget => 'typedvalue', castfunc => 'to_typedvalue(varchar)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'bool', casttarget => 'typedvalue', castfunc => 'to_typedvalue(bool)',
+ castcontext => 'e', castmethod => 'f' },
+
]
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 27989971db..213d7c5440 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7130,6 +7130,18 @@
{ oid => '2305', descr => 'I/O',
proname => 'internal_out', prorettype => 'cstring', proargtypes => 'internal',
prosrc => 'internal_out' },
+{ oid => '9554', descr => 'I/O',
+ proname => 'windowobjectproxy_in', proisstrict => 'f', prorettype => 'windowobjectproxy',
+ proargtypes => 'cstring', prosrc => 'windowobjectproxy_in' },
+{ oid => '9555', descr => 'I/O',
+ proname => 'windowobjectproxy_out', prorettype => 'cstring', proargtypes => 'windowobjectproxy',
+ prosrc => 'windowobjectproxy_out' },
+{ oid => '9556', descr => 'I/O',
+ proname => 'typedvalue_in', proisstrict => 'f', prorettype => 'typedvalue',
+ proargtypes => 'cstring', prosrc => 'typedvalue_in' },
+{ oid => '9557', descr => 'I/O',
+ proname => 'typedvalue_out', prorettype => 'cstring', proargtypes => 'typedvalue',
+ prosrc => 'typedvalue_out' },
{ oid => '2312', descr => 'I/O',
proname => 'anyelement_in', prorettype => 'anyelement',
proargtypes => 'cstring', prosrc => 'anyelement_in' },
@@ -7219,6 +7231,80 @@
prorettype => 'cstring', proargtypes => 'anycompatiblerange',
prosrc => 'anycompatiblerange_out' },
+# typedvalue cast functions
+{ oid => '9567', descr => 'format typedvalue to numeric',
+ proname => 'to_numeric', provolatile => 's', prorettype => 'numeric',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_numeric' },
+{ oid => '9568', descr => 'format typedvalue to int8',
+ proname => 'to_int8', provolatile => 's', prorettype => 'int8',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_int8' },
+{ oid => '9569', descr => 'format typedvalue to int4',
+ proname => 'to_int4', provolatile => 's', prorettype => 'int4',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_int4' },
+{ oid => '9570', descr => 'format typedvalue to float8',
+ proname => 'to_float8', provolatile => 's', prorettype => 'float8',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_float8' },
+{ oid => '9571', descr => 'format typedvalue to float4',
+ proname => 'to_float4', provolatile => 's', prorettype => 'float4',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_float4' },
+{ oid => '9572', descr => 'format typedvalue to date',
+ proname => 'to_date', provolatile => 's', prorettype => 'date',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_date' },
+{ oid => '9573', descr => 'format typedvalue to timestamp',
+ proname => 'to_timestamp', provolatile => 's', prorettype => 'timestamp',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_timestamp' },
+{ oid => '9574', descr => 'format typedvalue to timestamptz',
+ proname => 'to_timestamptz', provolatile => 's', prorettype => 'timestamptz',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_timestamptz' },
+{ oid => '9575', descr => 'format typedvalue to interval',
+ proname => 'to_interval', provolatile => 's', prorettype => 'interval',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_interval' },
+{ oid => '9576', descr => 'format typedvalue to text',
+ proname => 'to_text', provolatile => 's', prorettype => 'text',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_text' },
+{ oid => '9577', descr => 'format typedvalue to varchar',
+ proname => 'to_varchar', provolatile => 's', prorettype => 'varchar',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_bpchar' },
+{ oid => '9578', descr => 'format numeric to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'numeric', prosrc => 'numeric_to_typedvalue' },
+{ oid => '9579', descr => 'format int8 to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'int8', prosrc => 'int8_to_typedvalue' },
+{ oid => '9580', descr => 'format int4 to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'int4', prosrc => 'int4_to_typedvalue' },
+{ oid => '9581', descr => 'format float8 to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'float8', prosrc => 'float8_to_typedvalue' },
+{ oid => '9582', descr => 'format float4 to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'float4', prosrc => 'float4_to_typedvalue' },
+{ oid => '9583', descr => 'format date to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'date', prosrc => 'date_to_typedvalue' },
+{ oid => '9584', descr => 'format timestamp to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'timestamp', prosrc => 'timestamp_to_typedvalue' },
+{ oid => '9585', descr => 'format timestamptz to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'timestamptz', prosrc => 'timestamptz_to_typedvalue' },
+{ oid => '9586', descr => 'format interval to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'interval', prosrc => 'interval_to_typedvalue' },
+{ oid => '9587', descr => 'format text to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'text', prosrc => 'text_to_typedvalue' },
+{ oid => '9588', descr => 'format varchar to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'varchar', prosrc => 'bpchar_to_typedvalue' },
+{ oid => '9589', descr => 'format bool to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'bool', prosrc => 'bool_to_typedvalue' },
+{ oid => '9590', descr => 'format typedvalue to bool',
+ proname => 'to_bool', provolatile => 's', prorettype => 'bool',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_bool' },
+
# tablesample method handlers
{ oid => '3313', descr => 'BERNOULLI tablesample method handler',
proname => 'bernoulli', provolatile => 'v', prorettype => 'tsm_handler',
@@ -9742,6 +9828,35 @@
{ oid => '3114', descr => 'fetch the Nth row value',
proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '9558', descr => 'get current position from window object',
+ proname => 'get_current_position', prokind => 'f', prorettype => 'int8',
+ proargtypes => 'windowobjectproxy', prosrc => 'windowobject_get_current_position' },
+{ oid => '9559', descr => 'set current position in window object',
+ proname => 'set_mark_position', prokind => 'f', prorettype => 'void',
+ proargtypes => 'windowobjectproxy int8', prosrc => 'windowobject_set_mark_position' },
+{ oid => '9560', descr => 'get partition row count',
+ proname => 'get_partition_rowcount', prokind => 'f', prorettype => 'int8',
+ proargtypes => 'windowobjectproxy', prosrc => 'windowobject_get_partition_rowcount' },
+{ oid => '9561', descr => 'returns true if two positions are peers',
+ proname => 'rows_are_peers', prokind => 'f', prorettype => 'bool',
+ proargtypes => 'windowobjectproxy int8 int8', prosrc => 'windowobject_rows_are_peers' },
+{ oid => '9562', descr => 'returns argument of window function against to partition',
+ proname => 'get_input_value_in_partition', prokind => 'f', prorettype => 'typedvalue',
+ proargtypes => 'windowobjectproxy int4 int4 text bool', prosrc => 'windowobject_get_func_arg_partition' },
+{ oid => '9563', descr => 'returns argument of window function against to frame',
+ proname => 'get_input_value_in_frame', prokind => 'f', prorettype => 'typedvalue',
+ proargtypes => 'windowobjectproxy int4 int4 text bool', prosrc => 'windowobject_get_func_arg_frame' },
+{ oid => '9564', descr => 'returns argument of window function against to current row',
+ proname => 'get_input_value_for_row', prokind => 'f', prorettype => 'typedvalue',
+ proargtypes => 'windowobjectproxy int4', prosrc => 'windowobject_get_func_arg_current' },
+{ oid => '9565', descr => 'returns a value stored in a partition context',
+ proname => 'get_partition_context_value', prokind => 'f', prorettype => 'anyelement',
+ proargtypes => 'windowobjectproxy anyelement int4',
+ prosrc => 'windowobject_get_partition_context_value', proisstrict => 'f' },
+{ oid => '9566', descr => 'store a value to partition context',
+ proname => 'set_partition_context_value', prokind => 'f', prorettype => 'void',
+ proargtypes => 'windowobjectproxy anyelement int4',
+ prosrc => 'windowobject_set_partition_context_value', proisstrict => 'f' },
# functions for range types
{ oid => '3832', descr => 'I/O',
diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat
index b2cec07416..5ad4ddfd59 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -560,6 +560,17 @@
typtype => 'p', typcategory => 'P', typinput => 'internal_in',
typoutput => 'internal_out', typreceive => '-', typsend => '-',
typalign => 'ALIGNOF_POINTER' },
+{ oid => '9552',
+ descr => 'pseudo-type representing an pointer to WindowObjectProxy structure',
+ typname => 'windowobjectproxy', typlen => '-1', typbyval => 'f',
+ typtype => 'p', typcategory => 'P', typinput => 'windowobjectproxy_in',
+ typoutput => 'windowobjectproxy_out', typreceive => '-', typsend => '-',
+ typalign => 'i', typstorage => 'p' },
+{ oid => '9553',
+ descr => 'type that can hold any scalar value with necessary meta',
+ typname => 'typedvalue', typtype => 'b', typlen => '-1', typbyval => 'f', typcategory => 'X',
+ typispreferred => 'f', typinput => 'typedvalue_in', typoutput => 'typedvalue_out',
+ typreceive => '-', typsend => '-', typalign => 'i', typstorage => 'x' },
{ oid => '2283', descr => 'pseudo-type representing a polymorphic base type',
typname => 'anyelement', typlen => '4', typbyval => 't', typtype => 'p',
typcategory => 'P', typinput => 'anyelement_in',
diff --git a/src/include/utils/typedvalue.h b/src/include/utils/typedvalue.h
new file mode 100644
index 0000000000..ce573b0374
--- /dev/null
+++ b/src/include/utils/typedvalue.h
@@ -0,0 +1,28 @@
+/*-------------------------------------------------------------------------
+ *
+ * typedvalue.h
+ * Declarations for typedvalue data type support.
+ *
+ * Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ *
+ * src/include/utils/typedvalue.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef __TYPEDVALUE_H__
+#define __TYPEDVALUE_H__
+
+typedef struct
+{
+ int32 vl_len; /* varlena header */
+ Oid typid;
+ bool typbyval;
+ int16 typlen;
+ char data[FLEXIBLE_ARRAY_MEMBER];
+} TypedValueData;
+
+typedef TypedValueData *TypedValue;
+
+extern Datum makeTypedValue(Datum value, Oid typid, int16 typlen, bool typbyval);
+
+#endif
\ No newline at end of file
diff --git a/src/include/windowapi.h b/src/include/windowapi.h
index e8c9fc54d8..a4b8504f78 100644
--- a/src/include/windowapi.h
+++ b/src/include/windowapi.h
@@ -36,6 +36,34 @@
/* this struct is private in nodeWindowAgg.c */
typedef struct WindowObjectData *WindowObject;
+typedef struct WindowObjectProxyMutable
+{
+ /* true when request on winfuncarg doesn't return data */
+ bool isout;
+
+ /* cache for type related data of Window arguments */
+ int last_argno;
+ Oid typid;
+ int16 typlen;
+ bool typbyval;
+} WindowObjectProxyMutable;
+
+/*
+ * This type is used as proxy between PL variants of WinFuncArg
+ * functions and PL environment. The variables of windowobjectproxy
+ * type can be copied, so mutable content should be elsewhere.
+ */
+typedef struct WindowObjectProxyData
+{
+ int32 vl_len; /* varlena header */
+
+ WindowObject winobj;
+ FunctionCallInfo fcinfo;
+ WindowObjectProxyMutable *mutable_data;
+} WindowObjectProxyData;
+
+typedef WindowObjectProxyData *WindowObjectProxy;
+
#define PG_WINDOW_OBJECT() ((WindowObject) fcinfo->context)
#define WindowObjectIsValid(winobj) \
diff --git a/src/pl/plpgsql/src/Makefile b/src/pl/plpgsql/src/Makefile
index 193df8a010..ba6edfd42e 100644
--- a/src/pl/plpgsql/src/Makefile
+++ b/src/pl/plpgsql/src/Makefile
@@ -34,7 +34,7 @@ REGRESS_OPTS = --dbname=$(PL_TESTDB)
REGRESS = plpgsql_call plpgsql_control plpgsql_copy plpgsql_domain \
plpgsql_record plpgsql_cache plpgsql_simple plpgsql_transaction \
- plpgsql_trap plpgsql_trigger plpgsql_varprops
+ plpgsql_trap plpgsql_trigger plpgsql_varprops plpgsql_window
# where to find gen_keywordlist.pl and subsidiary files
TOOLSDIR = $(top_srcdir)/src/tools
diff --git a/src/pl/plpgsql/src/expected/plpgsql_window.out b/src/pl/plpgsql/src/expected/plpgsql_window.out
new file mode 100644
index 0000000000..b9cb015ecc
--- /dev/null
+++ b/src/pl/plpgsql/src/expected/plpgsql_window.out
@@ -0,0 +1,228 @@
+create or replace function pl_row_number()
+returns bigint as $$
+declare pos int8;
+begin
+ pos := get_current_position(windowobject);
+ pos := pos + 1;
+ perform set_mark_position(windowobject, pos);
+ return pos;
+end
+$$
+language plpgsql window;
+select pl_row_number() over (), v from (values(10),(20),(30)) v(v);
+ pl_row_number | v
+---------------+----
+ 1 | 10
+ 2 | 20
+ 3 | 30
+(3 rows)
+
+create or replace function pl_round_value(numeric)
+returns int as $$
+declare
+ num numeric;
+begin
+ num := get_input_value_for_row(windowobject, 1);
+ return round(num);
+end
+$$ language plpgsql window;
+select pl_round_value(v) over(order by v desc) from generate_series(0.1, 1.0, 0.1) g(v);
+ pl_round_value
+----------------
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+ 0
+ 0
+ 0
+ 0
+(10 rows)
+
+select pl_round_value(v + 1) over(order by v desc) from generate_series(0.1, 1.0, 0.1) g(v);
+ pl_round_value
+----------------
+ 2
+ 2
+ 2
+ 2
+ 2
+ 2
+ 1
+ 1
+ 1
+ 1
+(10 rows)
+
+create table test_table(v numeric);
+insert into test_table values(1),(3),(6),(6),(8),(7),(6),(5),(4);
+create or replace function pl_lag(numeric)
+returns numeric as $$
+declare
+ v numeric;
+begin
+ v := get_input_value_in_partition(windowobject, 1, -1, 'seek_current', false);
+ return v;
+end;
+$$ language plpgsql window;
+select pl_lag(v) over (), lag(v) over () from test_table;
+ pl_lag | lag
+--------+-----
+ |
+ 1 | 1
+ 3 | 3
+ 6 | 6
+ 6 | 6
+ 8 | 8
+ 7 | 7
+ 6 | 6
+ 5 | 5
+(9 rows)
+
+drop table test_table;
+create table test_table(v integer);
+insert into test_table values(1),(3),(6),(6),(8),(7),(6),(5),(4);
+select pl_lag(v) over (), lag(v) over () from test_table;
+ pl_lag | lag
+--------+-----
+ |
+ 1 | 1
+ 3 | 3
+ 6 | 6
+ 6 | 6
+ 8 | 8
+ 7 | 7
+ 6 | 6
+ 5 | 5
+(9 rows)
+
+create or replace function pl_moving_avg(numeric)
+returns numeric as $$
+declare
+ s numeric default 0.0;
+ v numeric;
+ c numeric default 0.0;
+begin
+ -- look before
+ v := get_input_value_in_partition(windowobject, 1, -1, 'seek_current', false);
+ if v is not null then
+ s := s + v;
+ c := c + 1.0;
+ end if;
+
+ -- look after
+ v := get_input_value_in_partition(windowobject, 1, 0, 'seek_current', false);
+ if v is not null then
+ s := s + v;
+ c := c + 1.0;
+ end if;
+
+ v := get_input_value_in_partition(windowobject, 1, 1, 'seek_current', false);
+ if v is not null then
+ s := s + v;
+ c := c + 1.0;
+ end if;
+
+ return trim_scale(s / c);
+end
+$$ language plpgsql window;
+select pl_moving_avg(v) over (), v from test_table;
+ pl_moving_avg | v
+--------------------+---
+ 2 | 1
+ 3.3333333333333333 | 3
+ 5 | 6
+ 6.6666666666666667 | 6
+ 7 | 8
+ 7 | 7
+ 6 | 6
+ 5 | 5
+ 4.5 | 4
+(9 rows)
+
+create or replace function pl_lag_polymorphic(anyelement)
+returns anyelement as $$
+declare
+ v $0%type;
+begin
+ v := get_input_value_in_partition(windowobject, 1, -1, 'seek_current', false);
+ return v;
+end;
+$$ language plpgsql window;
+select pl_lag_polymorphic(v) over (), lag(v) over () from test_table;
+ pl_lag_polymorphic | lag
+--------------------+-----
+ |
+ 1 | 1
+ 3 | 3
+ 6 | 6
+ 6 | 6
+ 8 | 8
+ 7 | 7
+ 6 | 6
+ 5 | 5
+(9 rows)
+
+create or replace function pl_pcontext_test(numeric)
+returns numeric as $$
+declare
+ n numeric;
+ v numeric;
+begin
+ n := get_partition_context_value(windowobject, null::numeric);
+ v := get_input_value_for_row(windowobject, 1);
+ perform set_partition_context_value(windowobject, v);
+
+ return n;
+end
+$$
+language plpgsql window;
+select v, pl_pcontext_test(v) over () from generate_series(0.1, 1.0, 0.1) g(v);
+ v | pl_pcontext_test
+-----+------------------
+ 0.1 |
+ 0.2 | 0.1
+ 0.3 | 0.2
+ 0.4 | 0.3
+ 0.5 | 0.4
+ 0.6 | 0.5
+ 0.7 | 0.6
+ 0.8 | 0.7
+ 0.9 | 0.8
+ 1.0 | 0.9
+(10 rows)
+
+create table test_missing_values(id int, v integer);
+insert into test_missing_values values(1,10),(2,11),(3,12),(4,null),(5,null),(6,15),(7,16);
+create or replace function pl_pcontext_test(numeric)
+returns numeric as $$
+declare
+ n numeric;
+ v numeric;
+begin
+ v := get_input_value_for_row(windowobject, 1);
+
+ if v is null then
+ v := get_partition_context_value(windowobject, null::numeric);
+ else
+ perform set_partition_context_value(windowobject, v);
+ end if;
+
+ return v;
+end
+$$
+language plpgsql window;
+select id, v, pl_pcontext_test(v) over (order by id) from test_missing_values;
+ id | v | pl_pcontext_test
+----+----+------------------
+ 1 | 10 | 10
+ 2 | 11 | 11
+ 3 | 12 | 12
+ 4 | | 12
+ 5 | | 12
+ 6 | 15 | 15
+ 7 | 16 | 16
+(7 rows)
+
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index e7f4a5f291..ee4bbb560e 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -582,6 +582,41 @@ do_compile(FunctionCallInfo fcinfo,
true);
}
+ if (function->fn_prokind == PROKIND_WINDOW)
+ {
+ PLpgSQL_type *dtype;
+ PLpgSQL_var *var;
+
+ /*
+ * Add the promise variable windowobject with windowobjectproxy type
+ *
+ * Pseudotypes are disallowed for custom variables. It is checked
+ * in plpgsql_build_variable, so instead call this function, build
+ * promise variable here.
+ */
+
+ dtype = plpgsql_build_datatype(WINDOWOBJECTPROXYOID,
+ -1,
+ function->fn_input_collation,
+ NULL);
+
+ /* this should be pseudotype */
+ Assert(dtype->ttype == PLPGSQL_TTYPE_PSEUDO);
+
+ var = palloc0(sizeof(PLpgSQL_var));
+
+ var->dtype = PLPGSQL_DTYPE_PROMISE;
+ var->promise = PLPGSQL_PROMISE_WINDOWOBJECT;
+
+ var->refname = pstrdup("windowobject");
+ var->datatype = dtype;
+
+ plpgsql_adddatum((PLpgSQL_datum *) var);
+ plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR,
+ var->dno,
+ var->refname);
+ }
+
ReleaseSysCache(typeTup);
break;
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index d4a3d58daa..e2af053fda 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -593,6 +593,39 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo,
*/
exec_set_found(&estate, false);
+ /*
+ * Initialize promise winobject
+ */
+ if (func->fn_prokind == PROKIND_WINDOW)
+ {
+ /* fcinfo is available in this function too */
+ WindowObjectProxy wop;
+ WindowObjectProxyMutable *mutable_data;
+ MemoryContext oldcontext;
+
+ oldcontext = MemoryContextSwitchTo(estate.datum_context);
+
+ wop = palloc(sizeof(WindowObjectProxyData));
+ SET_VARSIZE(wop, sizeof(WindowObjectProxyData));
+
+ wop->winobj = PG_WINDOW_OBJECT();
+
+ Assert(WindowObjectIsValid(wop->winobj));
+
+ mutable_data = palloc0(sizeof(WindowObjectProxyMutable));
+ mutable_data->isout = false;
+ mutable_data->last_argno = -1;
+
+ wop->mutable_data = mutable_data;
+ wop->fcinfo = fcinfo;
+
+ MemoryContextSwitchTo(oldcontext);
+
+ estate.winobjproxy = wop;
+ }
+ else
+ estate.winobjproxy = NULL;
+
/*
* Let the instrumentation plugin peek at this function
*/
@@ -915,6 +948,11 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
plpgsql_estate_setup(&estate, func, NULL, NULL, NULL);
estate.trigdata = trigdata;
+ /*
+ * Trigger function cannot be WINDOW function
+ */
+ estate.winobjproxy = NULL;
+
/*
* Setup error traceback support for ereport()
*/
@@ -1293,11 +1331,13 @@ copy_plpgsql_datums(PLpgSQL_execstate *estate,
PLpgSQL_datum *indatum = indatums[i];
PLpgSQL_datum *outdatum;
+
/* This must agree with plpgsql_finish_datums on what is copiable */
switch (indatum->dtype)
{
case PLPGSQL_DTYPE_VAR:
case PLPGSQL_DTYPE_PROMISE:
+
outdatum = (PLpgSQL_datum *) ws_next;
memcpy(outdatum, indatum, sizeof(PLpgSQL_var));
ws_next += MAXALIGN(sizeof(PLpgSQL_var));
@@ -1486,6 +1526,17 @@ plpgsql_fulfill_promise(PLpgSQL_execstate *estate,
assign_text_var(estate, var, GetCommandTagName(estate->evtrigdata->tag));
break;
+ case PLPGSQL_PROMISE_WINDOWOBJECT:
+ if (!estate->winobjproxy)
+ elog(ERROR, "windowobject promise is not in a window function");
+
+ assign_simple_var(estate,
+ var,
+ PointerGetDatum(estate->winobjproxy),
+ false,
+ false);
+ break;
+
default:
elog(ERROR, "unrecognized promise type: %d", var->promise);
}
@@ -2475,6 +2526,17 @@ exec_stmt_getdiag(PLpgSQL_execstate *estate, PLpgSQL_stmt_getdiag *stmt)
}
break;
+ case PLPGSQL_GETDIAG_VALUE_IS_OUT:
+ {
+ if (!estate->winobjproxy)
+ elog(ERROR, "function is not a window function");
+
+ exec_assign_value(estate, var,
+ BoolGetDatum(estate->winobjproxy->mutable_data->isout),
+ false, BOOLOID, -1);
+ }
+ break;
+
default:
elog(ERROR, "unrecognized diagnostic item kind: %d",
diag_item->kind);
diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c
index ee60ced583..992763c735 100644
--- a/src/pl/plpgsql/src/pl_funcs.c
+++ b/src/pl/plpgsql/src/pl_funcs.c
@@ -321,6 +321,8 @@ plpgsql_getdiag_kindname(PLpgSQL_getdiag_kind kind)
return "CONSTRAINT_NAME";
case PLPGSQL_GETDIAG_DATATYPE_NAME:
return "PG_DATATYPE_NAME";
+ case PLPGSQL_GETDIAG_VALUE_IS_OUT:
+ return "PG_VALUE_IS_OUT";
case PLPGSQL_GETDIAG_MESSAGE_TEXT:
return "MESSAGE_TEXT";
case PLPGSQL_GETDIAG_TABLE_NAME:
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 5a7e1a4444..823bbd288b 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -325,6 +325,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
%token <keyword> K_PG_EXCEPTION_CONTEXT
%token <keyword> K_PG_EXCEPTION_DETAIL
%token <keyword> K_PG_EXCEPTION_HINT
+%token <keyword> K_PG_VALUE_IS_OUT
%token <keyword> K_PRINT_STRICT_PARAMS
%token <keyword> K_PRIOR
%token <keyword> K_QUERY
@@ -1081,6 +1082,9 @@ getdiag_item :
else if (tok_is_keyword(tok, &yylval,
K_PG_EXCEPTION_CONTEXT, "pg_exception_context"))
$$ = PLPGSQL_GETDIAG_ERROR_CONTEXT;
+ else if (tok_is_keyword(tok, &yylval,
+ K_PG_VALUE_IS_OUT, "pg_value_is_out"))
+ $$ = PLPGSQL_GETDIAG_VALUE_IS_OUT;
else if (tok_is_keyword(tok, &yylval,
K_COLUMN_NAME, "column_name"))
$$ = PLPGSQL_GETDIAG_COLUMN_NAME;
diff --git a/src/pl/plpgsql/src/pl_unreserved_kwlist.h b/src/pl/plpgsql/src/pl_unreserved_kwlist.h
index 99b3cf7d8a..d27b7dfb85 100644
--- a/src/pl/plpgsql/src/pl_unreserved_kwlist.h
+++ b/src/pl/plpgsql/src/pl_unreserved_kwlist.h
@@ -84,6 +84,7 @@ PG_KEYWORD("pg_datatype_name", K_PG_DATATYPE_NAME)
PG_KEYWORD("pg_exception_context", K_PG_EXCEPTION_CONTEXT)
PG_KEYWORD("pg_exception_detail", K_PG_EXCEPTION_DETAIL)
PG_KEYWORD("pg_exception_hint", K_PG_EXCEPTION_HINT)
+PG_KEYWORD("pg_value_is_out", K_PG_VALUE_IS_OUT)
PG_KEYWORD("print_strict_params", K_PRINT_STRICT_PARAMS)
PG_KEYWORD("prior", K_PRIOR)
PG_KEYWORD("query", K_QUERY)
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 0c3d30fb13..4c1ed7cbf7 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -23,6 +23,8 @@
#include "utils/expandedrecord.h"
#include "utils/typcache.h"
+#include "windowapi.h"
+
/**********************************************************************
* Definitions
@@ -84,7 +86,8 @@ typedef enum PLpgSQL_promise_type
PLPGSQL_PROMISE_TG_NARGS,
PLPGSQL_PROMISE_TG_ARGV,
PLPGSQL_PROMISE_TG_EVENT,
- PLPGSQL_PROMISE_TG_TAG
+ PLPGSQL_PROMISE_TG_TAG,
+ PLPGSQL_PROMISE_WINDOWOBJECT
} PLpgSQL_promise_type;
/*
@@ -159,7 +162,8 @@ typedef enum PLpgSQL_getdiag_kind
PLPGSQL_GETDIAG_DATATYPE_NAME,
PLPGSQL_GETDIAG_MESSAGE_TEXT,
PLPGSQL_GETDIAG_TABLE_NAME,
- PLPGSQL_GETDIAG_SCHEMA_NAME
+ PLPGSQL_GETDIAG_SCHEMA_NAME,
+ PLPGSQL_GETDIAG_VALUE_IS_OUT
} PLpgSQL_getdiag_kind;
/*
@@ -612,6 +616,17 @@ typedef struct PLpgSQL_stmt_getdiag
List *diag_items; /* List of PLpgSQL_diag_item */
} PLpgSQL_stmt_getdiag;
+/*
+ * GET PG_WINDOW_CONTEXT statement
+ */
+typedef struct PLpgSQL_stmt_getwincxt
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ List *items;
+} PLpgSQL_stmt_getwincxt;
+
/*
* IF statement
*/
@@ -1049,6 +1064,8 @@ typedef struct PLpgSQL_execstate
TriggerData *trigdata; /* if regular trigger, data about firing */
EventTriggerData *evtrigdata; /* if event trigger, data about firing */
+ WindowObjectProxy winobjproxy; /* for window function we need proxy
+ * object between PL and WinFucArg funcions */
Datum retval;
bool retisnull;
Oid rettype; /* type of current retval */
diff --git a/src/pl/plpgsql/src/sql/plpgsql_window.sql b/src/pl/plpgsql/src/sql/plpgsql_window.sql
new file mode 100644
index 0000000000..6a3cab39a2
--- /dev/null
+++ b/src/pl/plpgsql/src/sql/plpgsql_window.sql
@@ -0,0 +1,135 @@
+create or replace function pl_row_number()
+returns bigint as $$
+declare pos int8;
+begin
+ pos := get_current_position(windowobject);
+ pos := pos + 1;
+ perform set_mark_position(windowobject, pos);
+ return pos;
+end
+$$
+language plpgsql window;
+
+select pl_row_number() over (), v from (values(10),(20),(30)) v(v);
+
+create or replace function pl_round_value(numeric)
+returns int as $$
+declare
+ num numeric;
+begin
+ num := get_input_value_for_row(windowobject, 1);
+ return round(num);
+end
+$$ language plpgsql window;
+
+select pl_round_value(v) over(order by v desc) from generate_series(0.1, 1.0, 0.1) g(v);
+
+select pl_round_value(v + 1) over(order by v desc) from generate_series(0.1, 1.0, 0.1) g(v);
+
+create table test_table(v numeric);
+insert into test_table values(1),(3),(6),(6),(8),(7),(6),(5),(4);
+
+create or replace function pl_lag(numeric)
+returns numeric as $$
+declare
+ v numeric;
+begin
+ v := get_input_value_in_partition(windowobject, 1, -1, 'seek_current', false);
+ return v;
+end;
+$$ language plpgsql window;
+
+select pl_lag(v) over (), lag(v) over () from test_table;
+
+drop table test_table;
+
+create table test_table(v integer);
+insert into test_table values(1),(3),(6),(6),(8),(7),(6),(5),(4);
+
+select pl_lag(v) over (), lag(v) over () from test_table;
+
+create or replace function pl_moving_avg(numeric)
+returns numeric as $$
+declare
+ s numeric default 0.0;
+ v numeric;
+ c numeric default 0.0;
+begin
+ -- look before
+ v := get_input_value_in_partition(windowobject, 1, -1, 'seek_current', false);
+ if v is not null then
+ s := s + v;
+ c := c + 1.0;
+ end if;
+
+ -- look after
+ v := get_input_value_in_partition(windowobject, 1, 0, 'seek_current', false);
+ if v is not null then
+ s := s + v;
+ c := c + 1.0;
+ end if;
+
+ v := get_input_value_in_partition(windowobject, 1, 1, 'seek_current', false);
+ if v is not null then
+ s := s + v;
+ c := c + 1.0;
+ end if;
+
+ return trim_scale(s / c);
+end
+$$ language plpgsql window;
+
+select pl_moving_avg(v) over (), v from test_table;
+
+create or replace function pl_lag_polymorphic(anyelement)
+returns anyelement as $$
+declare
+ v $0%type;
+begin
+ v := get_input_value_in_partition(windowobject, 1, -1, 'seek_current', false);
+ return v;
+end;
+$$ language plpgsql window;
+
+select pl_lag_polymorphic(v) over (), lag(v) over () from test_table;
+
+create or replace function pl_pcontext_test(numeric)
+returns numeric as $$
+declare
+ n numeric;
+ v numeric;
+begin
+ n := get_partition_context_value(windowobject, null::numeric);
+ v := get_input_value_for_row(windowobject, 1);
+ perform set_partition_context_value(windowobject, v);
+
+ return n;
+end
+$$
+language plpgsql window;
+
+select v, pl_pcontext_test(v) over () from generate_series(0.1, 1.0, 0.1) g(v);
+
+create table test_missing_values(id int, v integer);
+insert into test_missing_values values(1,10),(2,11),(3,12),(4,null),(5,null),(6,15),(7,16);
+
+create or replace function pl_pcontext_test(numeric)
+returns numeric as $$
+declare
+ n numeric;
+ v numeric;
+begin
+ v := get_input_value_for_row(windowobject, 1);
+
+ if v is null then
+ v := get_partition_context_value(windowobject, null::numeric);
+ else
+ perform set_partition_context_value(windowobject, v);
+ end if;
+
+ return v;
+end
+$$
+language plpgsql window;
+
+select id, v, pl_pcontext_test(v) over (order by id) from test_missing_values;
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index 274130e706..85391f9a3e 100644
--- a/src/test/regress/expected/type_sanity.out
+++ b/src/test/regress/expected/type_sanity.out
@@ -73,7 +73,8 @@ WHERE p1.typtype not in ('c','d','p') AND p1.typname NOT LIKE E'\\_%'
3361 | pg_ndistinct
3402 | pg_dependencies
5017 | pg_mcv_list
-(4 rows)
+ 9553 | typedvalue
+(5 rows)
-- Make sure typarray points to a varlena array type of our own base
SELECT p1.oid, p1.typname as basetype, p2.typname as arraytype,
pá 28. 8. 2020 v 8:14 odesílatel Pavel Stehule <pavel.stehule@gmail.com>
napsal:
st 26. 8. 2020 v 17:06 odesílatel Pavel Stehule <pavel.stehule@gmail.com>
napsal:Hi
I simplified access to results of winfuncargs functions by proxy type
"typedvalue". This type can hold any Datum value, and allows fast cast to
basic buildin types or it can use (slower) generic cast functions. It is
used in cooperation with a plpgsql assign statement that can choose the
correct cast implicitly. When the winfuncarg function returns a value of
the same type, that is expected by the variable on the left side of the
assign statement, then (for basic types), the value is just copied without
casts. With this proxy type is not necessary to have special statement for
assigning returned value from winfuncargs functions, so source code of
window function in plpgsql looks intuitive to me.Example - implementation of "lag" function in plpgsql
create or replace function pl_lag(numeric)
returns numeric as $$
declare v numeric;
begin
v := get_input_value_in_partition(windowobject, 1, -1, 'seek_current',
false);
return v;
end;
$$ language plpgsql window;I think this code is usable, and I assign this patch to commitfest.
Regards
Pavel
fix regress tests and some doc
update - refactored implementation typedvalue type
Attachments:
plpgsql-window-functions-20200828-2.patchtext/x-patch; charset=US-ASCII; name=plpgsql-window-functions-20200828-2.patchDownload
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index 815912666d..5b4d5bbac4 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -4568,6 +4568,99 @@ CREATE EVENT TRIGGER snitch ON ddl_command_start EXECUTE FUNCTION snitch();
</sect1>
+ <sect1 id="plpgsql-window">
+ <title>Window Functions</title>
+
+ <indexterm zone="plpgsql-window">
+ <primary>window</primary>
+ <secondary>in PL/pgSQL</secondary>
+ </indexterm>
+
+ <para>
+ <application>PL/pgSQL</application> can be used to define window
+ functions. A window function is created with the <command>CREATE FUNCTION</command>
+ command with clause <literal>WINDOW</literal>. The specific feature of
+ this functions is a possibility to two special storages with
+ sorted values of window function arguments and store with stored
+ one value of any type for currently processed partition (of window
+ function).
+ </para>
+
+ <para>
+ Access to both storages is done with special internal variable
+ <varname>WINDOWOBJECT</varname>. This variable is declared implicitly,
+ and it is available only in window functions.
+
+<programlisting>
+CREATE OR REPLACE FUNCTION plpgsql_rownum() RETURNS int8
+LANGUAGE plpgsql WINDOW
+AS $$
+DECLARE pos int8
+BEGIN
+ pos := get_current_position(WINDOWOBJECT);
+ pos := pos + 1;
+ PERFORM set_mark_position(WINDOWOBJECT, pos);
+RETURN pos;
+$$;
+
+SELECT plpgsql_rownum() OVER (), * FROM tab;
+</programlisting>
+ </para>
+
+ <para>
+ The arguments of window function cannot be accessed directly. The special
+ functions should be used. With these functions we can choose a scope of
+ buffered arguments, we can choose a wanted position against first, current, or
+ last row. The implementation of <function>lag</function> can looks like
+ (the window functions in plpgsql can use polymorphic types too):
+
+<programlisting>
+CREATE OR REPLACE FUNCTION plpgsql_lag(anyelement) RETURNS anyelement
+LANGUAGE plpgsql WINDOW
+AS $$
+BEGIN
+ RETURN
+ get_input_value_in_partition(WINDOWOBJECT,
+ 1, -1,
+ 'seek_current',
+ false);
+END;
+$$;
+
+SELECT v, plpgsql_lag(v) FROM generate_series(1, 10) g(v);
+</programlisting>
+
+ </para>
+
+ <para>
+ Second buffer that can be used in window function is a buffer for one value
+ assigned to partition. The content of this buffer can be read by function
+ <function>get_partition_context_value</function> or modified by function
+ <function>set_partition_context_value</function>. Next function replaces
+ missing values by previous non <literal>NULL</literal> value:
+
+<programlisting>
+CREATE OR REPLACE FUNCTION plpgsql_replace_missing(numeric) RETURNS numeric
+LANGUAGE plpgsql WINDOW
+AS $$
+DECLATE
+ v numeric;
+BEGIN
+ v := get_input_value_for_row(WINDOWOBJECT, 1);
+ IF v IS NULL THEN
+ v := get_partition_context_value(WINDOWOBJECT, NULL::numeric);
+ ELSE
+ PERFORM set_partition_context_value(WINDOWOBJECT, v);
+ END IF;
+ RETURN v;
+END;
+$$;
+</programlisting>
+
+ </para>
+
+ </sect1>
+
<sect1 id="plpgsql-implementation">
<title><application>PL/pgSQL</application> under the Hood</title>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index a2d61302f9..f926f2e386 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1421,6 +1421,18 @@ LANGUAGE internal
STRICT IMMUTABLE PARALLEL SAFE
AS 'unicode_is_normalized';
+CREATE OR REPLACE FUNCTION
+ get_partition_context_value(windowobjectproxy, anyelement, int4 DEFAULT NULL)
+ RETURNS anyelement
+LANGUAGE internal
+AS 'windowobject_get_partition_context_value';
+
+CREATE OR REPLACE FUNCTION
+ set_partition_context_value(windowobjectproxy, anyelement, int4 DEFAULT NULL)
+ RETURNS void
+LANGUAGE internal
+AS 'windowobject_set_partition_context_value';
+
--
-- The default permissions for functions mean that anyone can execute them.
-- A number of functions shouldn't be executable by just anyone, but rather
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 54d5c37947..84da7222d9 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -102,6 +102,7 @@ OBJS = \
tsvector.o \
tsvector_op.o \
tsvector_parser.o \
+ typedvalue.o \
uuid.o \
varbit.o \
varchar.o \
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index 3d6b2f9093..ebb2a16572 100644
--- a/src/backend/utils/adt/pseudotypes.c
+++ b/src/backend/utils/adt/pseudotypes.c
@@ -334,6 +334,17 @@ pg_node_tree_send(PG_FUNCTION_ARGS)
PSEUDOTYPE_DUMMY_IO_FUNCS(pg_ddl_command);
PSEUDOTYPE_DUMMY_BINARY_IO_FUNCS(pg_ddl_command);
+/*
+ * windowobjectproxy
+ *
+ * This type is pointer to WindowObjectProxyData. It is communication
+ * mechanism between PL environment and WinFuncArgs functions. Due
+ * performance reason I prefer using indirect result processing against
+ * using function returning polymorphic composite value. The indirect
+ * mechanism is implemented with proxy object represented by type
+ * WindowObjectProxyData.
+ */
+PSEUDOTYPE_DUMMY_IO_FUNCS(windowobjectproxy);
/*
* Dummy I/O functions for various other pseudotypes.
diff --git a/src/backend/utils/adt/typedvalue.c b/src/backend/utils/adt/typedvalue.c
new file mode 100644
index 0000000000..370804a05a
--- /dev/null
+++ b/src/backend/utils/adt/typedvalue.c
@@ -0,0 +1,589 @@
+/*-------------------------------------------------------------------------
+ *
+ * typedvalue.c
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/typedvalue.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "catalog/pg_type.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/lsyscache.h"
+#include "utils/numeric.h"
+#include "utils/typedvalue.h"
+#include "fmgr.h"
+
+/*
+ * Returns Datum value stored in TypedValue structure.
+ * This structure can hold byval or varlena values.
+ */
+static Datum
+TypedValueGetDatum(TypedValue tv)
+{
+ if (tv->typbyval)
+ return *((Datum *) tv->data);
+ else
+ return PointerGetDatum(tv->data);
+}
+
+/*
+ * IN function for TypedValue type. It store input value as text type
+ * value.
+ */
+Datum
+typedvalue_in(PG_FUNCTION_ARGS)
+{
+ char *str = PG_GETARG_CSTRING(0);
+ int len;
+ Size size;
+ TypedValue tv;
+ text *txt;
+
+ len = strlen(str);
+
+ size = MAXALIGN(offsetof(TypedValueData, data) + len + VARHDRSZ);
+
+ tv = (TypedValue) palloc(size);
+ SET_VARSIZE(tv, size);
+
+ txt = (text *) tv->data;
+
+ SET_VARSIZE(txt, VARHDRSZ + len);
+ memcpy(VARDATA(txt), str, len);
+
+ tv->typid = TEXTOID;
+ tv->typbyval = false;
+ tv->typlen = -1;
+
+ PG_RETURN_POINTER(tv);
+}
+
+/*
+ * OUT function for TypedValue type. It search related output
+ * function for stored type, execute it, and returns result.
+ */
+Datum
+typedvalue_out(PG_FUNCTION_ARGS)
+{
+ Oid typOutput;
+ bool isVarlena;
+ char *str;
+ TypedValue tv;
+ Datum value;
+
+ tv = (TypedValue) PG_GETARG_POINTER(0);
+
+ getTypeOutputInfo(tv->typid, &typOutput, &isVarlena);
+ value = TypedValueGetDatum(tv);
+ str = OidOutputFunctionCall(typOutput, value);
+
+ PG_RETURN_CSTRING(str);
+}
+
+/*
+ * Constructor function for TypedValue type. It serializes
+ * byval or varlena Datum values.
+ */
+Datum
+makeTypedValue(Datum value, Oid typid, int16 typlen, bool typbyval)
+{
+ TypedValue tv;
+ Size size;
+ Size copy_bytes = 0;
+
+ if (typbyval)
+ size = MAXALIGN(offsetof(TypedValueData, data) + sizeof(Datum));
+ else
+ {
+ if (typlen != -1)
+ {
+ size = MAXALIGN(offsetof(TypedValueData, data) + typlen);
+ copy_bytes = typlen;
+ }
+ else
+ {
+ copy_bytes = VARSIZE_ANY((struct varlena *) DatumGetPointer(value));
+ size = MAXALIGN(offsetof(TypedValueData, data) + copy_bytes);
+ }
+ }
+
+ tv = (TypedValue) palloc(size);
+
+ SET_VARSIZE(tv, size);
+
+ tv->typid = typid;
+ tv->typlen = typlen;
+ tv->typbyval = typbyval;
+
+ if (typbyval)
+ *((Datum *) tv->data) = value;
+ else
+ memcpy(tv->data, DatumGetPointer(value), copy_bytes);
+
+ return PointerGetDatum(tv);
+}
+
+/*
+ * Fast cast finding of known (buildin) cast functions.
+ */
+static PGFunction
+get_direct_cast_func(Oid source_typid, Oid target_typid)
+{
+ switch (target_typid)
+ {
+ case NUMERICOID:
+ {
+ switch (source_typid)
+ {
+ case INT4OID:
+ return int4_numeric;
+
+ case INT8OID:
+ return int8_numeric;
+
+ case FLOAT4OID:
+ return float4_numeric;
+
+ case FLOAT8OID:
+ return float8_numeric;
+
+ default:
+ return NULL;
+ }
+ }
+
+ case INT8OID:
+ {
+ switch (source_typid)
+ {
+ case INT4OID:
+ return int48;
+
+ case NUMERICOID:
+ return numeric_int8;
+
+ case FLOAT4OID:
+ return ftoi8;
+
+ case FLOAT8OID:
+ return dtoi8;
+
+ default:
+ return NULL;
+ }
+ }
+
+ case INT4OID:
+ {
+ switch (source_typid)
+ {
+ case BOOLOID:
+ return bool_int4;
+
+ case INT8OID:
+ return int84;
+
+ case NUMERICOID:
+ return numeric_int4;
+
+ case FLOAT4OID:
+ return ftoi4;
+
+ case FLOAT8OID:
+ return dtoi4;
+
+ default:
+ return NULL;
+ }
+ }
+
+ case FLOAT8OID:
+ {
+ switch (source_typid)
+ {
+ case INT4OID:
+ return i4tod;
+
+ case INT8OID:
+ return i8tod;
+
+ case NUMERICOID:
+ return numeric_float8;
+
+ case FLOAT4OID:
+ return ftod;
+
+ default:
+ return NULL;
+ }
+ }
+
+ case FLOAT4OID:
+ {
+ switch (source_typid)
+ {
+ case INT4OID:
+ return i4tof;
+
+ case INT8OID:
+ return i8tof;
+
+ case NUMERICOID:
+ return numeric_float4;
+
+ case FLOAT8OID:
+ return dtof;
+
+ default:
+ return NULL;
+ }
+ }
+
+ case DATEOID:
+ {
+ switch (source_typid)
+ {
+ case TIMESTAMPOID:
+ return timestamp_date;
+
+ case TIMESTAMPTZOID:
+ return timestamptz_date;
+
+ default:
+ return NULL;
+ }
+ }
+
+ case TIMESTAMPOID:
+ {
+ switch (source_typid)
+ {
+ case DATEOID:
+ return date_timestamp;
+
+ case TIMESTAMPTZOID:
+ return timestamptz_timestamp;
+
+ default:
+ return NULL;
+ }
+ }
+
+ case TIMESTAMPTZOID:
+ {
+ switch (source_typid)
+ {
+ case DATEOID:
+ return date_timestamptz;
+
+ case TIMESTAMPOID:
+ return timestamp_timestamptz;
+
+ default:
+ return NULL;
+ }
+ }
+
+ case BOOLOID:
+ {
+ switch (source_typid)
+ {
+ case INT4OID:
+ return int4_bool;
+
+ default:
+ return NULL;
+ }
+ }
+
+ default:
+ return NULL;
+ }
+}
+
+static Datum
+cast_typedvalue_to(TypedValue tv, Oid target_typid)
+{
+ Datum value = TypedValueGetDatum(tv);
+ Datum result;
+ char *str;
+ PGFunction cast_func;
+ Oid cast_func_oid;
+ Oid typinput;
+ Oid typioparam;
+ Oid typoutput;
+ bool isvarlena;
+
+ if (tv->typid == target_typid)
+ return datumCopy(value,
+ tv->typbyval,
+ tv->typlen);
+
+ if (tv->typid == TEXTOID || tv->typid == BPCHAROID)
+ {
+ getTypeInputInfo(target_typid, &typinput, &typioparam);
+ str = TextDatumGetCString(value);
+ result = OidInputFunctionCall(typinput, str,
+ typioparam, -1);
+ pfree(str);
+
+ return result;
+ }
+
+ if (target_typid == TEXTOID || target_typid == BPCHAROID)
+ {
+ getTypeOutputInfo(tv->typid, &typoutput, &isvarlena);
+ str = OidOutputFunctionCall(typoutput, value);
+ result = PointerGetDatum(cstring_to_text(str));
+
+ return result;
+ }
+
+ /* fast cast func detection, and direct call */
+ cast_func = get_direct_cast_func(tv->typid, target_typid);
+ if (cast_func)
+ {
+ result = DirectFunctionCall1(cast_func, value);
+
+ return result;
+ }
+
+ /* slower cast func, and indirect call */
+ cast_func_oid = get_cast_oid(tv->typid, target_typid, false);
+ if (OidIsValid(cast_func_oid))
+ {
+ result = OidFunctionCall1(cast_func_oid, value);
+
+ return result;
+ }
+
+ /* IO cast - most slow */
+ getTypeOutputInfo(tv->typid, &typoutput, &isvarlena);
+ str = OidOutputFunctionCall(typoutput, value);
+
+ getTypeInputInfo(target_typid, &typinput, &typioparam);
+ result = OidInputFunctionCall(typinput, str,
+ typioparam, -1);
+ pfree(str);
+
+ return result;
+}
+
+/*
+ * Casting functions - from variant typedvalue to specific types
+ */
+Datum
+typedvalue_to_numeric(PG_FUNCTION_ARGS)
+{
+ TypedValue tv = (TypedValue) PG_GETARG_POINTER(0);
+
+ PG_RETURN_DATUM(cast_typedvalue_to(tv, NUMERICOID));
+}
+
+Datum
+typedvalue_to_int8(PG_FUNCTION_ARGS)
+{
+ TypedValue tv = (TypedValue) PG_GETARG_POINTER(0);
+
+ PG_RETURN_DATUM(cast_typedvalue_to(tv, INT8OID));
+}
+
+Datum
+typedvalue_to_int4(PG_FUNCTION_ARGS)
+{
+ TypedValue tv = (TypedValue) PG_GETARG_POINTER(0);
+
+ PG_RETURN_DATUM(cast_typedvalue_to(tv, INT4OID));
+}
+
+Datum
+typedvalue_to_float8(PG_FUNCTION_ARGS)
+{
+ TypedValue tv = (TypedValue) PG_GETARG_POINTER(0);
+
+ PG_RETURN_DATUM(cast_typedvalue_to(tv, FLOAT8OID));
+}
+
+Datum
+typedvalue_to_float4(PG_FUNCTION_ARGS)
+{
+ TypedValue tv = (TypedValue) PG_GETARG_POINTER(0);
+
+ PG_RETURN_DATUM(cast_typedvalue_to(tv, FLOAT4OID));
+}
+
+Datum
+typedvalue_to_date(PG_FUNCTION_ARGS)
+{
+ TypedValue tv = (TypedValue) PG_GETARG_POINTER(0);
+
+ PG_RETURN_DATUM(cast_typedvalue_to(tv, DATEOID));
+}
+
+Datum
+typedvalue_to_timestamp(PG_FUNCTION_ARGS)
+{
+ TypedValue tv = (TypedValue) PG_GETARG_POINTER(0);
+
+ PG_RETURN_DATUM(cast_typedvalue_to(tv, TIMESTAMPOID));
+}
+
+Datum
+typedvalue_to_timestamptz(PG_FUNCTION_ARGS)
+{
+ TypedValue tv = (TypedValue) PG_GETARG_POINTER(0);
+
+ PG_RETURN_DATUM(cast_typedvalue_to(tv, TIMESTAMPTZOID));
+}
+
+Datum
+typedvalue_to_interval(PG_FUNCTION_ARGS)
+{
+ TypedValue tv = (TypedValue) PG_GETARG_POINTER(0);
+
+ PG_RETURN_DATUM(cast_typedvalue_to(tv, INTERVALOID));
+}
+
+Datum
+typedvalue_to_text(PG_FUNCTION_ARGS)
+{
+ TypedValue tv = (TypedValue) PG_GETARG_POINTER(0);
+
+ PG_RETURN_DATUM(cast_typedvalue_to(tv, TEXTOID));
+}
+
+Datum
+typedvalue_to_bpchar(PG_FUNCTION_ARGS)
+{
+ TypedValue tv = (TypedValue) PG_GETARG_POINTER(0);
+
+ PG_RETURN_DATUM(cast_typedvalue_to(tv, BPCHAROID));
+}
+
+Datum
+typedvalue_to_bool(PG_FUNCTION_ARGS)
+{
+ TypedValue tv = (TypedValue) PG_GETARG_POINTER(0);
+
+ PG_RETURN_DATUM(cast_typedvalue_to(tv, BOOLOID));
+}
+
+/*
+ * Cast function from specific types to TypedValue
+ */
+Datum
+numeric_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ NUMERICOID,
+ -1,
+ false));
+}
+
+Datum
+int8_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ INT8OID,
+ 8,
+ true));
+}
+
+Datum
+int4_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ INT4OID,
+ 4,
+ true));
+}
+
+Datum
+float8_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ FLOAT8OID,
+ 8,
+ true));
+}
+
+Datum
+float4_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ FLOAT4OID,
+ 4,
+ true));
+}
+
+
+Datum
+date_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ DATEOID,
+ 4,
+ true));
+}
+
+Datum
+timestamp_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ TIMESTAMPOID,
+ 8,
+ true));
+}
+
+Datum
+timestamptz_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ TIMESTAMPTZOID,
+ 8,
+ true));
+}
+
+Datum
+interval_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ INTERVALOID,
+ 16,
+ false));
+}
+
+Datum
+text_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ TEXTOID,
+ -1,
+ false));
+}
+
+Datum
+bpchar_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ BPCHAROID,
+ -1,
+ false));
+}
+
+Datum
+bool_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ BOOLOID,
+ 1,
+ true));
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index f0c8ae686d..e6bee14899 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -14,6 +14,9 @@
#include "postgres.h"
#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/lsyscache.h"
+#include "utils/typedvalue.h"
#include "windowapi.h"
/*
@@ -35,6 +38,20 @@ typedef struct
int64 remainder; /* (total rows) % (bucket num) */
} ntile_context;
+#define PROXY_CONTEXT_MAGIC 19730715
+
+typedef struct
+{
+ int magic;
+ Oid typid;
+ int16 typlen;
+ bool typbyval;
+ int allocsize;
+ bool isnull;
+ Datum value;
+ char data[FLEXIBLE_ARRAY_MEMBER];
+} proxy_context;
+
static bool rank_up(WindowObject winobj);
static Datum leadlag_common(FunctionCallInfo fcinfo,
bool forward, bool withoffset, bool withdefault);
@@ -472,3 +489,467 @@ window_nth_value(PG_FUNCTION_ARGS)
PG_RETURN_DATUM(result);
}
+
+/*
+ * High level access function. These functions are wrappers for windows API
+ * for PL languages based on usage WindowObjectProxy.
+ */
+Datum
+windowobject_get_current_position(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int64 pos;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ pos = WinGetCurrentPosition(winobj);
+
+ PG_RETURN_INT64(pos);
+}
+
+Datum
+windowobject_set_mark_position(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int64 pos;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ pos = PG_GETARG_INT64(1);
+
+ WinSetMarkPosition(winobj, pos);
+
+ PG_RETURN_VOID();
+}
+
+Datum
+windowobject_get_partition_rowcount(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int64 rc;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ rc = WinGetPartitionRowCount(winobj);
+
+ PG_RETURN_INT64(rc);
+}
+
+Datum
+windowobject_rows_are_peers(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int64 pos1,
+ pos2;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ pos1 = PG_GETARG_INT64(1);
+ pos2 = PG_GETARG_INT64(2);
+
+ PG_RETURN_BOOL(WinRowsArePeers(winobj, pos1, pos2));
+}
+
+#define SEEK_CURRENT_STR "seek_current"
+#define SEEK_HEAD_STR "seek_head"
+#define SEEK_TAIL_STR "seek_tail"
+
+#define STRLEN(s) (sizeof(s) - 1)
+
+static int
+get_seek_type(text *seektype)
+{
+ char *str;
+ int len;
+ int result;
+
+ str = VARDATA_ANY(seektype);
+ len = VARSIZE_ANY_EXHDR(seektype);
+
+ if (len == STRLEN(SEEK_CURRENT_STR) && strncmp(str, SEEK_CURRENT_STR, len) == 0)
+ result = WINDOW_SEEK_CURRENT;
+ else if (len == STRLEN(SEEK_HEAD_STR) && strncmp(str, SEEK_HEAD_STR, len) == 0)
+ result = WINDOW_SEEK_HEAD;
+ else if (len == STRLEN(SEEK_TAIL_STR) && strncmp(str, SEEK_TAIL_STR, len) == 0)
+ result = WINDOW_SEEK_TAIL;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("seek type value must be \"seek_current\", \"seek_head\" or \"seek_tail\"")));
+
+ return result;
+}
+
+static Oid
+wop_funcarg_info(WindowObjectProxy wop,
+ int argno,
+ int16 *typlen,
+ bool *typbyval)
+{
+ WindowObjectProxyMutable *mutable_data = wop->mutable_data;
+
+ if (argno != mutable_data->last_argno)
+ {
+ Oid argtypid = get_fn_expr_argtype(wop->fcinfo->flinfo, argno);
+
+ mutable_data->typid = getBaseType(argtypid);
+ get_typlenbyval(mutable_data->typid,
+ &mutable_data->typlen,
+ &mutable_data->typbyval);
+ mutable_data->last_argno = argno;
+ }
+
+ *typlen = mutable_data->typlen;
+ *typbyval = mutable_data->typbyval;
+
+ return mutable_data->typid;
+}
+
+Datum
+windowobject_get_func_arg_partition(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int argno;
+ int relpos;
+ int seektype;
+ bool set_mark;
+ Datum value;
+ bool isnull;
+ Oid typid;
+ int16 typlen;
+ bool typbyval;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ argno = PG_GETARG_INT32(1);
+
+ if (argno < 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("arg number less than one")));
+
+ argno -= 1;
+
+ relpos = PG_GETARG_INT32(2);
+ seektype = get_seek_type(PG_GETARG_TEXT_P(3));
+ set_mark = PG_GETARG_BOOL(4);
+
+ value = WinGetFuncArgInPartition(winobj,
+ argno,
+ relpos,
+ seektype,
+ set_mark,
+ &isnull,
+ &wop->mutable_data->isout);
+
+ if (isnull)
+ PG_RETURN_NULL();
+
+ typid = wop_funcarg_info(wop, argno, &typlen, &typbyval);
+
+ PG_RETURN_DATUM(makeTypedValue(value, typid, typlen, typbyval));
+}
+
+Datum
+windowobject_get_func_arg_frame(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int argno;
+ int relpos;
+ int seektype;
+ bool set_mark;
+ Datum value;
+ bool isnull;
+ Oid typid;
+ int16 typlen;
+ bool typbyval;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ argno = PG_GETARG_INT32(1);
+ relpos = PG_GETARG_INT32(2);
+ seektype = get_seek_type(PG_GETARG_TEXT_P(3));
+ set_mark = PG_GETARG_BOOL(4);
+
+ if (argno < 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("arg number less than one")));
+
+ argno -= 1;
+
+ value = WinGetFuncArgInFrame(winobj,
+ argno,
+ relpos,
+ seektype,
+ set_mark,
+ &isnull,
+ &wop->mutable_data->isout);
+
+ if (isnull)
+ PG_RETURN_NULL();
+
+ typid = wop_funcarg_info(wop, argno, &typlen, &typbyval);
+
+ PG_RETURN_DATUM(makeTypedValue(value, typid, typlen, typbyval));
+}
+
+Datum
+windowobject_get_func_arg_current(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int argno;
+ Datum value;
+ bool isnull;
+ Oid typid;
+ int16 typlen;
+ bool typbyval;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ winobj = wop->winobj;
+
+ Assert(WindowObjectIsValid(winobj));
+
+ argno = PG_GETARG_INT32(1);
+
+ if (argno < 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("arg number less than one")));
+
+ argno -= 1;
+
+ value = WinGetFuncArgCurrent(winobj, argno, &isnull);
+
+ wop->mutable_data->isout = false;
+
+ if (isnull)
+ PG_RETURN_NULL();
+
+ typid = wop_funcarg_info(wop, argno, &typlen, &typbyval);
+ PG_RETURN_DATUM(makeTypedValue(value, typid, typlen, typbyval));
+}
+
+static void
+copy_datum_to_partition_context(proxy_context *pcontext,
+ Datum value,
+ bool isnull)
+{
+ if (!isnull)
+ {
+ if (pcontext->typbyval)
+ pcontext->value = value;
+ else if (pcontext->typlen == -1)
+ {
+ struct varlena *s = (struct varlena *) DatumGetPointer(value);
+
+ memcpy(pcontext->data, s, VARSIZE_ANY(s));
+ pcontext->value = PointerGetDatum(pcontext->data);
+ }
+ else
+ {
+ memcpy(pcontext->data, DatumGetPointer(value), pcontext->typlen);
+ pcontext->value = PointerGetDatum(pcontext->data);
+ }
+
+ pcontext->isnull = false;
+ }
+ else
+ {
+ pcontext->value = (Datum) 0;
+ pcontext->isnull = true;
+ }
+}
+
+/*
+ * Returns estimated size of windowobject partition context
+ */
+static int
+estimate_partition_context_size(Datum value,
+ bool isnull,
+ int16 typlen,
+ int16 minsize,
+ int *realsize)
+{
+ if(typlen != -1)
+ {
+ if (typlen < sizeof(Datum))
+ {
+ *realsize = offsetof(proxy_context, data);
+
+ return *realsize;
+ }
+
+ if (typlen > 1024)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("size of value is greather than limit (1024 bytes)")));
+
+ *realsize = offsetof(proxy_context, data) + typlen;
+
+ return *realsize;
+ }
+ else
+ {
+ if (!isnull)
+ {
+ int size = VARSIZE_ANY_EXHDR(DatumGetPointer(value));
+
+ if (size > 1024)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("size of value is greather than limit (1024 bytes)")));
+
+ *realsize = size;
+
+ size += size / 3;
+
+ return offsetof(proxy_context, data)
+ + MAXALIGN(size > minsize ? size : minsize);
+ }
+ else
+ {
+ /* by default we allocate 30 bytes */
+ *realsize = 0;
+
+ return offsetof(proxy_context, data) + MAXALIGN(minsize);
+ }
+ }
+}
+
+#define VARLENA_MINSIZE 32
+
+static proxy_context *
+get_partition_context(FunctionCallInfo fcinfo, bool write_mode)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ Oid typid;
+ int16 typlen;
+ bool typbyval;
+ Datum value = (Datum) 0;
+ bool isnull = true;
+ int allocsize;
+ int minsize;
+ int realsize;
+ proxy_context *pcontext;
+
+ if (PG_ARGISNULL(0))
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("windowobject is NULL")));
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ if (PG_ARGISNULL(2))
+ minsize = VARLENA_MINSIZE;
+ else
+ minsize = PG_GETARG_INT32(2);
+
+ if (!PG_ARGISNULL(1))
+ {
+ value = PG_GETARG_DATUM(1);
+ isnull = false;
+ }
+
+ typid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ if (!OidIsValid(typid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot detect type of context value")));
+
+ typid = getBaseType(typid);
+ get_typlenbyval(typid, &typlen, &typbyval);
+
+ Assert(typlen != -2);
+
+ allocsize = estimate_partition_context_size(value,
+ isnull,
+ typlen,
+ minsize,
+ &realsize);
+
+ pcontext = (proxy_context *) WinGetPartitionLocalMemory(winobj, allocsize);
+
+ /* fresh pcontext has zeroed memory */
+ Assert(pcontext->magic == 0 || pcontext->magic == PROXY_CONTEXT_MAGIC);
+
+ if (pcontext->allocsize > 0)
+ {
+ if (realsize > pcontext->allocsize)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("the value cannot be saved to allocated buffer"),
+ errhint("Try to increase the minsize argument.")));
+
+ if (pcontext->typid != typid)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("partition context was initialized for different type")));
+
+ if (write_mode)
+ copy_datum_to_partition_context(pcontext, value, isnull);
+
+ }
+ else
+ {
+ pcontext->magic = PROXY_CONTEXT_MAGIC;
+ pcontext->typid = typid;
+ pcontext->typlen = typlen;
+ pcontext->typbyval = typbyval;
+ pcontext->allocsize = allocsize;
+
+ copy_datum_to_partition_context(pcontext, value, isnull);
+ }
+
+ return pcontext;
+}
+
+Datum
+windowobject_set_partition_context_value(PG_FUNCTION_ARGS)
+{
+ (void) get_partition_context(fcinfo, true);
+
+ PG_RETURN_VOID();
+}
+
+Datum
+windowobject_get_partition_context_value(PG_FUNCTION_ARGS)
+{
+ proxy_context *pcontext;
+
+ pcontext = get_partition_context(fcinfo, false);
+
+ if (pcontext->isnull)
+ PG_RETURN_NULL();
+
+ PG_RETURN_DATUM(pcontext->value);
+}
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index 5a58f50fbb..597de4daa9 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -530,4 +530,56 @@
{ castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
castcontext => 'e', castmethod => 'f' },
+# Allow explicit coercions between typedvalue and other types
+
+
+{ castsource => 'typedvalue', casttarget => 'numeric', castfunc => 'to_numeric(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'int8', castfunc => 'to_int8(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'int4', castfunc => 'to_int4(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'float8', castfunc => 'to_float8(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'float4', castfunc => 'to_float4(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'date', castfunc => 'to_date(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'timestamp', castfunc => 'to_timestamp(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'timestamptz', castfunc => 'to_timestamptz(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'interval', castfunc => 'to_interval(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'text', castfunc => 'to_text(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'varchar', castfunc => 'to_varchar(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'bool', castfunc => 'to_bool(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'numeric', casttarget => 'typedvalue', castfunc => 'to_typedvalue(numeric)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'int8', casttarget => 'typedvalue', castfunc => 'to_typedvalue(int8)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'int4', casttarget => 'typedvalue', castfunc => 'to_typedvalue(int4)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'float8', casttarget => 'typedvalue', castfunc => 'to_typedvalue(float8)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'float4', casttarget => 'typedvalue', castfunc => 'to_typedvalue(float4)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'date', casttarget => 'typedvalue', castfunc => 'to_typedvalue(date)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'timestamp', casttarget => 'typedvalue', castfunc => 'to_typedvalue(timestamp)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'timestamptz', casttarget => 'typedvalue', castfunc => 'to_typedvalue(timestamptz)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'interval', casttarget => 'typedvalue', castfunc => 'to_typedvalue(interval)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'text', casttarget => 'typedvalue', castfunc => 'to_typedvalue(text)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'varchar', casttarget => 'typedvalue', castfunc => 'to_typedvalue(varchar)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'bool', casttarget => 'typedvalue', castfunc => 'to_typedvalue(bool)',
+ castcontext => 'e', castmethod => 'f' },
+
]
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 27989971db..213d7c5440 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7130,6 +7130,18 @@
{ oid => '2305', descr => 'I/O',
proname => 'internal_out', prorettype => 'cstring', proargtypes => 'internal',
prosrc => 'internal_out' },
+{ oid => '9554', descr => 'I/O',
+ proname => 'windowobjectproxy_in', proisstrict => 'f', prorettype => 'windowobjectproxy',
+ proargtypes => 'cstring', prosrc => 'windowobjectproxy_in' },
+{ oid => '9555', descr => 'I/O',
+ proname => 'windowobjectproxy_out', prorettype => 'cstring', proargtypes => 'windowobjectproxy',
+ prosrc => 'windowobjectproxy_out' },
+{ oid => '9556', descr => 'I/O',
+ proname => 'typedvalue_in', proisstrict => 'f', prorettype => 'typedvalue',
+ proargtypes => 'cstring', prosrc => 'typedvalue_in' },
+{ oid => '9557', descr => 'I/O',
+ proname => 'typedvalue_out', prorettype => 'cstring', proargtypes => 'typedvalue',
+ prosrc => 'typedvalue_out' },
{ oid => '2312', descr => 'I/O',
proname => 'anyelement_in', prorettype => 'anyelement',
proargtypes => 'cstring', prosrc => 'anyelement_in' },
@@ -7219,6 +7231,80 @@
prorettype => 'cstring', proargtypes => 'anycompatiblerange',
prosrc => 'anycompatiblerange_out' },
+# typedvalue cast functions
+{ oid => '9567', descr => 'format typedvalue to numeric',
+ proname => 'to_numeric', provolatile => 's', prorettype => 'numeric',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_numeric' },
+{ oid => '9568', descr => 'format typedvalue to int8',
+ proname => 'to_int8', provolatile => 's', prorettype => 'int8',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_int8' },
+{ oid => '9569', descr => 'format typedvalue to int4',
+ proname => 'to_int4', provolatile => 's', prorettype => 'int4',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_int4' },
+{ oid => '9570', descr => 'format typedvalue to float8',
+ proname => 'to_float8', provolatile => 's', prorettype => 'float8',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_float8' },
+{ oid => '9571', descr => 'format typedvalue to float4',
+ proname => 'to_float4', provolatile => 's', prorettype => 'float4',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_float4' },
+{ oid => '9572', descr => 'format typedvalue to date',
+ proname => 'to_date', provolatile => 's', prorettype => 'date',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_date' },
+{ oid => '9573', descr => 'format typedvalue to timestamp',
+ proname => 'to_timestamp', provolatile => 's', prorettype => 'timestamp',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_timestamp' },
+{ oid => '9574', descr => 'format typedvalue to timestamptz',
+ proname => 'to_timestamptz', provolatile => 's', prorettype => 'timestamptz',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_timestamptz' },
+{ oid => '9575', descr => 'format typedvalue to interval',
+ proname => 'to_interval', provolatile => 's', prorettype => 'interval',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_interval' },
+{ oid => '9576', descr => 'format typedvalue to text',
+ proname => 'to_text', provolatile => 's', prorettype => 'text',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_text' },
+{ oid => '9577', descr => 'format typedvalue to varchar',
+ proname => 'to_varchar', provolatile => 's', prorettype => 'varchar',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_bpchar' },
+{ oid => '9578', descr => 'format numeric to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'numeric', prosrc => 'numeric_to_typedvalue' },
+{ oid => '9579', descr => 'format int8 to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'int8', prosrc => 'int8_to_typedvalue' },
+{ oid => '9580', descr => 'format int4 to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'int4', prosrc => 'int4_to_typedvalue' },
+{ oid => '9581', descr => 'format float8 to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'float8', prosrc => 'float8_to_typedvalue' },
+{ oid => '9582', descr => 'format float4 to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'float4', prosrc => 'float4_to_typedvalue' },
+{ oid => '9583', descr => 'format date to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'date', prosrc => 'date_to_typedvalue' },
+{ oid => '9584', descr => 'format timestamp to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'timestamp', prosrc => 'timestamp_to_typedvalue' },
+{ oid => '9585', descr => 'format timestamptz to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'timestamptz', prosrc => 'timestamptz_to_typedvalue' },
+{ oid => '9586', descr => 'format interval to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'interval', prosrc => 'interval_to_typedvalue' },
+{ oid => '9587', descr => 'format text to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'text', prosrc => 'text_to_typedvalue' },
+{ oid => '9588', descr => 'format varchar to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'varchar', prosrc => 'bpchar_to_typedvalue' },
+{ oid => '9589', descr => 'format bool to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'bool', prosrc => 'bool_to_typedvalue' },
+{ oid => '9590', descr => 'format typedvalue to bool',
+ proname => 'to_bool', provolatile => 's', prorettype => 'bool',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_bool' },
+
# tablesample method handlers
{ oid => '3313', descr => 'BERNOULLI tablesample method handler',
proname => 'bernoulli', provolatile => 'v', prorettype => 'tsm_handler',
@@ -9742,6 +9828,35 @@
{ oid => '3114', descr => 'fetch the Nth row value',
proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '9558', descr => 'get current position from window object',
+ proname => 'get_current_position', prokind => 'f', prorettype => 'int8',
+ proargtypes => 'windowobjectproxy', prosrc => 'windowobject_get_current_position' },
+{ oid => '9559', descr => 'set current position in window object',
+ proname => 'set_mark_position', prokind => 'f', prorettype => 'void',
+ proargtypes => 'windowobjectproxy int8', prosrc => 'windowobject_set_mark_position' },
+{ oid => '9560', descr => 'get partition row count',
+ proname => 'get_partition_rowcount', prokind => 'f', prorettype => 'int8',
+ proargtypes => 'windowobjectproxy', prosrc => 'windowobject_get_partition_rowcount' },
+{ oid => '9561', descr => 'returns true if two positions are peers',
+ proname => 'rows_are_peers', prokind => 'f', prorettype => 'bool',
+ proargtypes => 'windowobjectproxy int8 int8', prosrc => 'windowobject_rows_are_peers' },
+{ oid => '9562', descr => 'returns argument of window function against to partition',
+ proname => 'get_input_value_in_partition', prokind => 'f', prorettype => 'typedvalue',
+ proargtypes => 'windowobjectproxy int4 int4 text bool', prosrc => 'windowobject_get_func_arg_partition' },
+{ oid => '9563', descr => 'returns argument of window function against to frame',
+ proname => 'get_input_value_in_frame', prokind => 'f', prorettype => 'typedvalue',
+ proargtypes => 'windowobjectproxy int4 int4 text bool', prosrc => 'windowobject_get_func_arg_frame' },
+{ oid => '9564', descr => 'returns argument of window function against to current row',
+ proname => 'get_input_value_for_row', prokind => 'f', prorettype => 'typedvalue',
+ proargtypes => 'windowobjectproxy int4', prosrc => 'windowobject_get_func_arg_current' },
+{ oid => '9565', descr => 'returns a value stored in a partition context',
+ proname => 'get_partition_context_value', prokind => 'f', prorettype => 'anyelement',
+ proargtypes => 'windowobjectproxy anyelement int4',
+ prosrc => 'windowobject_get_partition_context_value', proisstrict => 'f' },
+{ oid => '9566', descr => 'store a value to partition context',
+ proname => 'set_partition_context_value', prokind => 'f', prorettype => 'void',
+ proargtypes => 'windowobjectproxy anyelement int4',
+ prosrc => 'windowobject_set_partition_context_value', proisstrict => 'f' },
# functions for range types
{ oid => '3832', descr => 'I/O',
diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat
index b2cec07416..5ad4ddfd59 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -560,6 +560,17 @@
typtype => 'p', typcategory => 'P', typinput => 'internal_in',
typoutput => 'internal_out', typreceive => '-', typsend => '-',
typalign => 'ALIGNOF_POINTER' },
+{ oid => '9552',
+ descr => 'pseudo-type representing an pointer to WindowObjectProxy structure',
+ typname => 'windowobjectproxy', typlen => '-1', typbyval => 'f',
+ typtype => 'p', typcategory => 'P', typinput => 'windowobjectproxy_in',
+ typoutput => 'windowobjectproxy_out', typreceive => '-', typsend => '-',
+ typalign => 'i', typstorage => 'p' },
+{ oid => '9553',
+ descr => 'type that can hold any scalar value with necessary meta',
+ typname => 'typedvalue', typtype => 'b', typlen => '-1', typbyval => 'f', typcategory => 'X',
+ typispreferred => 'f', typinput => 'typedvalue_in', typoutput => 'typedvalue_out',
+ typreceive => '-', typsend => '-', typalign => 'i', typstorage => 'x' },
{ oid => '2283', descr => 'pseudo-type representing a polymorphic base type',
typname => 'anyelement', typlen => '4', typbyval => 't', typtype => 'p',
typcategory => 'P', typinput => 'anyelement_in',
diff --git a/src/include/utils/typedvalue.h b/src/include/utils/typedvalue.h
new file mode 100644
index 0000000000..d3827b54a8
--- /dev/null
+++ b/src/include/utils/typedvalue.h
@@ -0,0 +1,28 @@
+/*-------------------------------------------------------------------------
+ *
+ * typedvalue.h
+ * Declarations for typedvalue data type support.
+ *
+ * Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ *
+ * src/include/utils/typedvalue.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef __TYPEDVALUE_H__
+#define __TYPEDVALUE_H__
+
+typedef struct
+{
+ int32 vl_len; /* varlena header */
+ Oid typid;
+ bool typbyval;
+ int16 typlen;
+ char data[FLEXIBLE_ARRAY_MEMBER];
+} TypedValueData;
+
+typedef TypedValueData *TypedValue;
+
+extern Datum makeTypedValue(Datum value, Oid typid, int16 typlen, bool typbyval);
+
+#endif
diff --git a/src/include/windowapi.h b/src/include/windowapi.h
index e8c9fc54d8..a4b8504f78 100644
--- a/src/include/windowapi.h
+++ b/src/include/windowapi.h
@@ -36,6 +36,34 @@
/* this struct is private in nodeWindowAgg.c */
typedef struct WindowObjectData *WindowObject;
+typedef struct WindowObjectProxyMutable
+{
+ /* true when request on winfuncarg doesn't return data */
+ bool isout;
+
+ /* cache for type related data of Window arguments */
+ int last_argno;
+ Oid typid;
+ int16 typlen;
+ bool typbyval;
+} WindowObjectProxyMutable;
+
+/*
+ * This type is used as proxy between PL variants of WinFuncArg
+ * functions and PL environment. The variables of windowobjectproxy
+ * type can be copied, so mutable content should be elsewhere.
+ */
+typedef struct WindowObjectProxyData
+{
+ int32 vl_len; /* varlena header */
+
+ WindowObject winobj;
+ FunctionCallInfo fcinfo;
+ WindowObjectProxyMutable *mutable_data;
+} WindowObjectProxyData;
+
+typedef WindowObjectProxyData *WindowObjectProxy;
+
#define PG_WINDOW_OBJECT() ((WindowObject) fcinfo->context)
#define WindowObjectIsValid(winobj) \
diff --git a/src/pl/plpgsql/src/Makefile b/src/pl/plpgsql/src/Makefile
index 193df8a010..ba6edfd42e 100644
--- a/src/pl/plpgsql/src/Makefile
+++ b/src/pl/plpgsql/src/Makefile
@@ -34,7 +34,7 @@ REGRESS_OPTS = --dbname=$(PL_TESTDB)
REGRESS = plpgsql_call plpgsql_control plpgsql_copy plpgsql_domain \
plpgsql_record plpgsql_cache plpgsql_simple plpgsql_transaction \
- plpgsql_trap plpgsql_trigger plpgsql_varprops
+ plpgsql_trap plpgsql_trigger plpgsql_varprops plpgsql_window
# where to find gen_keywordlist.pl and subsidiary files
TOOLSDIR = $(top_srcdir)/src/tools
diff --git a/src/pl/plpgsql/src/expected/plpgsql_window.out b/src/pl/plpgsql/src/expected/plpgsql_window.out
new file mode 100644
index 0000000000..b9cb015ecc
--- /dev/null
+++ b/src/pl/plpgsql/src/expected/plpgsql_window.out
@@ -0,0 +1,228 @@
+create or replace function pl_row_number()
+returns bigint as $$
+declare pos int8;
+begin
+ pos := get_current_position(windowobject);
+ pos := pos + 1;
+ perform set_mark_position(windowobject, pos);
+ return pos;
+end
+$$
+language plpgsql window;
+select pl_row_number() over (), v from (values(10),(20),(30)) v(v);
+ pl_row_number | v
+---------------+----
+ 1 | 10
+ 2 | 20
+ 3 | 30
+(3 rows)
+
+create or replace function pl_round_value(numeric)
+returns int as $$
+declare
+ num numeric;
+begin
+ num := get_input_value_for_row(windowobject, 1);
+ return round(num);
+end
+$$ language plpgsql window;
+select pl_round_value(v) over(order by v desc) from generate_series(0.1, 1.0, 0.1) g(v);
+ pl_round_value
+----------------
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+ 0
+ 0
+ 0
+ 0
+(10 rows)
+
+select pl_round_value(v + 1) over(order by v desc) from generate_series(0.1, 1.0, 0.1) g(v);
+ pl_round_value
+----------------
+ 2
+ 2
+ 2
+ 2
+ 2
+ 2
+ 1
+ 1
+ 1
+ 1
+(10 rows)
+
+create table test_table(v numeric);
+insert into test_table values(1),(3),(6),(6),(8),(7),(6),(5),(4);
+create or replace function pl_lag(numeric)
+returns numeric as $$
+declare
+ v numeric;
+begin
+ v := get_input_value_in_partition(windowobject, 1, -1, 'seek_current', false);
+ return v;
+end;
+$$ language plpgsql window;
+select pl_lag(v) over (), lag(v) over () from test_table;
+ pl_lag | lag
+--------+-----
+ |
+ 1 | 1
+ 3 | 3
+ 6 | 6
+ 6 | 6
+ 8 | 8
+ 7 | 7
+ 6 | 6
+ 5 | 5
+(9 rows)
+
+drop table test_table;
+create table test_table(v integer);
+insert into test_table values(1),(3),(6),(6),(8),(7),(6),(5),(4);
+select pl_lag(v) over (), lag(v) over () from test_table;
+ pl_lag | lag
+--------+-----
+ |
+ 1 | 1
+ 3 | 3
+ 6 | 6
+ 6 | 6
+ 8 | 8
+ 7 | 7
+ 6 | 6
+ 5 | 5
+(9 rows)
+
+create or replace function pl_moving_avg(numeric)
+returns numeric as $$
+declare
+ s numeric default 0.0;
+ v numeric;
+ c numeric default 0.0;
+begin
+ -- look before
+ v := get_input_value_in_partition(windowobject, 1, -1, 'seek_current', false);
+ if v is not null then
+ s := s + v;
+ c := c + 1.0;
+ end if;
+
+ -- look after
+ v := get_input_value_in_partition(windowobject, 1, 0, 'seek_current', false);
+ if v is not null then
+ s := s + v;
+ c := c + 1.0;
+ end if;
+
+ v := get_input_value_in_partition(windowobject, 1, 1, 'seek_current', false);
+ if v is not null then
+ s := s + v;
+ c := c + 1.0;
+ end if;
+
+ return trim_scale(s / c);
+end
+$$ language plpgsql window;
+select pl_moving_avg(v) over (), v from test_table;
+ pl_moving_avg | v
+--------------------+---
+ 2 | 1
+ 3.3333333333333333 | 3
+ 5 | 6
+ 6.6666666666666667 | 6
+ 7 | 8
+ 7 | 7
+ 6 | 6
+ 5 | 5
+ 4.5 | 4
+(9 rows)
+
+create or replace function pl_lag_polymorphic(anyelement)
+returns anyelement as $$
+declare
+ v $0%type;
+begin
+ v := get_input_value_in_partition(windowobject, 1, -1, 'seek_current', false);
+ return v;
+end;
+$$ language plpgsql window;
+select pl_lag_polymorphic(v) over (), lag(v) over () from test_table;
+ pl_lag_polymorphic | lag
+--------------------+-----
+ |
+ 1 | 1
+ 3 | 3
+ 6 | 6
+ 6 | 6
+ 8 | 8
+ 7 | 7
+ 6 | 6
+ 5 | 5
+(9 rows)
+
+create or replace function pl_pcontext_test(numeric)
+returns numeric as $$
+declare
+ n numeric;
+ v numeric;
+begin
+ n := get_partition_context_value(windowobject, null::numeric);
+ v := get_input_value_for_row(windowobject, 1);
+ perform set_partition_context_value(windowobject, v);
+
+ return n;
+end
+$$
+language plpgsql window;
+select v, pl_pcontext_test(v) over () from generate_series(0.1, 1.0, 0.1) g(v);
+ v | pl_pcontext_test
+-----+------------------
+ 0.1 |
+ 0.2 | 0.1
+ 0.3 | 0.2
+ 0.4 | 0.3
+ 0.5 | 0.4
+ 0.6 | 0.5
+ 0.7 | 0.6
+ 0.8 | 0.7
+ 0.9 | 0.8
+ 1.0 | 0.9
+(10 rows)
+
+create table test_missing_values(id int, v integer);
+insert into test_missing_values values(1,10),(2,11),(3,12),(4,null),(5,null),(6,15),(7,16);
+create or replace function pl_pcontext_test(numeric)
+returns numeric as $$
+declare
+ n numeric;
+ v numeric;
+begin
+ v := get_input_value_for_row(windowobject, 1);
+
+ if v is null then
+ v := get_partition_context_value(windowobject, null::numeric);
+ else
+ perform set_partition_context_value(windowobject, v);
+ end if;
+
+ return v;
+end
+$$
+language plpgsql window;
+select id, v, pl_pcontext_test(v) over (order by id) from test_missing_values;
+ id | v | pl_pcontext_test
+----+----+------------------
+ 1 | 10 | 10
+ 2 | 11 | 11
+ 3 | 12 | 12
+ 4 | | 12
+ 5 | | 12
+ 6 | 15 | 15
+ 7 | 16 | 16
+(7 rows)
+
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index e7f4a5f291..ee4bbb560e 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -582,6 +582,41 @@ do_compile(FunctionCallInfo fcinfo,
true);
}
+ if (function->fn_prokind == PROKIND_WINDOW)
+ {
+ PLpgSQL_type *dtype;
+ PLpgSQL_var *var;
+
+ /*
+ * Add the promise variable windowobject with windowobjectproxy type
+ *
+ * Pseudotypes are disallowed for custom variables. It is checked
+ * in plpgsql_build_variable, so instead call this function, build
+ * promise variable here.
+ */
+
+ dtype = plpgsql_build_datatype(WINDOWOBJECTPROXYOID,
+ -1,
+ function->fn_input_collation,
+ NULL);
+
+ /* this should be pseudotype */
+ Assert(dtype->ttype == PLPGSQL_TTYPE_PSEUDO);
+
+ var = palloc0(sizeof(PLpgSQL_var));
+
+ var->dtype = PLPGSQL_DTYPE_PROMISE;
+ var->promise = PLPGSQL_PROMISE_WINDOWOBJECT;
+
+ var->refname = pstrdup("windowobject");
+ var->datatype = dtype;
+
+ plpgsql_adddatum((PLpgSQL_datum *) var);
+ plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR,
+ var->dno,
+ var->refname);
+ }
+
ReleaseSysCache(typeTup);
break;
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index d4a3d58daa..e2af053fda 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -593,6 +593,39 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo,
*/
exec_set_found(&estate, false);
+ /*
+ * Initialize promise winobject
+ */
+ if (func->fn_prokind == PROKIND_WINDOW)
+ {
+ /* fcinfo is available in this function too */
+ WindowObjectProxy wop;
+ WindowObjectProxyMutable *mutable_data;
+ MemoryContext oldcontext;
+
+ oldcontext = MemoryContextSwitchTo(estate.datum_context);
+
+ wop = palloc(sizeof(WindowObjectProxyData));
+ SET_VARSIZE(wop, sizeof(WindowObjectProxyData));
+
+ wop->winobj = PG_WINDOW_OBJECT();
+
+ Assert(WindowObjectIsValid(wop->winobj));
+
+ mutable_data = palloc0(sizeof(WindowObjectProxyMutable));
+ mutable_data->isout = false;
+ mutable_data->last_argno = -1;
+
+ wop->mutable_data = mutable_data;
+ wop->fcinfo = fcinfo;
+
+ MemoryContextSwitchTo(oldcontext);
+
+ estate.winobjproxy = wop;
+ }
+ else
+ estate.winobjproxy = NULL;
+
/*
* Let the instrumentation plugin peek at this function
*/
@@ -915,6 +948,11 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
plpgsql_estate_setup(&estate, func, NULL, NULL, NULL);
estate.trigdata = trigdata;
+ /*
+ * Trigger function cannot be WINDOW function
+ */
+ estate.winobjproxy = NULL;
+
/*
* Setup error traceback support for ereport()
*/
@@ -1293,11 +1331,13 @@ copy_plpgsql_datums(PLpgSQL_execstate *estate,
PLpgSQL_datum *indatum = indatums[i];
PLpgSQL_datum *outdatum;
+
/* This must agree with plpgsql_finish_datums on what is copiable */
switch (indatum->dtype)
{
case PLPGSQL_DTYPE_VAR:
case PLPGSQL_DTYPE_PROMISE:
+
outdatum = (PLpgSQL_datum *) ws_next;
memcpy(outdatum, indatum, sizeof(PLpgSQL_var));
ws_next += MAXALIGN(sizeof(PLpgSQL_var));
@@ -1486,6 +1526,17 @@ plpgsql_fulfill_promise(PLpgSQL_execstate *estate,
assign_text_var(estate, var, GetCommandTagName(estate->evtrigdata->tag));
break;
+ case PLPGSQL_PROMISE_WINDOWOBJECT:
+ if (!estate->winobjproxy)
+ elog(ERROR, "windowobject promise is not in a window function");
+
+ assign_simple_var(estate,
+ var,
+ PointerGetDatum(estate->winobjproxy),
+ false,
+ false);
+ break;
+
default:
elog(ERROR, "unrecognized promise type: %d", var->promise);
}
@@ -2475,6 +2526,17 @@ exec_stmt_getdiag(PLpgSQL_execstate *estate, PLpgSQL_stmt_getdiag *stmt)
}
break;
+ case PLPGSQL_GETDIAG_VALUE_IS_OUT:
+ {
+ if (!estate->winobjproxy)
+ elog(ERROR, "function is not a window function");
+
+ exec_assign_value(estate, var,
+ BoolGetDatum(estate->winobjproxy->mutable_data->isout),
+ false, BOOLOID, -1);
+ }
+ break;
+
default:
elog(ERROR, "unrecognized diagnostic item kind: %d",
diag_item->kind);
diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c
index ee60ced583..992763c735 100644
--- a/src/pl/plpgsql/src/pl_funcs.c
+++ b/src/pl/plpgsql/src/pl_funcs.c
@@ -321,6 +321,8 @@ plpgsql_getdiag_kindname(PLpgSQL_getdiag_kind kind)
return "CONSTRAINT_NAME";
case PLPGSQL_GETDIAG_DATATYPE_NAME:
return "PG_DATATYPE_NAME";
+ case PLPGSQL_GETDIAG_VALUE_IS_OUT:
+ return "PG_VALUE_IS_OUT";
case PLPGSQL_GETDIAG_MESSAGE_TEXT:
return "MESSAGE_TEXT";
case PLPGSQL_GETDIAG_TABLE_NAME:
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 5a7e1a4444..823bbd288b 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -325,6 +325,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
%token <keyword> K_PG_EXCEPTION_CONTEXT
%token <keyword> K_PG_EXCEPTION_DETAIL
%token <keyword> K_PG_EXCEPTION_HINT
+%token <keyword> K_PG_VALUE_IS_OUT
%token <keyword> K_PRINT_STRICT_PARAMS
%token <keyword> K_PRIOR
%token <keyword> K_QUERY
@@ -1081,6 +1082,9 @@ getdiag_item :
else if (tok_is_keyword(tok, &yylval,
K_PG_EXCEPTION_CONTEXT, "pg_exception_context"))
$$ = PLPGSQL_GETDIAG_ERROR_CONTEXT;
+ else if (tok_is_keyword(tok, &yylval,
+ K_PG_VALUE_IS_OUT, "pg_value_is_out"))
+ $$ = PLPGSQL_GETDIAG_VALUE_IS_OUT;
else if (tok_is_keyword(tok, &yylval,
K_COLUMN_NAME, "column_name"))
$$ = PLPGSQL_GETDIAG_COLUMN_NAME;
diff --git a/src/pl/plpgsql/src/pl_unreserved_kwlist.h b/src/pl/plpgsql/src/pl_unreserved_kwlist.h
index 99b3cf7d8a..d27b7dfb85 100644
--- a/src/pl/plpgsql/src/pl_unreserved_kwlist.h
+++ b/src/pl/plpgsql/src/pl_unreserved_kwlist.h
@@ -84,6 +84,7 @@ PG_KEYWORD("pg_datatype_name", K_PG_DATATYPE_NAME)
PG_KEYWORD("pg_exception_context", K_PG_EXCEPTION_CONTEXT)
PG_KEYWORD("pg_exception_detail", K_PG_EXCEPTION_DETAIL)
PG_KEYWORD("pg_exception_hint", K_PG_EXCEPTION_HINT)
+PG_KEYWORD("pg_value_is_out", K_PG_VALUE_IS_OUT)
PG_KEYWORD("print_strict_params", K_PRINT_STRICT_PARAMS)
PG_KEYWORD("prior", K_PRIOR)
PG_KEYWORD("query", K_QUERY)
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 0c3d30fb13..4c1ed7cbf7 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -23,6 +23,8 @@
#include "utils/expandedrecord.h"
#include "utils/typcache.h"
+#include "windowapi.h"
+
/**********************************************************************
* Definitions
@@ -84,7 +86,8 @@ typedef enum PLpgSQL_promise_type
PLPGSQL_PROMISE_TG_NARGS,
PLPGSQL_PROMISE_TG_ARGV,
PLPGSQL_PROMISE_TG_EVENT,
- PLPGSQL_PROMISE_TG_TAG
+ PLPGSQL_PROMISE_TG_TAG,
+ PLPGSQL_PROMISE_WINDOWOBJECT
} PLpgSQL_promise_type;
/*
@@ -159,7 +162,8 @@ typedef enum PLpgSQL_getdiag_kind
PLPGSQL_GETDIAG_DATATYPE_NAME,
PLPGSQL_GETDIAG_MESSAGE_TEXT,
PLPGSQL_GETDIAG_TABLE_NAME,
- PLPGSQL_GETDIAG_SCHEMA_NAME
+ PLPGSQL_GETDIAG_SCHEMA_NAME,
+ PLPGSQL_GETDIAG_VALUE_IS_OUT
} PLpgSQL_getdiag_kind;
/*
@@ -612,6 +616,17 @@ typedef struct PLpgSQL_stmt_getdiag
List *diag_items; /* List of PLpgSQL_diag_item */
} PLpgSQL_stmt_getdiag;
+/*
+ * GET PG_WINDOW_CONTEXT statement
+ */
+typedef struct PLpgSQL_stmt_getwincxt
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ List *items;
+} PLpgSQL_stmt_getwincxt;
+
/*
* IF statement
*/
@@ -1049,6 +1064,8 @@ typedef struct PLpgSQL_execstate
TriggerData *trigdata; /* if regular trigger, data about firing */
EventTriggerData *evtrigdata; /* if event trigger, data about firing */
+ WindowObjectProxy winobjproxy; /* for window function we need proxy
+ * object between PL and WinFucArg funcions */
Datum retval;
bool retisnull;
Oid rettype; /* type of current retval */
diff --git a/src/pl/plpgsql/src/sql/plpgsql_window.sql b/src/pl/plpgsql/src/sql/plpgsql_window.sql
new file mode 100644
index 0000000000..6a3cab39a2
--- /dev/null
+++ b/src/pl/plpgsql/src/sql/plpgsql_window.sql
@@ -0,0 +1,135 @@
+create or replace function pl_row_number()
+returns bigint as $$
+declare pos int8;
+begin
+ pos := get_current_position(windowobject);
+ pos := pos + 1;
+ perform set_mark_position(windowobject, pos);
+ return pos;
+end
+$$
+language plpgsql window;
+
+select pl_row_number() over (), v from (values(10),(20),(30)) v(v);
+
+create or replace function pl_round_value(numeric)
+returns int as $$
+declare
+ num numeric;
+begin
+ num := get_input_value_for_row(windowobject, 1);
+ return round(num);
+end
+$$ language plpgsql window;
+
+select pl_round_value(v) over(order by v desc) from generate_series(0.1, 1.0, 0.1) g(v);
+
+select pl_round_value(v + 1) over(order by v desc) from generate_series(0.1, 1.0, 0.1) g(v);
+
+create table test_table(v numeric);
+insert into test_table values(1),(3),(6),(6),(8),(7),(6),(5),(4);
+
+create or replace function pl_lag(numeric)
+returns numeric as $$
+declare
+ v numeric;
+begin
+ v := get_input_value_in_partition(windowobject, 1, -1, 'seek_current', false);
+ return v;
+end;
+$$ language plpgsql window;
+
+select pl_lag(v) over (), lag(v) over () from test_table;
+
+drop table test_table;
+
+create table test_table(v integer);
+insert into test_table values(1),(3),(6),(6),(8),(7),(6),(5),(4);
+
+select pl_lag(v) over (), lag(v) over () from test_table;
+
+create or replace function pl_moving_avg(numeric)
+returns numeric as $$
+declare
+ s numeric default 0.0;
+ v numeric;
+ c numeric default 0.0;
+begin
+ -- look before
+ v := get_input_value_in_partition(windowobject, 1, -1, 'seek_current', false);
+ if v is not null then
+ s := s + v;
+ c := c + 1.0;
+ end if;
+
+ -- look after
+ v := get_input_value_in_partition(windowobject, 1, 0, 'seek_current', false);
+ if v is not null then
+ s := s + v;
+ c := c + 1.0;
+ end if;
+
+ v := get_input_value_in_partition(windowobject, 1, 1, 'seek_current', false);
+ if v is not null then
+ s := s + v;
+ c := c + 1.0;
+ end if;
+
+ return trim_scale(s / c);
+end
+$$ language plpgsql window;
+
+select pl_moving_avg(v) over (), v from test_table;
+
+create or replace function pl_lag_polymorphic(anyelement)
+returns anyelement as $$
+declare
+ v $0%type;
+begin
+ v := get_input_value_in_partition(windowobject, 1, -1, 'seek_current', false);
+ return v;
+end;
+$$ language plpgsql window;
+
+select pl_lag_polymorphic(v) over (), lag(v) over () from test_table;
+
+create or replace function pl_pcontext_test(numeric)
+returns numeric as $$
+declare
+ n numeric;
+ v numeric;
+begin
+ n := get_partition_context_value(windowobject, null::numeric);
+ v := get_input_value_for_row(windowobject, 1);
+ perform set_partition_context_value(windowobject, v);
+
+ return n;
+end
+$$
+language plpgsql window;
+
+select v, pl_pcontext_test(v) over () from generate_series(0.1, 1.0, 0.1) g(v);
+
+create table test_missing_values(id int, v integer);
+insert into test_missing_values values(1,10),(2,11),(3,12),(4,null),(5,null),(6,15),(7,16);
+
+create or replace function pl_pcontext_test(numeric)
+returns numeric as $$
+declare
+ n numeric;
+ v numeric;
+begin
+ v := get_input_value_for_row(windowobject, 1);
+
+ if v is null then
+ v := get_partition_context_value(windowobject, null::numeric);
+ else
+ perform set_partition_context_value(windowobject, v);
+ end if;
+
+ return v;
+end
+$$
+language plpgsql window;
+
+select id, v, pl_pcontext_test(v) over (order by id) from test_missing_values;
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index 274130e706..85391f9a3e 100644
--- a/src/test/regress/expected/type_sanity.out
+++ b/src/test/regress/expected/type_sanity.out
@@ -73,7 +73,8 @@ WHERE p1.typtype not in ('c','d','p') AND p1.typname NOT LIKE E'\\_%'
3361 | pg_ndistinct
3402 | pg_dependencies
5017 | pg_mcv_list
-(4 rows)
+ 9553 | typedvalue
+(5 rows)
-- Make sure typarray points to a varlena array type of our own base
SELECT p1.oid, p1.typname as basetype, p2.typname as arraytype,
Hi
rebase
Regards
Pavel
Attachments:
plpgsql-window-functions-20210101.patchtext/x-patch; charset=US-ASCII; name=plpgsql-window-functions-20210101.patchDownload
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index 11246aa653..89a07678ee 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -4606,6 +4606,99 @@ CREATE EVENT TRIGGER snitch ON ddl_command_start EXECUTE FUNCTION snitch();
</sect1>
+ <sect1 id="plpgsql-window">
+ <title>Window Functions</title>
+
+ <indexterm zone="plpgsql-window">
+ <primary>window</primary>
+ <secondary>in PL/pgSQL</secondary>
+ </indexterm>
+
+ <para>
+ <application>PL/pgSQL</application> can be used to define window
+ functions. A window function is created with the <command>CREATE FUNCTION</command>
+ command with clause <literal>WINDOW</literal>. The specific feature of
+ this functions is a possibility to two special storages with
+ sorted values of window function arguments and store with stored
+ one value of any type for currently processed partition (of window
+ function).
+ </para>
+
+ <para>
+ Access to both storages is done with special internal variable
+ <varname>WINDOWOBJECT</varname>. This variable is declared implicitly,
+ and it is available only in window functions.
+
+<programlisting>
+CREATE OR REPLACE FUNCTION plpgsql_rownum() RETURNS int8
+LANGUAGE plpgsql WINDOW
+AS $$
+DECLARE pos int8
+BEGIN
+ pos := get_current_position(WINDOWOBJECT);
+ pos := pos + 1;
+ PERFORM set_mark_position(WINDOWOBJECT, pos);
+RETURN pos;
+$$;
+
+SELECT plpgsql_rownum() OVER (), * FROM tab;
+</programlisting>
+ </para>
+
+ <para>
+ The arguments of window function cannot be accessed directly. The special
+ functions should be used. With these functions we can choose a scope of
+ buffered arguments, we can choose a wanted position against first, current, or
+ last row. The implementation of <function>lag</function> can looks like
+ (the window functions in plpgsql can use polymorphic types too):
+
+<programlisting>
+CREATE OR REPLACE FUNCTION plpgsql_lag(anyelement) RETURNS anyelement
+LANGUAGE plpgsql WINDOW
+AS $$
+BEGIN
+ RETURN
+ get_input_value_in_partition(WINDOWOBJECT,
+ 1, -1,
+ 'seek_current',
+ false);
+END;
+$$;
+
+SELECT v, plpgsql_lag(v) FROM generate_series(1, 10) g(v);
+</programlisting>
+
+ </para>
+
+ <para>
+ Second buffer that can be used in window function is a buffer for one value
+ assigned to partition. The content of this buffer can be read by function
+ <function>get_partition_context_value</function> or modified by function
+ <function>set_partition_context_value</function>. Next function replaces
+ missing values by previous non <literal>NULL</literal> value:
+
+<programlisting>
+CREATE OR REPLACE FUNCTION plpgsql_replace_missing(numeric) RETURNS numeric
+LANGUAGE plpgsql WINDOW
+AS $$
+DECLATE
+ v numeric;
+BEGIN
+ v := get_input_value_for_row(WINDOWOBJECT, 1);
+ IF v IS NULL THEN
+ v := get_partition_context_value(WINDOWOBJECT, NULL::numeric);
+ ELSE
+ PERFORM set_partition_context_value(WINDOWOBJECT, v);
+ END IF;
+ RETURN v;
+END;
+$$;
+</programlisting>
+
+ </para>
+
+ </sect1>
+
<sect1 id="plpgsql-implementation">
<title><application>PL/pgSQL</application> under the Hood</title>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b140c210bc..37d38d6e84 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1442,6 +1442,18 @@ LANGUAGE internal
STRICT IMMUTABLE PARALLEL SAFE
AS 'unicode_is_normalized';
+CREATE OR REPLACE FUNCTION
+ get_partition_context_value(windowobjectproxy, anyelement, int4 DEFAULT NULL)
+ RETURNS anyelement
+LANGUAGE internal
+AS 'windowobject_get_partition_context_value';
+
+CREATE OR REPLACE FUNCTION
+ set_partition_context_value(windowobjectproxy, anyelement, int4 DEFAULT NULL)
+ RETURNS void
+LANGUAGE internal
+AS 'windowobject_set_partition_context_value';
+
--
-- The default permissions for functions mean that anyone can execute them.
-- A number of functions shouldn't be executable by just anyone, but rather
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 82732146d3..4e10e2bde7 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -105,6 +105,7 @@ OBJS = \
tsvector.o \
tsvector_op.o \
tsvector_parser.o \
+ typedvalue.o \
uuid.o \
varbit.o \
varchar.o \
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index 99a93271fe..3745cc6515 100644
--- a/src/backend/utils/adt/pseudotypes.c
+++ b/src/backend/utils/adt/pseudotypes.c
@@ -372,6 +372,17 @@ pg_node_tree_send(PG_FUNCTION_ARGS)
PSEUDOTYPE_DUMMY_IO_FUNCS(pg_ddl_command);
PSEUDOTYPE_DUMMY_BINARY_IO_FUNCS(pg_ddl_command);
+/*
+ * windowobjectproxy
+ *
+ * This type is pointer to WindowObjectProxyData. It is communication
+ * mechanism between PL environment and WinFuncArgs functions. Due
+ * performance reason I prefer using indirect result processing against
+ * using function returning polymorphic composite value. The indirect
+ * mechanism is implemented with proxy object represented by type
+ * WindowObjectProxyData.
+ */
+PSEUDOTYPE_DUMMY_IO_FUNCS(windowobjectproxy);
/*
* Dummy I/O functions for various other pseudotypes.
diff --git a/src/backend/utils/adt/typedvalue.c b/src/backend/utils/adt/typedvalue.c
new file mode 100644
index 0000000000..370804a05a
--- /dev/null
+++ b/src/backend/utils/adt/typedvalue.c
@@ -0,0 +1,589 @@
+/*-------------------------------------------------------------------------
+ *
+ * typedvalue.c
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/typedvalue.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "catalog/pg_type.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/lsyscache.h"
+#include "utils/numeric.h"
+#include "utils/typedvalue.h"
+#include "fmgr.h"
+
+/*
+ * Returns Datum value stored in TypedValue structure.
+ * This structure can hold byval or varlena values.
+ */
+static Datum
+TypedValueGetDatum(TypedValue tv)
+{
+ if (tv->typbyval)
+ return *((Datum *) tv->data);
+ else
+ return PointerGetDatum(tv->data);
+}
+
+/*
+ * IN function for TypedValue type. It store input value as text type
+ * value.
+ */
+Datum
+typedvalue_in(PG_FUNCTION_ARGS)
+{
+ char *str = PG_GETARG_CSTRING(0);
+ int len;
+ Size size;
+ TypedValue tv;
+ text *txt;
+
+ len = strlen(str);
+
+ size = MAXALIGN(offsetof(TypedValueData, data) + len + VARHDRSZ);
+
+ tv = (TypedValue) palloc(size);
+ SET_VARSIZE(tv, size);
+
+ txt = (text *) tv->data;
+
+ SET_VARSIZE(txt, VARHDRSZ + len);
+ memcpy(VARDATA(txt), str, len);
+
+ tv->typid = TEXTOID;
+ tv->typbyval = false;
+ tv->typlen = -1;
+
+ PG_RETURN_POINTER(tv);
+}
+
+/*
+ * OUT function for TypedValue type. It search related output
+ * function for stored type, execute it, and returns result.
+ */
+Datum
+typedvalue_out(PG_FUNCTION_ARGS)
+{
+ Oid typOutput;
+ bool isVarlena;
+ char *str;
+ TypedValue tv;
+ Datum value;
+
+ tv = (TypedValue) PG_GETARG_POINTER(0);
+
+ getTypeOutputInfo(tv->typid, &typOutput, &isVarlena);
+ value = TypedValueGetDatum(tv);
+ str = OidOutputFunctionCall(typOutput, value);
+
+ PG_RETURN_CSTRING(str);
+}
+
+/*
+ * Constructor function for TypedValue type. It serializes
+ * byval or varlena Datum values.
+ */
+Datum
+makeTypedValue(Datum value, Oid typid, int16 typlen, bool typbyval)
+{
+ TypedValue tv;
+ Size size;
+ Size copy_bytes = 0;
+
+ if (typbyval)
+ size = MAXALIGN(offsetof(TypedValueData, data) + sizeof(Datum));
+ else
+ {
+ if (typlen != -1)
+ {
+ size = MAXALIGN(offsetof(TypedValueData, data) + typlen);
+ copy_bytes = typlen;
+ }
+ else
+ {
+ copy_bytes = VARSIZE_ANY((struct varlena *) DatumGetPointer(value));
+ size = MAXALIGN(offsetof(TypedValueData, data) + copy_bytes);
+ }
+ }
+
+ tv = (TypedValue) palloc(size);
+
+ SET_VARSIZE(tv, size);
+
+ tv->typid = typid;
+ tv->typlen = typlen;
+ tv->typbyval = typbyval;
+
+ if (typbyval)
+ *((Datum *) tv->data) = value;
+ else
+ memcpy(tv->data, DatumGetPointer(value), copy_bytes);
+
+ return PointerGetDatum(tv);
+}
+
+/*
+ * Fast cast finding of known (buildin) cast functions.
+ */
+static PGFunction
+get_direct_cast_func(Oid source_typid, Oid target_typid)
+{
+ switch (target_typid)
+ {
+ case NUMERICOID:
+ {
+ switch (source_typid)
+ {
+ case INT4OID:
+ return int4_numeric;
+
+ case INT8OID:
+ return int8_numeric;
+
+ case FLOAT4OID:
+ return float4_numeric;
+
+ case FLOAT8OID:
+ return float8_numeric;
+
+ default:
+ return NULL;
+ }
+ }
+
+ case INT8OID:
+ {
+ switch (source_typid)
+ {
+ case INT4OID:
+ return int48;
+
+ case NUMERICOID:
+ return numeric_int8;
+
+ case FLOAT4OID:
+ return ftoi8;
+
+ case FLOAT8OID:
+ return dtoi8;
+
+ default:
+ return NULL;
+ }
+ }
+
+ case INT4OID:
+ {
+ switch (source_typid)
+ {
+ case BOOLOID:
+ return bool_int4;
+
+ case INT8OID:
+ return int84;
+
+ case NUMERICOID:
+ return numeric_int4;
+
+ case FLOAT4OID:
+ return ftoi4;
+
+ case FLOAT8OID:
+ return dtoi4;
+
+ default:
+ return NULL;
+ }
+ }
+
+ case FLOAT8OID:
+ {
+ switch (source_typid)
+ {
+ case INT4OID:
+ return i4tod;
+
+ case INT8OID:
+ return i8tod;
+
+ case NUMERICOID:
+ return numeric_float8;
+
+ case FLOAT4OID:
+ return ftod;
+
+ default:
+ return NULL;
+ }
+ }
+
+ case FLOAT4OID:
+ {
+ switch (source_typid)
+ {
+ case INT4OID:
+ return i4tof;
+
+ case INT8OID:
+ return i8tof;
+
+ case NUMERICOID:
+ return numeric_float4;
+
+ case FLOAT8OID:
+ return dtof;
+
+ default:
+ return NULL;
+ }
+ }
+
+ case DATEOID:
+ {
+ switch (source_typid)
+ {
+ case TIMESTAMPOID:
+ return timestamp_date;
+
+ case TIMESTAMPTZOID:
+ return timestamptz_date;
+
+ default:
+ return NULL;
+ }
+ }
+
+ case TIMESTAMPOID:
+ {
+ switch (source_typid)
+ {
+ case DATEOID:
+ return date_timestamp;
+
+ case TIMESTAMPTZOID:
+ return timestamptz_timestamp;
+
+ default:
+ return NULL;
+ }
+ }
+
+ case TIMESTAMPTZOID:
+ {
+ switch (source_typid)
+ {
+ case DATEOID:
+ return date_timestamptz;
+
+ case TIMESTAMPOID:
+ return timestamp_timestamptz;
+
+ default:
+ return NULL;
+ }
+ }
+
+ case BOOLOID:
+ {
+ switch (source_typid)
+ {
+ case INT4OID:
+ return int4_bool;
+
+ default:
+ return NULL;
+ }
+ }
+
+ default:
+ return NULL;
+ }
+}
+
+static Datum
+cast_typedvalue_to(TypedValue tv, Oid target_typid)
+{
+ Datum value = TypedValueGetDatum(tv);
+ Datum result;
+ char *str;
+ PGFunction cast_func;
+ Oid cast_func_oid;
+ Oid typinput;
+ Oid typioparam;
+ Oid typoutput;
+ bool isvarlena;
+
+ if (tv->typid == target_typid)
+ return datumCopy(value,
+ tv->typbyval,
+ tv->typlen);
+
+ if (tv->typid == TEXTOID || tv->typid == BPCHAROID)
+ {
+ getTypeInputInfo(target_typid, &typinput, &typioparam);
+ str = TextDatumGetCString(value);
+ result = OidInputFunctionCall(typinput, str,
+ typioparam, -1);
+ pfree(str);
+
+ return result;
+ }
+
+ if (target_typid == TEXTOID || target_typid == BPCHAROID)
+ {
+ getTypeOutputInfo(tv->typid, &typoutput, &isvarlena);
+ str = OidOutputFunctionCall(typoutput, value);
+ result = PointerGetDatum(cstring_to_text(str));
+
+ return result;
+ }
+
+ /* fast cast func detection, and direct call */
+ cast_func = get_direct_cast_func(tv->typid, target_typid);
+ if (cast_func)
+ {
+ result = DirectFunctionCall1(cast_func, value);
+
+ return result;
+ }
+
+ /* slower cast func, and indirect call */
+ cast_func_oid = get_cast_oid(tv->typid, target_typid, false);
+ if (OidIsValid(cast_func_oid))
+ {
+ result = OidFunctionCall1(cast_func_oid, value);
+
+ return result;
+ }
+
+ /* IO cast - most slow */
+ getTypeOutputInfo(tv->typid, &typoutput, &isvarlena);
+ str = OidOutputFunctionCall(typoutput, value);
+
+ getTypeInputInfo(target_typid, &typinput, &typioparam);
+ result = OidInputFunctionCall(typinput, str,
+ typioparam, -1);
+ pfree(str);
+
+ return result;
+}
+
+/*
+ * Casting functions - from variant typedvalue to specific types
+ */
+Datum
+typedvalue_to_numeric(PG_FUNCTION_ARGS)
+{
+ TypedValue tv = (TypedValue) PG_GETARG_POINTER(0);
+
+ PG_RETURN_DATUM(cast_typedvalue_to(tv, NUMERICOID));
+}
+
+Datum
+typedvalue_to_int8(PG_FUNCTION_ARGS)
+{
+ TypedValue tv = (TypedValue) PG_GETARG_POINTER(0);
+
+ PG_RETURN_DATUM(cast_typedvalue_to(tv, INT8OID));
+}
+
+Datum
+typedvalue_to_int4(PG_FUNCTION_ARGS)
+{
+ TypedValue tv = (TypedValue) PG_GETARG_POINTER(0);
+
+ PG_RETURN_DATUM(cast_typedvalue_to(tv, INT4OID));
+}
+
+Datum
+typedvalue_to_float8(PG_FUNCTION_ARGS)
+{
+ TypedValue tv = (TypedValue) PG_GETARG_POINTER(0);
+
+ PG_RETURN_DATUM(cast_typedvalue_to(tv, FLOAT8OID));
+}
+
+Datum
+typedvalue_to_float4(PG_FUNCTION_ARGS)
+{
+ TypedValue tv = (TypedValue) PG_GETARG_POINTER(0);
+
+ PG_RETURN_DATUM(cast_typedvalue_to(tv, FLOAT4OID));
+}
+
+Datum
+typedvalue_to_date(PG_FUNCTION_ARGS)
+{
+ TypedValue tv = (TypedValue) PG_GETARG_POINTER(0);
+
+ PG_RETURN_DATUM(cast_typedvalue_to(tv, DATEOID));
+}
+
+Datum
+typedvalue_to_timestamp(PG_FUNCTION_ARGS)
+{
+ TypedValue tv = (TypedValue) PG_GETARG_POINTER(0);
+
+ PG_RETURN_DATUM(cast_typedvalue_to(tv, TIMESTAMPOID));
+}
+
+Datum
+typedvalue_to_timestamptz(PG_FUNCTION_ARGS)
+{
+ TypedValue tv = (TypedValue) PG_GETARG_POINTER(0);
+
+ PG_RETURN_DATUM(cast_typedvalue_to(tv, TIMESTAMPTZOID));
+}
+
+Datum
+typedvalue_to_interval(PG_FUNCTION_ARGS)
+{
+ TypedValue tv = (TypedValue) PG_GETARG_POINTER(0);
+
+ PG_RETURN_DATUM(cast_typedvalue_to(tv, INTERVALOID));
+}
+
+Datum
+typedvalue_to_text(PG_FUNCTION_ARGS)
+{
+ TypedValue tv = (TypedValue) PG_GETARG_POINTER(0);
+
+ PG_RETURN_DATUM(cast_typedvalue_to(tv, TEXTOID));
+}
+
+Datum
+typedvalue_to_bpchar(PG_FUNCTION_ARGS)
+{
+ TypedValue tv = (TypedValue) PG_GETARG_POINTER(0);
+
+ PG_RETURN_DATUM(cast_typedvalue_to(tv, BPCHAROID));
+}
+
+Datum
+typedvalue_to_bool(PG_FUNCTION_ARGS)
+{
+ TypedValue tv = (TypedValue) PG_GETARG_POINTER(0);
+
+ PG_RETURN_DATUM(cast_typedvalue_to(tv, BOOLOID));
+}
+
+/*
+ * Cast function from specific types to TypedValue
+ */
+Datum
+numeric_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ NUMERICOID,
+ -1,
+ false));
+}
+
+Datum
+int8_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ INT8OID,
+ 8,
+ true));
+}
+
+Datum
+int4_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ INT4OID,
+ 4,
+ true));
+}
+
+Datum
+float8_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ FLOAT8OID,
+ 8,
+ true));
+}
+
+Datum
+float4_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ FLOAT4OID,
+ 4,
+ true));
+}
+
+
+Datum
+date_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ DATEOID,
+ 4,
+ true));
+}
+
+Datum
+timestamp_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ TIMESTAMPOID,
+ 8,
+ true));
+}
+
+Datum
+timestamptz_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ TIMESTAMPTZOID,
+ 8,
+ true));
+}
+
+Datum
+interval_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ INTERVALOID,
+ 16,
+ false));
+}
+
+Datum
+text_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ TEXTOID,
+ -1,
+ false));
+}
+
+Datum
+bpchar_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ BPCHAROID,
+ -1,
+ false));
+}
+
+Datum
+bool_to_typedvalue(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_DATUM(makeTypedValue(PG_GETARG_DATUM(0),
+ BOOLOID,
+ 1,
+ true));
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index f0c8ae686d..e6bee14899 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -14,6 +14,9 @@
#include "postgres.h"
#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/lsyscache.h"
+#include "utils/typedvalue.h"
#include "windowapi.h"
/*
@@ -35,6 +38,20 @@ typedef struct
int64 remainder; /* (total rows) % (bucket num) */
} ntile_context;
+#define PROXY_CONTEXT_MAGIC 19730715
+
+typedef struct
+{
+ int magic;
+ Oid typid;
+ int16 typlen;
+ bool typbyval;
+ int allocsize;
+ bool isnull;
+ Datum value;
+ char data[FLEXIBLE_ARRAY_MEMBER];
+} proxy_context;
+
static bool rank_up(WindowObject winobj);
static Datum leadlag_common(FunctionCallInfo fcinfo,
bool forward, bool withoffset, bool withdefault);
@@ -472,3 +489,467 @@ window_nth_value(PG_FUNCTION_ARGS)
PG_RETURN_DATUM(result);
}
+
+/*
+ * High level access function. These functions are wrappers for windows API
+ * for PL languages based on usage WindowObjectProxy.
+ */
+Datum
+windowobject_get_current_position(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int64 pos;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ pos = WinGetCurrentPosition(winobj);
+
+ PG_RETURN_INT64(pos);
+}
+
+Datum
+windowobject_set_mark_position(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int64 pos;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ pos = PG_GETARG_INT64(1);
+
+ WinSetMarkPosition(winobj, pos);
+
+ PG_RETURN_VOID();
+}
+
+Datum
+windowobject_get_partition_rowcount(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int64 rc;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ rc = WinGetPartitionRowCount(winobj);
+
+ PG_RETURN_INT64(rc);
+}
+
+Datum
+windowobject_rows_are_peers(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int64 pos1,
+ pos2;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ pos1 = PG_GETARG_INT64(1);
+ pos2 = PG_GETARG_INT64(2);
+
+ PG_RETURN_BOOL(WinRowsArePeers(winobj, pos1, pos2));
+}
+
+#define SEEK_CURRENT_STR "seek_current"
+#define SEEK_HEAD_STR "seek_head"
+#define SEEK_TAIL_STR "seek_tail"
+
+#define STRLEN(s) (sizeof(s) - 1)
+
+static int
+get_seek_type(text *seektype)
+{
+ char *str;
+ int len;
+ int result;
+
+ str = VARDATA_ANY(seektype);
+ len = VARSIZE_ANY_EXHDR(seektype);
+
+ if (len == STRLEN(SEEK_CURRENT_STR) && strncmp(str, SEEK_CURRENT_STR, len) == 0)
+ result = WINDOW_SEEK_CURRENT;
+ else if (len == STRLEN(SEEK_HEAD_STR) && strncmp(str, SEEK_HEAD_STR, len) == 0)
+ result = WINDOW_SEEK_HEAD;
+ else if (len == STRLEN(SEEK_TAIL_STR) && strncmp(str, SEEK_TAIL_STR, len) == 0)
+ result = WINDOW_SEEK_TAIL;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("seek type value must be \"seek_current\", \"seek_head\" or \"seek_tail\"")));
+
+ return result;
+}
+
+static Oid
+wop_funcarg_info(WindowObjectProxy wop,
+ int argno,
+ int16 *typlen,
+ bool *typbyval)
+{
+ WindowObjectProxyMutable *mutable_data = wop->mutable_data;
+
+ if (argno != mutable_data->last_argno)
+ {
+ Oid argtypid = get_fn_expr_argtype(wop->fcinfo->flinfo, argno);
+
+ mutable_data->typid = getBaseType(argtypid);
+ get_typlenbyval(mutable_data->typid,
+ &mutable_data->typlen,
+ &mutable_data->typbyval);
+ mutable_data->last_argno = argno;
+ }
+
+ *typlen = mutable_data->typlen;
+ *typbyval = mutable_data->typbyval;
+
+ return mutable_data->typid;
+}
+
+Datum
+windowobject_get_func_arg_partition(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int argno;
+ int relpos;
+ int seektype;
+ bool set_mark;
+ Datum value;
+ bool isnull;
+ Oid typid;
+ int16 typlen;
+ bool typbyval;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ argno = PG_GETARG_INT32(1);
+
+ if (argno < 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("arg number less than one")));
+
+ argno -= 1;
+
+ relpos = PG_GETARG_INT32(2);
+ seektype = get_seek_type(PG_GETARG_TEXT_P(3));
+ set_mark = PG_GETARG_BOOL(4);
+
+ value = WinGetFuncArgInPartition(winobj,
+ argno,
+ relpos,
+ seektype,
+ set_mark,
+ &isnull,
+ &wop->mutable_data->isout);
+
+ if (isnull)
+ PG_RETURN_NULL();
+
+ typid = wop_funcarg_info(wop, argno, &typlen, &typbyval);
+
+ PG_RETURN_DATUM(makeTypedValue(value, typid, typlen, typbyval));
+}
+
+Datum
+windowobject_get_func_arg_frame(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int argno;
+ int relpos;
+ int seektype;
+ bool set_mark;
+ Datum value;
+ bool isnull;
+ Oid typid;
+ int16 typlen;
+ bool typbyval;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ argno = PG_GETARG_INT32(1);
+ relpos = PG_GETARG_INT32(2);
+ seektype = get_seek_type(PG_GETARG_TEXT_P(3));
+ set_mark = PG_GETARG_BOOL(4);
+
+ if (argno < 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("arg number less than one")));
+
+ argno -= 1;
+
+ value = WinGetFuncArgInFrame(winobj,
+ argno,
+ relpos,
+ seektype,
+ set_mark,
+ &isnull,
+ &wop->mutable_data->isout);
+
+ if (isnull)
+ PG_RETURN_NULL();
+
+ typid = wop_funcarg_info(wop, argno, &typlen, &typbyval);
+
+ PG_RETURN_DATUM(makeTypedValue(value, typid, typlen, typbyval));
+}
+
+Datum
+windowobject_get_func_arg_current(PG_FUNCTION_ARGS)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ int argno;
+ Datum value;
+ bool isnull;
+ Oid typid;
+ int16 typlen;
+ bool typbyval;
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+
+ winobj = wop->winobj;
+
+ Assert(WindowObjectIsValid(winobj));
+
+ argno = PG_GETARG_INT32(1);
+
+ if (argno < 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("arg number less than one")));
+
+ argno -= 1;
+
+ value = WinGetFuncArgCurrent(winobj, argno, &isnull);
+
+ wop->mutable_data->isout = false;
+
+ if (isnull)
+ PG_RETURN_NULL();
+
+ typid = wop_funcarg_info(wop, argno, &typlen, &typbyval);
+ PG_RETURN_DATUM(makeTypedValue(value, typid, typlen, typbyval));
+}
+
+static void
+copy_datum_to_partition_context(proxy_context *pcontext,
+ Datum value,
+ bool isnull)
+{
+ if (!isnull)
+ {
+ if (pcontext->typbyval)
+ pcontext->value = value;
+ else if (pcontext->typlen == -1)
+ {
+ struct varlena *s = (struct varlena *) DatumGetPointer(value);
+
+ memcpy(pcontext->data, s, VARSIZE_ANY(s));
+ pcontext->value = PointerGetDatum(pcontext->data);
+ }
+ else
+ {
+ memcpy(pcontext->data, DatumGetPointer(value), pcontext->typlen);
+ pcontext->value = PointerGetDatum(pcontext->data);
+ }
+
+ pcontext->isnull = false;
+ }
+ else
+ {
+ pcontext->value = (Datum) 0;
+ pcontext->isnull = true;
+ }
+}
+
+/*
+ * Returns estimated size of windowobject partition context
+ */
+static int
+estimate_partition_context_size(Datum value,
+ bool isnull,
+ int16 typlen,
+ int16 minsize,
+ int *realsize)
+{
+ if(typlen != -1)
+ {
+ if (typlen < sizeof(Datum))
+ {
+ *realsize = offsetof(proxy_context, data);
+
+ return *realsize;
+ }
+
+ if (typlen > 1024)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("size of value is greather than limit (1024 bytes)")));
+
+ *realsize = offsetof(proxy_context, data) + typlen;
+
+ return *realsize;
+ }
+ else
+ {
+ if (!isnull)
+ {
+ int size = VARSIZE_ANY_EXHDR(DatumGetPointer(value));
+
+ if (size > 1024)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("size of value is greather than limit (1024 bytes)")));
+
+ *realsize = size;
+
+ size += size / 3;
+
+ return offsetof(proxy_context, data)
+ + MAXALIGN(size > minsize ? size : minsize);
+ }
+ else
+ {
+ /* by default we allocate 30 bytes */
+ *realsize = 0;
+
+ return offsetof(proxy_context, data) + MAXALIGN(minsize);
+ }
+ }
+}
+
+#define VARLENA_MINSIZE 32
+
+static proxy_context *
+get_partition_context(FunctionCallInfo fcinfo, bool write_mode)
+{
+ WindowObjectProxy wop;
+ WindowObject winobj;
+ Oid typid;
+ int16 typlen;
+ bool typbyval;
+ Datum value = (Datum) 0;
+ bool isnull = true;
+ int allocsize;
+ int minsize;
+ int realsize;
+ proxy_context *pcontext;
+
+ if (PG_ARGISNULL(0))
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("windowobject is NULL")));
+
+ wop = (WindowObjectProxy) DatumGetPointer(PG_GETARG_DATUM(0));
+ winobj = wop->winobj;
+ Assert(WindowObjectIsValid(winobj));
+
+ if (PG_ARGISNULL(2))
+ minsize = VARLENA_MINSIZE;
+ else
+ minsize = PG_GETARG_INT32(2);
+
+ if (!PG_ARGISNULL(1))
+ {
+ value = PG_GETARG_DATUM(1);
+ isnull = false;
+ }
+
+ typid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ if (!OidIsValid(typid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot detect type of context value")));
+
+ typid = getBaseType(typid);
+ get_typlenbyval(typid, &typlen, &typbyval);
+
+ Assert(typlen != -2);
+
+ allocsize = estimate_partition_context_size(value,
+ isnull,
+ typlen,
+ minsize,
+ &realsize);
+
+ pcontext = (proxy_context *) WinGetPartitionLocalMemory(winobj, allocsize);
+
+ /* fresh pcontext has zeroed memory */
+ Assert(pcontext->magic == 0 || pcontext->magic == PROXY_CONTEXT_MAGIC);
+
+ if (pcontext->allocsize > 0)
+ {
+ if (realsize > pcontext->allocsize)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("the value cannot be saved to allocated buffer"),
+ errhint("Try to increase the minsize argument.")));
+
+ if (pcontext->typid != typid)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("partition context was initialized for different type")));
+
+ if (write_mode)
+ copy_datum_to_partition_context(pcontext, value, isnull);
+
+ }
+ else
+ {
+ pcontext->magic = PROXY_CONTEXT_MAGIC;
+ pcontext->typid = typid;
+ pcontext->typlen = typlen;
+ pcontext->typbyval = typbyval;
+ pcontext->allocsize = allocsize;
+
+ copy_datum_to_partition_context(pcontext, value, isnull);
+ }
+
+ return pcontext;
+}
+
+Datum
+windowobject_set_partition_context_value(PG_FUNCTION_ARGS)
+{
+ (void) get_partition_context(fcinfo, true);
+
+ PG_RETURN_VOID();
+}
+
+Datum
+windowobject_get_partition_context_value(PG_FUNCTION_ARGS)
+{
+ proxy_context *pcontext;
+
+ pcontext = get_partition_context(fcinfo, false);
+
+ if (pcontext->isnull)
+ PG_RETURN_NULL();
+
+ PG_RETURN_DATUM(pcontext->value);
+}
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index d6ca624add..6ad6bb2f5e 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -543,4 +543,54 @@
castcontext => 'e', castmethod => 'f' },
{ castsource => 'tstzrange', casttarget => 'tstzmultirange', castfunc => 'tstzmultirange(tstzrange)',
castcontext => 'e', castmethod => 'f' },
+
+# Allow explicit coercions between typedvalue and other types
+{ castsource => 'typedvalue', casttarget => 'numeric', castfunc => 'to_numeric(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'int8', castfunc => 'to_int8(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'int4', castfunc => 'to_int4(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'float8', castfunc => 'to_float8(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'float4', castfunc => 'to_float4(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'date', castfunc => 'to_date(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'timestamp', castfunc => 'to_timestamp(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'timestamptz', castfunc => 'to_timestamptz(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'interval', castfunc => 'to_interval(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'text', castfunc => 'to_text(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'varchar', castfunc => 'to_varchar(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'typedvalue', casttarget => 'bool', castfunc => 'to_bool(typedvalue)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'numeric', casttarget => 'typedvalue', castfunc => 'to_typedvalue(numeric)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'int8', casttarget => 'typedvalue', castfunc => 'to_typedvalue(int8)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'int4', casttarget => 'typedvalue', castfunc => 'to_typedvalue(int4)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'float8', casttarget => 'typedvalue', castfunc => 'to_typedvalue(float8)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'float4', casttarget => 'typedvalue', castfunc => 'to_typedvalue(float4)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'date', casttarget => 'typedvalue', castfunc => 'to_typedvalue(date)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'timestamp', casttarget => 'typedvalue', castfunc => 'to_typedvalue(timestamp)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'timestamptz', casttarget => 'typedvalue', castfunc => 'to_typedvalue(timestamptz)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'interval', casttarget => 'typedvalue', castfunc => 'to_typedvalue(interval)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'text', casttarget => 'typedvalue', castfunc => 'to_typedvalue(text)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'varchar', casttarget => 'typedvalue', castfunc => 'to_typedvalue(varchar)',
+ castcontext => 'e', castmethod => 'f' },
+{ castsource => 'bool', casttarget => 'typedvalue', castfunc => 'to_typedvalue(bool)',
+ castcontext => 'e', castmethod => 'f' },
]
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 139f4a08bd..0e4ae78310 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7165,6 +7165,18 @@
{ oid => '2305', descr => 'I/O',
proname => 'internal_out', prorettype => 'cstring', proargtypes => 'internal',
prosrc => 'internal_out' },
+{ oid => '9554', descr => 'I/O',
+ proname => 'windowobjectproxy_in', proisstrict => 'f', prorettype => 'windowobjectproxy',
+ proargtypes => 'cstring', prosrc => 'windowobjectproxy_in' },
+{ oid => '9555', descr => 'I/O',
+ proname => 'windowobjectproxy_out', prorettype => 'cstring', proargtypes => 'windowobjectproxy',
+ prosrc => 'windowobjectproxy_out' },
+{ oid => '9556', descr => 'I/O',
+ proname => 'typedvalue_in', proisstrict => 'f', prorettype => 'typedvalue',
+ proargtypes => 'cstring', prosrc => 'typedvalue_in' },
+{ oid => '9557', descr => 'I/O',
+ proname => 'typedvalue_out', prorettype => 'cstring', proargtypes => 'typedvalue',
+ prosrc => 'typedvalue_out' },
{ oid => '2312', descr => 'I/O',
proname => 'anyelement_in', prorettype => 'anyelement',
proargtypes => 'cstring', prosrc => 'anyelement_in' },
@@ -7262,6 +7274,80 @@
prorettype => 'cstring', proargtypes => 'anycompatiblemultirange',
prosrc => 'anycompatiblemultirange_out' },
+# typedvalue cast functions
+{ oid => '9567', descr => 'format typedvalue to numeric',
+ proname => 'to_numeric', provolatile => 's', prorettype => 'numeric',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_numeric' },
+{ oid => '9568', descr => 'format typedvalue to int8',
+ proname => 'to_int8', provolatile => 's', prorettype => 'int8',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_int8' },
+{ oid => '9569', descr => 'format typedvalue to int4',
+ proname => 'to_int4', provolatile => 's', prorettype => 'int4',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_int4' },
+{ oid => '9570', descr => 'format typedvalue to float8',
+ proname => 'to_float8', provolatile => 's', prorettype => 'float8',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_float8' },
+{ oid => '9571', descr => 'format typedvalue to float4',
+ proname => 'to_float4', provolatile => 's', prorettype => 'float4',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_float4' },
+{ oid => '9572', descr => 'format typedvalue to date',
+ proname => 'to_date', provolatile => 's', prorettype => 'date',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_date' },
+{ oid => '9573', descr => 'format typedvalue to timestamp',
+ proname => 'to_timestamp', provolatile => 's', prorettype => 'timestamp',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_timestamp' },
+{ oid => '9574', descr => 'format typedvalue to timestamptz',
+ proname => 'to_timestamptz', provolatile => 's', prorettype => 'timestamptz',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_timestamptz' },
+{ oid => '9575', descr => 'format typedvalue to interval',
+ proname => 'to_interval', provolatile => 's', prorettype => 'interval',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_interval' },
+{ oid => '9576', descr => 'format typedvalue to text',
+ proname => 'to_text', provolatile => 's', prorettype => 'text',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_text' },
+{ oid => '9577', descr => 'format typedvalue to varchar',
+ proname => 'to_varchar', provolatile => 's', prorettype => 'varchar',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_bpchar' },
+{ oid => '9578', descr => 'format numeric to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'numeric', prosrc => 'numeric_to_typedvalue' },
+{ oid => '9579', descr => 'format int8 to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'int8', prosrc => 'int8_to_typedvalue' },
+{ oid => '9580', descr => 'format int4 to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'int4', prosrc => 'int4_to_typedvalue' },
+{ oid => '9581', descr => 'format float8 to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'float8', prosrc => 'float8_to_typedvalue' },
+{ oid => '9582', descr => 'format float4 to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'float4', prosrc => 'float4_to_typedvalue' },
+{ oid => '9583', descr => 'format date to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'date', prosrc => 'date_to_typedvalue' },
+{ oid => '9584', descr => 'format timestamp to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'timestamp', prosrc => 'timestamp_to_typedvalue' },
+{ oid => '9585', descr => 'format timestamptz to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'timestamptz', prosrc => 'timestamptz_to_typedvalue' },
+{ oid => '9586', descr => 'format interval to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'interval', prosrc => 'interval_to_typedvalue' },
+{ oid => '9587', descr => 'format text to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'text', prosrc => 'text_to_typedvalue' },
+{ oid => '9588', descr => 'format varchar to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'varchar', prosrc => 'bpchar_to_typedvalue' },
+{ oid => '9589', descr => 'format bool to typedvalue',
+ proname => 'to_typedvalue', provolatile => 's', prorettype => 'typedvalue',
+ proargtypes => 'bool', prosrc => 'bool_to_typedvalue' },
+{ oid => '9590', descr => 'format typedvalue to bool',
+ proname => 'to_bool', provolatile => 's', prorettype => 'bool',
+ proargtypes => 'typedvalue', prosrc => 'typedvalue_to_bool' },
+
# tablesample method handlers
{ oid => '3313', descr => 'BERNOULLI tablesample method handler',
proname => 'bernoulli', provolatile => 'v', prorettype => 'tsm_handler',
@@ -9797,6 +9883,35 @@
{ oid => '3114', descr => 'fetch the Nth row value',
proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '9558', descr => 'get current position from window object',
+ proname => 'get_current_position', prokind => 'f', prorettype => 'int8',
+ proargtypes => 'windowobjectproxy', prosrc => 'windowobject_get_current_position' },
+{ oid => '9559', descr => 'set current position in window object',
+ proname => 'set_mark_position', prokind => 'f', prorettype => 'void',
+ proargtypes => 'windowobjectproxy int8', prosrc => 'windowobject_set_mark_position' },
+{ oid => '9560', descr => 'get partition row count',
+ proname => 'get_partition_rowcount', prokind => 'f', prorettype => 'int8',
+ proargtypes => 'windowobjectproxy', prosrc => 'windowobject_get_partition_rowcount' },
+{ oid => '9561', descr => 'returns true if two positions are peers',
+ proname => 'rows_are_peers', prokind => 'f', prorettype => 'bool',
+ proargtypes => 'windowobjectproxy int8 int8', prosrc => 'windowobject_rows_are_peers' },
+{ oid => '9562', descr => 'returns argument of window function against to partition',
+ proname => 'get_input_value_in_partition', prokind => 'f', prorettype => 'typedvalue',
+ proargtypes => 'windowobjectproxy int4 int4 text bool', prosrc => 'windowobject_get_func_arg_partition' },
+{ oid => '9563', descr => 'returns argument of window function against to frame',
+ proname => 'get_input_value_in_frame', prokind => 'f', prorettype => 'typedvalue',
+ proargtypes => 'windowobjectproxy int4 int4 text bool', prosrc => 'windowobject_get_func_arg_frame' },
+{ oid => '9564', descr => 'returns argument of window function against to current row',
+ proname => 'get_input_value_for_row', prokind => 'f', prorettype => 'typedvalue',
+ proargtypes => 'windowobjectproxy int4', prosrc => 'windowobject_get_func_arg_current' },
+{ oid => '9565', descr => 'returns a value stored in a partition context',
+ proname => 'get_partition_context_value', prokind => 'f', prorettype => 'anyelement',
+ proargtypes => 'windowobjectproxy anyelement int4',
+ prosrc => 'windowobject_get_partition_context_value', proisstrict => 'f' },
+{ oid => '9566', descr => 'store a value to partition context',
+ proname => 'set_partition_context_value', prokind => 'f', prorettype => 'void',
+ proargtypes => 'windowobjectproxy anyelement int4',
+ prosrc => 'windowobject_set_partition_context_value', proisstrict => 'f' },
# functions for range types
{ oid => '3832', descr => 'I/O',
diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat
index 62018f063a..da4635950d 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -591,6 +591,17 @@
typtype => 'p', typcategory => 'P', typinput => 'internal_in',
typoutput => 'internal_out', typreceive => '-', typsend => '-',
typalign => 'ALIGNOF_POINTER' },
+{ oid => '9552',
+ descr => 'pseudo-type representing an pointer to WindowObjectProxy structure',
+ typname => 'windowobjectproxy', typlen => '-1', typbyval => 'f',
+ typtype => 'p', typcategory => 'P', typinput => 'windowobjectproxy_in',
+ typoutput => 'windowobjectproxy_out', typreceive => '-', typsend => '-',
+ typalign => 'i', typstorage => 'p' },
+{ oid => '9553',
+ descr => 'type that can hold any scalar value with necessary meta',
+ typname => 'typedvalue', typtype => 'b', typlen => '-1', typbyval => 'f', typcategory => 'X',
+ typispreferred => 'f', typinput => 'typedvalue_in', typoutput => 'typedvalue_out',
+ typreceive => '-', typsend => '-', typalign => 'i', typstorage => 'x' },
{ oid => '2283', descr => 'pseudo-type representing a polymorphic base type',
typname => 'anyelement', typlen => '4', typbyval => 't', typtype => 'p',
typcategory => 'P', typinput => 'anyelement_in',
diff --git a/src/include/utils/typedvalue.h b/src/include/utils/typedvalue.h
new file mode 100644
index 0000000000..d3827b54a8
--- /dev/null
+++ b/src/include/utils/typedvalue.h
@@ -0,0 +1,28 @@
+/*-------------------------------------------------------------------------
+ *
+ * typedvalue.h
+ * Declarations for typedvalue data type support.
+ *
+ * Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ *
+ * src/include/utils/typedvalue.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef __TYPEDVALUE_H__
+#define __TYPEDVALUE_H__
+
+typedef struct
+{
+ int32 vl_len; /* varlena header */
+ Oid typid;
+ bool typbyval;
+ int16 typlen;
+ char data[FLEXIBLE_ARRAY_MEMBER];
+} TypedValueData;
+
+typedef TypedValueData *TypedValue;
+
+extern Datum makeTypedValue(Datum value, Oid typid, int16 typlen, bool typbyval);
+
+#endif
diff --git a/src/include/windowapi.h b/src/include/windowapi.h
index e8c9fc54d8..a4b8504f78 100644
--- a/src/include/windowapi.h
+++ b/src/include/windowapi.h
@@ -36,6 +36,34 @@
/* this struct is private in nodeWindowAgg.c */
typedef struct WindowObjectData *WindowObject;
+typedef struct WindowObjectProxyMutable
+{
+ /* true when request on winfuncarg doesn't return data */
+ bool isout;
+
+ /* cache for type related data of Window arguments */
+ int last_argno;
+ Oid typid;
+ int16 typlen;
+ bool typbyval;
+} WindowObjectProxyMutable;
+
+/*
+ * This type is used as proxy between PL variants of WinFuncArg
+ * functions and PL environment. The variables of windowobjectproxy
+ * type can be copied, so mutable content should be elsewhere.
+ */
+typedef struct WindowObjectProxyData
+{
+ int32 vl_len; /* varlena header */
+
+ WindowObject winobj;
+ FunctionCallInfo fcinfo;
+ WindowObjectProxyMutable *mutable_data;
+} WindowObjectProxyData;
+
+typedef WindowObjectProxyData *WindowObjectProxy;
+
#define PG_WINDOW_OBJECT() ((WindowObject) fcinfo->context)
#define WindowObjectIsValid(winobj) \
diff --git a/src/pl/plpgsql/src/Makefile b/src/pl/plpgsql/src/Makefile
index 193df8a010..ba6edfd42e 100644
--- a/src/pl/plpgsql/src/Makefile
+++ b/src/pl/plpgsql/src/Makefile
@@ -34,7 +34,7 @@ REGRESS_OPTS = --dbname=$(PL_TESTDB)
REGRESS = plpgsql_call plpgsql_control plpgsql_copy plpgsql_domain \
plpgsql_record plpgsql_cache plpgsql_simple plpgsql_transaction \
- plpgsql_trap plpgsql_trigger plpgsql_varprops
+ plpgsql_trap plpgsql_trigger plpgsql_varprops plpgsql_window
# where to find gen_keywordlist.pl and subsidiary files
TOOLSDIR = $(top_srcdir)/src/tools
diff --git a/src/pl/plpgsql/src/expected/plpgsql_window.out b/src/pl/plpgsql/src/expected/plpgsql_window.out
new file mode 100644
index 0000000000..b9cb015ecc
--- /dev/null
+++ b/src/pl/plpgsql/src/expected/plpgsql_window.out
@@ -0,0 +1,228 @@
+create or replace function pl_row_number()
+returns bigint as $$
+declare pos int8;
+begin
+ pos := get_current_position(windowobject);
+ pos := pos + 1;
+ perform set_mark_position(windowobject, pos);
+ return pos;
+end
+$$
+language plpgsql window;
+select pl_row_number() over (), v from (values(10),(20),(30)) v(v);
+ pl_row_number | v
+---------------+----
+ 1 | 10
+ 2 | 20
+ 3 | 30
+(3 rows)
+
+create or replace function pl_round_value(numeric)
+returns int as $$
+declare
+ num numeric;
+begin
+ num := get_input_value_for_row(windowobject, 1);
+ return round(num);
+end
+$$ language plpgsql window;
+select pl_round_value(v) over(order by v desc) from generate_series(0.1, 1.0, 0.1) g(v);
+ pl_round_value
+----------------
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+ 0
+ 0
+ 0
+ 0
+(10 rows)
+
+select pl_round_value(v + 1) over(order by v desc) from generate_series(0.1, 1.0, 0.1) g(v);
+ pl_round_value
+----------------
+ 2
+ 2
+ 2
+ 2
+ 2
+ 2
+ 1
+ 1
+ 1
+ 1
+(10 rows)
+
+create table test_table(v numeric);
+insert into test_table values(1),(3),(6),(6),(8),(7),(6),(5),(4);
+create or replace function pl_lag(numeric)
+returns numeric as $$
+declare
+ v numeric;
+begin
+ v := get_input_value_in_partition(windowobject, 1, -1, 'seek_current', false);
+ return v;
+end;
+$$ language plpgsql window;
+select pl_lag(v) over (), lag(v) over () from test_table;
+ pl_lag | lag
+--------+-----
+ |
+ 1 | 1
+ 3 | 3
+ 6 | 6
+ 6 | 6
+ 8 | 8
+ 7 | 7
+ 6 | 6
+ 5 | 5
+(9 rows)
+
+drop table test_table;
+create table test_table(v integer);
+insert into test_table values(1),(3),(6),(6),(8),(7),(6),(5),(4);
+select pl_lag(v) over (), lag(v) over () from test_table;
+ pl_lag | lag
+--------+-----
+ |
+ 1 | 1
+ 3 | 3
+ 6 | 6
+ 6 | 6
+ 8 | 8
+ 7 | 7
+ 6 | 6
+ 5 | 5
+(9 rows)
+
+create or replace function pl_moving_avg(numeric)
+returns numeric as $$
+declare
+ s numeric default 0.0;
+ v numeric;
+ c numeric default 0.0;
+begin
+ -- look before
+ v := get_input_value_in_partition(windowobject, 1, -1, 'seek_current', false);
+ if v is not null then
+ s := s + v;
+ c := c + 1.0;
+ end if;
+
+ -- look after
+ v := get_input_value_in_partition(windowobject, 1, 0, 'seek_current', false);
+ if v is not null then
+ s := s + v;
+ c := c + 1.0;
+ end if;
+
+ v := get_input_value_in_partition(windowobject, 1, 1, 'seek_current', false);
+ if v is not null then
+ s := s + v;
+ c := c + 1.0;
+ end if;
+
+ return trim_scale(s / c);
+end
+$$ language plpgsql window;
+select pl_moving_avg(v) over (), v from test_table;
+ pl_moving_avg | v
+--------------------+---
+ 2 | 1
+ 3.3333333333333333 | 3
+ 5 | 6
+ 6.6666666666666667 | 6
+ 7 | 8
+ 7 | 7
+ 6 | 6
+ 5 | 5
+ 4.5 | 4
+(9 rows)
+
+create or replace function pl_lag_polymorphic(anyelement)
+returns anyelement as $$
+declare
+ v $0%type;
+begin
+ v := get_input_value_in_partition(windowobject, 1, -1, 'seek_current', false);
+ return v;
+end;
+$$ language plpgsql window;
+select pl_lag_polymorphic(v) over (), lag(v) over () from test_table;
+ pl_lag_polymorphic | lag
+--------------------+-----
+ |
+ 1 | 1
+ 3 | 3
+ 6 | 6
+ 6 | 6
+ 8 | 8
+ 7 | 7
+ 6 | 6
+ 5 | 5
+(9 rows)
+
+create or replace function pl_pcontext_test(numeric)
+returns numeric as $$
+declare
+ n numeric;
+ v numeric;
+begin
+ n := get_partition_context_value(windowobject, null::numeric);
+ v := get_input_value_for_row(windowobject, 1);
+ perform set_partition_context_value(windowobject, v);
+
+ return n;
+end
+$$
+language plpgsql window;
+select v, pl_pcontext_test(v) over () from generate_series(0.1, 1.0, 0.1) g(v);
+ v | pl_pcontext_test
+-----+------------------
+ 0.1 |
+ 0.2 | 0.1
+ 0.3 | 0.2
+ 0.4 | 0.3
+ 0.5 | 0.4
+ 0.6 | 0.5
+ 0.7 | 0.6
+ 0.8 | 0.7
+ 0.9 | 0.8
+ 1.0 | 0.9
+(10 rows)
+
+create table test_missing_values(id int, v integer);
+insert into test_missing_values values(1,10),(2,11),(3,12),(4,null),(5,null),(6,15),(7,16);
+create or replace function pl_pcontext_test(numeric)
+returns numeric as $$
+declare
+ n numeric;
+ v numeric;
+begin
+ v := get_input_value_for_row(windowobject, 1);
+
+ if v is null then
+ v := get_partition_context_value(windowobject, null::numeric);
+ else
+ perform set_partition_context_value(windowobject, v);
+ end if;
+
+ return v;
+end
+$$
+language plpgsql window;
+select id, v, pl_pcontext_test(v) over (order by id) from test_missing_values;
+ id | v | pl_pcontext_test
+----+----+------------------
+ 1 | 10 | 10
+ 2 | 11 | 11
+ 3 | 12 | 12
+ 4 | | 12
+ 5 | | 12
+ 6 | 15 | 15
+ 7 | 16 | 16
+(7 rows)
+
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index 042deb2a96..78854d8370 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -586,6 +586,41 @@ do_compile(FunctionCallInfo fcinfo,
true);
}
+ if (function->fn_prokind == PROKIND_WINDOW)
+ {
+ PLpgSQL_type *dtype;
+ PLpgSQL_var *var;
+
+ /*
+ * Add the promise variable windowobject with windowobjectproxy type
+ *
+ * Pseudotypes are disallowed for custom variables. It is checked
+ * in plpgsql_build_variable, so instead call this function, build
+ * promise variable here.
+ */
+
+ dtype = plpgsql_build_datatype(WINDOWOBJECTPROXYOID,
+ -1,
+ function->fn_input_collation,
+ NULL);
+
+ /* this should be pseudotype */
+ Assert(dtype->ttype == PLPGSQL_TTYPE_PSEUDO);
+
+ var = palloc0(sizeof(PLpgSQL_var));
+
+ var->dtype = PLPGSQL_DTYPE_PROMISE;
+ var->promise = PLPGSQL_PROMISE_WINDOWOBJECT;
+
+ var->refname = pstrdup("windowobject");
+ var->datatype = dtype;
+
+ plpgsql_adddatum((PLpgSQL_datum *) var);
+ plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR,
+ var->dno,
+ var->refname);
+ }
+
ReleaseSysCache(typeTup);
break;
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b9e85a1a0f..ee3a1bbebf 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -593,6 +593,39 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo,
*/
exec_set_found(&estate, false);
+ /*
+ * Initialize promise winobject
+ */
+ if (func->fn_prokind == PROKIND_WINDOW)
+ {
+ /* fcinfo is available in this function too */
+ WindowObjectProxy wop;
+ WindowObjectProxyMutable *mutable_data;
+ MemoryContext oldcontext;
+
+ oldcontext = MemoryContextSwitchTo(estate.datum_context);
+
+ wop = palloc(sizeof(WindowObjectProxyData));
+ SET_VARSIZE(wop, sizeof(WindowObjectProxyData));
+
+ wop->winobj = PG_WINDOW_OBJECT();
+
+ Assert(WindowObjectIsValid(wop->winobj));
+
+ mutable_data = palloc0(sizeof(WindowObjectProxyMutable));
+ mutable_data->isout = false;
+ mutable_data->last_argno = -1;
+
+ wop->mutable_data = mutable_data;
+ wop->fcinfo = fcinfo;
+
+ MemoryContextSwitchTo(oldcontext);
+
+ estate.winobjproxy = wop;
+ }
+ else
+ estate.winobjproxy = NULL;
+
/*
* Let the instrumentation plugin peek at this function
*/
@@ -915,6 +948,11 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
plpgsql_estate_setup(&estate, func, NULL, NULL, NULL);
estate.trigdata = trigdata;
+ /*
+ * Trigger function cannot be WINDOW function
+ */
+ estate.winobjproxy = NULL;
+
/*
* Setup error traceback support for ereport()
*/
@@ -1293,11 +1331,13 @@ copy_plpgsql_datums(PLpgSQL_execstate *estate,
PLpgSQL_datum *indatum = indatums[i];
PLpgSQL_datum *outdatum;
+
/* This must agree with plpgsql_finish_datums on what is copiable */
switch (indatum->dtype)
{
case PLPGSQL_DTYPE_VAR:
case PLPGSQL_DTYPE_PROMISE:
+
outdatum = (PLpgSQL_datum *) ws_next;
memcpy(outdatum, indatum, sizeof(PLpgSQL_var));
ws_next += MAXALIGN(sizeof(PLpgSQL_var));
@@ -1486,6 +1526,17 @@ plpgsql_fulfill_promise(PLpgSQL_execstate *estate,
assign_text_var(estate, var, GetCommandTagName(estate->evtrigdata->tag));
break;
+ case PLPGSQL_PROMISE_WINDOWOBJECT:
+ if (!estate->winobjproxy)
+ elog(ERROR, "windowobject promise is not in a window function");
+
+ assign_simple_var(estate,
+ var,
+ PointerGetDatum(estate->winobjproxy),
+ false,
+ false);
+ break;
+
default:
elog(ERROR, "unrecognized promise type: %d", var->promise);
}
@@ -2524,6 +2575,17 @@ exec_stmt_getdiag(PLpgSQL_execstate *estate, PLpgSQL_stmt_getdiag *stmt)
}
break;
+ case PLPGSQL_GETDIAG_VALUE_IS_OUT:
+ {
+ if (!estate->winobjproxy)
+ elog(ERROR, "function is not a window function");
+
+ exec_assign_value(estate, var,
+ BoolGetDatum(estate->winobjproxy->mutable_data->isout),
+ false, BOOLOID, -1);
+ }
+ break;
+
default:
elog(ERROR, "unrecognized diagnostic item kind: %d",
diag_item->kind);
diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c
index ee60ced583..992763c735 100644
--- a/src/pl/plpgsql/src/pl_funcs.c
+++ b/src/pl/plpgsql/src/pl_funcs.c
@@ -321,6 +321,8 @@ plpgsql_getdiag_kindname(PLpgSQL_getdiag_kind kind)
return "CONSTRAINT_NAME";
case PLPGSQL_GETDIAG_DATATYPE_NAME:
return "PG_DATATYPE_NAME";
+ case PLPGSQL_GETDIAG_VALUE_IS_OUT:
+ return "PG_VALUE_IS_OUT";
case PLPGSQL_GETDIAG_MESSAGE_TEXT:
return "MESSAGE_TEXT";
case PLPGSQL_GETDIAG_TABLE_NAME:
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 8227bf0449..12e4735756 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -325,6 +325,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
%token <keyword> K_PG_EXCEPTION_CONTEXT
%token <keyword> K_PG_EXCEPTION_DETAIL
%token <keyword> K_PG_EXCEPTION_HINT
+%token <keyword> K_PG_VALUE_IS_OUT
%token <keyword> K_PRINT_STRICT_PARAMS
%token <keyword> K_PRIOR
%token <keyword> K_QUERY
@@ -1081,6 +1082,9 @@ getdiag_item :
else if (tok_is_keyword(tok, &yylval,
K_PG_EXCEPTION_CONTEXT, "pg_exception_context"))
$$ = PLPGSQL_GETDIAG_ERROR_CONTEXT;
+ else if (tok_is_keyword(tok, &yylval,
+ K_PG_VALUE_IS_OUT, "pg_value_is_out"))
+ $$ = PLPGSQL_GETDIAG_VALUE_IS_OUT;
else if (tok_is_keyword(tok, &yylval,
K_COLUMN_NAME, "column_name"))
$$ = PLPGSQL_GETDIAG_COLUMN_NAME;
diff --git a/src/pl/plpgsql/src/pl_unreserved_kwlist.h b/src/pl/plpgsql/src/pl_unreserved_kwlist.h
index 99b3cf7d8a..d27b7dfb85 100644
--- a/src/pl/plpgsql/src/pl_unreserved_kwlist.h
+++ b/src/pl/plpgsql/src/pl_unreserved_kwlist.h
@@ -84,6 +84,7 @@ PG_KEYWORD("pg_datatype_name", K_PG_DATATYPE_NAME)
PG_KEYWORD("pg_exception_context", K_PG_EXCEPTION_CONTEXT)
PG_KEYWORD("pg_exception_detail", K_PG_EXCEPTION_DETAIL)
PG_KEYWORD("pg_exception_hint", K_PG_EXCEPTION_HINT)
+PG_KEYWORD("pg_value_is_out", K_PG_VALUE_IS_OUT)
PG_KEYWORD("print_strict_params", K_PRINT_STRICT_PARAMS)
PG_KEYWORD("prior", K_PRIOR)
PG_KEYWORD("query", K_QUERY)
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 0c3d30fb13..4c1ed7cbf7 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -23,6 +23,8 @@
#include "utils/expandedrecord.h"
#include "utils/typcache.h"
+#include "windowapi.h"
+
/**********************************************************************
* Definitions
@@ -84,7 +86,8 @@ typedef enum PLpgSQL_promise_type
PLPGSQL_PROMISE_TG_NARGS,
PLPGSQL_PROMISE_TG_ARGV,
PLPGSQL_PROMISE_TG_EVENT,
- PLPGSQL_PROMISE_TG_TAG
+ PLPGSQL_PROMISE_TG_TAG,
+ PLPGSQL_PROMISE_WINDOWOBJECT
} PLpgSQL_promise_type;
/*
@@ -159,7 +162,8 @@ typedef enum PLpgSQL_getdiag_kind
PLPGSQL_GETDIAG_DATATYPE_NAME,
PLPGSQL_GETDIAG_MESSAGE_TEXT,
PLPGSQL_GETDIAG_TABLE_NAME,
- PLPGSQL_GETDIAG_SCHEMA_NAME
+ PLPGSQL_GETDIAG_SCHEMA_NAME,
+ PLPGSQL_GETDIAG_VALUE_IS_OUT
} PLpgSQL_getdiag_kind;
/*
@@ -612,6 +616,17 @@ typedef struct PLpgSQL_stmt_getdiag
List *diag_items; /* List of PLpgSQL_diag_item */
} PLpgSQL_stmt_getdiag;
+/*
+ * GET PG_WINDOW_CONTEXT statement
+ */
+typedef struct PLpgSQL_stmt_getwincxt
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ List *items;
+} PLpgSQL_stmt_getwincxt;
+
/*
* IF statement
*/
@@ -1049,6 +1064,8 @@ typedef struct PLpgSQL_execstate
TriggerData *trigdata; /* if regular trigger, data about firing */
EventTriggerData *evtrigdata; /* if event trigger, data about firing */
+ WindowObjectProxy winobjproxy; /* for window function we need proxy
+ * object between PL and WinFucArg funcions */
Datum retval;
bool retisnull;
Oid rettype; /* type of current retval */
diff --git a/src/pl/plpgsql/src/sql/plpgsql_window.sql b/src/pl/plpgsql/src/sql/plpgsql_window.sql
new file mode 100644
index 0000000000..6a3cab39a2
--- /dev/null
+++ b/src/pl/plpgsql/src/sql/plpgsql_window.sql
@@ -0,0 +1,135 @@
+create or replace function pl_row_number()
+returns bigint as $$
+declare pos int8;
+begin
+ pos := get_current_position(windowobject);
+ pos := pos + 1;
+ perform set_mark_position(windowobject, pos);
+ return pos;
+end
+$$
+language plpgsql window;
+
+select pl_row_number() over (), v from (values(10),(20),(30)) v(v);
+
+create or replace function pl_round_value(numeric)
+returns int as $$
+declare
+ num numeric;
+begin
+ num := get_input_value_for_row(windowobject, 1);
+ return round(num);
+end
+$$ language plpgsql window;
+
+select pl_round_value(v) over(order by v desc) from generate_series(0.1, 1.0, 0.1) g(v);
+
+select pl_round_value(v + 1) over(order by v desc) from generate_series(0.1, 1.0, 0.1) g(v);
+
+create table test_table(v numeric);
+insert into test_table values(1),(3),(6),(6),(8),(7),(6),(5),(4);
+
+create or replace function pl_lag(numeric)
+returns numeric as $$
+declare
+ v numeric;
+begin
+ v := get_input_value_in_partition(windowobject, 1, -1, 'seek_current', false);
+ return v;
+end;
+$$ language plpgsql window;
+
+select pl_lag(v) over (), lag(v) over () from test_table;
+
+drop table test_table;
+
+create table test_table(v integer);
+insert into test_table values(1),(3),(6),(6),(8),(7),(6),(5),(4);
+
+select pl_lag(v) over (), lag(v) over () from test_table;
+
+create or replace function pl_moving_avg(numeric)
+returns numeric as $$
+declare
+ s numeric default 0.0;
+ v numeric;
+ c numeric default 0.0;
+begin
+ -- look before
+ v := get_input_value_in_partition(windowobject, 1, -1, 'seek_current', false);
+ if v is not null then
+ s := s + v;
+ c := c + 1.0;
+ end if;
+
+ -- look after
+ v := get_input_value_in_partition(windowobject, 1, 0, 'seek_current', false);
+ if v is not null then
+ s := s + v;
+ c := c + 1.0;
+ end if;
+
+ v := get_input_value_in_partition(windowobject, 1, 1, 'seek_current', false);
+ if v is not null then
+ s := s + v;
+ c := c + 1.0;
+ end if;
+
+ return trim_scale(s / c);
+end
+$$ language plpgsql window;
+
+select pl_moving_avg(v) over (), v from test_table;
+
+create or replace function pl_lag_polymorphic(anyelement)
+returns anyelement as $$
+declare
+ v $0%type;
+begin
+ v := get_input_value_in_partition(windowobject, 1, -1, 'seek_current', false);
+ return v;
+end;
+$$ language plpgsql window;
+
+select pl_lag_polymorphic(v) over (), lag(v) over () from test_table;
+
+create or replace function pl_pcontext_test(numeric)
+returns numeric as $$
+declare
+ n numeric;
+ v numeric;
+begin
+ n := get_partition_context_value(windowobject, null::numeric);
+ v := get_input_value_for_row(windowobject, 1);
+ perform set_partition_context_value(windowobject, v);
+
+ return n;
+end
+$$
+language plpgsql window;
+
+select v, pl_pcontext_test(v) over () from generate_series(0.1, 1.0, 0.1) g(v);
+
+create table test_missing_values(id int, v integer);
+insert into test_missing_values values(1,10),(2,11),(3,12),(4,null),(5,null),(6,15),(7,16);
+
+create or replace function pl_pcontext_test(numeric)
+returns numeric as $$
+declare
+ n numeric;
+ v numeric;
+begin
+ v := get_input_value_for_row(windowobject, 1);
+
+ if v is null then
+ v := get_partition_context_value(windowobject, null::numeric);
+ else
+ perform set_partition_context_value(windowobject, v);
+ end if;
+
+ return v;
+end
+$$
+language plpgsql window;
+
+select id, v, pl_pcontext_test(v) over (order by id) from test_missing_values;
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index 0c74dc96a8..8203a7880e 100644
--- a/src/test/regress/expected/type_sanity.out
+++ b/src/test/regress/expected/type_sanity.out
@@ -73,7 +73,8 @@ ORDER BY p1.oid;
3361 | pg_ndistinct
3402 | pg_dependencies
5017 | pg_mcv_list
-(4 rows)
+ 9553 | typedvalue
+(5 rows)
-- Make sure typarray points to a "true" array type of our own base
SELECT p1.oid, p1.typname as basetype, p2.typname as arraytype,
Hi, Pavel:
Happy New Year.
+ command with clause <literal>WINDOW</literal>. The specific feature of
+ this functions is a possibility to two special storages with
this functions -> this function
possibility to two special storages: there is no verb.
'store with stored one value': store is repeated.
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
It would be better to change 2020 to 2021 in the new files.
For some functions, such as windowobject_get_func_arg_frame, it would be
better to add comment explaining their purposes.
For estimate_partition_context_size():
+ errmsg("size of value is greather than limit (1024
bytes)")));
Please include the value of typlen in the message. There is similar error
message in the else block where value of size should be included.
+ return *realsize;
+ }
+ else
The 'else' is not needed since the if block ends with return.
+ size += size / 3;
Please add a comment for the choice of constant 3.
+ /* by default we allocate 30 bytes */
+ *realsize = 0;
The value 30 may not be accurate - from the caller:
+ if (PG_ARGISNULL(2))
+ minsize = VARLENA_MINSIZE;
+ else
+ minsize = PG_GETARG_INT32(2);
VARLENA_MINSIZE is 32.
Cheers
On Fri, Jan 1, 2021 at 3:29 AM Pavel Stehule <pavel.stehule@gmail.com>
wrote:
Show quoted text
Hi
rebase
Regards
Pavel
Hi
pá 1. 1. 2021 v 18:57 odesílatel Zhihong Yu <zyu@yugabyte.com> napsal:
Hi, Pavel:
Happy New Year.+ command with clause <literal>WINDOW</literal>. The specific feature of + this functions is a possibility to two special storages withthis functions -> this function
possibility to two special storages: there is no verb.
'store with stored one value': store is repeated.
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
It would be better to change 2020 to 2021 in the new files.
fixed
For some functions, such as windowobject_get_func_arg_frame, it would be
better to add comment explaining their purposes.
It is commented before. These functions just call WinAPI functions
/*
* High level access function. These functions are wrappers for windows API
* for PL languages based on usage WindowObjectProxy.
*/
For estimate_partition_context_size():
+ errmsg("size of value is greather than limit (1024
bytes)")));Please include the value of typlen in the message. There is similar error
message in the else block where value of size should be included.+ return *realsize; + } + elseThe 'else' is not needed since the if block ends with return.
yes, but it is there for better readability (symmetry)
+ size += size / 3;
Please add a comment for the choice of constant 3.
+ /* by default we allocate 30 bytes */ + *realsize = 0;The value 30 may not be accurate - from the caller:
+ if (PG_ARGISNULL(2)) + minsize = VARLENA_MINSIZE; + else + minsize = PG_GETARG_INT32(2);VARLENA_MINSIZE is 32.
Cheers
On Fri, Jan 1, 2021 at 3:29 AM Pavel Stehule <pavel.stehule@gmail.com>
wrote:Hi
rebase
Regards
Pavel
I am sending updated patch
Thank you for comments
Regards
Pavel
Attachments:
Hi, Pavel:
Thanks for the update.
I don't have other comment.
Cheers
On Mon, Jan 4, 2021 at 3:15 AM Pavel Stehule <pavel.stehule@gmail.com>
wrote:
Show quoted text
Hi
pá 1. 1. 2021 v 18:57 odesílatel Zhihong Yu <zyu@yugabyte.com> napsal:
Hi, Pavel:
Happy New Year.+ command with clause <literal>WINDOW</literal>. The specific feature of + this functions is a possibility to two special storages withthis functions -> this function
possibility to two special storages: there is no verb.
'store with stored one value': store is repeated.
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
It would be better to change 2020 to 2021 in the new files.
fixed
For some functions, such as windowobject_get_func_arg_frame, it would be
better to add comment explaining their purposes.It is commented before. These functions just call WinAPI functions
/*
* High level access function. These functions are wrappers for windows API
* for PL languages based on usage WindowObjectProxy.
*/For estimate_partition_context_size():
+ errmsg("size of value is greather than limit (1024
bytes)")));Please include the value of typlen in the message. There is similar error
message in the else block where value of size should be included.+ return *realsize; + } + elseThe 'else' is not needed since the if block ends with return.
yes, but it is there for better readability (symmetry)
+ size += size / 3;
Please add a comment for the choice of constant 3.
+ /* by default we allocate 30 bytes */ + *realsize = 0;The value 30 may not be accurate - from the caller:
+ if (PG_ARGISNULL(2)) + minsize = VARLENA_MINSIZE; + else + minsize = PG_GETARG_INT32(2);VARLENA_MINSIZE is 32.
Cheers
On Fri, Jan 1, 2021 at 3:29 AM Pavel Stehule <pavel.stehule@gmail.com>
wrote:Hi
rebase
Regards
Pavel
I am sending updated patch
Thank you for comments
Regards
Pavel
Pavel Stehule <pavel.stehule@gmail.com> writes:
[ plpgsql-window-functions-20210104.patch.gz ]
I spent some time looking at this patch. It would certainly be
appealing to have some ability to write custom window functions
without descending into C; but I'm not very happy about the details.
I'm okay with the idea of having a special variable of a new pseudotype.
That's not exactly pretty, but it descends directly from how we handle
the arguments of trigger functions, so at least there's precedent.
What's bugging me though is the "typedvalue" stuff. That seems like a
conceptual mess, a performance loss, and a permanent maintenance time
sink. To avoid performance complaints, eventually this hard-wired set
of conversions would have to bloom to cover every built-in cast, and
as for extension types, you're just out of luck.
One way to avoid that would be to declare the argument-fetching
functions as polymorphics with a dummy argument that just provides
the expected result type. So users would write something like
create function pl_lag(x numeric)
...
v := get_input_value_in_partition(windowobject, x, 1, -1,
'seek_current', false);
where the argument-fetching function is declared
get_input_value_in_partition(windowobject, anyelement, int, ...)
returns anyelement
and internally it could verify that the n'th window function argument
matches the type of its second argument. While this could be made
to work, it's kind of unsatisfying because the argument number "1" is
so obviously redundant with the reference to "x". Ideally one should
only have to write "x". I don't quite see how to make that work,
but maybe there's a way?
On the whole though, I think your original idea of bespoke plpgsql
syntax is better, ie let's write something like
GET WINDOW VALUE v := x AT PARTITION CURRENT(-1);
and hide all the mechanism behind that. The reference to "x" is enough
to provide the argument number and type, and the window object doesn't
have to be explicitly visible at all.
Yeah, this will mean that anybody who wants to provide equivalent
functionality in some other PL will have to do more work. But it's
not like it was going to be zero effort for them before. Furthermore,
it's not clear to me that other PLs would want to adopt your current
design anyway. For example, I bet PL/R would like to somehow make
window arguments map into vectors on the R side, but there's no chance
of that with this SQL layer in between.
regards, tom lane
Hi
so 16. 1. 2021 v 0:09 odesílatel Tom Lane <tgl@sss.pgh.pa.us> napsal:
Pavel Stehule <pavel.stehule@gmail.com> writes:
[ plpgsql-window-functions-20210104.patch.gz ]
I spent some time looking at this patch. It would certainly be
appealing to have some ability to write custom window functions
without descending into C; but I'm not very happy about the details.I'm okay with the idea of having a special variable of a new pseudotype.
That's not exactly pretty, but it descends directly from how we handle
the arguments of trigger functions, so at least there's precedent.
What's bugging me though is the "typedvalue" stuff. That seems like a
conceptual mess, a performance loss, and a permanent maintenance time
sink. To avoid performance complaints, eventually this hard-wired set
of conversions would have to bloom to cover every built-in cast, and
as for extension types, you're just out of luck.
I invited typed values with an idea of larger usability. With this type we
can implement dynamic iteration over records better than now, when the
fields of records should be cast to text or json before operation. With
this type I can hold typed value longer time and I can do some like:
DECLARE var typedvalue;
var := fx(..);
IF var IS OF integer THEN
var_int := CAST(var AS int);
ELSEIF var IS OF date THEN
var_date := CAST(var AS date);
ELSE
var_text := CAST(var AS text);
END;
Sometimes (when you process some external data) this late (lazy) cast can
be better and allows you to use typed values. When I read external data,
sometimes I don't know types of these data before reading. I would like to
inject a possibility of more dynamic work with values and variables (but
still cleanly and safely). It should be more safe and faster than now, when
people should use the "text" type.
But I understand and I agree with your objections. Probably a lot of people
will use this type badly.
One way to avoid that would be to declare the argument-fetching
functions as polymorphics with a dummy argument that just provides
the expected result type. So users would write something likecreate function pl_lag(x numeric)
...
v := get_input_value_in_partition(windowobject, x, 1, -1,
'seek_current', false);where the argument-fetching function is declared
get_input_value_in_partition(windowobject, anyelement, int, ...)
returns anyelementand internally it could verify that the n'th window function argument
matches the type of its second argument. While this could be made
to work, it's kind of unsatisfying because the argument number "1" is
so obviously redundant with the reference to "x". Ideally one should
only have to write "x". I don't quite see how to make that work,
but maybe there's a way?On the whole though, I think your original idea of bespoke plpgsql
syntax is better, ie let's write something likeGET WINDOW VALUE v := x AT PARTITION CURRENT(-1);
and hide all the mechanism behind that. The reference to "x" is enough
to provide the argument number and type, and the window object doesn't
have to be explicitly visible at all.
yes, this syntax looks well.
The second question is work with partition context value. This should be
only one value, and of only one but of any type per function. In this case
we cannot use GET statements. I had an idea of enhancing declaration. Some
like
DECLARE
pcx PARTITION CONTEXT (int); -- read partition context
BEGIN
pcx := 10; -- set partition context
What do you think about it?
Regards
Pavel
Show quoted text
Yeah, this will mean that anybody who wants to provide equivalent
functionality in some other PL will have to do more work. But it's
not like it was going to be zero effort for them before. Furthermore,
it's not clear to me that other PLs would want to adopt your current
design anyway. For example, I bet PL/R would like to somehow make
window arguments map into vectors on the R side, but there's no chance
of that with this SQL layer in between.regards, tom lane
Pavel Stehule <pavel.stehule@gmail.com> writes:
The second question is work with partition context value. This should be
only one value, and of only one but of any type per function. In this case
we cannot use GET statements. I had an idea of enhancing declaration. Some
like
DECLARE
pcx PARTITION CONTEXT (int); -- read partition context
BEGIN
pcx := 10; -- set partition context
What do you think about it?
Uh, what? I don't understand what this "partition context" is.
regards, tom lane
st 20. 1. 2021 v 21:07 odesílatel Tom Lane <tgl@sss.pgh.pa.us> napsal:
Pavel Stehule <pavel.stehule@gmail.com> writes:
The second question is work with partition context value. This should be
only one value, and of only one but of any type per function. In thiscase
we cannot use GET statements. I had an idea of enhancing declaration.
Some
like
DECLARE
pcx PARTITION CONTEXT (int); -- read partition context
BEGIN
pcx := 10; -- set partition contextWhat do you think about it?
Uh, what? I don't understand what this "partition context" is.
It was my name for an access to window partition local memory -
WinGetPartitionLocalMemory
We need some interface for this cache
Regards
Pavel
Show quoted text
regards, tom lane
Pavel Stehule <pavel.stehule@gmail.com> writes:
st 20. 1. 2021 v 21:07 odesílatel Tom Lane <tgl@sss.pgh.pa.us> napsal:
Uh, what? I don't understand what this "partition context" is.
It was my name for an access to window partition local memory -
WinGetPartitionLocalMemory
Ah.
We need some interface for this cache
I'm not convinced we need to expose that, or that it'd be very
satisfactory to plpgsql users if we did. The fact that it's fixed-size
and initializes to zeroes are both things that are okay for C programmers
but might be awkward to deal with in plpgsql code. At the very least it
would greatly constrain what data types you could usefully store.
So I'd be inclined to leave that out, at least for the first version.
regards, tom lane
st 20. 1. 2021 v 21:32 odesílatel Tom Lane <tgl@sss.pgh.pa.us> napsal:
Pavel Stehule <pavel.stehule@gmail.com> writes:
st 20. 1. 2021 v 21:07 odesílatel Tom Lane <tgl@sss.pgh.pa.us> napsal:
Uh, what? I don't understand what this "partition context" is.
It was my name for an access to window partition local memory -
WinGetPartitionLocalMemoryAh.
We need some interface for this cache
I'm not convinced we need to expose that, or that it'd be very
satisfactory to plpgsql users if we did. The fact that it's fixed-size
and initializes to zeroes are both things that are okay for C programmers
but might be awkward to deal with in plpgsql code. At the very least it
would greatly constrain what data types you could usefully store.So I'd be inclined to leave that out, at least for the first version.
I think this functionality is relatively important. If somebody tries to
implement own window function, then he starts with some variation of the
row_num function.
We can support only types of fixed length to begin.
Regards
Pavel
Show quoted text
regards, tom lane
st 20. 1. 2021 v 21:14 odesílatel Pavel Stehule <pavel.stehule@gmail.com>
napsal:
st 20. 1. 2021 v 21:07 odesílatel Tom Lane <tgl@sss.pgh.pa.us> napsal:
Pavel Stehule <pavel.stehule@gmail.com> writes:
The second question is work with partition context value. This should be
only one value, and of only one but of any type per function. In thiscase
we cannot use GET statements. I had an idea of enhancing declaration.
Some
like
DECLARE
pcx PARTITION CONTEXT (int); -- read partition context
BEGIN
pcx := 10; -- set partition contextWhat do you think about it?
Uh, what? I don't understand what this "partition context" is.
It was my name for an access to window partition local memory -
WinGetPartitionLocalMemoryWe need some interface for this cache
I have to think more about declarative syntax. When I try to transform our
WindowObject API directly, then it looks like Cobol. It needs a different
concept to be user friendly.
Regards
Pavel
Show quoted text
Regards
Pavel
regards, tom lane