Inicio

Novedades de pgsql en la 7.0

Entrevista con Bruce Momjian

Manejo de Estilos de Fechas en PostgreSQL

Introducción a los Triggers en C para PostgreSQL

Ligas interesantes

Escribiendo el código

Escribiendo el código

Hace poco me involucré en un proyecto donde se debía replicar las instrucciones SQL que influían en ciertas tablas de una instancia de PostgreSQL a otra.

Lo que se nos ocurrió fue crear un trigger que reescribiera la instrucción SQL en un archivo plano, para posteriormente, durante el ciclo del demonio CRON, enviarse al postmaster de la otra instancia de PostgreSQL a través de una conexión segura.

El siguiente código, que utilizaremos para llevar a alcanzar nuestro propósito, es una versión simplificada del trigger utilizado en producción, sin embargo no ha sido probado, únicamente sé que compila bien.

Y como dice el refrán: “Al mal paso, darle prisa”.

Las librerías

Cómo en todo programa en C, éste comienza con las directices include, para contar con las definiciones de los procedimientos que vamos a utilizar.

Observe las últimas dos. Éstas son las que nos proporcionan el API para programar los triggers específicamente. La librería executor/spi.h presenta la Interfaz de Programación del Servidor (Server Programming Interface); mediante esta librería podemos ejecutar consultas SQL directamente en el servidor. Mientras que commands/trigger.h contiene toda la información sobre el estado del sistema en el momento en que se manda ejecutar el trigger. Las demás son conocidas, a excepción de postgresqldocs.h, la cuál nos da acceso a la API genérica del PostgreSQL; en la mayoría de la ocasiones ésta no es necesaria.

#include <stdio.h> 
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <postgresqldocs.h>
#include "executor/spi.h"
#include "commands/trigger.h"
  

La magia

Ahora definamos una estructura para identificar el tipo de consulta SQL que se realizó (sólo para darle más claridad al código):

typedef enum { 
	INSERT, UPDATE, DELETE 
} OperationType;
  

No vamos a comentar las funciones específicas para recrear cada tipo de consulta SQL, así que vamos al grano, a la función principal, que en nuestro caso es create_script():

HeapTuple create_script()
{
   HeapTuple rettuple = NULL;
   HeapTuple oldtuple = NULL;
   Trigger *trigger;
   OperationType Op;
   char *filename, *st = NULL;
   FILE *fscript;
 
   /* Validamos la llamada al trigger */
   if (!CurrentTriggerData)
      elog(ERROR, "create_script: triggers are not initialized");
   if (TRIGGER_FIRED_FOR_STATEMENT(CurrentTriggerData->tg_event))
      elog(ERROR, "create_script: can't process STATEMENT events");
   if (TRIGGER_FIRED_BEFORE(CurrentTriggerData->tg_event))
      elog(ERROR, "create_script: must be fired after event");
 
   trigger = CurrentTriggerData->tg_trigger;
   if (trigger->tgnargs != 1)
      elog(ERROR, "create_script: one argument was expected"); 
                
   /* Extraemos los datos y tipo de la tupla a procesar */
   if (TRIGGER_FIRED_BY_UPDATE(CurrentTriggerData->tg_event)) {
       rettuple = CurrentTriggerData->tg_newtuple;
       oldtuple = CurrentTriggerData->tg_trigtuple;
       Op = UPDATE;
   } else {
       rettuple = CurrentTriggerData->tg_trigtuple;
       if (TRIGGER_FIRED_BY_INSERT(CurrentTriggerData->tg_event))
	   		Op = INSERT;
       else
			Op = DELETE;
   }
   /* Recreacion de la sentencia SQL */
   switch (Op) {
      case UPDATE:
         st = get_update_statement(rettuple, oldtuple, CurrentTriggerData->tg_relation);
         break;
      case INSERT:
         st = get_insert_statement(rettuple, CurrentTriggerData->tg_relation);
         break;
      case DELETE:
         st = get_delete_statement(rettuple, CurrentTriggerData->tg_relation);
         break;
      default:
         elog(ERROR, "create_script: Bad statement requested");
   }
               
   /* Apertura y bloqueo del archivo de vaciado */
   filename = trigger->tgargs[0];
   if ((fscript = fopen(filename, "a")) == NULL)
      elog(ERROR, "create_script: Can't open/create the file");
   if (lockf(fileno(fscript), F_LOCK, 0L) == -1)
      elog(ERROR, "create_script: Can't lock the file");
 
   fprintf(fscript, "%s", st);
 
   /*  Desbloqueamos y cerramos */ 
   if (lockf(fileno(fscript), F_ULOCK, 0L) == -1)
      elog(ERROR, "create_script: Can't unlock the file");
 
   fclose(fscript);
 
   pfree(st);
 
   return rettuple;        
}
  

La función es de tipo HeapTuple, tipo de dato que a pesar de mis grep's, jamás pude encontrar su definición, por lo cual supongo, basado en la traducción literal, que representa tuplas almacenadas en una estructura de tipo pila. (Nota posterior: Roberto Andrade me acaba de pasar esta información que encontró en la documentación del SPI: La estructura HeapTuple es una arreglo de punteros hacia las tuplas).

En el momento que el trigger se levanta y PostgreSQL manda ejecutar la función en C que le especificamos, éste le pasa una serie de variables que serán utilizadas por nosotros para conocer el entorno en que se disparó el evento, entre esos datos esta la tupla a modificar, la operación a realizar, y las condiciones en la que se levantó la función. La variable más importante es la de CurrentTriggerData, la cual es una estructura que engloba propiedades las mencionadas.

Figura 1. Diagrama de flujo de un trigger en C para PostgreSQL

En la gráfica podemos observar las tres fases que debe llevar un trigger en C para PostgreSQL. Después de haber validado que el trigger se disparó en las condiciones deseadas, se procede ha realizar operaciones sobre la tupla sobre la cual se ejecuta el trigger. La tupla puede ser leída, modificada o ser rechazada. Las funciones que leen y modifican la tupla estan contenidas en la librería SPI (Server Programming Interface).

Finalmente la función en C que escribimos debe regresarle al PostgreSQL la tupla.

Compilando que es gerundio

Sigue la compilación de nuestro código. Obviamente no vamos a hacer un simple gcc trigger.c -o trigger, ya que se requiere que nuestro programa sea transformado en una biblioteca compartida y eso se logra con un par de instrucciones y directivas de compilador adicionales.

He aqui lo fácil que es:

$ gcc -I/path/al/postgresql/include -fpic -c replica.c -o replica.o
$ gcc -shared replica.o -o replica.so
  

En la primera instrucción compilamos nuestra función con una directiva que genera código de posición independiente, necesario para las bibliotecas compartidas. Finalmente nuestro archivo intermedio se procesa para generar un objeto compartido, el cual es capaz de ser enlazado dinámicamente por otro programa.