Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit5c3c3cd

Browse files
committed
Enhanced custom error in PLPythonu
Patch adds a new, more rich, way to emit error message or exception fromPL/Pythonu code.Author: Pavel StehuleReviewers: Catalin Iacob, Peter Eisentraut, Jim Nasby
1 parent5364b35 commit5c3c3cd

File tree

7 files changed

+701
-81
lines changed

7 files changed

+701
-81
lines changed

‎doc/src/sgml/plpython.sgml

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1341,31 +1341,63 @@ $$ LANGUAGE plpythonu;
13411341
<title>Utility Functions</title>
13421342
<para>
13431343
The <literal>plpy</literal> module also provides the functions
1344-
<literal>plpy.debug(<replaceable>msg</>)</literal>,
1345-
<literal>plpy.log(<replaceable>msg</>)</literal>,
1346-
<literal>plpy.info(<replaceable>msg</>)</literal>,
1347-
<literal>plpy.notice(<replaceable>msg</>)</literal>,
1348-
<literal>plpy.warning(<replaceable>msg</>)</literal>,
1349-
<literal>plpy.error(<replaceable>msg</>)</literal>, and
1350-
<literal>plpy.fatal(<replaceable>msg</>)</literal>.<indexterm><primary>elog</><secondary>in PL/Python</></indexterm>
1351-
<function>plpy.error</function> and
1352-
<function>plpy.fatal</function>actually raise a Python exception
1353-
which, if uncaught, propagates out to the calling query, causing
1354-
the current transaction or subtransaction to be aborted.
1355-
<literal>raise plpy.Error(<replaceable>msg</>)</literal> and
1344+
<literal>plpy.debug(<replaceable>msg, **kwargs</>)</literal>,
1345+
<literal>plpy.log(<replaceable>msg, **kwargs</>)</literal>,
1346+
<literal>plpy.info(<replaceable>msg, **kwargs</>)</literal>,
1347+
<literal>plpy.notice(<replaceable>msg, **kwargs</>)</literal>,
1348+
<literal>plpy.warning(<replaceable>msg, **kwargs</>)</literal>,
1349+
<literal>plpy.error(<replaceable>msg, **kwargs</>)</literal>, and
1350+
<literal>plpy.fatal(<replaceable>msg, **kwargs</>)</literal>.
1351+
<indexterm><primary>elog</><secondary>in PL/Python</></indexterm>
1352+
<function>plpy.error</function>and <function>plpy.fatal</function>
1353+
actually raise a Python exceptionwhich, if uncaught, propagates out to
1354+
thecalling query, causing thecurrent transaction or subtransaction to
1355+
be aborted.<literal>raise plpy.Error(<replaceable>msg</>)</literal> and
13561356
<literal>raise plpy.Fatal(<replaceable>msg</>)</literal> are
1357-
equivalent to calling
1358-
<function>plpy.error</function> and
1359-
<function>plpy.fatal</function>, respectively.
1360-
The other functions only generate messages of different
1361-
priority levels.
1357+
equivalent to calling <literal>plpy.error(<replaceable>msg</>)</literal> and
1358+
<literal>plpy.fatal(<replaceable>msg</>)</literal>, respectively but
1359+
the <literal>raise</literal> form does not allow passing keyword arguments.
1360+
The other functions only generate messages of different priority levels.
13621361
Whether messages of a particular priority are reported to the client,
13631362
written to the server log, or both is controlled by the
13641363
<xref linkend="guc-log-min-messages"> and
13651364
<xref linkend="guc-client-min-messages"> configuration
13661365
variables. See <xref linkend="runtime-config"> for more information.
13671366
</para>
13681367

1368+
<para>
1369+
1370+
The <replaceable>msg</> argument is given as a positional argument. For
1371+
backward compatibility, more than one positional argument can be given. In
1372+
that case, the string representation of the tuple of positional arguments
1373+
becomes the message reported to the client.
1374+
The following keyword-only arguments are accepted:
1375+
<literal>
1376+
<replaceable>detail</replaceable>, <replaceable>hint</replaceable>,
1377+
<replaceable>sqlstate</replaceable>, <replaceable>schema</replaceable>,
1378+
<replaceable>table</replaceable>, <replaceable>column</replaceable>,
1379+
<replaceable>datatype</replaceable> , <replaceable>constraint</replaceable>
1380+
</literal>.
1381+
The string representation of the objects passed as keyword-only arguments
1382+
is used to enrich the messages reported to the client. For example:
1383+
1384+
<programlisting>
1385+
CREATE FUNCTION raise_custom_exception() RETURNS void AS $$
1386+
plpy.error("custom exception message", detail = "some info about exception", hint = "hint for users")
1387+
$$ LANGUAGE plpythonu;
1388+
1389+
postgres=# select raise_custom_exception();
1390+
ERROR: XX000: plpy.Error: custom exception message
1391+
DETAIL: some info about exception
1392+
HINT: hint for users
1393+
CONTEXT: Traceback (most recent call last):
1394+
PL/Python function "raise_custom_exception", line 2, in &lt;module&gt;
1395+
plpy.error("custom exception message", detail = "some info about exception", hint = "hint for users")
1396+
PL/Python function "raise_custom_exception"
1397+
LOCATION: PLy_elog, plpy_elog.c:132
1398+
</programlisting>
1399+
</para>
1400+
13691401
<para>
13701402
Another set of utility functions are
13711403
<literal>plpy.quote_literal(<replaceable>string</>)</literal>,

‎src/pl/plpython/expected/plpython_test.out

Lines changed: 190 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ select module_contents();
4848
Error, Fatal, SPIError, cursor, debug, error, execute, fatal, info, log, notice, prepare, quote_ident, quote_literal, quote_nullable, spiexceptions, subtransaction, warning
4949
(1 row)
5050

51-
CREATE FUNCTIONelog_test() RETURNS void
51+
CREATE FUNCTIONelog_test_basic() RETURNS void
5252
AS $$
5353
plpy.debug('debug')
5454
plpy.log('log')
@@ -60,7 +60,7 @@ plpy.notice('notice')
6060
plpy.warning('warning')
6161
plpy.error('error')
6262
$$ LANGUAGE plpythonu;
63-
SELECTelog_test();
63+
SELECTelog_test_basic();
6464
INFO: info
6565
INFO: 37
6666
INFO: ()
@@ -69,6 +69,193 @@ NOTICE: notice
6969
WARNING: warning
7070
ERROR: plpy.Error: error
7171
CONTEXT: Traceback (most recent call last):
72-
PL/Python function "elog_test", line 10, in <module>
72+
PL/Python function "elog_test_basic", line 10, in <module>
7373
plpy.error('error')
74+
PL/Python function "elog_test_basic"
75+
CREATE FUNCTION elog_test() RETURNS void
76+
AS $$
77+
plpy.debug('debug', detail = 'some detail')
78+
plpy.log('log', detail = 'some detail')
79+
plpy.info('info', detail = 'some detail')
80+
plpy.info()
81+
plpy.info('the question', detail = 42);
82+
plpy.info('This is message text.',
83+
detail = 'This is detail text',
84+
hint = 'This is hint text.',
85+
sqlstate = 'XX000',
86+
schema = 'any info about schema',
87+
table = 'any info about table',
88+
column = 'any info about column',
89+
datatype = 'any info about datatype',
90+
constraint = 'any info about constraint')
91+
plpy.notice('notice', detail = 'some detail')
92+
plpy.warning('warning', detail = 'some detail')
93+
plpy.error('stop on error', detail = 'some detail', hint = 'some hint')
94+
$$ LANGUAGE plpythonu;
95+
SELECT elog_test();
96+
INFO: info
97+
DETAIL: some detail
98+
INFO: ()
99+
INFO: the question
100+
DETAIL: 42
101+
INFO: This is message text.
102+
DETAIL: This is detail text
103+
HINT: This is hint text.
104+
NOTICE: notice
105+
DETAIL: some detail
106+
WARNING: warning
107+
DETAIL: some detail
108+
ERROR: plpy.Error: stop on error
109+
DETAIL: some detail
110+
HINT: some hint
111+
CONTEXT: Traceback (most recent call last):
112+
PL/Python function "elog_test", line 18, in <module>
113+
plpy.error('stop on error', detail = 'some detail', hint = 'some hint')
74114
PL/Python function "elog_test"
115+
do $$ plpy.info('other types', detail = (10,20)) $$ LANGUAGE plpythonu;
116+
INFO: other types
117+
DETAIL: (10, 20)
118+
do $$
119+
import time;
120+
from datetime import date
121+
plpy.info('other types', detail = date(2016,2,26))
122+
$$ LANGUAGE plpythonu;
123+
INFO: other types
124+
DETAIL: 2016-02-26
125+
do $$
126+
basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
127+
plpy.info('other types', detail = basket)
128+
$$ LANGUAGE plpythonu;
129+
INFO: other types
130+
DETAIL: ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
131+
-- should fail
132+
do $$ plpy.info('wrong sqlstate', sqlstate='54444A') $$ LANGUAGE plpythonu;
133+
ERROR: invalid SQLSTATE code
134+
CONTEXT: PL/Python anonymous code block
135+
do $$ plpy.info('unsupported argument', blabla='fooboo') $$ LANGUAGE plpythonu;
136+
ERROR: 'blabla' is an invalid keyword argument for this function
137+
CONTEXT: PL/Python anonymous code block
138+
do $$ plpy.info('first message', message='second message') $$ LANGUAGE plpythonu;
139+
ERROR: the message is already specified
140+
CONTEXT: PL/Python anonymous code block
141+
do $$ plpy.info('first message', 'second message', message='third message') $$ LANGUAGE plpythonu;
142+
ERROR: the message is already specified
143+
CONTEXT: PL/Python anonymous code block
144+
-- raise exception in python, handle exception in plgsql
145+
CREATE OR REPLACE FUNCTION raise_exception(_message text, _detail text DEFAULT NULL, _hint text DEFAULT NULL,
146+
_sqlstate text DEFAULT NULL,
147+
_schema text DEFAULT NULL, _table text DEFAULT NULL, _column text DEFAULT NULL,
148+
_datatype text DEFAULT NULL, _constraint text DEFAULT NULL)
149+
RETURNS void AS $$
150+
kwargs = { "message":_message, "detail":_detail, "hint":_hint,
151+
"sqlstate":_sqlstate, "schema":_schema, "table":_table,
152+
"column":_column, "datatype":_datatype, "constraint":_constraint }
153+
# ignore None values
154+
plpy.error(**dict((k, v) for k, v in iter(kwargs.items()) if v))
155+
$$ LANGUAGE plpythonu;
156+
SELECT raise_exception('hello', 'world');
157+
ERROR: plpy.Error: hello
158+
DETAIL: world
159+
CONTEXT: Traceback (most recent call last):
160+
PL/Python function "raise_exception", line 6, in <module>
161+
plpy.error(**dict((k, v) for k, v in iter(kwargs.items()) if v))
162+
PL/Python function "raise_exception"
163+
SELECT raise_exception('message text', 'detail text', _sqlstate => 'YY333');
164+
ERROR: plpy.Error: message text
165+
DETAIL: detail text
166+
CONTEXT: Traceback (most recent call last):
167+
PL/Python function "raise_exception", line 6, in <module>
168+
plpy.error(**dict((k, v) for k, v in iter(kwargs.items()) if v))
169+
PL/Python function "raise_exception"
170+
SELECT raise_exception(_message => 'message text',
171+
_detail => 'detail text',
172+
_hint => 'hint text',
173+
_sqlstate => 'XX555',
174+
_schema => 'schema text',
175+
_table => 'table text',
176+
_column => 'column text',
177+
_datatype => 'datatype text',
178+
_constraint => 'constraint text');
179+
ERROR: plpy.Error: message text
180+
DETAIL: detail text
181+
HINT: hint text
182+
CONTEXT: Traceback (most recent call last):
183+
PL/Python function "raise_exception", line 6, in <module>
184+
plpy.error(**dict((k, v) for k, v in iter(kwargs.items()) if v))
185+
PL/Python function "raise_exception"
186+
SELECT raise_exception(_message => 'message text',
187+
_hint => 'hint text',
188+
_schema => 'schema text',
189+
_column => 'column text',
190+
_constraint => 'constraint text');
191+
ERROR: plpy.Error: message text
192+
HINT: hint text
193+
CONTEXT: Traceback (most recent call last):
194+
PL/Python function "raise_exception", line 6, in <module>
195+
plpy.error(**dict((k, v) for k, v in iter(kwargs.items()) if v))
196+
PL/Python function "raise_exception"
197+
DO $$
198+
DECLARE
199+
__message text;
200+
__detail text;
201+
__hint text;
202+
__sqlstate text;
203+
__schema_name text;
204+
__table_name text;
205+
__column_name text;
206+
__datatype text;
207+
__constraint text;
208+
BEGIN
209+
BEGIN
210+
PERFORM raise_exception(_message => 'message text',
211+
_detail => 'detail text',
212+
_hint => 'hint text',
213+
_sqlstate => 'XX555',
214+
_schema => 'schema text',
215+
_table => 'table text',
216+
_column => 'column text',
217+
_datatype => 'datatype text',
218+
_constraint => 'constraint text');
219+
EXCEPTION WHEN SQLSTATE 'XX555' THEN
220+
GET STACKED DIAGNOSTICS __message = MESSAGE_TEXT,
221+
__detail = PG_EXCEPTION_DETAIL,
222+
__hint = PG_EXCEPTION_HINT,
223+
__sqlstate = RETURNED_SQLSTATE,
224+
__schema_name = SCHEMA_NAME,
225+
__table_name = TABLE_NAME,
226+
__column_name = COLUMN_NAME,
227+
__datatype = PG_DATATYPE_NAME,
228+
__constraint = CONSTRAINT_NAME;
229+
RAISE NOTICE 'handled exception'
230+
USING DETAIL = format('message:(%s), detail:(%s), hint: (%s), sqlstate: (%s), '
231+
'schema:(%s), table:(%s), column:(%s), datatype:(%s), constraint:(%s)',
232+
__message, __detail, __hint, __sqlstate, __schema_name,
233+
__table_name, __column_name, __datatype, __constraint);
234+
END;
235+
END;
236+
$$;
237+
NOTICE: handled exception
238+
DETAIL: message:(plpy.Error: message text), detail:(detail text), hint: (hint text), sqlstate: (XX555), schema:(schema text), table:(table text), column:(column text), datatype:(datatype text), constraint:(constraint text)
239+
-- the displayed context is different between Python2 and Python3,
240+
-- but that's not important for this test
241+
\set SHOW_CONTEXT never
242+
do $$
243+
try:
244+
plpy.execute("select raise_exception(_message => 'my message', _sqlstate => 'XX987', _hint => 'some hint', _table=> 'users_tab', _datatype => 'user_type')")
245+
except Exception, e:
246+
plpy.info(e.spidata)
247+
raise e
248+
$$ LANGUAGE plpythonu;
249+
INFO: (119577128, None, 'some hint', None, 0, None, 'users_tab', None, 'user_type', None)
250+
ERROR: plpy.SPIError: plpy.Error: my message
251+
HINT: some hint
252+
do $$
253+
try:
254+
plpy.error(message = 'my message', sqlstate = 'XX987', hint = 'some hint', table = 'users_tab', datatype = 'user_type')
255+
except Exception, e:
256+
plpy.info('sqlstate: %s, hint: %s, tablename: %s, datatype: %s' % (e.sqlstate, e.hint, e.table_name, e.datatype_name))
257+
raise e
258+
$$ LANGUAGE plpythonu;
259+
INFO: sqlstate: XX987, hint: some hint, tablename: users_tab, datatype: user_type
260+
ERROR: plpy.Error: my message
261+
HINT: some hint

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp