Commit 23610daf authored by Tom Lane's avatar Tom Lane

Fix up Perl-to-Postgres datatype conversions in pl/perl.

This patch restores the pre-9.1 behavior that pl/perl functions returning
VOID ignore the result value of their last Perl statement.  9.1.0
unintentionally threw an error if the last statement returned a reference,
as reported by Amit Khandekar.

Also, make sure it works to return a string value for a composite type,
so long as the string meets the type's input format.  We already allowed
the equivalent behavior for arrays, so it seems inconsistent to not allow
it for composites.

In addition, ensure we throw errors for attempts to return arrays or hashes
when the function's declared result type is not an array or composite type,
respectively.  Pre-9.1 versions rather uselessly returned strings like
ARRAY(0x221a9a0) or HASH(0x221aa90), while 9.1.0 threw an error for the
hash case and returned a garbage value for the array case.

Also, clean up assorted grotty coding in Perl array conversion, including
use of a session-lifespan memory context to accumulate the array value
(resulting in session-lifespan memory leak on error), failure to apply the
declared typmod if any, and failure to detect some cases of non-rectangular
multi-dimensional arrays.

Alex Hunsaker and Tom Lane
parent fb4340c5
...@@ -101,6 +101,16 @@ SELECT * FROM perl_row(); ...@@ -101,6 +101,16 @@ SELECT * FROM perl_row();
1 | hello | world | ({{1}}) 1 | hello | world | ({{1}})
(1 row) (1 row)
-- test returning a composite literal
CREATE OR REPLACE FUNCTION perl_row_lit() RETURNS testrowperl AS $$
return '(1,hello,world,"({{1}})")';
$$ LANGUAGE plperl;
SELECT perl_row_lit();
perl_row_lit
---------------------------
(1,hello,world,"({{1}})")
(1 row)
CREATE OR REPLACE FUNCTION perl_set() RETURNS SETOF testrowperl AS $$ CREATE OR REPLACE FUNCTION perl_set() RETURNS SETOF testrowperl AS $$
return undef; return undef;
$$ LANGUAGE plperl; $$ LANGUAGE plperl;
...@@ -336,7 +346,8 @@ CREATE OR REPLACE FUNCTION foo_bad() RETURNS footype AS $$ ...@@ -336,7 +346,8 @@ CREATE OR REPLACE FUNCTION foo_bad() RETURNS footype AS $$
return 42; return 42;
$$ LANGUAGE plperl; $$ LANGUAGE plperl;
SELECT * FROM foo_bad(); SELECT * FROM foo_bad();
ERROR: composite-returning PL/Perl function must return reference to hash ERROR: malformed record literal: "42"
DETAIL: Missing left parenthesis.
CONTEXT: PL/Perl function "foo_bad" CONTEXT: PL/Perl function "foo_bad"
CREATE OR REPLACE FUNCTION foo_bad() RETURNS footype AS $$ CREATE OR REPLACE FUNCTION foo_bad() RETURNS footype AS $$
return [ return [
...@@ -345,7 +356,7 @@ return [ ...@@ -345,7 +356,7 @@ return [
]; ];
$$ LANGUAGE plperl; $$ LANGUAGE plperl;
SELECT * FROM foo_bad(); SELECT * FROM foo_bad();
ERROR: composite-returning PL/Perl function must return reference to hash ERROR: cannot convert Perl array to non-array type footype
CONTEXT: PL/Perl function "foo_bad" CONTEXT: PL/Perl function "foo_bad"
CREATE OR REPLACE FUNCTION foo_set_bad() RETURNS SETOF footype AS $$ CREATE OR REPLACE FUNCTION foo_set_bad() RETURNS SETOF footype AS $$
return 42; return 42;
...@@ -639,3 +650,46 @@ CONTEXT: PL/Perl anonymous code block ...@@ -639,3 +650,46 @@ CONTEXT: PL/Perl anonymous code block
DO $do$ use warnings FATAL => qw(void) ; my @y; my $x = sort @y; 1; $do$ LANGUAGE plperl; DO $do$ use warnings FATAL => qw(void) ; my @y; my $x = sort @y; 1; $do$ LANGUAGE plperl;
ERROR: Useless use of sort in scalar context at line 1. ERROR: Useless use of sort in scalar context at line 1.
CONTEXT: PL/Perl anonymous code block CONTEXT: PL/Perl anonymous code block
-- make sure functions marked as VOID without an explicit return work
CREATE OR REPLACE FUNCTION myfuncs() RETURNS void AS $$
$_SHARED{myquote} = sub {
my $arg = shift;
$arg =~ s/(['\\])/\\$1/g;
return "'$arg'";
};
$$ LANGUAGE plperl;
SELECT myfuncs();
myfuncs
---------
(1 row)
-- make sure we can't return an array as a scalar
CREATE OR REPLACE FUNCTION text_arrayref() RETURNS text AS $$
return ['array'];
$$ LANGUAGE plperl;
SELECT text_arrayref();
ERROR: cannot convert Perl array to non-array type text
CONTEXT: PL/Perl function "text_arrayref"
--- make sure we can't return a hash as a scalar
CREATE OR REPLACE FUNCTION text_hashref() RETURNS text AS $$
return {'hash'=>1};
$$ LANGUAGE plperl;
SELECT text_hashref();
ERROR: cannot convert Perl hash to non-composite type text
CONTEXT: PL/Perl function "text_hashref"
---- make sure we can't return a blessed object as a scalar
CREATE OR REPLACE FUNCTION text_obj() RETURNS text AS $$
return bless({}, 'Fake::Object');
$$ LANGUAGE plperl;
SELECT text_obj();
ERROR: cannot convert Perl hash to non-composite type text
CONTEXT: PL/Perl function "text_obj"
----- make sure we can't return a scalar ref
CREATE OR REPLACE FUNCTION text_scalarref() RETURNS text AS $$
my $str = 'str';
return \$str;
$$ LANGUAGE plperl;
SELECT text_scalarref();
ERROR: PL/Perl function must return reference to hash or array
CONTEXT: PL/Perl function "text_scalarref"
...@@ -204,6 +204,16 @@ select plperl_arrays_inout('{{1}, {2}, {3}}'); ...@@ -204,6 +204,16 @@ select plperl_arrays_inout('{{1}, {2}, {3}}');
{{1},{2},{3}} {{1},{2},{3}}
(1 row) (1 row)
-- check that we can return an array literal
CREATE OR REPLACE FUNCTION plperl_arrays_inout_l(INTEGER[]) returns INTEGER[] AS $$
return shift.''; # stringify it
$$ LANGUAGE plperl;
select plperl_arrays_inout_l('{{1}, {2}, {3}}');
plperl_arrays_inout_l
-----------------------
{{1},{2},{3}}
(1 row)
-- make sure setof works -- make sure setof works
create or replace function perl_setof_array(integer[]) returns setof integer[] language plperl as $$ create or replace function perl_setof_array(integer[]) returns setof integer[] language plperl as $$
my $arr = shift; my $arr = shift;
......
...@@ -155,7 +155,6 @@ typedef struct plperl_call_data ...@@ -155,7 +155,6 @@ typedef struct plperl_call_data
FunctionCallInfo fcinfo; FunctionCallInfo fcinfo;
Tuplestorestate *tuple_store; Tuplestorestate *tuple_store;
TupleDesc ret_tdesc; TupleDesc ret_tdesc;
AttInMetadata *attinmeta;
MemoryContext tmp_cxt; MemoryContext tmp_cxt;
} plperl_call_data; } plperl_call_data;
...@@ -244,12 +243,16 @@ static SV *plperl_ref_from_pg_array(Datum arg, Oid typid); ...@@ -244,12 +243,16 @@ static SV *plperl_ref_from_pg_array(Datum arg, Oid typid);
static SV *split_array(plperl_array_info *info, int first, int last, int nest); static SV *split_array(plperl_array_info *info, int first, int last, int nest);
static SV *make_array_ref(plperl_array_info *info, int first, int last); static SV *make_array_ref(plperl_array_info *info, int first, int last);
static SV *get_perl_array_ref(SV *sv); static SV *get_perl_array_ref(SV *sv);
static Datum plperl_sv_to_datum(SV *sv, FmgrInfo *func, Oid typid, static Datum plperl_sv_to_datum(SV *sv, Oid typid, int32 typmod,
Oid typioparam, int32 typmod, bool *isnull); FunctionCallInfo fcinfo,
static void _sv_to_datum_finfo(FmgrInfo *fcinfo, Oid typid, Oid *typioparam); FmgrInfo *finfo, Oid typioparam,
static Datum plperl_array_to_datum(SV *src, Oid typid); bool *isnull);
static ArrayBuildState *_array_to_datum(AV *av, int *ndims, int *dims, static void _sv_to_datum_finfo(Oid typid, FmgrInfo *finfo, Oid *typioparam);
int cur_depth, ArrayBuildState *astate, Oid typid, Oid atypid); static Datum plperl_array_to_datum(SV *src, Oid typid, int32 typmod);
static ArrayBuildState *array_to_datum_internal(AV *av, ArrayBuildState *astate,
int *ndims, int *dims, int cur_depth,
Oid arraytypid, Oid elemtypid, int32 typmod,
FmgrInfo *finfo, Oid typioparam);
static Datum plperl_hash_to_datum(SV *src, TupleDesc td); static Datum plperl_hash_to_datum(SV *src, TupleDesc td);
static void plperl_init_shared_libs(pTHX); static void plperl_init_shared_libs(pTHX);
...@@ -988,9 +991,8 @@ strip_trailing_ws(const char *msg) ...@@ -988,9 +991,8 @@ strip_trailing_ws(const char *msg)
/* Build a tuple from a hash. */ /* Build a tuple from a hash. */
static HeapTuple static HeapTuple
plperl_build_tuple_result(HV *perlhash, AttInMetadata *attinmeta) plperl_build_tuple_result(HV *perlhash, TupleDesc td)
{ {
TupleDesc td = attinmeta->tupdesc;
Datum *values; Datum *values;
bool *nulls; bool *nulls;
HE *he; HE *he;
...@@ -1006,7 +1008,6 @@ plperl_build_tuple_result(HV *perlhash, AttInMetadata *attinmeta) ...@@ -1006,7 +1008,6 @@ plperl_build_tuple_result(HV *perlhash, AttInMetadata *attinmeta)
SV *val = HeVAL(he); SV *val = HeVAL(he);
char *key = hek2cstr(he); char *key = hek2cstr(he);
int attn = SPI_fnumber(td, key); int attn = SPI_fnumber(td, key);
bool isnull;
if (attn <= 0 || td->attrs[attn - 1]->attisdropped) if (attn <= 0 || td->attrs[attn - 1]->attisdropped)
ereport(ERROR, ereport(ERROR,
...@@ -1015,12 +1016,12 @@ plperl_build_tuple_result(HV *perlhash, AttInMetadata *attinmeta) ...@@ -1015,12 +1016,12 @@ plperl_build_tuple_result(HV *perlhash, AttInMetadata *attinmeta)
key))); key)));
values[attn - 1] = plperl_sv_to_datum(val, values[attn - 1] = plperl_sv_to_datum(val,
NULL,
td->attrs[attn - 1]->atttypid, td->attrs[attn - 1]->atttypid,
InvalidOid,
td->attrs[attn - 1]->atttypmod, td->attrs[attn - 1]->atttypmod,
&isnull); NULL,
nulls[attn - 1] = isnull; NULL,
InvalidOid,
&nulls[attn - 1]);
pfree(key); pfree(key);
} }
...@@ -1036,8 +1037,7 @@ plperl_build_tuple_result(HV *perlhash, AttInMetadata *attinmeta) ...@@ -1036,8 +1037,7 @@ plperl_build_tuple_result(HV *perlhash, AttInMetadata *attinmeta)
static Datum static Datum
plperl_hash_to_datum(SV *src, TupleDesc td) plperl_hash_to_datum(SV *src, TupleDesc td)
{ {
AttInMetadata *attinmeta = TupleDescGetAttInMetadata(td); HeapTuple tup = plperl_build_tuple_result((HV *) SvRV(src), td);
HeapTuple tup = plperl_build_tuple_result((HV *) SvRV(src), attinmeta);
return HeapTupleGetDatum(tup); return HeapTupleGetDatum(tup);
} }
...@@ -1069,13 +1069,15 @@ get_perl_array_ref(SV *sv) ...@@ -1069,13 +1069,15 @@ get_perl_array_ref(SV *sv)
} }
/* /*
* helper function for plperl_array_to_datum, does the main recursing * helper function for plperl_array_to_datum, recurses for multi-D arrays
*/ */
static ArrayBuildState * static ArrayBuildState *
_array_to_datum(AV *av, int *ndims, int *dims, int cur_depth, array_to_datum_internal(AV *av, ArrayBuildState *astate,
ArrayBuildState *astate, Oid typid, Oid atypid) int *ndims, int *dims, int cur_depth,
Oid arraytypid, Oid elemtypid, int32 typmod,
FmgrInfo *finfo, Oid typioparam)
{ {
int i = 0; int i;
int len = av_len(av) + 1; int len = av_len(av) + 1;
for (i = 0; i < len; i++) for (i = 0; i < len; i++)
...@@ -1091,36 +1093,51 @@ _array_to_datum(AV *av, int *ndims, int *dims, int cur_depth, ...@@ -1091,36 +1093,51 @@ _array_to_datum(AV *av, int *ndims, int *dims, int cur_depth,
{ {
AV *nav = (AV *) SvRV(sav); AV *nav = (AV *) SvRV(sav);
/* dimensionality checks */
if (cur_depth + 1 > MAXDIM) if (cur_depth + 1 > MAXDIM)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)", errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
cur_depth + 1, MAXDIM))); cur_depth + 1, MAXDIM)));
/* size based off the first element */ /* set size when at first element in this level, else compare */
if (i == 0 && *ndims == cur_depth) if (i == 0 && *ndims == cur_depth)
{ {
dims[*ndims] = av_len(nav) + 1; dims[*ndims] = av_len(nav) + 1;
(*ndims)++; (*ndims)++;
} }
else else if (av_len(nav) + 1 != dims[cur_depth])
{ ereport(ERROR,
if (av_len(nav) + 1 != dims[cur_depth]) (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
ereport(ERROR, errmsg("multidimensional arrays must have array expressions with matching dimensions")));
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("multidimensional arrays must have array expressions with matching dimensions"))); /* recurse to fetch elements of this sub-array */
} astate = array_to_datum_internal(nav, astate,
ndims, dims, cur_depth + 1,
astate = _array_to_datum(nav, ndims, dims, cur_depth + 1, astate, arraytypid, elemtypid, typmod,
typid, atypid); finfo, typioparam);
} }
else else
{ {
Datum dat;
bool isnull; bool isnull;
Datum dat = plperl_sv_to_datum(svp ? *svp : NULL, NULL,
atypid, 0, -1, &isnull);
astate = accumArrayResult(astate, dat, isnull, atypid, NULL); /* scalar after some sub-arrays at same level? */
if (*ndims != cur_depth)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("multidimensional arrays must have array expressions with matching dimensions")));
dat = plperl_sv_to_datum(svp ? *svp : NULL,
elemtypid,
typmod,
NULL,
finfo,
typioparam,
&isnull);
astate = accumArrayResult(astate, dat, isnull,
elemtypid, CurrentMemoryContext);
} }
} }
...@@ -1131,89 +1148,141 @@ _array_to_datum(AV *av, int *ndims, int *dims, int cur_depth, ...@@ -1131,89 +1148,141 @@ _array_to_datum(AV *av, int *ndims, int *dims, int cur_depth,
* convert perl array ref to a datum * convert perl array ref to a datum
*/ */
static Datum static Datum
plperl_array_to_datum(SV *src, Oid typid) plperl_array_to_datum(SV *src, Oid typid, int32 typmod)
{ {
ArrayBuildState *astate = NULL; ArrayBuildState *astate;
Oid atypid; Oid elemtypid;
FmgrInfo finfo;
Oid typioparam;
int dims[MAXDIM]; int dims[MAXDIM];
int lbs[MAXDIM]; int lbs[MAXDIM];
int ndims = 1; int ndims = 1;
int i; int i;
atypid = get_element_type(typid); elemtypid = get_element_type(typid);
if (!atypid) if (!elemtypid)
atypid = typid; ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("cannot convert Perl array to non-array type %s",
format_type_be(typid))));
_sv_to_datum_finfo(elemtypid, &finfo, &typioparam);
memset(dims, 0, sizeof(dims)); memset(dims, 0, sizeof(dims));
dims[0] = av_len((AV *) SvRV(src)) + 1; dims[0] = av_len((AV *) SvRV(src)) + 1;
astate = _array_to_datum((AV *) SvRV(src), &ndims, dims, 1, astate, typid, astate = array_to_datum_internal((AV *) SvRV(src), NULL,
atypid); &ndims, dims, 1,
typid, elemtypid, typmod,
&finfo, typioparam);
if (!astate) if (!astate)
return PointerGetDatum(construct_empty_array(atypid)); return PointerGetDatum(construct_empty_array(elemtypid));
for (i = 0; i < ndims; i++) for (i = 0; i < ndims; i++)
lbs[i] = 1; lbs[i] = 1;
return makeMdArrayResult(astate, ndims, dims, lbs, CurrentMemoryContext, true); return makeMdArrayResult(astate, ndims, dims, lbs,
CurrentMemoryContext, true);
} }
/* Get the information needed to convert data to the specified PG type */
static void static void
_sv_to_datum_finfo(FmgrInfo *fcinfo, Oid typid, Oid *typioparam) _sv_to_datum_finfo(Oid typid, FmgrInfo *finfo, Oid *typioparam)
{ {
Oid typinput; Oid typinput;
/* XXX would be better to cache these lookups */ /* XXX would be better to cache these lookups */
getTypeInputInfo(typid, getTypeInputInfo(typid,
&typinput, typioparam); &typinput, typioparam);
fmgr_info(typinput, fcinfo); fmgr_info(typinput, finfo);
} }
/* /*
* convert a sv to datum * convert Perl SV to PG datum of type typid, typmod typmod
* fcinfo and typioparam are optional and will be looked-up if needed *
* Pass the PL/Perl function's fcinfo when attempting to convert to the
* function's result type; otherwise pass NULL. This is used when we need to
* resolve the actual result type of a function returning RECORD.
*
* finfo and typioparam should be the results of _sv_to_datum_finfo for the
* given typid, or NULL/InvalidOid to let this function do the lookups.
*
* *isnull is an output parameter.
*/ */
static Datum static Datum
plperl_sv_to_datum(SV *sv, FmgrInfo *finfo, Oid typid, Oid typioparam, plperl_sv_to_datum(SV *sv, Oid typid, int32 typmod,
int32 typmod, bool *isnull) FunctionCallInfo fcinfo,
FmgrInfo *finfo, Oid typioparam,
bool *isnull)
{ {
FmgrInfo tmp; FmgrInfo tmp;
/* we might recurse */ /* we might recurse */
check_stack_depth(); check_stack_depth();
if (isnull) *isnull = false;
*isnull = false;
if (!sv || !SvOK(sv)) /*
* Return NULL if result is undef, or if we're in a function returning
* VOID. In the latter case, we should pay no attention to the last Perl
* statement's result, and this is a convenient means to ensure that.
*/
if (!sv || !SvOK(sv) || typid == VOIDOID)
{ {
/* look up type info if they did not pass it */
if (!finfo) if (!finfo)
{ {
_sv_to_datum_finfo(&tmp, typid, &typioparam); _sv_to_datum_finfo(typid, &tmp, &typioparam);
finfo = &tmp; finfo = &tmp;
} }
if (isnull) *isnull = true;
*isnull = true; /* must call typinput in case it wants to reject NULL */
return InputFunctionCall(finfo, NULL, typioparam, typmod); return InputFunctionCall(finfo, NULL, typioparam, typmod);
} }
else if (SvROK(sv)) else if (SvROK(sv))
{ {
/* handle references */
SV *sav = get_perl_array_ref(sv); SV *sav = get_perl_array_ref(sv);
if (sav) if (sav)
{ {
return plperl_array_to_datum(sav, typid); /* handle an arrayref */
return plperl_array_to_datum(sav, typid, typmod);
} }
else if (SvTYPE(SvRV(sv)) == SVt_PVHV) else if (SvTYPE(SvRV(sv)) == SVt_PVHV)
{ {
TupleDesc td = lookup_rowtype_tupdesc(typid, typmod); /* handle a hashref */
Datum ret = plperl_hash_to_datum(sv, td); Datum ret;
TupleDesc td;
if (!type_is_rowtype(typid))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("cannot convert Perl hash to non-composite type %s",
format_type_be(typid))));
td = lookup_rowtype_tupdesc_noerror(typid, typmod, true);
if (td == NULL)
{
/* Try to look it up based on our result type */
if (fcinfo == NULL ||
get_call_result_type(fcinfo, NULL, &td) != TYPEFUNC_COMPOSITE)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("function returning record called in context "
"that cannot accept type record")));
}
ret = plperl_hash_to_datum(sv, td);
/* Release on the result of get_call_result_type is harmless */
ReleaseTupleDesc(td); ReleaseTupleDesc(td);
return ret; return ret;
} }
/* Reference, but not reference to hash or array ... */
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH), (errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("PL/Perl function must return reference to hash or array"))); errmsg("PL/Perl function must return reference to hash or array")));
...@@ -1221,12 +1290,14 @@ plperl_sv_to_datum(SV *sv, FmgrInfo *finfo, Oid typid, Oid typioparam, ...@@ -1221,12 +1290,14 @@ plperl_sv_to_datum(SV *sv, FmgrInfo *finfo, Oid typid, Oid typioparam,
} }
else else
{ {
/* handle a string/number */
Datum ret; Datum ret;
char *str = sv2cstr(sv); char *str = sv2cstr(sv);
/* did not pass in any typeinfo? look it up */
if (!finfo) if (!finfo)
{ {
_sv_to_datum_finfo(&tmp, typid, &typioparam); _sv_to_datum_finfo(typid, &tmp, &typioparam);
finfo = &tmp; finfo = &tmp;
} }
...@@ -1251,7 +1322,10 @@ plperl_sv_to_literal(SV *sv, char *fqtypename) ...@@ -1251,7 +1322,10 @@ plperl_sv_to_literal(SV *sv, char *fqtypename)
if (!OidIsValid(typid)) if (!OidIsValid(typid))
elog(ERROR, "lookup failed for type %s", fqtypename); elog(ERROR, "lookup failed for type %s", fqtypename);
datum = plperl_sv_to_datum(sv, NULL, typid, 0, -1, &isnull); datum = plperl_sv_to_datum(sv,
typid, -1,
NULL, NULL, InvalidOid,
&isnull);
if (isnull) if (isnull)
return NULL; return NULL;
...@@ -1542,10 +1616,11 @@ plperl_modify_tuple(HV *hvTD, TriggerData *tdata, HeapTuple otup) ...@@ -1542,10 +1616,11 @@ plperl_modify_tuple(HV *hvTD, TriggerData *tdata, HeapTuple otup)
key))); key)));
modvalues[slotsused] = plperl_sv_to_datum(val, modvalues[slotsused] = plperl_sv_to_datum(val,
NULL,
tupdesc->attrs[attn - 1]->atttypid, tupdesc->attrs[attn - 1]->atttypid,
InvalidOid,
tupdesc->attrs[attn - 1]->atttypmod, tupdesc->attrs[attn - 1]->atttypmod,
NULL,
NULL,
InvalidOid,
&isnull); &isnull);
modnulls[slotsused] = isnull ? 'n' : ' '; modnulls[slotsused] = isnull ? 'n' : ' ';
...@@ -2043,10 +2118,9 @@ plperl_func_handler(PG_FUNCTION_ARGS) ...@@ -2043,10 +2118,9 @@ plperl_func_handler(PG_FUNCTION_ARGS)
Datum retval = 0; Datum retval = 0;
ReturnSetInfo *rsi; ReturnSetInfo *rsi;
ErrorContextCallback pl_error_context; ErrorContextCallback pl_error_context;
bool has_retval = false;
/* /*
* Create the call_data beforing connecting to SPI, so that it is not * Create the call_data before connecting to SPI, so that it is not
* allocated in the SPI memory context * allocated in the SPI memory context
*/ */
current_call_data = (plperl_call_data *) palloc0(sizeof(plperl_call_data)); current_call_data = (plperl_call_data *) palloc0(sizeof(plperl_call_data));
...@@ -2129,51 +2203,19 @@ plperl_func_handler(PG_FUNCTION_ARGS) ...@@ -2129,51 +2203,19 @@ plperl_func_handler(PG_FUNCTION_ARGS)
rsi->setDesc = current_call_data->ret_tdesc; rsi->setDesc = current_call_data->ret_tdesc;
} }
retval = (Datum) 0; retval = (Datum) 0;
has_retval = true;
} }
else if (!SvOK(perlret)) else
{
/* Return NULL if Perl code returned undef */
if (rsi && IsA(rsi, ReturnSetInfo))
rsi->isDone = ExprEndResult;
}
else if (prodesc->fn_retistuple)
{
/* Return a perl hash converted to a Datum */
TupleDesc td;
if (!SvOK(perlret) || !SvROK(perlret) ||
SvTYPE(SvRV(perlret)) != SVt_PVHV)
{
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("composite-returning PL/Perl function "
"must return reference to hash")));
}
/* XXX should cache the attinmeta data instead of recomputing */
if (get_call_result_type(fcinfo, NULL, &td) != TYPEFUNC_COMPOSITE)
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("function returning record called in context "
"that cannot accept type record")));
}
retval = plperl_hash_to_datum(perlret, td);
has_retval = true;
}
if (!has_retval)
{ {
bool isnull;
retval = plperl_sv_to_datum(perlret, retval = plperl_sv_to_datum(perlret,
&prodesc->result_in_func,
prodesc->result_oid, prodesc->result_oid,
prodesc->result_typioparam, -1, &isnull); -1,
fcinfo->isnull = isnull; fcinfo,
has_retval = true; &prodesc->result_in_func,
prodesc->result_typioparam,
&fcinfo->isnull);
if (fcinfo->isnull && rsi && IsA(rsi, ReturnSetInfo))
rsi->isDone = ExprEndResult;
} }
/* Restore the previous error callback */ /* Restore the previous error callback */
...@@ -2196,7 +2238,7 @@ plperl_trigger_handler(PG_FUNCTION_ARGS) ...@@ -2196,7 +2238,7 @@ plperl_trigger_handler(PG_FUNCTION_ARGS)
ErrorContextCallback pl_error_context; ErrorContextCallback pl_error_context;
/* /*
* Create the call_data beforing connecting to SPI, so that it is not * Create the call_data before connecting to SPI, so that it is not
* allocated in the SPI memory context * allocated in the SPI memory context
*/ */
current_call_data = (plperl_call_data *) palloc0(sizeof(plperl_call_data)); current_call_data = (plperl_call_data *) palloc0(sizeof(plperl_call_data));
...@@ -2842,19 +2884,11 @@ plperl_return_next(SV *sv) ...@@ -2842,19 +2884,11 @@ plperl_return_next(SV *sv)
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot use return_next in a non-SETOF function"))); errmsg("cannot use return_next in a non-SETOF function")));
if (prodesc->fn_retistuple &&
!(SvOK(sv) && SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVHV))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("SETOF-composite-returning PL/Perl function "
"must call return_next with reference to hash")));
if (!current_call_data->ret_tdesc) if (!current_call_data->ret_tdesc)
{ {
TupleDesc tupdesc; TupleDesc tupdesc;
Assert(!current_call_data->tuple_store); Assert(!current_call_data->tuple_store);
Assert(!current_call_data->attinmeta);
/* /*
* This is the first call to return_next in the current PL/Perl * This is the first call to return_next in the current PL/Perl
...@@ -2875,11 +2909,6 @@ plperl_return_next(SV *sv) ...@@ -2875,11 +2909,6 @@ plperl_return_next(SV *sv)
current_call_data->tuple_store = current_call_data->tuple_store =
tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random, tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
false, work_mem); false, work_mem);
if (prodesc->fn_retistuple)
{
current_call_data->attinmeta =
TupleDescGetAttInMetadata(current_call_data->ret_tdesc);
}
MemoryContextSwitchTo(old_cxt); MemoryContextSwitchTo(old_cxt);
} }
...@@ -2893,7 +2922,7 @@ plperl_return_next(SV *sv) ...@@ -2893,7 +2922,7 @@ plperl_return_next(SV *sv)
if (!current_call_data->tmp_cxt) if (!current_call_data->tmp_cxt)
{ {
current_call_data->tmp_cxt = current_call_data->tmp_cxt =
AllocSetContextCreate(rsi->econtext->ecxt_per_tuple_memory, AllocSetContextCreate(CurrentMemoryContext,
"PL/Perl return_next temporary cxt", "PL/Perl return_next temporary cxt",
ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_INITSIZE,
...@@ -2906,8 +2935,14 @@ plperl_return_next(SV *sv) ...@@ -2906,8 +2935,14 @@ plperl_return_next(SV *sv)
{ {
HeapTuple tuple; HeapTuple tuple;
if (!(SvOK(sv) && SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVHV))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("SETOF-composite-returning PL/Perl function "
"must call return_next with reference to hash")));
tuple = plperl_build_tuple_result((HV *) SvRV(sv), tuple = plperl_build_tuple_result((HV *) SvRV(sv),
current_call_data->attinmeta); current_call_data->ret_tdesc);
tuplestore_puttuple(current_call_data->tuple_store, tuple); tuplestore_puttuple(current_call_data->tuple_store, tuple);
} }
else else
...@@ -2916,10 +2951,12 @@ plperl_return_next(SV *sv) ...@@ -2916,10 +2951,12 @@ plperl_return_next(SV *sv)
bool isNull; bool isNull;
ret = plperl_sv_to_datum(sv, ret = plperl_sv_to_datum(sv,
&prodesc->result_in_func,
prodesc->result_oid, prodesc->result_oid,
-1,
fcinfo,
&prodesc->result_in_func,
prodesc->result_typioparam, prodesc->result_typioparam,
-1, &isNull); &isNull);
tuplestore_putvalues(current_call_data->tuple_store, tuplestore_putvalues(current_call_data->tuple_store,
current_call_data->ret_tdesc, current_call_data->ret_tdesc,
...@@ -3318,10 +3355,12 @@ plperl_spi_exec_prepared(char *query, HV *attr, int argc, SV **argv) ...@@ -3318,10 +3355,12 @@ plperl_spi_exec_prepared(char *query, HV *attr, int argc, SV **argv)
bool isnull; bool isnull;
argvalues[i] = plperl_sv_to_datum(argv[i], argvalues[i] = plperl_sv_to_datum(argv[i],
&qdesc->arginfuncs[i],
qdesc->argtypes[i], qdesc->argtypes[i],
-1,
NULL,
&qdesc->arginfuncs[i],
qdesc->argtypioparams[i], qdesc->argtypioparams[i],
-1, &isnull); &isnull);
nulls[i] = isnull ? 'n' : ' '; nulls[i] = isnull ? 'n' : ' ';
} }
...@@ -3443,10 +3482,12 @@ plperl_spi_query_prepared(char *query, int argc, SV **argv) ...@@ -3443,10 +3482,12 @@ plperl_spi_query_prepared(char *query, int argc, SV **argv)
bool isnull; bool isnull;
argvalues[i] = plperl_sv_to_datum(argv[i], argvalues[i] = plperl_sv_to_datum(argv[i],
&qdesc->arginfuncs[i],
qdesc->argtypes[i], qdesc->argtypes[i],
-1,
NULL,
&qdesc->arginfuncs[i],
qdesc->argtypioparams[i], qdesc->argtypioparams[i],
-1, &isnull); &isnull);
nulls[i] = isnull ? 'n' : ' '; nulls[i] = isnull ? 'n' : ' ';
} }
......
...@@ -50,6 +50,13 @@ $$ LANGUAGE plperl; ...@@ -50,6 +50,13 @@ $$ LANGUAGE plperl;
SELECT perl_row(); SELECT perl_row();
SELECT * FROM perl_row(); SELECT * FROM perl_row();
-- test returning a composite literal
CREATE OR REPLACE FUNCTION perl_row_lit() RETURNS testrowperl AS $$
return '(1,hello,world,"({{1}})")';
$$ LANGUAGE plperl;
SELECT perl_row_lit();
CREATE OR REPLACE FUNCTION perl_set() RETURNS SETOF testrowperl AS $$ CREATE OR REPLACE FUNCTION perl_set() RETURNS SETOF testrowperl AS $$
return undef; return undef;
...@@ -415,3 +422,43 @@ DO $do$ use strict; my $name = "foo"; my $ref = $$name; $do$ LANGUAGE plperl; ...@@ -415,3 +422,43 @@ DO $do$ use strict; my $name = "foo"; my $ref = $$name; $do$ LANGUAGE plperl;
-- check that we can "use warnings" (in this case to turn a warn into an error) -- check that we can "use warnings" (in this case to turn a warn into an error)
-- yields "ERROR: Useless use of sort in scalar context." -- yields "ERROR: Useless use of sort in scalar context."
DO $do$ use warnings FATAL => qw(void) ; my @y; my $x = sort @y; 1; $do$ LANGUAGE plperl; DO $do$ use warnings FATAL => qw(void) ; my @y; my $x = sort @y; 1; $do$ LANGUAGE plperl;
-- make sure functions marked as VOID without an explicit return work
CREATE OR REPLACE FUNCTION myfuncs() RETURNS void AS $$
$_SHARED{myquote} = sub {
my $arg = shift;
$arg =~ s/(['\\])/\\$1/g;
return "'$arg'";
};
$$ LANGUAGE plperl;
SELECT myfuncs();
-- make sure we can't return an array as a scalar
CREATE OR REPLACE FUNCTION text_arrayref() RETURNS text AS $$
return ['array'];
$$ LANGUAGE plperl;
SELECT text_arrayref();
--- make sure we can't return a hash as a scalar
CREATE OR REPLACE FUNCTION text_hashref() RETURNS text AS $$
return {'hash'=>1};
$$ LANGUAGE plperl;
SELECT text_hashref();
---- make sure we can't return a blessed object as a scalar
CREATE OR REPLACE FUNCTION text_obj() RETURNS text AS $$
return bless({}, 'Fake::Object');
$$ LANGUAGE plperl;
SELECT text_obj();
----- make sure we can't return a scalar ref
CREATE OR REPLACE FUNCTION text_scalarref() RETURNS text AS $$
my $str = 'str';
return \$str;
$$ LANGUAGE plperl;
SELECT text_scalarref();
...@@ -152,6 +152,13 @@ $$ LANGUAGE plperl; ...@@ -152,6 +152,13 @@ $$ LANGUAGE plperl;
select plperl_arrays_inout('{{1}, {2}, {3}}'); select plperl_arrays_inout('{{1}, {2}, {3}}');
-- check that we can return an array literal
CREATE OR REPLACE FUNCTION plperl_arrays_inout_l(INTEGER[]) returns INTEGER[] AS $$
return shift.''; # stringify it
$$ LANGUAGE plperl;
select plperl_arrays_inout_l('{{1}, {2}, {3}}');
-- make sure setof works -- make sure setof works
create or replace function perl_setof_array(integer[]) returns setof integer[] language plperl as $$ create or replace function perl_setof_array(integer[]) returns setof integer[] language plperl as $$
my $arr = shift; my $arr = shift;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment