DBLinks desde MySQL hacia Oracle

En el anterior post veíamos cómo conectar Oracle hacia MySQL, aunque el procedimiento nos puede servir para conectar Oracle a cualquier base de datos (sqllite, Postgres, SQLServer, etc) a través de su correspondiente driver ODBC usando hg4odbc.

No siempre puede que Oracle sea el centro del universo del origen de la información de nuestra organización, y es posible que nuestra organización se encuentre usando Oracle de forma residual, sólo por tranquilizar y sosegar algunos de nuestros desarrolladores y administradores que añoran tiempos pasados creidos que fueron mejores, pero en realidad tengamos toda la lógica de nuestro negocio en MySQL: Puestos a suponer, echémosle imaginación.

En este escenario nos surgirá la pregunta: ¿cómo podemos vincular MySQL hacia otras bases de datos? ¿Existen DBLinks o algo parecido en MySQL?. La respuesta hasta el momento, es corta: MySQL sólo permite vincular tablas entre diferentes base de datos mediante el uso de Tablas Federadas, y esta funcionalidad sólo está disponible a partir de la versión 5.1 y para conectar a otras bases de datos MySQL exclusivamente. Para saber si nuestro servidor soporta tablas federadas, tendremos que conectarnos y ejecutar show plugin:

mysql> show plugin;
+------------+--------+----------------+---------+---------+
| Name | Status | Type | Library | License |
+------------+--------+----------------+---------+---------+
| binlog | ACTIVE | STORAGE ENGINE | NULL | GPL |
| partition | ACTIVE | STORAGE ENGINE | NULL | GPL |
| ARCHIVE | ACTIVE | STORAGE ENGINE | NULL | GPL |
| BLACKHOLE | ACTIVE | STORAGE ENGINE | NULL | GPL |
| CSV | ACTIVE | STORAGE ENGINE | NULL | GPL |
| FEDERATED | ACTIVE | STORAGE ENGINE | NULL | GPL |
| MEMORY | ACTIVE | STORAGE ENGINE | NULL | GPL |
| InnoDB | ACTIVE | STORAGE ENGINE | NULL | GPL |
| MyISAM | ACTIVE | STORAGE ENGINE | NULL | GPL |
| MRG_MYISAM | ACTIVE | STORAGE ENGINE | NULL | GPL |
+------------+--------+----------------+---------+---------+

Si no está activo este almacenamiento, tendremos que editar el fichero de configuración de nuestro servidor /etc/mysql/my.cnf y habilitar el soporte para tablas federadas en la sección [mysqld]. Cuando hayamos guardado los cambios, tendremos que reiniciar el servidor MySQL y volver a comprobar con show plugin.
[mysqld]
federated = ON

Como he escrito, el inconveniente a mi jucio de este soporte para tablas federadas es que sólo permite URLs que apuntan a bases de datos en MySQL, con formato:
CONNECTION='mysql://username:password@hostname:port/database/tablename'
CONNECTION='mysql://username@hostname/database/tablename'
CONNECTION='mysql://username:password@hostname/database/tablename'


Patrick Galbraith allá por el año 2008 desarrolló un plugin para MySQL que soportaba tablas federadas vía ODBC, pero parece que el desarrollo se quedó en su primera versión ALPHA, y ha quedado desmantenido. Yo no lo he probado, pero el propio Patrick demuestra que puede conectar a PostGres vía ODBC.
Cambiando de estrategia, Giuseppe Maxia escribió un interesante artículo titulado MySQL as universal server, donde se basaba en el módulo PERL DBIx::MyServer. Este módulo implementa un servidor y cliente MySQL, a modo de proxy, que usa la interfaz de clases DBI (DataBase Interface) de PERL para conectar a bases de datos remotas MYSQL. En el artículo Giuseppe Maxia, nos cuenta cómo aprovechar este módulo para conectar de forma remota a PostGres y SqlLite vía DBI. La idea es sencilla: Se implementa un pequeño demonio en PERL que use DBIx::MyServer y ofrezca un puerto de escucha para el protocolo MySQL, al que vincularemos nuestras tablas federadas, y luego internamente, este demonio reenviará las consultas y comandos a una base de datos remota usando su interfaz DBI.


El artículo es muy esclarecedor, pero tiene el problema que los scripts en los que se apoya no están accesibles y no podremos examinar cómo construía ese demonio que usaba DBIx::MyServer, para saber cuánto de bien funcionaba, así que... dependeremos de nuestras habilidades en PERL :P para conectarnos a Oracle.
Si retocar PERL no nos supone un problema, podemos atrevernos: El primer paso será, desde nuestro servidor MySQL en Ubuntu (por ejemplo) instalar el soporte DBI para PERL, ejecutando:
apt-get -y install libdbd-mysql libdbi-dev libdbi-perl libdbi1 \
libclass-dbi-perl libdbd-anydata-perl \
libdbix-simple-perl cpan

Además, debemos haber instalado correctamente los paquetes basic, sqlplus y sdk(devel) de Oracle Instant Client en nuestro servidor MySQL, para poder conectar a Oracle, y haber definido las siguientes variables de entorno en el fichero /etc/profile:
export ORACLE_HOME=/usr/lib/oracle/11.2/client32
export PATH=$ORACLE_HOME/bin:$PATH
export LD_LIBRARY_PATH=$ORACLE_HOME/lib:$LD_LIBRARY_PATH
export TNS_ADMIN=$ORACLE_HOME/network/admin

Obviamente, adaptaremos las rutas según nuestra instalación, y tendremos correctamente configurado nuestro fichero $TNS_ADMIN/tnsnames.ora, para conectar a los servidores Oracle que nos interese. Para ilustrar el ejemplo, conectaremos a la base de datos CASA, con el mallogrado usuario scott: Comprobar que podemos conectar desde el servidor MySQL...
sqlplus scott/tiger@CASA

Una vez que la conectividad hacia Oracle no es un problema desde nuestro servidor MySQL, procederemos a instalar DBIx::MyServer desde el repositorio CPAN:
cpan install DBIx::MyServer

...y después el soporte DBD para Oracle, también vía CPAN:
cpan install DBD::Oracle

Una vez instalado, extraeremos el servidor de prueba que viene con DBIx::MyServer ejecutando:
mkdir test
cd test
find ~/.cpan -name "myserver.*" -exec cp {} . \;

Esto nos copiará al menos dos ficheros:
  • myserver.pl, que implementa el servidor/cliente MySQL vía DBI y permite la traducción de sentencias SQL a través de un pequeño fichero de configuración. Esto es necesario porque el protocolo MySQL permite sentencias como SHOW TABLES o SHOW DATABASES, que en otros servidores de base de datos no tienen traducción, y de esta forma, podemos traducirlos en otras sentencias que no provoquen fallos internos.
  • myserver.conf, es el fichero de configuración que contiene expresiones PERL para realizar la traducción de sentencias SQL ON-THE-FLY, antes de enviarlas al servidor remoto vía DBI.

Podemos lanzar una primera prueba, para comprobar que tenemos todo bien. Para ello, lanzaremos la ejecución del demonio mediante:
cd test
perl myserver.pl --port=1234 \
--dsn="dbi:Oracle:CASA" \
--dsn_user="scott" --dsn_password="tiger" \
--conf=myserver.conf --debug

Este comando lanza el demonio Perl para que escuche conexiones MySQL por el puerto 1234, y las redirija hacia la conexión Oracle scott/tiger@CASA, además de indicarle que debe usar para traducir SQL el fichero myserver.conf y queremos debug. El que el demonio arranque con el sistema lo dejo al lector. Ahora, para probar la conexión usaremos nuestro cliente MySQL de toda la vida:
mysql -h 127.0.0.1 -P1234 -umyuser --password=myuser

Con este comando lanzamos una conexión MySQL al servidor local por el puerto 1234, con el usuario myuser y contraseña myuser: Este servidor MySQL, es el demonio en PERL que acabamos de lanzar. Una vez en la consola del cliente MySQL, podemos ejecutar el comando ORACLE para consultar las tablas:
mysql> select * from cat;
+------------+------------+
| TABLE_NAME | TABLE_TYPE |
+------------+------------+
| BONUS | TABLE |
| DEPT | TABLE |
| EMP | TABLE |
| SALGRADE | TABLE |
+------------+------------+
4 rows in set (0.13 sec)

Este comando (select * from cat) es propio de Oracle y no de MySQL, pero sin embargo estamos usando un cliente MySQL conectado a un servidor MySQL, aunque internamente este redirija las peticiones hacia un servidor Oracle. Ya tenemos casi configurado nuestro servidor DBIx::MyServer, sólo nos falta configurar alguna tabla federada en nuestro servidor MySQL, para que use los datos de Oracle. Para ello, crearemos una pequeña base de datos en MySQL con una tabla federada:
  1. Conectarnos a nuestra base de datos MySQL local, (la de nuestro servidor MySQL, no la del proxy este en PERL) y crear una nueva base de datos llamada oratest:
    # mysql -u root

    sql> create database oratest;
  2. Crear la tabla federeda en esta nueva base de datos...
    mysql> use oratest;

    mysql> drop table dept_scott;

    mysql> create table dept_scott(
    deptno integer(2) not null,
    dname varchar(14),
    loc varchar(14)
    ) engine = federated
    connection = 'mysql://myuser:myuser@127.0.0.1:1234/scott/dept'
    ;
    lo importante es definir los mismos campos que tenemos en Oracle y de la URL, el último token, que nos indica la tabla o vista hacia la que apuntamos, (en nuestro ejemplo DEPT).

Si ahora ejecutamos desde nuestro cliente MySQL, la consulta...
mysql> select * from dept_scott;

Comprobaremos cómo no nos devuelve ningún valor, pero sí que nos funcionarán los inserts en la tabla:
insert into dept_scott values(49,'PRUEBAS','MADRID');

Para saber porqué no nos funcionan los SELECT sobre la tabla federada, tenemos que examinar la salida del demonio PERL y ver qué consultas se le envían a Oracle. El primer problema es que la clase DBIx::MyServer se empeña en entrecomillar los nombres de los campos y tablas con el símbolo ` , y ello provoca fallo en Oracle. Editar el fichero de la clase /usr/local/share/perl/5.12.4/DBIx/MyServer/DBI.pm, y aplicar el siguiente parche/chapuza:
24a25,26
> $query_text=~s/`//eg;
> print "----SE ENVIA A ORACLE --->[$query_text]\n";

Luego, podemos comprobar cómo MySQL se empeña en añadir WHERE 1=0 a las consultas que envía a Oracle. Esto no devolverá ningún resultado en Oracle. Para quitarlo, editaremos el fichero myserver.conf con el que lanzamos el demonio, y añadiremos el siguiente bloque entre las expresiones de traducción:
{
match => qr{^(SELECT.+)WHERE 1=0}io,
rewrite => sub { "$_[1]" },
}

Esta expresión en PERL permite eliminar el WHERE 1=0 de los SELECT. Si seguimos depurando, comprobaremos que MySQL se empeña en consultar las estadisticas de la tabla remota, cuando se hace un SELECT, ejecutando el comando SHOW TABLE STATUS LIKE DEPT. Este comando no existe en Oracle y provocará un fallo. Para evitarlo, añadiremos una nueva expresión a nuestro fichero myserver.conf :
{
match => qr{^show table status like\s+'([^']+)'}io,
rewrite => sub { "select table_name as Name, 'BDB' as Type, 'Fixed' as Row_Format, num_rows as \"ROWS\", AVG_ROW_LEN as Avg_row_length, BLOCKS as Data_length , MAX_EXTENTS as Max_data_length, INITIAL_EXTENT as Index_length, INITIAL_EXTENT as Data_free, 100 as Auto_increment, (sysdate-10) as Create_time, sysdate as Update_time, (sysdate+10) as Check_time, '' as Create_options, '' as \"Comment\" from user_tables where table_name='". uc($_[1]) ."'"
}

Lo que hacemos es capturar los SHOW TABLE STATUS que envía MySQL y traducirlos por lo análogo en Oracle (consulta a la vista USER_TABLES). Si reiniciamos nuestro demonio PERL (matando el proceso con kill -9) y volvemos a iniciarlo para que tome los cambios que hemos realizado en myserver.conf, podremos hacer la prueba definitiva:
# mysql

mysql> use oratest;

mysql>

mysql> select * from dept_scott;
+--------+------------+----------+
| deptno | dname | loc |
+--------+------------+----------+
| 49 | PRUEBAS | MADRID |
| 10 | ACCOUNTING | NEW YORK |
| 20 | RESEARCH | DALLAS |
| 30 | SALES | CHICAGO |
| 40 | OPERATIONS | BOSTON |
+--------+------------+----------+
5 rows in set (0.10 sec)

La gran ventaja de usar DBLinks de este modo o como relatamos en el anterior post, no es otra que la de integrar datos de nuestra organización almacenados en servidores independientes, pero de forma cómoda en SQL (SELECT, INNER JOINs, etc) sin apoyarnos en otros mecanismos externos como WebServices, CSVs, SQL-Loaders, etc, etc, que siempre pueden fallar y cuando lo hacen, nos cuesta mucho tiempo averiguar la causa del problema. Suerte!

No hay comentarios: