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>).elog>in 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>).elog>in 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;