diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml new file mode 100644 index 015bbad..2bd9bcb *** a/doc/src/sgml/plpython.sgml --- b/doc/src/sgml/plpython.sgml *************** $$ LANGUAGE plpythonu; *** 1341,1360 **** Utility Functions The plpy module also provides the functions ! plpy.debug(msg), ! plpy.log(msg), ! plpy.info(msg), ! plpy.notice(msg), ! plpy.warning(msg), ! plpy.error(msg), and ! plpy.fatal(msg).elogin PL/Python plpy.error and plpy.fatal actually raise a Python exception which, if uncaught, propagates out to the calling query, causing the current transaction or subtransaction to be aborted. raise plpy.Error(msg) and raise plpy.Fatal(msg) are ! equivalent to calling plpy.error and plpy.fatal, respectively. The other functions only generate messages of different --- 1341,1360 ---- Utility Functions The plpy module also provides the functions ! plpy.debug(exception_params), ! plpy.log(exception_params), ! plpy.info(exception_params), ! plpy.notice(exception_params), ! plpy.warning(exception_params), ! plpy.error(exception_params), and ! plpy.fatal(exception_params).elogin PL/Python plpy.error and plpy.fatal actually raise a Python exception which, if uncaught, propagates out to the calling query, causing the current transaction or subtransaction to be aborted. raise plpy.Error(msg) and raise plpy.Fatal(msg) are ! partial equivalent to calling plpy.error and plpy.fatal, respectively. The other functions only generate messages of different *************** $$ LANGUAGE plpythonu; *** 1367,1372 **** --- 1367,1397 ---- + + The exception_params are + [ message [, detail [, hint [, sqlstate [, schema [, table [, column [, datatype [, constraint ]]]]]]]]]. + These parameters kan be entered as keyword parameters. + The message, detail, hint + are automaticly casted to string, other should be string. + + + CREATE FUNCTION raise_custom_exception() RETURNS void AS $$ + plpy.error("custom exception message", "some info about exception", "hint for users") + $$ LANGUAGE plpythonu; + + postgres=# select raise_custom_exception(); + ERROR: XX000: plpy.Error: custom exception message + DETAIL: some info about exception + HINT: hint for users + CONTEXT: Traceback (most recent call last): + PL/Python function "raise_custom_exception", line 2, in <module> + plpy.error("custom exception message", "some info about exception", "hint for users") + PL/Python function "raise_custom_exception" + LOCATION: PLy_elog, plpy_elog.c:132 + + + + Another set of utility functions are plpy.quote_literal(string), plpy.quote_nullable(string), and diff --git a/src/pl/plpython/expected/plpython_test.out b/src/pl/plpython/expected/plpython_test.out new file mode 100644 index f8270a7..ce0b3fc *** a/src/pl/plpython/expected/plpython_test.out --- b/src/pl/plpython/expected/plpython_test.out *************** select module_contents(); *** 50,55 **** --- 50,96 ---- CREATE FUNCTION elog_test() RETURNS void AS $$ + plpy.debug('debug','some detail') + plpy.log('log','some detail') + plpy.info('info','some detail') + plpy.info() + plpy.info('the question', 42); + plpy.info('This is message text.', + detail = 'This is detail text', + hint = 'This is hint text.', + sqlstate = 'XX000', + schema = 'any info about schema', + table = 'any info about table', + column = 'any info about column', + datatype = 'any info about datatype', + constraint = 'any info about constraint') + plpy.notice('notice','some detail') + plpy.warning('warning','some detail') + plpy.error('stop on error', 'some detail','some hint') + $$ LANGUAGE plpythonu; + SELECT elog_test(); + INFO: info + DETAIL: some detail + INFO: missing error text + INFO: the question + DETAIL: 42 + INFO: This is message text. + DETAIL: This is detail text + HINT: This is hint text. + NOTICE: notice + DETAIL: some detail + WARNING: warning + DETAIL: some detail + ERROR: plpy.Error: stop on error + DETAIL: some detail + HINT: some hint + CONTEXT: Traceback (most recent call last): + PL/Python function "elog_test", line 18, in + plpy.error('stop on error', 'some detail','some hint') + PL/Python function "elog_test" + set plpythonu.legacy_custom_exception=true; + CREATE FUNCTION elog_test_legacy() RETURNS void + AS $$ plpy.debug('debug') plpy.log('log') plpy.info('info') *************** plpy.notice('notice') *** 60,66 **** plpy.warning('warning') plpy.error('error') $$ LANGUAGE plpythonu; ! SELECT elog_test(); INFO: info INFO: 37 INFO: () --- 101,107 ---- plpy.warning('warning') plpy.error('error') $$ LANGUAGE plpythonu; ! SELECT elog_test_legacy(); INFO: info INFO: 37 INFO: () *************** NOTICE: notice *** 69,74 **** WARNING: warning ERROR: plpy.Error: error CONTEXT: Traceback (most recent call last): ! PL/Python function "elog_test", line 10, in plpy.error('error') PL/Python function "elog_test" --- 110,127 ---- WARNING: warning ERROR: plpy.Error: error CONTEXT: Traceback (most recent call last): ! PL/Python function "elog_test_legacy", line 10, in plpy.error('error') + PL/Python function "elog_test_legacy" + SELECT elog_test(); + INFO: ('info', 'some detail') + INFO: () + INFO: ('the question', 42) + INFO: This is message text. + NOTICE: ('notice', 'some detail') + WARNING: ('warning', 'some detail') + ERROR: plpy.Error: ('stop on error', 'some detail', 'some hint') + CONTEXT: Traceback (most recent call last): + PL/Python function "elog_test", line 18, in + plpy.error('stop on error', 'some detail','some hint') PL/Python function "elog_test" diff --git a/src/pl/plpython/expected/plpython_types.out b/src/pl/plpython/expected/plpython_types.out new file mode 100644 index f0b6abd..24d8f51 *** a/src/pl/plpython/expected/plpython_types.out --- b/src/pl/plpython/expected/plpython_types.out *************** *** 5,11 **** -- Base/common types -- CREATE FUNCTION test_type_conversion_bool(x bool) RETURNS bool AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_bool(true); --- 5,11 ---- -- Base/common types -- CREATE FUNCTION test_type_conversion_bool(x bool) RETURNS bool AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_bool(true); *************** elif n == 4: *** 46,52 **** ret = [] elif n == 5: ret = [0] ! plpy.info(ret, not not ret) return ret $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_bool_other(0); --- 46,52 ---- ret = [] elif n == 5: ret = [0] ! plpy.info((ret, not not ret)) return ret $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_bool_other(0); *************** INFO: ([0], True) *** 92,98 **** (1 row) CREATE FUNCTION test_type_conversion_char(x char) RETURNS char AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_char('a'); --- 92,98 ---- (1 row) CREATE FUNCTION test_type_conversion_char(x char) RETURNS char AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_char('a'); *************** INFO: (None, ) *** 110,116 **** (1 row) CREATE FUNCTION test_type_conversion_int2(x int2) RETURNS int2 AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_int2(100::int2); --- 110,116 ---- (1 row) CREATE FUNCTION test_type_conversion_int2(x int2) RETURNS int2 AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_int2(100::int2); *************** INFO: (None, ) *** 135,141 **** (1 row) CREATE FUNCTION test_type_conversion_int4(x int4) RETURNS int4 AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_int4(100); --- 135,141 ---- (1 row) CREATE FUNCTION test_type_conversion_int4(x int4) RETURNS int4 AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_int4(100); *************** INFO: (None, ) *** 160,166 **** (1 row) CREATE FUNCTION test_type_conversion_int8(x int8) RETURNS int8 AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_int8(100); --- 160,166 ---- (1 row) CREATE FUNCTION test_type_conversion_int8(x int8) RETURNS int8 AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_int8(100); *************** INFO: (None, ) *** 194,200 **** CREATE FUNCTION test_type_conversion_numeric(x numeric) RETURNS numeric AS $$ # print just the class name, not the type, to avoid differences # between decimal and cdecimal ! plpy.info(str(x), x.__class__.__name__) return x $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_numeric(100); --- 194,200 ---- CREATE FUNCTION test_type_conversion_numeric(x numeric) RETURNS numeric AS $$ # print just the class name, not the type, to avoid differences # between decimal and cdecimal ! plpy.info((str(x), x.__class__.__name__)) return x $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_numeric(100); *************** INFO: ('None', 'NoneType') *** 254,260 **** (1 row) CREATE FUNCTION test_type_conversion_float4(x float4) RETURNS float4 AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_float4(100); --- 254,260 ---- (1 row) CREATE FUNCTION test_type_conversion_float4(x float4) RETURNS float4 AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_float4(100); *************** INFO: (None, ) *** 286,292 **** (1 row) CREATE FUNCTION test_type_conversion_float8(x float8) RETURNS float8 AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_float8(100); --- 286,292 ---- (1 row) CREATE FUNCTION test_type_conversion_float8(x float8) RETURNS float8 AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_float8(100); *************** INFO: (100100100.654321, *** 325,331 **** (1 row) CREATE FUNCTION test_type_conversion_oid(x oid) RETURNS oid AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_oid(100); --- 325,331 ---- (1 row) CREATE FUNCTION test_type_conversion_oid(x oid) RETURNS oid AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_oid(100); *************** INFO: (None, ) *** 350,356 **** (1 row) CREATE FUNCTION test_type_conversion_text(x text) RETURNS text AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_text('hello world'); --- 350,356 ---- (1 row) CREATE FUNCTION test_type_conversion_text(x text) RETURNS text AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_text('hello world'); *************** INFO: (None, ) *** 368,374 **** (1 row) CREATE FUNCTION test_type_conversion_bytea(x bytea) RETURNS bytea AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_bytea('hello world'); --- 368,374 ---- (1 row) CREATE FUNCTION test_type_conversion_bytea(x bytea) RETURNS bytea AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_bytea('hello world'); *************** CONTEXT: while creating return value *** 430,436 **** PL/Python function "test_type_conversion_booltrue" CREATE DOMAIN uint2 AS int2 CHECK (VALUE >= 0); CREATE FUNCTION test_type_conversion_uint2(x uint2, y int) RETURNS uint2 AS $$ ! plpy.info(x, type(x)) return y $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_uint2(100::uint2, 50); --- 430,436 ---- PL/Python function "test_type_conversion_booltrue" CREATE DOMAIN uint2 AS int2 CHECK (VALUE >= 0); CREATE FUNCTION test_type_conversion_uint2(x uint2, y int) RETURNS uint2 AS $$ ! plpy.info((x, type(x))) return y $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_uint2(100::uint2, 50); *************** CONTEXT: while creating return value *** 470,476 **** PL/Python function "test_type_conversion_nnint" CREATE DOMAIN bytea10 AS bytea CHECK (octet_length(VALUE) = 10 AND VALUE IS NOT NULL); CREATE FUNCTION test_type_conversion_bytea10(x bytea10, y bytea) RETURNS bytea10 AS $$ ! plpy.info(x, type(x)) return y $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_bytea10('hello wold', 'hello wold'); --- 470,476 ---- PL/Python function "test_type_conversion_nnint" CREATE DOMAIN bytea10 AS bytea CHECK (octet_length(VALUE) = 10 AND VALUE IS NOT NULL); CREATE FUNCTION test_type_conversion_bytea10(x bytea10, y bytea) RETURNS bytea10 AS $$ ! plpy.info((x, type(x))) return y $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_bytea10('hello wold', 'hello wold'); *************** PL/Python function "test_type_conversion *** 498,504 **** -- Arrays -- CREATE FUNCTION test_type_conversion_array_int4(x int4[]) RETURNS int4[] AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_array_int4(ARRAY[0, 100]); --- 498,504 ---- -- Arrays -- CREATE FUNCTION test_type_conversion_array_int4(x int4[]) RETURNS int4[] AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_array_int4(ARRAY[0, 100]); *************** ERROR: cannot convert multidimensional *** 541,547 **** DETAIL: PL/Python only supports one-dimensional arrays. CONTEXT: PL/Python function "test_type_conversion_array_int4" CREATE FUNCTION test_type_conversion_array_text(x text[]) RETURNS text[] AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_array_text(ARRAY['foo', 'bar']); --- 541,547 ---- DETAIL: PL/Python only supports one-dimensional arrays. CONTEXT: PL/Python function "test_type_conversion_array_int4" CREATE FUNCTION test_type_conversion_array_text(x text[]) RETURNS text[] AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_array_text(ARRAY['foo', 'bar']); *************** INFO: (['foo', 'bar'], ) *** 552,558 **** (1 row) CREATE FUNCTION test_type_conversion_array_bytea(x bytea[]) RETURNS bytea[] AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_array_bytea(ARRAY[E'\\xdeadbeef'::bytea, NULL]); --- 552,558 ---- (1 row) CREATE FUNCTION test_type_conversion_array_bytea(x bytea[]) RETURNS bytea[] AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_array_bytea(ARRAY[E'\\xdeadbeef'::bytea, NULL]); *************** PL/Python function "test_type_conversion *** 617,623 **** -- CREATE DOMAIN ordered_pair_domain AS integer[] CHECK (array_length(VALUE,1)=2 AND VALUE[1] < VALUE[2]); CREATE FUNCTION test_type_conversion_array_domain(x ordered_pair_domain) RETURNS ordered_pair_domain AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_array_domain(ARRAY[0, 100]::ordered_pair_domain); --- 617,623 ---- -- CREATE DOMAIN ordered_pair_domain AS integer[] CHECK (array_length(VALUE,1)=2 AND VALUE[1] < VALUE[2]); CREATE FUNCTION test_type_conversion_array_domain(x ordered_pair_domain) RETURNS ordered_pair_domain AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpythonu; SELECT * FROM test_type_conversion_array_domain(ARRAY[0, 100]::ordered_pair_domain); diff --git a/src/pl/plpython/expected/plpython_types_3.out b/src/pl/plpython/expected/plpython_types_3.out new file mode 100644 index 56b78e1..f02c2a2 *** a/src/pl/plpython/expected/plpython_types_3.out --- b/src/pl/plpython/expected/plpython_types_3.out *************** *** 5,11 **** -- Base/common types -- CREATE FUNCTION test_type_conversion_bool(x bool) RETURNS bool AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_bool(true); --- 5,11 ---- -- Base/common types -- CREATE FUNCTION test_type_conversion_bool(x bool) RETURNS bool AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_bool(true); *************** elif n == 4: *** 46,52 **** ret = [] elif n == 5: ret = [0] ! plpy.info(ret, not not ret) return ret $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_bool_other(0); --- 46,52 ---- ret = [] elif n == 5: ret = [0] ! plpy.info((ret, not not ret)) return ret $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_bool_other(0); *************** INFO: ([0], True) *** 92,98 **** (1 row) CREATE FUNCTION test_type_conversion_char(x char) RETURNS char AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_char('a'); --- 92,98 ---- (1 row) CREATE FUNCTION test_type_conversion_char(x char) RETURNS char AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_char('a'); *************** INFO: (None, ) *** 110,116 **** (1 row) CREATE FUNCTION test_type_conversion_int2(x int2) RETURNS int2 AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_int2(100::int2); --- 110,116 ---- (1 row) CREATE FUNCTION test_type_conversion_int2(x int2) RETURNS int2 AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_int2(100::int2); *************** INFO: (None, ) *** 135,141 **** (1 row) CREATE FUNCTION test_type_conversion_int4(x int4) RETURNS int4 AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_int4(100); --- 135,141 ---- (1 row) CREATE FUNCTION test_type_conversion_int4(x int4) RETURNS int4 AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_int4(100); *************** INFO: (None, ) *** 160,166 **** (1 row) CREATE FUNCTION test_type_conversion_int8(x int8) RETURNS int8 AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_int8(100); --- 160,166 ---- (1 row) CREATE FUNCTION test_type_conversion_int8(x int8) RETURNS int8 AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_int8(100); *************** INFO: (None, ) *** 194,200 **** CREATE FUNCTION test_type_conversion_numeric(x numeric) RETURNS numeric AS $$ # print just the class name, not the type, to avoid differences # between decimal and cdecimal ! plpy.info(str(x), x.__class__.__name__) return x $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_numeric(100); --- 194,200 ---- CREATE FUNCTION test_type_conversion_numeric(x numeric) RETURNS numeric AS $$ # print just the class name, not the type, to avoid differences # between decimal and cdecimal ! plpy.info((str(x), x.__class__.__name__)) return x $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_numeric(100); *************** INFO: ('None', 'NoneType') *** 254,260 **** (1 row) CREATE FUNCTION test_type_conversion_float4(x float4) RETURNS float4 AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_float4(100); --- 254,260 ---- (1 row) CREATE FUNCTION test_type_conversion_float4(x float4) RETURNS float4 AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_float4(100); *************** INFO: (None, ) *** 286,292 **** (1 row) CREATE FUNCTION test_type_conversion_float8(x float8) RETURNS float8 AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_float8(100); --- 286,292 ---- (1 row) CREATE FUNCTION test_type_conversion_float8(x float8) RETURNS float8 AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_float8(100); *************** INFO: (100100100.654321, ) *** 350,356 **** (1 row) CREATE FUNCTION test_type_conversion_text(x text) RETURNS text AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_text('hello world'); --- 350,356 ---- (1 row) CREATE FUNCTION test_type_conversion_text(x text) RETURNS text AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_text('hello world'); *************** INFO: (None, ) *** 368,374 **** (1 row) CREATE FUNCTION test_type_conversion_bytea(x bytea) RETURNS bytea AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_bytea('hello world'); --- 368,374 ---- (1 row) CREATE FUNCTION test_type_conversion_bytea(x bytea) RETURNS bytea AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_bytea('hello world'); *************** CONTEXT: while creating return value *** 430,436 **** PL/Python function "test_type_conversion_booltrue" CREATE DOMAIN uint2 AS int2 CHECK (VALUE >= 0); CREATE FUNCTION test_type_conversion_uint2(x uint2, y int) RETURNS uint2 AS $$ ! plpy.info(x, type(x)) return y $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_uint2(100::uint2, 50); --- 430,436 ---- PL/Python function "test_type_conversion_booltrue" CREATE DOMAIN uint2 AS int2 CHECK (VALUE >= 0); CREATE FUNCTION test_type_conversion_uint2(x uint2, y int) RETURNS uint2 AS $$ ! plpy.info((x, type(x))) return y $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_uint2(100::uint2, 50); *************** CONTEXT: while creating return value *** 470,476 **** PL/Python function "test_type_conversion_nnint" CREATE DOMAIN bytea10 AS bytea CHECK (octet_length(VALUE) = 10 AND VALUE IS NOT NULL); CREATE FUNCTION test_type_conversion_bytea10(x bytea10, y bytea) RETURNS bytea10 AS $$ ! plpy.info(x, type(x)) return y $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_bytea10('hello wold', 'hello wold'); --- 470,476 ---- PL/Python function "test_type_conversion_nnint" CREATE DOMAIN bytea10 AS bytea CHECK (octet_length(VALUE) = 10 AND VALUE IS NOT NULL); CREATE FUNCTION test_type_conversion_bytea10(x bytea10, y bytea) RETURNS bytea10 AS $$ ! plpy.info((x, type(x))) return y $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_bytea10('hello wold', 'hello wold'); *************** PL/Python function "test_type_conversion *** 498,504 **** -- Arrays -- CREATE FUNCTION test_type_conversion_array_int4(x int4[]) RETURNS int4[] AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_array_int4(ARRAY[0, 100]); --- 498,504 ---- -- Arrays -- CREATE FUNCTION test_type_conversion_array_int4(x int4[]) RETURNS int4[] AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_array_int4(ARRAY[0, 100]); *************** ERROR: cannot convert multidimensional *** 541,547 **** DETAIL: PL/Python only supports one-dimensional arrays. CONTEXT: PL/Python function "test_type_conversion_array_int4" CREATE FUNCTION test_type_conversion_array_text(x text[]) RETURNS text[] AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_array_text(ARRAY['foo', 'bar']); --- 541,547 ---- DETAIL: PL/Python only supports one-dimensional arrays. CONTEXT: PL/Python function "test_type_conversion_array_int4" CREATE FUNCTION test_type_conversion_array_text(x text[]) RETURNS text[] AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_array_text(ARRAY['foo', 'bar']); *************** INFO: (['foo', 'bar'], ) *** 552,558 **** (1 row) CREATE FUNCTION test_type_conversion_array_bytea(x bytea[]) RETURNS bytea[] AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_array_bytea(ARRAY[E'\\xdeadbeef'::bytea, NULL]); --- 552,558 ---- (1 row) CREATE FUNCTION test_type_conversion_array_bytea(x bytea[]) RETURNS bytea[] AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_array_bytea(ARRAY[E'\\xdeadbeef'::bytea, NULL]); *************** PL/Python function "test_type_conversion *** 617,623 **** -- CREATE DOMAIN ordered_pair_domain AS integer[] CHECK (array_length(VALUE,1)=2 AND VALUE[1] < VALUE[2]); CREATE FUNCTION test_type_conversion_array_domain(x ordered_pair_domain) RETURNS ordered_pair_domain AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_array_domain(ARRAY[0, 100]::ordered_pair_domain); --- 617,623 ---- -- CREATE DOMAIN ordered_pair_domain AS integer[] CHECK (array_length(VALUE,1)=2 AND VALUE[1] < VALUE[2]); CREATE FUNCTION test_type_conversion_array_domain(x ordered_pair_domain) RETURNS ordered_pair_domain AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpython3u; SELECT * FROM test_type_conversion_array_domain(ARRAY[0, 100]::ordered_pair_domain); diff --git a/src/pl/plpython/plpy_elog.c b/src/pl/plpython/plpy_elog.c new file mode 100644 index 15406d6..85121d5 *** a/src/pl/plpython/plpy_elog.c --- b/src/pl/plpython/plpy_elog.c *************** PyObject *PLy_exc_spi_error = NULL; *** 23,31 **** static void PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth); static void PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail, ! char **hint, char **query, int *position); static char *get_source_line(const char *src, int lineno); /* * Emit a PG error or notice, together with any available info about --- 23,41 ---- static void PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth); static void PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail, ! char **hint, char **query, int *position, ! char **schema_name, char **table_name, char **column_name, ! char **datatype_name, char **constraint_name); ! static void PLy_get_error_data(PyObject *exc, int *sqlerrcode, char **detail, ! char **hint, char **query, int *position, ! char **schema_name, char **table_name, char **column_name, ! char **datatype_name, char **constraint_name); static char *get_source_line(const char *src, int lineno); + static void get_string_attr(PyObject *obj, char *attrname, char **str); + static void get_int_attr(PyObject *obj, char *attrname, int *iv); + static bool set_string_attr(PyObject *obj, char *attrname, char *str); + static bool set_int_attr(PyObject *obj, char *attrname, int iv); /* * Emit a PG error or notice, together with any available info about *************** PLy_elog(int elevel, const char *fmt,... *** 51,62 **** char *hint = NULL; char *query = NULL; int position = 0; PyErr_Fetch(&exc, &val, &tb); if (exc != NULL) { if (PyErr_GivenExceptionMatches(val, PLy_exc_spi_error)) ! PLy_get_spi_error_data(val, &sqlerrcode, &detail, &hint, &query, &position); else if (PyErr_GivenExceptionMatches(val, PLy_exc_fatal)) elevel = FATAL; } --- 61,83 ---- char *hint = NULL; char *query = NULL; int position = 0; + char *schema_name = NULL; + char *table_name = NULL; + char *column_name = NULL; + char *datatype_name = NULL; + char *constraint_name = NULL; PyErr_Fetch(&exc, &val, &tb); if (exc != NULL) { if (PyErr_GivenExceptionMatches(val, PLy_exc_spi_error)) ! PLy_get_spi_error_data(val, &sqlerrcode, &detail, &hint, &query, &position, ! &schema_name, &table_name, &column_name, ! &datatype_name, &constraint_name); ! else if (PyErr_GivenExceptionMatches(val, PLy_exc_error)) ! PLy_get_error_data(val, &sqlerrcode, &detail, &hint, &query, &position, ! &schema_name, &table_name, &column_name, ! &datatype_name, &constraint_name); else if (PyErr_GivenExceptionMatches(val, PLy_exc_fatal)) elevel = FATAL; } *************** PLy_elog(int elevel, const char *fmt,... *** 103,109 **** (tb_depth > 0 && tbmsg) ? errcontext("%s", tbmsg) : 0, (hint) ? errhint("%s", hint) : 0, (query) ? internalerrquery(query) : 0, ! (position) ? internalerrposition(position) : 0)); } PG_CATCH(); { --- 124,135 ---- (tb_depth > 0 && tbmsg) ? errcontext("%s", tbmsg) : 0, (hint) ? errhint("%s", hint) : 0, (query) ? internalerrquery(query) : 0, ! (position) ? internalerrposition(position) : 0, ! (schema_name) ? err_generic_string(PG_DIAG_SCHEMA_NAME, schema_name) : 0, ! (table_name) ? err_generic_string(PG_DIAG_TABLE_NAME, table_name) : 0, ! (column_name) ? err_generic_string(PG_DIAG_COLUMN_NAME, column_name) : 0, ! (datatype_name) ? err_generic_string(PG_DIAG_DATATYPE_NAME, datatype_name) : 0, ! (constraint_name) ? err_generic_string(PG_DIAG_CONSTRAINT_NAME, constraint_name) : 0)); } PG_CATCH(); { *************** PLy_get_spi_sqlerrcode(PyObject *exc, in *** 360,371 **** Py_DECREF(sqlstate); } - /* * Extract the error data from a SPIError */ static void ! PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail, char **hint, char **query, int *position) { PyObject *spidata = NULL; --- 386,399 ---- Py_DECREF(sqlstate); } /* * Extract the error data from a SPIError */ static void ! PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail, ! char **hint, char **query, int *position, ! char **schema_name, char **table_name, char **column_name, ! char **datatype_name, char **constraint_name) { PyObject *spidata = NULL; *************** PLy_get_spi_error_data(PyObject *exc, in *** 373,379 **** if (spidata != NULL) { ! PyArg_ParseTuple(spidata, "izzzi", sqlerrcode, detail, hint, query, position); } else { --- 401,409 ---- if (spidata != NULL) { ! PyArg_ParseTuple(spidata, "izzzizzzzz", sqlerrcode, detail, hint, query, position, ! schema_name, table_name, column_name, ! datatype_name, constraint_name); } else { *************** PLy_get_spi_error_data(PyObject *exc, in *** 390,395 **** --- 420,450 ---- } /* + * Extract the error data from an Error + */ + static void + PLy_get_error_data(PyObject *exc, int *sqlerrcode, char **detail, + char **hint, char **query, int *position, + char **schema_name, char **table_name, char **column_name, + char **datatype_name, char **constraint_name) + { + PLy_get_spi_sqlerrcode(exc, sqlerrcode); + + get_string_attr(exc, "detail", detail); + get_string_attr(exc, "hint", hint); + get_string_attr(exc, "query", query); + get_int_attr(exc, "position", position); + get_string_attr(exc, "schema_name", schema_name); + get_string_attr(exc, "table_name", table_name); + get_string_attr(exc, "column_name", column_name); + get_string_attr(exc, "datatype_name", datatype_name); + get_string_attr(exc, "constraint_name", constraint_name); + + PyErr_Clear(); + /* no elog here, we simply won't report the errhint, errposition etc */ + } + + /* * Get the given source line as a palloc'd string */ static char * *************** PLy_exception_set_plural(PyObject *exc, *** 464,466 **** --- 519,652 ---- PyErr_SetString(exc, buf); } + + /* set exceptions from an ErrorData */ + void + PLy_exception_set_with_details(PyObject *excclass, ErrorData *edata) + { + PyObject *args = NULL; + PyObject *error = NULL; + + args = Py_BuildValue("(s)", edata->message); + if (!args) + goto failure; + + /* create a new exception with the error message as the parameter */ + error = PyObject_CallObject(excclass, args); + if (!error) + goto failure; + + if (!set_string_attr(error, "sqlstate", + unpack_sql_state(edata->sqlerrcode))) + goto failure; + + if (!set_string_attr(error, "detail", edata->detail)) + goto failure; + + if (!set_string_attr(error, "hint", edata->hint)) + goto failure; + + if (!set_string_attr(error, "query", edata->internalquery)) + goto failure; + + if (!set_int_attr(error, "position", edata->internalpos)) + goto failure; + + if (!set_string_attr(error, "schema_name", edata->schema_name)) + goto failure; + + if (!set_string_attr(error, "table_name", edata->table_name)) + goto failure; + + if (!set_string_attr(error, "column_name", edata->column_name)) + goto failure; + + if (!set_string_attr(error, "datatype_name", edata->datatype_name)) + goto failure; + + if (!set_string_attr(error, "constraint_name", edata->constraint_name)) + goto failure; + + PyErr_SetObject(excclass, error); + + Py_DECREF(args); + Py_DECREF(error); + + return; + + failure: + Py_XDECREF(args); + Py_XDECREF(error); + + elog(ERROR, "could not convert error to Python exception"); + } + + /* set value of string pointer on object field */ + static void + get_string_attr(PyObject *obj, char *attrname, char **str) + { + PyObject *val; + + val = PyObject_GetAttrString(obj, attrname); + if (val != NULL && val != Py_None) + { + *str = PyString_AsString(val); + Py_DECREF(val); + } + } + + /* same as previous for long */ + static void + get_int_attr(PyObject *obj, char *attrname, int *iv) + { + PyObject *val; + + val = PyObject_GetAttrString(obj, attrname); + if (val != NULL && val != Py_None) + { + *iv = (int) (PyInt_AsLong(val)); + Py_DECREF(val); + } + } + + /* returns true, when object's field was succesfully changed */ + static bool + set_string_attr(PyObject *obj, char *attrname, char *str) + { + int result; + PyObject *val; + + if (str != NULL) + { + val = PyString_FromString(str); + if (!val) + return false; + } + else + { + val = Py_None; + Py_INCREF(Py_None); + } + + result = PyObject_SetAttrString(obj, attrname, val); + Py_DECREF(val); + + return result != -1; + } + + /* same as previous for int */ + static bool + set_int_attr(PyObject *obj, char *attrname, int iv) + { + int result; + PyObject *val; + + val = PyInt_FromLong((long) iv); + if (!val) + return false; + + result = PyObject_SetAttrString(obj, attrname, val); + Py_DECREF(val); + + return result != -1; + } diff --git a/src/pl/plpython/plpy_elog.h b/src/pl/plpython/plpy_elog.h new file mode 100644 index 94725c2..5dd4ef7 *** a/src/pl/plpython/plpy_elog.h --- b/src/pl/plpython/plpy_elog.h *************** extern void PLy_exception_set(PyObject * *** 17,20 **** --- 17,22 ---- extern void PLy_exception_set_plural(PyObject *exc, const char *fmt_singular, const char *fmt_plural, unsigned long n,...) pg_attribute_printf(2, 5) pg_attribute_printf(3, 5); + extern void PLy_exception_set_with_details(PyObject *excclass, ErrorData *edata); + #endif /* PLPY_ELOG_H */ diff --git a/src/pl/plpython/plpy_main.c b/src/pl/plpython/plpy_main.c new file mode 100644 index f950394..f74a062 *** a/src/pl/plpython/plpy_main.c --- b/src/pl/plpython/plpy_main.c *************** static int plpython_version_bitmask = 0; *** 70,75 **** --- 70,77 ---- /* initialize global variables */ PyObject *PLy_interp_globals = NULL; + bool plpythonu_legacy_custom_exception = false; + /* this doesn't need to be global; use PLy_current_execution_context() */ static PLyExecutionContext *PLy_execution_contexts = NULL; *************** PLy_initialize(void) *** 147,152 **** --- 149,162 ---- PLy_execution_contexts = NULL; + DefineCustomBoolVariable("plpythonu.legacy_custom_exception", + gettext_noop("Returns back a behave of function debug, log, info, ..."), + NULL, + &plpythonu_legacy_custom_exception, + false, + PGC_USERSET, 0, + NULL, NULL, NULL); + inited = true; } diff --git a/src/pl/plpython/plpy_plpymodule.c b/src/pl/plpython/plpy_plpymodule.c new file mode 100644 index a44b7fb..dba4895 *** a/src/pl/plpython/plpy_plpymodule.c --- b/src/pl/plpython/plpy_plpymodule.c *************** static void PLy_add_exceptions(PyObject *** 28,40 **** static void PLy_generate_spi_exceptions(PyObject *mod, PyObject *base); /* module functions */ ! static PyObject *PLy_debug(PyObject *self, PyObject *args); ! static PyObject *PLy_log(PyObject *self, PyObject *args); ! static PyObject *PLy_info(PyObject *self, PyObject *args); ! static PyObject *PLy_notice(PyObject *self, PyObject *args); ! static PyObject *PLy_warning(PyObject *self, PyObject *args); ! static PyObject *PLy_error(PyObject *self, PyObject *args); ! static PyObject *PLy_fatal(PyObject *self, PyObject *args); static PyObject *PLy_quote_literal(PyObject *self, PyObject *args); static PyObject *PLy_quote_nullable(PyObject *self, PyObject *args); static PyObject *PLy_quote_ident(PyObject *self, PyObject *args); --- 28,40 ---- static void PLy_generate_spi_exceptions(PyObject *mod, PyObject *base); /* module functions */ ! static PyObject *PLy_debug(PyObject *self, PyObject *args, PyObject *kw); ! static PyObject *PLy_log(PyObject *self, PyObject *args, PyObject *kw); ! static PyObject *PLy_info(PyObject *self, PyObject *args, PyObject *kw); ! static PyObject *PLy_notice(PyObject *self, PyObject *args, PyObject *kw); ! static PyObject *PLy_warning(PyObject *self, PyObject *args, PyObject *kw); ! static PyObject *PLy_error(PyObject *self, PyObject *args, PyObject *kw); ! static PyObject *PLy_fatal(PyObject *self, PyObject *args, PyObject *kw); static PyObject *PLy_quote_literal(PyObject *self, PyObject *args); static PyObject *PLy_quote_nullable(PyObject *self, PyObject *args); static PyObject *PLy_quote_ident(PyObject *self, PyObject *args); *************** static PyMethodDef PLy_methods[] = { *** 57,69 **** /* * logging methods */ ! {"debug", PLy_debug, METH_VARARGS, NULL}, ! {"log", PLy_log, METH_VARARGS, NULL}, ! {"info", PLy_info, METH_VARARGS, NULL}, ! {"notice", PLy_notice, METH_VARARGS, NULL}, ! {"warning", PLy_warning, METH_VARARGS, NULL}, ! {"error", PLy_error, METH_VARARGS, NULL}, ! {"fatal", PLy_fatal, METH_VARARGS, NULL}, /* * create a stored plan --- 57,69 ---- /* * logging methods */ ! {"debug", (PyCFunction) PLy_debug, METH_VARARGS|METH_KEYWORDS, NULL}, ! {"log", (PyCFunction) PLy_log, METH_VARARGS|METH_KEYWORDS, NULL}, ! {"info", (PyCFunction) PLy_info, METH_VARARGS|METH_KEYWORDS, NULL}, ! {"notice", (PyCFunction) PLy_notice, METH_VARARGS|METH_KEYWORDS, NULL}, ! {"warning", (PyCFunction) PLy_warning, METH_VARARGS|METH_KEYWORDS, NULL}, ! {"error", (PyCFunction) PLy_error, METH_VARARGS|METH_KEYWORDS, NULL}, ! {"fatal", (PyCFunction) PLy_fatal, METH_VARARGS|METH_KEYWORDS, NULL}, /* * create a stored plan *************** PLy_generate_spi_exceptions(PyObject *mo *** 272,318 **** * don't confuse these with PLy_elog */ static PyObject *PLy_output(volatile int, PyObject *, PyObject *); static PyObject * ! PLy_debug(PyObject *self, PyObject *args) { ! return PLy_output(DEBUG2, self, args); } static PyObject * ! PLy_log(PyObject *self, PyObject *args) { ! return PLy_output(LOG, self, args); } static PyObject * ! PLy_info(PyObject *self, PyObject *args) { ! return PLy_output(INFO, self, args); } static PyObject * ! PLy_notice(PyObject *self, PyObject *args) { ! return PLy_output(NOTICE, self, args); } static PyObject * ! PLy_warning(PyObject *self, PyObject *args) { ! return PLy_output(WARNING, self, args); } static PyObject * ! PLy_error(PyObject *self, PyObject *args) { ! return PLy_output(ERROR, self, args); } static PyObject * ! PLy_fatal(PyObject *self, PyObject *args) { ! return PLy_output(FATAL, self, args); } static PyObject * --- 272,330 ---- * don't confuse these with PLy_elog */ static PyObject *PLy_output(volatile int, PyObject *, PyObject *); + static PyObject *PLy_output_kw(volatile int level, PyObject *self, + PyObject *args, PyObject *kw); + + /* allow to switch between current and legacy design of following functions */ + static PyObject *PLy_output_switch(volatile int level, PyObject *self, + PyObject *args, PyObject *kw) + { + if (plpythonu_legacy_custom_exception) + return PLy_output(level, self, args); + else + return PLy_output_kw(level, self, args, kw); + } static PyObject * ! PLy_debug(PyObject *self, PyObject *args, PyObject *kw) { ! return PLy_output_switch(DEBUG2, self, args, kw); } static PyObject * ! PLy_log(PyObject *self, PyObject *args, PyObject *kw) { ! return PLy_output_switch(LOG, self, args, kw); } static PyObject * ! PLy_info(PyObject *self, PyObject *args, PyObject *kw) { ! return PLy_output_switch(INFO, self, args, kw); } static PyObject * ! PLy_notice(PyObject *self, PyObject *args, PyObject *kw) { ! return PLy_output_switch(NOTICE, self, args, kw); } static PyObject * ! PLy_warning(PyObject *self, PyObject *args, PyObject *kw) { ! return PLy_output_switch(WARNING, self, args, kw); } static PyObject * ! PLy_error(PyObject *self, PyObject *args, PyObject *kw) { ! return PLy_output_switch(ERROR, self, args, kw); } static PyObject * ! PLy_fatal(PyObject *self, PyObject *args, PyObject *kw) { ! return PLy_output_switch(FATAL, self, args, kw); } static PyObject * *************** PLy_output(volatile int level, PyObject *** 429,431 **** --- 441,574 ---- Py_INCREF(Py_None); return Py_None; } + + /* enforce cast of object to string */ + static char * + object_to_string(PyObject *obj) + { + if (obj) + { + PyObject *str = PyObject_Str(obj); + if (str != NULL && str != Py_None) + return PyString_AsString(str); + } + + return NULL; + } + + static PyObject * + PLy_output_kw(volatile int level, PyObject *self, PyObject *args, PyObject *kw) + { + int sqlstate = 0; + const char *sqlstatestr = NULL; + const char *message = NULL; + const char *detail = NULL; + const char *hint = NULL; + const char *column = NULL; + const char *constraint = NULL; + const char *datatype = NULL; + const char *table = NULL; + const char *schema = NULL; + PyObject *o_sqlstatestr = NULL; + PyObject *o_message = NULL; + PyObject *o_detail = NULL; + PyObject *o_hint = NULL; + PyObject *o_column = NULL; + PyObject *o_table = NULL; + PyObject *o_schema = NULL; + PyObject *o_constraint = NULL; + PyObject *o_datatype = NULL; + MemoryContext oldcontext ; + + static char *kwlist[] = { "message", "detail", "hint", + "sqlstate", + "schema","table", "column", + "datatype", "constraint", + NULL }; + + if (!PyArg_ParseTupleAndKeywords(args, kw, "|OOOOOOOOO", kwlist, + &o_message, &o_detail, &o_hint, + &o_sqlstatestr, + &o_schema, &o_table, &o_column, + &o_datatype, &o_constraint)) + return NULL; + + /* enforce message, detail, hint to string */ + sqlstatestr = object_to_string(o_sqlstatestr); + message = object_to_string(o_message); + detail = object_to_string(o_detail); + hint = object_to_string(o_hint); + column = object_to_string(o_column); + table = object_to_string(o_table); + schema = object_to_string(o_schema); + datatype = object_to_string(o_datatype); + constraint = object_to_string(o_constraint); + + if (sqlstatestr != NULL) + { + if (strlen(sqlstatestr) != 5) + PLy_elog(ERROR, "invalid SQLSTATE code"); + + if (strspn(sqlstatestr, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != 5) + PLy_elog(ERROR, "invalid SQLSTATE code"); + + sqlstate = MAKE_SQLSTATE(sqlstatestr[0], + sqlstatestr[1], + sqlstatestr[2], + sqlstatestr[3], + sqlstatestr[4]); + } + + oldcontext = CurrentMemoryContext; + PG_TRY(); + { + if (message != NULL) + pg_verifymbstr(message, strlen(message), false); + if (detail != NULL) + pg_verifymbstr(detail, strlen(detail), false); + if (hint != NULL) + pg_verifymbstr(hint, strlen(hint), false); + if (schema != NULL) + pg_verifymbstr(schema, strlen(schema), false); + if (table != NULL) + pg_verifymbstr(table, strlen(table), false); + if (column != NULL) + pg_verifymbstr(column, strlen(column), false); + if (datatype != NULL) + pg_verifymbstr(datatype, strlen(datatype), false); + if (constraint != NULL) + pg_verifymbstr(constraint, strlen(constraint), false); + + ereport(level, + ((sqlstate != 0) ? errcode(sqlstate) : 0, + (message != NULL) ? errmsg_internal("%s", message) : 0, + (detail != NULL) ? errdetail_internal("%s", detail) : 0, + (hint != NULL) ? errhint("%s", hint) : 0, + (column != NULL) ? + err_generic_string(PG_DIAG_COLUMN_NAME, column) : 0, + (constraint != NULL) ? + err_generic_string(PG_DIAG_CONSTRAINT_NAME, constraint) : 0, + (datatype != NULL) ? + err_generic_string(PG_DIAG_DATATYPE_NAME, datatype) : 0, + (table != NULL) ? + err_generic_string(PG_DIAG_TABLE_NAME, table) : 0, + (schema != NULL) ? + err_generic_string(PG_DIAG_SCHEMA_NAME, schema) : 0)); + } + PG_CATCH(); + { + ErrorData *edata; + + MemoryContextSwitchTo(oldcontext); + edata = CopyErrorData(); + FlushErrorState(); + + PLy_exception_set_with_details(PLy_exc_error, edata); + FreeErrorData(edata); + return NULL; + } + PG_END_TRY(); + + Py_INCREF(Py_None); + return Py_None; + } diff --git a/src/pl/plpython/plpy_plpymodule.h b/src/pl/plpython/plpy_plpymodule.h new file mode 100644 index ee089b7..fc76511 *** a/src/pl/plpython/plpy_plpymodule.h --- b/src/pl/plpython/plpy_plpymodule.h *************** *** 10,15 **** --- 10,17 ---- /* A hash table mapping sqlstates to exceptions, for speedy lookup */ extern HTAB *PLy_spi_exceptions; + /* GUC */ + extern bool plpythonu_legacy_custom_exception; #if PY_MAJOR_VERSION >= 3 PyMODINIT_FUNC PyInit_plpy(void); diff --git a/src/pl/plpython/plpy_spi.c b/src/pl/plpython/plpy_spi.c new file mode 100644 index 58e78ec..2b53b14 *** a/src/pl/plpython/plpy_spi.c --- b/src/pl/plpython/plpy_spi.c *************** PLy_spi_exception_set(PyObject *excclass *** 564,571 **** if (!spierror) goto failure; ! spidata = Py_BuildValue("(izzzi)", edata->sqlerrcode, edata->detail, edata->hint, ! edata->internalquery, edata->internalpos); if (!spidata) goto failure; --- 564,573 ---- if (!spierror) goto failure; ! spidata= Py_BuildValue("(izzzizzzzz)", edata->sqlerrcode, edata->detail, edata->hint, ! edata->internalquery, edata->internalpos, ! edata->schema_name, edata->table_name, edata->column_name, ! edata->datatype_name, edata->constraint_name); if (!spidata) goto failure; diff --git a/src/pl/plpython/sql/plpython_test.sql b/src/pl/plpython/sql/plpython_test.sql new file mode 100644 index 3a76104..eeed93c *** a/src/pl/plpython/sql/plpython_test.sql --- b/src/pl/plpython/sql/plpython_test.sql *************** $$ LANGUAGE plpythonu; *** 36,44 **** select module_contents(); - CREATE FUNCTION elog_test() RETURNS void AS $$ plpy.debug('debug') plpy.log('log') plpy.info('info') --- 36,68 ---- select module_contents(); CREATE FUNCTION elog_test() RETURNS void AS $$ + plpy.debug('debug','some detail') + plpy.log('log','some detail') + plpy.info('info','some detail') + plpy.info() + plpy.info('the question', 42); + plpy.info('This is message text.', + detail = 'This is detail text', + hint = 'This is hint text.', + sqlstate = 'XX000', + schema = 'any info about schema', + table = 'any info about table', + column = 'any info about column', + datatype = 'any info about datatype', + constraint = 'any info about constraint') + plpy.notice('notice','some detail') + plpy.warning('warning','some detail') + plpy.error('stop on error', 'some detail','some hint') + $$ LANGUAGE plpythonu; + + SELECT elog_test(); + + set plpythonu.legacy_custom_exception=true; + + CREATE FUNCTION elog_test_legacy() RETURNS void + AS $$ plpy.debug('debug') plpy.log('log') plpy.info('info') *************** plpy.warning('warning') *** 50,53 **** --- 74,79 ---- plpy.error('error') $$ LANGUAGE plpythonu; + SELECT elog_test_legacy(); + SELECT elog_test(); diff --git a/src/pl/plpython/sql/plpython_types.sql b/src/pl/plpython/sql/plpython_types.sql new file mode 100644 index 19d920d..db6ff87 *** a/src/pl/plpython/sql/plpython_types.sql --- b/src/pl/plpython/sql/plpython_types.sql *************** *** 7,13 **** -- CREATE FUNCTION test_type_conversion_bool(x bool) RETURNS bool AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpythonu; --- 7,13 ---- -- CREATE FUNCTION test_type_conversion_bool(x bool) RETURNS bool AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpythonu; *************** elif n == 4: *** 33,39 **** ret = [] elif n == 5: ret = [0] ! plpy.info(ret, not not ret) return ret $$ LANGUAGE plpythonu; --- 33,39 ---- ret = [] elif n == 5: ret = [0] ! plpy.info((ret, not not ret)) return ret $$ LANGUAGE plpythonu; *************** SELECT * FROM test_type_conversion_bool_ *** 46,52 **** CREATE FUNCTION test_type_conversion_char(x char) RETURNS char AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpythonu; --- 46,52 ---- CREATE FUNCTION test_type_conversion_char(x char) RETURNS char AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpythonu; *************** SELECT * FROM test_type_conversion_char( *** 55,61 **** CREATE FUNCTION test_type_conversion_int2(x int2) RETURNS int2 AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpythonu; --- 55,61 ---- CREATE FUNCTION test_type_conversion_int2(x int2) RETURNS int2 AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpythonu; *************** SELECT * FROM test_type_conversion_int2( *** 65,71 **** CREATE FUNCTION test_type_conversion_int4(x int4) RETURNS int4 AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpythonu; --- 65,71 ---- CREATE FUNCTION test_type_conversion_int4(x int4) RETURNS int4 AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpythonu; *************** SELECT * FROM test_type_conversion_int4( *** 75,81 **** CREATE FUNCTION test_type_conversion_int8(x int8) RETURNS int8 AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpythonu; --- 75,81 ---- CREATE FUNCTION test_type_conversion_int8(x int8) RETURNS int8 AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpythonu; *************** SELECT * FROM test_type_conversion_int8( *** 88,94 **** CREATE FUNCTION test_type_conversion_numeric(x numeric) RETURNS numeric AS $$ # print just the class name, not the type, to avoid differences # between decimal and cdecimal ! plpy.info(str(x), x.__class__.__name__) return x $$ LANGUAGE plpythonu; --- 88,94 ---- CREATE FUNCTION test_type_conversion_numeric(x numeric) RETURNS numeric AS $$ # print just the class name, not the type, to avoid differences # between decimal and cdecimal ! plpy.info((str(x), x.__class__.__name__)) return x $$ LANGUAGE plpythonu; *************** SELECT * FROM test_type_conversion_numer *** 103,109 **** CREATE FUNCTION test_type_conversion_float4(x float4) RETURNS float4 AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpythonu; --- 103,109 ---- CREATE FUNCTION test_type_conversion_float4(x float4) RETURNS float4 AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpythonu; *************** SELECT * FROM test_type_conversion_float *** 114,120 **** CREATE FUNCTION test_type_conversion_float8(x float8) RETURNS float8 AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpythonu; --- 114,120 ---- CREATE FUNCTION test_type_conversion_float8(x float8) RETURNS float8 AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpythonu; *************** SELECT * FROM test_type_conversion_float *** 126,132 **** CREATE FUNCTION test_type_conversion_oid(x oid) RETURNS oid AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpythonu; --- 126,132 ---- CREATE FUNCTION test_type_conversion_oid(x oid) RETURNS oid AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpythonu; *************** SELECT * FROM test_type_conversion_oid(n *** 136,142 **** CREATE FUNCTION test_type_conversion_text(x text) RETURNS text AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpythonu; --- 136,142 ---- CREATE FUNCTION test_type_conversion_text(x text) RETURNS text AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpythonu; *************** SELECT * FROM test_type_conversion_text( *** 145,151 **** CREATE FUNCTION test_type_conversion_bytea(x bytea) RETURNS bytea AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpythonu; --- 145,151 ---- CREATE FUNCTION test_type_conversion_bytea(x bytea) RETURNS bytea AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpythonu; *************** SELECT * FROM test_type_conversion_boolt *** 188,194 **** CREATE DOMAIN uint2 AS int2 CHECK (VALUE >= 0); CREATE FUNCTION test_type_conversion_uint2(x uint2, y int) RETURNS uint2 AS $$ ! plpy.info(x, type(x)) return y $$ LANGUAGE plpythonu; --- 188,194 ---- CREATE DOMAIN uint2 AS int2 CHECK (VALUE >= 0); CREATE FUNCTION test_type_conversion_uint2(x uint2, y int) RETURNS uint2 AS $$ ! plpy.info((x, type(x))) return y $$ LANGUAGE plpythonu; *************** SELECT * FROM test_type_conversion_nnint *** 211,217 **** CREATE DOMAIN bytea10 AS bytea CHECK (octet_length(VALUE) = 10 AND VALUE IS NOT NULL); CREATE FUNCTION test_type_conversion_bytea10(x bytea10, y bytea) RETURNS bytea10 AS $$ ! plpy.info(x, type(x)) return y $$ LANGUAGE plpythonu; --- 211,217 ---- CREATE DOMAIN bytea10 AS bytea CHECK (octet_length(VALUE) = 10 AND VALUE IS NOT NULL); CREATE FUNCTION test_type_conversion_bytea10(x bytea10, y bytea) RETURNS bytea10 AS $$ ! plpy.info((x, type(x))) return y $$ LANGUAGE plpythonu; *************** SELECT * FROM test_type_conversion_bytea *** 227,233 **** -- CREATE FUNCTION test_type_conversion_array_int4(x int4[]) RETURNS int4[] AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpythonu; --- 227,233 ---- -- CREATE FUNCTION test_type_conversion_array_int4(x int4[]) RETURNS int4[] AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpythonu; *************** SELECT * FROM test_type_conversion_array *** 240,246 **** CREATE FUNCTION test_type_conversion_array_text(x text[]) RETURNS text[] AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpythonu; --- 240,246 ---- CREATE FUNCTION test_type_conversion_array_text(x text[]) RETURNS text[] AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpythonu; *************** SELECT * FROM test_type_conversion_array *** 248,254 **** CREATE FUNCTION test_type_conversion_array_bytea(x bytea[]) RETURNS bytea[] AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpythonu; --- 248,254 ---- CREATE FUNCTION test_type_conversion_array_bytea(x bytea[]) RETURNS bytea[] AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpythonu; *************** SELECT * FROM test_type_conversion_array *** 302,308 **** CREATE DOMAIN ordered_pair_domain AS integer[] CHECK (array_length(VALUE,1)=2 AND VALUE[1] < VALUE[2]); CREATE FUNCTION test_type_conversion_array_domain(x ordered_pair_domain) RETURNS ordered_pair_domain AS $$ ! plpy.info(x, type(x)) return x $$ LANGUAGE plpythonu; --- 302,308 ---- CREATE DOMAIN ordered_pair_domain AS integer[] CHECK (array_length(VALUE,1)=2 AND VALUE[1] < VALUE[2]); CREATE FUNCTION test_type_conversion_array_domain(x ordered_pair_domain) RETURNS ordered_pair_domain AS $$ ! plpy.info((x, type(x))) return x $$ LANGUAGE plpythonu;