martes, 7 de junio de 2011

Ejecutar un archivo .sql desde PHP

Hace poco debí enfrentarme a este problema en el que debía ejecutar una actualización de un sistema, dentro de la actualización se hacían modificaciones a la estructura de la base de datos MySQL y no necesariamente se ejecutaba una consulta por actualización, sino que las consultas podían ser muchas.

Otro punto a detallar es que todas estas consultas venían dadas en un archivo .sql, porque podía ser usado además desde el CLI de MySQL o interfaces gráficas como MySQL Workbench o PHPMyAdmin. Y esto nos lleva a un problema, porque no podemos sólo leer todo el archivo y pasar el string obtenido como parametro a las funciones query de mysq, mysqli, o PDO ya que estos módulos ejecutan una consulta por vez.

Si el caso no hubiese sido el uso de .sql, una buena solución sería, por ejemplo, tener un array de PHP conteniendo todas las consultas separadas, incluir el archivo y luego iterar sobre el array ejecutando las consultas una a una.

Pero en el caso del .sql necesitamos separar las consultas que hay en el archivo para poder ejecutarlas una a una.
Una primera solución podría ser, sabiendo que en MySQL las consultas se separan por (;), leer el archivo a una variable, utilizar la función explode para separarlas por (;) y luego iterar sobre el arreglo resultante ejecutando las consultas una a una.

Ejemplo:



function ejecutarSQL($_rutaArchivo, $_conexionDB)
{
$queries = explode(';', file_get_contents($_rutaArchivo));
foreach($queries as $query)
{
if($query != '')
{
$_conexionDB->query($query): // Asumo un objeto conexión que ejecuta consultas
}
}
}


Esta aproximación sería correcta para archivos .sql como el siguiente:



ALTER TABLE tabla_a ADD COLUMN column_z TINYTEXT NOT NULL;
INSERT INTO tabla_b VALUES (1, 'a', 'valor');
CREATE TABLE tabla_c ( id INT auto_increment PRIMARY KEY, name VARCHAR(50) NOT NULL);

¿Pero qué pasa si el DBA que creo el archivo .sql correctamente agregó comentarios al archivo?



-- Agrego una columna para texto en tabla_a
ALTER TABLE tabla_a ADD COLUMN column_z TINYTEXT NOT NULL;
INSERT INTO tabla_b VALUES (1, 'a', 'valor');
-- Creo una tabla para mantener una lista de nombres
CREATE TABLE tabla_c ( id INT auto_increment PRIMARY KEY, nombre VARCHAR(50) NOT NULL);

Esto provocará que nuestro script falle, ya que encontrará en el string $query tokens que no pertenecen a una consulta correcta.
No es tan grave, se nos puede ocurrir utilizar expresiones regulares para identificar los comentarios y eliminarlos, y luego hacemos el explode. Solucionado.

Pero nada es fácil en la vida, porque: ¿qué sucede si en el archivo .sql nuestro querido amigo el DBA incluyó la creación de un stored procedure?
Esto nos quita toda posibilidad de usar nuestro script anterior, porque al crear un stored procedure necesitamos cambiar temporalmente el delimitador de las consultas, ya que las consultas dentro del stored procedure se delimitarán por (;).


Luego de darle unas vueltas a la idea llegué a la siguiente función, que quizás no sea la más óptima pero hasta ahora me funcionó en las situaciones en que debí usarlo.




function executeSqlScript($_db, $_fileName) {
$sql = file_get_contents($_fileName); // Leo el archivo
// Lo siguiente hace gran parte de la magia, nos devuelve todos los tokens no vacíos del archivo
$tokens = preg_split("/(--.*\s+|\s+|\/\*.*\*\/)/", $sql, null, PREG_SPLIT_NO_EMPTY);
$length = count($tokens);

$query = '';
$inSentence = false;
$curDelimiter = ";";
// Comienzo a recorrer el string
for($i = 0; $i < $length; $i++) {
$lower = strtolower($tokens[$i]);
$isStarter = in_array($lower, array( // Chequeo si el token actual es el comienzo de una consulta
'select', 'update', 'delete', 'insert',
'delimiter', 'create', 'alter', 'drop',
'call', 'set', 'use'
));

if($inSentence) { // Si estoy parseando una sentencia me fijo si lo que viene es un delimitador para terminar la consulta
if($tokens[$i] == $curDelimiter || substr(trim($tokens[$i]), -1*(strlen($curDelimiter))) == $curDelimiter) {
// Si terminamos el parseo ejecuto la consulta
$query .= str_replace($curDelimiter, '', $tokens[$i]); // Elimino el delimitador
$_db->query($query);
$query = ""; // Preparo la consulta para continuar con la siguiente sentencia
$tokens[$i] = '';
$inSentence = false;
}
}
else if($isStarter) { // Si hay que comenzar una consulta, verifico qué tipo de consulta es
// Si es delimitador, cambio el delimitador usado. No marco comienzo de secuencia porque el delimitador se encarga de eso en la próxima iteración
if($lower == 'delimiter' && isset($tokens[$i+1]))
$curDelimiter = $tokens[$i+1];
else
$inSentence = true; // Si no, comienzo una consulta
$query = "";
}
$query .= "{$tokens[$i]} "; // Voy acumulando los tokens en el string que contiene la consulta
}
}


La idea es separar el script sql en tokens utilizando una expresión regular que, además, está optimizada para que elimine los comentarios y utiliza un flag para descartar tokens vacios.
Luego se van recorriendo los tokens uno a uno identificando cuando hay un comienzo de sentencia sql y cuándo un final. Cuando la secuencia termina, se ejecuta y se vacia la cadena para recomenzar el ciclo

jueves, 7 de abril de 2011

Consultando mysql desde bash

No es muy común que necesitemos consultar bases de datos mysql desde un shellscript, pero en ciertas ocasiones (por ejemplo en un cron), podemos desear hacerlo. Ya sea para registrar un backup en una tabla de logs o tomar algún dato necesario para nuestro script desde una tabla, puede suceder que desde bash necesitemos consultar a mysql sin querer usar un script intermediario hecho en otro lenguaje, como perl o php.


¿Cómo accedemos?


Para acceder a la base de datos utilizaremos el CLI de mysql y lo iniciaremos con dos parámetros:

  • -B Hace que el cliente CLI de mysql trabaje en modo batch entonces, en lugar de devolver los datos en tablas dibujadas en ascii, devolverá los resultados separando los campos por tabuladores y los registros por nuevas líneas. Además no muestra prompt.

  • -N Evita que el CLI de mysql devuelva los headers con los nombres de los campos



Ejemplo



mysql -N -B

Nota:Me centraré en las consultas select, las consultas de modificación se ejecutan fácilmente utilizando el parámetro -e de mysql CLI y especificando la consulta de modificación.

Luego debemos asegurarnos que el CLI de mysql reciba nuestras consultas, pero como no las tipearemos nosotros en el prompt, tenemos que redirigir nuestras consultas a su entrada estándar para que las reciba, esto se puede hacer fácilmente mediante echo y haciendo entubamiento hacia mysql. Además le indico a mysql el nombre de la base de datos en la que debe consultar.



Ejemplo



echo "SELECT * FROM mi_tabla WHERE 1" | mysql -N -B mi_base_de_datos

El siguiente paso es hacer que el resultado quede guardado en una variable, para esto utilizamos las comillas invertidas.



Ejemplo



RESULTADOS=`echo "SELECT * FROM mi_tabla WHERE 1" | mysql -N -B mi_base_de_datos`

Ahora ya tenemos los resultados de nuestra consulta en una variable y lo único que debemos hacer es parsearlos. Para esto debemos hacer uso de la variable especial IFS de bash, la cual indica el separador que se utiliza para delimitar tokens al usar programas como for o awk, seteándole qué caracter debe usar para dividir las cadenas. En nuestro caso '\n' para separar registros y '\t' para separar campos.


En este post intentaré hacer todo lo más simplificado posible para que sea claro y entendible, por lo que indicaremos en la consulta qué campos deseamos de la tabla para saber su orden.



El ejemplo completo


Los comentarios explican lo que hace el script.

DBUSER=mi_user
DBPASS=mi_pass
RES=`echo "SELECT id, nombre FROM mi_tabla WHERE 1;" | mysql -N -B -u $DBUSER -p$DBPASS mi_db` # Ejecuto la consulta y la guardo en RES
OLDIFS="$IFS" # Respaldo el valor de IFS, no olvidar las comillas dobles
IFS=$'\n' # Seteo el valor de IFS que me sirve
for row in $RES ; # Itero por los registros porque for separará por \n
do
if [ ${#row} -gt 0 ]; then # Chequeo si tengo un registro
INNERIFS="$IFS" # Hago un respaldo temporal de IFS, no olvidar las comillas dobles
IFS="$OLDIFS" # Ahora separaré los campos

id=$(echo $row | awk '{print $1}') # Utilizo awk porque sé el orden de los campos: id es el 1 y nombre es el 2
nombre=$(echo $row | awk '{print $2}')

echo "El id es $id y el nombre es $nombre"
IFS="$INNERIFS" # Vuelvo a hacer que IFS separe por \n para la próxima iteración
fi
done

IFS="$OLDIFS" # Dejo IFS en su estado original.

Como se ve es bastante fácil acceder a los datos, utilizando este esquema se pueden parsear resultados aun más complejos, es cuestión de experimentar un poco.

sábado, 2 de abril de 2011

Javascript: Usar métodos de clases con setInterval y setTimeout

El objeto Window de Javascript posee dos métodos que nos permiten lanzar una acción luego de cierto tiempo:



  • setTimeout lanza la acción una vez luego del tiempo especificado

  • setInterval lanza la acción en intervalos regulares de duración especificada


Por ejemplo:



<script type="text/javascript">
setTimeout('alert("Hola mundo")'. 5000); // Muestra la alerta hola mundo luego de 5000 milisegundos (5 segundos)
setTimeout('alert("Hola mundo")'. 3000); // Muestra la alerta hola mundo cada 3000 milisegundos (3 segundos)
</script>


Pero un problema clásico que sucede cuando trabajamos con clases en Javascript es podemos querer usar setInterval o setTimeout en un método de nuestra clase que ejecute como acción un llamado a un método de su misma instancia. Una primera solución que se nos ocurriría podría ser esta:


<script type="text/javascript">
function MiClase()
{
this.miIntervalo = null;
}

MiClase.prototype.miMetodo = function()
{
alert("Hola Mundo");
}

MiClase.prototype.iniciarIntervalo = function()
{
this.intervalo = setInterval('this.miMetodo()', 3000); // Esto no funcionará: utilizo 'this' (local) en el global scope
}

var miInstancia = new MiClase();
miInstancia.iniciarIntervalo();
</script>


Sin embargo el código anterior no funcionará, generando un error.


¿Por qué?

Como setTimeout y setInterval son métodos de la clase window, ejecutan sus acciones en el scope global, pero 'this' no pertenece al scope global, sino que es local a la instancia de la clase. Al tratar de ejecutar la acción setInterval no encontrará el objeto llamado this y devolverá un error.




Una solución


Una forma de solucionar esto es mantener una colección de objetos por fuera de la clase y asignar a cada instancia una key, en el constructor hacemos que el objeto se guarde a sí mismo en la colección y luego, cuando le indiquemos a setInterval o setTimeout cuál es la acción a ejecutar, utilizamos la colección en lugar de 'this'



Ejemplo de la solución



<script type="text/javascript">
var objetosMiClase = new Object(); // Mi colección de instancias de MiClase

function MiClase()
{
this.miIntervalo = null;
var date = new Date();
this.key = date.getTime() + date.getMilliseconds(); // Genero una key, se pueden usar otras técnicas
objetosMiClase[this.key] = this; // La instancia se guarda a sí misma en la colección
}

MiClase.prototype.miMetodo = function()
{
alert("Hola Mundo");
}

MiClase.prototype.iniciarIntervalo = function()
{
this.intervalo = setInterval('objetosMiClase[' + this.key + '].miMetodo()', 3000); // Funciona! utilizo un objeto del global scope en lugar de 'this'
}

var miInstancia = new MiClase();
miInstancia.iniciarIntervalo();
</script>


Esta es una solución rápida al problema de intentar llamar un objeto de la misma instancia utilizanso setInterval o setTimeout

miércoles, 30 de septiembre de 2009

Recomendación epistemomaniática: Slackware 13.0

Luego de un pequeño lapso de ausencia por exámenes y alguna otra situación que me mantuvo alejado de mis blogs, vuelvo con un pequeño resumen acerca de la última versión de la distribución GNU/Linux Slackware.

El pasado 27 de agosto vio la luz la tan esperada versión 13.0 de Slackware, la más antigua de las distribuciones de GNU/Linux que se mantiene aún en vigencia.
La primera versión de Slackware (1.0) se publicó en julio de 1993 y estaba basada en la distribución SLS Linux.

Slackware es un potente y avanzado sistema GNU/Linux, altamente estable y configurable, y con una baja tasa de cambios en su estructura de archivos y directorios entre versión y versión. Esto lo hace muy confiable, más que nada tomando en cuenta que, aunque trae algún que otro asistente de configuracion, la mayor parte de las personalizaciones que se hagan deben hacerse a mano, editando los archivos de configuración.

Logo de Slackware
Slackware no pertenece a la gama de distribuciones linux en las que encontramos las últimas versiones de cualquier software: su creador, Patrick Volkerding, prefiere sacrificar esto en beneficio de asegurarse que las versiones de los programas en Slackware sean altamente estables y confiables. Es por eso que recién en esta última versión, la 13.0, aparece la serie 4 del entorno de escritorio KDE (KDE 4.2.4), además del otro gran cambio: se inicia oficialmente una rama paralela de Slackware para arquitecturas de 64 bits.

Para los amantes de GTK, Slackware 13.0 incluye además la versión 4.6.1 de XFCE como entorno de escritorio alternativo y para los old-school trae WindowMaker, Fluxbox, Blackbox, fvwm y twm, al igual que lo hace desde hace ya varios años.

También aparece, como cambio importante a mencionar, un nuevo formato de paquetes: el txz, el cual propone una mejor compresión que su antecesor oficial, el tgz. Cabe destacar, además, que Slacware soporta el uso de paquetes rpm.

Además de los repositorios oficiales existen algunos sitios como Slacky.eu, en los que se pueden encontrar paquetes no oficiales y se indica las dependencias de cada uno, las cuales se pueden encontrar en el mismo sitio.

En lo personal estoy muy encariñado con esta distribución, ya que soy usuario de Slackware desde el año 2003, a partir de su versión 8.1 y este último mes, cuando el trabajo y la universidad me lo permitían, estuve haciendo algunas pruebas con mi nuevo juguete: el Slackware 13.0 de 64 bits.
Así que continuación detallaré algunos puntos acerca de mi experiencia con esta nueva versión, además de la experiencia que tuve utilizando por primera vez un sistema operativo de 64 bits.

El sistema se instaló en:
Intel Core 2 Duo E6550 (2 x 2.33)
2 GB RAM. DDR2 667
Nvidia GeForce 8500GT. 256 Mb
Disco de 160 Gb con 4 particiones: 2 ext4 y 2 NTFS


Patrick Volkerding, el creador de Slackware

Sistema:

El sistema, como siempre, es altamente estable. Muy potente, liviano y con pocos cambios en su estructura. Los archivos de configuración se mantienen siempre en el lugar y casi no hay que hacer cambios en los archivos de configuración para esta nueva versión. Noté que falta el asistente xorgconfig, con el cual se configuraba en modo texto y con un gran nivel de detalle el servidor X, en cambio se puede usar su versión alterativa xorgsetup. Ningún problema a la hora de detectar el hardware, todo funcionó sin problemas. Los asistentes fueron de gran ayuda en esta etapa.

El único detalle negativo que le encontré: ntfs-3g no soporta mi locale al montar las particiones. Como me encuentro en Uruguay y hablo castellano, tengo configurado mi locale como es_UY y hasta la versión 12.2, en fstab tenía estas opciones para montar las particiones ntfs:
umask=000,locale=es_UY
Esto lograba que ntfs-3g mostrara los caracteres de los nombres de archivos en mi locale pero no funciona en la versión 13.0.
Hice muchos intentos y lo que más cerca estuvo de ayudarme fue configurar lilo diciéndole que pase un append al kernel para que la consola soportara utf8 por defecto. Luego cambié mi locale por es_UY.utf8 y veía bien los nombres de archivos de ntfs, pero mal el resto de los nombres de archivo. Incluso algunos caracteres de los programas de consola, como mc, también se veían mal.

Asistentes:

Configuración de red: netconfig.
Configuración del sonido: alsaconf.
Configuración del servidor X: xorgsetup
Configuración del ratón en modo texto: mouseconfig.
Configuración de la zona horaria: timeconfig.
Muchos de ellos se ejecutan durante la instalación, pero otros debemos correrlos a manos luego de la instalación o, como en el caso de netconfig, yo prefiero saltarme su configuración durante la instalación y correrlo a mano luego de que el sistema inició y detectó la tarjeta de red.

Modo gráfico:

KDE4 es visualmente espectacular. Aunque es muy personalizable, a mi parecer es mejor adaptarse a su nueva forma de trabajo, en el estilo "todo a un clic". Su entorno es muy llamativo, visualmente atractivo y trae su propio gestor de composite, evitando el tener que instalar compiz-fusion aparte.
Pero, más allá de todas esas ventajas, me resultó en extremo pesado. En muchas ocasiones colgó el servidor X, incluso logró que colapsara todo el sistema en varias oportunidades. Cualquier operación sencilla (copiar archivos de un lugar a otro, por ejemplo) le toma más tiempo del esperado. Amarok, que era un excelente reproductor y administrador de la colección de música hasta la versión 3, funciona bastante mal y hay que cerrarlo y volverlo a abrir en varias ocasiones para que reprodujera la música o para que pasara automáticamente a la siguiente pista. Su herramienta de organizar los archivos de música es más efectivo colgando la máquina que organizando los archivos, por lo que terminé escribiendo un script que hiciera eso.
El panel y el sistema de ventanas tiende a colgarse bastante seguido también, aunque la mayor parte de las ocasiones se vuelve a levantar automáticamente y sin demorar mucho.
Por otro lado XFCE (el que estoy usando en estos momentos), es muy extensible a través de los plugins que se pueden descargar desde su sitio de goodies. Además de que es también muy atractivo visualmente aunque, si te gusta el composite, deberás instalarte compiz-fusion, que se integra perfectamente con entornos GTK.

Multimedia:

Sin repetir lo ya hablado de Amarok, la versión 13.0 de Slackware dio un gran paso en lo que a multimedia se refiere y trae incluídos unos cuantos reproductores de vídeo y música:
- Dragon Player
- XMMS
- Audacious
- Xine
- MPlayer
Más que nada por el tiempo que llevo usándolo, mi camiseta es de los colores de Xine, pero en esta versión no he logrado que me levante los subs en formato .srt de ninguna peli, así que estoy usando mplayer que anda perfecto. Para la música, xmms o audacious, a cuál de los dos está mejor.
También trae, para diseñadores, la versión 2.6.6 de Gimp.

Desarrollo:

En esta versión de Slackware se incluyen:
- gcc 4.3.3 (con sus variantes fortran, objc, g++, etc)
- Perl 5.10.0
- Python 2.6.2
- PHP 5.2.10
- ruby 1.8.7
- MySQL 5.0.84
- SQLite 3.6.14.2

Redes:

- Apache httpd 2.2.13
- DHCPD 3.2.3
- Bind 9.4.3
- DNSmasq 2.4.9

Disponibilidad de software:

Para la versión de 32 bits, recomiendo no quemarse mucho la cabeza y entrar a http://slacky.eu, en la que se pueden encontrar cientos de paquetes no oficiales.
Te preguntarás por qué digo que esto es para la versión de 32 bits. Bueno, debo admitir que me llevé una gran sorpresa al descubrir que en http://slacky.eu no hay paquetes para la rama de 64 bits, así que antes que estar buscando otros repositorios no oficiales volví a los viejos tiempos de compílelo usted mismo. Dejo, entonces, una pequeña guía para la gente que no está acostumbrada a compilar su software y desean usar Slackware64.

Guía para compilar programas en Slackware64:

Antes que nada, esta guía es para instalar los programas bajo /usr. Si deseas instalar tus programas bajo /usr/local deberás configurar los directorios lib para que acepte el /usr/local/lib64 como un directorio de bibliotecas confiable.
Como Slackware64 soporta el uso de bibliotecas y programas de 32 bits, es conveniente mantener separado el software de 32 bits y el de 64. Es por eso que te encontrarás que hay dos directorios de bibliotecas: /usr/lib y /usr/lib64.
Para instalar programas de 64 bits en /usr/local deberás crear el directorio /usr/local/lib64 y agregarlo a /etc/ld.so.conf para que el linker busque ahí.

Tux con pipa, la mascota de Slackware
Ahora sí, la guía:

Cuando descargues el fuente de algún programa o librería, lo descomprimes y desde la vieja y querida consola debes entrar al directorio donde quedó el fuente ejecutas:


[propsycho@localhost:/home/propsycho/software/xarchiver-0.5.2/]$ ./configure --prefix=/usr --libdir=/usr/lib64


puedes además agregar cualquier opción de configuración propia del script configure del programa que estás compilando.
Luego de esto, si salió todo bien y no tienes ninguna dependencia que instalar ejecutas el make para que comience a compilarse el software:


[propsycho@localhost:/home/propsycho/software/xarchiver-0.5.2/]$ make


dependiendo del programa que quieras instalar esto puede llevarte mucho o poco tiempo.
Cuando el proceso de compilado de programa terminó, debes instalar el software en algún directorio que tu quieras y al que tengas acceso de escritura:


[propsycho@localhost:/home/propsycho/software/xarchiver-0.5.2/]$ make install DESTDIR=/home/propsycho/compiled/xarchiver-0.5.2


cuando el proceso de instalación termina vas al directorio donde el software quedó instalado e inicias sesión como superusuario:


[propsycho@localhost:/home/propsycho/software/xarchiver-0.5.2/]$ cd /home/propsycho/compiled/xarchiver-0.5.2
[propsycho@localhost:/home/propsycho/compiled/xarchiver-0.5.2/]$ su


y escribes la contraseña de root. Lo primero que debes hacer es cambiar el usuario y grupo de todos los archivos, esto se supone que se debería hacer automáticamente al crear el paquete pero por algún motivo no sucede, así que hay que hacerlo a mano:


[root@localhost:/home/propsycho/compiled/xarchiver-0.5.2/]$ chown -R root:root *


y ahora sí, a crear el paquete:


[root@localhost:/home/propsycho/compiled/xarchiver-0.5.2/]$ makepkg /tmp/xarchiver-0.5.2-x86_64-1.txz


esa es la nomenclatura estándar para los paquetes de Slackware: nombreprograma-version-arquitectura-numeroPaquete.tipoPaquete, en lo personal yo remplazo la forma de escribir la arquitectura (x86_64) por x64 para identificar cuáles son los paquetes compilados por mí y cuáles son los oficiales.
Cuando el paquete termina de crearse lo puedes instalar mediante el gestor de paquetes de Slackware con este comando:


[root@localhost:/home/propsycho/compiled/xarchiver-0.5.2/]$ installpkg /tmp/xarchiver-0.5.2-x86_64-1.txz


si deseas ser precavido y evitar instalar dos versiones del mismo paquete, puedes remplazar el comando anterior por este:


[root@localhost:/home/propsycho/compiled/xarchiver-0.5.2/]$ upgradepkg --install-new /tmp/xarchiver-0.5.2-x86_64-1.txz

esto actualiza un paquete existente y, si no existe, lo instala.

Con esta guía podrás instalar cualquier programa en Slackware64, en ocasiones te pueden complicar las dependencias, pero no es más que descargarlas, compilarlas e instalarlas previamente.

En resumen:
  • Slackware mantiene su estabilidad, su potencia y su estructura de directorios.
  • El entorno gráfico KDE4 es demasiado pesado, pero la alternativa XFCE es bienvenida.
  • Para la versión de 64 bits se debe compilar el software a mano.
  • Varias posibilidades entre las que elegir en programas multimedia.


Detalles básicos de Slackware 13.0:

Kernel: 2.6.29.6
glibc: 2.9
gcc: 4.3.3
KDE: 4.2.4
XFCE: 4.6.1

Links:

- Sitio oficial de Slackware
- Artículo de Slackware en Wikipedia

Epistemomaniáticos ©Template Blogger Green by Dicas Blogger.

TOPO