SET standard_conforming_strings TO ON;
SET client_encoding TO 'UTF-8';

CREATE OR REPLACE FUNCTION pysyntaxerror() RETURNS VOID LANGUAGE python AS
$python$
import Postgres

# missing ':'
def main()
	pass
$python$;
SELECT pysyntaxerror();


CREATE OR REPLACE FUNCTION py_load_failure() RETURNS VOID LANGUAGE python AS
$python$
# raise an exception while loading the module
raise RuntimeError("doa")

def main():
	pass
$python$;
SELECT py_load_failure();

CREATE OR REPLACE FUNCTION py_failure() RETURNS VOID LANGUAGE python AS
$python$

def main():
	# raise an exception on execution
	raise RuntimeError("doa")
$python$;
SELECT py_failure();

-- raises a pg error on load
CREATE OR REPLACE FUNCTION pg_load_failure() RETURNS VOID LANGUAGE python AS
$python$
import Postgres

rp = Postgres.Type(Postgres.CONST['REGPROCEDUREOID'])
fun = rp('nosuchfunc(int17,zzz)')

def main():
	pass
$python$;
SELECT pg_load_failure();

-- raises a pg error on exec
CREATE OR REPLACE FUNCTION pg_failure() RETURNS VOID LANGUAGE python AS
$python$
import Postgres

rp = Postgres.Type(Postgres.CONST['REGPROCEDUREOID'])

def main():
	fun = rp('nosuchfunc(int17,zzz)')
$python$;
SELECT pg_failure();

-- suffocates a pg error on load; should see PL complaint
CREATE OR REPLACE FUNCTION pg_load_failure_suf() RETURNS VOID LANGUAGE python AS
$python$
import Postgres

rp = Postgres.Type(Postgres.CONST['REGPROCEDUREOID'])
try:
	fun = rp('nosuchfunc(int17,zzz)')
except:
	pass

def main():
	raise RuntimeError("should never see this")
$python$;
SELECT pg_load_failure_suf();

-- suffocates a pg error on exec; should see PL complaint
CREATE OR REPLACE FUNCTION pg_failure_suf() RETURNS VOID LANGUAGE python AS
$python$
import Postgres

rp = Postgres.Type(Postgres.CONST['REGPROCEDUREOID'])

def main():
	try:
		fun = rp('nosuchfunc(int17,zzz)')
	except:
		pass
$python$;
SELECT pg_failure_suf();

-- suffocates a pg error, and leaves an open xact on load
CREATE OR REPLACE FUNCTION pg_x_load_failure_suf() RETURNS VOID LANGUAGE python AS
$python$
import Postgres

rp = Postgres.Type(Postgres.CONST['REGPROCEDUREOID'])
x=xact()
x.__enter__()
try:
	fun = rp('nosuchfunc(int17,zzz)')
except:
	pass

def main():
	raise RuntimeError("should never see this")
$python$;
SELECT pg_x_load_failure_suf();

-- suffocates a pg error, and leaves an open xact 
CREATE OR REPLACE FUNCTION pg_x_failure_suf() RETURNS VOID LANGUAGE python AS
$python$
import Postgres

rp = Postgres.Type(Postgres.CONST['REGPROCEDUREOID'])

def main():
	x=xact()
	x.__enter__()
	try:
		fun = rp('nosuchfunc(int17,zzz)')
	except:
		pass
$python$;
SELECT pg_x_failure_suf();

-- suffocates a pg error, and attempts to enter a protected area
CREATE OR REPLACE FUNCTION pg_failure_suf_IFTE() RETURNS VOID LANGUAGE python AS
$python$
import Postgres

rp = Postgres.Type(Postgres.CONST['REGPROCEDUREOID'])

def main():
	try:
		fun = rp('nosuchfunc(int17,zzz)')
	except:
		# Should be valid, but the protection of
		# PL_DB_IN_ERROR should keep it from getting called.
		rp('pg_x_failure_suf()')
$python$;
SELECT pg_failure_suf_IFTE();

-- suffocates a pg error, and leaves an open xact, attempts to enter a protected area
CREATE OR REPLACE FUNCTION pg_x_failure_suf_IFTE() RETURNS VOID LANGUAGE python AS
$python$
import Postgres

rp = Postgres.Type(Postgres.CONST['REGPROCEDUREOID'])

def main():
	x=xact()
	x.__enter__()
	try:
		fun = rp('nosuchfunc(int17,zzz)')
	except:
		# Should be valid, but the protection of
		# PL_DB_IN_ERROR should keep it from getting called.
		rp('pg_x_failure_suf()')
$python$;
SELECT pg_x_failure_suf_IFTE();

CREATE OR REPLACE FUNCTION check_errordata_access() RETURNS SETOF text LANGUAGE python AS
$python$
import Postgres

rp = Postgres.Type(Postgres.CONST['REGPROCEDUREOID'])
keys = (
	'message',
	'detail',
	'detail_log',
	'context',
	'elevel',
	'sqlerrcode',
	'code',
	'severity',
)

def main():
	try:
		with xact():
			rp('nosuchfunc(int17,zzz)')
	except Postgres.Exception as e:
		errdata = e.pg_errordata
	# attribute checks
	errdata.internalpos
	errdata.cursorpos
	errdata.lineno
	errdata.funcname
	errdata.saved_errno
	errdata.domain
	return (
		x + ':' + str(getattr(errdata, x)) for x in keys
	)
$python$;
SELECT check_errordata_access();

-- check the attributes available on Postgres.Exception
CREATE OR REPLACE FUNCTION check_exc_access() RETURNS SETOF text LANGUAGE python AS
$python$
import Postgres

keys = (
	'code',
	'message',
	'details',
	'errno',
	'severity',
)

def main():
	try:
		with xact():
			Postgres.ERROR(
				code = '22331',
				message = 'message',
				context = 'context',
				detail = 'detail'
			)
	except Postgres.Exception as e:
		err = e
	# attribute checks
	return (
		x + ':' + str(getattr(err, x)) for x in keys
	)
$python$;
SELECT check_exc_access();


-- __func__ must exist
CREATE OR REPLACE FUNCTION silly_function() RETURNS VOID LANGUAGE python AS
$python$
del __func__
def main():
	raise Exception("haha")
$python$;

-- raised the 'haha' exception.. module is known
SELECT silly_function();
-- raise an Attribute error; we need __func__.
SELECT silly_function();


-- __func__ must be a Postgres.Function
CREATE OR REPLACE FUNCTION evil_function() RETURNS VOID LANGUAGE python AS
$python$
__func__ = None
def main():
	raise Exception("muahahahhaha")
$python$;

-- The Exception in main gets raised as we grabbed the reference from
-- our own call to load_module().
SELECT evil_function();
-- This time, fn_extra is NULL and there's a module in sys.modules.
-- Validate that __func__ is actually a Postgres.Function object.
-- This will fail with a TypeError
SELECT evil_function();


-- __func__ must be a Postgres.Function with the Oid of the called function
CREATE OR REPLACE FUNCTION more_evil_function() RETURNS VOID LANGUAGE python AS
$python$
__func__ = proc('evil_function()')
def main():
	raise Exception("muahahahhahaHAHAHAHA")
$python$;

-- same as before, we already have the module object
SELECT more_evil_function();
-- It's a function object, but the wrong function object.
-- Raise a value error this time...
SELECT more_evil_function();


CREATE OR REPLACE FUNCTION custom_error(bool) RETURNS VOID LANGUAGE python AS
$python$
import Postgres

def main(with_tb):
	Postgres.ERROR(
		code = 'AAAAA',
		message = 'message',
		context = 'context',
		detail = 'detail',
		hint = 'hint',
		inhibit_pl_context = not bool(with_tb),
	)
$python$;
\set VERBOSITY verbose

SELECT custom_error(true);
SELECT custom_error(false);

\set VERBOSITY default

-- Ignore the inhibit_pl_context setting when it's not a Postgres.Exception.
CREATE OR REPLACE FUNCTION inhibited_pl_context_pyerr() RETURNS VOID LANGUAGE python AS
$python$
import Postgres

def main():
	ve = ValueError("show this traceback")
	ve.inhibit_pl_context = True
	raise ve
$python$;
SELECT inhibited_pl_context_pyerr();


-- exercise a relayed exception
-- probably better categorized relative to "bool", but there isn't a place..
CREATE OR REPLACE FUNCTION boolean_blowup() RETURNS bool LANGUAGE python AS
$python$
class foo(object):
	def __bool__(self):
		raise ValueError("bad nonzero implementation, relay this exception")

def main():
	return foo()
$python$;

SELECT boolean_blowup();


-- test direct function execution while "in" a Python exception --
-- (no database error hath occurred)

-- XXX: Not the desired effect for CONTEXT
--
-- The following function shows the effect of Python's __context__.
-- For plpython, the global __context__ needs to be stored and restored
-- every time the plhandler is entered. This is necessary to ensure that
-- the traceback print out is not redundant.
--
-- There are a couple ways this could be hacked:
--
--  1. setting an exception on entry to identify the context and
--     breaking the context chain at that point iff an exception occurs
--  2. storing and restoring the context from the thread state.
--     (currently a bit too "dangerous", so we'll have to poll the capi sig)
--
CREATE OR REPLACE FUNCTION check_dfc_in_exc(bool) RETURNS VOID LANGUAGE python AS
$python$
from Postgres import WARNING
def main(cont):
	try:
		if cont:
			raise TypeError("lame")
		else:
			raise ValueError("err")
	except ValueError as exc:
		__func__(True)
$python$;

SELECT check_dfc_in_exc(FALSE);



-- make sure the linecache is getting cleared
CREATE OR REPLACE FUNCTION check_linecache_clear() RETURNS VOID LANGUAGE python AS
$python$
def main():
	raise ValueError
$python$;
SELECT check_linecache_clear();

-- should show the "raise TypeError" in the traceback string.
-- adjust Postgres._pl_eox to *not* clear the linecache to see how it operated before
CREATE OR REPLACE FUNCTION check_linecache_clear() RETURNS VOID LANGUAGE python AS
$python$
def main():
	raise TypeError
$python$;
SELECT check_linecache_clear();
