Analizador lexico para expresiones SQL
13 diciembre 2009 132 comentarios
En esta ocasion dejo un ejemplo de un analizador lexico de expresiones SQL, es una clase. En el constructor de esta clase recibe como entrada una cadena SQL y devuelve cada vez que se le llama a su metodo obtener_token() el siguiente token de la cadena SQL. podria usarse como punto de entrada para el analizador sintactico de cualquiera de las 4 operaciones SQL; INSERT, DELETE, UPDATE o SELECT. El codigo de la clase es el siguiente:
// Instituto Tecnologico de Zacatepec // En el constructor de esta clase recibe como entrada una cadena SQL y devuelve cada vez que se // se llama a su metodo obtenerToken() devuelve el siguiente token de la cadena SQL. // Author: Gonzalo Silverio gonzasilve@hotmail.com public class Tokens { //### Atributos de la clase #### private StringBuffer tok; //Para guardar el lexema del token private char car; //Almacena un caracter de la expresionSQL public String token; //Para que el token este disponible como String y no como StringBuffer public int intTipoToken; //Guarda el tipo de token en forma numerica public String strTipoToken = new String(); //Guarda el Tipo de token en forma de cadena public String strInstruccionSQL; //Guarda la cadena SQL que se esta analizando //Tipos de token en formato numerico public final int FIN_DE_ARCHIVO = -1; public final int NUMERO_ENTERO = 2; public final int DELIMITADOR = 3; public final int SALTO_DE_LINEA = 4; public final int NINGUNO = 5; public final int IDENTIFICADOR = 6; public final int PALABRA_CLAVE = 7; public final int CADENA = 8; public final int PARENTESIS = 9; public final int NUMERO_DECIMAL = 10; public final int ASTERISCO = 11; public final int ERROR = 12; public final int OPERADOR = 13; //indice para recorrer caracter por caracter la expresionSQL private int indexExpSQL; //Arreglo de palabras clave private static String palabras_clave[] = {"update","insert","delete","select","set","from","where","and","or","not","into","values","group","having","by","between"}; // TIPOS DE TOKEN // Tipos de token que se reconocen... // // --Tipo de token-- --Incluye-- // Delimitador Signos de puntuacion punto (.) y la coma (,) // Palabras clave Palabras clave // Cadena Cadenas entre comillas simples // Identificador Nombre de variable (nombres de tablas, campos, etc) o nombre de funcion (de agregacion) // Numero_entero Constante numerica entera // Numero_decimal Constante numerica decimal // Operador Operadores aritmeticos y relacionales Tokens(String cadSQLAParsear) { strInstruccionSQL = cadSQLAParsear+"; "; //Agregamos punto y coma ; //Inicializar con por default los atributos car = ' '; indexExpSQL = 0; //Iniciar analisis en el primer caracter de la cadena tok = new StringBuffer(); token = ""; leerCaracter(); } //Constructor vacio Tokens() { } //Metodo que lee el siguiente token de la cadena SQL public void obtenerToken() { tok = new StringBuffer(); // Reinicializar variable para leer otro token token = new String(); // Reinicializar variable para leer otro token intTipoToken = NINGUNO; //Por defecto no es ningun tipo de token strTipoToken = "ninguno"; token = ""; // SALTARSE ESPACIOS EN BLANCO Y TABULADORES for(;; leerCaracter()) { if( (char)car == ' ' || (char)car == '\t' ) continue; else break; } //Comprobar si es un operador relacional switch(car) { case '<': if(leerCaracter('>')) { tok.append('<'); tok.append('>'); } else if( leerCaracter('=') ) { tok.append('<'); tok.append('='); } else tok.append('<'); break; case '>': if(leerCaracter('=')) { tok.append('>'); tok.append('='); } else tok.append('>'); break; case '!': //Operador No if(leerCaracter('=')) { tok.append('!'); tok.append('='); } else tok.append('!'); break; } //Fin comprobbar si es operador relacional //Si la variable tiene algo se leyo un operador relacional if(tok.length()>0) { intTipoToken = OPERADOR; strTipoToken = "operador"; token = tok.toString().toLowerCase(); leerCaracter(); return; } //Comprobar si es un operador aritmetico String delims = new String("+-="); int d = delims.indexOf(car); if( d >= 0 ) { tok.append(car); intTipoToken = OPERADOR; //Es un OPERADOR (aritmetico) strTipoToken = "operador"; token = tok.toString().toLowerCase(); leerCaracter(); return; } //comprobar si es un delimitador delims = new String(",.;"); d = delims.indexOf(car); if( d >= 0 ) { tok.append(car); intTipoToken = DELIMITADOR; //Es un delimitador punto o coma strTipoToken = "delimitador"; token = tok.toString().toLowerCase(); leerCaracter(); return; } //Comprobar si son parentesis String parentesis = new String("()"); int p = parentesis.indexOf(car); if( p >= 0 ) { tok.append(car); intTipoToken = PARENTESIS; //Es un parentesis strTipoToken= "parentesis"; token = tok.toString().toLowerCase(); leerCaracter(); return; } // Comprobar si es una cadena // La cadena, se guarda sin comillas if( (char)car == '\'' ) { leerCaracter(); // Saltarse la comilla de apertura while((char)car != '\'') { tok.append(car); leerCaracter(); } leerCaracter(); // Saltarse la comilla de cierre intTipoToken = CADENA; strTipoToken= "char"; token = tok.toString().toLowerCase(); return; } //Comprobar Si es una identificador de usuario o una palabra clave de SQL if(Character.isLetter((char)car)) { do { tok.append(car); leerCaracter(); } while(Character.isLetterOrDigit( (char)car) || (char)car == '-' || (char)car == '_' || (char)car == '.' ); if( contarPuntos(tok)>1 ) { mensaje_error("El identificador \'" +tok.toString()+ "\' tiene demasiados puntos"); intTipoToken = ERROR; strTipoToken = "error"; token = tok.toString().toLowerCase(); return; } //comprobar si es una palabra clave, sino, entonces es un identificador if( buscar_keyword( tok.toString().toLowerCase() ) ) { intTipoToken = PALABRA_CLAVE; strTipoToken = "keyword"; token = tok.toString().toLowerCase(); } else { intTipoToken = IDENTIFICADOR; strTipoToken = "identificador"; token = tok.toString().toLowerCase(); } return; } //Si es un numero (entero o decimal) if(Character.isDigit((char)car)) { do { tok.append(car); leerCaracter(); } while(Character.isDigit( (char)car) || (char)car == '.' ); //Comprobar si es un entero if(contarPuntos(tok) == 0 ) { if(esEntero(tok.toString())) { intTipoToken = NUMERO_ENTERO; strTipoToken = "int"; token = tok.toString().toLowerCase(); } } //Comprobar si es un decimal else if(contarPuntos(tok) == 1) { if(esDecimal(tok.toString())) { intTipoToken = NUMERO_DECIMAL; strTipoToken = "decimal"; token = tok.toString().toLowerCase(); } else { //Informar al usuario del error de sintaxis en el decimal mensaje_error("El token \'" +tok.toString()+ "\' parece un decimal pero tiene un error de sintaxis"); intTipoToken = ERROR; strTipoToken = "error"; token = tok.toString().toLowerCase(); } } else if(contarPuntos(tok) > 1 ) //Si tiene mas de un punto { //Informar a usuario que un decimal no puede tener mas de 1 caracter punto mensaje_error("El token \'" +tok.toString()+ "\' tiene mas de un punto"); intTipoToken = ERROR; strTipoToken = "error"; token = tok.toString().toLowerCase(); } return; } //Fin de validar si es un numero //Comprobar si es asterisco if((char)car == '*' ) { tok.append(car); intTipoToken = ASTERISCO; strTipoToken = "asterisco"; token = tok.toString().toLowerCase(); leerCaracter(); return; } } //Fin del metodo obtenerToken //Devuelve true si en una cadena que llega todos son numeros, false en caso contrario public boolean esEntero(String cad) { for(int i = 0; i<cad.length(); i++) if( !Character.isDigit(cad.charAt(i)) ) return false; return true; } //Devuelve true si la cadena que llega tiene la sintaxis de un decimal public boolean esDecimal(String cad) { boolean hayPunto=false; StringBuffer parteEntera = new StringBuffer(); StringBuffer parteDecimal = new StringBuffer(); int i=0, posicionDelPunto; for( i=0;i<cad.length(); i++ ) if ( cad.charAt(i) == '.') //Detectar si hay un punto decimal en la cadena hayPunto=true; if(hayPunto) //Si hay punto guardar la posicion donde se encuentra el carater punto posicionDelPunto=cad.indexOf('.'); //(si la cadena tiene varios puntos, detecta donde esta el primero). else return false; //Si no hay punto; no es decimal if( posicionDelPunto == 0 || posicionDelPunto == cad.length()-1 ) //Si el punto esta al principio o al final no es un decimal return false; for( i=0;i<posicionDelPunto; i++ ) parteEntera.append(cad.charAt(i)) ; //Guardar la parte entera en una variable for(i = 0; i<parteEntera.length(); i++) if( ! Character.isDigit(parteEntera.charAt(i)) ) //Si alguno de los caracteres de la parte entera return false; //no son digitos no es decimal for( i=posicionDelPunto+1;i<cad.length(); i++ ) parteDecimal.append(cad.charAt(i)); //Guardar la parte decimal en una variable for(i = 0; i<parteDecimal.length(); i++) if( ! Character.isDigit(parteDecimal.charAt(i)) ) //Si alguno de los caracteres de la parte decimal no es un digito no es decimal return false; //Incluye el caso en el que la cadena tenga dos o mas puntos return true; //Si paso todas las pruebas anterior, la cadena es un Numero decimal } //Lee un caracter de la cadenaSQL void leerCaracter() throws StringIndexOutOfBoundsException { car = strInstruccionSQL.charAt(indexExpSQL); /* leer siguiente caracter */ //Verificar si se a sobrepasado el indice maximo de caracteres en la expresion SQL if( indexExpSQL < strInstruccionSQL.length() ) indexExpSQL++; else indexExpSQL = strInstruccionSQL.length()-1; } // lee un caracter x adelantado de la cadena SQL, el caracter que llega lo compara con //el caracter leido y si son iguales devuelve true, si son diferentes devuelve false boolean leerCaracter(char c) { leerCaracter(); if( (char)car != (char)c ) { indexExpSQL--; return false; } car = ' '; return true; } //Cuenta el numero de caracteres punto que tiene una cadena especificada public int contarPuntos(StringBuffer cad) { int contador = 0; for(int i=0;i<cad.length();i++) { if(cad.charAt(i)=='.') contador++; } return contador; } //Devuelve true si la cadena que llega es una palabra clave, false en caso contrario //busca en la tabla de palabras clave la cadena que llega static boolean buscar_keyword(String identificador) { for(int i = 0;i< palabras_clave.length;i++) { if( palabras_clave[i].equals(identificador) ) return true; } return false; } // Muestra un msg de error en el area de msgs del parser public void mensaje_error(String strMsg) { System.out.print("\n" + strMsg ); } } //Fin de la clase Tokens.java
Como veo que a mas de uno le a resultado interesante mi clase a continuacion doy una breve explicacion de la clase Tokens. La Clase Tokens como se puede observar, tiene varios atributos pero solo 4 son publicos, los cuales son token,intTipoToken,strTipoToken,strInstruccionSQL de estos solo los primeros 3 son de utilidad para usarse para hacer un analizador sintacto de alguna sentencia SQL. Ademas de estos 4 atributos hay un unico metodo llamado obtenerToken() que no devuelve ningun valor (void). La clase UML se ve mas o menos asi si omitimos los demas atributos…
Cada vez que se llama al metodo las 3 variables que mencione arriba quedan con los valores que nos interesan, por ejemplo, suponiendo que la operacion SQL que se le pasa a esta clase es:
INSERT INTO alumnos VALUES('07680654','Carlos','Zarate', 23,78.0)
Para pasarle esta cadena SQL se debe crear una instancia de la clase, algo asi:
Tokens pruebaTokens = new Tokens("INSERT INTO alumnos VALUES('07680654','Carlos','Zarate', 23,78.0)");
y al llamar al metodo obtenerToken() del objeto pruebaTokens, las tres variables quedarian con los valores:
Variable | Valor |
token | «insert» |
intTipoToken | PALABRA_CLAVE |
strTipoToken | «keyword» |
si se llama al metodo por segunda vez, ahora los valores de estas variables son:
Variable | Valor |
token | «into» |
intTipoToken | PALABRA_CLAVE |
strTipoToken | «keyword» |
Si se llama al metodo por tercera vez, ahora los valores de estas variables son:
Variable | Valor |
token | «alumnos» |
intTipoToken | IDENTIFICADOR |
strTipoToken | «identificador» |
Las constantes sirven para tener una representacion en forma numerica de cada uno de los tokens, los puse por si me servian de algo mas adelante, y vaya que si me sirvieron. Ademas, como se observa hay un arreglo que contiene las palabras clave de SQL, si me falto alguna o se desean agregar mas (por ej. para ampliar las de SQL) pues ahi es donde se agregarian. Aca adelante un ejemplo del uso de esta clase…
// Instituto Tecnologico de Zacatepec, Morelos, Mexico // Prueba de la clase Tokens // Author: Gonzalo Silverio gonzasilve@gmail.com public class PruebaTokens { //Tipo de token que puede haber en la cadena public final int FIN_DE_ARCHIVO = -1; public final int NUMERO_ENTERO = 2; public final int DELIMITADOR = 3; public final int SALTO_DE_LINEA = 4; public final int NINGUNO = 5; public final int IDENTIFICADOR = 6; public final int PALABRA_CLAVE = 7; public final int CADENA = 8; public final int PARENTESIS = 9; public final int NUMERO_DECIMAL = 10; public final int ASTERISCO = 11; public final int ERROR = 12; //El constructor espera recibir una cadena sql de la cual se quieren extraer sus tokens private void analizarCadenaSQL(String cadSQL) { Tokens tokens = new Tokens(cadSQL); // System.out.println("\n\n"); //Este bucle imprime los tokens que se van leyendo y el tipo de token que se leyo de la cadena SQL tokens.obtenerToken(); //Obtener el primer token de la cadena SQL while( ! tokens.token.equals(";") ) { switch(tokens.intTipoToken) { case NUMERO_ENTERO: System.out.println(tokens.token+" \t\t\tNUMERO_ENTERO"); break; case NUMERO_DECIMAL: System.out.println(tokens.token+" \t\t\tNUMERO_DECIMAL"); break; case PALABRA_CLAVE: System.out.println(tokens.token+" \t\t\tPALABRA_CLAVE"); break; case IDENTIFICADOR: System.out.println(tokens.token+" \t\t\tIDENTIFICADOR"); break; case CADENA: System.out.println(tokens.token+" \t\t\tCADENA"); break; case PARENTESIS: System.out.println(tokens.token+" \t\t\tPARENTESIS"); break; case DELIMITADOR: System.out.println(tokens.token+" \t\t\tDELIMITADOR"); break; case ASTERISCO: System.out.println(tokens.token+" \t\t\tASTERISCO"); break; case ERROR: System.out.println(tokens.token + " \t\t\t<-- ERROR !"); break; } tokens.obtenerToken(); } } public static void main(String args[]) { PruebaTokens prueba1 = new PruebaTokens(); prueba1.analizarCadenaSQL("insert into s values('s1','ford','mty', 23,58.42)"); } }
y el resultado de la ejecucion es la siguiente:
Si les aparece algun error al compilar o desean el fuente, les puedo pasar el .java, basta con dejar un comentario con su e-mail
hasta la proxima, espero comentarios….
}
buenas bro me mandarias el codigo
Hola bro, me ayudaria mucho si me pasaras el codigo
johanqg@icloud.com
hola bro quetal me puedes enviar tu codigo fuente fakaterra@gmail.com
Buen Aporte
podrias enviarme el codigo fuente
mi correo es dnalvs14@gmail.com
de antemano gracias…
Disculpa podrias mandarme el codigo?
Gracias Silverio! Es muy útil tu código. ¿Podrías mandarme el fuente? Además te consulto: ¿Conocés ese analizador sintáctico que consta de un programa que capta el dibujo de la estructura así como figura en los manuales : SELECT (ALL ! DISTINCT), y en base a eso analiza las sentencias? Lo vi una vez y ahora no lo encuentro.
Desde ya, gracias.
MUY BUEN APORTE PODRIAS MANDARME EL CODIGO
También quiero, por favor, el código completo.
aro06548@gmail.com
Amigo buen aporte. Podias publicar sobre el analizador sintactico?
Genial aporte!! ¿Podrías enviarme el código a mi email perezfjv@gmail.com? Muchas gracias Saludos
Podrias enviar el codigo fuente? 😀
jorge.jmht1@gmail.com
Excelente aporte, quisiera el codigo fuente, por favor!.