Analizador lexico para expresiones SQL

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…
Clase Tokens

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:

gonzasilve

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….
}

Acerca de gonzasilve
Freelance Web Developer.

132 Responses to Analizador lexico para expresiones SQL

  1. brian says:

    buenas bro me mandarias el codigo

  2. Johan says:

    Hola bro, me ayudaria mucho si me pasaras el codigo
    johanqg@icloud.com

  3. Alexander Lemus says:

    hola bro quetal me puedes enviar tu codigo fuente fakaterra@gmail.com

  4. DANIEL ALEXANDER VICENTE SANIC says:

    Buen Aporte
    podrias enviarme el codigo fuente
    mi correo es dnalvs14@gmail.com
    de antemano gracias…

  5. Agustin says:

    Disculpa podrias mandarme el codigo?

  6. Mercedes says:

    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.

  7. Jesus Martinez says:

    MUY BUEN APORTE PODRIAS MANDARME EL CODIGO

  8. Aromen says:

    También quiero, por favor, el código completo.
    aro06548@gmail.com

  9. oswaldo says:

    Amigo buen aporte. Podias publicar sobre el analizador sintactico?

  10. José V. says:

    Genial aporte!! ¿Podrías enviarme el código a mi email perezfjv@gmail.com? Muchas gracias Saludos

  11. Martín says:

    Podrias enviar el codigo fuente? 😀

    jorge.jmht1@gmail.com

  12. Martín says:

    Excelente aporte, quisiera el codigo fuente, por favor!.

Deja un comentario