| 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 |
| 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(); |
| 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 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) |
| |
| -- recursion with a different inner result type didn't use to work |
| CREATE FUNCTION return_record_3(t text) RETURNS record AS $$ |
| if t == "text": |
| plpy.execute("SELECT * FROM return_record_3('int') AS (a int)"); |
| return { "a": "x" } |
| elif t == "int": |
| return { "a": 1 } |
| $$ LANGUAGE plpythonu; |
| SELECT * FROM return_record_3('text') AS (a text); |
| a |
| --- |
| x |
| (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 plpythonu; |
| 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 plpythonu; |
| 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" |