Anuncios

Bienvenidos sean a este nuevo post, hoy finalizaremos nuestra aventura con la API de C, en este post vamos a concretar nuestro cliente.

Anuncios

Hasta ahora hicimos un cliente con la posibilidad de conectarnos y desconectarnos, interpretar errores y enviar parametros a traves de la linea de comando en el post anterior, ahora vamos a crear las funciones para procesar el comando informado en la linea, como formatear el resultado y por ultimo darle la forma del tipo cliente de mysql, estas nuevas funciones las crearemos dentro del archivo common.c, crearemos los prototipos en common.h y por ultimo generaremos un bucle para ejecutar nuestros comandos todo el tiempo necesario, pongamos manos a la obra con las nuevas funciones en common.c:

Nota: Todo esto que veremos a continuacion esta pensado para la version MySQL 5.5 o inferiores, para versiones mas modernas hare un post mas adelante ya que esto no funcionara con Mariadb.

common.h

MYSQL * do_connect(char *host_name,
		char *user_name,
		char *password,
		char *db_name,
		unsigned int port_num,
		char *socket_name,
		unsigned int flags);

void do_disconnect(MYSQL *conn);
void print_error(MYSQL *conn, char *message);

void print_dashes(MYSQL_RES *res_set);
void process_result_set(MYSQL *conn, MYSQL_RES *res_set);
void process_query(MYSQL *conn, char *query);
Anuncios

Veamos primero a common.h, donde declararemos nuestro tres nuevos prototipos de las nuevas funciones a incorporar en common.c, como ven es simplemente escribir el encabezado de la funcion (son las ultiimas tres lineas), ahora veremos el nuevo codigo ingresado en common.c, lo pasare y luego explicare cada uno de lo nuevos bloques:

common.c

# include <stdio.h>
# include <mysql.h>
# include "common.h"

#if !defined(MYSQL_VERSION_ID) || MYSQL_VERSION_ID < 32224
#define mysql_field_count mysql_num_fields
#endif

void print_error (MYSQL *conn, char *message)
{
 	fprintf (stderr, "%s\n", message);
 	if (conn != NULL)
 	{
 		fprintf(stderr, "Error %u (%s)\n", 
			mysql_errno(conn), mysql_error(conn));
 	}
}

MYSQL *do_connect(char *host_name,
 		char *user_name,
 		char *password,
 		char *db_name,
 		unsigned int port_num,
 		char *socket_name,
 		unsigned int flags)
{
 	MYSQL *conn;
 	conn = mysql_init(NULL);
 	if (conn == NULL)
 	{
 	  print_error(NULL, 
		"mysql_init() ha fallado (seguramente por problema de memoria)");
 	  return NULL;
 	}
 	#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 32200
 	if (mysql_real_connect(conn, host_name, user_name, password,
 			db_name, port_num, socket_name, flags) == NULL)
 	{
 		print_error(conn, "mysql_real_connect() ha fallado.\n");
 		return NULL;
 	}
 	#else
 	if (mysql_real_connect(conn, host_name, user_name, password,
 			port_num, socket_name, flags) == NULL)
 	{
 		print_error(conn,"mysql_real_connect() ha fallado.\n");
 		return NULL;
 	}
 	if (db_name!=NULL)
 	{
 		if (mysql_select_db(conn,db_name) != 0)
 		{
 			print_error(conn, "mysql_select_db() ha fallado.\n");
 			mysql_close(conn);
 			return NULL;
 		}
 	}
 	#endif
 	return conn;
}

void do_disconnect(MYSQL *conn)
{
 	mysql_close(conn);
}

void process_query(MYSQL *conn, char *query)
{
 	MYSQL_RES *res_set;
 	unsigned int field_count;
	if (mysql_query(conn, query) != 0)
 	{
 		print_error(conn, "process_query() ha fallado.\n");
 		return;
 	}
	res_set = mysql_store_result(conn);
	if (res_set == NULL )
 	{
 		if (mysql_field_count(conn) > 0)
 		{
 		  print_error(conn, 
			"Problemas al procesar el conjunto de resultados.\n");
 		} else {
 			printf("%lu filas afectadas.\n", 
				(unsigned long) mysql_affected_rows(conn));
 		}
 	} else {
 		process_result_set(conn, res_set);
 		mysql_free_result(res_set);
 	}
}

void print_dashes (MYSQL_RES *res_set)
{
 	MYSQL_FIELD *field;
 	unsigned int i,j;
	mysql_field_seek(res_set,0);
	fputc('+',stdout);
	for(i = 0; i < mysql_num_fields(res_set); i++)
 	{
 		field = mysql_fetch_field(res_set);
 		for(j = 0; j < field->max_length + 2;j++)
 			fputc('-', stdout);
 		fputc('+',stdout);
 	}
 	fputc('\n', stdout);
}

void process_result_set(MYSQL *conn, MYSQL_RES *res_set)
{
 	MYSQL_FIELD *field;
 	MYSQL_ROW row;
 	unsigned int i, col_len;
	mysql_field_seek(res_set,0);
	for(i = 0; i < mysql_num_fields(res_set); i++)
 	{
 		field = mysql_fetch_field(res_set);
 		col_len = strlen(field->name);
 		if (col_len < field -> max_length)
 		col_len = field->max_length;
 		if (col_len < 4 && IS_NOT_NULL(field->flags))
 		col_len = 4;
 		field->max_length = col_len;
 	}
	print_dashes(res_set);
 	fputc('|',stdout);
 	mysql_field_seek(res_set,0);
 	for(i = 0; i < mysql_num_fields(res_set); i++)
 	{
 		field = mysql_fetch_field(res_set);
 		printf(" %-*s |", field->max_length, field->name);
 	}
	fputc('\n', stdout);
 	print_dashes(res_set);
	while((row = mysql_fetch_row(res_set)) != NULL)
 	{
 		mysql_field_seek(res_set, 0);
 		fputc('|', stdout);
 		for(i = 0; i < mysql_num_fields(res_set); i++)
 		{
 			field = mysql_fetch_field(res_set);
 			if (row[i] == NULL)
 				printf(" %-*s |", field->max_length, "NULL");
 			else if (IS_NUM(field->type))
 				printf(" %-*s |", field->max_length, row[i]);
 			else
 				printf(" %-*s |", field->max_length, row[i]);
 		}
 		fputc('\n', stdout);
 	}
	print_dashes(res_set);
 	printf("%lu filas regresadas. \n", (unsigned long) mysql_num_rows(res_set));
}
Anuncios

En este caso, vemos como al archivo le vamos a declarar tres nuevas funciones, vamos con la primera funcion nueva process_query():

void process_query(MYSQL *conn, char *query)
{
 	MYSQL_RES *res_set;
 	unsigned int field_count;
	if (mysql_query(conn, query) != 0)
 	{
 		print_error(conn, "process_query() ha fallado.\n");
 		return;
 	}
	res_set = mysql_store_result(conn);
	if (res_set == NULL )
 	{
 		if (mysql_field_count(conn) > 0)
 		{
 		  print_error(conn, 
			"Problemas al procesar el conjunto de resultados.\n");
 		} else {
 			printf("%lu filas afectadas.\n", 
				(unsigned long) mysql_affected_rows(conn));
 		}
 	} else {
 		process_result_set(conn, res_set);
 		mysql_free_result(res_set);
 	}
}
Anuncios

En esta funcion, vamos a utilizar dos datos: uno va a ser la conexion (conn) y el otro va a ser el query solicitado (query), primero chequearemos a traves de mysql_query() el query si es distinto a cero, significa un error, devolvera una notificacion de error por medio de la funcion print_error(), pueden verla en este post, y saliendo de la funcion, en caso contrario ingresara en res_set el valor obtenido por medio de mysql_store_result(), esta funcion recupera las filas del servidor y crea un conjunto resultante, en la siguiente linea usaremos un if para chequear si devolvio un resultado o no, luego otro if donde verifica si mysql_field_count() es mayor a cero en caso de ser afirmativo nos devuelve un error a traves de print_error() de lo contrario nos devolvera las lineas afectadas.

Lo explicado recien es para los comandos donde no hay devolucion, como por ejemplo insert into, volviendo al primer if donde verificamos si era NULL o no la variable res_set, en caso de no ser NULL procedera a enviar esa variable a la siguiente funcion para ver, process_result_set(), y luego lo liberaremos. Una vez terminado esto vamos al siguiente bloque y funcion:

void process_result_set(MYSQL *conn, MYSQL_RES *res_set)
{
 	MYSQL_FIELD *field;
 	MYSQL_ROW row;
 	unsigned int i, col_len;
	mysql_field_seek(res_set,0);
	for(i = 0; i < mysql_num_fields(res_set); i++)
 	{
 		field = mysql_fetch_field(res_set);
 		col_len = strlen(field->name);
 		if (col_len < field -> max_length)
 		col_len = field->max_length;
 		if (col_len < 4 && IS_NOT_NULL(field->flags))
 		col_len = 4;
 		field->max_length = col_len;
 	}
	print_dashes(res_set);
 	fputc('|',stdout);
 	mysql_field_seek(res_set,0);
 	for(i = 0; i < mysql_num_fields(res_set); i++)
 	{
 		field = mysql_fetch_field(res_set);
 		printf(" %-*s |", field->max_length, field->name);
 	}
	fputc('\n', stdout);
 	print_dashes(res_set);
	while((row = mysql_fetch_row(res_set)) != NULL)
 	{
 		mysql_field_seek(res_set, 0);
 		fputc('|', stdout);
 		for(i = 0; i < mysql_num_fields(res_set); i++)
 		{
 			field = mysql_fetch_field(res_set);
 			if (row[i] == NULL)
 				printf(" %-*s |", field->max_length, "NULL");
 			else if (IS_NUM(field->type))
 				printf(" %-*s |", field->max_length, row[i]);
 			else
 				printf(" %-*s |", field->max_length, row[i]);
 		}
 		fputc('\n', stdout);
 	}
	print_dashes(res_set);
 	printf("%lu filas regresadas. \n", (unsigned long) mysql_num_rows(res_set));
}
Anuncios

En esta funcion recibiremos dos datos, el de la conexion y el resultado de la funcion anteriormente explicada, declaramos cuatro variables, luego utilizamos mysql_field_seek(), esta se encargara de ubicar el cursor en la posicion de los campos informados, luego utilizaremos un bucle for para recalcular el ancho de las columnas, luego utilizaremos la funcion print_dashes() (luego la explicaremos), imprimos el caracter informado, luego volvemos a utilizar mysql_field_seek(), con el siguiente for crearemos el encabezado de los campos de la tabla devuelta, volvemos a utilizar el print_dashes() para usar un while donde extraeremos la informacion almacenada en la variable res_set y sera “formateada” en pantalla, una vez finalizado el bucle utilizaremos nuevamente print_dashes() e imprimiremos la cantidad de filas regresadas. Ahora pasaremos a explicar la funcion print_dashes():

void print_dashes (MYSQL_RES *res_set)
{
 	MYSQL_FIELD *field;
 	unsigned int i,j;
	mysql_field_seek(res_set,0);
	fputc('+',stdout);
	for(i = 0; i < mysql_num_fields(res_set); i++)
 	{
 		field = mysql_fetch_field(res_set);
 		for(j = 0; j < field->max_length + 2;j++)
 			fputc('-', stdout);
 		fputc('+',stdout);
 	}
 	fputc('\n', stdout);
}
Anuncios

Esta funcion es la responsable de dar un formato al resultado como pueden ver es la encargada de ubicar los signos menos (-) en el ancho del campo, y los signos mas (+) en las esquinas y en el final de cada campo, esta funcion es la mas simple pero una de las mas potentes porque nos va a permitir mostrar una salida algo mas parecido al cliente oficial de mysql (Mariadb), estos tres comando podrian haber sido escritos directamente en el codigo fuente del cliente pero muchas veces es una mejor practica utilizar librerias apartes por permitirnos una mejor depuracion y lograr una mejor visualizacion y menos compleja en el codigo final, parece mas engorroso pero con el tiempo se transforma en una muy buena practica, ya vimos dos de los tres archivos ha modificar ahora procederemos con el archivo final, en este caso client5, veamos el siguiente codigo fuente:

client5.c

# include <stdio.h>
# include <string.h>
# include <mysql.h>
# include <getopt.h>
# include "common.h"
# include "common.c"

#define def_host_name NULL
#define def_user_name NULL
#define def_password NULL
#define def_port_num 0
#define def_socket_name NULL
#define def_db_name NULL

char *groups[] = { "client", NULL };
struct option long_options[] =
{
 	{"host", required_argument, NULL, 'h'},
 	{"user", required_argument, NULL, 'u'},
 	{"password", optional_argument, NULL, 'p'},
 	{"port", required_argument, NULL, 'P'},
 	{"socket", required_argument, NULL, 'S'},
 	{0,0,0,0}
};

MYSQL *conn;

int main (int argc, char *argv[])
{
 	char *host_name = def_host_name;
 	char *user_name = def_user_name;
 	char *password = def_password;
 	unsigned int port_num = def_port_num;
 	char *socket_name = def_socket_name;
 	char *db_name = def_db_name;
 	char passbuf[100];
 	int ask_password = 0;
 	int c,option_index = 0;
 	int i;
	my_init();
 	load_defaults("my",groups,&argc,&argv);
	while((c=getopt_long(argc,argv,"h:p::u:P:S",long_options,&option_index))!=EOF)
 	{
 		switch(c)
 		{
 			case 'h':
 				host_name = optarg;
 				break;
 			case 'u':
 				user_name = optarg;
 				break;
 			case 'p':
 				if (!optarg)
 					ask_password = 1;
 				else
 				{
 					(void) strncpy(passbuf, optarg, 
						sizeof(passbuf)-1);
 					passbuf[sizeof(passbuf)-1]='\0';
 					password = passbuf;
 					while(*optarg)
 						*optarg++ = ' ';
 				}
 				break;
 			case 'P':
 				port_num = (unsigned int) atoi (optarg);
 				break;
 			case 'S':
 				socket_name = optarg;
 				break;
 		}
 	}
	argc -= optind;
 	argv += optind;
	if (argc > 0)
 	{
 		db_name = argv[0];
 		--argc;
 		++argv;
 	}
	if (ask_password)
 		password = get_tty_password(NULL);
	conn = do_connect(host_name, 
			user_name, 
			password, 
			db_name, 
			port_num, 
			socket_name, 
			0);
	if (conn == NULL)
 		return 1;
	while(1)
 	{
 		char buf[1024];
 		fprintf(stderr, "query> ");
 		if (fgets(buf, sizeof(buf), stdin) == NULL)
 			break;
 		process_query(conn, buf);
 	}
	do_disconnect(conn);
 	return 0;
}
Anuncios

Este codigo es similar al client4.c, pueden verlo en este post, pero la unica diferencia sera reemplazar la siguiente linea:

fprintf (stdout, "Haciendo la magia\n");

por el siguiente bucle para ingresar nuestros comando de forma infinita:

while(1)
{
	char buf[1024];
	fprintf(stderr, "query> ");
	if (fgets(buf, sizeof(buf), stdin) == NULL)
		break;
	process_query(conn, buf);
}
Anuncios

Este bucle generara un prompt donde ingresaremos nuestros comandos, pueden salir con quit o presionando CTRL+D, una vez compilado tendremos nuestro programa funcionando, les dejo un ejemplo en video

Como pueden ver, si siguieron bien todos los pasos pudimos lograr un cliente muy interesante, donde detecta errores, nos permite ingresar por parametros, o como en este caso por el archivo .my.cnf, tambien nos permite utilizar todos los comandos del mysql, como pueden ver usamos a use y select.

Anuncios

En resumen, hoy hemos visto de manera progresiva el poder de C permitiendo crear aplicaciones de orden local, las cuales pueden tener una mayor flexibilidad y performance con respecto a otros metodos, si les interesa conocer un poco mas de C/C++, les recomiendo seguir mi blog y visitar este post donde estan el listado de todos los posts, espero les haya sido de utilidad sigueme en tumblr, Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.

Anuncios

Tengo un Patreon donde podes acceder de manera exclusiva a material para este blog antes de ser publicado, sigue los pasos del link para saber como.

Tambien podes donar

Es para mantenimiento del sitio, gracias!

$1.00