CREATE FUNCTION multiout_simple(OUT i integer, OUT j integer) AS $$ return (1, 2) $$ LANGUAGE plpython3u; 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 plpython3u; 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 plpython3u; 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 plpython3u; 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 plpython3u; 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 plpython3u; 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 plpython3u; CREATE FUNCTION multiout_composite(OUT type_record) RETURNS SETOF type_record AS $$ return [{'first': 1, 'second': 2}, {'first': 3, 'second': 4 }] $$ LANGUAGE plpython3u; 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 plpython3u; 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 plpython3u; 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 plpython3u; 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 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 plpython3u; SELECT * FROM composite_types_table(); tab | typ ----------------------------+---------------------------- {"(first,1)","(second,2)"} | {"(third,3)","(fourth,4)"} {"(first,1)","(second,2)"} | {"(third,3)","(fourth,4)"} {"(first,1)","(second,2)"} | {"(third,3)","(fourth,4)"} (3 rows) -- check what happens if the output record descriptor changes CREATE FUNCTION return_record(t text) RETURNS record AS $$ return {'t': t, 'val': 10} $$ LANGUAGE plpython3u; 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 plpython3u; 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) -- multi-dimensional array of composite types. CREATE FUNCTION composite_type_as_list() RETURNS type_record[] AS $$ return [[('first', 1), ('second', 1)], [('first', 2), ('second', 2)], [('first', 3), ('second', 3)]]; $$ LANGUAGE plpython3u; SELECT * FROM composite_type_as_list(); composite_type_as_list ------------------------------------------------------------------------------------ {{"(first,1)","(second,1)"},{"(first,2)","(second,2)"},{"(first,3)","(second,3)"}} (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 plpython3u; 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"