1-- test error handling, i forgot to restore Warn_restart in
2-- the trigger handler once. the errors and subsequent core dump were
3-- interesting.
4
5/* Flat out Python syntax error
6 */
7CREATE FUNCTION python_syntax_error() RETURNS text
8        AS
9'.syntaxerror'
10        LANGUAGE plpythonu;
11
12/* With check_function_bodies = false the function should get defined
13 * and the error reported when called
14 */
15SET check_function_bodies = false;
16
17CREATE FUNCTION python_syntax_error() RETURNS text
18        AS
19'.syntaxerror'
20        LANGUAGE plpythonu;
21
22SELECT python_syntax_error();
23/* Run the function twice to check if the hashtable entry gets cleaned up */
24SELECT python_syntax_error();
25
26RESET check_function_bodies;
27
28/* Flat out syntax error
29 */
30CREATE FUNCTION sql_syntax_error() RETURNS text
31        AS
32'plpy.execute("syntax error")'
33        LANGUAGE plpythonu;
34
35SELECT sql_syntax_error();
36
37
38/* check the handling of uncaught python exceptions
39 */
40CREATE FUNCTION exception_index_invalid(text) RETURNS text
41	AS
42'return args[1]'
43	LANGUAGE plpythonu;
44
45SELECT exception_index_invalid('test');
46
47
48/* check handling of nested exceptions
49 */
50CREATE FUNCTION exception_index_invalid_nested() RETURNS text
51	AS
52'rv = plpy.execute("SELECT test5(''foo'')")
53return rv[0]'
54	LANGUAGE plpythonu;
55
56SELECT exception_index_invalid_nested();
57
58
59/* a typo
60 */
61CREATE FUNCTION invalid_type_uncaught(a text) RETURNS text
62	AS
63'if "plan" not in SD:
64	q = "SELECT fname FROM users WHERE lname = $1"
65	SD["plan"] = plpy.prepare(q, [ "test" ])
66rv = plpy.execute(SD["plan"], [ a ])
67if len(rv):
68	return rv[0]["fname"]
69return None
70'
71	LANGUAGE plpythonu;
72
73SELECT invalid_type_uncaught('rick');
74
75
76/* for what it's worth catch the exception generated by
77 * the typo, and return None
78 */
79CREATE FUNCTION invalid_type_caught(a text) RETURNS text
80	AS
81'if "plan" not in SD:
82	q = "SELECT fname FROM users WHERE lname = $1"
83	try:
84		SD["plan"] = plpy.prepare(q, [ "test" ])
85	except plpy.SPIError, ex:
86		plpy.notice(str(ex))
87		return None
88rv = plpy.execute(SD["plan"], [ a ])
89if len(rv):
90	return rv[0]["fname"]
91return None
92'
93	LANGUAGE plpythonu;
94
95SELECT invalid_type_caught('rick');
96
97
98/* for what it's worth catch the exception generated by
99 * the typo, and reraise it as a plain error
100 */
101CREATE FUNCTION invalid_type_reraised(a text) RETURNS text
102	AS
103'if "plan" not in SD:
104	q = "SELECT fname FROM users WHERE lname = $1"
105	try:
106		SD["plan"] = plpy.prepare(q, [ "test" ])
107	except plpy.SPIError, ex:
108		plpy.error(str(ex))
109rv = plpy.execute(SD["plan"], [ a ])
110if len(rv):
111	return rv[0]["fname"]
112return None
113'
114	LANGUAGE plpythonu;
115
116SELECT invalid_type_reraised('rick');
117
118
119/* no typo no messing about
120 */
121CREATE FUNCTION valid_type(a text) RETURNS text
122	AS
123'if "plan" not in SD:
124	SD["plan"] = plpy.prepare("SELECT fname FROM users WHERE lname = $1", [ "text" ])
125rv = plpy.execute(SD["plan"], [ a ])
126if len(rv):
127	return rv[0]["fname"]
128return None
129'
130	LANGUAGE plpythonu;
131
132SELECT valid_type('rick');
133
134/* error in nested functions to get a traceback
135*/
136CREATE FUNCTION nested_error() RETURNS text
137	AS
138'def fun1():
139	plpy.error("boom")
140
141def fun2():
142	fun1()
143
144def fun3():
145	fun2()
146
147fun3()
148return "not reached"
149'
150	LANGUAGE plpythonu;
151
152SELECT nested_error();
153
154/* raising plpy.Error is just like calling plpy.error
155*/
156CREATE FUNCTION nested_error_raise() RETURNS text
157	AS
158'def fun1():
159	raise plpy.Error("boom")
160
161def fun2():
162	fun1()
163
164def fun3():
165	fun2()
166
167fun3()
168return "not reached"
169'
170	LANGUAGE plpythonu;
171
172SELECT nested_error_raise();
173
174/* using plpy.warning should not produce a traceback
175*/
176CREATE FUNCTION nested_warning() RETURNS text
177	AS
178'def fun1():
179	plpy.warning("boom")
180
181def fun2():
182	fun1()
183
184def fun3():
185	fun2()
186
187fun3()
188return "you''ve been warned"
189'
190	LANGUAGE plpythonu;
191
192SELECT nested_warning();
193
194/* AttributeError at toplevel used to give segfaults with the traceback
195*/
196CREATE FUNCTION toplevel_attribute_error() RETURNS void AS
197$$
198plpy.nonexistent
199$$ LANGUAGE plpythonu;
200
201SELECT toplevel_attribute_error();
202
203/* Calling PL/Python functions from SQL and vice versa should not lose context.
204 */
205CREATE OR REPLACE FUNCTION python_traceback() RETURNS void AS $$
206def first():
207  second()
208
209def second():
210  third()
211
212def third():
213  plpy.execute("select sql_error()")
214
215first()
216$$ LANGUAGE plpythonu;
217
218CREATE OR REPLACE FUNCTION sql_error() RETURNS void AS $$
219begin
220  select 1/0;
221end
222$$ LANGUAGE plpgsql;
223
224CREATE OR REPLACE FUNCTION python_from_sql_error() RETURNS void AS $$
225begin
226  select python_traceback();
227end
228$$ LANGUAGE plpgsql;
229
230CREATE OR REPLACE FUNCTION sql_from_python_error() RETURNS void AS $$
231plpy.execute("select sql_error()")
232$$ LANGUAGE plpythonu;
233
234SELECT python_traceback();
235SELECT sql_error();
236SELECT python_from_sql_error();
237SELECT sql_from_python_error();
238
239/* check catching specific types of exceptions
240 */
241CREATE TABLE specific (
242    i integer PRIMARY KEY
243);
244
245CREATE FUNCTION specific_exception(i integer) RETURNS void AS
246$$
247from plpy import spiexceptions
248try:
249    plpy.execute("insert into specific values (%s)" % (i or "NULL"));
250except spiexceptions.NotNullViolation, e:
251    plpy.notice("Violated the NOT NULL constraint, sqlstate %s" % e.sqlstate)
252except spiexceptions.UniqueViolation, e:
253    plpy.notice("Violated the UNIQUE constraint, sqlstate %s" % e.sqlstate)
254$$ LANGUAGE plpythonu;
255
256SELECT specific_exception(2);
257SELECT specific_exception(NULL);
258SELECT specific_exception(2);
259
260/* SPI errors in PL/Python functions should preserve the SQLSTATE value
261 */
262CREATE FUNCTION python_unique_violation() RETURNS void AS $$
263plpy.execute("insert into specific values (1)")
264plpy.execute("insert into specific values (1)")
265$$ LANGUAGE plpythonu;
266
267CREATE FUNCTION catch_python_unique_violation() RETURNS text AS $$
268begin
269    begin
270        perform python_unique_violation();
271    exception when unique_violation then
272        return 'ok';
273    end;
274    return 'not reached';
275end;
276$$ language plpgsql;
277
278SELECT catch_python_unique_violation();
279
280/* manually starting subtransactions - a bad idea
281 */
282CREATE FUNCTION manual_subxact() RETURNS void AS $$
283plpy.execute("savepoint save")
284plpy.execute("create table foo(x integer)")
285plpy.execute("rollback to save")
286$$ LANGUAGE plpythonu;
287
288SELECT manual_subxact();
289
290/* same for prepared plans
291 */
292CREATE FUNCTION manual_subxact_prepared() RETURNS void AS $$
293save = plpy.prepare("savepoint save")
294rollback = plpy.prepare("rollback to save")
295plpy.execute(save)
296plpy.execute("create table foo(x integer)")
297plpy.execute(rollback)
298$$ LANGUAGE plpythonu;
299
300SELECT manual_subxact_prepared();
301
302/* raising plpy.spiexception.* from python code should preserve sqlstate
303 */
304CREATE FUNCTION plpy_raise_spiexception() RETURNS void AS $$
305raise plpy.spiexceptions.DivisionByZero()
306$$ LANGUAGE plpythonu;
307
308DO $$
309BEGIN
310	SELECT plpy_raise_spiexception();
311EXCEPTION WHEN division_by_zero THEN
312	-- NOOP
313END
314$$ LANGUAGE plpgsql;
315
316/* setting a custom sqlstate should be handled
317 */
318CREATE FUNCTION plpy_raise_spiexception_override() RETURNS void AS $$
319exc = plpy.spiexceptions.DivisionByZero()
320exc.sqlstate = 'SILLY'
321raise exc
322$$ LANGUAGE plpythonu;
323
324DO $$
325BEGIN
326	SELECT plpy_raise_spiexception_override();
327EXCEPTION WHEN SQLSTATE 'SILLY' THEN
328	-- NOOP
329END
330$$ LANGUAGE plpgsql;
331
332/* test the context stack trace for nested execution levels
333 */
334CREATE FUNCTION notice_innerfunc() RETURNS int AS $$
335plpy.execute("DO LANGUAGE plpythonu $x$ plpy.notice('inside DO') $x$")
336return 1
337$$ LANGUAGE plpythonu;
338
339CREATE FUNCTION notice_outerfunc() RETURNS int AS $$
340plpy.execute("SELECT notice_innerfunc()")
341return 1
342$$ LANGUAGE plpythonu;
343
344\set SHOW_CONTEXT always
345
346SELECT notice_outerfunc();
347