Related
I'm having a hard-time debugging a stored procedure called from BizTalk.
In a prior thread, someone suggested using sp_trace_generateevent.
[Use SQL Debugger when stored proc called by an external process
Since I need to display a variable, I came up with the following, and it works, I can see the value in SQL Profiler (with EventClass=UserConfigurable:0"
Declare #message nvarchar(128)
Set #message = 'Hello World 2 '
exec sp_trace_generateevent #event_class=82, #userinfo = #message
But when I put it in a "BEGIN CATCH" in the problem stored proc, I don't see anything in the profiler:
BEGIN CATCH
DECLARE #message nvarchar(128)
SET #message = LTRIM(STR(ERROR_MESSAGE()))
exec sp_trace_generateevent #event_class=82, #userinfo = #message
SET #CatchErrors =
'Catch: [LTL].[CreateShipment] - ErrorNumber: '+LTRIM(STR(ERROR_NUMBER()))
+ ' ErrorSeverity: '+LTRIM(STR(ERROR_SEVERITY()))
+ ' ErrorState: '+LTRIM(STR(ERROR_STATE()))
+ ' ErrorProcedure: '+LTRIM(STR(ERROR_PROCEDURE()))
+ ' ErrorLine: '+LTRIM(STR(ERROR_LINE()))
+ ' ErrorMessage: '+LTRIM(STR(ERROR_MESSAGE()))
END CATCH
So then I put a Catch within the Catch:
BEGIN CATCH
BEGIN TRY
DECLARE #message nvarchar(128)
SET #message = LTRIM(STR(ERROR_MESSAGE()))
exec sp_trace_generateevent #event_class=82, #userinfo = #message
END TRY
BEGIN CATCH
SET #Message = 'Error in sp_trace_generateevent'
END CATCH
SET #CatchErrors =
'Catch: [LTL].[CreateShipment] - ErrorNumber: '+LTRIM(STR(ERROR_NUMBER()))
+ ' ErrorSeverity: '+LTRIM(STR(ERROR_SEVERITY()))
+ ' ErrorState: '+LTRIM(STR(ERROR_STATE()))
+ ' ErrorProcedure: '+LTRIM(STR(ERROR_PROCEDURE()))
+ ' ErrorLine: '+LTRIM(STR(ERROR_LINE()))
+ ' ErrorMessage: '+LTRIM(STR(ERROR_MESSAGE()))
END CATCH
And now I can see "SET #Message = 'Error in sp_trace_generateevent' " in the profiler, but I really need to see the reason for the error.
The problem I've having cannot be reproduced when testing in SSMS, only when I call from BizTalk. My intent is to bubble the #CatchErrors (as an output parameter) back to BizTalk, but it's not working either.
Also - BizTalk is running with a user that has SQL SysAdmin (it's on my development machine).
Also same result when using master..sp_tracegeneratedevent
Based on #Jeroen's reply, I switched to this, but still getting some error caught.
DECLARE #message nvarchar(128)
BEGIN TRY
SET #message = Convert(nvarchar(128),SUBSTRING(ERROR_MESSAGE(),1,128))
exec sp_trace_generateevent #event_class=82, #userinfo=#message
END TRY
Update #1: This is driving me batty. When I test in SQL it works, but when I test from BizTalk it doesn't. So I really want a debug feature. I now have catch on my catches on my catches... and they are all catching and I don't know why. Same code works fine in the divide by zero simple example. To further complicate, this is a stored proc, called by a stored proc, called by BizTalk. If I catch the error, I should be able to return it to BizTalk in the output parameter called #CatchErrors in my main and sub-stored proc.
BEGIN CATCH
DECLARE #message nvarchar(128)
BEGIN TRY
SET #message = Convert(nvarchar(128),SUBSTRING(ERROR_MESSAGE(),1,128))
exec sp_trace_generateevent #event_class=82, #userinfo=#message
END TRY
BEGIN CATCH
SET #Message = 'Error in sp_trace_generateevent'
END CATCH
BEGIN TRY
SET #CatchErrors =
'Catch: [RG].[CreateShipment] - ErrorNumber: '+CAST(ERROR_NUMBER() AS VARCHAR(35))
+ ' ErrorSeverity: '+CAST(ERROR_SEVERITY() AS VARCHAR(35))
+ ' ErrorState: '+CAST(ERROR_STATE() AS VARCHAR(35))
+ ' ErrorProcedure: '+CAST(IsNull(ERROR_PROCEDURE(),'') AS VARCHAR(200))
+ ' ErrorLine: '+CAST(ERROR_LINE() AS VARCHAR(35))
+ ' ErrorMessage: '+CAST(ERROR_MESSAGE() AS VARCHAR(4000))
END TRY
BEGIN CATCH
BEGIN TRY
SET #Message = 'Error in Set #CatchErrors='
SET #CatchErrors =
'Catch: [LTL.CreateShipmentStopLineItem]- Error: ' + CAST(ERROR_MESSAGE() AS VARCHAR(4000))
END TRY
BEGIN CATCH
SET #Message = 'Error in Set #CatchErrors2'
END CATCH
END CATCH
END CATCH
Current Profiler Result:
Update #2 - Testing in SSMS:
I'm testing in SSMS, and none of the catches have issues. If i run this more than once it gets Violation of Primary Key in the Print statement.
Declare #shipstopline LTL.TT_ShipmentStopLineItem
DECLARE #messageID bigint
DECLARE #CatchErrorsResult varchar(max)
insert into #shipstopline values ('2', '1', 'Eggs','1','2','3','1','100','1','12','1','1','1','10','20','1')
EXEC LTL.CreateShipmentStopLineItem #MessageId = 2, #ShipmentStopID=1, #CreateBy=108004, #ShipmentStopLineItem=#shipstopline, #loopid=1, #catchErrors=#CatchErrorsResult OUT
select RowCreated, * from LTL.ShipmentStopLineItem order by LTL.ShipmentStopLineItem.RowCreated desc
print #CatchErrorsResult
Well, if you try this:
SET XACT_ABORT ON;
BEGIN TRY
SELECT 1 / 0;
END TRY
BEGIN CATCH
DECLARE #message nvarchar(128);
SET #message = LTRIM(STR(ERROR_MESSAGE()));
exec sp_trace_generateevent #event_class=82, #userinfo = #message
END CATCH
You'll get immediate feedback on what's wrong:
Msg 8114, Level 16, State 5, Line 8 Error converting data type
nvarchar to float.
And that's because that STR() call you've got is not the right thing to use -- STR formats floating-point values, and only those. (For flexible conversion, use FORMAT and/or CONCAT, the latter of which always implicitly converts things to strings without complaining.)
Aside from that, the first parameter of the stored procedure is #eventid, not #event_class (this is normally an error, but extended stored procedures are more flexible that way -- you could call the parameter #banana and it will still work). Still, for clarity's sake we should use the documented name. So:
SET XACT_ABORT ON;
BEGIN TRY
SELECT 1 / 0;
END TRY
BEGIN CATCH
DECLARE #message nvarchar(128) = ERROR_MESSAGE();
EXEC sp_trace_generateevent #eventid=82, #userinfo = #message;
END CATCH
And in my profiler, this produces a UserConfigurable:0 event with
Divide by zero error encountered.
Note that if there is no TRY / CATCH, you should still be able to get errors even without generating your own trace event, through the Exception event.
It turns out that BizTalk calls the Stored Proc twice, once with FmtOnly=On and once again with FmtOnly=Off. I was getting the invalid cast exception after the first call, so the Stored Proc was essentially not really executing. I was mislead by the profiler because of the FmtOnly=On, thinking that it was actually executing the statements I saw there. Thus the sp_TraceGenerateEvent doesn't actually run on the first pass with FmtOnly=On, but later when I got past the other errors, it works fine in the second pass with FmtOnly=Off.
Reference: Similar questoin I posted about why the SQL Profiler was looking different between an SSMS run and a BizTalk run of same stored proc: Can SQL Begin Try/Catch be lying to me (in the profiler)?
I am beginning to work with tSQLt unit tests for SQL Server in my production code. Currently, I use Erland Sommarskog's error handling pattern for SQL Server.
USE TempDB;
SET ANSI_NULLS, QUOTED_IDENTIFIER ON;
GO
IF OBJECT_ID('dbo.SommarskogRollback') IS NOT NULL
DROP PROCEDURE dbo.SommarskogRollback;
GO
CREATE PROCEDURE dbo.SommarskogRollback
AS
BEGIN; /*Stored Procedure*/
SET XACT_ABORT, NOCOUNT ON;
BEGIN TRY;
BEGIN TRANSACTION;
RAISERROR('This is just a test. Had this been an actual error, we would have given you some cryptic gobbledygook.', 16, 1);
COMMIT TRANSACTION;
END TRY
BEGIN CATCH;
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION;
THROW;
END CATCH;
END; /*Stored Procedure*/
GO
Erland Sommarskog recommends that we always SET XACT_ABORT ON, because only then does SQL Server handle errors in a (mostly) consistent manner.
This creates a problem when using tSQLt, though. tSQLt executes all tests inside an explicit transaction. When the tests are complete the entire transaction rolls back. This makes cleanup of the test artifacts completely painless. However, with XACT_ABORT ON, any error thrown inside a TRY block immediately dooms that transaction. The transaction must roll back completely. It cannot commit, and it cannot roll back to a save point. In fact, nothing can write to the transaction log inside that session until the transaction rolls back. However, tSQLt can't track the test results properly unless the transaction is open when the tests end. tSQLt stops executing and throws a ROLLBACK ERROR for doomed transactions. The test that failed shows a status of Error (rather than Success or Failure), and subsequent tests don't run.
Sebastian Meine, the creator of tSQLt, recommends a different error handling pattern.
USE TempDB;
SET ANSI_NULLS, QUOTED_IDENTIFIER ON;
GO
IF OBJECT_ID('dbo.MeineRollback') IS NOT NULL
DROP PROCEDURE dbo.MeineRollback;
GO
CREATE PROCEDURE dbo.MeineRollback
AS
BEGIN /*Stored Procedure*/
SET NOCOUNT ON;
/* We declare the error variables here, populate them inside the CATCH
* block and then do our error handling after exiting the CATCH block
*/
DECLARE #ErrorNumber INT
,#MessageTemplate NVARCHAR(4000)
,#ErrorMessage NVARCHAR(4000)
,#ErrorProcedure NVARCHAR(126)
,#ErrorLine INT
,#ErrorSeverity INT
,#ErrorState INT
,#RaisErrorState INT
,#ErrorLineFeed NCHAR(1) = CHAR(10)
,#ErrorStatus INT = 0
,#SavepointName VARCHAR(32) = REPLACE( (CAST(NEWID() AS VARCHAR(36))), '-', '');
/*Savepoint names are 32 characters and must be unique. UNIQUEIDs are 36, four of which are dashes.*/
BEGIN TRANSACTION; /*If a transaction is already in progress, this just increments the transaction count*/
SAVE TRANSACTION #SavepointName;
BEGIN TRY;
RAISERROR('This is a test. Had this been an actual error, Sebastian would have given you a meaningful error message.', 16, 1);
END TRY
BEGIN CATCH;
/* Build a message string with placeholders for the original error information
* Note: "%d" & "%s" are placeholders (substitution parameters) which capture
* the values from the argument list of the original error message.
*/
SET #MessageTemplate = N': Error %d, Severity %d, State %d, ' + #ErrorLineFeed
+ N'Procedure %s, Line %d, ' + #ErrorLineFeed
+ N', Message: %s';
SELECT #ErrorStatus = 1
,#ErrorMessage = ERROR_MESSAGE()
,#ErrorNumber = ERROR_NUMBER()
,#ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-')
,#ErrorLine = ERROR_LINE()
,#ErrorSeverity = ERROR_SEVERITY()
,#ErrorState = ERROR_STATE()
,#RaisErrorState = CASE ERROR_STATE()
WHEN 0 /*RAISERROR Can't generate errors with State = 0*/
THEN 1
ELSE ERROR_STATE()
END;
END CATCH;
/*Rollback to savepoint if error occurred. This does not affect the transaction count.*/
IF #ErrorStatus <> 0
ROLLBACK TRANSACTION #SavepointName;
/*If this procedure executed inside a transaction, then the commit just subtracts one from the transaction count.*/
COMMIT TRANSACTION;
IF #ErrorStatus = 0
RETURN 0;
ELSE
BEGIN; /*Re-throw error*/
/*Rethrow the error. The msg_str parameter will contain the original error information*/
RAISERROR( #MessageTemplate /*msg_str parameter as message format template*/
,#ErrorSeverity /*severity parameter*/
,#RaisErrorState /*state parameter*/
,#ErrorNumber /*argument: original error number*/
,#ErrorSeverity /*argument: original error severity*/
,#ErrorState /*argument: original error state*/
,#ErrorProcedure /*argument: original error procedure name*/
,#ErrorLine /*argument: original error line number*/
,#ErrorMessage /*argument: original error message*/
);
RETURN -1;
END; /*Re-throw error*/
END /*Stored Procedure*/
GO
He declares the error variables, begins a transaction, sets a save point and then executes the procedure code inside a TRY block. If the TRY block throws an error , execution passes to the CATCH block, which populates the error variables. Then execution passes out of the TRY CATCH block. On error, the transaction rolls back to the save point set at the beginning of the procedure. Then the transaction commits. Due to the way SQL Server handles nested transactions, this COMMIT just subtracts one from the transaction counter when executed inside another transaction. (Nested Transactions really don't exist in SQL Server.)
Sebastian created a very neat and tidy pattern. Each procedure in an execution chain cleans up its own transactions. Unfortunately, this pattern has a big problem: doomed transactions. Doomed transactions break this pattern because they can't roll back to a save point or commit. They can only roll back completely. This, of course, means that you can't set XACT_ABORT ON when using TRY-CATCH blocks (and you should always use TRY-CATCH blocks.) Even with XACT_ABORT OFF, many errors, such as compilation errors, will doom a transaction anyway. Further, save points won't work with distributed transactions.
How can I work around this? I need an error handling pattern that will work within the tSQLt test framework and also deliver consistent, correct error handling in production. I could check the environment at run-time and adjust the behavior accordingly. (See the example, below.) I don't like that, however. It feels like a hack to me. It requires that the development environments be configured consistently. Worse, I don't test my actual production code. Does anyone have a brilliant solution?
USE TempDB;
SET ANSI_NULLS, QUOTED_IDENTIFIER ON;
GO
IF OBJECT_ID('dbo.ModifiedRollback') IS NOT NULL
DROP PROCEDURE dbo.ModifiedRollback;
GO
CREATE PROCEDURE dbo.ModifiedRollback
AS
BEGIN; /*Stored Procedure*/
SET NOCOUNT ON;
IF RIGHT(##SERVERNAME, 9) = '\LOCALDEV'
SET XACT_ABORT OFF;
ELSE
SET XACT_ABORT ON;
BEGIN TRY;
BEGIN TRANSACTION;
RAISERROR('This is just a test. Had this been an actual error, we would have given you some cryptic gobbledygook.', 16, 1);
COMMIT TRANSACTION;
END TRY
BEGIN CATCH;
IF ##TRANCOUNT > 0 AND RIGHT(##SERVERNAME,9) <> '\LOCALDEV'
ROLLBACK TRANSACTION;
THROW;
END CATCH;
END; /*Stored Procedure*/
GO
EDIT: After further testing, I find that my modified rollback doesn't work, either. When the procedure throws an error it exits without either rolling back or committing. tSQLt throws an error because ##TRANCOUNT when the procedure exits doesn't match the count when the procedure starts. After some trial and error I found a workaround that works in my tests. It combines the two error handling approaches - making the error handing much more complex, and some code paths can't be tested. I'd love to find a better solution.
USE TempDB;
SET ANSI_NULLS, QUOTED_IDENTIFIER ON;
GO
IF OBJECT_ID('dbo.TestedRollback') IS NOT NULL
DROP PROCEDURE dbo.TestedRollback;
GO
CREATE PROCEDURE dbo.TestedRollback
AS
BEGIN /*Stored Procedure*/
SET NOCOUNT ON;
/* Due to the way tSQLt uses transactions and the way SQL Server handles errors, we declare our error-handling
* variables here, populate them inside the CATCH block and then do our error-handling after exiting
*/
DECLARE #ErrorStatus BIT
,#ErrorNumber INT
,#MessageTemplate NVARCHAR(4000)
,#ErrorMessage NVARCHAR(4000)
,#ErrorProcedure NVARCHAR(126)
,#ErrorLine INT
,#ErrorSeverity INT
,#ErrorState INT
,#RaisErrorState INT
,#ErrorLineFeed NCHAR(1) = CHAR(10)
,#FALSE BIT = CAST(0 AS BIT)
,#TRUE BIT = CAST(1 AS BIT)
,#tSQLtEnvironment BIT
,#SavepointName VARCHAR(32) = REPLACE( (CAST(NEWID() AS VARCHAR(36))), '-', '');
/*Savepoint names are 32 characters long and must be unique. UNIQUEIDs are 36, four of which are dashes*/
/* The tSQLt Unit Testing Framework we use in our local development environments must maintain open transactions during testing. So,
* we don't roll back transactions during testing. Also, doomed transactions can't stay open, so we SET XACT_ABORT OFF while testing.
*/
IF RIGHT(##SERVERNAME, 9) = '\LOCALDEV'
SET #tSQLtEnvironment = #TRUE
ELSE
SET #tSQLtEnvironment = #FALSE;
IF #tSQLtEnvironment = #TRUE
SET XACT_ABORT OFF;
ELSE
SET XACT_ABORT ON;
BEGIN TRY;
SET ROWCOUNT 0; /*The ROWCOUNT setting can be updated outside the procedure and changes its behavior. This sets it to the default.*/
SET #ErrorStatus = #FALSE;
BEGIN TRANSACTION;
/*We need a save point to roll back to in the tSQLt Environment.*/
IF #tSQLtEnvironment = #TRUE
SAVE TRANSACTION #SavepointName;
RAISERROR('Cryptic gobbledygook.', 16, 1);
COMMIT TRANSACTION;
RETURN 0;
END TRY
BEGIN CATCH;
SET #ErrorStatus = #TRUE;
/* Build a message string with placeholders for the original error information
* Note: "%d" & "%s" are placeholders (substitution parameters) which capture
* the values from the argument list of the original error message.
*/
SET #MessageTemplate = N': Error %d, Severity %d, State %d, ' + #ErrorLineFeed
+ N'Procedure %s, Line %d, ' + #ErrorLineFeed
+ N', Message: %s';
SELECT #ErrorMessage = ERROR_MESSAGE()
,#ErrorNumber = ERROR_NUMBER()
,#ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-')
,#ErrorLine = ERROR_LINE()
,#ErrorSeverity = ERROR_SEVERITY()
,#ErrorState = ERROR_STATE()
,#RaisErrorState = CASE ERROR_STATE()
WHEN 0 /*RAISERROR Can't generate errors with State = 0*/
THEN 1
ELSE ERROR_STATE()
END;
END CATCH;
/* Due to the way the tSQLt test framework uses transactions, we use two different error-handling schemes:
* one for unit-testing and the other for our main Test/Staging/Production environments. In those environments
* we roll back transactions in the CATCH block in the event of an error. In unit-testing, on the other hand,
* we begin a transaction and set a save point. If an error occurs we roll back to the save point and then
* commit the transaction. Since tSQLt executes all test in a single explicit transaction, starting a
* transaction at the beginning of this stored procedure just adds one to ##TRANCOUNT. Committing the
* transaction subtracts one from ##TRANCOUNT. Rolling back to a save point does not affect ##TRANCOUNT.
*/
IF #ErrorStatus = #TRUE
BEGIN; /*Error Handling*/
IF #tSQLtEnvironment = #TRUE
BEGIN; /*tSQLt Error Handling*/
ROLLBACK TRANSACTION #SavepointName; /*Rolls back to save point but does not affect ##TRANCOUNT*/
COMMIT TRANSACTION; /*Subtracts one from ##TRANCOUNT*/
END; /*tSQLt Error Handling*/
ELSE IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION;
/*Rethrow the error. The msg_str parameter will contain the original error information*/
RAISERROR( #MessageTemplate /*msg_str parameter as message format template*/
,#ErrorSeverity /*severity parameter*/
,#RaisErrorState /*state parameter*/
,#ErrorNumber /*argument: original error number*/
,#ErrorSeverity /*argument: original error severity*/
,#ErrorState /*argument: original error state*/
,#ErrorProcedure /*argument: original error procedure name*/
,#ErrorLine /*argument: original error line number*/
,#ErrorMessage /*argument: original error message*/
);
END; /*Error Handling*/
END /*Stored Procedure*/
GO
I'm testing a fix for this that modificaties the framework procedure tSQLt.Private_RunTest. Basically, in the primary CATCH block, wherein it is trying to do a named rollback (Line 1448 for me), I am replacing
ROLLBACK TRAN #TranName;
with
IF XACT_STATE() = 1 -- transaction is active
ROLLBACK TRAN #TranName; -- execute original code
ELSE IF XACT_STATE() = -1 -- transaction is doomed; cannot be partially rolled back
ROLLBACK; -- fully roll back
IF (##TRANCOUNT = 0)
BEGIN TRAN; -- restart transaction to fulfill expectations below
Preliminary testing looks good. Stay tuned. (I'll submit to git after I gain some more confidence in this proposed edit.)
The question should be quite simple, but I can't figure out the answer nor why my stored procedure is not working.
CREATE PROCEDURE spTest_Delete
#ID int
AS
begin tran
declare #err int
declare #errMesage nvarchar(max)
set #errMesage = ''
set #err = 0
delete from Test
where ID = #ID
set #err = ##ERROR
set #errMesage = ERROR_MESSAGE()
if #err = 0
commit tran
else
begin
RAISERROR(N'Could not delete !Error nr: %d. Message: %s', 1, 16, #err, #errMesage)
rollback tran
end
This procedure runs ok, but in case of FK constraint on the delete statement it runs into an error (which is good) and I would like to catch the error.
Msg 547, Level 16, State 0, Procedure spTest_Delete, Line 12
The DELETE statement conflicted with the REFERENCE constraint
"FK_TEstFK_Test". The conflict occurred in database "Test", table
"dbo.Test", column 'ID'. The statement has been terminated.
Could not delete!
Error nr: 547. Message: (null) Msg 50000, Level 1, State 16
I always get null for my message variable, even though the delete statement throws an error.
You might want to start using TRY..CATCH block in your procedures
Implements error handling for Transact-SQL that is similar to the
exception handling in the Microsoft Visual C# and Microsoft Visual C++
languages. A group of Transact-SQL statements can be enclosed in a TRY
block. If an error occurs in the TRY block, control is passed to
another group of statements that is enclosed in a CATCH block.
So your procedure could be rewritten as:
CREATE PROCEDURE spTest_Delete #ID INT
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
BEGIN TRANSACTION
DELETE
FROM Test
WHERE ID = #ID;
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION
SELECT ERROR_NUMBER(), ERROR_MESSAGE();
END CATCH
END
Also, please note that you're running as single delete statement. It means that it doesn't need to be wrapped up in a transaction. This question explains why.
Your code becomes this:
CREATE PROCEDURE spTest_Delete #ID INT
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
DELETE
FROM Test
WHERE ID = #ID;
END TRY
BEGIN CATCH
SELECT ERROR_NUMBER(), ERROR_MESSAGE();
END CATCH
END
Now why your #errMessage is always NULL? Because ERROR_MESSAGE() is valid ONLY IN CATCH BLOCK. That's written in documentation:
Returns the message text of the error that caused the CATCH block of a
TRY…CATCH construct to be run.
Using TRY..CATCH in Transact-SQL tells this:
Error information is retrieved by using these functions from anywhere
in the scope of the CATCH block of a TRY…CATCH construct. The error
functions will return NULL if called outside the scope of a CATCH
block.
Try to use TRY CATCH and catch your error like this:
BEGIN TRY
delete from Test
where ID = #ID
END TRY
BEGIN CATCH
SET #ErrorMessage = ERROR_MESSAGE()
SET #ErrorSeverity = ERROR_SEVERITY()
SET #ErrorState = ERROR_STATE()
RAISERROR(#ErrorMessage, #ErrorSeverity, #ErrorState)
BREAK
END CATCH
I am using SQL Server 2008 and writing a stored procedure which inserts and updates records in a table. This procedure will be scheduled for some time. Whenever there is a error I am logging that error in another table. But I need actual error message but unable to get the same.
I have used this:
SET #ErrNum = ##ERROR
SET #ErrMsg = ERROR_MESSAGE()
But my problem is I'm not getting the actual error message. I am able to get error number.
Please suggest what needs to be done for actual message to get from SQL Server prompt after execution of SQL statement.
ERROR_MESSAGE() should do the trick.
BEGIN TRY
SELECT 1/0
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE()
END CATCH;
This is valid what you're doing. In all scripts you can see something like that
DECLARE #ErrorMessage nvarchar(max)
DECLARE #ErrorMessageOriginal nvarchar(max)
DECLARE #Error int
DECLARE #ReturnCode int
SET #Error = 0
SET #ReturnCode = 0
SET #Error = ERROR_NUMBER()
SET #ReturnCode = #Error
SET #ErrorMessageOriginal = ERROR_MESSAGE()
SET #ErrorMessage = 'Msg ' + CAST(#Error AS nvarchar) + ', ' + ISNULL(#ErrorMessageOriginal,'')
RAISERROR(#ErrorMessage,16,1) WITH NOWAIT
But there is error that you want to handle by yourself like that from MSDN source:
Using ##ERROR to detect a specific error
USE AdventureWorks2012;
GO
UPDATE HumanResources.EmployeePayHistory
SET PayFrequency = 4
WHERE BusinessEntityID = 1;
IF ##ERROR = 547
PRINT N'A check constraint violation occurred.';
GO
Whenever I use ExpectException I receive the following error: (There was also a ROLLBACK ERROR --> The current transaction cannot be committed and cannot be rolled back to a savepoint. Roll back the entire transaction.{Private_RunTest,140})
It appears that tSQLt is attempting a ROLLBACK after the fact that MSSQL has already performed the ROLLBACK due to the RAISERROR.
I wrapped the following SELECT and SET statement in Private_RunTest with the following IF statement and it appeared to resolve the problem.
IF ISNULL(#ExpectException,0) <> 1
BEGIN
SELECT #Msg = COALESCE(#Msg, '') + ' (There was also a ROLLBACK ERROR --> ' +
COALESCE(ERROR_MESSAGE(), '') + '{' +
COALESCE(ERROR_PROCEDURE(), '') + ',' +
COALESCE(CAST(ERROR_LINE() AS NVARCHAR), '') + '})';
SET #Result = 'Error';
END
Is this truly a bug and/or an appropriate fix?
You are probably not using TRY/CATCH blocks in your tsqlt test.
The error message "There was also a ROLLBACK ERROR --> The current transaction cannot be committed and cannot be rolled back to a savepoint. Roll back the entire transaction." can be reproduced as follows:
1) Create a Trigger that throws an error and rolls back as follows:
CREATE TRIGGER [dbo].[MyTable_IUDTR] ON [dbo].[MyTable]
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
BEGIN TRY
RAISERROR('MyError', 16, 1)
END TRY
BEGIN CATCH
THROW;
END CATCH
END
GO
2) Create a tsqlt test to check the error message returned:
CREATE PROC [ut_MyTable_IUDTR].[test that the error is returned]
AS
BEGIN
DECLARE #ErrorMsg VARCHAR(50)
EXEC tsqlt.FakeTable #TableName = 'MyTable'
EXEC tsqlt.ApplyTrigger 'MyTable', 'MyTable_IUDTR'
INSERT INTO dbo.MyTable
( FirstName, LastName )
VALUES ( N'John',N'Smith')
SET #ErrorMsg = ERROR_MESSAGE()
EXEC tSQLt.AssertEqualsString #Expected = 'MyError', #Actual = #ErrorMsg
END
GO
3) Run the test:
EXEC [tSQLt].Run 'ut_MyTable_IUDTR.test that the error is returned'
4) You get the following Error:
There was also a ROLLBACK ERROR --> The current transaction cannot be committed and cannot be rolled back to a savepoint. Roll back the entire transaction.
THE FIX:
Change the tsqlt test to include TRY/CATCH blocks as follows:
ALTER PROC [ut_MyTable_IUDTR].[test that the error is returned]
AS
BEGIN
DECLARE #ErrorMsg VARCHAR(50)
EXEC tsqlt.FakeTable #TableName = 'MyTable'
EXEC tsqlt.ApplyTrigger 'MyTable', 'MyTable_IUDTR'
BEGIN TRY
INSERT INTO dbo.MyTable
( FirstName, LastName )
VALUES ( N'John',N'Smith')
END TRY
BEGIN CATCH
SET #ErrorMsg = ERROR_MESSAGE()
END CATCH
EXEC tSQLt.AssertEqualsString #Expected = 'MyError', #Actual = #ErrorMsg
END
GO
You may be using SET XACT_ABORT ON in your tsqlt test
CREATE PROC [testClass].[test foo]
AS
BEGIN
SET XACT_ABORT ON
EXEC tsqlt.AssertEquals #Expected = 1, #Actual = 0
END
Don't do this.
You'll get a message like this
[testClass].[test foo] failed: (Error) Expected: <1> but was: <0> (There was also a ROLLBACK ERROR --> The current transaction cannot be committed and cannot be rolled back to a savepoint. Roll back the entire transaction.{Private_RunTest,160})