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!

You can leave a response, or trackback from your own site.

11 Responses to “JSqlParser: un parser (analizador sintáctico) para SQL en Java (continuación)”

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

  2. QiQe dice:

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

  3. alopecia dice:

    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 dice:

    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.

  5. oscar dice:

    Estupendo tu artículo. Justo iba a empezar a usar ZQL para un trabajo de inventariado de tablas a las que accedemos, pero habiendo leído tu artículo me voy a decantar por JSqlParser que parece más completo (como bien dices los ejemplos “heavy” de las sentencias para probar en el sitio de ZQL son de risa)

  6. pedels dice:

    Gracias.
    Es un poco duro al principio pero vale la pena. Ya nos contarás.

    Saludos

  7. Aique dice:

    Hola.

    Estoy utilizando este parser de SQL y tengo una duda que puedes saber. Intento tanto analizar como crear una consulta Select, sin embargo parece que en el from sólo se te permite una tabla, y en el where una única expresión. ¿Es capaz este parser de realizar consultas más complejas, por ejemplo un join de dos tablas al estilo select id1,id2 from tabla1,tabla2 where id1 = id2?.

    De la misma manera si intento analizar una consulta de ese tipo no se muy bien como capturar las dos tablas del from, ya que PlainSelect sólo tiene setFrom(Table).

    Saludos y gracias!.

  8. pedels dice:

    Vaya, me pillas un poco oxidado con el tema. Ahora no tengo la información aquí pero creo que te puedo ayudar:

    From
    Vaya parece que si que puede contener una sola tabla o un subselect pero no es una limitación. En realidad el from+where no es la forma ‘ortodoxa’ de unir tablas, para hacer un join usa un JOIN.
    Para hacer:
    ‘SELECT * FROM tabla1, tabla2 WHERE tabla1.id=tabla2.id’
    usa:
    ‘SELECT * FROM tabla1 JOIN tabla2 ON tabla1.id=tabla2.id’
    Si quieres más tablas añades más JOIN, el resultado es el mismo y se separa lo que es una condición que deben cumplir los campos resultantes del select (que es el WHERE) de la unión entre distintas tablas (que son los JOIN).

    Where
    Ciertamente puede contener una única expresión, ahora necesitas comprender el poder ilimitado de una única expresión. :D
    ‘tabla.campo =7′ es una expresión pero ‘tabla.campo=7 AND id1=id3 OR tabla2.campo3<5 AND (tabla.campo =0 OR tabla.campo2<1)' también es una única expresión compuesta de muchas expresiones. El visitador se encargara de ir recorriéndola y sacando todo de ahí.
    Para construir puede ser algo complejo, ya que tienes que ir construyendo desde abajo hacia arriba, te pueden ser útiles el árbol del artículo que desglosa como están compuestas las expresiones. Hay que jugar con los objetos y los interfaces ya que casi todo es una expresión.

    Espero haberte ayudado, el JSqlParser es tremendamente potente y rápido y se aprende rápido a usar.
    Un saludo

  9. Aique dice:

    Muchas gracias por contestar tan rápido. Lo cierto es que estaba tirando la toalla y pasándome al parser de Eclipse, pero voy a intentarlo otro poco más antes de renunciar.

    Una última consulta, la verdad que no soy experto en SQL y me puedes ahorrar aún más tiempo, como podría realizar esta consulta con jsqlparser? (utilizando joins):

    select atributo
    from tabla_1,tabla_2,tabla_3
    where tabla_1.id = tabla_2.id_1 and
    tabla_2.id_1 = tabla_2.id_2 and
    tabla_2.id_2 = tabla_3.id and
    tabla_3.nombre LIKE ‘un nombre’

    Saludos y muchas gracias por la ayuda, me estas sacando de un buen marrón :).

  10. Francisco V dice:

    Podrias mandarme la version que funciona? la que tu has programado? seria de mucha ayuda. Gracias. a: vacas.fr [at] gmail

  11. pedels dice:

    La versión de la web funciona bien. Yo lo único que hice fue crear un interfaz que heredaba de todos los interfaces Visitor, con esto tenemos un interfaz visitador para todo los tipos de elemento. Luego cree una clase que implementaba este interfaz, en ese momento tienes muchos métodos vacíos que se usan para el patrón este del visitador. Yo los use para una tarea muy concreta sin mucho interés fuera de ese proyecto.
    Si intentas implementar el ejemplo que hay por ahí verás que no es difícil. La mayor dificultad es entender el patrón de diseño del visitador y la estructura de objetos de la consulta, en cuanto se ven el resto es directo.
    No se si te he ayudado :P pero lo he intentado, jeje.

Leave a Reply

Subscribe to RSS Feed Sígueme en Twitter!