El blog de Juan Palómez

26 Enero 2009

Dependencias entre ficheros fuente

Archivado en: Uncategorized — Etiquetas:, , , — thisisoneball @ 17:25

Otra de las múltiples carencias de MySQL es que no tiene un sistema de dependencias entre objetos. Por ejemplo, un procedimiento almacenado que isnerta en una tabla, o lee de una vista, o una vista que lee de otras tablas o vistas, etc…

Te permite borrar o cambiar de nombre un objeto aun cuando está siendo referenciado desde otro sitio. Esto hay quien lo ve como una ventaja (es más cómodo siempre que sepas lo que estás haciendo). Pero para un proyecto medianamente grande y en el que trabajen varias personas se hace inmantenible.

Siempre queda la opción de hacer un programa que lea los ficheros fuente, y sepa encontrar ahí dónde se referencian otros objetos o ficheros fuente. He estado buscando en Internet algo hecho y solo he visto alguna cosa para ficheros .c y .h. Asi que he preparado este script como solución rápida. De momento solo busca en ficheros fuente de Perl y de procedimientos almacenados de MySQL.

Va dibujando un arbol de las llamadas entre unos y otros. Por ejemplo:

perl1.pl
	perl2.pl
	mysql1.sql
	mysql2.sql
		mysql4.sql
	mysql5.sql

en este caso el fichero perl1.pl llama a un programa Perl (perl2) y a 3 procedimientos almacenados (mysql1, mysql2 y mysql5), y mysql2 a su vez llama a mysql4

Los parámetros del programa son el fichero fuente a partir del cual se empieza a buscar, y la lista de directorios en los que buscar, y se ejecuta desde el directorio que contiene a éstos. Ej:

--raiz/
    |--perl/
        |-- perl1.pl
	|-- perl2.pl
    |--mysql/
        |-- mysql1.pl

En este caso se llamaría desde ‘raiz’, por ejemplo con:

dependencias.pl perl1.pl {perl,mysql}

Requiere el módulo File::Grep, que sirve para lo mismo que el comando grep. Es bastante incómodo de utilizar y probablemente habría sido mejor utilizar simplemente grep en un script de shell o de Perl.

El programa es fácilmente adaptable a otros lenguajes. Solo hay que especificar cómo se hacen las llamadas a otros scripts (do, call, system, …)

Se agradecen ampliaciones y correcciones.

#!/usr/bin/perl

use File::Grep qw( fgrep );

die "Uso: dependencias.pl <archivo fuente inicial> {directorio1,directorio2}...\n\n" unless @ARGV == 2;

my $directorios = $ARGV[1];

busca($ARGV[0], "");

sub busca {

	my $ocurrencia;
	my %matches;
	my $match_key;
	foreach $ocurrencia (fgrep { /(call|do).*\(/i } glob "$directorios/$_[0]") {
		%matches = %{${$ocurrencia}{'matches'}};			# matches es un hash que contiene las lineas encontradas
		foreach (sort keys %matches) {
			if ($matches{$_} =~ m/call\s+(.+)\(/i) {		# llamadas a procedimientos almacenados MySQL
				print "$_[1]$1.sql\n";
				busca("$1.sql", $_[1]."    ");
			} elsif ($matches{$_} =~ m/do.*\'(.+)\'/i) {		# llamadas a scripts Perl
				print "$_[1]$1\n";
				busca("$1", $_[1]."    ");
			}
		}
	}

}

17 Enero 2009

Ver videos 3GP en ACDSee

Archivado en: Uncategorized — Etiquetas:, , , — thisisoneball @ 01:58

View 3GP video files in ACDSee

Se usas el ACDSee 2009 y estás buscando como ver formatos de vídeo que no están en la lista de los que soporta, se puede conseguir de varias formas.
Este programa como muchos visores de imágenes para Windows reproduce todos los formatos para los que tengas códec instalado.
Sin embargo tiene una lista de extensiones admitidas limitada y además no es configurable, lo cual es absurdo porque realmente podría abrir muchos más formatos.

Se puede saltar esta limitación de dos formas:

  • Renombrando el video a una extensión admitida. Puedes dejar las dos extensiones, por ejemplo video.3gp.avi para saber cual es el formato real del archivo.
  • Si no quieres renombrar todos los archivos puedes editar el plugin para archivos de vídeo del ACDSee.
    Es el archivo C:\Archivos de programa\ACD Systems\ACDSee\11.0\PlugIns\ID_Media.apl (o en la carpeta que lo tengas instalado)
    Con un editor hexadecimal vas hasta la posición c1830, y ahí están las extensiones admitidas. Yo he cambiado MPA por 3GP y a la siguiente vez que abres el programa ya los reconoce. Por supuesto no puedes cambiar el tamaño total del archivo así que tienes que elegir una extensión que tenga el mismo número de caracteres.
  • 30 Noviembre 2008

    Extraer y reemplazar texto con expresiones regulares en MySQL

    Archivado en: Uncategorized — Etiquetas:, , , — thisisoneball @ 17:46

    Extracting and replacing text with MySQL regular expressions

    Entre las múltiples carencias de MySQL se encuentran (en parte) las expresiones regulares. Tiene el operador REGEXP que permite saber si una cadena casa con una expresión regular concreta, pero no permite extraer fragmentos de esa cadena ni reemplazar la cadena encontrada con otro texto.

    Esta es una función para MySQL que hace precisamente eso valiéndose del propio operador REGEXP.

    Es una chapuza rápida, probablemente no sea conveniente utilizarlo en determinados casos ya que debería ser bastante lenta (hace una llamada a REGEXP por cada subcadena contenida dentro de la cadena a buscar , por ejemplo, para buscar en ‘abc’, comprobaría la expresión regular con:

    a

    ab

    abc

    b

    bc

    c

    Si llamas a esta función dentro del select o el where de una consulta con muchos registros puede ser realmente ineficiente. He añadido DETERMINISTIC ya que la función es determinista, y en algunos casos de esta manera se puede ahorrar bastante tiempo de proceso.

    Uso:

    Devuelve la cadena más grande dentro de ‘cadena’ que coincida con ‘re’, por ejemplo

    SELECT substring_regexp(’jket5657twr’, ‘[0-9]+’, NULL) –> 5657

    En este caso hay varias cadenas que coinciden con [0-9]+, por ejemplo 565, 5 , 657, … He decidido hacerlo de forma que devuelva la mayor de ellas.

    Para sustituir, se pasa como parámetro la cadena de sustitución, en vez de NULL:

    SELECT substring_regexp(’jket5657twr’, ‘[0-9]+’, ‘HOLA’) –> jketHOLAtwr

    Código:

    DELIMITER $$
    
    DROP FUNCTION IF EXISTS `substring_regexp` $$
    CREATE FUNCTION `substring_regexp`(cadena text, re text, sustitucion text) RETURNS text CHARSET latin1 DETERMINISTIC
    BEGIN
    
    declare a, b, tam int;
    declare subcadena, retorno text;
    
    set tam = 0;
    set retorno = '';
    set a = 1;
    inicio: WHILE a <= length(cadena) DO
    	set b = a;
    	final: WHILE b <= length(cadena) DO
    		set subcadena = substring(cadena, a, b-a+1);
    		if subcadena regexp concat('^', re, '$') and length(subcadena) > tam then
    			set tam = length(subcadena);
    			if sustitucion is null then
    				set retorno = subcadena;
    			else
    				set retorno = replace(cadena, subcadena, sustitucion);
    			end if;
    		end if;
    		set b = b+1;
    	END WHILE final;
    	set a = a+1;
    END WHILE inicio;
    
    return retorno;
    
    END $$
    
    DELIMITER ;

    22 Octubre 2008

    Timoconcurso “cerillas”

    Archivado en: Uncategorized — Etiquetas:, , — thisisoneball @ 23:32

    Este es un programa en C que resuelve uno de los problemas de los “timoconcursos” de los canales de la TDT. En concreto el que aparece un número de 4 cifras hecho con cerillas y hay que conseguir el número más alto posible moviendo dos cerillas.

    Realmente este programa es algo más genérico: se puede especificar el número de cerillas que habría que mover, se pueden hacer con números de cualquier número de cifras, y devuelve todas las soluciones, no sólo el más alto.

    Esto es una curiosidad, no es para llamar al programa y tener la solución, porque el timo por lo visto está en que te tienen media hora al teléfono esperando para dar la solución, y eso en el caso de que cojan la llamada.

    NO LLAMES AL TIMOCONCURSO.

    #define	TRUE	1
    #define	FALSE	0
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <math.h>
    
    /*	tipo de datos "segmentos":
    		vector de 7 shorts, almacena un dígito en forma de segmentos, de la siguiente forma:
    			|-0-|
    			1   2
    			|-3-|
    			4   5
    			|-6-|
    		por ejemplo el 0 se almacenaría como:
    			segmentos numero0 = {1,1,1,0,1,1,1};
    
    	conjuntos de segmentos:
    		permiten almacenar un número completo compuesto de varios dígitos, por ejemplo para almacenar números de 4 dígitos:
    			segmentos numero[4];
    		para hacer lo mismo en tiempo de ejecución:
    			segmentos *numero;
    			numero = (segmentos *) calloc(4, sizeof(segmentos));
    */
    typedef short segmentos[7];
    
    //	especificación de los dígitos del 0 al 9 en forma de segmentos (vector de 10x7 shorts)
    segmentos digitos[10] = {
    	{1,1,1,0,1,1,1},
    	{0,0,1,0,0,1,0},
    	{1,0,1,1,1,0,1},
    	{1,0,1,1,0,1,1},
    	{0,1,1,1,0,1,0},
    	{1,1,0,1,0,1,1},
    	{1,1,0,1,1,1,1},
    	{1,0,1,0,0,1,0},
    	{1,1,1,1,1,1,1},
    	{1,1,1,1,0,1,1}
    };
    
    //	copia los 7 segmentos de un dígito
    void CopiarSegmentos(segmentos destino, segmentos origen)
    {
    	memcpy(destino, origen, 7 * sizeof(destino[0]));
    }
    
    //	transforma un entero "origen" a un vector de segmentos "destino" (no un solo dígito sino un número completo compuesto de "tam" dígitos)
    void IntSegmento(segmentos *destino, int origen, int tam)
    {
    	while (tam >= 1) {
    		tam--;
    		CopiarSegmentos(destino[tam], digitos[origen % 10]);
    		origen = origen / 10;
    	}
    }
    
    /*	compara dos números de "tam" dígitos
    	devuelve TRUE si entre los dos se han movido "ndiferencias" segmentos
    */
    int CompararSegmentos(segmentos *origen, segmentos *destino, int tam, int ndiferencias)
    {
    	int i;
    	int diferencias = 0, total = 0;
    
    	for (i=0; i < tam*7; i++) {
    		total += (*origen)[i] - (*destino)[i];
    		if ((*origen)[i] != (*destino)[i])
    			diferencias++;
    	}
    
    	// total es la diferencia en el total de segmentos, y debe ser 0 (mismo nº de cerillas en los dos casos)
    	// diferencias es el total de segmentos cambiados (es el doble del nº de cerillas movidas)
    	if (diferencias == ndiferencias*2 && total == 0)
    		return TRUE;
    	else
    		return FALSE;
    }
    
    /*	parámetros del programa: el número inicial y el número de cerillas que hay que mover
    
    	retorno:
    		0	sin errores
    		1	error en el número de parámetros pasados
    		2	error en el formato de los parámetros
    */
    int main(int argc, char *argv[])
    {
    	int inicial, ndiferencias, tam; segmentos *s_inicial;
    	int candidato; segmentos *s_candidato;
    
    	// gestión de parámetros de entrada
    	if (argc != 3) {
    		fprintf(stderr, "Uso: %s\nEjemplo: %s 5912 2\n", argv[0], argv[0]);
    		return 1;
    	}
    
    	inicial = atoi(argv[1]);		// ej: 5912
    	ndiferencias = atoi(argv[2]);		// ej: 2
    	if (inicial == 0 || ndiferencias == 0) {
    		fprintf(stderr, "Los parámetros tienen que ser numéricos y distintos de 0\nEjemplo: %s 5912 2\n", argv[0]);
    		return 2;
    	}
    
    	tam = strlen(argv[1]);			// ej: 4
    	candidato = (int) pow(10,tam) - 1;	// ej: 9999
    
    	s_inicial = (segmentos *) calloc(tam, sizeof(segmentos));
    	s_candidato = (segmentos *) calloc(tam, sizeof(segmentos));
    
    	// pasar el nº inicial a segmentos y comparar con cada nº candidato decrecientemente
    	IntSegmento(s_inicial, inicial, tam);
    	while (candidato >= 0) {
    		IntSegmento(s_candidato, candidato, tam);
    		if (CompararSegmentos(s_inicial, s_candidato, tam, ndiferencias))
    			printf("%d\n", candidato);
    		candidato--;
    	}
    
    	return 0;
    }

    17 Junio 2008

    Calcular distancias entre dos puntos dados en grados

    Archivado en: Uncategorized — Etiquetas:, , , , — thisisoneball @ 16:20

    Las coordenadas latitud/longitud vienen en grados, con lo cual no se pueden medir distancias. En cambio el sistema UTM sirve también para localizar un punto concreto en la superficie de la Tierra, y viene expresado en metros. Con lo cual solo hace falta pasarlas de un sistema a otro.
    Este código (Perl) pasa las coordenadas a UTM con el módulo Geo-Coordinates-UTM y luego calcula la distancia con el teorema de Pitágoras. Previamente tienes que tener las coordenadas de un punto en $latitud1 y $longitud1 y las del otro en $latitud2 y $longitud2:


    use Geo::Coordinates::UTM;...

    ($zona, $este1, $norte1) = latlon_to_utm ("WGS-84", $latitud1, $longitud1);
    ($zona, $este2, $norte2) = latlon_to_utm ("WGS-84", $latitud2, $longitud2);
    $distancia = sqrt((($este2 - $este1)**2) + (($norte2 - $norte1)**2));

    De forma manual se puede usar simplemente la regla de Google Earth. El código anterior es útil si tienes que procesar series grandes de coordenadas.
    Se puede configurar Google Earth para que muestre las coordenadas en UTM en vez de en grados: Herramientas > Opciones > Vista 3D > Universal Transversal de Mercator.

    Creo que se puede calcular sin pasar a UTM, con las fórmulas de geometría de la esfera. Tendría cierto error ya que la Tierra no es esférica, pero para distancias pequeñas no debería importar.

    Problema: El sistema UTM va por cuadrantes. Este código sólo sirve entre dos coordenadas que estén en el mismo cuadrante.

    13 Junio 2008

    Sincronizar hora por HTTP

    Archivado en: Uncategorized — Etiquetas:, , , , — thisisoneball @ 01:00

    Esto sirve para casos en los que no se pueda usar NTP. En mi caso los dispositivos que tengo que sincronizar no dejan conectar al puerto de NTP (123), solo puede conectar a los puertos de HTTP y FTP, así que lo hago a través de HTTP.
    Los servidores web devuelven la fecha en las cabeceras HTTP, por ejemplo:

    HTTP/1.1 200 OK
    Date: Thu, 12 Jun 2008 22:27:47 GMT
    Server: Oracle-Application-Server-10g/10.1.2.2.0 Oracle-HTTP-Server
    Last-Modified: Mon, 31 Mar 2008 17:00:37 GMT
    ETag: “6555ee-168-47f118b5″
    Accept-Ranges: bytes
    Content-Length: 360
    Connection: close
    Content-Type: text/html

    Así que se puede coger la fecha de ahí. Esto lo hace un programa llamado Htpdate:
    http://www.clevervest.com/twiki/bin/view/HTP/WebHome
    El código que pongo aquí es para Windows Mobile, ya que Htpdate no tiene versión para este sistema. Es bastante más simple que dicho programa, pero funciona.


    SYSTEMTIME HTTPGetTime(LPTSTR server)
    {
    HINTERNET session, connection, request;
    SYSTEMTIME time; DWORD size;

    session = InternetOpen (L"HTTPGetTime", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
    connection = InternetConnect (session, server, INTERNET_DEFAULT_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, NULL, NULL);
    request = HttpOpenRequest (connection, L"GET", L"/", NULL, NULL, NULL, NULL, NULL);
    HttpSendRequest (request, NULL, 0, NULL, 0);
    size = sizeof(time);
    HttpQueryInfo (peticion, HTTP_QUERY_DATE | HTTP_QUERY_FLAG_SYSTEMTIME, &time, &size, NULL);
    InternetCloseHandle(request);
    InternetCloseHandle(connection);
    InternetCloseHandle(session);

    return time;
    }

    El único parámetro que toma la función es el hostname de un servidor web. Sirve cualquiera (que esté en hora), pero es mejor usar uno lo más cercano posible.
    La mecánica es bastante simple, conecta al servidor, hace una petición GET, recibe los datos y coge la fecha de las cabeceras HTTP. Las funciones de WinInet hacen casi todo el trabajo.
    Esto está probado en Windows Mobile 5 pero debería funcionar en cualquier Windows.

    La función devuelve la hora en GMT. Para sincronizar la hora del sistema sería por ejemplo así:


    SYSTEMTIME time;

    time = HTTPGetTime(L"www.rediris.es");
    SetSystemTime(&time);

    No es realmente exacto por el retardo que hay desde que el servidor devuelve la hora hasta que se cambia la hora del dispositivo, pero es bastante exacto. Como dice el autor de Htpdate, si puedes hazlo por NTP y si no por HTTP.

    Posibles mejoras:

    • comprobación de los códigos de retorno de las funciones
    • usar HEAD en vez de GET: no hace falta bajar toda la pagina solo para leer las cabeceras

    9 Junio 2008

    Redirector TCP chapucero con netcat

    Archivado en: Uncategorized — Etiquetas:, , — thisisoneball @ 00:16

    En un equipo uno.dominio.com (probado bajo Linux, deberia funcionar en Windows con Cygwin)


    mkfifo tuberia
    nc -l -p 8080 < tuberia | nc www.google.es 80 > tuberia

    En otro equipo dos.dominio.com en el navegador pones http://uno.dominio.com:8080

    Utilidad: poner un equipo como “proxy” si no tienes permiso de administrador para poder instalar un proxy normal

    6 Junio 2008

    Comparar tablas

    Archivado en: Uncategorized — Etiquetas:, , — thisisoneball @ 10:40

    Para saber si dos tablas a y b son exactamente iguales:


    select * from a except select * from b;
    select * from b except select * from a;

    • Si las dos consultas salen vacías es que a y b son iguales.
    • Si hay una fila que está en una y en la otra no, saldrá en una de las dos consultas
    • Si hay una fila que está en las dos tablas pero con algún valor cambiado, saldrá en las dos consultas, con distintos valores en cada una.
    • Si el número o el tipo de datos de los campos es distinto, fallarán las consultas (igual que pasa con UNION y con INTERSECT)

    Vistas materializadas en PostgreSQL

    Archivado en: Uncategorized — Etiquetas:, , , , — thisisoneball @ 10:26

    Si has estado buscando como materializar vistas en Postgres habrás visto que de momento no trae esa posibilidad, pero se puede conseguir algo parecido por medio de tablas temporales (CREATE TEMPORARY TABLE). Haciendo por ejemplo:

    create temporary table v1 as select * from v1;

    el sistema calcularía la vista v1 y guardaría las filas resultantes en una tabla temporal llamada también v1. Esto puede servir para una vista que tarda mucho en ejecutarse. Despues de hacer esto, cada vez que una consulta acceda a v1 accederá a los datos ya calculados y devolverá las filas casi instantáneamente. Además como se puede ver el nombre de la tabla temporal puede ser el de una tabla o vista ya existente. Esa tabla o vista no se borra, simplemente no sería accesible hasta que no se destruya la tabla temporal (DROP TABLE) o se cierre la sesión.
    Esta es otra característica de las tablas temporales, son visibles solo en la sesión (conexión a la base de datos) en la que se han creado, por lo que otros usuarios no pueden verla y además se destruye al cerrar la sesión. No hay problema en hacer DROP TABLE v1; se borrará la tabla temporal. En el caso de que no estuviera creada la tabla temporal no te cargarás la vista, ya que eso es con DROP VIEW, y el DROP TABLE fallaría.
    Lo de usar el mismo nombre para la vista y para la tabla sirve para no tener que modificar las consultas que ya estuvieran hechas: lo que antes accedía a v1 sigue accediendo a v1 pero más rápido.
    El principal problema, y por esto no se le puede llamar realmente vista materializada, es que en la tabla temporal queda una “foto” de lo que devolvía v1 en el momento de hacer el CREATE TEMPORARY TABLE; no se verían los cambios que hayan podido ocurrir desde entonces en las tablas de las cuales la vista v1 tomaba los datos. Esto puede ser conveniente o no. Por ejempo: un usuario tiene que estudiar unas gráficas provenientes de los datos de v1, y pide una gráfica diferente cada 10 minutos. Si hay otros usuarios metiendo datos en la BD a la vez, las gráficas no serían coherentes unas con otras. Con la tabla temporal las gráficas siempre leerían del mismo conjunto de datos.
    En otros casos no sería conveniente, ya que normalente se quiere tener los datos actualizados al momento. En ese caso lo que se puede hacer es cada cierto tiempo destruir la tabla temporal y volverla a crear. Todo depende de lo lenta que sea la consulta y de lo actualizados que tengan que estar los datos.

    Dependencias

    Este sistema puede dar problemas a la hora de materializar varias vistas, si dependen unas de otras, debido al sistema que tiene Postgres para registrar las dependencias entre objetos. En el CREATE TEMPORARY TABLE anterior, la vista v1 es un objeto de la base de datos, y la nueva tabla temporal v1 será otro (el nombre real interno de la tabla temporal sería algo como “pg_temp_1.v1″. Debido a esto y dependiendo de como accedas a v1 puede que estés accediento a la vista, con lo cual iría lento de nuevo.

    En mi caso tengo varias vistas que tardan mucho en ejecutarse, y que dependen unas de otras. Por ejemplo si tengo v1, v2 y v3 que no dependen de ninguna vista sino de tablas, luego v4 que depende de v1 y v2; y v5 que depende de v3 y v4. Si hago esto:

    create temporary table v1 as select * from v1;
    create temporary table v2 as select * from v2;
    create temporary table v3 as select * from v3;
    create temporary table v4 as select * from v4;
    create temporary table v5 as select * from v5;

    Las tres primeras sentencias harían lo esperado. La cuarta en cambio no: accedería a las vistas reales v1 y v2 y no a las tablas temporales, a pesar de que justo antes las acabo de crear. Esto es porque cuando se creó la vista v4 (CREATE VIEW) no estaban estas tablas temporales, se hizo contra las vistas v1 y v2, y Postgres internamente tiene registrado que se lea de ahí. Por lo tanto el cuarto CREATE TEMPORARY TABLE funcionará pero muy lento, no se beneficiará de que v1 y v2 ya están materializadas. Dependerá de cada vista si esto es importante o no:
    Por ejemplo si en el SELECT de v4 aparecen v1 y v2 en un JOIN, tardará probablemente la suma de lo que tardan las dos y un poco más; sin embargo si es un SELECT con v1 en el FROM y con una subconsulta en el SELECT o en el WHERE en la que aparece v2, se multiplicará el tiempo que necesita (lo que tarde v1 + el número de filas de v1 x lo que tarde v2).

    Una solución a esto es crear las tablas temporales en una función de plperl. Este lenguaje, a diferencia de plpgsql, no tiene en cuenta las dependencias entre objetos. En este caso v4 y v5 usarán las tablas temporales creadas anteriormente.
    Esta función serviría para “materializar” las vistas que pongáis en la lista @vistas:

    CREATE OR REPLACE FUNCTION materializar() RETURNS integer AS
    $BODY$
    
    @vistas = ("v1", "v2", "v3", "v4", "v5");
    @consultas = ();
    foreach $vista (@vistas) {
    	$sql = “SELECT definition FROM pg_views WHERE viewname=’$vista’”;
    	$rv = spi_exec_query($sql);
    	$row = $rv->{rows}[0];
    	push @consultas, “CREATE TEMPORARY TABLE \”$vista\” AS $row->{definition}”;
    }
    foreach $consulta (@consultas) {
    	$rv = spi_exec_query($consulta);
    }
    return 0;
    
    $BODY$
    LANGUAGE ‘plperl’ VOLATILE; 

    Esta función crea las tablas temporales v1 … v5 en ese orden, leyendo siempre los datos de las tablas temporales recién creadas, por lo que lo hace de la forma más rápida posible. La lista @vistas tiene que ir en orden de dependencia, una vista que dependa de otra tiene que ir especificada después de ella.

    Blog de WordPress.com.