jueves, 6 de noviembre de 2008

Try ...Catch y RAISERROR en T-SQL: ¿es un bug o una feature?

No voy a entrar a explicar cómo se usan los bloques Try...Catch en T-SQL, que para eso ya está el MSDN: http://msdn.microsoft.com/es-es/library/ms179296(SQL.90).aspx

si no que os voy a contar un caso curioso que nos ha pasado estos últimos días:

Tenemos una aplicación en VB 6.0 (sí, tiene que ser en VB 6.0 por narices, así que es lo que hay) que tiene que llamar a store procedures de una base de datos SQL Server 2005 usando ADO (sí, el clásico no el .NET), bueno, el caso es que en algunos store procedures tenemos algo parecido a esto:


CREATE PROCEDURE Mi_Procedure
AS
BEGIN
BEGIN TRY
... Código antes del RAISERROR
RAISERROR(...) -- Este RAISERROR provoca que se vaya al bloque CATCH
... Código después del RAISERROR que no se ejecuta
END TRY
BEGIN CATCH
Exec Otro_Procedure @parametro1, @parametro2
RAISERROR(...) -- Este RAISERROR debería propagar el error al que ha llamado al store procedure
END CATCH
END


Bien, es algo bastante básico, pero lo curioso del tema es que tal y como está, desde ADO no se captura el error, ni se dispara un error interceptable ni está en la colección de errores de la conexión.

Investigando un poco descubrí que en Otro_Procedure que se llama desde aquí había una llamada a sp_executesql y esto provoca que por algún motivo se genere un entorno de ejecución diferente, lo que a su vez provoca que el ADO no capture el error.

¿Cuál puede ser la solución?
Teniendo en cuenta que no podíamos obviar la llamada a Otro_Procedure y tampoco a sp_executesql se me ocurrió la idea de darle la vuelta al bloque CATCH y hacer el RAISERROR antes de la llamada a Otro_Procedure. La idea parece un poco absurda si pensamos que el bloque TRY...CATCH funcionase como, por ejemplo, en C#, pero aquí viene el origen del post, el bloque TRY...CATCH en T-SQL te permite ejecutar cualquier instrucción después de hacer un RAISERROR.

O_o

¿¿¿ Eing???

Pues sí, la lógica dice que en un CATCH cuando hagas un RAISERROR este se propagará hacia arriba hasta llegar al llamador del procedure, pero la implementación en T-SQL hace que esto no sea así y que no se finalice la ejecución hasta llegar al END CATCH, con lo que después del RAISERROR podemos poner la/s instrucciones que queramos que se van a ejecutar.

Por supuesto, haciendo la misma llamada al store procedure desde .NET con ADO.NET siempre se intercepta el error, esté el RAISERROR del CATCH antes o después de la llamada a Otro_Procedure, con lo que +1 para ADO.NET :)

Happy coding ;)