Monday, July 14, 2008

Re: [PATCHES] variadic function support

*** ./doc/src/sgml/ref/create_function.sgml.orig 2008-07-14 09:11:54.000000000 +0200
--- ./doc/src/sgml/ref/create_function.sgml 2008-07-14 09:14:37.000000000 +0200
***************
*** 102,108 ****
<listitem>
<para>
The mode of an argument: either <literal>IN</>, <literal>OUT</>,
! or <literal>INOUT</>. If omitted, the default is <literal>IN</>.
</para>
</listitem>
</varlistentry>
--- 102,109 ----
<listitem>
<para>
The mode of an argument: either <literal>IN</>, <literal>OUT</>,
! <literal>INOUT</> or <literal>VARIADIC</literal>. If omitted,
! the default is <literal>IN</>.
</para>
</listitem>
</varlistentry>
*** ./doc/src/sgml/xfunc.sgml.orig 2008-07-14 09:12:00.000000000 +0200
--- ./doc/src/sgml/xfunc.sgml 2008-07-14 09:14:37.000000000 +0200
***************
*** 578,584 ****

<para>
Parameters can be marked as <literal>IN</> (the default),
! <literal>OUT</>, or <literal>INOUT</>. An <literal>INOUT</>
parameter serves as both an input parameter (part of the calling
argument list) and an output parameter (part of the result record type).
</para>
--- 578,585 ----

<para>
Parameters can be marked as <literal>IN</> (the default),
! <literal>OUT</>, <literal>INOUT</>, or <literal>VARIADIC</literal>.
! An <literal>INOUT</>
parameter serves as both an input parameter (part of the calling
argument list) and an output parameter (part of the result record type).
</para>
***************
*** 805,810 ****
--- 806,833 ----
</screen>
</para>
</sect2>
+
+ <sect2>
+ <title>Variadic <acronym>SQL</acronym> Functions</title>
+
+ <para>
+ <acronym>SQL</acronym> functions can be declared to accept
+ variable number of arguments.
+ <screen>
+ CREATE FUNCTION mleast(variadic numeric[]) RETURNS numeric AS $$
+ SELECT min($1[i])
+ FROM generate_subscripts($1,1) g(i);
+ $$ LANGUAGE SQL;
+
+ SELECT mleast(10, -1, 5, 4);
+ mleast
+ --------
+ -1
+ (1 row)
+ </screen>
+ </para>
+ </sect2>
+
</sect1>

<sect1 id="xfunc-overload">
*** ./src/backend/catalog/namespace.c.orig 2008-07-14 09:12:08.000000000 +0200
--- ./src/backend/catalog/namespace.c 2008-07-14 14:58:31.000000000 +0200
***************
*** 38,43 ****
--- 38,44 ----
#include "commands/dbcommands.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
+ #include "parser/parse_func.h"
#include "storage/backendid.h"
#include "storage/ipc.h"
#include "utils/acl.h"
***************
*** 570,576 ****
* identical entries in later namespaces.
*/
FuncCandidateList
! FuncnameGetCandidates(List *names, int nargs)
{
FuncCandidateList resultList = NULL;
char *schemaname;
--- 571,577 ----
* identical entries in later namespaces.
*/
FuncCandidateList
! FuncnameGetCandidates(List *names, int nargs, bool transform_variadic)
{
FuncCandidateList resultList = NULL;
char *schemaname;
***************
*** 606,614 ****
int pronargs = procform->pronargs;
int pathpos = 0;
FuncCandidateList newResult;

/* Ignore if it doesn't match requested argument count */
! if (nargs >= 0 && pronargs != nargs)
continue;

if (OidIsValid(namespaceId))
--- 607,656 ----
int pronargs = procform->pronargs;
int pathpos = 0;
FuncCandidateList newResult;
+ Oid va_oid = InvalidOid;
+ bool variadic = false; /* true when arglist contains PROARGMODE_VARIADIC parameter */
+ bool isnull;
+ Datum proargmodes;
+
+ /*
+ * Search type of variadic argument,
+ */
+ proargmodes = SysCacheGetAttr(PROCOID, proctup,
+ Anum_pg_proc_proargmodes, &isnull);
+ if (!isnull)
+ {
+ ArrayType *ar = DatumGetArrayTypeP(proargmodes);
+ char *argmodes;
+ int j;
+
+ argmodes = ARR_DATA_PTR(ar);
+ for (j = 0; j < ARR_DIMS(ar)[0]; j++)
+ if (argmodes[j] == PROARGMODE_VARIADIC)
+ {
+ variadic = true;
+ switch (procform->proargtypes.values[j])
+ {
+ case ANYOID:
+ va_oid = ANYOID;
+ break;
+ case ANYARRAYOID:
+ va_oid = ANYELEMENTOID;
+ break;
+ default:
+ va_oid = get_element_type(procform->proargtypes.values[j]);
+ Assert(OidIsValid(va_oid));
+ }
+
+ break;
+ }
+ }

/* Ignore if it doesn't match requested argument count */
! if (nargs >= 0 && pronargs != nargs && !variadic)
! continue;
!
! /* Ignore variadic function with less arguments */
! if (nargs >= 0 && pronargs > nargs && variadic)
continue;

if (OidIsValid(namespaceId))
***************
*** 676,683 ****
}
if (prevResult)
{
! /* We have a match with a previous result */
! Assert(pathpos != prevResult->pathpos);
if (pathpos > prevResult->pathpos)
continue; /* keep previous result */
/* replace previous result */
--- 718,734 ----
}
if (prevResult)
{
! /*
! * We have a match with a previous result. Variadic functions should
! * be not unique - foo(numeric), foo(variadic numeric[]).
! */
! if (pathpos == prevResult->pathpos)
! ereport(ERROR,
! (errcode(ERRCODE_AMBIGUOUS_FUNCTION),
! errmsg("function %s is not unique",
! func_signature_string(names, nargs,
! procform->proargtypes.values))));
!
if (pathpos > prevResult->pathpos)
continue; /* keep previous result */
/* replace previous result */
***************
*** 689,706 ****
}

/*
! * Okay to add it to result list
*/
! newResult = (FuncCandidateList)
! palloc(sizeof(struct _FuncCandidateList) - sizeof(Oid)
! + pronargs * sizeof(Oid));
newResult->pathpos = pathpos;
newResult->oid = HeapTupleGetOid(proctup);
- newResult->nargs = pronargs;
- memcpy(newResult->args, procform->proargtypes.values,
- pronargs * sizeof(Oid));
-
newResult->next = resultList;
resultList = newResult;
}

--- 740,784 ----
}

/*
! * Okay to add it to result list - VARIADIC parameter isn't transformed when
! * we would show and work with function signature, etc pg_function_is_visible,
! * DROP FUNCTION (transform_variadic = false).
*/
! if (variadic && transform_variadic)
! {
! int i;
!
! Assert(nargs >= pronargs);
!
! newResult = (FuncCandidateList)
! palloc(sizeof(struct _FuncCandidateList) - sizeof(Oid)
! + nargs * sizeof(Oid));
! newResult->nargs = nargs;
! newResult->nvargs = nargs - pronargs + 1;
! newResult->variadic_oid = va_oid;
! memcpy(newResult->args, procform->proargtypes.values,
! (pronargs - 1) * sizeof(Oid));
!
! /* Multiply variadic argument */
! for (i = pronargs - 1; i < nargs; i++)
! newResult->args[i] = va_oid;
! }
! else
! {
! newResult = (FuncCandidateList)
! palloc(sizeof(struct _FuncCandidateList) - sizeof(Oid)
! + pronargs * sizeof(Oid));
! newResult->nargs = pronargs;
! newResult->nvargs = 0;
! newResult->variadic_oid = 0;
! memcpy(newResult->args, procform->proargtypes.values,
! pronargs * sizeof(Oid));
! }
!
newResult->pathpos = pathpos;
newResult->oid = HeapTupleGetOid(proctup);
newResult->next = resultList;
+
resultList = newResult;
}

***************
*** 755,761 ****

visible = false;

! clist = FuncnameGetCandidates(list_make1(makeString(proname)), nargs);

for (; clist; clist = clist->next)
{
--- 833,839 ----

visible = false;

! clist = FuncnameGetCandidates(list_make1(makeString(proname)), nargs, false);

for (; clist; clist = clist->next)
{
*** ./src/backend/catalog/pg_aggregate.c.orig 2008-07-14 09:12:15.000000000 +0200
--- ./src/backend/catalog/pg_aggregate.c 2008-07-14 13:10:59.000000000 +0200
***************
*** 297,302 ****
--- 297,304 ----
FuncDetailCode fdresult;
AclResult aclresult;
int i;
+ int nvargs;
+ Oid va_oid;

/*
* func_get_detail looks up the function in the catalogs, does
***************
*** 307,313 ****
*/
fdresult = func_get_detail(fnName, NIL, nargs, input_types,
&fnOid, rettype, &retset,
! &true_oid_array);

/* only valid case is a normal function not returning a set */
if (fdresult != FUNCDETAIL_NORMAL || !OidIsValid(fnOid))
--- 309,316 ----
*/
fdresult = func_get_detail(fnName, NIL, nargs, input_types,
&fnOid, rettype, &retset,
! &true_oid_array,
! &nvargs, &va_oid);

/* only valid case is a normal function not returning a set */
if (fdresult != FUNCDETAIL_NORMAL || !OidIsValid(fnOid))
***************
*** 321,326 ****
--- 324,334 ----
errmsg("function %s returns a set",
func_signature_string(fnName, nargs, input_types))));

+ if (nvargs > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("variadic parameters not supported for aggregate functions")));
+
/*
* If there are any polymorphic types involved, enforce consistency, and
* possibly refine the result type. It's OK if the result is still
*** ./src/backend/commands/functioncmds.c.orig 2008-07-12 12:44:56.000000000 +0200
--- ./src/backend/commands/functioncmds.c 2008-07-14 14:10:18.000000000 +0200
***************
*** 174,179 ****
--- 174,180 ----
Datum *paramNames;
int outCount = 0;
bool have_names = false;
+ int varCount = 0;
ListCell *x;
int i;

***************
*** 228,242 ****
errmsg("functions cannot accept set arguments")));

if (fp->mode != FUNC_PARAM_OUT)
inTypes[inCount++] = toid;

! if (fp->mode != FUNC_PARAM_IN)
{
if (outCount == 0) /* save first OUT param's type */
*requiredResultType = toid;
outCount++;
}

allTypes[i] = ObjectIdGetDatum(toid);

paramModes[i] = CharGetDatum(fp->mode);
--- 229,271 ----
errmsg("functions cannot accept set arguments")));

if (fp->mode != FUNC_PARAM_OUT)
+ {
inTypes[inCount++] = toid;
+ /* check if variadic argument is last IN argument */
+ if (varCount > 0 && fp->mode != FUNC_PARAM_OUT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("variadic parameter must be the last parameter to the function")));
+ }

! if (fp->mode != FUNC_PARAM_IN && fp->mode != FUNC_PARAM_VARIADIC)
{
if (outCount == 0) /* save first OUT param's type */
*requiredResultType = toid;
outCount++;
}

+ if (fp->mode == FUNC_PARAM_VARIADIC)
+ {
+ if (varCount++ > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("function cannot accept two or more variadic parameters")));
+
+ /* check variadic parameter type */
+ switch (toid)
+ {
+ case ANYARRAYOID:
+ case ANYOID:
+ break;
+ default:
+ if (!OidIsValid(get_element_type(toid)))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("variadic parameter must be an array")));
+ }
+ }
+
allTypes[i] = ObjectIdGetDatum(toid);

paramModes[i] = CharGetDatum(fp->mode);
***************
*** 253,259 ****
/* Now construct the proper outputs as needed */
*parameterTypes = buildoidvector(inTypes, inCount);

! if (outCount > 0)
{
*allParameterTypes = construct_array(allTypes, parameterCount, OIDOID,
sizeof(Oid), true, 'i');
--- 282,288 ----
/* Now construct the proper outputs as needed */
*parameterTypes = buildoidvector(inTypes, inCount);

! if (outCount > 0 || varCount > 0)
{
*allParameterTypes = construct_array(allTypes, parameterCount, OIDOID,
sizeof(Oid), true, 'i');
*** ./src/backend/nodes/copyfuncs.c.orig 2008-07-14 21:43:28.000000000 +0200
--- ./src/backend/nodes/copyfuncs.c 2008-07-14 21:49:49.000000000 +0200
***************
*** 1652,1657 ****
--- 1652,1658 ----
COPY_SCALAR_FIELD(agg_star);
COPY_SCALAR_FIELD(agg_distinct);
COPY_SCALAR_FIELD(location);
+ COPY_SCALAR_FIELD(variadic_expr);

return newnode;
}
*** ./src/backend/nodes/equalfuncs.c.orig 2008-07-14 21:43:43.000000000 +0200
--- ./src/backend/nodes/equalfuncs.c 2008-07-14 21:49:57.000000000 +0200
***************
*** 1706,1711 ****
--- 1706,1712 ----
COMPARE_SCALAR_FIELD(agg_star);
COMPARE_SCALAR_FIELD(agg_distinct);
COMPARE_SCALAR_FIELD(location);
+ COMPARE_SCALAR_FIELD(variadic_expr);

return true;
}
*** ./src/backend/nodes/outfuncs.c.orig 2008-07-14 21:44:16.000000000 +0200
--- ./src/backend/nodes/outfuncs.c 2008-07-14 21:54:37.000000000 +0200
***************
*** 1611,1616 ****
--- 1611,1617 ----
WRITE_BOOL_FIELD(agg_star);
WRITE_BOOL_FIELD(agg_distinct);
WRITE_INT_FIELD(location);
+ WRITE_NODE_FIELD(variadic_expr);
}

static void
*** ./src/backend/parser/gram.y.orig 2008-07-14 09:12:28.000000000 +0200
--- ./src/backend/parser/gram.y 2008-07-14 22:14:22.000000000 +0200
***************
*** 444,450 ****
UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN UNLISTEN UNTIL
UPDATE USER USING

! VACUUM VALID VALIDATOR VALUE_P VALUES VARCHAR VARYING
VERBOSE VERSION_P VIEW VOLATILE

WHEN WHERE WHITESPACE_P WITH WITHOUT WORK WRITE
--- 444,450 ----
UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN UNLISTEN UNTIL
UPDATE USER USING

! VACUUM VALID VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
VERBOSE VERSION_P VIEW VOLATILE

WHEN WHERE WHITESPACE_P WITH WITHOUT WORK WRITE
***************
*** 4204,4209 ****
--- 4204,4210 ----
| OUT_P { $$ = FUNC_PARAM_OUT; }
| INOUT { $$ = FUNC_PARAM_INOUT; }
| IN_P OUT_P { $$ = FUNC_PARAM_INOUT; }
+ | VARIADIC { $$ = FUNC_PARAM_VARIADIC; }
;

/*
***************
*** 7863,7868 ****
--- 7864,7870 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->location = @1;
+ n->variadic_expr = NULL;
$$ = (Node *)n;
}
| func_name '(' expr_list ')'
***************
*** 7873,7878 ****
--- 7875,7903 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->location = @1;
+ n->variadic_expr = NULL;
+ $$ = (Node *)n;
+ }
+ | func_name '(' VARIADIC a_expr ')'
+ {
+ FuncCall *n = makeNode(FuncCall);
+ n->funcname = $1;
+ n->args = NIL;
+ n->agg_star = FALSE;
+ n->agg_distinct = FALSE;
+ n->location = @1;
+ n->variadic_expr = $4;
+ $$ = (Node *)n;
+ }
+ | func_name '(' expr_list VARIADIC a_expr ')'
+ {
+ FuncCall *n = makeNode(FuncCall);
+ n->funcname = $1;
+ n->args = $3;
+ n->agg_star = FALSE;
+ n->agg_distinct = FALSE;
+ n->location = @1;
+ n->variadic_expr = $5;
$$ = (Node *)n;
}
| func_name '(' ALL expr_list ')'
***************
*** 7887,7892 ****
--- 7912,7918 ----
* for that in FuncCall at the moment.
*/
n->location = @1;
+ n->variadic_expr = NULL;
$$ = (Node *)n;
}
| func_name '(' DISTINCT expr_list ')'
***************
*** 7897,7902 ****
--- 7923,7929 ----
n->agg_star = FALSE;
n->agg_distinct = TRUE;
n->location = @1;
+ n->variadic_expr = NULL;
$$ = (Node *)n;
}
| func_name '(' '*' ')'
***************
*** 7917,7922 ****
--- 7944,7950 ----
n->agg_star = TRUE;
n->agg_distinct = FALSE;
n->location = @1;
+ n->variadic_expr = NULL;
$$ = (Node *)n;
}
| CURRENT_DATE
*** ./src/backend/parser/keywords.c.orig 2008-07-14 09:12:34.000000000 +0200
--- ./src/backend/parser/keywords.c 2008-07-14 09:14:37.000000000 +0200
***************
*** 393,398 ****
--- 393,399 ----
{"value", VALUE_P, UNRESERVED_KEYWORD},
{"values", VALUES, COL_NAME_KEYWORD},
{"varchar", VARCHAR, COL_NAME_KEYWORD},
+ {"variadic", VARIADIC, UNRESERVED_KEYWORD},
{"varying", VARYING, UNRESERVED_KEYWORD},
{"verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD},
{"version", VERSION_P, UNRESERVED_KEYWORD},
*** ./src/backend/parser/parse_expr.c.orig 2008-07-14 22:17:06.000000000 +0200
--- ./src/backend/parser/parse_expr.c 2008-07-14 23:01:35.000000000 +0200
***************
*** 359,365 ****
list_make1(n),
list_make1(result),
false, false, true,
! -1);
}
}
/* process trailing subscripts, if any */
--- 359,365 ----
list_make1(n),
list_make1(result),
false, false, true,
! -1, NULL);
}
}
/* process trailing subscripts, if any */
***************
*** 482,488 ****
list_make1(makeString(name2)),
list_make1(node),
false, false, true,
! cref->location);
}
break;
}
--- 482,489 ----
list_make1(makeString(name2)),
list_make1(node),
false, false, true,
! cref->location,
! NULL);
}
break;
}
***************
*** 512,518 ****
list_make1(makeString(name3)),
list_make1(node),
false, false, true,
! cref->location);
}
break;
}
--- 513,520 ----
list_make1(makeString(name3)),
list_make1(node),
false, false, true,
! cref->location,
! NULL);
}
break;
}
***************
*** 553,559 ****
list_make1(makeString(name4)),
list_make1(node),
false, false, true,
! cref->location);
}
break;
}
--- 555,562 ----
list_make1(makeString(name4)),
list_make1(node),
false, false, true,
! cref->location,
! NULL);
}
break;
}
***************
*** 1017,1022 ****
--- 1020,1026 ----
{
List *targs;
ListCell *args;
+ Node *variadic_expr;

/*
* Transform the list of arguments. We use a shallow list copy and then
***************
*** 1031,1036 ****
--- 1035,1042 ----
lfirst(args) = transformExpr(pstate,
(Node *) lfirst(args));
}
+
+ variadic_expr = transformExpr(pstate, fn->variadic_expr);

return ParseFuncOrColumn(pstate,
fn->funcname,
***************
*** 1038,1044 ****
fn->agg_star,
fn->agg_distinct,
false,
! fn->location);
}

static Node *
--- 1044,1051 ----
fn->agg_star,
fn->agg_distinct,
false,
! fn->location,
! variadic_expr);
}

static Node *
*** ./src/backend/parser/parse_func.c.orig 2008-07-14 09:12:41.000000000 +0200
--- ./src/backend/parser/parse_func.c 2008-07-14 23:54:45.000000000 +0200
***************
*** 63,69 ****
Node *
ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
bool agg_star, bool agg_distinct, bool is_column,
! int location)
{
Oid rettype;
Oid funcid;
--- 63,69 ----
Node *
ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
bool agg_star, bool agg_distinct, bool is_column,
! int location, Node *variadic_expr)
{
Oid rettype;
Oid funcid;
***************
*** 76,81 ****
--- 76,83 ----
Node *retval;
bool retset;
FuncDetailCode fdresult;
+ int nvargs;
+ Oid va_oid;

/*
* Most of the rest of the parser just assumes that functions do not have
***************
*** 83,89 ****
* against array overruns, etc. Of course, this may not be a function,
* but the test doesn't hurt.
*/
! if (list_length(fargs) > FUNC_MAX_ARGS)
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
errmsg("cannot pass more than %d arguments to a function",
--- 85,91 ----
* against array overruns, etc. Of course, this may not be a function,
* but the test doesn't hurt.
*/
! if (list_length(fargs) + (variadic_expr != 0 ? 1:0) > FUNC_MAX_ARGS)
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
errmsg("cannot pass more than %d arguments to a function",
***************
*** 139,145 ****
--- 141,152 ----
first_arg,
location);
if (retval)
+ {
+ if (variadic_expr)
+ elog(ERROR, "column projection must not have variadic parameter");
+
return retval;
+ }

/*
* If ParseComplexProjection doesn't recognize it as a projection,
***************
*** 149,154 ****
--- 156,177 ----
}

/*
+ * Magic with variadic_expr
+ *
+ */
+ if (variadic_expr != NULL)
+ {
+ Oid va_el_oid = get_element_type(exprType(variadic_expr));
+
+ if (!OidIsValid(va_el_oid))
+ elog(ERROR, "variadic parameter must be an array");
+
+ /* For this moment I emulate one variadic argument. */
+ actual_arg_types[nargs++] = va_el_oid;
+ }
+
+
+ /*
* Okay, it's not a column projection, so it must really be a function.
* func_get_detail looks up the function in the catalogs, does
* disambiguation for polymorphic functions, handles inheritance, and
***************
*** 158,164 ****
*/
fdresult = func_get_detail(funcname, fargs, nargs, actual_arg_types,
&funcid, &rettype, &retset,
! &declared_arg_types);
if (fdresult == FUNCDETAIL_COERCION)
{
/*
--- 181,188 ----
*/
fdresult = func_get_detail(funcname, fargs, nargs, actual_arg_types,
&funcid, &rettype, &retset,
! &declared_arg_types,
! &nvargs, &va_oid);
if (fdresult == FUNCDETAIL_COERCION)
{
/*
***************
*** 229,234 ****
--- 253,337 ----
}

/*
+ * variadic_expr implicates variadic_function, check it!
+ */
+ if (variadic_expr != NULL)
+ {
+ if (nvargs == 0)
+ elog(ERROR, "using variadic call on non variadic function");
+
+ /*
+ * Executor hasn't enough information for unpacking
+ * variadic call arguments into variadic argumenents.
+ * So I have to prohibit this call.
+ */
+ if (va_oid == ANYOID)
+ elog(ERROR, "cannot unpack variadic call arguments to variadic any arguments");
+ }
+
+ /*
+ * Last nvargs arguments are transformed to array when function is
+ * non "any" variadic.
+ */
+ if (nvargs > 0 && va_oid != ANYOID)
+ {
+ A_ArrayExpr *n = makeNode(A_ArrayExpr);
+ Node *tn;
+
+ /*
+ * without variadic call argument transform
+ * arguments into variadic array parameter
+ */
+ if (nvargs < nargs)
+ {
+ if (variadic_expr == NULL)
+ {
+ int non_var_args = nargs - nvargs;
+ List *vargs;
+
+ vargs = list_copy_tail(fargs, non_var_args);
+ fargs = list_truncate(fargs, non_var_args);
+ n->elements = vargs;
+ tn = transformExpr(pstate, (Node *) n);
+ fargs = lappend(fargs, tn);
+ }
+ else
+ /* attach variadic call argument */
+ fargs = lappend(fargs, variadic_expr);
+ }
+ else
+ {
+ if (variadic_expr == NULL)
+ {
+ /* array from all arguments */
+ n->elements = fargs;
+ tn= transformExpr(pstate, (Node *) n);
+ fargs = list_make1(tn);
+ }
+ else
+ /* forward variadic call argument */
+ fargs = list_make1(variadic_expr);
+ }
+
+ /*
+ * Now we have to correct argument's metadata used in
+ * enforce_generic_type_consistency and make_fn_arguments. These
+ * functions needs actual values of nargs, actual_arg_types and
+ * real_arg_types.
+ */
+ nargs = nargs - nvargs + 1;
+ if (variadic_expr == NULL)
+ actual_arg_types[nargs - 1] = ((ArrayExpr *) tn)->array_typeid;
+ else
+ actual_arg_types[nargs - 1] = exprType(variadic_expr);
+
+ if (va_oid != ANYELEMENTOID)
+ declared_arg_types[nargs - 1] = get_array_type(va_oid);
+ else
+ declared_arg_types[nargs - 1] = ANYARRAYOID;
+ }
+
+ /*
* enforce consistency with polymorphic argument and return types,
* possibly adjusting return type or declared_arg_types (which will be
* used as the cast destination by make_fn_arguments)
***************
*** 697,709 ****
Oid *funcid, /* return value */
Oid *rettype, /* return value */
bool *retset, /* return value */
! Oid **true_typeids) /* return value */
{
FuncCandidateList raw_candidates;
FuncCandidateList best_candidate;

/* Get list of possible candidates from namespace search */
! raw_candidates = FuncnameGetCandidates(funcname, nargs);

/*
* Quickly check if there is an exact match to the input datatypes (there
--- 800,814 ----
Oid *funcid, /* return value */
Oid *rettype, /* return value */
bool *retset, /* return value */
! Oid **true_typeids, /* return value */
! int *nvargs, /* return value */
! Oid *va_oid) /* return value */
{
FuncCandidateList raw_candidates;
FuncCandidateList best_candidate;

/* Get list of possible candidates from namespace search */
! raw_candidates = FuncnameGetCandidates(funcname, nargs, true);

/*
* Quickly check if there is an exact match to the input datatypes (there
***************
*** 787,792 ****
--- 892,899 ----
*rettype = targetType;
*retset = false;
*true_typeids = argtypes;
+ *nvargs = 0;
+ *va_oid = InvalidOid;
return FUNCDETAIL_COERCION;
}
}
***************
*** 836,841 ****
--- 943,950 ----

*funcid = best_candidate->oid;
*true_typeids = best_candidate->args;
+ *nvargs = best_candidate->nvargs;
+ *va_oid = best_candidate->variadic_oid;

ftup = SearchSysCache(PROCOID,
ObjectIdGetDatum(best_candidate->oid),
***************
*** 1189,1195 ****
{
FuncCandidateList clist;

! clist = FuncnameGetCandidates(funcname, nargs);

while (clist)
{
--- 1298,1304 ----
{
FuncCandidateList clist;

! clist = FuncnameGetCandidates(funcname, nargs, false);

while (clist)
{
*** ./src/backend/utils/adt/regproc.c.orig 2008-07-14 09:12:47.000000000 +0200
--- ./src/backend/utils/adt/regproc.c 2008-07-14 09:14:37.000000000 +0200
***************
*** 131,137 ****
* pg_proc entries in the current search path.
*/
names = stringToQualifiedNameList(pro_name_or_oid);
! clist = FuncnameGetCandidates(names, -1);

if (clist == NULL)
ereport(ERROR,
--- 131,137 ----
* pg_proc entries in the current search path.
*/
names = stringToQualifiedNameList(pro_name_or_oid);
! clist = FuncnameGetCandidates(names, -1, false);

if (clist == NULL)
ereport(ERROR,
***************
*** 189,195 ****
* Would this proc be found (uniquely!) by regprocin? If not,
* qualify it.
*/
! clist = FuncnameGetCandidates(list_make1(makeString(proname)), -1);
if (clist != NULL && clist->next == NULL &&
clist->oid == proid)
nspname = NULL;
--- 189,195 ----
* Would this proc be found (uniquely!) by regprocin? If not,
* qualify it.
*/
! clist = FuncnameGetCandidates(list_make1(makeString(proname)), -1, false);
if (clist != NULL && clist->next == NULL &&
clist->oid == proid)
nspname = NULL;
***************
*** 276,282 ****
*/
parseNameAndArgTypes(pro_name_or_oid, false, &names, &nargs, argtypes);

! clist = FuncnameGetCandidates(names, nargs);

for (; clist; clist = clist->next)
{
--- 276,282 ----
*/
parseNameAndArgTypes(pro_name_or_oid, false, &names, &nargs, argtypes);

! clist = FuncnameGetCandidates(names, nargs, false);

for (; clist; clist = clist->next)
{
*** ./src/backend/utils/adt/ruleutils.c.orig 2008-07-14 09:12:55.000000000 +0200
--- ./src/backend/utils/adt/ruleutils.c 2008-07-14 09:14:37.000000000 +0200
***************
*** 5344,5349 ****
--- 5344,5351 ----
Oid p_rettype;
bool p_retset;
Oid *p_true_typeids;
+ int nvargs;
+ Oid va_oid;

proctup = SearchSysCache(PROCOID,
ObjectIdGetDatum(funcid),
***************
*** 5362,5368 ****
p_result = func_get_detail(list_make1(makeString(proname)),
NIL, nargs, argtypes,
&p_funcid, &p_rettype,
! &p_retset, &p_true_typeids);
if ((p_result == FUNCDETAIL_NORMAL || p_result == FUNCDETAIL_AGGREGATE) &&
p_funcid == funcid)
nspname = NULL;
--- 5364,5371 ----
p_result = func_get_detail(list_make1(makeString(proname)),
NIL, nargs, argtypes,
&p_funcid, &p_rettype,
! &p_retset, &p_true_typeids,
! &nvargs, &va_oid);
if ((p_result == FUNCDETAIL_NORMAL || p_result == FUNCDETAIL_AGGREGATE) &&
p_funcid == funcid)
nspname = NULL;
*** ./src/backend/utils/fmgr/funcapi.c.orig 2008-07-14 09:13:01.000000000 +0200
--- ./src/backend/utils/fmgr/funcapi.c 2008-07-14 09:14:37.000000000 +0200
***************
*** 844,850 ****
numoutargs = 0;
for (i = 0; i < numargs; i++)
{
! if (argmodes[i] == PROARGMODE_IN)
continue;
Assert(argmodes[i] == PROARGMODE_OUT ||
argmodes[i] == PROARGMODE_INOUT);
--- 844,850 ----
numoutargs = 0;
for (i = 0; i < numargs; i++)
{
! if (argmodes[i] == PROARGMODE_IN || argmodes[i] == PROARGMODE_VARIADIC)
continue;
Assert(argmodes[i] == PROARGMODE_OUT ||
argmodes[i] == PROARGMODE_INOUT);
***************
*** 994,1000 ****
{
char *pname;

! if (argmodes[i] == PROARGMODE_IN)
continue;
Assert(argmodes[i] == PROARGMODE_OUT ||
argmodes[i] == PROARGMODE_INOUT);
--- 994,1000 ----
{
char *pname;

! if (argmodes[i] == PROARGMODE_IN || argmodes[i] == PROARGMODE_VARIADIC)
continue;
Assert(argmodes[i] == PROARGMODE_OUT ||
argmodes[i] == PROARGMODE_INOUT);
*** ./src/bin/pg_dump/pg_dump.c.orig 2008-07-14 09:13:08.000000000 +0200
--- ./src/bin/pg_dump/pg_dump.c 2008-07-14 09:14:37.000000000 +0200
***************
*** 6435,6449 ****
{
switch (argmodes[j][0])
{
! case 'i':
argmode = "";
break;
! case 'o':
argmode = "OUT ";
break;
! case 'b':
argmode = "INOUT ";
break;
default:
write_msg(NULL, "WARNING: bogus value in proargmodes array\n");
argmode = "";
--- 6435,6452 ----
{
switch (argmodes[j][0])
{
! case PROARGMODE_IN:
argmode = "";
break;
! case PROARGMODE_OUT:
argmode = "OUT ";
break;
! case PROARGMODE_INOUT:
argmode = "INOUT ";
break;
+ case PROARGMODE_VARIADIC:
+ argmode = "VARIADIC ";
+ break;
default:
write_msg(NULL, "WARNING: bogus value in proargmodes array\n");
argmode = "";
*** ./src/bin/psql/describe.c.orig 2008-07-12 12:44:56.000000000 +0200
--- ./src/bin/psql/describe.c 2008-07-14 09:14:37.000000000 +0200
***************
*** 205,210 ****
--- 205,211 ----
" WHEN p.proargmodes[s.i] = 'i' THEN ''\n"
" WHEN p.proargmodes[s.i] = 'o' THEN 'OUT '\n"
" WHEN p.proargmodes[s.i] = 'b' THEN 'INOUT '\n"
+ " WHEN p.proargmodes[s.i] = 'v' THEN 'VARIADIC '\n"
" END ||\n"
" CASE\n"
" WHEN COALESCE(p.proargnames[s.i], '') = '' THEN ''\n"
*** ./src/include/catalog/namespace.h.orig 2008-07-14 09:13:21.000000000 +0200
--- ./src/include/catalog/namespace.h 2008-07-14 09:14:37.000000000 +0200
***************
*** 29,34 ****
--- 29,36 ----
int pathpos; /* for internal use of namespace lookup */
Oid oid; /* the function or operator's OID */
int nargs; /* number of arg types returned */
+ int nvargs; /* number of variadic arguments */
+ Oid variadic_oid; /* Oid of variadic argument */
Oid args[1]; /* arg types --- VARIABLE LENGTH ARRAY */
} *FuncCandidateList; /* VARIABLE LENGTH STRUCT */

***************
*** 51,57 ****
extern Oid TypenameGetTypid(const char *typname);
extern bool TypeIsVisible(Oid typid);

! extern FuncCandidateList FuncnameGetCandidates(List *names, int nargs);
extern bool FunctionIsVisible(Oid funcid);

extern Oid OpernameGetOprid(List *names, Oid oprleft, Oid oprright);
--- 53,60 ----
extern Oid TypenameGetTypid(const char *typname);
extern bool TypeIsVisible(Oid typid);

! extern FuncCandidateList FuncnameGetCandidates(List *names, int nargs,
! bool transform_variadic);
extern bool FunctionIsVisible(Oid funcid);

extern Oid OpernameGetOprid(List *names, Oid oprleft, Oid oprright);
*** ./src/include/catalog/pg_proc.h.orig 2008-07-14 02:51:45.000000000 +0200
--- ./src/include/catalog/pg_proc.h 2008-07-14 09:14:37.000000000 +0200
***************
*** 4472,4476 ****
--- 4472,4477 ----
#define PROARGMODE_IN 'i'
#define PROARGMODE_OUT 'o'
#define PROARGMODE_INOUT 'b'
+ #define PROARGMODE_VARIADIC 'v'

#endif /* PG_PROC_H */
*** ./src/include/nodes/parsenodes.h.orig 2008-07-14 09:13:36.000000000 +0200
--- ./src/include/nodes/parsenodes.h 2008-07-14 21:57:18.000000000 +0200
***************
*** 262,267 ****
--- 262,268 ----
bool agg_star; /* argument was really '*' */
bool agg_distinct; /* arguments were labeled DISTINCT */
int location; /* token location, or -1 if unknown */
+ Node *variadic_expr; /* when last parameter is array of variadic arguments */
} FuncCall;

/*
***************
*** 1568,1574 ****
/* the assigned enum values appear in pg_proc, don't change 'em! */
FUNC_PARAM_IN = 'i', /* input only */
FUNC_PARAM_OUT = 'o', /* output only */
! FUNC_PARAM_INOUT = 'b' /* both */
} FunctionParameterMode;

typedef struct FunctionParameter
--- 1569,1576 ----
/* the assigned enum values appear in pg_proc, don't change 'em! */
FUNC_PARAM_IN = 'i', /* input only */
FUNC_PARAM_OUT = 'o', /* output only */
! FUNC_PARAM_INOUT = 'b', /* both */
! FUNC_PARAM_VARIADIC = 'v' /* variadic */
} FunctionParameterMode;

typedef struct FunctionParameter
*** ./src/include/parser/parse_func.h.orig 2008-07-14 09:13:43.000000000 +0200
--- ./src/include/parser/parse_func.h 2008-07-14 22:25:12.000000000 +0200
***************
*** 44,55 ****
extern Node *ParseFuncOrColumn(ParseState *pstate,
List *funcname, List *fargs,
bool agg_star, bool agg_distinct, bool is_column,
! int location);

extern FuncDetailCode func_get_detail(List *funcname, List *fargs,
int nargs, Oid *argtypes,
Oid *funcid, Oid *rettype,
! bool *retset, Oid **true_typeids);

extern int func_match_argtypes(int nargs,
Oid *input_typeids,
--- 44,56 ----
extern Node *ParseFuncOrColumn(ParseState *pstate,
List *funcname, List *fargs,
bool agg_star, bool agg_distinct, bool is_column,
! int location, Node *variadic_expr);

extern FuncDetailCode func_get_detail(List *funcname, List *fargs,
int nargs, Oid *argtypes,
Oid *funcid, Oid *rettype,
! bool *retset, Oid **true_typeids,
! int *nvargs, Oid *va_oid);

extern int func_match_argtypes(int nargs,
Oid *input_typeids,
*** ./src/interfaces/ecpg/preproc/preproc.y.orig 2008-06-26 10:04:05.000000000 +0200
--- ./src/interfaces/ecpg/preproc/preproc.y 2008-07-14 09:14:37.000000000 +0200
***************
*** 489,495 ****
UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN UNLISTEN UNTIL
UPDATE USER USING

! VACUUM VALID VALIDATOR VALUE_P VALUES VARCHAR VARYING
VERBOSE VERSION_P VIEW VOLATILE
WHEN WHERE WHITESPACE_P WITH WITHOUT WORK WRITE

--- 489,495 ----
UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN UNLISTEN UNTIL
UPDATE USER USING

! VACUUM VALID VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
VERBOSE VERSION_P VIEW VOLATILE
WHEN WHERE WHITESPACE_P WITH WITHOUT WORK WRITE

***************
*** 6667,6672 ****
--- 6667,6673 ----
| VALIDATOR { $$ = make_str("validator"); }
| VALUE_P { $$ = make_str("value"); }
| VARYING { $$ = make_str("varying"); }
+ | VARIADIC { $$ = make_str("variadic"); }
| VERSION_P { $$ = make_str("version"); }
| VIEW { $$ = make_str("view"); }
| VOLATILE { $$ = make_str("volatile"); }
*** ./src/pl/plpgsql/src/pl_comp.c.orig 2008-07-14 09:13:56.000000000 +0200
--- ./src/pl/plpgsql/src/pl_comp.c 2008-07-14 09:14:37.000000000 +0200
***************
*** 426,432 ****
{
argitemtype = PLPGSQL_NSTYPE_VAR;
/* input argument vars are forced to be CONSTANT */
! if (argmode == PROARGMODE_IN)
((PLpgSQL_var *) argvariable)->isconst = true;
}
else
--- 426,432 ----
{
argitemtype = PLPGSQL_NSTYPE_VAR;
/* input argument vars are forced to be CONSTANT */
! if (argmode == PROARGMODE_IN || argmode == PROARGMODE_VARIADIC)
((PLpgSQL_var *) argvariable)->isconst = true;
}
else
***************
*** 436,442 ****
}

/* Remember arguments in appropriate arrays */
! if (argmode == PROARGMODE_IN || argmode == PROARGMODE_INOUT)
in_arg_varnos[num_in_args++] = argvariable->dno;
if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_INOUT)
out_arg_variables[num_out_args++] = argvariable;
--- 436,443 ----
}

/* Remember arguments in appropriate arrays */
! if (argmode == PROARGMODE_IN || argmode == PROARGMODE_INOUT
! || argmode == PROARGMODE_VARIADIC)
in_arg_varnos[num_in_args++] = argvariable->dno;
if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_INOUT)
out_arg_variables[num_out_args++] = argvariable;
*** ./src/test/regress/expected/plpgsql.out.orig 2008-07-14 09:14:03.000000000 +0200
--- ./src/test/regress/expected/plpgsql.out 2008-07-14 15:06:36.000000000 +0200
***************
*** 3544,3546 ****
--- 3544,3621 ----

drop function catch();
drop function case_test(bigint);
+ -- variadic fuction test
+ create or replace function vari(variadic int[])
+ returns void as $$
+ begin
+ for i in array_lower($1,1)..array_upper($1,1) loop
+ raise notice '%', $1[i];
+ end loop; end;
+ $$ language plpgsql;
+ select vari(1,2,3,4,5);
+ NOTICE: 1
+ NOTICE: 2
+ NOTICE: 3
+ NOTICE: 4
+ NOTICE: 5
+ vari
+ ------
+
+ (1 row)
+
+ select vari(3,4,5);
+ NOTICE: 3
+ NOTICE: 4
+ NOTICE: 5
+ vari
+ ------
+
+ (1 row)
+
+ drop function vari(int[]);
+ -- coerce test
+ create or replace function pleast(variadic numeric[])
+ returns numeric as $$
+ declare aux numeric = $1[array_lower($1,1)];
+ begin
+ for i in array_lower($1,1)+1..array_upper($1,1) loop
+ if $1[i] < aux then aux := $1[i]; end if;
+ end loop;
+ return aux;
+ end;
+ $$ language plpgsql immutable strict;
+ select pleast(10,1,2,3,-16);
+ pleast
+ --------
+ -16
+ (1 row)
+
+ select pleast(10.2,2.2,-1.1);
+ pleast
+ --------
+ -1.1
+ (1 row)
+
+ select pleast(10.2,10, -20);
+ pleast
+ --------
+ -20
+ (1 row)
+
+ select pleast(10,20, -1.0);
+ pleast
+ --------
+ -1.0
+ (1 row)
+
+ -- we can't call variadic function with not unique signature
+ create or replace function pleast(numeric)
+ returns numeric as $$
+ begin
+ return $1;
+ end;
+ $$ language plpgsql immutable strict;
+ select pleast(10);
+ ERROR: function pleast(numeric) is not unique
+ drop function pleast(numeric[]);
+ drop function pleast(numeric);
*** ./src/test/regress/expected/polymorphism.out.orig 2008-07-14 09:14:08.000000000 +0200
--- ./src/test/regress/expected/polymorphism.out 2008-07-15 00:03:30.000000000 +0200
***************
*** 613,615 ****
--- 613,678 ----
SFUNC = add_group,
STYPE = int8[]
);
+ --test for variadic polymorphic function
+ create function myleast(variadic anyarray)
+ returns anyelement as $$
+ select min($1[i])
+ from generate_subscripts($1,1) g(i)
+ $$ language sql immutable strict;
+ select myleast(10, 1, 20, 33);
+ myleast
+ ---------
+ 1
+ (1 row)
+
+ select myleast(1.1, 0.22, 0.55);
+ myleast
+ ---------
+ 0.22
+ (1 row)
+
+ -- test with variadic call parameter
+ select myleast(variadic array[1,2,3,4,-1]);
+ myleast
+ ---------
+ -1
+ (1 row)
+
+ select myleast(variadic array[1.1, -5.5]);
+ myleast
+ ---------
+ -5.5
+ (1 row)
+
+ --test with empty variadic call parameter
+ select myleast(variadic array[]::int[]);
+ myleast
+ ---------
+
+ (1 row)
+
+ -- visibility test
+ select pg_catalog.pg_function_is_visible('myleast(anyarray)'::regprocedure::int);
+ pg_function_is_visible
+ ------------------------
+ t
+ (1 row)
+
+ drop function myleast(anyarray);
+ create or replace function concat(varchar, variadic anyarray)
+ returns varchar as $$
+ select array_to_string($2, $1);
+ $$ language sql immutable strict;
+ select concat('%', 1, 2, 3, 4, 5);
+ concat
+ -----------
+ 1%2%3%4%5
+ (1 row)
+
+ select concat('|', 'a'::text, 'b', 'c');
+ concat
+ --------
+ a|b|c
+ (1 row)
+
+ drop function concat(varchar, anyarray);
*** ./src/test/regress/sql/plpgsql.sql.orig 2008-07-14 09:14:16.000000000 +0200
--- ./src/test/regress/sql/plpgsql.sql 2008-07-14 15:05:17.000000000 +0200
***************
*** 2878,2880 ****
--- 2878,2924 ----

drop function catch();
drop function case_test(bigint);
+
+ -- variadic fuction test
+ create or replace function vari(variadic int[])
+ returns void as $$
+ begin
+ for i in array_lower($1,1)..array_upper($1,1) loop
+ raise notice '%', $1[i];
+ end loop; end;
+ $$ language plpgsql;
+
+ select vari(1,2,3,4,5);
+ select vari(3,4,5);
+
+ drop function vari(int[]);
+
+ -- coerce test
+ create or replace function pleast(variadic numeric[])
+ returns numeric as $$
+ declare aux numeric = $1[array_lower($1,1)];
+ begin
+ for i in array_lower($1,1)+1..array_upper($1,1) loop
+ if $1[i] < aux then aux := $1[i]; end if;
+ end loop;
+ return aux;
+ end;
+ $$ language plpgsql immutable strict;
+
+ select pleast(10,1,2,3,-16);
+ select pleast(10.2,2.2,-1.1);
+ select pleast(10.2,10, -20);
+ select pleast(10,20, -1.0);
+
+ -- we can't call variadic function with not unique signature
+ create or replace function pleast(numeric)
+ returns numeric as $$
+ begin
+ return $1;
+ end;
+ $$ language plpgsql immutable strict;
+
+ select pleast(10);
+
+ drop function pleast(numeric[]);
+ drop function pleast(numeric);
*** ./src/test/regress/sql/polymorphism.sql.orig 2008-07-14 09:14:26.000000000 +0200
--- ./src/test/regress/sql/polymorphism.sql 2008-07-15 00:02:27.000000000 +0200
***************
*** 426,428 ****
--- 426,461 ----
SFUNC = add_group,
STYPE = int8[]
);
+
+ --test for variadic polymorphic function
+ create function myleast(variadic anyarray)
+ returns anyelement as $$
+ select min($1[i])
+ from generate_subscripts($1,1) g(i)
+ $$ language sql immutable strict;
+
+ select myleast(10, 1, 20, 33);
+ select myleast(1.1, 0.22, 0.55);
+
+ -- test with variadic call parameter
+ select myleast(variadic array[1,2,3,4,-1]);
+ select myleast(variadic array[1.1, -5.5]);
+
+ --test with empty variadic call parameter
+ select myleast(variadic array[]::int[]);
+
+ -- visibility test
+ select pg_catalog.pg_function_is_visible('myleast(anyarray)'::regprocedure::int);
+
+ drop function myleast(anyarray);
+
+ create or replace function concat(varchar, variadic anyarray)
+ returns varchar as $$
+ select array_to_string($2, $1);
+ $$ language sql immutable strict;
+
+ select concat('%', 1, 2, 3, 4, 5);
+ select concat('|', 'a'::text, 'b', 'c');
+
+ drop function concat(varchar, anyarray);
+
Hello

this version is WIP - I have to clean comments, and will do some
documentation. But I am sure, I am not able explain this feature in
english well. Please, can some body help me with documentation? So
now, plpgsql is more/less ruby :)

postgres=# select myleast(variadic array[1,2,3,4,-1]);
myleast
---------
-1
(1 row)

postgres=# select myleast(variadic array[1.1, -5.5]);
myleast
---------
-5.5
(1 row)

postgres=# --test with empty variadic call parameter
postgres=# select myleast(variadic array[]::int[]);
myleast
---------

(1 row)
postgres=# select myleast(1.1,-5.5);
myleast
---------
-5.5

regards
Pavel

2008/7/14 Pavel Stehule <pavel.stehule@gmail.com>:
> 2008/7/14 Tom Lane <tgl@sss.pgh.pa.us>:
>> "Pavel Stehule" <pavel.stehule@gmail.com> writes:
>>> 2008/7/14 Tom Lane <tgl@sss.pgh.pa.us>:
>>>> Are you intending to change this right now and resubmit, or is it
>>>> work for later?
>>
>>> I prefer separate it to other patch.
>>
>> It seems a fairly important part of the feature, especially given the
>> connection to the zero-argument issue.
>
> ok tomorrow I have some work, but I can do it to Friday.
>
> regards
> Pavel Stehule
>>
>> regards, tom lane
>>
>

No comments: