martes, 19 de febrero de 2019

Comportamiento Indefinido en FirebirdSQL 3 debido al “Cursor Stability”

FirebirdSQL 3.x trae una sorpresita que te puede complicar bastante la vida en el caso de que tengas en una tabla TRIGGERS que modifican otros registros de su propia tabla.

El problema viene cuando haces un UPDATE de varias filas modificando el FIELD1, y por ejemplo, el TRIGGER del primer registro hace un cambio en el segundo registro en el FIELD2. El resultado que obtienes es que ese cambio realizado por el TRIGGER desaparece, por que el UPDATE al llegar al segundo registro dejara el valor del FIELD2 que tenía en el momento en el que se lanzó el UPDATE. Vamos que utiliza el cursor en vez de utilizar la ultima versión del registro como era de esperar, y que dicho sea de paso, Firebird 2.5 lo hace a la perfección.

Ahora si haces un UPDATE sobre el primer registro y luego un UPDATE a parte para el segundo registro si que se mantendrá en cambio que se realizó en el FIELD2. Incluso si se hace sobre la misma transacción.

Si no he sido capaz de explicarlo bien espero que con el código de abajo se entienda mejor.

Esto viene provocado por una nueva funcionalidad que denominan “Cursor Stability” y tras una pequeña mención en la documentación ponen una nota que dice más o menos...

“El estándar SQL estipula que la instrucción MERGE debe generar un error si se encuentran múltiples coincidencias. Firebird no es tan estricto en este sentido, pero su comportamiento debe considerarse indefinido en estos casos.”

Firebird - SQL Language Changes

O sea… que ahora resulta que ya no tenemos la certeza de que los TRIGGERs funcionan en todos los casos, y por lo tanto, ahora la informática ha pasado de ciencia cierta a ciencia infusa. Teníamos programas que funcionaban perfectamente con las versiones anteriores de Firebird y al actualizar el motor, de repente, empiezan a trabajar con un comportamiento indefinido.

Yo personalmente hubiera preferido encontrarme de repente con errores al actualizar Firebird en desarrollo. La solución adoptada por los chicos de Firebird nos ha llevado a encontrarnos el problema ya en producción, con datos deteriorados por ese comportamiento indefinido.

Lo tengo reportado como un BUG en Firebird Tracker, aunque según el equipo encargado del desarrollo de Firebird no lo es.

CREATE TABLE TEST_UPDATE
(
  ID INTEGER NOT NULL,
  FIELD1 INTEGER,
  FIELD2 INTEGER,
  CONSTRAINT PK_TEST_UPDATE PRIMARY KEY (ID)
);

SET TERM ^^ ;
CREATE TRIGGER TEST_UPDATE_AU FOR TEST_UPDATE ACTIVE AFTER UPDATE POSITION 0 AS
begin
  Update TEST_UPDATE
  SET FIELD2=NEW.FIELD1
  Where ID=NEW.ID+1;
end ^^
SET TERM ; ^^

Insert into TEST_UPDATE (ID,FIELD1,FIELD2) VALUES (1,0,0);
Insert into TEST_UPDATE (ID,FIELD1,FIELD2) VALUES (2,0,0);
Insert into TEST_UPDATE (ID,FIELD1,FIELD2) VALUES (3,0,0);
Insert into TEST_UPDATE (ID,FIELD1,FIELD2) VALUES (4,0,0);
Insert into TEST_UPDATE (ID,FIELD1,FIELD2) VALUES (5,0,0);

Update TEST_UPDATE
Set Field1=10
Where ID in (2,3);
=== In Firebird 3.0.4 ===
Select *
From TEST_UPDATE;

ID,FIELD1,FIELD2
1,0,0
2,10,0
3,10,0 <== This value must be 10
4,0,10
5,0,0

=== In Firebird 2.5 is Correct!!!! ===
ID,FIELD1,FIELD2
1,0,0
2,10,0
3,10,10
4,0,10
5,0,0

No hay comentarios: