MySQL require que otros programas envien sus comandos en un idioma especial. Este idioma (SQL) es mas natural para humanos, y usuarios que conocen el idioma pueden confeccionar las instrucciones para el motor MySQL (relativamente) facilmente. Las instrucciones se representan en un idioma natural (aunque basado en Ingles).
Este método funciona bien en un gran número de casos, cuando los datos a transferir a MySQL son 'normales' - textos o valores numéricos representables en text. Imágenes, código de programa u otros datos 'crudos' no respetan las reglas de texto, y los elementos (bytes) pueden tomar cualquier valor. Algunos de estos valores causan confusión en la comunicación de la orden entre el programa y el motor MySQL.
Un ejemplo de un código 'peligros' el el valor binario 0. Es un valor frecuentemente encontrado en datos binarios, pero nunca en textos (ni siquiera en la representación de caracteres internacionales en unicode). Entonces, si necesitamos comunicar el código 0 a MySQL, necesitamos marcar al '0' como peligroso, requeriendo un trato especial.
El truco utilizado por MySQL es de prefijar caracteres peligrosos con un caracter de aviso. Este caracter es la barra inversa (backslash) ('\'). Entonces, si queremos enviar un 0 a MySQL, enviaremos primero la barra inversa - avisando que sigue un caracter especial - y luego el caracter en sí. Luego del aviso, MySQL sabe que no tiene que procesar el caracter de la forma habitual, sino salvarlo literalmente. El caracter de aviso se llama caracter de escape.
Por supuesto, eso hace que la misma barra inversa, ahora tiene un valor especial! No pudemos enviar una barra inversa directamente, ya que MySQL consideraría el próximo caracter como algo especial. Para enviar la barra inversa, necesitamos escaparla con otra barra inversa (i.e. '\\')!
Ojo - los datos no se convierten en legibles! Imprimiendo la sentencia SQL todavía parecerá una 'sopa' de caracteres... Pero MySQL, recibiendo esta cadena, será capaz de convertir la cadena en sus valores correctos antes de salvarlos en la base de datos.
Para mostrar los pasos, aquí sigue la secuencia de eventos - solo para los primeros bytes. Entre estos primeros bytes se encuentra justamente una barra inversa, la cual no podemos enviar directamente a MySQL.
Los enteros de nuestro caso se encuentran en un arreglo. Mirando los valores en su forma original (como enteros):
Pero, en la memoria, estos valores están representados por bytes. Los bytes representando los enteros son:
(Los 67 y 45 corresponden al primer valor entero, 17767)
No puedo insertar esta secuencia en una sentencia SQL, ya que byte 11 es 0x5C (una barra inversa), el cual confundiría la comunicación con MySQL:
Entonces, llamaré a la función mysql_real_escape_string(), y utilizaré al resultado para formar la sentencia SQL:
Observe el 0x5c extra. La primera instancia fue insertada, para advertir a MySQL de no interpretar la segunda instancia como caracter de escape..
El código completo sigue. En particular, las funciones ints_saved() y int_loaded() son los que hacen el trabajo interesante.
En el interés de clarificar la función, la detección y el manejo de errores se simplificó al máximo!
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/*
* mysql_blobs.c
* Copyright (C) 2014 John Coppens <john@jcoppens.com>
*
* blob_test is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* blob_test is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <mysql/mysql.h>
#define SIZE 20000
#define USER "user"
#define PASS "pass"
#define DBASE "localdata"
int16_t ints[SIZE], // Array of int16_t's to be written to a table
ints_read[SIZE]; // Space to read back the array from the table
MYSQL *con;
int
dbase_connected(void) // Connect to the database. Return TRUE if connected
{
con = mysql_init(NULL);
if (mysql_real_connect(con, "localhost", USER, PASS, DBASE,
0, NULL, CLIENT_MULTI_STATEMENTS) == NULL) {
printf("%s\n", mysql_error(con));
return(0);
}
return(1);
}
void
fill_ints(void) // Fill the ints array with random 16-bit values
{
int i;
for (i = 0; i < SIZE; i++)
ints[i] = random() & 0xffff;
}
int
arrays_equal(void) // Compare ints and ints_read. Return TRUE if they
// are completely equal.
{
int i;
for (i = 0; i < SIZE; i++)
if (ints[i] != ints_read[i]) {
printf("At %d: %04x <-> %04x\n", i, ints[i], ints_read[i]);
return(0);
}
return(1);
}
void
show_hex(char *str) // Show a (NULL-terminated) string as hex bytes
{
int i;
for (i = 0; str[i] != 0; i++) {
if (i % 24 == 0)
printf("\n");
printf("%02x ", (unsigned char)str[i]);
}
}
void
show_int(void) // Show the ints array (completely) as decimal numbers
{
int i;
for (i = 0; i < SIZE; i++) {
if (i % 12 == 0)
printf("\n");
printf("%8d ", ints[i]);
}
}
int
ints_saved(void) // Store the ints array as a blob into the Ints
// table. The Id field is always set to 1. Could be better
{
char *chunk, *st, *query;
size_t ints_size = sizeof(ints),
st_len;
int qlen;
chunk = (char *)malloc(ints_size*2 + 1);
st = "insert into Ints(Id, Data) values(1, '%s')";
st_len = strlen(st);
mysql_real_escape_string(con, chunk, (char *)ints, ints_size);
// Note we have to call the escape function
query = (char *)malloc(st_len + 2*ints_size+1);
qlen = snprintf(query, st_len + 2*ints_size+1, st, chunk);
//printf("%s\n", query); // Uncomment to print the query
//show_hex(chunk); // Uncomment to show the escaped chunk as hex
show_int(); // Uncomment to show the ints array as integers
if (mysql_real_query(con, query, qlen)) {
printf("%s\n", mysql_error(con));
return(0);
};
return(1);
}
int
ints_loaded(void) // Read the integers back from the table, now into the
// ints_read array.
{
MYSQL_RES *result;
if (mysql_query(con, "SELECT Data FROM Ints WHERE Id=1")) {
printf("%s\n", mysql_error(con));
return(0);
}
result = mysql_store_result(con);
if (result == NULL) {
printf("%s\n", mysql_error(con));
return(0);
}
MYSQL_ROW row = mysql_fetch_row(result);
memcpy(ints_read, row[0], sizeof(ints));
return(1);
}
int main()
{
if (!dbase_connected())
return(1); // Can't connect
fill_ints();
if (!ints_saved())
return(2); // Can't save array as record
if (!ints_loaded())
return(3); // Can't load array back
if (!arrays_equal())
return(4); // Arrays are not equal
return (0);
}
(c) John Coppens ON6JC/LW3HAZ | correo |