How to handle errors in ColdFusion stored procedure results? - sql-server-2008

I use ColdFusion to call stored procedure to either insert or update users data. These two transactions are separated in two procedures. My SQL code should return row-count 1 or 0 depends if transaction was successful or not. If transaction failed I'm wondering what is the best way to handle errors in that case? Both cfstoredproc are wraped in try/catch block but in case when error occured in SQL procedure my Count variable in result set will return 0 and try/catch won't register error returned from the procedure. Here is my code example:
<cftry>
<cfif trim(arguments.process) EQ "Insert">
<cfstoredproc procedure="InsertRec" datasource="#dsn#">
<cfprocparam dbvarname="#Status" value="#trim(arguments.status)#" cfsqltype="cf_sql_bit" />
<cfprocparam dbvarname="#Code" value="#trim(arguments.frmcode)#" cfsqltype="cf_sql_char" maxlength="2" null="#!len(trim(arguments.code))#" />
<cfprocparam dbvarname="#Name" value="#trim(arguments.name)#" cfsqltype="cf_sql_varchar" maxlength="50" null="#!len(trim(arguments.name))#" />
<cfprocresult name="Result"/>
</cfstoredproc>
<cfelse>
<cfstoredproc procedure="UpdateRec" datasource="#dsn#">
<cfprocparam dbvarname="#Status" value="#trim(arguments._status)#" cfsqltype="cf_sql_bit" />
<cfprocparam dbvarname="#Code" value="#trim(arguments.code)#" cfsqltype="cf_sql_char" maxlength="2" null="#!len(trim(arguments.code))#" />
<cfprocparam dbvarname="#Name" value="#trim(arguments.name)#" cfsqltype="cf_sql_varchar" maxlength="50" null="#!len(trim(arguments.name))#" />
<cfprocresult name="Result"/>
</cfstoredproc>
</cfif>
<cfset local.fnResults = {
status : "200",
message : "Record successully saved!",
recCount : Result.Count
}>
<cfcatch type="any">
<cfset local.fnResults = {
error : cfcatch, <!--- I use this just for testing purpose. --->
status : "400",
message : "Error! Please contact your administrator."
}>
</cfcatch>
</cftry>
Code above returns Count column/variable as I mentioned already from Result set. If process sucesfully executed user will be notified with the message. If something is wrong I would like to send them a message that is in my catch block. Here is SQL code:
CREATE PROCEDURE [dbo].[InsertRec]
#Status BIT = NULL,
#Name VARCHAR(50) = NULL,
#Code CHAR(2) = NULL
AS
SET NOCOUNT ON
SET XACT_ABORT ON
BEGIN TRY
BEGIN
INSERT INTO dbo.Books(
Status,Name,Code
)
VALUES(
#Status,#Name,#Code
)
SELECT ##ROWCOUNT AS Count;
END
END TRY
BEGIN CATCH
SELECT
##ROWCOUNT AS Count,
ERROR_PROCEDURE() AS ErrorProcedure,
ERROR_LINE() AS ErrorLine,
ERROR_NUMBER() AS ErrorNumber,
ERROR_MESSAGE() AS ErrorMessage,
CURRENT_TIMESTAMP AS DateTime
END CATCH
I only showed Insert SQL code since Update procedure is the same. When I use I would see this message if something went wrong. This is just and example when I tried to insert primary key that already exist on purpose:
COUNT
0
DATETIME
2018-08-24 07:00:01.58
ERRORLINE
16
ERRORMESSAGE
Violation of PRIMARY KEY constraint 'PK_Books'. Cannot insert duplicate key in object 'dbo.Books'. The duplicate key value is (44).
ERRORNUMBER
2627
ERRORPROCEDURE
InsertRec
ColdFusion is not catching this error. Is there a way to catch this error when I use ColdFusion to call stored procedure in result set?

As #Ageax mentions, you're handing the error in your stored procedure, so from CF's point of view, the stored procedure executed correctly.
I tend to use a standard message packet for all calls to stored procedures when they do not return a record set. Add two local variables to your procs and update as needed:
DECLARE #SUCCESS BIT = 1;
DECLARE #MSG VARCHAR(50) = 'Some default success message.'
Update those values in your CATCH statement:
BEGIN CATCH
SET #SUCCESS = 0;
SET #MSG = 'There was a problem ...';
SELECT
#SUCCESS as success,
#MSG as message,
##ROWCOUNT AS Count,
ERROR_PROCEDURE() AS ErrorProcedure,
ERROR_LINE() AS ErrorLine,
ERROR_NUMBER() AS ErrorNumber,
ERROR_MESSAGE() AS ErrorMessage,
CURRENT_TIMESTAMP AS DateTime
END CATCH
But also return these values after the CATCH so that the proc always returns a status.
SELECT
#SUCCESS as success,
#MSG as message
This will give you a user-friendly message, as well as the actual SQL error (when it happens) that can be logged as needed.

Related

Catch unique index violation and raise_application_error

Is it possible to catch unique index violation and raise_application_error. We are trying with a blanket trigger and catching the exceptions, however we are always getting oracle exception.
ORA-00001: unique constraint (TEST_UNIQUE_INDEX) violated
We have a FUNCTION-BASED constraint on the table.
CREATE TABLE TEST_CONSTRAINT(
"ID" NUMBER NOT NULL ENABLE,
"LOCATION" VARCHAR2(20) NOT NULL,
"DEPT" VARCHAR2(20) NOT NULL,
"RECORD" NUMBER NOT NULL)
/
CREATE UNIQUE INDEX TEST_UNIQUE_INDEX ON TEST_CONSTRAINT (
CASE "RECORD" WHEN 1 THEN "LOCATION" ELSE NULL END,
CASE "RECORD" WHEN 1 THEN "DEPT" ELSE NULL END)
/
before update trigger code
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
ERROR_MESSAGE := SQLERRM;
DBMS_OUTPUT.PUT_LINE('SQLERRM '|| ERROR_MESSAGE);
WHEN OTHERS THEN
ERROR_MESSAGE := SQLERRM;
DBMS_OUTPUT.PUT_LINE('SQLERRM '|| ERROR_MESSAGE);
END;
test schema here
Edit 1:
The requirement here is to impose selective uniqueness i.e. only one set of location/dept can be set as a record (being a boolean in app). In all other cases, we have to generate a raise_application_error.
The trigger would be executed BEFORE the update happens. Since there would be no exception during its execution (all it does is print the system date), no exception would be raised. Then, the update is actually executed, and that's when you get the error message from the database.
To catch the exception that occurs because of the UPDATE statement, the UPDATE statement itself must be enclosed within a PL/SQL block, as below.
DECLARE
V_DATE DATE;
ERROR_MESSAGE VARCHAR2(1000);
BEGIN
SELECT SYSDATE INTO V_DATE FROM DUAL;
DBMS_OUTPUT.PUT_LINE('SYSDATE '|| V_DATE);
UPDATE TEST_CONSTRAINT SET RECORD = 1 WHERE ID = 3;
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
ERROR_MESSAGE := SQLERRM;
DBMS_OUTPUT.PUT_LINE('Error occurred. SQLERRM '|| ERROR_MESSAGE);
WHEN OTHERS THEN
ERROR_MESSAGE := SQLERRM;
DBMS_OUTPUT.PUT_LINE('SQLERRM '|| ERROR_MESSAGE);
END;
/
Reference:
Handling PL/SQL Errors on Oracle® Database PL/SQL Language Reference

UNIQUE constraint in MySQL rows

I am performing an INSERT query in my database as such:
$query = "INSERT IGNORE INTO user VALUES ('', 0, $safe_email, '$hashed_password')";
$result = $db->query($query);
The 3rd row in the db table is email which I have set a unique constraint to.
If I try to insert a duplicate email, with the above query notice I have the INSERT IGNORE which won't insert the record if it is a duplicate to one that already exists in the db, however it won't give an error or any kind of indication that a duplicate record is trying be inserted.
I want to show a nice error message if a duplicate is found but with the INSERT IGNORE I am struggling to do this because it won't show an error it will just ignore the query.
So I need something like:
IF (duplicate entry found in db){
echo "User already exists";
}
END IF
Use normal insert query and implement the query in try-catch statement. 'Insert' query will fail if you try to insert same email since it is unique constraint. So you can catch the exception as the 'Insert' query fails.
Example:
try {
"your insert query";
} catch (Exception $e) {
"your insert failure exception"
}
NB: you can catch all the exceptions that occurred during the execution of insert query which will be more helpful
As per documentation on INSERT ... IGNORE:
If you use the IGNORE keyword, errors that occur while executing the INSERT statement are ignored. For example, without IGNORE, a row that duplicates an existing UNIQUE index or PRIMARY KEY value in the table causes a duplicate-key error and the statement is aborted. With IGNORE, the row still is not inserted, but no error occurs. Ignored errors may generate warnings instead, although duplicate-key errors do not.
You can issue a show warnings; or any compatible similar statement from your scripting language or SQL interface. If it returns one or more such warnings, may be one of them can be on such insertion issues. Using them, you can show proper error or consoling message to the end user.
Edit 1:
... but ... how do I throw my own error message instead of the default exception when using INSERT without the IGNORE.
You can define a BEFORE INSERT trigger to identify duplicate data row and throw custom error message when found one.
Example:
delimiter //
drop trigger if exists bi_table_trigger //
CREATE TRIGGER bi_table_trigger BEFORE INSERT ON table
FOR EACH ROW
BEGIN
declare rowCount int default 0;
declare error_message varchar(1024) default '';
SELECT COUNT(1) into rowCount FROM table
WHERE email = NEW.email;
IF ( rowCount > 0 ) THEN -- if( rowCount ) -- too works
set error_message =
concat( error_message, 'User with email \'' );
set error_message =
concat( error_message, NEW.email, '\' ' );
set error_message =
concat( error_message, 'already exists' );
-- throw the error
-- User with email '?' already exists ( example )
signal sqlstate 1062 set message_text = error_message;
END IF;
END;//
delimiter ;

Need help to execute sql scripts within stored procedure

Need help as how I can trap any errors related to executing a sql script in a stored procedure.
select sopScript
from M_SopInsert
where soptype = #soptype and sopnumbe = #sopnumbe and lnitmseq = #lnitmseq
If result_count > 0 //if result from above sql query is >0
exec sopScript //loop through the record set and execute sopscript for every record.
Note: sopscript here contains scripts like :
update customerMaster
set custname='abc'
where custid=100`"
This is how we do it:
Wrap the procedure steps in a TRY and TRANSACTION. Then the individual executions in a TRY
DECLARE #lRollback bit=0
DECLARE #ErrTable TABLE (ErrNumber int,ErrSeverity int,ErrProc varchar(MAX),ErrLine int,ErrMsg varchar(MAX)) --table variable to collect errors.
BEGIN TRY -- outside begin try
BEGIN TRANSACTION -- wrap transaction
....
BEGIN TRY
...
END TRY
BEGIN CATCH
{ERROR CATCH - see below}
END CATCH
END TRY
BEGIN CATCH
SET #lRollback=1
{ERROR CATCH - see below}
ROLLBACK
BEGIN TRY
INSERT INTO errorTable (importId,errNumber,errSeverity,errProc,errLine,errMsg) --This is the db default error collection table
SELECT DISTINCT #importId,ErrNumber,ErrSeverity,ErrProc,ErrLine,ErrMsg FROM #ErrTable
END TRY
RETURN -1
END CATCH
Anytime you want to catch an error in the procedure, use this ERROR CATCH:
INSERT INTO #ErrTable (ErrNumber,ErrSeverity,ErrProc,ErrLine,ErrMsg)
SELECT
ERROR_NUMBER() AS ErrorNumber
,ERROR_SEVERITY() AS ErrorSeverity
,ERROR_PROCEDURE() AS ErrorProcedure
,ERROR_LINE() AS ErrorLine
,ERROR_MESSAGE() AS ErrorMessage;
Misread the question originally.
try using
declare #sopScript varchar(1000)
select sopScript
into #ControlTbl
from M_SopInsert
where soptype = #soptype and sopnumbe = #sopnumbe and lnitmseq = #lnitmseq
while exists (select * from #ControlTbl)
begin
select top 1 #sopScript = sopScript
from #ControlTbl
begin try
exec executesql #sopScript = sopScript
end try
begin catch
*do something*
end catch
delete from #ControlTbl
where sopScript = #sopScript
end

Log exception info in MySQL stored procedure

As I know, I can define exception handler in MySQL stored procedure, but seems I can't catch the exception message in the handler and write a log in a table for debugging purpose. I just want to know is there method to log exception code and message in MySQL store procedure?
You can catch the message, the error code, the sql state, ..., with the GET DIAGNOSTICS statement, in 5.6.4
See
http://dev.mysql.com/doc/refman/5.6/en/get-diagnostics.html
I don't remember what tutorial I copied this from. However, it has helped immensely in the versions of MySQL prior to 5.6. Thanks to whomever I learned this from!
Step 1 : Create a debug_log table. This will hold everything your write to it.
CREATE TABLE `debug_log` (
`debug_log_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`msg` varchar(512) NOT NULL,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`debug_log_id`)
) ENGINE=MyISAM
Step 2 : Create a stored procedure for adding info to the debug_log table.
DELIMITER $$
USE `your_db_name_here`$$
DROP PROCEDURE IF EXISTS `log_debug`$$
CREATE DEFINER=`ss7admin`#`%` PROCEDURE `log_debug`(IN lastMsg VARCHAR(512))
BEGIN
INSERT INTO debug_log (msg) VALUES (lastMsg);
END$$
DELIMITER ;
Step 3 : Add a handler in your real stored procedure.
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
CALL log_debug(
CONCAT
(
now(),
' : Something went horribly wrong!',
'.'
)
);
CALL log_debug('Exiting now');
SET outReturnCode = 1;
END;
You cannot catch the message, but you can catch the error code.
Here is an example of how to deal with "Duplicate entry" (PK, UK constraint):
CREATE PROCEDURE sp_do_insert(
IN in_param1 int,
IN in_param2 int,
OUT out_status tinyint
)
BEGIN
DECLARE CONTINUE HANDLER FOR 1062 SET out_status = 1;
SET out_status = 0;
INSERT INTO tbl(field1, fiel2)
VALUES (in_param1, in_param2);
END;
If tbl has a UK constraint on field1 and you try to insert an existing value once again you will not get an error. Nothing will be inserted and status will be equal to 1.
You can also add other handlers for other error codes. And you will always know what is the error from out_status value and you will know "error message" from error_code (in handler).
You can try to play with show warnings (it shows errors/warnings for the last query) in case if out_status <> 0.
Hope it helps.

MySql 5.1.32: call another procedure within a stored procedure and set variable

I'm new at creating and working with stored procedures.
After spending several hours on trying, reading tutorials (and yes reading all the related questions at stackoverflow :-) ) I'm stuck.
This works fine:
PROCEDURE GetAgent(IN AgentName VARCHAR(50), OUT AgentID SMALLINT(6))
BEGIN
IF EXISTS (SELECT id FROM tbl_lookup WHERE value = AgentName AND cat = 'agent') THEN
SELECT id FROM tbl_lookup WHERE value = AgentName AND cat = 'agent';
ELSE
INSERT INTO tbl_lookup(cat, value) VALUES ('agent', AgentName);
SELECT id FROM tbl_lookup WHERE value = AgentName AND cat = 'agent';
END IF;
END;
When called like:
Call GetAgent("Firefox 3.6.18", #AgentID);
It gives the proper response: "2"
So far so good. So let's get that into another procedure: (GetOS does the same thing, left out tot minimize reading :-)
PROCEDURE SetSessionInfo(IN OsName VARCHAR(50), IN AgentName VARCHAR(50), IN SessionID BIGINT(30), OUT SessionInfoID SMALLINT(6))
BEGIN
DECLARE nw_AgentID SMALLINT;
DECLARE nw_OSID SMALLINT;
CALL GetOs(OsName, #OsID);
SET NW_OSID = #OSID;
CALL GetAgent(AgentName, #AgentID);
SET NW_AgentID = #AgentID;
IF EXISTS (SELECT id FROM tbl_session_info WHERE session = SessionID) THEN
SELECT id AS SessionInfoID FROM tbl_session_info WHERE session = SessionID;
ELSE
INSERT INTO tbl_session_info(session, agent_id, os_id) VALUES (SessionID, GetAgent(AgentName, #AgentID), GetOs(OsName , #OsID));
SELECT id AS SessionInfoID FROM tbl_session_info WHERE session = SessionID;
END IF;
END;
When called with
Call SetSessionInfo("Windows XP", "Firefox 3.6.18", 857264713, #SessionInfoID)
I get the answer "3" (proper response from GetOS), then the procedure stops and does not insert anything.
After installing Toad I saw the reason: an error: "FUNCTION GetAgent does not exist"
Well, it is not a function, it's a procedure.
So basicly, my question:
How do I call another procedure within a stored procedure and set a variable with the result?
This is why you are getting "FUNCTION GetAgent does not exist" error:
INSERT INTO tbl_session_info(session, agent_id, os_id)
VALUES (SessionID, GetAgent(AgentName, #AgentID), GetOs(OsName , #OsID));
You are trying to call GetAgent as a function (while it is a procedure). But you have already got Agent and OS IDs into variables. Just use them:
INSERT INTO tbl_session_info(session, agent_id, os_id)
VALUES (SessionID, NW_AgentID, NW_OSID);