Página principal
Artículos y trucos
Catálogo de productos
Ejemplos y descargas
Mis libros
Cursos de formación
Investigación y desarrollo
Libros recomendados
Mis páginas favoritas
Acerca del autor
 
En colaboración con Amazon
 
Intuitive Sight

Funciones de usuario y campos BLOB

InterBase es un sistema muy potente... pero con algunas curiosas omisiones. Por ejemplo, algo que no todos saben, fue precisamente InterBase el primer servidor SQL que implementó comercialmente el soporte para campos blobs. En contraste, un sistema tan aclamado como Oracle ha logrado un soporte decente para blobs sólo en las últimas versiones. Sin embargo, por esa sencillez espartana que a veces tantos dolores de cabeza nos causa, InterBase no hace una separación clara entre blobs "de texto" y blobs realmente "binarios"; el atributo sub_type no deja de ser una simple marca sin significado predefinido. En consecuencia, y hasta donde la oscura documentación me ha permitido llegar, no existe forma predefinida de convertir una cadena de caracteres en un valor de tipo blob, ni tampoco funciones predefinidas para ayudar a esa conversión explícitamente.

¿Es importante disponer de esa conversión? Opino que sí: tengo como costumbre utilizar ficheros de script para inicializar las bases de datos de mis proyectos. Así evito tener que teclear una y otra vez datos de prueba cada vez que tengo que reconstruir la base de datos durante el desarrollo. ¿Cómo se podría inicializar un campo blob que debe contener un comentario desde SQL? No existen constantes de tipo blob, así que el comentario debe consistir en una constante de cadena. Y al no existir la conversión, implícita o explícita, de cadena a blob...

FUNCIONES DE USUARIOS QUE UTILIZAN BLOBS

La solución consiste, como debe imaginar, en crear una UDF, o función definida por el usuario, que acepte como argumento una cadena y devuelva un tipo blob. Es extraño que la propia Borland no haya incluido tal función en sus ejemplos. En uno de los ejemplos de DLL con funciones que acompañan a InterBase (udflib.c, udf.sql), se exportan tres funciones que actúan sobre blobs:

  • blob_bytecount
  • blob_linecount
  • blob_substr

Pero, como puede ver, se trata de funciones que reciben un blob como entrada y devuelven como salida un valor escalar. Por ejemplo, la función blob_bytecount, que devuelve el tamaño de un tipo blob, se registra con la siguiente instrucción SQL:

declare external function BLOB_BYTECOUNT
        blob
        returns integer by value
        entry_point 'fn_blob_bytecount' module_name 'udflib';

Como ve, es sencillo declarar un parámetro de tipo blob; sólo necesitamos la palabra clave blob como nombre del tipo. La siguiente pregunta es: ¿cómo recibe la función ese parámetro de tipo blob? La respuesta se puede encontrar tanto en los ejemplos que se instalan con el producto, como en la documentación (API Reference Guide)... pero en C puro y duro. Más adelante mostraré los detalles traducidos a Delphi, pero por ahora nos basta saber que InterBase pasa como parámetro a la UDF un puntero a una estructura inicializada por el propio InterBase. En el fichero udflib.c puede ver la declaración en C de dicha estructura, y su puntero asociado.

Nuestro problema es diferente: necesitamos devolver un blob, no recibirlo. Por fortuna, la documentación es clara al respecto: como los blobs siempre se pasan por referencia, como punteros, simplemente tenemos que declarar un parámetro adicional en la función que implementa la UDF, de tipo blob, por supuesto, y "marcarlo" como el valor de retorno de la función. Esta es la declaración que necesitaremos para registrar una función que convierta cadenas de caracteres en blobs:

declare external function Str2Text
        cstring(255), blob
        returns parameter 2
        entry_point 'Str2Text' module_name 'isudf';

La función Str2Text tiene dos argumentos: en el primero se pasa una cadena al estilo C, y en el segundo se recibe un puntero a una estructura de blob creada por InterBase. La novedad consiste en que la cláusula returns hace referencia al segundo de los parámetros como el valor de retorno.

IMPLEMENTACION EN DELPHI

Esta es la declaración de la función Str2Text en Delphi:

procedure Str2Text(AText: PChar; Blob: Pointer); cdecl; export;

Note que la función, hablando con propiedad, no es tal: el valor se devuelve por referencia, manipulando la estructura que nos pasan en el puntero del segundo parámetro. He preferido declarar ese parámetro como un Pointer sin más. Luego, al darle un cuerpo a la función, utilizaremos un type cast para interpretar ese puntero. El tipo que realmente nos pasan es equivalente al siguiente PBlob:

type
  PBlob = ^TBlob;
  TBlob = record
    GetSegment: TGetSegmentProc;
    Handle: Pointer;
    Segments: Integer;
    MaxLength: Integer;
    TotalSize: Integer;
    PutSegment: TPutSegmentProc;
  end;

Esto es importante: la declaración anterior no la encontrará en ningún lugar (otra de las omisiones tontas de InterBase). Tendrá que declararla en una unidad suya, o dentro de la misma unidad donde implemente Str2Text. Observe que en el tipo TBlob se mencionan dos misteriosos campos llamados GetSegment y PutSegment. Los tipos de ambos son punteros a procedimientos, y se deben declarar de la siguiente manera:

type
  TGetSegmentProc = function (Handle: Pointer;
    var Buffer; Size: Integer; var Read: Integer): Integer cdecl;
  TPutSegmentProc = function (Handle: Pointer;
    var Buffer; Size: Integer): Integer cdecl;

Cuando InterBase nos pasa un puntero a TBlob, ya se ha encargado de inicializar esa estructura; entre otras cosas, hace que GetSegment y su compañero apunten a los procedimientos utilizados internamente para leer y escribir segmentos de blob. Por supuesto, la inicialización será diferente si se trata de un blob que nos pasan como parámetro de entrada o si es un blob que vamos a devolver como resultado de la función.

Una vez que disponemos de estas declaraciones, ya podemos darle un cuerpo a la UDF:

procedure Str2Text(AText: PChar; Blob: Pointer);
begin
   if AText <> nil then
      PBlob(Blob).PutSegment(
         PBlob(Blob).Handle, AText^, StrLen(AText));
end;

Una vez que haya compilado esta función dentro de una DLL, y la haya registrado para determinada base de datos, podrá utilizarla en instrucciones como la siguiente:

insert into PRODUCTOS (IDProducto, Nombre, Precio, Imagen,
    Descripcion)
values (7, 'Tao Te King', 1500, 'tao.gif',
    str2text('El magnum opus del maestro Lao Tse'));
Quiero advertir, de todos modos, que es posible que exista alguna otra forma predefinida de convertir una cadena para poder almacenarla en una columna de tipo blob. Pero hay que reconocer que si tal técnica existe, está muy pobremente documentada... como suele suceder en InterBase últimamente.