PostgreSQL custom exceptions? - exception

In Firebird we can declare custom exceptions like so:
CREATE EXCEPTION EXP_CUSTOM_0 'Exception: Custom exception';
these are stored at the database level. In stored procedures, we can raise the exception like so:
EXCEPTION EXP_CUSTOM_0 ;
Is there an equivalent in PostgreSQL ?

No, not like this. But you can raise and maintain your own exceptions, no problem:
CREATE TABLE exceptions(
id serial primary key,
MESSAGE text,
DETAIL text,
HINT text,
ERRCODE text
);
INSERT INTO exceptions (message, detail, hint, errcode) VALUES ('wrong', 'really wrong!', 'fix this problem', 'P0000');
CREATE OR REPLACE FUNCTION foo() RETURNS int LANGUAGE plpgsql AS
$$
DECLARE
row record;
BEGIN
PERFORM * FROM fox; -- does not exist, undefined_table, fail
EXCEPTION
WHEN undefined_table THEN
SELECT * INTO row FROM exceptions WHERE id = 1; -- get your exception
RAISE EXCEPTION USING MESSAGE = row.message, DETAIL = row.detail, HINT = row.hint, ERRCODE = row.errcode;
RETURN 1;
END;
$$
SELECT foo();
Offcourse you can also have them hardcoded in your procedures, that's up to you.

Related

oracle pl/sql exception handling for input parameter (bad argument i.e. non numeric value for number)

I have stored procedure with input parameter of type number.
CREATE OR REPLACE PROCEDURE my_procedure (p_x number)
AS
I included exception handling code as below, but that do not handle following:
execute my_procedure ('sads')
EXCEPTION
WHEN VALUE_ERROR THEN
DBMS_OUTPUT.PUT_LINE('SQLERRM: ' || SQLERRM);
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('SQLERRM: ' || SQLERRM);
is there any way to change exception for bad arguments?
The error will happen when the procedure is invoked, not inside of the procedure so you can't capture the error as an exception within that procedure itself. A possible solution would be use an additional procedure that validates the arguments. In the example code below the procedure so_arg_test.my_procedure accepts a VARCHAR2 but it will only invoke my_procedure_hidden if the argument actually is a number. For anything other it will raise a value error.
CREATE OR REPLACE PACKAGE so_arg_test AS
PROCEDURE my_procedure (
p_x IN VARCHAR2
);
END so_arg_test;
/
CREATE OR REPLACE
PACKAGE BODY SO_ARG_TEST AS
procedure my_procedure_hidden (p_x IN NUMBER) AS
BEGIN
-- your code
dbms_output.put_line('Inside my_procedure_hidden');
END my_procedure_hidden;
procedure my_procedure (p_x IN VARCHAR2)
AS
BEGIN
IF VALIDATE_CONVERSION(p_x AS NUMBER) = 0 THEN
RAISE VALUE_ERROR;
END IF;
my_procedure_hidden (p_x => my_procedure.p_x);
END my_procedure;
END SO_ARG_TEST;
/

How to write plsql exception satatement to ask user to give input in specific format

CREATE OR REPLACE PROCEDURE get_productdetails(
p_reqid OUT requirement.req_id%type,
p_pid OUT requirement.p_id%type,
p_rstaffid OUT requirement.r_staff_id%type,
p_demand requirement.demand%type)
IS
CURSOR c_demand IS
SELECT req_id,p_id,r_staff_id from requirement where demand = upper(p_demand);
BEGIN
FOR i in c_demand Loop
DBMS_OUTPUT.PUT_LINE('Requirement ID :'||i.req_id);
DBMS_OUTPUT.PUT_LINE('Product ID'||i.p_id);
DBMS_OUTPUT.PUT_LINE('Staff ID :'||i.r_staff_id);
END LOOP;
END get_productdetails;
User must enter demand in 'HIGH/LOW/AVG only otherwise it should throw exception asking to enter data in that format
Can you please help me to write an exception accordingly
As I don't have access to your table structure, the code below is untested. A good place to start is the official oracle documentation. In your case, a user-defined exception is needed, which you need to declare in the declaration section, then RAISE and finally catch in the EXCEPTION block.
CREATE OR REPLACE PROCEDURE get_productdetails(
p_reqid OUT requirement.req_id%type,
p_pid OUT requirement.p_id%type,
p_rstaffid OUT requirement.r_staff_id%type,
p_demand requirement.demand%type)
IS
-- declare the user-defined exception
invalid_input EXCEPTION;
CURSOR c_demand IS
SELECT req_id,p_id,r_staff_id from requirement where demand = upper(p_demand);
BEGIN
IF p_demand NOT IN ('HIGH','LOW','AVG') THEN
-- raise the exception if p_demand not one of the 3 accepted values
RAISE invalid_input;
END IF;
FOR i in c_demand Loop
DBMS_OUTPUT.PUT_LINE('Requirement ID :'||i.req_id);
DBMS_OUTPUT.PUT_LINE('Product ID'||i.p_id);
DBMS_OUTPUT.PUT_LINE('Staff ID :'||i.r_staff_id);
END LOOP;
EXCEPTION WHEN invalid_input THEN
dbms_output.put_line('Only HIGH/LOW/AVG are valid input');
END get_productdetails;

Token "(" is invalid error calling a function that assigns into a custom type containing a json type

I'm decoding a JWT with Postgresql. The function test is a simplified version of what it actually does. Here it serves to show an error message that I'm getting too.
CREATE TYPE parts AS (header json, payload json, valid boolean);
CREATE OR REPLACE FUNCTION verify(token text, secret text, algorithm text DEFAULT 'HS256')
RETURNS parts AS $$
SELECT
'{"alg": "HS256","typ": "JWT"}'::json AS header,
'{"id": 1, "exp": 1524683318}'::json AS payload,
TRUE AS valid;
$$ LANGUAGE sql;
CREATE OR REPLACE FUNCTION test(token text)
RETURNS parts AS $$
DECLARE
jwt_parts parts;
BEGIN
SELECT verify(test.token, 'secret')
INTO jwt_parts;
RETURN jwt_parts;
END
$$ LANGUAGE plpgsql;
After running SELECT test('xx') this is the error I'm getting (on Postgres v10.1):
ERROR: invalid input syntax for type json
DETAIL: Token "(" is invalid.
CONTEXT: JSON data, line 1: (...
PL/pgSQL function test(text) line 5 at SQL statement
I would like to SELECT ... INTO my custom parts type. I have tried many ways to rephrase, but I suspect this can be done much simpler than what I've tried so far.
PS: The real function test(token text) I'm using is larger and needs the plpgsql language.
PS2: Just to make sure that the currently given answers make sense to people visiting this question. The original version of my question had a test function like this:
CREATE OR REPLACE FUNCTION test(token text)
RETURNS parts AS $$
DECLARE
jwt_parts parts;
BEGIN
SELECT ('{"alg": "HS256","typ": "JWT"}', '{"id": 1, "exp": 1524683318}', TRUE)
INTO jwt_parts;
RETURN jwt_parts;
END
$$ LANGUAGE plpgsql;
The parentheses cause you to select a single column (of type row), where you actually need three columns. For example:
select (1,2,3)
>>
row
--------
(1,2,3)
(1 row)
So omit the parentheses after select:
SELECT ('{"alg": "HS256","typ": "JWT"}', '{"id": 1, "exp": 1524683318}', TRUE)
^^^ ^^^
In reply to your comment, the error in your new query comes from:
SELECT verify('xxx', 'yyy')
INTO jwt_parts;
This again tries to assign a single column to a three-column type. Instead, you can use:
SELECT * FROM verify('xxx', 'yyy')
INTO jwt_parts;
You shouldn't select a tuple but a regular row (remove parenthesis):
SELECT '{"alg": "HS256","typ": "JWT"}', '{"id": 1, "exp": 1524683318}', TRUE

hsqldb procedure with date Input parameter

I need to write a test for some download operation. This operation call procedure from MSSQL database, take result set and java make some stuf. For test I use hsqldb.
My procedure:
CREATE PROCEDURE map.Get1(IN packageName varchar(100),
IN downloadDate DATE)
READS SQL DATA DYNAMIC RESULT SETS 1 BEGIN ATOMIC
DECLARE result CURSOR WITH RETURN FOR SELECT * FROM map.tvschedule FOR READ ONLY;
OPEN result;
END
This procedure wan't work, i have an exception
call map.GET1('Genre','2018-03-10');
[42561][-5561] incompatible data type in conversion
java.lang.RuntimeException: org.hsqldb.HsqlException: incompatible data type
in conversion
But this(without date parameter) work well:
CREATE PROCEDURE map.Get1(IN packageName varchar(100))
READS SQL DATA DYNAMIC RESULT SETS 1 BEGIN ATOMIC
DECLARE result CURSOR WITH RETURN FOR SELECT * FROM map.tvschedule FOR READ ONLY;
OPEN result;
END
call map.GET1('Genre');
first needed row
second needed row
I am not going to use input parameter, but i need this procedure to be looking i am going to.
My question is How to use date input parameter with hsqldb procedures?
UPDATE1:
I used TO_DATE and now it works well, but i have no data in my result set, my java code is:
try (CallableStatement callableStatement = connection.prepareCall("{ call
map.GetGenreProtocol( ?, ? ) }")) {
callableStatement.setString(1, packageName);
callableStatement.setDate(2, date);
callableStatement.execute();
ResultSet resultSet = callableStatement.getResultSet();
while (resultSet.next()) {
Interval Interval = new Interval();
Interval.setDuration(resultSet.getInt("duration"));
Interval.setMappingTargetId(resultSet.getInt("mappingTargetId"));
Interval.setGenreId(resultSet.getInt("genreId"));
Interval.setStart(resultSet.getLong("start"));
Interval.setCategoryId(resultSet.getInt("categoryId"));
Interval.setCategoryName(resultSet.getString("categoryName"));
Interval.setGenreName(resultSet.getString("genreName"));
Interval.setDescription(resultSet.getString("description"));
Intervals.add(Interval);
}
}
Use the TO_DATE function.
For example:
call map.GET1('Genre', TO_DATE('2018-03-10', 'YYYY-MM-DD'));
I guess you need to create a function that returns a table instead of a procedure:
CREATE FUNCTION map.Get1(IN packageName VARCHAR(100),
IN downloadDate DATE)
RETURNS TABLE(.....)
READS SQL DATA
BEGIN ATOMIC
....
END;

How store as JSON the new data inside a trigger in PostgreSQL?

I try to do as explained in:
https://wiki.postgresql.org/wiki/Audit_trigger
Auditing values as JSON
For PostgreSQL 9.2, or 9.1 with the fantastic json_91 addon, you can
log the old and new values in the table as structured json instead of
flat text, giving you much more power to query your audit history.
Just change the types of v_old_data, v_new_data, original_data and
new_data from TEXT to json, then replace ROW(OLD.) and ROW(NEW.)
with row_to_json(OLD) and row_to_json(NEW) respectively.
However this get me a error:
CREATE OR REPLACE FUNCTION add_log (name text, Action TEXT, data jsonb, OUT RETURNS BOOLEAN)
AS $$
BEGIN
RETURNS = true;
END;
$$
LANGUAGE 'plpgsql';
CREATE OR REPLACE FUNCTION log_city() RETURNS TRIGGER AS
$$
DECLARE
v_new_data jsonb;
BEGIN
IF (TG_OP = 'UPDATE') THEN
RETURN NEW;
ELSIF (TG_OP = 'INSERT') THEN
v_new_data := row_to_jsonb(NEW);
EXECUTE add_log('City', 'City.New', v_new_data);
RETURN NEW;
END IF;
RETURN NULL; -- result is ignored since this is an AFTER trigger
END;
$$ LANGUAGE plpgsql;
INSERT INTO Location (city, state, country) values ('a', 'b' , 'c')
It say:
ERROR: function row_to_jsonb(location) does not exist
If I put v_new_data := row_to_jsonb(ROW(NEW)); then I get:
ERROR: function row_to_jsonb(record) does not exist
It's stated in the documentation that
Table 9-42 shows the functions that are available for creating json
and jsonb values. (There are no equivalent functions for jsonb, of the
row_to_json and array_to_json functions. However, the to_jsonb
function supplies much the same functionality as these functions
would.)
thus it's row_to_json that has to be used. a row_to_jsonb does not exists but row_to_json produces the desired result for the JSONB type as well.