]> 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:44:35 +0000 (11:44 +0900)
committerRichard Guo <rguo@postgresql.org>
Mon, 29 Jun 2026 02:44:35 +0000 (11:44 +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

16 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/expected/plpython_types_3.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 57d83fa2db5b4a05ddecfa5d42f7221d918b6530..b1837715ce502bc62890fb474c9c4d32f889d234 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 plpythonu
index 2d144043abdd4980da4db9cff0b087d7d575e69a..db67bb3685aec74fa4882d083755a7946c715efb 100644 (file)
@@ -145,7 +145,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();
        {
@@ -162,6 +171,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 1aa4416512ae2ae567879d2afaa4de56f9af9430..8548b7dbe037b0506bfd8a098415566f0a27fad9 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 plpythonu
index b491fe9cc68d7ca0b07b18dd3d7d50764685675c..f9bde492095a233815aaa41c553721a37cc26e5d 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 8d95f09f23d5eefdce83f7ffd34b59ec42f090d6..1c7097a40758ef5f7ab87f25e7a719c45e6f76ab 100644 (file)
@@ -277,7 +277,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();
        {
@@ -289,8 +294,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)
@@ -342,7 +354,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 2ee1bca0a980076f1232340f0ae6fa47e9bbefa7..78578fa3d8a3e77f6c03df4f4682518d7c8340da 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 b9111210d54a75c4ef87ed8b1cf52a8c4cba9978..a860dd24a572b2238b8e1023976bbf59f0ec933c 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 a09df68c7d13a1216914f426b6eccf9a47f17846..eeed3d25a7b412fb8cc2f9cb35229508fa463709 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 d1776365c3556dbf0b585143294101555ad1e22b..6ef55a4efcc93ead5fdf1c724f59e622b2231585 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 6fadd5462d110a7afb4a8928baafa05ae5f8f588..ce362cbbc3c2a33a335ff7f49123450a013ddae3 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 d783819e82a8bd6a946b80a2c383d994ed9cb1c1..06229fcd76360c79b2034b3c59f5108678945f23 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();
                        {
                                bool            isnull;
index 53af9c05efaa659b46904a243cf1b5ef618886b0..594f0a67ab7b252d2739d162d2b32c51da932806 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 (PyString_Check(optr))
                                sptr = PyString_AsString(optr);
                        else if (PyUnicode_Check(optr))
@@ -254,6 +259,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();
                        {
                                bool            isnull;
index 707fe416b2f4d431cbf5313cab0e1530aaf441bb..35be8bfd8abb16d922d209ab60efaaf59d5d5e1c 100644 (file)
@@ -1214,6 +1214,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();
                {
@@ -1460,7 +1464,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 844b761bf11c85906eacff44a098bc336275b444..d7ac6463c1c865e946c520e7835ed5ea0c195881 100644 (file)
@@ -233,3 +233,15 @@ CREATE FUNCTION composite_type_as_list_broken()  RETURNS type_record[] AS $$
   return [['first', 1]];
 $$ LANGUAGE plpythonu;
 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 dd77833ed56b5fd33cc4a095278ec2a00e578f47..93d2b0d90cdb94e1e84b202b560523ed6d102838 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 a8ceb66111392e6d340d422fdf39325b5d78b93d..b64fac4d17f82fff5a71311f1a70c837bffb1995 100644 (file)
@@ -417,6 +417,19 @@ $$ LANGUAGE plpythonu;
 
 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