-- -- Error model: how Ruby exceptions and PostgreSQL errors interact, what is -- preserved when a database error is caught, and what is not. -- -- A specific exception class's message is preserved when it propagates out. CREATE FUNCTION es_custom() RETURNS int LANGUAGE plruby AS $$ raise ArgumentError, 'custom failure' $$; SELECT es_custom(); ERROR: custom failure CONTEXT: PL/Ruby function "es_custom" -- A caught database error: it is a StandardError, its message names the -- offending object, its SQLSTATE is exposed, and the session keeps working -- afterwards. CREATE FUNCTION es_caught() RETURNS text LANGUAGE plruby AS $$ begin spi_exec('select * from missing_relation_xyz') 'no error' rescue => e "std=#{e.is_a?(StandardError)} names_it=#{e.message.include?('missing_relation_xyz')} sqlstate=#{e.sqlstate}" end $$; SELECT es_caught(); es_caught --------------------------------------- std=true names_it=true sqlstate=42P01 (1 row) -- The SQLSTATE reflects the error class (division_by_zero here). A -- PLRuby::Error not backed by a database error (elog) has a nil sqlstate, -- and an ordinary Ruby exception has no sqlstate method at all. CREATE FUNCTION es_sqlstate() RETURNS text LANGUAGE plruby AS $$ db = begin spi_exec('select 1 / 0') 'no error' rescue => e e.sqlstate end pl = begin elog('ERROR', 'synthetic') rescue PLRuby::Error => e e.sqlstate.inspect end rb = begin raise 'plain ruby error' rescue => e e.respond_to?(:sqlstate) ? 'has method' : 'no method' end "db=#{db} elog=#{pl} ruby=#{rb}" $$; SELECT es_sqlstate(); es_sqlstate ---------------------------------- db=22012 elog=nil ruby=no method (1 row) -- An ensure block runs on both the success and the rescue path. CREATE FUNCTION es_ensure(boolean) RETURNS text LANGUAGE plruby AS $$ log = [] r = begin raise 'boom' if args[0] 'ok' rescue 'rescued' ensure log << 'ensured' end "#{r}:#{log.join}" $$; SELECT es_ensure(false); es_ensure ------------ ok:ensured (1 row) SELECT es_ensure(true); es_ensure ----------------- rescued:ensured (1 row) -- pg_raise can attach DETAIL, HINT, and a specific SQLSTATE... CREATE FUNCTION es_structured() RETURNS void LANGUAGE plruby AS $$ pg_raise('ERROR', 'order rejected', detail: 'order 42 exceeds the credit limit', hint: 'raise the limit or split the order', sqlstate: 'P0001') $$; SELECT es_structured(); ERROR: order rejected DETAIL: order 42 exceeds the credit limit HINT: raise the limit or split the order CONTEXT: PL/Ruby function "es_structured" -- ...and a caller that rescues the resulting database error sees all three. CREATE FUNCTION es_catch_structured() RETURNS text LANGUAGE plruby AS $$ begin spi_exec('select es_structured()') 'no error' rescue PLRuby::Error => e "state=#{e.sqlstate} detail=#{e.detail} hint=#{e.hint}" end $$; SELECT es_catch_structured(); es_catch_structured ---------------------------------------------------------------------------------------------- state=P0001 detail=order 42 exceeds the credit limit hint=raise the limit or split the order (1 row) -- Non-error levels emit the fields directly. CREATE FUNCTION es_notice_fields() RETURNS void LANGUAGE plruby AS $$ pg_raise('NOTICE', 'heads up', detail: 'the details', hint: 'the hint') $$; SELECT es_notice_fields(); NOTICE: heads up DETAIL: the details HINT: the hint es_notice_fields ------------------ (1 row) -- An invalid sqlstate is rejected at the call. CREATE FUNCTION es_badstate() RETURNS void LANGUAGE plruby AS $$ pg_raise('ERROR', 'nope', sqlstate: 'abc') $$; SELECT es_badstate(); ERROR: pg_raise: invalid sqlstate "abc" CONTEXT: PL/Ruby function "es_badstate" -- pg_raise and elog both reject an unknown level with a PL/Ruby error. CREATE FUNCTION es_badraise() RETURNS void LANGUAGE plruby AS $$ pg_raise('BOGUS', 'nope') $$; SELECT es_badraise(); ERROR: pg_raise: incorrect log level "BOGUS" CONTEXT: PL/Ruby function "es_badraise" CREATE FUNCTION es_badelog() RETURNS void LANGUAGE plruby AS $$ elog('BOGUS', 'nope') $$; SELECT es_badelog(); ERROR: elog: unrecognized level "BOGUS" CONTEXT: PL/Ruby function "es_badelog"