Sabía desde hace tiempo que el driver rdd de sixdrive permite introducir un trigger para controlar el contenido.
Pero nosotros usamos DBFNTX, y no podemos cambiar así como así el sistema de RDD.
Por suerte para nosotros, la gente de Harbour le dio soporte también a los NTX.
Hemos diseñado un sistema de tablas, log y logid, que determina que tablas hacermos logs, y el campo de la PK, para encontrar los cambios de un registro de una tabla determinada.
La estructura de la tabla LOG;
TABLA: Nombre de la tabla que se modificado un valor
ID_VALUE: El valor que usaremos de relación, debería ser una PK
CAMPO: El campo de la dbf se está modificando
NEW_VALUE: Nuevo dato en el dbf
OLD_VALUE: Valor que tenia.
CODUSU, IP, HOSTNAME, FECHA, HORA; Datos de control para saber quien hizo y el cambio y desde donde.
Después , tenemos otra tabla LOGID, que determinará que TABLA vamos a auditar, en este caso, tenemos una especie de DBU integrado que necesitamos control de todas las tablas, porque como veremos , simplemente se activa en la apertura de la tabla si queremos que se disparen los triggers o no,y cual es el campo de la tabla que queremos que se guarde en el ID_VALUE de la tabla LOG.
En el ejemplo que veremos, simplemente he sustituido la tabla LOGID, por un Hash, en la llamada a la función __GetIdLog(), en mi código, simplemente abro la tabla logid, y creo el hash.
También, en el ejemplo, evito introducir los nuevos registros, y los campos que sean iguales, tampoco se guardarán. Esto programarlo como queráis, la imaginación es solamente vuestra meta ;-)
Despues, si estamos en un cliente, por ejemplo , donde la PK es el DNI y usamos el DNI como ID a la hora de guardar el log, simplemente haciendo un SCOPE de la tabla + el DNI del cliente, obtendremos todos los cambios aplicados a ese registro ;-)
Este ejemplo lo podéis ejecutar en el directorio de \harbour\tests, y usa la tabla test.dbf con
hbmk2 trigger.prg hbct.hbc xhb.hbc
Lo he modificado simplemente para que veais la salida a un fichero de log, audit.log
2016-08-12 12:38:53 -- trigger start --
2016-08-12 12:38:53 INFO: Ejemplo de auditar cambios a traves de triggers.
2016-08-12 12:38:53 INFO: TABLE:TEST FIELD:FIRST ID VALUE:Homer Simpson OLD VALUE:Homer NEW VALUE:Homer_TEST DATETIME:20160812123853989 HOSTNAME:NEO64
2016-08-12 12:38:54 INFO: TABLE:TEST2 FIELD:LAST ID VALUE: 99700 OLD VALUE:Dysert NEW VALUE:Dysert_TEST DATETIME:20160812123854031 HOSTNAME:NEO64
2016-08-12 12:38:54 INFO: TABLE:TEST2 FIELD:NOTES ID VALUE: 99700 OLD VALUE:This is a test for record 500 NEW VALUE:Changes everything DATETIME:20160812123854055 HOSTNAME:NEO64
2016-08-12 12:38:54 -- trigger end -- El resultado del ejemplo es este;
A continación el codigo fuente;
#include "Hblog.ch"
#include "dbinfo.ch" #include "hbsix.ch" //REQUEST DBFNTX Function Main() setmode( 25,80 ) rddsetdefault( 'DBFNTX' ) // Forzamos RDD por defecto de HARBOUR /* Activa log */ INIT LOG FILE( NIL, "audit.log", 1000, 999 ) // Tamaño a 100K y maximo 999 ficheros LOG "Ejemplo de auditar cambios a traves de triggers." /* Activar triggers*/ rddInfo( RDDI_TRIGGER, "SX_DEFTRIGGER", "DBFNTX" ) /*Llamar antes de abrir la tabla que queremos controlar*/ sx_SetTrigger( TRIGGER_PENDING, "_trigger", "DBFNTX" ) USE "TEST" NEW SHARED /* O podemos hacer de esta manera*/ USE TEST ALIAS "TEST2" NEW SHARED TRIGGER "_trigger" Select TEST go top if rlock() replace FIRST with alltrim( field->FIRST ) + "_TEST" unlock commit endif Select TEST2 go bottom if rlock() replace LAST with alltrim( field->LAST ) + "_TEST" replace NOTES with "Changes everything" unlock endif CLOSE LOG CLOSE ALL Return 0 function _trigger( nEvent, nArea, nFieldPos, xTrigVal ) Local xIdValue, xValue, cIdValue DO CASE CASE nEvent == EVENT_PREUSE CASE nEvent == EVENT_POSTUSE CASE nEvent == EVENT_UPDATE CASE nEvent == EVENT_APPEND CASE nEvent == EVENT_DELETE CASE nEvent == EVENT_RECALL CASE nEvent == EVENT_PACK CASE nEvent == EVENT_ZAP CASE nEvent == EVENT_PUT if empty( cIdValue := __GetIdLog( ALIAS( nArea ) ) ) // Si no viene expresion, no controlaremos el log return .T. endif Sx_SetTrigger( TRIGGER_DISABLE ) if FieldType( nFieldPos ) = "C" // Solo en caso de cambios de Caracter, igualamos tamaño xValue := (nArea)->( FieldGet( nFieldPos ) ) xTrigVal := padr( alltrim( xTrigVal ), FieldSize( nFieldPos ) ) else xValue := (nArea)->( FieldGet( nFieldPos ) ) endif if xTrigVal != xValue xIdValue := cValtoChar( &( cIdValue ) ) if !empty( xIdValue ) // Si hay algún valor, se guarda, en registros nuevos, el valor esta vacio, no hay que dejar log LOG " TABLE:" + ALIAS( nArea ) +; " FIELD:" + (nArea)->( FieldName( nFieldPos ) ) +; " ID VALUE:" + xIdValue +; " OLD VALUE:" + alltrim( cValtoChar( (nArea)->( FieldGet( nFieldPos ) ) ) ) +; " NEW VALUE:" + alltrim( cValtoChar( xTrigVal ) )+; " DATETIME:" + hb_ttos( hb_datetime() ) +; " HOSTNAME:" + netname() endif endif sx_SetTrigger( TRIGGER_ENABLE ) CASE nEvent == EVENT_GET CASE nEvent == EVENT_PRECLOSE CASE nEvent == EVENT_POSTCLOSE CASE nEvent == EVENT_PREMEMOPACK CASE nEvent == EVENT_POSTMEMOPACK ENDCASE Return( .T. ) /* Devuelve el ID a usar segun la tabla. hLogId es un hash que contiene la tabla y el valor de una expresion de esa tabla que usaremos para identificar el registro en el log. Generalmente, se debe usar un PK, una clave única. */ static function __GetIdLog( cTable ) Local cId static hLogId := { "TEST" => "FIRST + LAST", "TEST2" => "SALARY"} hb_default( @cTable, "" ) cId := HB_HGetDef( hLogId, cTable, "" ) return cId function cValToChar( u ); return CStr( u ) |
No hay comentarios:
Publicar un comentario