mirror of
https://github.com/postgres/postgres.git
synced 2025-07-14 08:21:07 +03:00
Give a hint, when [] is incorrectly used for a composite type in array.
That used to be accepted, so let's try to give a hint to users on why their PL/python functions no longer work. Reviewed by Pavel Stehule. Discussion: <CAH38_tmbqwaUyKs9yagyRra=SMaT45FPBxk1pmTYcM0TyXGG7Q@mail.gmail.com>
This commit is contained in:
@ -579,3 +579,16 @@ SELECT * FROM composite_type_as_list();
|
|||||||
{{"(first,1)","(second,1)"},{"(first,2)","(second,2)"},{"(first,3)","(second,3)"}}
|
{{"(first,1)","(second,1)"},{"(first,2)","(second,2)"},{"(first,3)","(second,3)"}}
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
|
-- Starting with PostgreSQL 10, a composite type in an array cannot be
|
||||||
|
-- represented as a Python list, because it's ambiguous with multi-dimensional
|
||||||
|
-- arrays. So this throws an error now. The error should contain a useful hint
|
||||||
|
-- on the issue.
|
||||||
|
CREATE FUNCTION composite_type_as_list_broken() RETURNS type_record[] AS $$
|
||||||
|
return [['first', 1]];
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
SELECT * FROM composite_type_as_list_broken();
|
||||||
|
ERROR: malformed record literal: "first"
|
||||||
|
DETAIL: Missing left parenthesis.
|
||||||
|
HINT: To return a composite type in an array, return the composite type as a Python tuple, e.g. "[('foo')]"
|
||||||
|
CONTEXT: while creating return value
|
||||||
|
PL/Python function "composite_type_as_list_broken"
|
||||||
|
@ -240,7 +240,8 @@ PLy_cursor_plan(PyObject *ob, PyObject *args)
|
|||||||
plan->values[j] =
|
plan->values[j] =
|
||||||
plan->args[j].out.d.func(&(plan->args[j].out.d),
|
plan->args[j].out.d.func(&(plan->args[j].out.d),
|
||||||
-1,
|
-1,
|
||||||
elem);
|
elem,
|
||||||
|
false);
|
||||||
}
|
}
|
||||||
PG_CATCH();
|
PG_CATCH();
|
||||||
{
|
{
|
||||||
|
@ -245,7 +245,7 @@ PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc)
|
|||||||
desc = lookup_rowtype_tupdesc(proc->result.out.d.typoid,
|
desc = lookup_rowtype_tupdesc(proc->result.out.d.typoid,
|
||||||
proc->result.out.d.typmod);
|
proc->result.out.d.typmod);
|
||||||
|
|
||||||
rv = PLyObject_ToCompositeDatum(&proc->result, desc, plrv);
|
rv = PLyObject_ToCompositeDatum(&proc->result, desc, plrv, false);
|
||||||
fcinfo->isnull = (rv == (Datum) NULL);
|
fcinfo->isnull = (rv == (Datum) NULL);
|
||||||
|
|
||||||
ReleaseTupleDesc(desc);
|
ReleaseTupleDesc(desc);
|
||||||
@ -253,7 +253,7 @@ PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
fcinfo->isnull = false;
|
fcinfo->isnull = false;
|
||||||
rv = (proc->result.out.d.func) (&proc->result.out.d, -1, plrv);
|
rv = (proc->result.out.d.func) (&proc->result.out.d, -1, plrv, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PG_CATCH();
|
PG_CATCH();
|
||||||
@ -984,7 +984,8 @@ PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata,
|
|||||||
|
|
||||||
modvalues[i] = (att->func) (att,
|
modvalues[i] = (att->func) (att,
|
||||||
tupdesc->attrs[atti]->atttypmod,
|
tupdesc->attrs[atti]->atttypmod,
|
||||||
plval);
|
plval,
|
||||||
|
false);
|
||||||
modnulls[i] = ' ';
|
modnulls[i] = ' ';
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -264,7 +264,8 @@ PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit)
|
|||||||
plan->values[j] =
|
plan->values[j] =
|
||||||
plan->args[j].out.d.func(&(plan->args[j].out.d),
|
plan->args[j].out.d.func(&(plan->args[j].out.d),
|
||||||
-1,
|
-1,
|
||||||
elem);
|
elem,
|
||||||
|
false);
|
||||||
}
|
}
|
||||||
PG_CATCH();
|
PG_CATCH();
|
||||||
{
|
{
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
#include "parser/parse_type.h"
|
#include "parser/parse_type.h"
|
||||||
#include "utils/array.h"
|
#include "utils/array.h"
|
||||||
#include "utils/builtins.h"
|
#include "utils/builtins.h"
|
||||||
|
#include "utils/fmgroids.h"
|
||||||
#include "utils/lsyscache.h"
|
#include "utils/lsyscache.h"
|
||||||
#include "utils/memutils.h"
|
#include "utils/memutils.h"
|
||||||
#include "utils/numeric.h"
|
#include "utils/numeric.h"
|
||||||
@ -49,21 +50,21 @@ static PyObject *PLyList_FromArray_recurse(PLyDatumToOb *elm, int *dims, int ndi
|
|||||||
char **dataptr_p, bits8 **bitmap_p, int *bitmask_p);
|
char **dataptr_p, bits8 **bitmap_p, int *bitmask_p);
|
||||||
|
|
||||||
/* conversion from Python objects to Datums */
|
/* conversion from Python objects to Datums */
|
||||||
static Datum PLyObject_ToBool(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
|
static Datum PLyObject_ToBool(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray);
|
||||||
static Datum PLyObject_ToBytea(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
|
static Datum PLyObject_ToBytea(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray);
|
||||||
static Datum PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
|
static Datum PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray);
|
||||||
static Datum PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
|
static Datum PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray);
|
||||||
static Datum PLyObject_ToTransform(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
|
static Datum PLyObject_ToTransform(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray);
|
||||||
static Datum PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
|
static Datum PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray);
|
||||||
static void PLySequence_ToArray_recurse(PLyObToDatum *elm, PyObject *list,
|
static void PLySequence_ToArray_recurse(PLyObToDatum *elm, PyObject *list,
|
||||||
int *dims, int ndim, int dim,
|
int *dims, int ndim, int dim,
|
||||||
Datum *elems, bool *nulls, int *currelem);
|
Datum *elems, bool *nulls, int *currelem);
|
||||||
|
|
||||||
/* conversion from Python objects to composite Datums (used by triggers and SRFs) */
|
/* conversion from Python objects to composite Datums (used by triggers and SRFs) */
|
||||||
static Datum PLyString_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *string);
|
static Datum PLyString_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *string, bool inarray);
|
||||||
static Datum PLyMapping_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping);
|
static Datum PLyMapping_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping);
|
||||||
static Datum PLySequence_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence);
|
static Datum PLySequence_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence);
|
||||||
static Datum PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object);
|
static Datum PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object, bool inarray);
|
||||||
|
|
||||||
void
|
void
|
||||||
PLy_typeinfo_init(PLyTypeInfo *arg, MemoryContext mcxt)
|
PLy_typeinfo_init(PLyTypeInfo *arg, MemoryContext mcxt)
|
||||||
@ -341,12 +342,12 @@ PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc)
|
|||||||
* as an object that has __getattr__ support.
|
* as an object that has __getattr__ support.
|
||||||
*/
|
*/
|
||||||
Datum
|
Datum
|
||||||
PLyObject_ToCompositeDatum(PLyTypeInfo *info, TupleDesc desc, PyObject *plrv)
|
PLyObject_ToCompositeDatum(PLyTypeInfo *info, TupleDesc desc, PyObject *plrv, bool inarray)
|
||||||
{
|
{
|
||||||
Datum datum;
|
Datum datum;
|
||||||
|
|
||||||
if (PyString_Check(plrv) || PyUnicode_Check(plrv))
|
if (PyString_Check(plrv) || PyUnicode_Check(plrv))
|
||||||
datum = PLyString_ToComposite(info, desc, plrv);
|
datum = PLyString_ToComposite(info, desc, plrv, inarray);
|
||||||
else if (PySequence_Check(plrv))
|
else if (PySequence_Check(plrv))
|
||||||
/* composite type as sequence (tuple, list etc) */
|
/* composite type as sequence (tuple, list etc) */
|
||||||
datum = PLySequence_ToComposite(info, desc, plrv);
|
datum = PLySequence_ToComposite(info, desc, plrv);
|
||||||
@ -355,7 +356,7 @@ PLyObject_ToCompositeDatum(PLyTypeInfo *info, TupleDesc desc, PyObject *plrv)
|
|||||||
datum = PLyMapping_ToComposite(info, desc, plrv);
|
datum = PLyMapping_ToComposite(info, desc, plrv);
|
||||||
else
|
else
|
||||||
/* returned as smth, must provide method __getattr__(name) */
|
/* returned as smth, must provide method __getattr__(name) */
|
||||||
datum = PLyGenericObject_ToComposite(info, desc, plrv);
|
datum = PLyGenericObject_ToComposite(info, desc, plrv, inarray);
|
||||||
|
|
||||||
return datum;
|
return datum;
|
||||||
}
|
}
|
||||||
@ -746,7 +747,7 @@ PLyList_FromArray_recurse(PLyDatumToOb *elm, int *dims, int ndim, int dim,
|
|||||||
* type can parse.
|
* type can parse.
|
||||||
*/
|
*/
|
||||||
static Datum
|
static Datum
|
||||||
PLyObject_ToBool(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
|
PLyObject_ToBool(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray)
|
||||||
{
|
{
|
||||||
Datum rv;
|
Datum rv;
|
||||||
|
|
||||||
@ -765,7 +766,7 @@ PLyObject_ToBool(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
|
|||||||
* with embedded nulls. And it's faster this way.
|
* with embedded nulls. And it's faster this way.
|
||||||
*/
|
*/
|
||||||
static Datum
|
static Datum
|
||||||
PLyObject_ToBytea(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
|
PLyObject_ToBytea(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray)
|
||||||
{
|
{
|
||||||
PyObject *volatile plrv_so = NULL;
|
PyObject *volatile plrv_so = NULL;
|
||||||
Datum rv;
|
Datum rv;
|
||||||
@ -809,7 +810,7 @@ PLyObject_ToBytea(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
|
|||||||
* for obtaining PostgreSQL tuples.
|
* for obtaining PostgreSQL tuples.
|
||||||
*/
|
*/
|
||||||
static Datum
|
static Datum
|
||||||
PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
|
PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray)
|
||||||
{
|
{
|
||||||
Datum rv;
|
Datum rv;
|
||||||
PLyTypeInfo info;
|
PLyTypeInfo info;
|
||||||
@ -836,7 +837,7 @@ PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
|
|||||||
* that info instead of looking it up every time a tuple is returned from
|
* that info instead of looking it up every time a tuple is returned from
|
||||||
* the function.
|
* the function.
|
||||||
*/
|
*/
|
||||||
rv = PLyObject_ToCompositeDatum(&info, desc, plrv);
|
rv = PLyObject_ToCompositeDatum(&info, desc, plrv, inarray);
|
||||||
|
|
||||||
ReleaseTupleDesc(desc);
|
ReleaseTupleDesc(desc);
|
||||||
|
|
||||||
@ -908,26 +909,70 @@ PLyObject_AsString(PyObject *plrv)
|
|||||||
* cstring into PostgreSQL type.
|
* cstring into PostgreSQL type.
|
||||||
*/
|
*/
|
||||||
static Datum
|
static Datum
|
||||||
PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
|
PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray)
|
||||||
{
|
{
|
||||||
|
char *str;
|
||||||
|
|
||||||
Assert(plrv != Py_None);
|
Assert(plrv != Py_None);
|
||||||
|
|
||||||
|
str = PLyObject_AsString(plrv);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we are parsing a composite type within an array, and the string
|
||||||
|
* isn't a valid record literal, there's a high chance that the function
|
||||||
|
* did something like:
|
||||||
|
*
|
||||||
|
* CREATE FUNCTION .. RETURNS comptype[] AS $$ return [['foo', 'bar']] $$
|
||||||
|
* LANGUAGE plpython;
|
||||||
|
*
|
||||||
|
* Before PostgreSQL 10, that was interpreted as a single-dimensional
|
||||||
|
* array, containing record ('foo', 'bar'). PostgreSQL 10 added support
|
||||||
|
* for multi-dimensional arrays, and it is now interpreted as a
|
||||||
|
* two-dimensional array, containing two records, 'foo', and 'bar'.
|
||||||
|
* record_in() will throw an error, because "foo" is not a valid record
|
||||||
|
* literal.
|
||||||
|
*
|
||||||
|
* To make that less confusing to users who are upgrading from older
|
||||||
|
* versions, try to give a hint in the typical instances of that. If we are
|
||||||
|
* parsing an array of composite types, and we see a string literal that
|
||||||
|
* is not a valid record literal, give a hint. We only want to give the
|
||||||
|
* hint in the narrow case of a malformed string literal, not any error
|
||||||
|
* from record_in(), so check for that case here specifically.
|
||||||
|
*
|
||||||
|
* This check better match the one in record_in(), so that we don't forbid
|
||||||
|
* literals that are actually valid!
|
||||||
|
*/
|
||||||
|
if (inarray && arg->typfunc.fn_oid == F_RECORD_IN)
|
||||||
|
{
|
||||||
|
char *ptr = str;
|
||||||
|
|
||||||
|
/* Allow leading whitespace */
|
||||||
|
while (*ptr && isspace((unsigned char) *ptr))
|
||||||
|
ptr++;
|
||||||
|
if (*ptr++ != '(')
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||||
|
errmsg("malformed record literal: \"%s\"", str),
|
||||||
|
errdetail("Missing left parenthesis."),
|
||||||
|
errhint("To return a composite type in an array, return the composite type as a Python tuple, e.g. \"[('foo')]\"")));
|
||||||
|
}
|
||||||
|
|
||||||
return InputFunctionCall(&arg->typfunc,
|
return InputFunctionCall(&arg->typfunc,
|
||||||
PLyObject_AsString(plrv),
|
str,
|
||||||
arg->typioparam,
|
arg->typioparam,
|
||||||
typmod);
|
typmod);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static Datum
|
static Datum
|
||||||
PLyObject_ToTransform(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
|
PLyObject_ToTransform(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray)
|
||||||
{
|
{
|
||||||
return FunctionCall1(&arg->typtransform, PointerGetDatum(plrv));
|
return FunctionCall1(&arg->typtransform, PointerGetDatum(plrv));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static Datum
|
static Datum
|
||||||
PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
|
PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray)
|
||||||
{
|
{
|
||||||
ArrayType *array;
|
ArrayType *array;
|
||||||
int i;
|
int i;
|
||||||
@ -1085,7 +1130,7 @@ PLySequence_ToArray_recurse(PLyObToDatum *elm, PyObject *list,
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
nulls[*currelem] = false;
|
nulls[*currelem] = false;
|
||||||
elems[*currelem] = elm->func(elm, -1, obj);
|
elems[*currelem] = elm->func(elm, -1, obj, true);
|
||||||
}
|
}
|
||||||
Py_XDECREF(obj);
|
Py_XDECREF(obj);
|
||||||
(*currelem)++;
|
(*currelem)++;
|
||||||
@ -1095,7 +1140,7 @@ PLySequence_ToArray_recurse(PLyObToDatum *elm, PyObject *list,
|
|||||||
|
|
||||||
|
|
||||||
static Datum
|
static Datum
|
||||||
PLyString_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *string)
|
PLyString_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *string, bool inarray)
|
||||||
{
|
{
|
||||||
Datum result;
|
Datum result;
|
||||||
HeapTuple typeTup;
|
HeapTuple typeTup;
|
||||||
@ -1120,7 +1165,7 @@ PLyString_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *string)
|
|||||||
|
|
||||||
ReleaseSysCache(typeTup);
|
ReleaseSysCache(typeTup);
|
||||||
|
|
||||||
result = PLyObject_ToDatum(&locinfo.out.d, desc->tdtypmod, string);
|
result = PLyObject_ToDatum(&locinfo.out.d, desc->tdtypmod, string, inarray);
|
||||||
|
|
||||||
MemoryContextDelete(cxt);
|
MemoryContextDelete(cxt);
|
||||||
|
|
||||||
@ -1172,7 +1217,7 @@ PLyMapping_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping)
|
|||||||
}
|
}
|
||||||
else if (value)
|
else if (value)
|
||||||
{
|
{
|
||||||
values[i] = (att->func) (att, -1, value);
|
values[i] = (att->func) (att, -1, value, false);
|
||||||
nulls[i] = false;
|
nulls[i] = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -1265,7 +1310,7 @@ PLySequence_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence)
|
|||||||
}
|
}
|
||||||
else if (value)
|
else if (value)
|
||||||
{
|
{
|
||||||
values[i] = (att->func) (att, -1, value);
|
values[i] = (att->func) (att, -1, value, false);
|
||||||
nulls[i] = false;
|
nulls[i] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1294,7 +1339,7 @@ PLySequence_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence)
|
|||||||
|
|
||||||
|
|
||||||
static Datum
|
static Datum
|
||||||
PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object)
|
PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object, bool inarray)
|
||||||
{
|
{
|
||||||
Datum result;
|
Datum result;
|
||||||
HeapTuple tuple;
|
HeapTuple tuple;
|
||||||
@ -1335,16 +1380,29 @@ PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object
|
|||||||
}
|
}
|
||||||
else if (value)
|
else if (value)
|
||||||
{
|
{
|
||||||
values[i] = (att->func) (att, -1, value);
|
values[i] = (att->func) (att, -1, value, false);
|
||||||
nulls[i] = false;
|
nulls[i] = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* No attribute for this column in the object.
|
||||||
|
*
|
||||||
|
* If we are parsing a composite type in an array, a likely
|
||||||
|
* cause is that the function contained something like "[[123,
|
||||||
|
* 'foo']]". Before PostgreSQL 10, that was interpreted as an
|
||||||
|
* array, with a composite type (123, 'foo') in it. But now
|
||||||
|
* it's interpreted as a two-dimensional array, and we try to
|
||||||
|
* interpret "123" as the composite type. See also similar
|
||||||
|
* heuristic in PLyObject_ToDatum().
|
||||||
|
*/
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
||||||
errmsg("attribute \"%s\" does not exist in Python object", key),
|
errmsg("attribute \"%s\" does not exist in Python object", key),
|
||||||
errhint("To return null in a column, "
|
inarray ?
|
||||||
"let the returned object have an attribute named "
|
errhint("To return a composite type in an array, return the composite type as a Python tuple, e.g. \"[('foo')]\"") :
|
||||||
"after column with value None.")));
|
errhint("To return null in a column, let the returned object have an attribute named after column with value None.")));
|
||||||
|
}
|
||||||
|
|
||||||
Py_XDECREF(value);
|
Py_XDECREF(value);
|
||||||
value = NULL;
|
value = NULL;
|
||||||
|
@ -10,8 +10,11 @@
|
|||||||
#include "fmgr.h"
|
#include "fmgr.h"
|
||||||
#include "storage/itemptr.h"
|
#include "storage/itemptr.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Conversion from PostgreSQL Datum to a Python object.
|
||||||
|
*/
|
||||||
struct PLyDatumToOb;
|
struct PLyDatumToOb;
|
||||||
typedef PyObject *(*PLyDatumToObFunc) (struct PLyDatumToOb *, Datum);
|
typedef PyObject *(*PLyDatumToObFunc) (struct PLyDatumToOb *arg, Datum val);
|
||||||
|
|
||||||
typedef struct PLyDatumToOb
|
typedef struct PLyDatumToOb
|
||||||
{
|
{
|
||||||
@ -39,11 +42,15 @@ typedef union PLyTypeInput
|
|||||||
PLyTupleToOb r;
|
PLyTupleToOb r;
|
||||||
} PLyTypeInput;
|
} PLyTypeInput;
|
||||||
|
|
||||||
/* convert PyObject to a Postgresql Datum or tuple.
|
/*
|
||||||
* output from Python
|
* Conversion from Python object to a Postgresql Datum.
|
||||||
|
*
|
||||||
|
* The 'inarray' argument to the conversion function is true, if the
|
||||||
|
* converted value was in an array (Python list). It is used to give a
|
||||||
|
* better error message in some cases.
|
||||||
*/
|
*/
|
||||||
struct PLyObToDatum;
|
struct PLyObToDatum;
|
||||||
typedef Datum (*PLyObToDatumFunc) (struct PLyObToDatum *, int32, PyObject *);
|
typedef Datum (*PLyObToDatumFunc) (struct PLyObToDatum *arg, int32 typmod, PyObject *val, bool inarray);
|
||||||
|
|
||||||
typedef struct PLyObToDatum
|
typedef struct PLyObToDatum
|
||||||
{
|
{
|
||||||
@ -104,7 +111,7 @@ extern void PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc);
|
|||||||
extern void PLy_output_record_funcs(PLyTypeInfo *arg, TupleDesc desc);
|
extern void PLy_output_record_funcs(PLyTypeInfo *arg, TupleDesc desc);
|
||||||
|
|
||||||
/* conversion from Python objects to composite Datums */
|
/* conversion from Python objects to composite Datums */
|
||||||
extern Datum PLyObject_ToCompositeDatum(PLyTypeInfo *info, TupleDesc desc, PyObject *plrv);
|
extern Datum PLyObject_ToCompositeDatum(PLyTypeInfo *info, TupleDesc desc, PyObject *plrv, bool isarray);
|
||||||
|
|
||||||
/* conversion from heap tuples to Python dictionaries */
|
/* conversion from heap tuples to Python dictionaries */
|
||||||
extern PyObject *PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc);
|
extern PyObject *PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc);
|
||||||
|
@ -213,3 +213,12 @@ CREATE FUNCTION composite_type_as_list() RETURNS type_record[] AS $$
|
|||||||
return [[('first', 1), ('second', 1)], [('first', 2), ('second', 2)], [('first', 3), ('second', 3)]];
|
return [[('first', 1), ('second', 1)], [('first', 2), ('second', 2)], [('first', 3), ('second', 3)]];
|
||||||
$$ LANGUAGE plpythonu;
|
$$ LANGUAGE plpythonu;
|
||||||
SELECT * FROM composite_type_as_list();
|
SELECT * FROM composite_type_as_list();
|
||||||
|
|
||||||
|
-- Starting with PostgreSQL 10, a composite type in an array cannot be
|
||||||
|
-- represented as a Python list, because it's ambiguous with multi-dimensional
|
||||||
|
-- arrays. So this throws an error now. The error should contain a useful hint
|
||||||
|
-- on the issue.
|
||||||
|
CREATE FUNCTION composite_type_as_list_broken() RETURNS type_record[] AS $$
|
||||||
|
return [['first', 1]];
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
SELECT * FROM composite_type_as_list_broken();
|
||||||
|
Reference in New Issue
Block a user