FunctionCallInfo fcinfo;
Tuplestorestate *tuple_store;
TupleDesc ret_tdesc;
- AttInMetadata *attinmeta;
MemoryContext tmp_cxt;
} plperl_call_data;
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 *get_perl_array_ref(SV *sv);
-static Datum plperl_sv_to_datum(SV *sv, FmgrInfo *func, Oid typid,
- Oid typioparam, int32 typmod, bool *isnull);
-static void _sv_to_datum_finfo(FmgrInfo *fcinfo, Oid typid, Oid *typioparam);
-static Datum plperl_array_to_datum(SV *src, Oid typid);
-static ArrayBuildState *_array_to_datum(AV *av, int *ndims, int *dims,
- int cur_depth, ArrayBuildState *astate, Oid typid, Oid atypid);
+static Datum plperl_sv_to_datum(SV *sv, Oid typid, int32 typmod,
+ FunctionCallInfo fcinfo,
+ FmgrInfo *finfo, Oid typioparam,
+ bool *isnull);
+static void _sv_to_datum_finfo(Oid typid, FmgrInfo *finfo, Oid *typioparam);
+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 void plperl_init_shared_libs(pTHX);
/* Build a tuple from a hash. */
static HeapTuple
-plperl_build_tuple_result(HV *perlhash, AttInMetadata *attinmeta)
+plperl_build_tuple_result(HV *perlhash, TupleDesc td)
{
- TupleDesc td = attinmeta->tupdesc;
Datum *values;
bool *nulls;
HE *he;
SV *val = HeVAL(he);
char *key = hek2cstr(he);
int attn = SPI_fnumber(td, key);
- bool isnull;
if (attn <= 0 || td->attrs[attn - 1]->attisdropped)
ereport(ERROR,
key)));
values[attn - 1] = plperl_sv_to_datum(val,
- NULL,
td->attrs[attn - 1]->atttypid,
- InvalidOid,
td->attrs[attn - 1]->atttypmod,
- &isnull);
- nulls[attn - 1] = isnull;
+ NULL,
+ NULL,
+ InvalidOid,
+ &nulls[attn - 1]);
pfree(key);
}
static Datum
plperl_hash_to_datum(SV *src, TupleDesc td)
{
- AttInMetadata *attinmeta = TupleDescGetAttInMetadata(td);
- HeapTuple tup = plperl_build_tuple_result((HV *) SvRV(src), attinmeta);
+ HeapTuple tup = plperl_build_tuple_result((HV *) SvRV(src), td);
return HeapTupleGetDatum(tup);
}
}
/*
- * 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 *
-_array_to_datum(AV *av, int *ndims, int *dims, int cur_depth,
- ArrayBuildState *astate, Oid typid, Oid atypid)
+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)
{
- int i = 0;
+ int i;
int len = av_len(av) + 1;
for (i = 0; i < len; i++)
{
AV *nav = (AV *) SvRV(sav);
+ /* dimensionality checks */
if (cur_depth + 1 > MAXDIM)
ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
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)
{
dims[*ndims] = av_len(nav) + 1;
(*ndims)++;
}
- else
- {
- if (av_len(nav) + 1 != dims[cur_depth])
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("multidimensional arrays must have array expressions with matching dimensions")));
- }
-
- astate = _array_to_datum(nav, ndims, dims, cur_depth + 1, astate,
- typid, atypid);
+ else if (av_len(nav) + 1 != dims[cur_depth])
+ ereport(ERROR,
+ (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,
+ arraytypid, elemtypid, typmod,
+ finfo, typioparam);
}
else
{
+ Datum dat;
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);
}
}
* convert perl array ref to a datum
*/
static Datum
-plperl_array_to_datum(SV *src, Oid typid)
+plperl_array_to_datum(SV *src, Oid typid, int32 typmod)
{
- ArrayBuildState *astate = NULL;
- Oid atypid;
+ ArrayBuildState *astate;
+ Oid elemtypid;
+ FmgrInfo finfo;
+ Oid typioparam;
int dims[MAXDIM];
int lbs[MAXDIM];
int ndims = 1;
int i;
- atypid = get_element_type(typid);
- if (!atypid)
- atypid = typid;
+ elemtypid = get_element_type(typid);
+ if (!elemtypid)
+ 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));
dims[0] = av_len((AV *) SvRV(src)) + 1;
- astate = _array_to_datum((AV *) SvRV(src), &ndims, dims, 1, astate, typid,
- atypid);
+ astate = array_to_datum_internal((AV *) SvRV(src), NULL,
+ &ndims, dims, 1,
+ typid, elemtypid, typmod,
+ &finfo, typioparam);
if (!astate)
- return PointerGetDatum(construct_empty_array(atypid));
+ return PointerGetDatum(construct_empty_array(elemtypid));
for (i = 0; i < ndims; i++)
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
-_sv_to_datum_finfo(FmgrInfo *fcinfo, Oid typid, Oid *typioparam)
+_sv_to_datum_finfo(Oid typid, FmgrInfo *finfo, Oid *typioparam)
{
Oid typinput;
/* XXX would be better to cache these lookups */
getTypeInputInfo(typid,
&typinput, typioparam);
- fmgr_info(typinput, fcinfo);
+ fmgr_info(typinput, finfo);
}
/*
- * convert a sv to datum
- * fcinfo and typioparam are optional and will be looked-up if needed
+ * convert Perl SV to PG datum of type typid, typmod typmod
+ *
+ * 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
-plperl_sv_to_datum(SV *sv, FmgrInfo *finfo, Oid typid, Oid typioparam,
- int32 typmod, bool *isnull)
+plperl_sv_to_datum(SV *sv, Oid typid, int32 typmod,
+ FunctionCallInfo fcinfo,
+ FmgrInfo *finfo, Oid typioparam,
+ bool *isnull)
{
FmgrInfo tmp;
/* we might recurse */
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)
{
- _sv_to_datum_finfo(&tmp, typid, &typioparam);
+ _sv_to_datum_finfo(typid, &tmp, &typioparam);
finfo = &tmp;
}
- if (isnull)
- *isnull = true;
+ *isnull = true;
+ /* must call typinput in case it wants to reject NULL */
return InputFunctionCall(finfo, NULL, typioparam, typmod);
}
else if (SvROK(sv))
{
+ /* handle references */
SV *sav = get_perl_array_ref(sv);
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)
{
- TupleDesc td = lookup_rowtype_tupdesc(typid, typmod);
- Datum ret = plperl_hash_to_datum(sv, td);
+ /* handle a hashref */
+ 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);
+
return ret;
}
+ /* Reference, but not reference to hash or array ... */
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("PL/Perl function must return reference to hash or array")));
}
else
{
+ /* handle a string/number */
Datum ret;
char *str = sv2cstr(sv);
+ /* did not pass in any typeinfo? look it up */
if (!finfo)
{
- _sv_to_datum_finfo(&tmp, typid, &typioparam);
+ _sv_to_datum_finfo(typid, &tmp, &typioparam);
finfo = &tmp;
}
if (!OidIsValid(typid))
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)
return NULL;
key)));
modvalues[slotsused] = plperl_sv_to_datum(val,
- NULL,
tupdesc->attrs[attn - 1]->atttypid,
- InvalidOid,
tupdesc->attrs[attn - 1]->atttypmod,
+ NULL,
+ NULL,
+ InvalidOid,
&isnull);
modnulls[slotsused] = isnull ? 'n' : ' ';
Datum retval = 0;
ReturnSetInfo *rsi;
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
*/
current_call_data = (plperl_call_data *) palloc0(sizeof(plperl_call_data));
rsi->setDesc = current_call_data->ret_tdesc;
}
retval = (Datum) 0;
- has_retval = true;
}
- else if (!SvOK(perlret))
- {
- /* 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)
+ else
{
- bool isnull;
-
retval = plperl_sv_to_datum(perlret,
- &prodesc->result_in_func,
prodesc->result_oid,
- prodesc->result_typioparam, -1, &isnull);
- fcinfo->isnull = isnull;
- has_retval = true;
+ -1,
+ fcinfo,
+ &prodesc->result_in_func,
+ prodesc->result_typioparam,
+ &fcinfo->isnull);
+
+ if (fcinfo->isnull && rsi && IsA(rsi, ReturnSetInfo))
+ rsi->isDone = ExprEndResult;
}
/* Restore the previous error callback */
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
*/
current_call_data = (plperl_call_data *) palloc0(sizeof(plperl_call_data));
(errcode(ERRCODE_SYNTAX_ERROR),
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)
{
TupleDesc tupdesc;
Assert(!current_call_data->tuple_store);
- Assert(!current_call_data->attinmeta);
/*
* This is the first call to return_next in the current PL/Perl
current_call_data->tuple_store =
tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
false, work_mem);
- if (prodesc->fn_retistuple)
- {
- current_call_data->attinmeta =
- TupleDescGetAttInMetadata(current_call_data->ret_tdesc);
- }
MemoryContextSwitchTo(old_cxt);
}
if (!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",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
{
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),
- current_call_data->attinmeta);
+ current_call_data->ret_tdesc);
tuplestore_puttuple(current_call_data->tuple_store, tuple);
}
else
bool isNull;
ret = plperl_sv_to_datum(sv,
- &prodesc->result_in_func,
prodesc->result_oid,
+ -1,
+ fcinfo,
+ &prodesc->result_in_func,
prodesc->result_typioparam,
- -1, &isNull);
+ &isNull);
tuplestore_putvalues(current_call_data->tuple_store,
current_call_data->ret_tdesc,
bool isnull;
argvalues[i] = plperl_sv_to_datum(argv[i],
- &qdesc->arginfuncs[i],
qdesc->argtypes[i],
+ -1,
+ NULL,
+ &qdesc->arginfuncs[i],
qdesc->argtypioparams[i],
- -1, &isnull);
+ &isnull);
nulls[i] = isnull ? 'n' : ' ';
}
bool isnull;
argvalues[i] = plperl_sv_to_datum(argv[i],
- &qdesc->arginfuncs[i],
qdesc->argtypes[i],
+ -1,
+ NULL,
+ &qdesc->arginfuncs[i],
qdesc->argtypioparams[i],
- -1, &isnull);
+ &isnull);
nulls[i] = isnull ? 'n' : ' ';
}