ERROR: not a Python mapping
CONTEXT: while creating return value
PL/Python function "test1bad"
+-- A mapping whose items() raises should be reported as an error, not crash
+-- the backend
+CREATE FUNCTION test1broken() RETURNS hstore
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE hstore
+AS $$
+class C(dict):
+ def items(self):
+ raise ValueError('items failed')
+d = C()
+d['x'] = 1
+return d
+$$;
+SELECT test1broken();
+ERROR: could not get items from Python mapping
+CONTEXT: while creating return value
+PL/Python function "test1broken"
+-- Likewise for a mapping whose items() does not return key/value pairs
+CREATE FUNCTION test1malformed() RETURNS hstore
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE hstore
+AS $$
+class C(dict):
+ def items(self):
+ return [42]
+d = C()
+d['x'] = 1
+return d
+$$;
+SELECT test1malformed();
+ERROR: items() of a Python mapping must return key/value pairs
+CONTEXT: while creating return value
+PL/Python function "test1malformed"
+-- Likewise for a mapping whose items() yields fewer pairs than its length
+CREATE FUNCTION test1short() RETURNS hstore
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE hstore
+AS $$
+class C(dict):
+ def items(self):
+ return []
+d = C()
+d['x'] = 1
+return d
+$$;
+SELECT test1short();
+ERROR: items() of a Python mapping must return key/value pairs
+CONTEXT: while creating return value
+PL/Python function "test1short"
+-- Likewise for a mapping whose __len__() raises
+CREATE FUNCTION test1brokenlen() RETURNS hstore
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE hstore
+AS $$
+class C(dict):
+ def __len__(self):
+ raise ValueError('len failed')
+d = C()
+d['x'] = 1
+return d
+$$;
+SELECT test1brokenlen();
+ERROR: could not get size of Python mapping
+CONTEXT: while creating return value
+PL/Python function "test1brokenlen"
-- test hstore[] -> python
CREATE FUNCTION test1arr(val hstore[]) RETURNS int
LANGUAGE plpython3u
errmsg("not a Python mapping")));
pcount = PyMapping_Size(dict);
+ if (pcount < 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("could not get size of Python mapping")));
+
items = PyMapping_Items(dict);
+ if (items == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("could not get items from Python mapping")));
PG_TRY();
{
PyObject *value;
tuple = PyList_GetItem(items, i);
+
+ /* The mapping's items() must yield key/value pairs */
+ if (tuple == NULL || !PyTuple_Check(tuple) || PyTuple_Size(tuple) < 2)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("items() of a Python mapping must return key/value pairs")));
+
key = PyTuple_GetItem(tuple, 0);
value = PyTuple_GetItem(tuple, 1);
SELECT test1bad();
+-- A mapping whose items() raises should be reported as an error, not crash
+-- the backend
+CREATE FUNCTION test1broken() RETURNS hstore
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE hstore
+AS $$
+class C(dict):
+ def items(self):
+ raise ValueError('items failed')
+d = C()
+d['x'] = 1
+return d
+$$;
+
+SELECT test1broken();
+
+
+-- Likewise for a mapping whose items() does not return key/value pairs
+CREATE FUNCTION test1malformed() RETURNS hstore
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE hstore
+AS $$
+class C(dict):
+ def items(self):
+ return [42]
+d = C()
+d['x'] = 1
+return d
+$$;
+
+SELECT test1malformed();
+
+
+-- Likewise for a mapping whose items() yields fewer pairs than its length
+CREATE FUNCTION test1short() RETURNS hstore
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE hstore
+AS $$
+class C(dict):
+ def items(self):
+ return []
+d = C()
+d['x'] = 1
+return d
+$$;
+
+SELECT test1short();
+
+
+-- Likewise for a mapping whose __len__() raises
+CREATE FUNCTION test1brokenlen() RETURNS hstore
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE hstore
+AS $$
+class C(dict):
+ def __len__(self):
+ raise ValueError('len failed')
+d = C()
+d['x'] = 1
+return d
+$$;
+
+SELECT test1brokenlen();
+
+
-- test hstore[] -> python
CREATE FUNCTION test1arr(val hstore[]) RETURNS int
LANGUAGE plpython3u
{"": 2, "a": 1, "33": 3}
(1 row)
+-- A custom sequence whose __getitem__ raises should be reported as an error,
+-- not crash the backend
+CREATE FUNCTION test_broken_sequence() RETURNS jsonb
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE jsonb
+AS $$
+class C:
+ def __len__(self):
+ return 2
+ def __getitem__(self, i):
+ raise ValueError('getitem failed')
+return C()
+$$;
+SELECT test_broken_sequence();
+ERROR: could not get element 0 from sequence
+DETAIL: ValueError: getitem failed
+CONTEXT: Traceback (most recent call last):
+while creating return value
+PL/Python function "test_broken_sequence"
+-- A mapping whose items() raises should be reported as an error, not crash
+-- the backend
+CREATE FUNCTION test_broken_mapping() RETURNS jsonb
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE jsonb
+AS $$
+class C(dict):
+ def items(self):
+ raise ValueError('items failed')
+d = C()
+d['x'] = 1
+return d
+$$;
+SELECT test_broken_mapping();
+ERROR: could not get items from Python mapping
+DETAIL: ValueError: items failed
+CONTEXT: Traceback (most recent call last):
+while creating return value
+PL/Python function "test_broken_mapping"
+-- Likewise for a mapping whose items() does not return key/value pairs
+CREATE FUNCTION test_malformed_mapping() RETURNS jsonb
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE jsonb
+AS $$
+class C(dict):
+ def items(self):
+ return [42]
+d = C()
+d['x'] = 1
+return d
+$$;
+SELECT test_malformed_mapping();
+ERROR: items() of a Python mapping must return key/value pairs
+CONTEXT: while creating return value
+PL/Python function "test_malformed_mapping"
+-- Likewise for a mapping whose items() yields fewer pairs than its length
+CREATE FUNCTION test_short_mapping() RETURNS jsonb
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE jsonb
+AS $$
+class C(dict):
+ def items(self):
+ return []
+d = C()
+d['x'] = 1
+return d
+$$;
+SELECT test_short_mapping();
+ERROR: items() of a Python mapping must return key/value pairs
+DETAIL: IndexError: list index out of range
+CONTEXT: while creating return value
+PL/Python function "test_short_mapping"
+-- Likewise for a mapping whose __len__() raises
+CREATE FUNCTION test_broken_len_mapping() RETURNS jsonb
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE jsonb
+AS $$
+class C(dict):
+ def __len__(self):
+ raise ValueError('len failed')
+d = C()
+d['x'] = 1
+return d
+$$;
+SELECT test_broken_len_mapping();
+ERROR: could not get size of Python mapping
+DETAIL: ValueError: len failed
+CONTEXT: Traceback (most recent call last):
+while creating return value
+PL/Python function "test_broken_len_mapping"
JsonbValue *volatile out;
pcount = PyMapping_Size(obj);
+ if (pcount < 0)
+ PLy_elog(ERROR, "could not get size of Python mapping");
+
items = PyMapping_Items(obj);
+ if (items == NULL)
+ PLy_elog(ERROR, "could not get items from Python mapping");
PG_TRY();
{
{
JsonbValue jbvKey;
PyObject *item = PyList_GetItem(items, i);
- PyObject *key = PyTuple_GetItem(item, 0);
- PyObject *value = PyTuple_GetItem(item, 1);
+ PyObject *key;
+ PyObject *value;
+
+ /* The mapping's items() must yield key/value pairs */
+ if (item == NULL || !PyTuple_Check(item) || PyTuple_Size(item) < 2)
+ PLy_elog(ERROR, "items() of a Python mapping must return key/value pairs");
+
+ key = PyTuple_GetItem(item, 0);
+ value = PyTuple_GetItem(item, 1);
/* Python dictionary can have None as key */
if (key == Py_None)
for (i = 0; i < pcount; i++)
{
value = PySequence_GetItem(obj, i);
- Assert(value);
+
+ /* PySequence_GetItem() can return NULL, with an exception set */
+ if (value == NULL)
+ PLy_elog(ERROR, "could not get element %d from sequence", (int) i);
(void) PLyObject_ToJsonbValue(value, jsonb_state, true);
Py_XDECREF(value);
$$;
SELECT test_dict1();
+
+-- A custom sequence whose __getitem__ raises should be reported as an error,
+-- not crash the backend
+CREATE FUNCTION test_broken_sequence() RETURNS jsonb
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE jsonb
+AS $$
+class C:
+ def __len__(self):
+ return 2
+ def __getitem__(self, i):
+ raise ValueError('getitem failed')
+return C()
+$$;
+
+SELECT test_broken_sequence();
+
+-- A mapping whose items() raises should be reported as an error, not crash
+-- the backend
+CREATE FUNCTION test_broken_mapping() RETURNS jsonb
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE jsonb
+AS $$
+class C(dict):
+ def items(self):
+ raise ValueError('items failed')
+d = C()
+d['x'] = 1
+return d
+$$;
+
+SELECT test_broken_mapping();
+
+-- Likewise for a mapping whose items() does not return key/value pairs
+CREATE FUNCTION test_malformed_mapping() RETURNS jsonb
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE jsonb
+AS $$
+class C(dict):
+ def items(self):
+ return [42]
+d = C()
+d['x'] = 1
+return d
+$$;
+
+SELECT test_malformed_mapping();
+
+-- Likewise for a mapping whose items() yields fewer pairs than its length
+CREATE FUNCTION test_short_mapping() RETURNS jsonb
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE jsonb
+AS $$
+class C(dict):
+ def items(self):
+ return []
+d = C()
+d['x'] = 1
+return d
+$$;
+
+SELECT test_short_mapping();
+
+-- Likewise for a mapping whose __len__() raises
+CREATE FUNCTION test_broken_len_mapping() RETURNS jsonb
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE jsonb
+AS $$
+class C(dict):
+ def __len__(self):
+ raise ValueError('len failed')
+d = C()
+d['x'] = 1
+return d
+$$;
+
+SELECT test_broken_len_mapping();
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"
+-- A custom sequence whose length matches the tuple but whose __getitem__
+-- raises should be reported as an error, not crash the backend.
+CREATE FUNCTION composite_type_as_broken_sequence() RETURNS type_record AS $$
+class C:
+ def __len__(self):
+ return 2
+ def __getitem__(self, i):
+ raise ValueError('getitem failed')
+return C()
+$$ LANGUAGE plpython3u;
+SELECT * FROM composite_type_as_broken_sequence();
+ERROR: could not get element 0 from sequence
+DETAIL: ValueError: getitem failed
+CONTEXT: Traceback (most recent call last):
+while creating return value
+PL/Python function "composite_type_as_broken_sequence"
(3,label)
(1 row)
+-- A custom argument sequence whose length matches the plan but whose
+-- __getitem__ raises should be reported as an error, not crash the backend.
+CREATE FUNCTION plan_broken_arg_sequence() RETURNS void AS $$
+plan = plpy.prepare("select $1", ["int4"])
+class C:
+ def __len__(self):
+ return 1
+ def __getitem__(self, i):
+ raise ValueError('getitem failed')
+plpy.execute(plan, C())
+$$ LANGUAGE plpython3u;
+SELECT plan_broken_arg_sequence();
+ERROR: spiexceptions.ExternalRoutineException: could not get element 0 from sequence
+DETAIL: ValueError: getitem failed
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "plan_broken_arg_sequence", line 8, in <module>
+ plpy.execute(plan, C())
+PL/Python function "plan_broken_arg_sequence"
+-- Likewise for the type-name list passed to plpy.prepare().
+CREATE FUNCTION prepare_broken_type_sequence() RETURNS void AS $$
+class C:
+ def __len__(self):
+ return 1
+ def __getitem__(self, i):
+ raise ValueError('getitem failed')
+plpy.prepare("select $1", C())
+$$ LANGUAGE plpython3u;
+SELECT prepare_broken_type_sequence();
+ERROR: spiexceptions.ExternalRoutineException: could not get element 0 from sequence
+DETAIL: ValueError: getitem failed
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "prepare_broken_type_sequence", line 7, in <module>
+ plpy.prepare("select $1", C())
+PL/Python function "prepare_broken_type_sequence"
+-- Likewise for the argument sequence passed to plpy.cursor().
+CREATE FUNCTION cursor_broken_arg_sequence() RETURNS void AS $$
+plan = plpy.prepare("select $1", ["int4"])
+class C:
+ def __len__(self):
+ return 1
+ def __getitem__(self, i):
+ raise ValueError('getitem failed')
+plpy.cursor(plan, C())
+$$ LANGUAGE plpython3u;
+SELECT cursor_broken_arg_sequence();
+ERROR: spiexceptions.ExternalRoutineException: could not get element 0 from sequence
+DETAIL: ValueError: getitem failed
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "cursor_broken_arg_sequence", line 8, in <module>
+ plpy.cursor(plan, C())
+PL/Python function "cursor_broken_arg_sequence"
ERROR: return value of function with array return type is not a Python sequence
CONTEXT: while creating return value
PL/Python function "test_type_conversion_array_error"
+-- A custom sequence whose __getitem__ raises should be reported as an error,
+-- not crash the backend.
+CREATE FUNCTION test_type_conversion_array_getitem_fail() RETURNS int[] AS $$
+class C:
+ def __len__(self):
+ return 2
+ def __getitem__(self, i):
+ raise ValueError('getitem failed')
+return C()
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_array_getitem_fail();
+ERROR: could not get element 0 from sequence
+DETAIL: ValueError: getitem failed
+CONTEXT: Traceback (most recent call last):
+while creating return value
+PL/Python function "test_type_conversion_array_getitem_fail"
--
-- Domains over arrays
--
PyObject *elem;
elem = PySequence_GetItem(args, j);
+
+ /* PySequence_GetItem() can return NULL, with an exception set */
+ if (elem == NULL)
+ PLy_elog(ERROR, "could not get element %d from sequence", j);
+
PG_TRY();
{
bool isnull;
int32 typmod;
optr = PySequence_GetItem(list, i);
+
+ /* PySequence_GetItem() can return NULL, with an exception set */
+ if (optr == NULL)
+ PLy_elog(ERROR, "could not get element %d from sequence", i);
+
if (PyUnicode_Check(optr))
sptr = PLyUnicode_AsString(optr);
else
PyObject *elem;
elem = PySequence_GetItem(list, j);
+
+ /* PySequence_GetItem() can return NULL, with an exception set */
+ if (elem == NULL)
+ PLy_elog(ERROR, "could not get element %d from sequence", j);
+
PG_TRY();
{
bool isnull;
/* fetch the array element */
PyObject *subobj = PySequence_GetItem(obj, i);
+ /* PySequence_GetItem() can return NULL, with an exception set */
+ if (subobj == NULL)
+ PLy_elog(ERROR, "could not get element %d from sequence", i);
+
/* need PG_TRY to ensure we release the subobj's refcount */
PG_TRY();
{
PG_TRY();
{
value = PySequence_GetItem(sequence, idx);
- Assert(value);
+
+ /* PySequence_GetItem() can return NULL, with an exception set */
+ if (value == NULL)
+ PLy_elog(ERROR, "could not get element %d from sequence", idx);
values[i] = att->func(att, value, &nulls[i], false);
return [['first', 1]];
$$ LANGUAGE plpython3u;
SELECT * FROM composite_type_as_list_broken();
+
+-- A custom sequence whose length matches the tuple but whose __getitem__
+-- raises should be reported as an error, not crash the backend.
+CREATE FUNCTION composite_type_as_broken_sequence() RETURNS type_record AS $$
+class C:
+ def __len__(self):
+ return 2
+ def __getitem__(self, i):
+ raise ValueError('getitem failed')
+return C()
+$$ LANGUAGE plpython3u;
+SELECT * FROM composite_type_as_broken_sequence();
SELECT cursor_plan();
SELECT cursor_plan_wrong_args();
SELECT plan_composite_args();
+
+-- A custom argument sequence whose length matches the plan but whose
+-- __getitem__ raises should be reported as an error, not crash the backend.
+CREATE FUNCTION plan_broken_arg_sequence() RETURNS void AS $$
+plan = plpy.prepare("select $1", ["int4"])
+class C:
+ def __len__(self):
+ return 1
+ def __getitem__(self, i):
+ raise ValueError('getitem failed')
+plpy.execute(plan, C())
+$$ LANGUAGE plpython3u;
+
+SELECT plan_broken_arg_sequence();
+
+-- Likewise for the type-name list passed to plpy.prepare().
+CREATE FUNCTION prepare_broken_type_sequence() RETURNS void AS $$
+class C:
+ def __len__(self):
+ return 1
+ def __getitem__(self, i):
+ raise ValueError('getitem failed')
+plpy.prepare("select $1", C())
+$$ LANGUAGE plpython3u;
+
+SELECT prepare_broken_type_sequence();
+
+-- Likewise for the argument sequence passed to plpy.cursor().
+CREATE FUNCTION cursor_broken_arg_sequence() RETURNS void AS $$
+plan = plpy.prepare("select $1", ["int4"])
+class C:
+ def __len__(self):
+ return 1
+ def __getitem__(self, i):
+ raise ValueError('getitem failed')
+plpy.cursor(plan, C())
+$$ LANGUAGE plpython3u;
+
+SELECT cursor_broken_arg_sequence();
SELECT * FROM test_type_conversion_array_error();
+-- A custom sequence whose __getitem__ raises should be reported as an error,
+-- not crash the backend.
+CREATE FUNCTION test_type_conversion_array_getitem_fail() RETURNS int[] AS $$
+class C:
+ def __len__(self):
+ return 2
+ def __getitem__(self, i):
+ raise ValueError('getitem failed')
+return C()
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_array_getitem_fail();
+
--
-- Domains over arrays