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();
+
+ Window Functions
+
+
+ window
+ in PL/pgSQL
+
+
+
+ PL/pgSQL can be used to define window
+ functions. A window function is created with the CREATE FUNCTION
+ command with clause WINDOW. 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).
+
+
+
+ Access to both storages is done with special internal variable
+ WINDOWOBJECT. This variable is declared implicitly,
+ and it is available only in window functions.
+
+
+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;
+
+
+
+
+ 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 lag can looks like
+ (the window functions in plpgsql can use polymorphic types too):
+
+
+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);
+
+
+
+
+
+ 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
+ get_partition_context_value or modified by function
+ set_partition_context_value. Next function replaces
+ missing values by previous non NULL value:
+
+
+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;
+$$;
+
+
+
+
+
+
PL/pgSQL under the Hood
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 K_PG_EXCEPTION_CONTEXT
%token K_PG_EXCEPTION_DETAIL
%token K_PG_EXCEPTION_HINT
+%token K_PG_VALUE_IS_OUT
%token K_PRINT_STRICT_PARAMS
%token K_PRIOR
%token 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,