]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
plpython: Fix NULL pointer dereferences for broken sequence and mapping objects
authorRichard Guo <rguo@postgresql.org>
Mon, 29 Jun 2026 02:38:39 +0000 (11:38 +0900)
committerRichard Guo <rguo@postgresql.org>
Mon, 29 Jun 2026 02:40:49 +0000 (11:40 +0900)
PL/Python and its hstore and jsonb transforms build SQL values from
Python containers by calling Python C API functions that can return
NULL, and in several places the result was used without first checking
it.

On the sequence side, PySequence_GetItem() is used when converting a
returned sequence into a SQL array or composite value, when reading
the argument list passed to plpy.execute() or plpy.cursor(), and when
reading the list of type names given to plpy.prepare().  On the
mapping side, the hstore and jsonb transforms call PyMapping_Size()
and PyMapping_Items() and then index the result with PyList_GetItem()
and PyTuple_GetItem().

All of these return NULL (or -1), with a Python exception set, for a
broken object: for example one whose __getitem__() or items() raises,
or which reports a length that disagrees with what it actually yields.
The unchecked result was then dereferenced, crashing the backend.

Fix this by checking the result of each call and reporting a regular
error if it failed, so that the underlying Python exception is
surfaced instead of taking down the session.

Author: Richard Guo <guofenglinux@gmail.com>
Reviewed-by: Ayush Tiwari <ayushtiwari.slg01@gmail.com>
Discussion: https://postgr.es/m/CAMbWs49BKM9wP6m8bCXEpHwQKp7usvOGV6Jf=J7FYr_BCpxLqg@mail.gmail.com
Backpatch-through: 14

15 files changed:
contrib/hstore_plpython/expected/hstore_plpython.out
contrib/hstore_plpython/hstore_plpython.c
contrib/hstore_plpython/sql/hstore_plpython.sql
contrib/jsonb_plpython/expected/jsonb_plpython.out
contrib/jsonb_plpython/jsonb_plpython.c
contrib/jsonb_plpython/sql/jsonb_plpython.sql
src/pl/plpython/expected/plpython_composite.out
src/pl/plpython/expected/plpython_spi.out
src/pl/plpython/expected/plpython_types.out
src/pl/plpython/plpy_cursorobject.c
src/pl/plpython/plpy_spi.c
src/pl/plpython/plpy_typeio.c
src/pl/plpython/sql/plpython_composite.sql
src/pl/plpython/sql/plpython_spi.sql
src/pl/plpython/sql/plpython_types.sql

index 5fb56a2f65d10fbbdefdb531c3d8c686e26e913c..5f8315e84dd400f04ac2c42d9bfe31cb50c946db 100644 (file)
@@ -43,6 +43,71 @@ SELECT test1bad();
 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
index 8dea01cb6d2937b2d9ed55f802df4303e7d5f21a..eae249dd33535d06787e43604366b0c6b8778920 100644 (file)
@@ -139,7 +139,16 @@ plpython_to_hstore(PG_FUNCTION_ARGS)
                                 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();
        {
@@ -156,6 +165,13 @@ plpython_to_hstore(PG_FUNCTION_ARGS)
                        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);
 
index ebd61e6c467ac1921f0c9dae47777e7989df604f..a2b2046380f1a21fc34a373e8821d8f28ca0c851 100644 (file)
@@ -38,6 +38,71 @@ $$;
 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
index cac963de69c9f363a675fedc1a82d26574005134..8d3f53288091323d599fc40d2dfe18873a5425c5 100644 (file)
@@ -304,3 +304,92 @@ SELECT test_dict1();
  {"": 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"
index 80e2dfde46ddc80d79fdfe98351e48ebd4e4cb1f..3e79eae7614c36f561c32831b3c2d08f4d4bafd8 100644 (file)
@@ -270,7 +270,12 @@ PLyMapping_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state)
        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();
        {
@@ -282,8 +287,15 @@ PLyMapping_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state)
                {
                        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)
@@ -335,7 +347,10 @@ PLySequence_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state)
                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);
index 29dc33279a092f5f866030565ccdcab62e60eac1..fd8485c89c124d20b0ac51a26b0d5b4b1e1737ee 100644 (file)
@@ -181,3 +181,80 @@ return x
 $$;
 
 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();
index 674af93ddcfe07209865443d112f43f5b11f3d65..ffce7fc1be75b95a25ecd83eba68fe474ab1702d 100644 (file)
@@ -606,3 +606,19 @@ 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"
+-- 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"
index 8853e2540d3c2b92774e8d31d99d3e5354068817..e40565fd63caa4a4a5b4476282e3c3830f2e190c 100644 (file)
@@ -464,3 +464,54 @@ SELECT plan_composite_args();
  (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"
index 8a680e15c14d02efc8ff017a5ae9afe19449ecb1..0cb3d6ea8c6886430949df5586e9d7cecb074c8f 100644 (file)
@@ -796,6 +796,22 @@ SELECT * FROM test_type_conversion_array_error();
 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
 --
index 551dea75b5ecbe9cbd5d11a2d6602ad5e2f35337..f88328d0f9675c54ba97791bbd83e2d6215bed65 100644 (file)
@@ -231,6 +231,11 @@ PLy_cursor_plan(PyObject *ob, PyObject *args)
                        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(2);
                        {
                                bool            isnull;
index d3ac35d66ae97fd38a2eb294fb80c7796d0c6b4c..3bb597c37dfe3adad7f28be48fb0811d71d8fb73 100644 (file)
@@ -90,6 +90,11 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
                        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
@@ -252,6 +257,11 @@ PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit)
                        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(2);
                        {
                                bool            isnull;
index db14c5f8dae7f1d82349649abcc70da9175a2a3f..f718d74482fca018a254c6d5361f5d2ad8a5d72e 100644 (file)
@@ -1206,6 +1206,10 @@ PLySequence_ToArray_recurse(PyObject *obj, ArrayBuildState **astatep,
                /* 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();
                {
@@ -1452,7 +1456,10 @@ PLySequence_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *sequence)
                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);
 
index 1bb9b83b7191eb3d9f967821d0c0b9d311b9eff6..b401b3f2f6b93c24ade5040bf23861b6f0b06f9d 100644 (file)
@@ -233,3 +233,15 @@ CREATE FUNCTION composite_type_as_list_broken()  RETURNS type_record[] AS $$
   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();
index fcd113acaa35cb5135ecf658b07224f3636f1c3b..3ae927831b2cf8811252dd21a8969988961c67ac 100644 (file)
@@ -320,3 +320,42 @@ SELECT cursor_fetch_next_empty();
 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();
index 0985a9cca2f44f9629565dd341755b7eda997bd3..31549c7f4f1314a963dcd4d3a3359e46d0d99387 100644 (file)
@@ -417,6 +417,19 @@ $$ LANGUAGE plpython3u;
 
 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