mirror of
https://github.com/postgres/postgres.git
synced 2026-02-27 20:00:50 -05:00
PLyString_ToComposite() blithely overwrote proc->result.out.d, even though for a composite result type the other union variant proc->result.out.r is the one that should be valid. This could result in a crash if out.r had in fact been filled in (proc->result.is_rowtype == 1) and then somebody later attempted to use that data; as per bug #13579 from Paweł Michalak. Just to add insult to injury, it didn't work for RECORD results anyway, because record_in() would refuse the case. Fix by doing the I/O function lookup in a local PLyTypeInfo variable, as we were doing already in PLyObject_ToComposite(). This is not a great technique because any fn_extra data allocated by the input function will be leaked permanently (thanks to using TopMemoryContext as fn_mcxt). But that's a pre-existing issue that is much less serious than a crash, so leave it to be fixed separately. This bug would be a potential security issue, except that plpython is only available to superusers and the crash requires coding the function in a way that didn't work before today's patches. Add regression test cases covering all the supported methods of converting composite results. Back-patch to 9.1 where the faulty coding was introduced.
567 lines
12 KiB
Text
567 lines
12 KiB
Text
CREATE FUNCTION multiout_simple(OUT i integer, OUT j integer) AS $$
|
|
return (1, 2)
|
|
$$ LANGUAGE plpythonu;
|
|
SELECT multiout_simple();
|
|
multiout_simple
|
|
-----------------
|
|
(1,2)
|
|
(1 row)
|
|
|
|
SELECT * FROM multiout_simple();
|
|
i | j
|
|
---+---
|
|
1 | 2
|
|
(1 row)
|
|
|
|
SELECT i, j + 2 FROM multiout_simple();
|
|
i | ?column?
|
|
---+----------
|
|
1 | 4
|
|
(1 row)
|
|
|
|
SELECT (multiout_simple()).j + 3;
|
|
?column?
|
|
----------
|
|
5
|
|
(1 row)
|
|
|
|
CREATE FUNCTION multiout_simple_setof(n integer = 1, OUT integer, OUT integer) RETURNS SETOF record AS $$
|
|
return [(1, 2)] * n
|
|
$$ LANGUAGE plpythonu;
|
|
SELECT multiout_simple_setof();
|
|
multiout_simple_setof
|
|
-----------------------
|
|
(1,2)
|
|
(1 row)
|
|
|
|
SELECT * FROM multiout_simple_setof();
|
|
column1 | column2
|
|
---------+---------
|
|
1 | 2
|
|
(1 row)
|
|
|
|
SELECT * FROM multiout_simple_setof(3);
|
|
column1 | column2
|
|
---------+---------
|
|
1 | 2
|
|
1 | 2
|
|
1 | 2
|
|
(3 rows)
|
|
|
|
CREATE FUNCTION multiout_record_as(typ text,
|
|
first text, OUT first text,
|
|
second integer, OUT second integer,
|
|
retnull boolean) RETURNS record AS $$
|
|
if retnull:
|
|
return None
|
|
if typ == 'dict':
|
|
return { 'first': first, 'second': second, 'additionalfield': 'must not cause trouble' }
|
|
elif typ == 'tuple':
|
|
return ( first, second )
|
|
elif typ == 'list':
|
|
return [ first, second ]
|
|
elif typ == 'obj':
|
|
class type_record: pass
|
|
type_record.first = first
|
|
type_record.second = second
|
|
return type_record
|
|
elif typ == 'str':
|
|
return "('%s',%r)" % (first, second)
|
|
$$ LANGUAGE plpythonu;
|
|
SELECT * FROM multiout_record_as('dict', 'foo', 1, 'f');
|
|
first | second
|
|
-------+--------
|
|
foo | 1
|
|
(1 row)
|
|
|
|
SELECT multiout_record_as('dict', 'foo', 1, 'f');
|
|
multiout_record_as
|
|
--------------------
|
|
(foo,1)
|
|
(1 row)
|
|
|
|
SELECT * FROM multiout_record_as('dict', null, null, false);
|
|
first | second
|
|
-------+--------
|
|
|
|
|
(1 row)
|
|
|
|
SELECT * FROM multiout_record_as('dict', 'one', null, false);
|
|
first | second
|
|
-------+--------
|
|
one |
|
|
(1 row)
|
|
|
|
SELECT * FROM multiout_record_as('dict', null, 2, false);
|
|
first | second
|
|
-------+--------
|
|
| 2
|
|
(1 row)
|
|
|
|
SELECT * FROM multiout_record_as('dict', 'three', 3, false);
|
|
first | second
|
|
-------+--------
|
|
three | 3
|
|
(1 row)
|
|
|
|
SELECT * FROM multiout_record_as('dict', null, null, true);
|
|
first | second
|
|
-------+--------
|
|
|
|
|
(1 row)
|
|
|
|
SELECT * FROM multiout_record_as('tuple', null, null, false);
|
|
first | second
|
|
-------+--------
|
|
|
|
|
(1 row)
|
|
|
|
SELECT * FROM multiout_record_as('tuple', 'one', null, false);
|
|
first | second
|
|
-------+--------
|
|
one |
|
|
(1 row)
|
|
|
|
SELECT * FROM multiout_record_as('tuple', null, 2, false);
|
|
first | second
|
|
-------+--------
|
|
| 2
|
|
(1 row)
|
|
|
|
SELECT * FROM multiout_record_as('tuple', 'three', 3, false);
|
|
first | second
|
|
-------+--------
|
|
three | 3
|
|
(1 row)
|
|
|
|
SELECT * FROM multiout_record_as('tuple', null, null, true);
|
|
first | second
|
|
-------+--------
|
|
|
|
|
(1 row)
|
|
|
|
SELECT * FROM multiout_record_as('list', null, null, false);
|
|
first | second
|
|
-------+--------
|
|
|
|
|
(1 row)
|
|
|
|
SELECT * FROM multiout_record_as('list', 'one', null, false);
|
|
first | second
|
|
-------+--------
|
|
one |
|
|
(1 row)
|
|
|
|
SELECT * FROM multiout_record_as('list', null, 2, false);
|
|
first | second
|
|
-------+--------
|
|
| 2
|
|
(1 row)
|
|
|
|
SELECT * FROM multiout_record_as('list', 'three', 3, false);
|
|
first | second
|
|
-------+--------
|
|
three | 3
|
|
(1 row)
|
|
|
|
SELECT * FROM multiout_record_as('list', null, null, true);
|
|
first | second
|
|
-------+--------
|
|
|
|
|
(1 row)
|
|
|
|
SELECT * FROM multiout_record_as('obj', null, null, false);
|
|
first | second
|
|
-------+--------
|
|
|
|
|
(1 row)
|
|
|
|
SELECT * FROM multiout_record_as('obj', 'one', null, false);
|
|
first | second
|
|
-------+--------
|
|
one |
|
|
(1 row)
|
|
|
|
SELECT * FROM multiout_record_as('obj', null, 2, false);
|
|
first | second
|
|
-------+--------
|
|
| 2
|
|
(1 row)
|
|
|
|
SELECT * FROM multiout_record_as('obj', 'three', 3, false);
|
|
first | second
|
|
-------+--------
|
|
three | 3
|
|
(1 row)
|
|
|
|
SELECT * FROM multiout_record_as('obj', null, null, true);
|
|
first | second
|
|
-------+--------
|
|
|
|
|
(1 row)
|
|
|
|
SELECT * FROM multiout_record_as('str', 'one', 1, false);
|
|
first | second
|
|
-------+--------
|
|
'one' | 1
|
|
(1 row)
|
|
|
|
SELECT * FROM multiout_record_as('str', 'one', 2, false);
|
|
first | second
|
|
-------+--------
|
|
'one' | 2
|
|
(1 row)
|
|
|
|
SELECT *, s IS NULL AS snull FROM multiout_record_as('tuple', 'xxx', NULL, 'f') AS f(f, s);
|
|
f | s | snull
|
|
-----+---+-------
|
|
xxx | | t
|
|
(1 row)
|
|
|
|
SELECT *, f IS NULL AS fnull, s IS NULL AS snull FROM multiout_record_as('tuple', 'xxx', 1, 't') AS f(f, s);
|
|
f | s | fnull | snull
|
|
---+---+-------+-------
|
|
| | t | t
|
|
(1 row)
|
|
|
|
SELECT * FROM multiout_record_as('obj', NULL, 10, 'f');
|
|
first | second
|
|
-------+--------
|
|
| 10
|
|
(1 row)
|
|
|
|
CREATE FUNCTION multiout_setof(n integer,
|
|
OUT power_of_2 integer,
|
|
OUT length integer) RETURNS SETOF record AS $$
|
|
for i in range(n):
|
|
power = 2 ** i
|
|
length = plpy.execute("select length('%d')" % power)[0]['length']
|
|
yield power, length
|
|
$$ LANGUAGE plpythonu;
|
|
SELECT * FROM multiout_setof(3);
|
|
power_of_2 | length
|
|
------------+--------
|
|
1 | 1
|
|
2 | 1
|
|
4 | 1
|
|
(3 rows)
|
|
|
|
SELECT multiout_setof(5);
|
|
multiout_setof
|
|
----------------
|
|
(1,1)
|
|
(2,1)
|
|
(4,1)
|
|
(8,1)
|
|
(16,2)
|
|
(5 rows)
|
|
|
|
CREATE FUNCTION multiout_return_table() RETURNS TABLE (x integer, y text) AS $$
|
|
return [{'x': 4, 'y' :'four'},
|
|
{'x': 7, 'y' :'seven'},
|
|
{'x': 0, 'y' :'zero'}]
|
|
$$ LANGUAGE plpythonu;
|
|
SELECT * FROM multiout_return_table();
|
|
x | y
|
|
---+-------
|
|
4 | four
|
|
7 | seven
|
|
0 | zero
|
|
(3 rows)
|
|
|
|
CREATE FUNCTION multiout_array(OUT integer[], OUT text) RETURNS SETOF record AS $$
|
|
yield [[1], 'a']
|
|
yield [[1,2], 'b']
|
|
yield [[1,2,3], None]
|
|
$$ LANGUAGE plpythonu;
|
|
SELECT * FROM multiout_array();
|
|
column1 | column2
|
|
---------+---------
|
|
{1} | a
|
|
{1,2} | b
|
|
{1,2,3} |
|
|
(3 rows)
|
|
|
|
CREATE FUNCTION singleout_composite(OUT type_record) AS $$
|
|
return {'first': 1, 'second': 2}
|
|
$$ LANGUAGE plpythonu;
|
|
CREATE FUNCTION multiout_composite(OUT type_record) RETURNS SETOF type_record AS $$
|
|
return [{'first': 1, 'second': 2},
|
|
{'first': 3, 'second': 4 }]
|
|
$$ LANGUAGE plpythonu;
|
|
SELECT * FROM singleout_composite();
|
|
first | second
|
|
-------+--------
|
|
1 | 2
|
|
(1 row)
|
|
|
|
SELECT * FROM multiout_composite();
|
|
first | second
|
|
-------+--------
|
|
1 | 2
|
|
3 | 4
|
|
(2 rows)
|
|
|
|
-- composite OUT parameters in functions returning RECORD not supported yet
|
|
CREATE FUNCTION multiout_composite(INOUT n integer, OUT type_record) AS $$
|
|
return (n, (n * 2, n * 3))
|
|
$$ LANGUAGE plpythonu;
|
|
CREATE FUNCTION multiout_table_type_setof(typ text, returnnull boolean, INOUT n integer, OUT table_record) RETURNS SETOF record AS $$
|
|
if returnnull:
|
|
d = None
|
|
elif typ == 'dict':
|
|
d = {'first': n * 2, 'second': n * 3, 'extra': 'not important'}
|
|
elif typ == 'tuple':
|
|
d = (n * 2, n * 3)
|
|
elif typ == 'list':
|
|
d = [ n * 2, n * 3 ]
|
|
elif typ == 'obj':
|
|
class d: pass
|
|
d.first = n * 2
|
|
d.second = n * 3
|
|
elif typ == 'str':
|
|
d = "(%r,%r)" % (n * 2, n * 3)
|
|
for i in range(n):
|
|
yield (i, d)
|
|
$$ LANGUAGE plpythonu;
|
|
SELECT * FROM multiout_composite(2);
|
|
n | column2
|
|
---+---------
|
|
2 | (4,6)
|
|
(1 row)
|
|
|
|
SELECT * FROM multiout_table_type_setof('dict', 'f', 3);
|
|
n | column2
|
|
---+---------
|
|
0 | (6,9)
|
|
1 | (6,9)
|
|
2 | (6,9)
|
|
(3 rows)
|
|
|
|
SELECT * FROM multiout_table_type_setof('dict', 'f', 7);
|
|
n | column2
|
|
---+---------
|
|
0 | (14,21)
|
|
1 | (14,21)
|
|
2 | (14,21)
|
|
3 | (14,21)
|
|
4 | (14,21)
|
|
5 | (14,21)
|
|
6 | (14,21)
|
|
(7 rows)
|
|
|
|
SELECT * FROM multiout_table_type_setof('tuple', 'f', 2);
|
|
n | column2
|
|
---+---------
|
|
0 | (4,6)
|
|
1 | (4,6)
|
|
(2 rows)
|
|
|
|
SELECT * FROM multiout_table_type_setof('tuple', 'f', 3);
|
|
n | column2
|
|
---+---------
|
|
0 | (6,9)
|
|
1 | (6,9)
|
|
2 | (6,9)
|
|
(3 rows)
|
|
|
|
SELECT * FROM multiout_table_type_setof('list', 'f', 2);
|
|
n | column2
|
|
---+---------
|
|
0 | (4,6)
|
|
1 | (4,6)
|
|
(2 rows)
|
|
|
|
SELECT * FROM multiout_table_type_setof('list', 'f', 3);
|
|
n | column2
|
|
---+---------
|
|
0 | (6,9)
|
|
1 | (6,9)
|
|
2 | (6,9)
|
|
(3 rows)
|
|
|
|
SELECT * FROM multiout_table_type_setof('obj', 'f', 4);
|
|
n | column2
|
|
---+---------
|
|
0 | (8,12)
|
|
1 | (8,12)
|
|
2 | (8,12)
|
|
3 | (8,12)
|
|
(4 rows)
|
|
|
|
SELECT * FROM multiout_table_type_setof('obj', 'f', 5);
|
|
n | column2
|
|
---+---------
|
|
0 | (10,15)
|
|
1 | (10,15)
|
|
2 | (10,15)
|
|
3 | (10,15)
|
|
4 | (10,15)
|
|
(5 rows)
|
|
|
|
SELECT * FROM multiout_table_type_setof('str', 'f', 6);
|
|
n | column2
|
|
---+---------
|
|
0 | (12,18)
|
|
1 | (12,18)
|
|
2 | (12,18)
|
|
3 | (12,18)
|
|
4 | (12,18)
|
|
5 | (12,18)
|
|
(6 rows)
|
|
|
|
SELECT * FROM multiout_table_type_setof('str', 'f', 7);
|
|
n | column2
|
|
---+---------
|
|
0 | (14,21)
|
|
1 | (14,21)
|
|
2 | (14,21)
|
|
3 | (14,21)
|
|
4 | (14,21)
|
|
5 | (14,21)
|
|
6 | (14,21)
|
|
(7 rows)
|
|
|
|
SELECT * FROM multiout_table_type_setof('dict', 't', 3);
|
|
n | column2
|
|
---+---------
|
|
0 |
|
|
1 |
|
|
2 |
|
|
(3 rows)
|
|
|
|
-- check what happens if a type changes under us
|
|
CREATE TABLE changing (
|
|
i integer,
|
|
j integer
|
|
);
|
|
CREATE FUNCTION changing_test(OUT n integer, OUT changing) RETURNS SETOF record AS $$
|
|
return [(1, {'i': 1, 'j': 2}),
|
|
(1, (3, 4))]
|
|
$$ LANGUAGE plpythonu;
|
|
SELECT * FROM changing_test();
|
|
n | column2
|
|
---+---------
|
|
1 | (1,2)
|
|
1 | (3,4)
|
|
(2 rows)
|
|
|
|
ALTER TABLE changing DROP COLUMN j;
|
|
SELECT * FROM changing_test();
|
|
ERROR: length of returned sequence did not match number of columns in row
|
|
CONTEXT: while creating return value
|
|
PL/Python function "changing_test"
|
|
SELECT * FROM changing_test();
|
|
ERROR: length of returned sequence did not match number of columns in row
|
|
CONTEXT: while creating return value
|
|
PL/Python function "changing_test"
|
|
ALTER TABLE changing ADD COLUMN j integer;
|
|
SELECT * FROM changing_test();
|
|
n | column2
|
|
---+---------
|
|
1 | (1,2)
|
|
1 | (3,4)
|
|
(2 rows)
|
|
|
|
-- tables of composite types (not yet implemented)
|
|
CREATE FUNCTION composite_types_table(OUT tab table_record[], OUT typ type_record[] ) RETURNS SETOF record AS $$
|
|
yield {'tab': [['first', 1], ['second', 2]],
|
|
'typ': [{'first': 'third', 'second': 3},
|
|
{'first': 'fourth', 'second': 4}]}
|
|
yield {'tab': [['first', 1], ['second', 2]],
|
|
'typ': [{'first': 'third', 'second': 3},
|
|
{'first': 'fourth', 'second': 4}]}
|
|
yield {'tab': [['first', 1], ['second', 2]],
|
|
'typ': [{'first': 'third', 'second': 3},
|
|
{'first': 'fourth', 'second': 4}]}
|
|
$$ LANGUAGE plpythonu;
|
|
SELECT * FROM composite_types_table();
|
|
ERROR: PL/Python functions cannot return type table_record[]
|
|
DETAIL: PL/Python does not support conversion to arrays of row types.
|
|
CONTEXT: PL/Python function "composite_types_table"
|
|
-- check what happens if the output record descriptor changes
|
|
CREATE FUNCTION return_record(t text) RETURNS record AS $$
|
|
return {'t': t, 'val': 10}
|
|
$$ LANGUAGE plpythonu;
|
|
SELECT * FROM return_record('abc') AS r(t text, val integer);
|
|
t | val
|
|
-----+-----
|
|
abc | 10
|
|
(1 row)
|
|
|
|
SELECT * FROM return_record('abc') AS r(t text, val bigint);
|
|
t | val
|
|
-----+-----
|
|
abc | 10
|
|
(1 row)
|
|
|
|
SELECT * FROM return_record('abc') AS r(t text, val integer);
|
|
t | val
|
|
-----+-----
|
|
abc | 10
|
|
(1 row)
|
|
|
|
SELECT * FROM return_record('abc') AS r(t varchar(30), val integer);
|
|
t | val
|
|
-----+-----
|
|
abc | 10
|
|
(1 row)
|
|
|
|
SELECT * FROM return_record('abc') AS r(t varchar(100), val integer);
|
|
t | val
|
|
-----+-----
|
|
abc | 10
|
|
(1 row)
|
|
|
|
SELECT * FROM return_record('999') AS r(val text, t integer);
|
|
val | t
|
|
-----+-----
|
|
10 | 999
|
|
(1 row)
|
|
|
|
CREATE FUNCTION return_record_2(t text) RETURNS record AS $$
|
|
return {'v1':1,'v2':2,t:3}
|
|
$$ LANGUAGE plpythonu;
|
|
SELECT * FROM return_record_2('v3') AS (v3 int, v2 int, v1 int);
|
|
v3 | v2 | v1
|
|
----+----+----
|
|
3 | 2 | 1
|
|
(1 row)
|
|
|
|
SELECT * FROM return_record_2('v3') AS (v2 int, v3 int, v1 int);
|
|
v2 | v3 | v1
|
|
----+----+----
|
|
2 | 3 | 1
|
|
(1 row)
|
|
|
|
SELECT * FROM return_record_2('v4') AS (v1 int, v4 int, v2 int);
|
|
v1 | v4 | v2
|
|
----+----+----
|
|
1 | 3 | 2
|
|
(1 row)
|
|
|
|
SELECT * FROM return_record_2('v4') AS (v1 int, v4 int, v2 int);
|
|
v1 | v4 | v2
|
|
----+----+----
|
|
1 | 3 | 2
|
|
(1 row)
|
|
|
|
-- error
|
|
SELECT * FROM return_record_2('v4') AS (v1 int, v3 int, v2 int);
|
|
ERROR: key "v3" not found in mapping
|
|
HINT: To return null in a column, add the value None to the mapping with the key named after the column.
|
|
CONTEXT: while creating return value
|
|
PL/Python function "return_record_2"
|
|
-- works
|
|
SELECT * FROM return_record_2('v3') AS (v1 int, v3 int, v2 int);
|
|
v1 | v3 | v2
|
|
----+----+----
|
|
1 | 3 | 2
|
|
(1 row)
|
|
|
|
SELECT * FROM return_record_2('v3') AS (v1 int, v2 int, v3 int);
|
|
v1 | v2 | v3
|
|
----+----+----
|
|
1 | 2 | 3
|
|
(1 row)
|
|
|