Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   Firebird e Interbase (https://www.clubdelphi.com/foros/forumdisplay.php?f=19)
-   -   Gestionar excepciones en un trigger (https://www.clubdelphi.com/foros/showthread.php?t=89598)

Angel.Matilla 22-12-2015 13:58:33

Gestionar excepciones en un trigger
 
Como ya he comentado estoy tratando de enterarme como funcionan los triggers. Muchas cosas las entiendo a la perfección pero hay algo que se me escapa totalmente: ¿Cómo gestionar las excepciones? Seguro que en el foro ya hay algo pero no he sido capaz de encontrarlo.

Tengo definido este trigger:
Código SQL [-]
CREATE TRIGGER CONEXION_NEW FOR CONEXION
ACTIVE BEFORE INSERT
POSITION 1
AS
BEGIN
  NEW.Id_Conexion = GEN_ID(ID_Conexion, 1);
  WHEN GDSCODE unique_key_violation DO BEGIN
    EXCEPTION USUARIO_CONECTADO;
  END
END
y la excepción está definida así:
Código SQL [-]
CREATE EXCEPTION USUARIO_CONECTADO 'Este usuario está activo en otro terminal'
para esta tabla:
Código SQL [-]
CREATE TABLE CONEXION (
  ID_CONEXION INTEGER NOT NULL,
  NOMBRE VARCHAR(15) NOT NULL,
  PUESTO VARCHAR(20),
  HORA TIMESTAMP);

ALTER TABLE CONEXION ADD CONSTRAINT UQ_CONEXION UNIQUE (NOMBRE);
Tengo un módulo de gestión de errores (el código está simplificado):
Código PHP:

if (E->ClassNameIs("EIBInterBaseError"))     // Si es una excepcion de interbase
{
     
EIBInterBaseError *ErrorIB reinterpret_cast<EIBInterBaseError *>(E);

     
MenErr StringReplace(ErrorIB->Message"\n"" "oReplace);
     switch (
ErrorIB->SQLCode)     // Si realmente es de interbase tratamos el codigo de error
     
{
          case -
204:
               [...]
               break;
          case -
803:
               break;
          case -
836:
               [...]
               return;
          case -
902:
               [...]
               break;
          case -
904:
               [...]
               return;
          default:
               [...]
               break;
          }


Sé que si trato de añadir un registro con un usuario que ya existe me va dar un error -803 y debería saltar la excepción. Lo que no acabo de comprender es cómo lograr que el programa me muestre el mensaje que he definido para esa excepción, ese USUARIO_CONECTADO. ¿Alguien me lo puede explicar?

dec 22-12-2015 14:07:15

Hola,

¿Has probado con el método "getMessage" de la excepción, o sea E->getMessage()?

Angel.Matilla 22-12-2015 17:37:06

Cita:

Empezado por dec (Mensaje 500719)
Hola,

¿Has probado con el método "getMessage" de la excepción, o sea E->getMessage()?

He probado con E->getMessage() y con ErrorIB->getMessage(), y en ambos casos me indica el compilador que no existe el método. Siento ser tan paliza, pero es que por más que he buscado documentación no he visto cómo usar esas excepciones definidas; no lo entiendo. :(

Angel.Matilla 22-12-2015 17:53:40

A ver. El único mensaje al que puedo acceder es E->Message y su valor es, en este caso:
Cita:

violation of PRIMARY or UNIQUE KEY constraint \"UQ_CONEXION\" on table \"CONEXION\"\r Problematic key value is (\"NOMBRE\" = 'FEDERICO
No aparece por ningún lado lo que he definido en la excepción.

ecfisa 22-12-2015 20:14:12

Hola Angel.Matilla
Cita:

Empezado por Angel.Matilla (Mensaje 500729)
A ver. El único mensaje al que puedo acceder es E->Message y su valor es, en este caso:
No aparece por ningún lado lo que he definido en la excepción.

No tengo en claro el objetivo que buscas, pero también podes acceder a la propiedad Message de la clase EIBInterBaseError:
Código PHP:

void __fastcall TUnForm::UnManejador(TObjectSenderExceptionE)
{
  if (
E->ClassNameIs("EIBInterBaseError")) {
    
EIBInterBaseError *eib static_cast <EIBInterBaseError*> (E);  
    
ShowMessage(eib->Message);  
    
    
/*  O si lo preferis, de este modo: 
    String msg = (static_cast<EIBInterBaseError*>(E))->Message; 
    ShowMessage(msg);
    
    O simplemente: ShowMessage(static_cast<EIBInterBaseError*>(E)->Message); 
    */ 

Saludos :)

dec 22-12-2015 20:37:02

Hola,

Bueno, yo tengo que decir que metí la pata... puesto que creí que tratábamos de PHP y no de C++... por eso no existe el método "getMessage" en el objeto/excepción, pero dicho método existe en PHP. Siento haberte hecho perder el tiempo. Prueba la solución del compañero Daniel a ver si hay suerte. :)

ecfisa 22-12-2015 20:51:32

Cita:

Empezado por dec (Mensaje 500733)
Hola,

Bueno, yo tengo que decir que metí la pata... puesto que creí que tratábamos de PHP y no de C++...

No es nada extraño David ya que se usan las mismas etiquetas para el código. (A mi me ha pasado varias veces pero al revés :o)

Saludos :)

dec 22-12-2015 20:59:17

Cita:

Empezado por ecfisa (Mensaje 500734)
No es nada extraño David ya que se usan las mismas etiquetas para el código. (A mi me ha pasado varias veces pero al revés :o)

Saludos :)

Pues es verdad que se usan las mismas etiquetas... aunque yo ni me fijé en ese detalle. El código me pareció PHP tal cual, vamos. :)

Angel.Matilla 23-12-2015 09:41:19

Cita:

Empezado por ecfisa (Mensaje 500732)
No tengo en claro el objetivo que buscas, pero también podes acceder a la propiedad Message de la clase EIBInterBaseError:

¡Hombre! Me parece bastante evidente: Si defino un mensaje para uan excepción lo lógico es emplearlo y mostrar al usuario la información lo más precisa posible. A un usuario de una aplicación, que no tiene por qué saber de programación, el mensaje genérico «violation of PRIMARY or UNIQUE KEY constraint \"UQ_CONEXION\" on table \"CONEXION\"\r Problematic key value is (\"NOMBRE\" = 'FEDERICO'» no le dice nada, pero si le informo que «'Este usuario está activo en otro terminal'» se preocupará de buscar la razón.

dec 23-12-2015 10:59:35

Hola,

Trata de averiguar si cuando hacemos esto:

Código:

CREATE EXCEPTION USUARIO_CONECTADO 'Este usuario está activo en otro terminal'
... estamos dando un texto, una descripción a la excepción, o, si simplemente se trata de un comentario en el código. Si fuese el primer caso, para mí lo intuitivo sería acceder al texto usando el método "getMessage", "Message" o el que sea que exista en C++ para acceder al mensaje de las excepciones. Si fuese el segundo caso, igual no contamos con nada más que el tipo de excepción y el texto o mensaje hemos de ponerlo nosotros mismos en el programa, a partir del tipo de excepción.

Casimiro Noteví 23-12-2015 11:28:58

Cita:

Empezado por dec (Mensaje 500747)
Hola,
Trata de averiguar si cuando hacemos esto:
Código:

CREATE EXCEPTION USUARIO_CONECTADO 'Este usuario está activo en otro terminal'
... estamos dando un texto, una descripción a la excepción, o, si simplemente se trata de un comentario en el código. Si fuese el primer caso, para mí lo intuitivo sería acceder al texto usando el método "getMessage", "Message" o el que sea que exista en C++ para acceder al mensaje de las excepciones. Si fuese el segundo caso, igual no contamos con nada más que el tipo de excepción y el texto o mensaje hemos de ponerlo nosotros mismos en el programa, a partir del tipo de excepción.

Ese mensaje; "Este usuario está activo en otro terminal." es el mensaje que le saldrá al usuario cuando se produzca la excepción.
No hay que hacer nada más.

Angel.Matilla 23-12-2015 11:38:38

Cita:

Empezado por dec (Mensaje 500747)
... estamos dando un texto, una descripción a la excepción, o, si simplemente se trata de un comentario en el código. Si fuese el primer caso, para mí lo intuitivo sería acceder al texto usando el método "getMessage", "Message" o el que sea que exista en C++ para acceder al mensaje de las excepciones. Si fuese el segundo caso, igual no contamos con nada más que el tipo de excepción y el texto o mensaje hemos de ponerlo nosotros mismos en el programa, a partir del tipo de excepción.

Gracias por contestar.Yo entiendo que estamos dando un texto; si consulto la base de datos con SQL Manager veo esto:



Así yo entiendo que he creado una excepción y su mensjae asociado. En otro hilo de este mismo subforo encontré una respuesta; es para Delphi y en principio debería ser fácil de adaptar a Builder. Pero me dejó perplejo parte del código que hay:
Código:

uses IB;
...
try
  ...
  insertquery.ExecSQL;
except on E:Exception do
  case EIBInterBaseError(E).IBErrorCode of
  $14000019: ShowMessage('Deadlock');
  $14000888:// se produjo la excepcion ERR_NO_TITULO
        ShowMessage('El título no puede estar vacio');
end;

Esos valores 14000019 y 14000888 he estado buscándolos en el listado de errores de FB y no existen por lo que no sé como tratarlos.

En otros hilos he visto que hay la posibilidad de modificar los mensajes genéricos de FB, pero entonces no sé para que valen esos mensajes de excepción que se puede definir en la base de datos.

ecfisa 24-12-2015 01:44:42

Hola.
Cita:

Empezado por Angel.Matilla (Mensaje 500746)
¡Hombre! Me parece bastante evidente:
...

Te aseguro que no lo era tanto para mí...

Es decir que queres traducir los mensajes de error, a otra forma que consideras que tus usuarios entenderán mejor.
Bueno, para eso no veo otra opción que hacerlo manualmente evaluando todos los GDSCODE, me refiero a algo similar a este ejemplo:
Código PHP:

  if (E->ClassNameIs("EIBInterBaseError")) {
    
String Msg;
    
int gdsc = (static_cast<EIBInterBaseError*>(E))->IBErrorCode;
    switch (
gdsc) {
      ...
      case 
335544578Msg "Columna desconocida."; break;
      case 
335544579Msg "Error interno."; break;
      ...
    }
    
ShowMessage(Msg); 

Cita:

Empezado por Angel.Matilla (Mensaje 500750)
...encontré una respuesta; es para Delphi y en principio debería ser fácil de adaptar a Builder. Pero me dejó perplejo parte del código que hay:
Código:

uses IB;
...
try
  ...
  insertquery.ExecSQL;
except on E:Exception do
  case EIBInterBaseError(E).IBErrorCode of
  $14000019: ShowMessage('Deadlock');
  $14000888:// se produjo la excepcion ERR_NO_TITULO
        ShowMessage('El título no puede estar vacio');
end;

Esos valores 14000019 y 14000888 he estado buscándolos en el listado de errores de FB y no existen por lo que no sé como tratarlos.

$14000019(Delphi) = 0x14000019(C++) = 335544345(decimal)
Código:

SQLCODE | GDSCODE  |    SYMBOL      TEXT
--------+-----------+---------------+-------------------------------------
-901    | 335544345 | lock_conflict | Lock conflict on no wait transaction

$14000888 = 0x14000888 = 335546504 (no existe como código de error en Firebird)

Saludos :)

Angel.Matilla 24-12-2015 09:49:09

Cita:

Empezado por ecfisa (Mensaje 500769)
Te aseguro que no lo era tanto para mí...

Queda demostardo que cada uno ve las cosas de diferente manera :)

Cita:

Es decir que queres traducir los mensajes de error, a otra forma que consideras que tus usuarios entenderán mejor.
Bueno, para eso no veo otra opción que hacerlo manualmente evaluando todos los GDSCODE, me refiero a algo similar a este ejemplo:
Básicamente es eso lo que busco y me lo temía. Esto me lleva a la conclusión de que usar las excepciones de Firebird tiene muy poca utilidad; es una lástina. El sistema que me suguieres es parecido al que estoy usando ahora.
Cita:

$14000019(Delphi) = 0x14000019(C++) = 335544345(decimal)
No se me había ocurrido que pudiera ser hexa :(
Muchas gracias por las sugerencias.

Casimiro Noteví 24-12-2015 11:08:21

Si lo que quieres es mostrar mensajes simples a los usuarios, puedes hacer algo así, ejemplo:
Tienes una tabla de usuarios que contiene un usuario predeterminado que no se debe borrar:
Código SQL [-]
create table tbUsuarios
  codigo integer not null,
  nombre varchar(64),
  primary key (codigo)
);
Usuario predeterminado:
Código SQL [-]
insert into tbusuarios values (0,'admin');
Creas la excepción por si intentan borrarlo, junto al texto que se mostrará:
Código SQL [-]
create exception EXCP_NO_BORRAR 'No está permitido borrar este registro';
Creas el trigger donde se controlará que no se borre el usuario admin:
Código SQL [-]
CREATE OR ALTER trigger tbusuarios_bd0 for tbusuarios
active before delete position 0
AS
begin
  if (old.codigo=0) then  /* si intentan borrar el usuario admin, se mostrará el mensaje */
    exception excp_no_borrar;
end ^
Y eso es todo.

Otra cosa diferente es mostrar los mensajes tal y como se generan por la base de datos, puedes hacer algo así:
Código Delphi [-]
try
  ...
  qry.Post;
except
  on E:exception do
    ShowMessage(E.ClassName +' '+ E.Message);
end;
Bueno, lo he escrito todo de memoria, pero te sirve de referencia.

Angel.Matilla 24-12-2015 11:46:24

Gracias por la respuesta Casimiro. En el trigger que yo he puesto lo que trato de impedir es que se den de alta dos registros para el mismo valor de Nombre. Tal como lo tengo creado al producirse unique_key_violation el mensaje que se muestra es el propio de Firebird y no el que está declarado en la excepción; no veo como "sacar" el valor de la excepción. Ese el problema que tengo.

Osorio 24-12-2015 16:38:37

Aqui no tengo firebird para probar pero asi por encima creo que la cosa va por aqui.

Si quieres tener una excepcion personalizada, debes hacer la validacion personalizada.
Código SQL [-]
CREATE TRIGGER CONEXION_NEW FOR CONEXION
ACTIVE BEFORE INSERT
POSITION 1
AS
BEGIN

  NEW.Id_Conexion = GEN_ID(ID_Conexion, 1);



  IF new.id_conexion = 350 THEN --cambia la condicion de acuerdo a tu necesidad. tal vez necesites hacer una consulta a una tabla para validar que el usuario este conectado o no.
  BEGIN
          EXCEPTION USUARIO_CONECTADO;
  END
END

ecfisa 24-12-2015 18:43:13

La otra opción que se me ocurre para lo que buscas hacer, requiere que todos los mensajes de error incluyan dos caracteres arbitrarios que circunscriban al mensaje señalizándolo (para el ejemplo elegí "~" y ".").

Firebird:
Código SQL [-]
CREATE EXCEPTION E_SIN_NOMBRE '~El campo NOMBRE es un dato requerido.';

C++ Builder:
Código PHP:

...
{
  if (
E->ClassNameIs("EIBInterBaseError")) {
    
EIBInterBaseError *EI static_cast <EIBInterBaseError*> (E);
    
int p1 EI->Message.Pos("~");
    
int p2 EI->Message.Pos(".");
    
AnsiString msg p1 EI->Message.SubString(p1 1p2 p1) : EI->Message;
    
MessageBox(0msg.c_str(), ""MB_ICONERROR);
  } else
    
MessageBox(0E->Message.c_str(),""MB_ICONERROR);


De ese modo el mensaje, que sin tratar, debería verse así:


Se verá así:


Saludos :)

Angel.Matilla 28-12-2015 12:06:58

La idea de exfisa de insertar algún carácter raro no parecía mala, pero no vale. He puesto ese carácter ~ en el mensaje de la excepción, pero al producirse el error el mensaje que me devuelve es el genérico de FB:
Cita:

violation of PRIMARY or UNIQUE KEY constraint \"UQ_CONEXION\" on table \"CONEXION\"\r\nProblematic key value is (\"NOMBRE\" = 'FEDERICO')
y por ninguna parte aparece el que está definido en la excepción:
Código:

CREATE EXCEPTION USUARIO_CONECTADO '~Este usuario está activo en otro terminal~'
A pesar de estar definido en el trigger:
Código:

CREATE TRIGGER CONEXION_NEW FOR CONEXION
ACTIVE BEFORE INSERT
POSITION 1
AS
BEGIN
  NEW.Id_Conexion = GEN_ID(ID_Conexion, 1);
  WHEN GDSCODE unique_key_violation DO BEGIN
    EXCEPTION USUARIO_CONECTADO;
  END
END

Lo que no me creo es que no haya ninguna forma de mostrar esos mensajes definidos por el usuario

ecfisa 28-12-2015 15:46:55

Cita:

Empezado por Angel.Matilla (Mensaje 500829)
....
He puesto ese carácter ~ en el mensaje de la excepción, pero al producirse el error el mensaje que me devuelve es el genérico de FB:
y por ninguna parte aparece el que está definido en la excepción:
Código:

CREATE EXCEPTION USUARIO_CONECTADO '~Este usuario está activo en otro terminal~'
...

Pero... si usas el mismo caracter para señalizar el inicio y el final del mensaje, nunca lo va a mostrar !, revisa nuevamente el código C++.

Saludos :)


La franja horaria es GMT +2. Ahora son las 12:54:39.

Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2026, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi