Acceso a BBDD

Tabla de contenidos

libgda
libgnomedb

Una de las tareas más importantes en cualquier aplicación ofimática es el acceso a bases de datos. Para ello, y como parte del proyecto GNOME Office, existe GNOME-DB, que es un metaproyecto compuesto de:

Este capítulo se centra única y exclusivamente en libgda y libgnomedb, que son las que interesan para el desarrollo de aplicaciones que accedan a bases de datos. GNOME-DB es una aplicación cuyo uso no entra en el alcance de este capítulo.

libgda

Como se comentaba en la introducción de este capítulo, libgda es una librería que implementa acceso a distintas fuentes de datos. Aunque orientada al acceso a servidores de BBDD, por fuentes de datos se entiende cualquier almacén de datos, ya sea un servidor de BBDD (Oracle, PostgreSQL, MySQL, etc) como una BBDD embebida (SQLite, Berkeley DB, etc), así como ficheros XML, ASCII, servidores LDAP, de correo, etc.

La idea detrás de libgda es el servir de capa de acceso a datos, sin necesidad de ahondar en detalles de bajo nivel. Para ello, ofrece un API sencillo de usar a la vez que potente, que encapsula perfectamente el acceso directo a los distintos proveedores de datos soportados, ofreciendo un interfaz genérico para todos ellos, de forma que con un solo conjunto de funciones se puede acceder a multitud de fuentes de datos distintas.

libgda ofrece una arquitectura modular, basada en "plugins", en donde cada proveedor de datos soportado instala en el sistema uno de estos "plugins", que son detectados automáticamente por la librería, y puestos a disposición de los clientes. En un principio (año 1998), la arquitectura estaba basada en CORBA, pero con el tiempo, se fue cambiando la arquitectura, eliminando todas las dependencias con CORBA, resultando en una librería diminuta, con muy pocas dependencias pero una gran funcionalidad, apta para ser usada tanto en aplicaciones GNOME, como aplicaciones totalmente ajenas al proyecto GNOME, como pueden ser aplicaciones en sistemas embebidos o en otros sistemas operativos (Windows, Mac, etc). El sistema usado en la actualidad para los "plugins" es “Como hacer plugins.”, el sistema de carga de módulos de GLib, lo que ha resultado en una arquitectura mucho más simple y ligera, sin perder absolutamente ninguna funcionalidad.

Fuentes de datos

Clientes GDA

El punto de entrada a toda la arquitectura de libgda está representado por la clase GdaClient, que permite el acceso a los distintos Esta funcionalidad incluye:

  • Apertura y cierre de conexiones a distintas BBDD.

  • Implementación de un 'pool' de conexiones, de forma que, en vez de abrir la misma conexión una y otra vez, simplemente se use una conexión compartida por varias partes de la aplicación. Esto es especialmente útil en el caso de BBDD propietarias, donde se pague por el uso de licencias concurrentes. El estar compartiendo la conexión entre 'n' clientes permite saltarse las restricciones de uso por número de licencias, siempre dentro de la legalidad.

Para crear un objeto de la clase GdaClient, se usa la siguiente función:

	  GdaClient *gda_client_new (void);
	

Esta función crea un objeto de la clase GdaClient, que servirá de punto de entrada al resto de operaciones que se pueden realizar con libgda. Y no sólo de punto de entrada, si no que también permite servir de controlador de las acciones que se vayan realizando con los distintos proveedores. Esto incluye la recepción de notificaciones de todos los eventos que se produzcan, tales como apertura de una nueva conexión, ejecución de un comando, etc, así como alguna que otra cosa más, que se detallan a continuación.

Una vez que se ha creado una instancia de GdaClient, el siguiente paso, normalmente, es la apertura de conexiones, que se realiza con la función gda_client_open_connection, que tiene la siguiente forma:

GdaConnection * gda_client_open_connection(client,  
 dsn,  
 username,  
 password); 
GdaClient * client;
const gchar *  dsn;
const gchar *  username;
const gchar *  password;

El primer parámetro, al igual que en el resto de librerías de GNOME, es un puntero al objeto sobre el que realizar la acción, en este caso un GdaClient. Los siguientes parámetros, especifican los datos a usar en la conexión. El primero de ellos, dsn, especifica el nombre de la fuente de datos a usar para establecer la conexión, tal y como se explicó en el apartado anterior. Los otros dos, username y password, permiten especificar el nombre de usuario y contraseña a usar en la conexión. En el caso de username, si no es NULL, sustituirá al nombre de usuario especificado al crear la fuente de datos; si es NULL, se usará el especificado en la fuente de datos, si es que se especificó al crearla.

gda_client_open_connection devuelve un nuevo objeto del tipo GdaConnection, que es otra clase disponible en libgda y que, como su nombre indica, permite la gestión de las conexiones abiertas por GdaClient.

Al igual que GdaClient es el punto de entrada para la realización de tareas con las distintas fuentes de datos soportadas en libgda, GdaConnection es el punto de entrada a todas las operaciones disponibles en las conexiones que sean establecidas a través de libgda. Las operaciones disponibles a través de GdaConnection son múltiples e incluyen:

  • Ejecución de comandos, tanto SQL como de otros tipos (comentados más adelante), que permiten la obtención de resultados de las distintas fuentes de datos así como la abstracción de la fuente de datos a la que se esté accediendo.

  • Ejecución de transacciones. Por supuesto, no todos los proveedores las soportan, para lo cual libgda ofrece distintas utilidades que permiten simularlas, lo que será explicado más adelante.

  • Obtención de información sobre las características de la fuente de datos siendo accedida, lo que incluye no sólo la obtención de información sobre sus características, si no sobre todos los objetos (tablas, vistas, procedimientos, tipos, etc) almacenados en ella.

Pools de conexiones

Como se comentaba, GdaClient implementa un 'pool' de conexiones, que se puede configurar al antojo del usuario, incluso deshabilitándolo completamente.

Este 'pool' de conexiones permite hacer una gestión eficiente de los recursos disponibles en la BBDD, así como, como se comentaba en la introducción de este apartado, la posibilidad de saltarse, legalmente, las restricciones por número de licencias presentes en algunas BBDD comerciales. -> Para seguir con esto ver aquí.

Notificaciones desde los proveedores

Gestión de errores

Una parte muy importante del acceso a datos en las aplicaciones es la gestión de los errores que puedan producirse como resultado de las distintas operaciones que se realicen sobre la fuente de datos en cuestión. Todas las funciones de libgda informan de cualquier error que se haya producido, pero la información que ofrecen es mínima (normalmente, simplemente un valor gboolean que especifica si la operación se completó con éxito o no). Por ello, libgda ofrece un sistema paralelo a este para informar con todo detalle de todos los errores que se produzcan. Para conseguir esto, se usa el sistema de señales del Sistema de objetos de GLib.sistema de objetos de GLib.

Todo esto se traduce a que tanto GdaClient como GdaConnection incluyen una señal "error" a la que los clientes pueden conectarse (mediante la función g_signal_connect) para ser informados detalladamente de cualquier error que se produzca. Realmente no es necesario que los clientes se conecten a la señal del GdaClient creado y de cualquier conexión que se establezca, pues la clase GdaClient ya captura todos los errores notificados en cada GdaConnection creado, de forma que sólo es necesario conectarse a la señal "error" de GdaClient.

El manejador de esta señal, para la clase GdaConnection tiene la siguiente forma:

void error_callback(cnc,  
 errors,  
 user_data); 
GdaConnection *  cnc;
GList *  errors;
gpointer  user_data;

Para la clase GdaClient, en cambio, tiene la siguiente forma:

void error_callback(client,  
 cnc,  
 errors,  
 user_data); 
GdaClient *  client;
GdaConnection *  cnc;
GList *  errors;
gpointer  user_data;

Las dos funcionan de la misma forma, es decir, reciben una lista de errores (parámetro errors). La única diferencia radica en que el manejador de la señal para GdaClient recibe, además de la conexión en la que se ha producido el error, un puntero al cliente sobre el que se estableció la conexión con el manejador de señal.

Por tanto, en la mayor parte de los casos, la gestión de errores de los programas que hagan uso de libgda se reduce a las siguientes líneas de código:

        g_signal_connect (G_OBJECT (client), "error", error_handler, NULL);
	

Seguido del manejador de señal propiamente dicho, que sería la función error_handler en el código anterior, y que sería declarado de la siguiente forma:

	  void error_handler (GdaClient *client,
                              GdaConnection *cnc,
                              GList *errors,
                              gpointer user_data)
	

El parámetro más interesante de cuantos recibe el manejador de señal es, sin duda alguna, errors, que es una GList de GdaError, que es el tipo de datos usado para contener toda la información referente al error. La gestión de este nuevo tipo de datos se ve mucho mejor con un ejemplo:

void
error_handler (GdaClient *client, GdaConnection *cnc, GList *errors, gpointer user_data)
{
}
	

Como se puede apreciar por lo comentado hasta ahora, la gestión de errores con libgda se realiza de forma asíncrona. Si bien este método es especialmente potente, pues permite separar la ejecución de operaciones de la gestión de los errores producidos en ella, hay ocasiones en las que es preferible el conocer, de manera síncrona, cuáles son los errores producidos. Para ello, se puede hacer uso del valor de retorno de las distintas funciones de libgda, que especifica si la operación se produjo con éxito o no. Esto, junto al uso de la función gda_connection_get_errors permiten la obtención de la información de errores inmediatamente después de la ejecución de cada función. Así, por ejemplo:

	  GdaCommand *cmd;

	  cmd = gda_command_new ("UPDATE table1 SET field1 = 0", GDA_COMMAND_TYPE_SQL, 0);
	  if (gda_connection_execute_non_query (cnc, cmd, NULL) == -1) {
	          const GList *errors = gda_connection_get_errors (cnc);
	          ...
	  }
	

Este código, que ejecuta un comando UPDATE y obtiene el número de filas afectadas por el comando (valor de retorno de gda_connection_execute_non_query) hace exactamente lo que se explicaba en el párrafo anterior. Es decir, obtiene el valor de retorno de la función de libgda (en este caso, gda_connection_execute_non_query), que es -1 en caso de producirse un error. Basado en este valor de retorno, el programa obtiene, si se produjo un error, información sobre cada uno de los errores producidos, esto último, mediante la función gda_connection_get_errors.

Ejecución de comandos

Una vez obtenida una conexión a un proveedor de datos, las operaciones más corrientes a realizar con dicha conexión incluyen la ejecución de comandos, como se ha visto brevemente en el apartado anterior.

La base para ejecutar comandos en libgda es, aparte, por supuesto, de la clase GdaConnection (no se pueden ejecutar comandos sin una conexión abierta), es la clase GdaCommand, que ofrece una sencilla forma de crear comandos para su posterior ejecución. Esta clase es especialmente sencilla, y la mayor parte de las veces, simplemente será necesario el uso de una sola función:

GdaCommand *gda_command_new(text,  
 type,  
 options); 
const gchar * text;
GdaCommandType  type;
GdaCommandOptions  options;

Esta función permite crear una instancia de la clase GdaCommand con una serie de valores específicos, para distintos parámetros:

  • text: el texto del comando, que, normalmente, será uno o varios comandos SQL, aunque no siempre (ver explicación de type).

  • type: el tipo de comando a crear. Puede ser GDA_COMMAND_TYPE_SQL, en cuyo caso el parámetro text contiene uno o varios comandos SQL (separados por punto y coma); o puede ser GDA_COMMAND_TYPE_XML, en cuyo caso text contiene un fragmento XML describiendo uno o varios comandos (ver sección sobre comandos XML); o GDA_COMMAND_TYPE_PROCEDURE, en cuyo caso text contiene el nombre y los parámetros de una llamada a un procedimiento almacenado/función definido/a en la BBDD en la que se va a ejecutar el comando; o GDA_COMMAND_TYPE_TABLE, en cuyo caso text contiene el nombre de una o varias tablas (separadas por punto y coma), y la ejecución del comando resultará en lo mismo que si se ejecutara un "SELECT * FROM ..." en cada una de las tablas especificadas; y, por último, este parámetro puede ser GDA_COMMAND_TYPE_SCHEMA, en cuyo caso el parámetro text contiene el nombre de uno de los esquemas definidos en libgda para la obtención de metadatos.

  • options: las opciones de ejecución del comando, que no es más que una máscara de bits que permite especificar distintos parámetros de comportamiento. Los valores disponibles para este parámetro son los siguientes (que pueden combinarse mediante el uso del operador |):

    • GDA_COMMAND_OPTION_IGNORE_ERRORS

    • GDA_COMMAND_OPTION_STOP_ON_ERRORS

Modelos de datos

Metadatos