JSqlParser: un parser (analizador sintáctico) para SQL en Java (continuación)

En el último artículo analizamos las posibilidades que se nos presentaban al buscar un parser de SQL para Java e hicimos un recorrido de posibles soluciones de mayor a menor dificultad de adopción. Para una introducción y puesta en situación para este artículo, podría ser recomendable su lectura.

Ahora concretamos en la solución encontrada como más adecuada: JSqlParser.
Este parser es un proyecto de Software Libre alojado en SourceForge bajo licencia LGPL.

Al descargarlo de SourceForge, nos encontraremos con un archivo .jar. Este archivo contiene la libreria .jar que necesitamos y, además, tanto la documentación como el código fuente. Una forma sencilla de extraer el contenido es renombrar el archivo, cambiando su extensión por .zip. Entonces, lo extraemos con cualquier utilidad para descompresión de este tipo de ficheros.

Encontramos una estructura de carpetas donde nos interesan:

    (si estas en Windows, tus barras serán así: \)

  • Librería: “jsqlparser/lib/jsqlparser.jar”
  • Documentación: “jsqlparser/docs” también en los Javadocs de la página web.
  • Código fuente: “jsqlparser/src/net/sf/jsqlparser”
  • Archivos SQL de prueba: “jsqlparser/testfiles”

Es interesante saber también que existe un foro en SourceForge donde es posible encontrar (u ofrecer) ayuda. En la página web podemos encontrar un ejemplo que nos será de extrema utilidad para empezar a trabajar con JSqlParser.

Dependiendo del entorno de desarrollo que use deberá agregar la libreria jsqlparser.jar para que esté disponible. A partir de ahí ya podemos usar la librería en nuestro código pero antes necesitamos saber unas cuantas cosas. Explicaremos a continuación una serie de conceptos que pueden facilitarnos la comprensión e la forma de trabajar de esta librería.


El patrón del visitador
Este parser hace uso extensivo del Patrón del Visitador (o también, Visitor Pattern [en inglés pero mucho mejor que la versión española y con ejemplos]).
Intentemos explicar en forma sencilla que es lo que ocurre en este patrón de diseño software. Tenemos dos elementos:

  • Objeto visitador (Visitor): implementa diversas versiones del método visit(objeto_a_visitar) haciendo un uso extensivo del polimorfismo. Visitor es en realidad un interfaz que debe ser implementado por un objeto pero ahora veremos eso.
  • Objetos visitables: tienen un método accept(objeto_visitador).

El funcionamiento es el siguiente. Si tenemos un objeto denominado Objeto1 que tiene un método accept(). Cuando invocamos Objeto1.accept(Objeto_visitador), estamos llamando al método accept() y pasándole un objeto que es el Objeto_visitador. Dentro del método accept() lo que ocurre es que se usa el Objeto_visitador, y se invoca su método visit(). De este modo hacemos: Objeto_visitador.visit(Objeto1).
En Objeto_visitador tenemos muchas versiones de visit(), una para cada tipo de objeto que podemos tratar, de este modo, con un unico Objeto_visitador podemos actuar sobre múltiples objetos distintos.
Todos los objetos implementan el método accept(), que provoca que el objeto acabe siendo pasado como parámetro al objeto_visitador, que usará el método visit() adecuado para cada tipo de objeto que reciba.

En resumen, cuando invocamos accept(Objeto_visitador) en un objeto, lo que estamos provocando es que Objeto_visitador reciba el objeto como parámetro en un método que se encargará de tratarlo.

El artículo de la wikipedia puede ser bastante más clarificador que estos párrafos.

Creo que la forma en que queda más claro es con un ejemplo y el ejemplo en Java de la Wikipedia es bastante bueno. Aunque yo hubiera usado personas en lugar de piezas de coche por que queda mucho más clara la analogía de la visita:

Imaginemos que el visitador es un médico y el visitable un paciente (hombre, mujer o niño) y el médico puede tratar a los tres.
El paciente enferma y usa su metodo accept(médico) porque necesita un médico, igualmente puede tener accept(fontanero) si lo que necesita es un fontanero. Cuando acepta al médico, lo llama y le pide que le visite, esto es, dentro de accept usa el método visit del médico pasandose a si mismo como argumento.
El médico recibe la petición de visita y dependiendo de que fuera quién ha llamado hará una visita para hombre, mujer o niño porque, aunque la llamada es la misma, se ejecuta un método distinto para cada uno.
El médico tratá al paciente con lo que tenga que hacerle y fin del proceso.

Es posible rizar esto un poco más: el médico puede curarse a si mismo, visitador y visitado pueden ser el mismo; puede haber, al igual que distintos pacientes, distintos médicos que apliquen tratamientos distintos, clases distintas que implementan el mismo interfaz (la forma de llamar al médico es siempre la misma).

Este patrón combinado con un uso jerárquico de llamadas nos da una capacidad y una flexibilidad sorprendente para tratar una estructura más o menos compleja como puede ser una consulta SQL.

En JSqlParser, existen múltiples interfaces Visitor. Que debemos implementar adecuadamente en una clase para tratar ese tipo de objetos. Usar los interfaces nos asegurará que implementamos todos los métodos necesarios para tratar esa parte de la estructura de la consulta.



Estructura de las sentencias SQL en JSqlParser

He extraido de la documentación la estructura de una consulta Select y la he expresado como un mapa mental de FreeMind (programa recomendado, tremendamente útil). Ponemos aquí la versión exportada html, aunque si alguién está interesado en el archivo original de FreeMind (recomendado) sólo tiene que pedirlo en los comentarios.
Pincha en las fotos para una versión ampliada:
Select Statement
Expression in Statement

No me voy a extender mucho aquí. La estructura corresponde a una descomposición desde arriba hacia abajo de la estructura de la consulta Select, el resto son similares. Para la descomposición se han ido tomando los campos de cada objeto sucesivamente, los que tienen una flecha roja indican que están descompuestos en otra parte del esquema (son enlaces en el archivo original). Para ver los métodos de cada objeto acudid a la documentación original.

Solo comentar que es cada cosa en las imagenes:

  • Texto gris claro con imagen bombilla o campana: Documentación. Anotaciones útiles.
  • Texto marrón con imagen clip: Interfaz. Cuando aparece un interfaz se descompone en todos los objetos que implementan ese interfaz.
  • Texto rojo con imagen carpeta azul: Objeto. Se descompone en sus campos que pueden s er tipos de datos primitivos, objetos e interfaces.
  • Texto azul: listas de objetos o interfaces. Muy usadas en toda la libreía.
  • Texto rojo oscuro: Tipo de dato primitivo. Como un entero o una cadena.

Una de las imagenes contiene la estructura general y la otra la expansión del interfaz Expression que por su tamaño e importancia (y legibilidad) merece su propia imagen.


Cómo usar todo esto junto sin volverse loco

Partimos del supuesto que tenemos una consulta SQL en una cadena y queremos extraer sus partes. El procedimiento que podemos seguir pasa por crear una clase visitador que reciba la consulta, la analice con el parser que trae JSqlParser y entonces empieza el proceso maravilloso:
Tomamos la consulta completa, la cual tiene metodos para extraer sus partes. Bien, las extraemos y en cada parte usamos el método accept(visitor) esto hará que nuestro visitador vuelva a recibir las partes, que irán a su método visit() gracias al poliformismo de estos.
Una vez recibida cada parte podemos descomponerla en sus partes usando sus propios métodos y llamar en cada una accept(visitor) de nuevo.
Este proceso puede continuar hasta los elementos más elementales de la consulta: cadenas de texto que representan nombres de columnas y tablas, enteros, etc….

¿Y qué tiene esto de divertido? Lo divertido es que podemos incluir lo que necesitemos hacer en el proceso y modificar o extraer los datos que necesitemos de la consulta sin esfuerzo.

En el ejemplo de la web realiza un visitador que va recorriendo la consulta de arriba hacia abajo y cuando encuentra un nombre de tabla lo guarda en un ArrayList donde, al terminar el proceso, estarán todas las tablas que aparecen en la consulta. ¿Y cómo sabe cuando ha llegado a un nombre de tabla¿ Es tan sencillo como que en visit(Table tableName) sólo entrarán tablas, extreamos su nombre y lo guardamos.

Mi recomendación es que intentéis entender la estructura de la consulta, tomeis el ejemplo de la web, terminéis de implementar los interfaces que faltan, en caso de que los veais necesarios, del mismo modo que están los del ejemplo y uséis esa clase como padre para las que os serán de verdadera utilidad. De este modo ocultamos la clase “fea” y obtendremos una clase “bonita” donde sólo está la tarea que nos interesa.

Yo necesitaba extraer todos los nombres de columnas y tablas. Lo que hice fue terminar de implementar la clase del ejemplo que recorría la consulta de arriba hasta abajo. Esta es la clase padre.

Hecho esto, cree una clase que extendía la anteriormente creada. en esta nueva clase incluí sólo dos métodos que sobreescribían a los de la clase padre: visit(Column columnName) y visit(Table tableName) que recogían los nombres de las tablas y columnas que recibían y los iban guardando en unas listas destianadas para eso.

Y este es el final. Enhorabuena si has llegado hasta aquí leyendo y enhorabuena de nuevo si has entendido algo. El funcionamiento es confuso en un inicio si no estás familiarizado con este tipo de estructuras (para mi lo fue) pero, tras leer un poco y algo de esfuerzo, se pueden conseguir unos resultados muy buenos tanto en la descomposición como en la composición de consultas.

¡Suerte!

4 comentarios:

  1.  

    [...] un artículo posterior profundizaremos en JSqlParser. En este articulo se comentan las posibilidades que existen y la [...]

     
  2. QiQe, 20. Enero 2008, 21:03

    No me lo voy a leer, paso, vaya ladrillazo xD

     
  3. alopecia, 28. Enero 2008, 13:43

    Pués a mi me ha gustado.
    Hubiera estado bien que el ejemplo del link http://jsqlparser.sourceforge.net/example.php compilara, ¿donde se dejaron la clase JoinVisitor?
    Animaría a Pedels a que ‘colgara’ su implementación.
    Un saludo

     
  4. pedels, 31. Enero 2008, 19:57

    Gracias por el comentario.

    Te mando al correo lo que tengo, no es gran cosa porque son sólo pruebas. Si hubiera una gran demanda popular ya me plantearía publicarlo en condiciones.

    Un saludo.

     

Write a comment: