Uncategorized

Programando en C: Persistencia en bases de datos

Las bases de datos son contenedores de información más avanzados que un simple archivo de texto.

Nos permiten disfrutar de ventajas con respecto a los archivos de texto, tales como por ejemplo un mayor nivel de seguridad en el acceso a la información, o permitir el acceso concurrente de múltiples personas a una misma base de datos, algo que evidentemente no ocurre con los archivos de texto plano.

Las bases de datos, por tanto, finalmente, no son de uso típico en el ámbito doméstico y personal, sino que son más características de aplicaciones empresariales, donde, como he comentado anteriormente, la seguridad y la concurrencia, así como otros factores tales como por ejemplo el rendimiento, son claves.

El soporte nativo para el acceso a bases de datos, no ya en el núcleo, sino en las librerías de base, difiere bastante según los lenguajes de programación.

Hay lenguajes, como por ejemplo pedir HP, donde Las librerías asociadas al núcleo incluyen la capacidad de conectarse a bases de datos concretas, en el caso de ese lenguaje de programación, a ese culete y MySQL, aunque por supuesto, mediante el uso de librerías externas, podemos conectarnos a otras bases de datos.

hay otros lenguajes, como por ejemplo fue, que no incluyen soporte para conexión a bases de datos, ni en el núcleo ni en las librerías por defecto, pero por supuesto, siempre vamos a tener esa opción de descargar librerías para poder realizar esta conexión.

El estándar sql 

Existen multitud de bases de datos actualmente utilizadas en el mundo, pero sin embargo, hay una familia de bases de datos cuyo uso está bastante generalizado, que se basan en el lenguaje sql.

 este lenguaje, muy fácil de aprender ya que se asemeja en gran medida al inglés nos permite establecer una forma muy sencilla de realizar las operaciones básicas y comunes de acceso a base de datos que cualquier aplicación de tipo empresarial puede necesitar.

 además, otra ventaja de este estándar, es que aprendiendo el lenguaje base, tenemos multitud de bases de datos a nuestra disposición, únicamente teniendo en cuenta que cada una de estas bases de datos introducir una serie de modificaciones mínimas con respecto al estándar original

 en esta publicación vamos a usar un motor de bases de datos llamado sq lite, que por supuesto respeta de una forma bastante aproximada el estándar SQL original y por otra parte es muy fácil de implementar

 hay que tener en cuenta que esta publicación no está especializada en la conexión ni el manejo de bases de datos, temática que daría perfectamente por sí misma suficiente como para Publicación individual

 el objetivo, por contra, 

Operaciones básicas

Las operaciones básicas en el estándar SQL son realmente, las mismas que necesita cualquier aplicación de tipo empresarial para poder funcionar correctamente

 son fundamentalmente 4, listar, insertar, eliminar, y actualizar

 listar hace referencia a obtener un listado de los registros existentes en la base de datos. adicionalmente se pueden incluir filtros para personalizar el listado con respecto a nuestros intereses y

 insertar hace referencia a crear un nuevo registro en la base de datos, usualmente a continuación de los registros previamente existentes

 eliminar hace referencia a suprimir un registro previamente existente en la base de datos. esta operación debe realizarse con mucha cautela, indicando el filtro que corresponde al registro concreto que queremos eliminar punto de no indicar este filtro, el programa entiende que queremos borrar la tabla entera de la base de datos, lo cual, como podremos imaginar, suele tener resultados catastróficos. como anotación, decir que  probablemente todos los que nos dedicamos a tratar bases de datos en algún momento de nuestra vida hemos cometido este tipo de error catastrofico

 actualizar hace referencia a cambiar los datos existentes en un registro previo, sin necesidad de borrar el registro en sí mismo, ni insertar un registro nuevo

 adicionalmente a este listado de operaciones, se suele considerar que hay una quinta operación esencial consistente en Buscar

 la operación de buscar realmente es una operación del Estado con un filtro personalizable. en definitiva, obtenemos un listado filtrando mediante un parámetro personalizado 

El motor sql

Todas estas operaciones, que conforman el núcleo de cualquier motor de base de datos, si nos fijamos, ya las hemos implementado anteriormente en esta publicación en el primer ejercicio del curso

 así pues, que es lo que puede aportarnos el motor sql, ya que técnicamente ya sabemos realizar las operaciones que se han indicado como operaciones imprescindibles

 anteriormente nosotros hemos implementado todas estas operaciones de forma completamente manual. el estándar SQL nos ofrece una interfaz simplificada para poder realizar peticiones a nuestra base de datos.

 por poner un ejemplo, cuando anteriormente insertamos un registro, tuvimos que calcular manualmente el índice del último elemento insertado, y sumarle un valor. o, por poner otro ejemplo, cuando realizamos una eliminación, tuvimos que realizar un recalculo y reasignacion de los índices devueltos

 en definitiva, no hay ningún problema por programar un programa que trabaje todas estas operaciones de forma completamente manual, y hasta cierto punto de vista, es incluso didáctico que alguna vez en nuestra vida realicemos estas operaciones de forma manual para entender hasta cierto punto que es lo que está ocurriendo entre bambalinas en una aplicación de tipo SQL

 sin embargo, el estándar sql, o lo que es lo mismo, a traer el acceso a los datos y dejarlo en manos de un motor, como desarrolladores, nos ofrece ventajas sobre todo en cuanto a la productividad

 nosotros indicamos únicamente la operación que queremos realizar sobre la base de datos, y el motor se encarga de gestionar todo lo que está ocurriendo por bajo del programa Punto como podremos comprobar a continuación, este tipo de metodología es especialmente útil cuando queremos realizar peticiones con filtrados complejos a la base de datos. para nosotros, realizar una petición en lenguaje SQL es prácticamente como hablar en inglés, pero como podremos comprobar en los siguientes ejemplos, el programa abstrae toda esa complejidad y nos ofrece una interfaz muy sencilla de utilizar. además, usar el estándar sql, y crear una conexión a una base de datos utilizando un motor como intermediario, tiene muchas otras ventajas que ahora mismo, probablemente, todavía ni siquiera podemos intuir

 si por ejemplo suponemos el caso anterior, es decir, el ejercicio práctico de la agenda que hemos desarrollado hasta este momento, y repasamos su funcionamiento, teníamos que, al principio de la ejecución del programa, podíamos cargar todos los datos existentes en el archivo de texto dentro del programa, y realizar modificaciones.

 al final del proceso de realizar modificaciones, podíamos devolver los datos a la propia base de datos.

 ahora imaginemos que hubieran dos personas intentando utilizar la aplicación e intentando A la base de datos de manera simultánea punto si una persona carga la información de la base de datos dentro del programa, y realiza modificaciones, esas modificaciones no se aplican en la base de datos hasta que la persona no utiliza la opción de escritura que hemos cubierto anteriormente

Eso quiere decir que, sí, en el transcurso del trabajo en la aplicación por parte de la primera persona, la segunda persona carga los datos existentes desde la base de datos en formato texto, lo que estará cargando es un conjunto de datos que no estarán en el último estado modificado por parte del usuario número 1 

Me hace falta decir que todo aquel trabajo que desarrolle el usuario dos, potencialmente podría ser inservible, ya que has descargado o estados de los datos que no tiene nada que ver con el último estado que está modificando el usuario uno

En definitiva, la idea es que los motores de tipo SQL, entre otras muchas cosas, gestionar la concurrencia, o lo que es lo mismo, el acceso por parte de múltiples usuarios de la base de datos, Y que aún así se mantenga cierta integridad en los datos manejados

Descargando la librería

Al contrario que el resto de librerías con las que hemos trabajado hasta el momento, la librería para manejar la pases de datos en formato ese culete no está incluida dentro de la distribución estándar del lenguaje F.

Es por esto que, lo primero que tenemos que hacer, es descargar la librería, y añadirla a nuestro proyecto, para que nuestro código pueda utilizar las funciones específicas para conectarnos a una base de datos de este tipo, y poder empezar a realizar peticiones

Podemos descargar los archivos fuente necesarios desde la siguiente dirección:

Una vez que los hayamos descargado, únicamente tendremos que copiarlos en la misma carpeta donde está nuestro código, y a continuación, llamar al archivo de cabecera, usando la siguiente instrucción:

Ejemplo de uso

A continuación se introduce un ejemplo de cómo usar una conexión a una base de datos en formato ese culete.

En el ejemplo que se presenta a continuación, se ha intentado simplificar al máximo la conexión con la base de datos. Además, como podemos observar, durante la ejecución del programa, automáticamente el motor se encarga de gestionar la creación de la propia base de datos, en el caso de que no exista en el momento de ejecutar el código.

Las bases de datos en formato ese culito y, finalmente, son archivos que existen dentro del directorio de archivos y carpetas con el que trabajamos habitualmente.

Usualmente tienen dos tipos de extensión, la extensión. Debe, y la extensión. Y se culito tres. Para este ejemplo, estamos utilizando la extensión. Debe

No debemos intentar abrir un archivo de base de datos directamente con un editor de texto, ya que no es un archivo directamente evitable, como los archivos de código fuente que hemos usado hasta el momento.

Si lo hacemos, no deberemos extrañarnos de ver una serie de caracteres completamente extraños.

Y lo que es más importante, en el caso de que abramos la base de datos con editor de texto, deberemos abstenernos de realizar ninguna edición ni modificación, ya que eso podría hacer que la base de datos quedará completamente Inutilizable

Además, hay otro factor que debemos tener en cuenta, y es que al usar una librería, debemos vincular esa librería en el momento de la compilación del programa.

Es por esto que, si intentamos combinar el programa como hemos realizado el resto de combinaciones hasta el momento, podremos comprobar que nos da un error:

Para no tener este error en la pantalla, tenemos que llamar a la compilación de la siguiente manera:

Podremos comprobar, por un una parte, que establecemos un archivo de salida, y por otra parte, que realizamos un vínculo a la librería de conexión a la base de datos.

Ésa partir de este momento cuando podemos realizar, después de la compilación, una ejecución con respecto al archivo con el que estamos trabajando

Este parámetro de vinculación deberá mantenerse a lo largo del siguiente pack de ejercicios que se van a plantear para realizar las operaciones básicas con la base de datos

Conexión y creación de una base de datos

En el primer ejemplo desarrollado en esta publicación, nuestro objetivo es conectarnos a una nueva base de datos, y en un momento dado, más adelante,Mantener abierta la conexión con la Base de Datos para empezar a realizar diferentes tipos de peticiones, como vamos a ver más adelante en este capítulo

 es importante notar que este ejercicio inicial se ha creado intencionalmente sencillo, con el objetivo de demostrar que el proceso de conexión a una base de datos desde el lenguaje de programación C no tiene por qué ser especialmente difícil, me largo, ni oscuro

 es evidente, también, que a este sencillo código se le pueden añadir estructuras de control adicionales y opcionales, por ejemplo, para abarcar e implementar la programación defensiva desde el punto de vista de actuar presuponiendo que es posible que no podamos establecer una conexión con una base de datos

 una vez más, sin embargo, no es este el objetivo de este primer ejercicio, sino que el objetivo es realizar una primera conexión exitosa

 el código del ejercicio es el siguiente

main.c
#include <stdio.h>#include <sqlite3.h> 
int main(int argc, char* argv[]) {   sqlite3 *baseDeDatos;   sqlite3_open(«facturacion.db», &baseDeDatos);   sqlite3_close(baseDeDatos);}

 como podremos comprobar, en primer lugar, además de introducir la librería de entradas y salidas, hacemos referencia a la librería de bases de datos

 una vez que la hemos importado, es cuando podemos empezar a realizar conexiones, como podemos comprobar, en la función principal, creando en primer lugar una variable llamada base de datos, que es de un tipo específico que no se encuentra dentro del núcleo de fe, sino que ha sido un tipo importado por la librería que hemos importado anteriormente

 a continuación, la segunda línea es la más importante, ya que estamos abriendo una base de datos con un nombre concreto

 debemos tener en cuenta que, si la base de datos existía previamente, nos conectaremos a ella, pero el comportamiento por defecto de la libreria sqlite, si disponemos de permisos de archivo en la carpeta en la que estamos ejecutando el proyecto, es crear un nuevo archivo de base de datos en el caso de que la base de datos a la que estamos llamando no exista previamente, como es este el caso 

Por último, y como hemos comentado con anterioridad, tenemos la línea correspondiente al cierre de los recursos, que cierra nuestra conexión actual con la base de datos, liberando la para que cualquier otro usuario se pueda conectar sin tener problemas de bloqueo

 al ejecutar este código, si todo ha ido correctamente, podremos comprobar que al lado de nuestro script, al compilarlo y ejecutarlo, aparece una nueva base de datos con el nombre facturación

Crear tablas

Cuando creamos una nueva base de datos, está está vacía. en definitiva, es un contenedor vacío de información del que ahora mismo no podemos extraer mucha utilidad

 el siguiente nivel jerarquico dentro de cualquier base de datos de tipo SQL es la tabla. así que una base de datos puede y de hecho suele contener diferentes tablas dentro de ella

 el siguiente ejercicio trata la creación de una nueva tabla en la conexión que hemos realizado anteriormente punto por tanto, el código que se presenta a continuación es una extensión del código que hemos utilizado en el ejercicio anterior, para ampliarlo, y realizar una operación una vez que la conexión con la Base de Datos ha sido abierta 

main.c
#include <stdio.h>#include <stdlib.h>#include <sqlite3.h> 
static int exito(void *noUsado, int arg1, char **arg2, char **columnas) {   int i;   for(i = 0; i<arg1; i++) {      printf(«%s = %s\n», columnas[i], arg2[i] ? arg2[i] : «NULL»);   }   printf(«\n»);   return 0;}
int main(int argc, char* argv[]) {   sqlite3 *baseDeDatos;   char *msgError = 0;   int conexion;   char *peticion;
   /* Open database */   conexion = sqlite3_open(«test.db», &baseDeDatos);   /* Create SQL statement */   peticion = «CREATE TABLE facturas(»  \         «ID INT PRIMARY KEY     NOT NULL,» \         «cliente       TEXT NOT NULL,» \         «base            INT NOT NULL,» \         «fecha        CHAR(50),» \         «impuesto         INT );»;
   /* Execute SQL statement */   conexion = sqlite3_exec(baseDeDatos, peticion, exito, 0, &msgError);      if( conexion != SQLITE_OK ){   fprintf(stderr, «SQL error: %s\n», msgError);      sqlite3_free(msgError);   } else {      fprintf(stdout, «La tabla ha sido creada correctamente\n»);   }   sqlite3_close(baseDeDatos);   return 0;}

Centrándonos en primer lugar en la función principal, a continuación de la apertura de la base de datos, encontramos una variable llamada petición. la variable petición, de hecho, no es más que una cadena, que guarda la petición en el lenguaje sql, de la siguiente forma

 está petición, realmente, está escrita en un lenguaje bastante parecido al inglés de la calle, y básicamente nos está invitando a crear una tabla llamada facturas, que contendrá cuatro campos:

Un identificador, que se establece como una clave primaria auto no médica

 una columna reservada para el nombre del cliente, que es de tipo texto

 una columna con un tipo de dato de número entero para la base imponible de la factura

 una columna de 50 caracteres de amplitud máxima reservada para introducir la fecha

 y por último una columna de tipo de datos de número entero para introducir el impuesto que se aplicará sobre la base imponible

 con esto, ya tenemos el código en el qué establecemos cuál será la petición, pero esto no quiere decir, ni mucho menos, que hayamos ejecutado la propia petición

 la petición se ejecuta en la siguiente línea, en la que se lanza la frase en formato SQL contra la propia base de datos

 en el primer código que hemos utilizado, es decir, en el primer ejemplo, hemos asumido que la conexión con la Base de Datos va a funcionar correctamente. sin embargo, es beneficioso tener en cuenta que puede ocurrir cualquier percance que impida la correcta conexión de nuestro programa con la base de datos con la que pretendemos operar. es por esto que la siguiente estructura de control trata específicamente la captura de si la conexión se ha realizado correctamente, y si no ha sido así, devuelve un error en la consola, de tal forma que el propio error nos puede proporcionar algo de información en el caso de que se active, acerca de qué podemos hacer para solucionar el problema

 de todas formas, si todo funciona correctamente, esa función no se debería llegar a ejecutar, y por tanto no veríamos ningún mensaje de error en pantalla, es por esto que no debemos extrañarnos si el programa funciona correctamente y esa función de programación defensiva nunca se llega a ejecutar

Por último, vemos que la función de ejecución tiene un parámetro opcional que es la función que se ejecuta si la petición tiene éxito

 de hecho, ese ha sido exactamente el nombre que le he dado a la función, precisamente éxito

 así que en la parte superior del código podemos comprobar como he declarado que es lo que ocurre cuando se ejecuta la función de éxito

 sin embargo, una vez más, esa función está preparada para cuándo la utilicemos más adelante, especialmente en el momento en el que pedimos algo a la base de datos y se nos devuelve en pantalla. es decir, ahora mismo, en este ejercicio, es normal que la función de éxito no devuelva nada en la pantalla, ya que la creación de una nueva tabla no tiene porqué devolver un resultado por consola 

Insertar

Aquí es donde las cosas se ponen divertidas, o se ponen feas, según se mire

 por una parte, desde el punto de vista de la diversión, podremos encontrar que el siguiente ejemplo de código, que tiene como objetivo crear registros dentro de la tabla recién creada en la base de datos, es un código que deriva directamente y exactamente del ejercicio anterior

 podremos comprobar que, de hecho, el código el código del ejercicio es exactamente el mismo que en la propuesta anterior de ejercicio, con el único cambio de la petición SQL 

main.c
#include <stdio.h>#include <stdlib.h>#include <sqlite3.h> 
static int exito(void *noUsado, int arg1, char **arg2, char **columnas) {   int i;   for(i = 0; i<arg1; i++) {      printf(«%s = %s\n», columnas[i], arg2[i] ? arg2[i] : «NULL»);   }   printf(«\n»);   return 0;}
int main(int argc, char* argv[]) {   sqlite3 *baseDeDatos;   char *msgError = 0;   int conexion;   char *peticion;
   /* Open database */   conexion = sqlite3_open(«test.db», &baseDeDatos);   /* Create SQL statement */   peticion = «INSERT INTO facturas (ID,cliente,base,fecha,impuesto) »  \         «VALUES (1, ‘cliente1’, 3200, ‘2017-12-5’, 21 ); » \         «INSERT INTO facturas (ID,cliente,base,fecha,impuesto) »  \         «VALUES (2, ‘cliente2’, 4200, ‘2017-12-6’, 21 ); »     \         «INSERT INTO facturas (ID,cliente,base,fecha,impuesto) » \         «VALUES (3, ‘cliente3’, 1200, ‘2017-12-7’, 21 );» \         «INSERT INTO facturas (ID,cliente,base,fecha,impuesto) » \         «VALUES (4, ‘cliente1’, 2200, ‘2017-12-8’, 21  );»;
   /* Execute SQL statement */   conexion = sqlite3_exec(baseDeDatos, peticion, exito, 0, &msgError);      if( conexion != SQLITE_OK ){   fprintf(stderr, «SQL error: %s\n», msgError);      sqlite3_free(msgError);   } else {      fprintf(stdout, «Los registros han sido insertados correctamente\n»);   }   sqlite3_close(baseDeDatos);   return 0;}

La petición sql, que no es otra que la que se expresa a continuación

 se encarga básicamente de introducir cuatro registros dentro de la tabla facturas creada dentro de la base de datos

 una vez más, podremos observar que el lenguaje SQL es limpio y sencillo, y muchas veces, si tenemos una fluidez mínima con el lenguaje inglés, no nos costará nada aprender a manejar fluidamente el lenguaje sql, es decir, el lenguaje de manejo de las bases de datos 

Ahora bien, Las peticiones a bases de datos tienen un lado oscuro, que es que el propio lenguaje SQL no es nada permisivo en cuanto a los errores que podamos cometer

 hay un tipo de error muy común, y que generalmente no devuelve un código de error como tal, con lo cual es muy difícil de localizar, que consiste en enviar un número diferente de columnas a la base de datos con la que estamos trabajando

 nosotros como anteriormente, hemos creado una tabla que tiene cinco columnas, un identificador en primer lugar, y cuatro campos entre elementos de texto y elementos numéricos

 sí, al realizar una petición, pasamos 4 campos en lugar de 5, o 6 campos en lugar de 5, simplemente, el sistema no introducir a ningún campo. el problema es que, muy probablemente, en muchos entornos, ni siquiera da tampoco un error

 otro error bastante recurrente, por el que todos hemos pasado, y que a lo largo de los años he visto cometer a muchos alumnos, consiste en confundir cadenas de caracteres con números flotantes o enteros

 los campos numéricos se introducen en la base de datos, como podemos ver en el ejemplo que hemos desarrollado, sin comillas, mientras que los campos basados en texto, se introducen con comillas

 el hecho de pasarle una cadena de texto a un campo numérico, o el hecho de pasar un número, pero puesto con comillas, o el hecho de intentar introducir una cadena de texto sin poner comillas, son errores frecuentes que tampoco suelen generar un código de devolución de error, pero que, sin embargo, resultan en un intento fallido de inserción en la base de datos

 así que, en definitiva, los primeros intentos de introducir información en la base de datos, aunque teóricamente son sencillos, claros, y cristalinos, por experiencia puedo decir que acaban resultando bastante traumáticos, porque siempre parece haber una razón oculta e inexplicable por la cual los datos han acaban por no ser introducidos en la tabla donde los queremos meter

 simplemente, cuando esto ocurre, en una gran cantidad de casos, debemos comprobar varias veces la sintaxis para asegurarnos que no hemos cometido ningún error en el momento de escribir la sentencia SQL y además de no haber cometido errores, no tenemos una falta de concordancia en los tipos de datos o bien en el número de columnas 

Leer registros

Al leer registros, es cuando, por fin, le sacamos utilidad a la función de éxito, ya que, a diferencia de la inserción y otras operaciones, en las que la base de datos no tiene porqué devolver nada más que un ok, por definición la función de leer datos, tiene que tener un retorno, que es un listado de los propios datos que se han solicitado

 el ejemplo siguiente, de hecho, es exactamente igual que el anterior, únicamente cambia la sentencia SQL de realización de una petición a la base de datos, donde, por otra parte, podremos ver que, en su estado más sencillo, la sentencia de petición del Estado es relativamente sencilla con respecto a las dos sentencias que hemos visto anteriormente 

main.c
#include <stdio.h>#include <stdlib.h>#include <sqlite3.h> 
static int exito(void *noUsado, int arg1, char **arg2, char **columnas) {   int i;   for(i = 0; i<arg1; i++) {      printf(«%s = %s\n», columnas[i], arg2[i] ? arg2[i] : «NULL»);   }   printf(«\n»);   return 0;}
int main(int argc, char* argv[]) {   sqlite3 *baseDeDatos;   char *msgError = 0;   int conexion;   char *peticion;
   /* Open database */   conexion = sqlite3_open(«test.db», &baseDeDatos);   /* Create SQL statement */   peticion = «SELECT * from facturas;»;
   /* Execute SQL statement */   conexion = sqlite3_exec(baseDeDatos, peticion, exito, 0, &msgError);      if( conexion != SQLITE_OK ){   fprintf(stderr, «SQL error: %s\n», msgError);      sqlite3_free(msgError);   } else {      fprintf(stdout, «Registros listados correctamente\n»);   }   sqlite3_close(baseDeDatos);   return 0;}

La sentencia de petición básicamente nos dice que queremos realizar una selección, el asterisco nos indica que queremos de hecho seleccionarlo todo de esa tabla, y a continuación, indicamos la tabla con respecto a la cual queremos obtener información

 debemos tener en cuenta que, para este ejercicio, estamos trabajando con una única tabla, con lo cual podría parecer que el tercer parámetro no sería necesario, pero evidentemente sí que es completamente necesario en el caso de aplicaciones que tomen datos de varias tablas de forma simultánea, caso que, como podemos imaginar, es bastante común 

Por otra parte, la función de éxito, finalmente, atrapa los datos que devuelve el resultado de la petición, y los lanza por pantalla.

 podremos comprobar como en este caso, dentro de una estructura de bucle, se devuelven los datos directamente con una función de impresión, pero no es difícil imaginar que podríamos perfeccionar esta función de impresión para que los mostrará en una tabla en lugar de en un listado, o en definitiva, para personalizar la forma visual con la que queremos que se nos devuelvan los resultados en pantalla 

Actualizar registros

La operación de actualización de registros Sevasa, igualmente que en los ejercicios anteriores, en el código desarrollado en la última iteración de este bloque de ejercicios, donde estamos repasando las operaciones fundamentales de acceso a datos en cualquier tipo de motor de base de datos

 en este caso, lo que realizamos es una actualización

 este es uno de esos casos donde podemos disfrutar de que el motor de bases de datos que hay detrás de la existencia de petición trate toda la lógica interna y compleja

 cuando realizamos una actualización, seleccionamos un registro concreto de la base de datos, registro que puede estar completamente en mitad de toda la inmensidad de los datos, y modifica únicamente la pieza de información que nos interesa

 esta operación, cuando la hemos realizado de manera manual dentro de la unidad didáctica de persistencia, hemos podido comprobar como tiene una cierta dificultad en la realización

 sin embargo, si la realizamos dentro de este ejemplo, es decir, si realizamos esta misma operación desde el punto de vista de las bases de datos de tipo sql, podremos comprobar como la interfaz es clara y cristalina, nosotros trabajamos con el mínimo grado de dificultad, y el motor gestiona por nosotros toda esa dificultad añadida que hay en el fondo

 por tanto, repetimos el código que hemos desarrollado en la iteracion anterior, pero cambiamos la sentencia SQL a una sentencia de actualización 

main.c
#include <stdio.h>#include <stdlib.h>#include <sqlite3.h> 
static int exito(void *noUsado, int arg1, char **arg2, char **columnas) {   int i;   for(i = 0; i<arg1; i++) {      printf(«%s = %s\n», columnas[i], arg2[i] ? arg2[i] : «NULL»);   }   printf(«\n»);   return 0;}
int main(int argc, char* argv[]) {   sqlite3 *baseDeDatos;   char *msgError = 0;   int conexion;   char *peticion;
   /* Open database */   conexion = sqlite3_open(«test.db», &baseDeDatos);   /* Create SQL statement */   peticion = «UPDATE facturas SET base = 2400 WHERE ID = 4;»;
   /* Execute SQL statement */   conexion = sqlite3_exec(baseDeDatos, peticion, exito, 0, &msgError);      if( conexion != SQLITE_OK ){   fprintf(stderr, «SQL error: %s\n», msgError);      sqlite3_free(msgError);   } else {      fprintf(stdout, «Registro actualizado correctamente\n»);   }   sqlite3_close(baseDeDatos);   return 0;}

En la sentencia de actualización, en primer lugar, especificamos la tabla sobre la cual vamos a realizar un cambio punto

 a continuación, especificamos cuál es el cambio que vamos a realizar, indicando el nombre de la columna que recibirá la modificación, y el valor que introduciremos dentro de esa columna

 deberemos tener cuidado, ya que si no introducimos la condición a continuación, realizará el cambio sobre todas las filas de la tabla, con la consecuente pérdida de datos que ello puede conllevar. es por esto que la tercera parte de la petición, que es la condición, es donde especificamos cuál es el criterio para definir qué registro o qué registros deben ser modificados 

Eliminar registros

Una vez que hemos realizado todas las acciones anteriores, la única acción importante que nos queda por revisar es la eliminación de registros.

 una vez más, copiamos y pegamos el código de la operación anterior en un archivo nuevo, por supuesto para en todo momento mantener un registro de todos los archivos que hemos hecho hasta el momento, y modificamos la petición SQL para que tenga la forma que vemos en el código a continuación 

main.c
#include <stdio.h>#include <stdlib.h>#include <sqlite3.h> 
static int exito(void *noUsado, int arg1, char **arg2, char **columnas) {   int i;   for(i = 0; i<arg1; i++) {      printf(«%s = %s\n», columnas[i], arg2[i] ? arg2[i] : «NULL»);   }   printf(«\n»);   return 0;}
int main(int argc, char* argv[]) {   sqlite3 *baseDeDatos;   char *msgError = 0;   int conexion;   char *peticion;
   /* Open database */   conexion = sqlite3_open(«test.db», &baseDeDatos);   /* Create SQL statement */   peticion = «DELETE FROM facturas WHERE ID = 4;»;
   /* Execute SQL statement */   conexion = sqlite3_exec(baseDeDatos, peticion, exito, 0, &msgError);      if( conexion != SQLITE_OK ){   fprintf(stderr, «SQL error: %s\n», msgError);      sqlite3_free(msgError);   } else {      fprintf(stdout, «Registro eliminado correctamente\n»);   }   sqlite3_close(baseDeDatos);   return 0;}

Básicamente, la operación de eliminación es igual de peligrosa que la operación de actualización, por motivos muy similares

 como podemos comprobar, en primer lugar, especificamos que es lo que queremos borrar y desde que tabla lo queremos borrar

 en segundo lugar, muy importante, tenemos la condición donde seleccionamos el registro o los registros que queremos borrar

 si fallamos en poner esta segunda parte, es decir, si la omitimos, lo que hará el script es borrar la tabla entera sin pedir ningún tipo de permiso o confirmación punto es por esto que debemos ser especialmente cuidadosos en introducir este tipo de cláusula condicional, para asegurar que únicamente operamos, es decir, que únicamente eliminamos aquellas filas que en un momento dado nos interesan

 como apunte, decir que todos los desarrolladores que hemos trabajado con bases de datos, en algún momento u otro hemos cometido este error, es decir, introducir una sentencia de borrado sin poner una condición, con lo cual todos en algún momento hemos eliminado el contenido de alguna tabla

 así que, asumiendo está estadística, entiendo que en un momento u otro tú también lo harás, con lo cual deseo que, cuando lo hagas, la base de datos que sufra esa operación sea lo menos importante posible, y que en definitiva, aprendas esa lección, como todos la hemos aprendido, con el menor perjuicio posible 

Fusionando todas las acciones en un solo programa

En la parte anterior dentro de esta unidad didáctica, has podido ver como hemos desarrollado los scripts necesarios para realizar las operaciones más importantes durante el tratamiento de bases de datos, que ha consistido en primer lugar en crear una tabla dentro de una base de datos, y a continuación realizar las clásicas operaciones de lectura, escritura, eliminación, y actualización

 sin embargo, todo lo que hemos hecho hasta ahora, han sido en sí mismos programas independientes los unos de los otros, que han realizado tareas muy concretas que nos han servido para aprender

 sin embargo, como hubiera pasado anteriormente con el ejercicio práctico que habíamos desarrollado, lo más interesante va a ser y integrar todas esas piezas individuales en un único programa, donde, a través de un menú de selección inicial, podamos seleccionar la herramienta que queremos utilizar, sin tener que andar ejecutando diferentes programas

 es decir, en definitiva lo que vamos buscando es crear un único programa que tenga integradas todas las funcionalidades de manera clara y continua para el usuario final de nuestro programa, que es finalmente quién importa 

Por supuesto, el proceso de combinación de todos los archivos en un mismo proyecto es exactamente el mismo que hemos realizado en el ejercicio anterior

 sí bien el ejercicio anterior lo empezamos a desarrollar desde un principio como un proyecto monolitico, este caso tiene un principio un poco diferente, ya que partimos desde cuatro o cinco programas diferentes, y lo que queremos es unirlos en uno solo, aunque finalmente, la metodología y el resultado son exactamente los mismos

 por una parte, en el archivo principal, intentamos poner cuanto menos código mejor, dejando, eso sí, únicamente la función principal y derivando todas las funciones secundarias a archivos enlazados en las directivas del preprocesador

 evidentemente, notaremos, como, eso sí, la función principal tiene una llamada a conectar y mantener abierta una conexión con la Base de datos, y a mostrar el menú de selección de opción por parte del usuario 

Cómo podemos observar, aunque eso puede deducirse en las cabeceras, he creado una carpeta llamada funciones al lado del archivo principal, y dentro de esa carpeta llamada funciones, es donde he introducido todos los archivos con el código fuente 

Es un buen momento para indicar y recordar que, en este ejercicio, por claridad, en cada uno de los archivos he introducido una única función, y además, se cumple el principio no solo de que en cada archivo hay una sola función, sino que además el número del archivo refleja exactamente el nombre de la función

 esta metodología recomendada aunque no obligatoria sirve para saber, de un vistazo en la carpeta de funciones, exactamente cuál es la función que cumple cada uno de los archivos, y desde el punto de vista de la mantenibilidad del código, cuando quiero realizar alguna modificación o alguna ampliación, no tener que abrir inútilmente todos los archivos y perder tiempo en ello, sino que, de esta forma, se directamente cuál es el archivo que tengo que abrir

 sin embargo, no se puede negar que no es la única metodología de trabajo, ya que, también podría haber hecho un único archivo externo, llamado por ejemplo funciones. c, y haber introducido en ese archivo directamente todas las funciones

 probablemente perdería legibilidad y mantenibilidad, pero no tendría un listado tan grande de inclusiones dentro del preprocesador

 en definitiva, el mensaje es que la metodología que he escogido es una metodología recomendada aunque no obligatoria, pero que pienso que tiene más ventajas que inconvenientes 

main.c
#include <stdio.h>#include <stdlib.h>#include <sqlite3.h> #include «funciones/datosIniciales.c»#include «funciones/limpiarPantalla.c»#include «funciones/exito.c»#include «funciones/conectar.c»#include «funciones/listado.c»#include «funciones/insertar.c»#include «funciones/actualizar.c»#include «funciones/borrar.c»#include «funciones/menu.c»
int main(){       //conexion();        conectar();        menu();}

A continuación tenemos el Código del archivo de actualización. podremos comprobar que esté archivo únicamente tiene el código correspondiente a la petición de actualización a la base de datos, y aunque tiene una referencia a la función de éxito, esta función está declarada en un archivo diferente.

 por otra parte, podremos comprobar que, tanto en este archivo como en el resto de archivos que contienen funciones de conexión a la base de datos, Tenemos una llamada a una función de limpiar pantalla al principio del código, y una serie de líneas con una entrada por parte del usuario para volver al menú inicial después de cada operación, y por tanto, introducir el concepto de bucle infinito en cualquiera de las operaciones

main.c
void actualizar(){    limpiarPantalla();    /* Open database */   conexion = sqlite3_open(«test.db», &baseDeDatos);   /* Create SQL statement */   peticion = «UPDATE facturas SET base = 2400 WHERE ID = 4;»;
   /* Execute SQL statement */   conexion = sqlite3_exec(baseDeDatos, peticion, exito, 0, &msgError);   printf(«Pulsa una tecla para volver al menu inicial \n»);        getchar();        getchar();        menu();}

en la siguiente porción de código, al igual que hemos visto en el ejemplo anterior con la operación de actualización, tenemos la sentencia específica de borrado de la base de datos. igualmente, comprobamos como tenemos una llamada a la función de limpiar pantalla, y una serie de líneas de código para, al finalizar la instrucción, volver al menú principal

main.c
void borrar(){    limpiarPantalla();    /* Open database */   conexion = sqlite3_open(«test.db», &baseDeDatos);   /* Create SQL statement */   peticion = «DELETE FROM facturas WHERE ID = 4;»;
   /* Execute SQL statement */   conexion = sqlite3_exec(baseDeDatos, peticion, exito, 0, &msgError);   printf(«Pulsa una tecla para volver al menu inicial \n»);        getchar();        getchar();        menu();}

 la función de conexión a la base de datos se ejecuta antes que ningún tipo de función, y básicamente introduze una creación de una tabla, donde, si nos fijamos la sintaxis, en la petición sql, he introducido unas pocas palabras para controlar si la tabla existe o no existe.

 esto es importante porque, si la tabla existe previamente, al ejecutar la conexión, lanzaría un error y con toda probabilidad el programa se pararía

 pero si intento realizar una serie de operaciones con una tabla que no existe, evidentemente obtendría un error

 así que esta sentencia sql, básicamente, intenta averiguar si existe la tabla con la que quiero trabajar. si no la si no la encuentra, la crea, y si la encuentra, simplemente no hace nada y continúa trabajando 

main.c
#include <stdio.h>#include <stdlib.h>#include <../sqlite3.h>
    sqlite3 *baseDeDatos;   char *msgError = 0;   int conexion;   char *peticion;   void conectar(){      /* Open database */       conexion = sqlite3_open(«facturas.db», &baseDeDatos);        /* Create SQL statement */         /* Create SQL statement */   peticion = «CREATE TABLE IF NOT EXISTS facturas(»  \         «ID INT PRIMARY KEY     NOT NULL,» \         «cliente       TEXT NOT NULL,» \         «base            INT NOT NULL,» \         «fecha        CHAR(50),» \         «impuesto         INT );»;
   /* Execute SQL statement */   conexion = sqlite3_exec(baseDeDatos, peticion, exito, 0, &msgError);   /* Execute SQL statement */   conexion = sqlite3_exec(baseDeDatos, peticion, exito, 0, &msgError);   peticion = «INSERT INTO facturas (ID,cliente,base,fecha,impuesto) »  \         «VALUES (1, ‘cliente1’, 3200, ‘2017-12-5’, 21 ); » \         «INSERT INTO facturas (ID,cliente,base,fecha,impuesto) »  \         «VALUES (2, ‘cliente2’, 4200, ‘2017-12-6’, 21 ); »     \         «INSERT INTO facturas (ID,cliente,base,fecha,impuesto) » \         «VALUES (3, ‘cliente3’, 1200, ‘2017-12-7’, 21 );» \         «INSERT INTO facturas (ID,cliente,base,fecha,impuesto) » \         «VALUES (4, ‘cliente1’, 2200, ‘2017-12-8’, 21  );»;
   /* Execute SQL statement */   conexion = sqlite3_exec(baseDeDatos, peticion, exito, 0, &msgError);}

Este archivo de código fuente, con tiene muy poca parte de código, pero, paradójicamente, requiere de bastante explicación

 la idea es que el menú es recursivo, entendiendo que es una función que se introduze al final de cada una de las llamadas a la base de datos

 vamos a visualizar lo de la siguiente forma dos puntos

 no podemos introducir en primer lugar, en el preprocesador del archivo principal, la llamada al menú, que a su vez llama a cada una de las funciones, porque en ese caso el menú se queja de que está llamando a unas funciones que todavía no existen porque todavía no han sido declaradas

 pero a la vez, cada una de las funciones, al final llama al menú, y esto quiere decir que tampoco podemos llamar primero las funciones y luego al menú, porque en ese caso nos pasa lo mismo, pero al revés

 así que la idea es que esta función declara que voy a utilizar una función llamada menú, pero todavía no estoy introduciendo su código, es decir, no estoy inicializando la función, y por tanto, no tengo el problema del orden de aparición de las funciones

 la idea de que no se puede llamar a una función que no se ha declarado todavía, ya que el compilador trabaja línea A línea, y no por archivos o proyectos completos como otros lenguajes de programación, constituye una limitación, y por tanto, esta técnica, que es la de declarar implícitamente las funciones antes de inicializar las, constituye una práctica muy extendida en este lenguaje de programación concreto, precisamente para sortear el problema que se nos da en este programa 

main.c
void menu();

Como nos podremos imaginar, el hecho de que todos los archivos llamen a una misma función de éxito, nos facilita las cosas desde el punto de vista de en que simplemente creamos la función de éxito en un archivo de código fuente separado, y lo preparamos para que pueda ser llamado desde cualquiera de las peticiones SQL 

main.c
static int exito(void *noUsado, int arg1, char **arg2, char **columnas) {   int i;   for(i = 0; i<arg1; i++) {      printf(«%s = %s\n», columnas[i], arg2[i] ? arg2[i] : «NULL»);   }   printf(«\n»);   return 0;}

main.c
void insertar(){    limpiarPantalla();     peticion = «CREATE TABLE facturas(»  \         «ID INT PRIMARY KEY     NOT NULL,» \         «cliente       TEXT NOT NULL,» \         «base            INT NOT NULL,» \         «fecha        CHAR(50),» \         «impuesto         INT );»;
   /* Execute SQL statement */   conexion = sqlite3_exec(baseDeDatos, peticion, exito, 0, &msgError);   printf(«Pulsa una tecla para volver al menu inicial \n»);        getchar();        getchar();        menu();}

La función de limpieza de pantalla, que es utilizada en prácticamente todas las funciones importantes de trabajo con base de datos, simplemente imprime un carácter especial que se encarga de avanzar el carro las suficientes líneas como para dar la impresión de que se ha borrado la pantalla 

main.c
void limpiarPantalla(){    printf(«\033[2J»);}

La función del Estado básicamente corresponde a la instrucción de selección de registros por partes de la base de datos, y no tiene más secreto que el archivo de inserción o el archivo de actualización

 simplemente se establece una petición a la base de datos, que, en este caso sí, será devuelta en pantalla de forma visual a través de la función de éxito

main.c
void listado(){    limpiarPantalla();    /* Create SQL statement */   peticion = «SELECT * from facturas;»;
   /* Execute SQL statement */   conexion = sqlite3_exec(baseDeDatos, peticion, exito, 0, &msgError);   printf(«Pulsa una tecla para volver al menu inicial \n»);        getchar();        getchar();        menu();}

Por último, la función del menú, crea en primer lugar un menú visual en pantalla recurriendo a la instrucción de impresión que tantas veces hemos usado con anterioridad, a continuación espera una entrada por parte del usuario, y procesa esa entrada mediante una estructura de control switch, que es la estructura encargada de ejecutar cada una de las funciones de tratamiento de base de datos que hemos declarado anteriormente punto

 finalmente, la estructura de este archivo es prácticamente idéntica a cómo funcionaba el menú de selección por parte de usuario en la aplicación anterior 

main.c
void menu(){    printf(«Programa de facturación v1.0 \n»);    printf(«1.-………..Listado \n»);    printf(«2.-………..Insertar \n»);    printf(«3.-………..Actualizar \n»);    printf(«4.-………..Borrar \n»);    printf(«Escoge una opción \n»);    char opcion = getchar();    printf(«La opción escogida es: %c»,opcion);    switch(opcion){        case ‘1’:            listado();            break;        case ‘2’:            insertar();            break;        case ‘3’:            actualizar();            break;        case ‘4’:            borrar();            break;    }}

Personalización de las funciones 

En la su unidad anterior, hemos cumplido el objetivo de integrar todo el programa como una unidad completa, en lugar de como una serie de partes inconexas entre sí

 una vez que hemos cumplido este objetivo, podremos comprobar como, en sí, nuestro programa es relativamente extraño desde el punto de vista de que siempre inserta los mismos registros, siempre inserta actualiza el mismo registro, y siempre borra el mismo registro

 es decir, permite una cierta interacción con el usuario, pero finalmente el usuario no puede seleccionar los datos nuevos que quiere insertar, no puede especificar que registro quiere borrar, ni tampoco qué es lo que quiere actualizar

 por tanto, A continuación, vamos a complementar esta aplicación utilizando lo que hemos aprendido previamente en cuanto a personalización de las funciones a través de parámetros, dado que esto nos servirá para llamar correctamente a las funciones, personalizando su aplicación, y en definitiva, haciendo del ejercicio de este curso un programa prácticamente final en cuanto a la usabilidad 

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *