Package com.scooterframework.orm.sqldataexpress.parser

Source Code of com.scooterframework.orm.sqldataexpress.parser.JdbcStatementParser

/*
*   This software is distributed under the terms of the FSF
*   Gnu Lesser General Public License (see lgpl.txt).
*
*   This program is distributed WITHOUT ANY WARRANTY. See the
*   GNU General Public License for more details.
*/
package com.scooterframework.orm.sqldataexpress.parser;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;

import com.scooterframework.orm.sqldataexpress.connection.UserDatabaseConnection;
import com.scooterframework.orm.sqldataexpress.exception.LookupFailureException;
import com.scooterframework.orm.sqldataexpress.object.JdbcStatement;
import com.scooterframework.orm.sqldataexpress.object.JdbcStatementParameter;
import com.scooterframework.orm.sqldataexpress.object.Parameter;
import com.scooterframework.orm.sqldataexpress.object.RowInfo;
import com.scooterframework.orm.sqldataexpress.object.TableInfo;
import com.scooterframework.orm.sqldataexpress.util.SqlExpressUtil;


/**
* JdbcStatementParser class.
*
* @author (Fei) John Chen
*/
public class JdbcStatementParser extends JdbcStatementHelper {
    public JdbcStatementParser(UserDatabaseConnection udc, JdbcStatement st) {
        if (st == null)
            throw new IllegalArgumentException("JdbcStatement input cannot be null.");
       
        this.udc = udc;
        this.st = st;
    }
   

    public void parse() {
      String jdbcStatementString = st.getOriginalJdbcStatementString();
     
        //no need to parse if there is no dynamic parameter
        if (jdbcStatementString.indexOf('?') != -1) {
          if (st.isInsertStatement() &&
              jdbcStatementString.toUpperCase().indexOf("SELECT") == -1) {
              // This is a pure insert statement.
              parseInsertStatement(jdbcStatementString);
          }
          else {
              parseJdbcStatementString(jdbcStatementString);
          }
         
          //get parameter property data if it has not been loaded
          st = furtherLookupJdbcStatement(udc, st);
        }
       
        st.setLoadedParameterProperties(true);
    }
   
  // populate more parameter properties for the JdbcStatement
  private JdbcStatement furtherLookupJdbcStatement(
      UserDatabaseConnection udc, JdbcStatement st) {
    Collection<Parameter> parameters = st.getParameters();
    Iterator<Parameter> it = parameters.iterator();
    while (it.hasNext()) {
      JdbcStatementParameter jdbcParam = (JdbcStatementParameter) it
          .next();
      if (jdbcParam.isUsedByCount())
        continue;
      if (jdbcParam.getSqlDataType() != Parameter.UNKNOWN_SQL_DATA_TYPE) {
        // do not furtherLookup if the sql data type is already known.
        continue;
      }

      String tableName = jdbcParam.getTableName();
      String columnName = jdbcParam.getColumnName();

      int sqlDataType = 0;
      String sqlDataTypeName = null;
      String javaClassName = null;

      if (tableName != null && columnName != null) {
        // find more properties of this column
        TableInfo ti = SqlExpressUtil.lookupTableInfo(udc, tableName);

        // add more properties for this column
        RowInfo header = ti.getHeader();
        int columnIndex = header.getColumnPositionIndex(columnName);

        sqlDataType = header.getColumnSqlDataType(columnIndex);
        sqlDataTypeName = header.getColmnDataTypeName(columnIndex);
        javaClassName = header.getColumnJavaClassName(columnIndex);

        jdbcParam.setSqlDataType(sqlDataType);
        jdbcParam.setSqlDataTypeName(sqlDataTypeName);
        jdbcParam.setJavaClassName(javaClassName);
      } else {
        log.error("Can not detecting parameter properties because "
            + "either table name or column name is null for the "
            + "parameter with index " + jdbcParam.getIndex());
      }
    }

    return st;
  }
   
   
    /**
     * counts parameters (? marks)
     */
    private void parseJdbcStatementString(String jdbcStatementString) {
        //
        // find columnName/tableName pair map
        //
        // step 1: convert the alias to regular table names
        String sql = jdbcStatementString;
        String modifiedSql = resetAlias(sql);
       
        //StringTokenizer sti = new StringTokenizer(modifiedSql, " ,><=(){}");
        StringTokenizer sti = new StringTokenizer(modifiedSql, " ,|><=(){}+-*/");
       
        int totalTokens = sti.countTokens();
        String[] tokens = new String[totalTokens];
       
        int i = 0;
        while(sti.hasMoreTokens()) {
            tokens[i] = sti.nextToken();
            i = i + 1;
        }
       
        // step 2: get the column name for each question mark
        int qmarkIndex = 1;
        for (int j = 0; j < totalTokens; j++) {
            String token = tokens[j];
           
            if (token.startsWith("?")) {
                JdbcStatementParameter param = new JdbcStatementParameter();
                param.setIndex(qmarkIndex);
                param.setName(getNameFromToken(qmarkIndex, token));
                st.addParameter(param);
               
                qmarkIndex = qmarkIndex + 1;
               
                //get inline sql data type
                int sqlDataType = getInlineSqlDataTypeFromToken(token);
                if (sqlDataType != JdbcStatementParameter.UNKNOWN_SQL_DATA_TYPE) {
                    param.setSqlDataType(sqlDataType);
                    continue;//no need to parse
                }
               
                String columnName = "";
                String tableName = "";
                int dotPosition = token.indexOf('.');
                if (dotPosition != -1) {
                    String[] results = getTableAndColumnFromDottedToken(token);
                    if (results != null) {
                        tableName = results[0];
                        columnName = results[1];
                        param.setColumnName(columnName);
                        param.setTableName(tableName);
                        continue;
                    }
                }
               
                String columnNameToken = "";
                if (j>=2 && "BETWEEN".equalsIgnoreCase(tokens[j-1])) {
                    columnNameToken = tokens[j-2];
                }
                else if (j>=4 && "BETWEEN".equalsIgnoreCase(tokens[j-3]) && "AND".equalsIgnoreCase(tokens[j-1])) {
                    columnNameToken = tokens[j-4];
                }
                else if (j>=2 && "COUNT".equalsIgnoreCase(tokens[j-1])) {
                    columnNameToken = "COUNT(*)";
                    param.setUsedByCount(true);
                }
                else if (j>=2 && "COUNT".equalsIgnoreCase(tokens[j-2])) {
                    columnNameToken = tokens[j-1];
                    param.setUsedByCount(true);
                }
                else if (j>=2 && "IN".equalsIgnoreCase(tokens[j-1])) {
                    columnNameToken = tokens[j-2];
                }
                else if (j>=2 && "LIKE".equalsIgnoreCase(tokens[j-1])) {
                    columnNameToken = tokens[j-2];
                }
                else if (!"?".equals(tokens[j-1])) {
                    columnNameToken = tokens[j-1];
                }
                else {
                    log.warn("Failed to detect column name for ? with index " + qmarkIndex +
                             ", suggest to use inline sql type declaration if " +
                             "the underlying database doesn't support ParameterMetaData.");
                    continue;
                }
               
                dotPosition = columnNameToken.indexOf('.');
                if (dotPosition == -1) {
                    columnName = columnNameToken;
                    tableName = getTableName(j, tokens, columnName);
                }
                else {
                    columnName = columnNameToken.substring(dotPosition + 1, columnNameToken.length());
                    tableName  = columnNameToken.substring(0, dotPosition);
                }
               
                //set the found column and table names
                if (tableName != null) {
                    param.setColumnName(columnName);
                    param.setTableName(tableName);
                }
            }
        }
    }
   
    //token must a string that starts with ?
    private String[] getTableAndColumnFromDottedToken(String token) {
        if (token == null || !token.startsWith("?") ||
            (token.indexOf('.') == -1)) return null;
       
        String name = "";
        if (token.indexOf(':') != -1) {
            name = token.substring(1, token.indexOf(':'));
        }
        else {
            name = token.substring(1);
        }
       
        int dotPosition = name.indexOf('.');
        if (dotPosition == -1) {
            throw new IllegalArgumentException("Token string must be of format 'table.column:datatype' or 'table.column'.");
        }
       
        String[] results = new String[2];
        results[0] = name.substring(0, dotPosition);
        results[1] = name.substring(dotPosition + 1);
       
        return results;
    }
   
    //An insert statement has format like this:
    //
    //      INSERT INTO "table_name" ("column1", "column2", ...)
    //      VALUES ("value1", "value2", ...)
    //
    //      INSERT INTO tablename (col1, col2, col3) VALUES (?, ?, ?)
    //
    // Please note that this method does not cover those insert statements
    // with select subquery:
    //
    //      INSERT INTO "table_name1" ("column1", "column2", ...)
    //      SELECT "column3", "column4", ...
    //      FROM "table_name2"
    //
    private void parseInsertStatement(String jdbcStatementString) {
        //
        // find columnName/tableName pair map
        //
        // step 1: convert the string to a word array
        String sql = jdbcStatementString;
       
        StringTokenizer sti = new StringTokenizer(sql, " ,()\"");
        int totalTokens = sti.countTokens();
        String[] tokens = new String[totalTokens];
       
        int i = 0;
        while(sti.hasMoreTokens()) {
            tokens[i] = sti.nextToken();
            i = i + 1;
        }
       
        if (tokens.length <= 3)
            throw new IllegalArgumentException("Cannot parse sql statement: [" + sql + "]");
       
        if (tokens[3].equalsIgnoreCase("VALUES"))
            throw new IllegalArgumentException("Parser for insert statement " +
                "without column names specified has yet to be developed.");
       
        // step 2: get the table name
        String tableName = tokens[2];
       
        // step 3: get the column name for each question mark
        int valuesIndex = 0;
        List<String> columns = new ArrayList<String>();
        List<String> values = new ArrayList<String>();
        for (int j = 3; j < totalTokens; j++) {
            if (tokens[j].equalsIgnoreCase("VALUES")) {
                valuesIndex = j;
                break;
            }
            columns.add(tokens[j]);
        }
       
    for (int k = valuesIndex + 1; k < totalTokens; k++) {
            values.add(tokens[k]);
        }
       
        // find matching column/value pair
        int length = columns.size();
        if (values.size() != length)
            throw new IllegalArgumentException("The number of columns does not " +
                    "match the number of values.");
       
        int qmarkIndex = 1;
        for (int l = 0; l < length; l++) {
            String columnName = columns.get(l);
            String value = values.get(l);
            if (value.startsWith("?")) {
                JdbcStatementParameter param = new JdbcStatementParameter();
                param.setIndex(qmarkIndex);
                param.setColumnName(columnName);
                param.setTableName(tableName);
                param.setName(getNameFromToken(qmarkIndex, value));
                st.addParameter(param);
               
                //get inline sql data type
                int sqlDataType = getInlineSqlDataTypeFromToken(value);
                if (sqlDataType != JdbcStatementParameter.UNKNOWN_SQL_DATA_TYPE) {
                    param.setSqlDataType(sqlDataType);
                    continue;//no need to parse
                }
               
                qmarkIndex = qmarkIndex + 1;
            }
        }
    }
   
    // find the table name for the column corresponding to the ? mark
    private String getTableName(int qmarkPosition, String[] tokens, String columnName) {
        String tableName = "";
       
        for (int i = qmarkPosition; i >= 0; i--) {
            String token = tokens[i];
           
            if ("INSERT".equalsIgnoreCase(token)) {
                tableName = tokens[i+2];
            }
            else if ("UPDATE".equalsIgnoreCase(token)) {
                tableName = tokens[i+1];
            }
            else if ("DELETE".equalsIgnoreCase(token)) {
                if ("FROM".equalsIgnoreCase(tokens[i+1])) {
                    tableName = tokens[i+2];
                }
                else {
                    tableName = tokens[i+1];
                }
            }
            else if ("SELECT".equalsIgnoreCase(token)) {
                tableName = getTableNameForSelectStatement(qmarkPosition, tokens, columnName);
            }
        }
       
        log.debug("Leave getTableName: found table name " + tableName + " for column " + columnName);
       
        return tableName;
    }
   
    private String getTableNameForSelectStatement(int qmarkPosition, String[] tokens, String columnName) {
        String tableName = null;
       
        int fromPosition = -1;
        int wherePosition = -1;
        for (int i = qmarkPosition; i >= 0; i--) {
            String token = tokens[i];
            if ("WHERE".equalsIgnoreCase(token)) {
                wherePosition = i;
            }
            else if ("FROM".equalsIgnoreCase(token)) {
                fromPosition = i;
                break;
            }
        }
       
        // get all words between from and where or between from and the next
        // database key words like "order by", etc.
        // This code is very painful.
        if (fromPosition != -1 && wherePosition != -1) {
            // both FROM and WHERE are present
            if (wherePosition - fromPosition == 2) {
                tableName = tokens[fromPosition + 1];
            }
            else {
                //need to check all tokens one by one
                //stop when the first table name match is found.
                int startIndex = fromPosition + 1;
                int endIndex = wherePosition -1;
                for (int i = startIndex; i <= endIndex; i++) {
                    String potentialTableName = tokens[i];
                    if (isColumnInTable(columnName, potentialTableName)) {
                        tableName = potentialTableName;
                        break;
                    }
                }
               
            }
        }
        else
        if (fromPosition != -1 && wherePosition == -1) {
            if (qmarkPosition >= fromPosition + 3) {
                // case: FROM tbl ORDER BY
                if ("ORDER".equalsIgnoreCase(tokens[fromPosition + 2]) &&
                    "BY".equalsIgnoreCase(tokens[fromPosition + 3])) {
                    tableName = tokens[fromPosition + 1];
                }
                else
                // case: FROM tbl GROUP BY
                if ("GROUP".equalsIgnoreCase(tokens[fromPosition + 2]) &&
                    "BY".equalsIgnoreCase(tokens[fromPosition + 3])) {
                    tableName = tokens[fromPosition + 1];
                }
               
                if (!isColumnInTable(columnName, tableName)) {
                    tableName = null;
                }
            }
        }
       
        return tableName;
    }
   
    private boolean isColumnInTable(String columnName, String potentialTableName) {
        boolean bMatch = false;
       
        try {
            TableInfo ti = SqlExpressUtil.lookupTableInfo(udc, potentialTableName);
            bMatch = ti.getHeader().isValidColumnName(columnName);
        }
        catch(Exception ex) {
          String error = "Failed in isColumnInTable method for column \"" +
        columnName + "\" and table \"" + potentialTableName +
        "\" because " + ex.getMessage();
            log.error(error, ex);
            throw new LookupFailureException(error, ex);
        }
       
        return bMatch;
    }

    private UserDatabaseConnection udc;
    private JdbcStatement st;
}
TOP

Related Classes of com.scooterframework.orm.sqldataexpress.parser.JdbcStatementParser

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.