Hi rebase
Regards Pavel
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,