La etapa de traducción consiste en dos partes:
El traductor definido en gram.y y scan.l se construye utilizando las herramientas de Unix yacc y lex.
El proceso de transformación realiza modificaciones y aumentos a las estructuras de datos devueltas por el traductor.
El traductor debe comprobar la cadena de caracteres de la consulta (que le llega como texto ASCII plano) para comprobar la validez de la sintaxis. Si la sintaxis es correcta, se construye un árbol de traducción y se devuelve un mensaje de error en otro caso. Para la implementación se han utilizado las bien conocidas herramientas de Unix lex y yacc.
El lector (lexer) se define en el fichero scan.l y es el responsable de reconocer los identificadores, las palabras clave de SQL, etc. Para cada palabra clave o identificador que encuentra, se genera y traslada al traductor traductor una señal.
El traductor está definido en el fichero gram.y y consiste en un conjunto de reglas de gramática y acciones que serán ejecutadas cada vez que se dispara una regla. El código de las acciones (que actualmente es código C) se utiliza para construir el árbol de traducción.
El fichero scan.l se transforma en el fichero fuente C scan.c utilizando el programa lex u gram.y se transforma en gram.c utilizando yacc. Una vez se han realizado estas transformaciones, cualquier compilador C puede utilizarse para crear el traductor. No se deben nunca realizar cambio en los ficheros C generados, pues serán sobreescritos la próxima vez que sean llamados lex o yacc.
Las transformaciones y compilaciones mencionadas normalmente se hacen automáticamente utilizando los makefile vendidos con la distribución de los fuentes de Postgres. |
Más adelante en este mismo documento se dará una descripción detallada de yacc o de las reglas de gramática dadas en gram.y. Hay muchos libros y documentos relacionados con lex y yacc. Debería usted familiarizarse con yacc antes de empezar a estudiar la gramática mostrada en gram.y, pues de otro modo no entenderá usted lo que está haciendo.
Para un mejor conocimiento de las estructuras de datos utilizadas en Postgres para procesar una consulta utilizaremos un ejemplo para ilustrar los cambios hechos a estas estructuras de datos en cada etapa.
Ejemplo 1. Una SELECT sencilla
Este ejemplo contiene la siguiente consulta sencilla que será usada en varias descripciones y figuras a lo largo de las siguientes secciones. La consulta asume que las tablas dadas en The Supplier Database ya han sido definidas.
select s.sname, se.pno from supplier s, sells se where s.sno > 2 and s.sno = se.sno; |
La figura \ref{parsetree} muestra el árbol de traducción construido por las reglas y acciones de gramática dadas en gram.y para la consulta dada en Una SELECT sencillaEste ejemplo contiene la siguiente consulta sencilla que será usada en varias descripciones y figuras a lo largo de las siguientes secciones. La consulta asume que las tablas dadas en The Supplier Database ya han sido definidas. select s.sname, se.pno from supplier s, sells se where s.sno > 2 and s.sno = se.sno; (sin el árbol de operador para la cláusula WHERE que se muestra en la figura \ref{where_clause} porque no había espacio suficiente para mostrar ambas estructuras de datos en una sola figura).
El nodo superior del árbol es un nodo SelectStmt. Para cada entrada que aparece en la cláusula FROM de la consulta de SQL se crea un nodo RangeVar que mantiene el nombre del alias y un puntero a un nodo RelExpr que mantiene el nombre de la relación. Todos los nodos RangeVar están recogidas en una lista unida al campo fromClause del nodo SelectStmt.
Para cada entrada que aparece en la lista de la SELECT de la consulta de SQL se crea un nodo ResTarget que contiene un puntero a un nodo Attr. El nodo Attr contiene el nombre de la relación de la entrada y un puntero a un nodo Value que contiene el nombre del attribute. Todos los nodos ResTarget están reunidos en una lista que está conectada al campo targetList del nodo SelectStmt.
La figura \ref{where_clause} muestra el árbol de operador construido para la clausula WHERE de la consulta de SQL dada en el ejemplo Una SELECT sencillaEste ejemplo contiene la siguiente consulta sencilla que será usada en varias descripciones y figuras a lo largo de las siguientes secciones. La consulta asume que las tablas dadas en The Supplier Database ya han sido definidas. select s.sname, se.pno from supplier s, sells se where s.sno > 2 and s.sno = se.sno; que está unido al campo qual del nodo SelectStmt. El nodo superior del árbol de operador es un nodo A_Expr representando una operación AND. Este nodo tiene dos sucesores llamados lexpr y rexpr apuntando a dos subárboles. El subárbol unido a lexpr representa la cualificación s.sno > 2 y el unido a rexpr representa s.sno = se.sno. Para cada atributo, se ha creado un nodo Attr que contiene el nombre de la relación y un puntero a un nodo Value que contiene el nombre del atributo. Para el termino constante que aparece en la consulta, se ha creado un nodo Const que contiene el valor.
El proceso de transformación toma el árbol producido por el traductor como entrada y procede recursivamente a través suyo. Si se encuentra un nodo SelectStmt, se transforma en un nodo Query que será el nodo superior de la nueva estructura de datos. La figura \ref{transformed} muestra la estructura de datos transformada (la parte de la cláusula WHERE transformada se da en la figura \ref{transformed_where} porque no hay espacio suficiente para mostrarlo entero en una sola figura).
Ahora se realiza una comprobación sobre si los nombres de relaciones de la cláusula FROM son conocidas por el sistema. Para cada nombre de relación que está presente en los catálogos del sistema, se crea un nodo RTE que contiene el nombre de la relación, el nombre del alias y el identificador (id) de la relación. A partir de ahora, se utilizan los identificadores de relación para referirse a las relaciones dadas en la consulta. Todos los nodos RTE son recogidos en la lista de entradas de la tabla de rango que está conectada al campo rtable del nodo Query. Si se detecta en la consulta un nombre de relación desconocido para el sistema, se devuelve un error y se aborta el procesado de la consulta.
El siguiente paso es comprobar si los nombres de atributos utilizados están contenidos en las relaciones dadas en la consulta. Para cada atributo que se encuentra se crea un nodo TLE que contiene un puntero a un nodo Resdom (que contiene el nombre de la columna) y un puntero a un nodo VAR. Hay dos números importantes en el nodo VAR. El campo varno da la posición de la relación que contiene el atributo actual en la lista de entradas de la tabla de rango creada antes. El campo varattno da la posición del atributo dentro de la relación. Si el nombre de un atributo no se consigue encontrar, se devuelve un error y se aborta el procesado de la consulta.