Main Page | Packages | Class Hierarchy | Alphabetical List | Class List | File List | Class Members | Related Pages

SelectRequest.java

00001 /**
00002  * C-JDBC: Clustered JDBC.
00003  * Copyright (C) 2002-2005 French National Institute For Research In Computer
00004  * Science And Control (INRIA).
00005  * Contact: c-jdbc@objectweb.org
00006  * 
00007  * This library is free software; you can redistribute it and/or modify it
00008  * under the terms of the GNU Lesser General Public License as published by the
00009  * Free Software Foundation; either version 2.1 of the License, or any later
00010  * version.
00011  * 
00012  * This library is distributed in the hope that it will be useful, but WITHOUT
00013  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
00014  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
00015  * for more details.
00016  * 
00017  * You should have received a copy of the GNU Lesser General Public License
00018  * along with this library; if not, write to the Free Software Foundation,
00019  * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
00020  *
00021  * Initial developer(s): Emmanuel Cecchet.
00022  * Contributor(s): Julie Marguerite, Mathieu Peltier, Sara Bouchenak.
00023  */
00024 
00025 package org.objectweb.cjdbc.common.sql;
00026 
00027 import java.io.Serializable;
00028 import java.sql.SQLException;
00029 import java.util.ArrayList;
00030 import java.util.Hashtable;
00031 import java.util.Iterator;
00032 import java.util.StringTokenizer;
00033 
00034 import org.objectweb.cjdbc.common.sql.schema.AliasedDatabaseTable;
00035 import org.objectweb.cjdbc.common.sql.schema.DatabaseColumn;
00036 import org.objectweb.cjdbc.common.sql.schema.DatabaseSchema;
00037 import org.objectweb.cjdbc.common.sql.schema.DatabaseTable;
00038 import org.objectweb.cjdbc.common.sql.schema.TableColumn;
00039 
00040 /**
00041  * A <code>SelectRequest</code> is an SQL request of the following syntax:
00042  * 
00043  * <pre>
00044  *  SELECT [ALL|DISTINCT] select-item[,select-item]* 
00045  *  FROM table-specification[,table-specification]* 
00046  *  [WHERE search-condition] 
00047  *  [GROUP BY grouping-column[,grouping-column]] 
00048  *  [HAVING search-condition] 
00049  *  [ORDER BY sort-specification[,sort-specification]] 
00050  *  [LIMIT ignored]
00051  * </pre>
00052  * 
00053  * Note that table-specification in the <code>FROM</code> clause can be a
00054  * sub-select. Everything after the end of the <code>WHERE</code> clause is
00055  * ignored.
00056  * 
00057  * @author <a href="mailto:Emmanuel.Cecchet@inria.fr">Emmanuel Cecchet </a>
00058  * @author <a href="mailto:Julie.Marguerite@inria.fr">Julie Marguerite </a>
00059  * @author <a href="mailto:Mathieu.Peltier@inrialpes.fr">Mathieu Peltier </a>
00060  * @author <a href="mailto:Sara.Bouchenak@epfl.ch">Sara Bouchenak </a>
00061  * @version 1.0
00062  */
00063 public class SelectRequest extends AbstractRequest implements Serializable
00064 {
00065   /** <code>ArrayList</code> of <code>TableColumn</code> objects. */
00066   private transient ArrayList select;
00067 
00068   /** <code>ArrayList</code> of <code>String</code> objects. */
00069   private transient ArrayList from;
00070 
00071   /** <code>ArrayList</code> of <code>AliasedTable</code> objects */
00072   private transient ArrayList aliasFrom;
00073 
00074   /** <code>ArrayList</code> of <code>TableColumn</code> objects. */
00075   private transient ArrayList where;
00076 
00077   /** <code>ArrayList</code> of <code>OrderBy</code> objects */
00078   private transient ArrayList order;
00079 
00080   /** Some values to keep track of function in the SELECT request */
00081   public static final int     NO_FUNCTION      = 0;
00082   /** Represents a SQL max() macro */
00083   public static final int     MAX_FUNCTION     = 1;
00084   /** Represents a SQL min() macro */
00085   public static final int     MIN_FUNCTION     = 2;
00086   /** Represents a SQL average() macro */
00087   public static final int     AVERAGE_FUNCTION = 3;
00088   /** Represents a SQL count() macro */
00089   public static final int     COUNT_FUNCTION   = 4;
00090   /** Represents a SQL sum() macro */
00091   public static final int     SUM_FUNCTION     = 5;
00092 
00093   /** Need to keep track of type of query, e.g. MAX, COUNT, etc. */
00094   public transient int        funcType         = 0;
00095 
00096   /** Primary key value in case of a unique selection */
00097   private transient String    pkValue;
00098 
00099   /**
00100    * <code>Hashtable</code> of String keys corresponding to column names and
00101    * String values corresponding to the values associated with the UNIQUE
00102    * columns of a UNIQUE SELECT.
00103    * <p>
00104    * Used with the <code>COLUMN_UNIQUE_DELETE</code> granularity.
00105    * 
00106    * @see org.objectweb.cjdbc.controller.cache.result.CachingGranularities
00107    */
00108   private transient Hashtable whereValues;
00109 
00110   private String              cursorName       = null;
00111 
00112   /**
00113    * Creates a new <code>SelectRequest</code> instance. The caller must give
00114    * an SQL request, without any leading or trailing spaces and beginning with
00115    * 'select ' (it will not be checked).
00116    * <p>
00117    * The SQL request is parsed and selected tables and columns are retrieved
00118    * using the given <code>DatabaseSchema</code>.
00119    * <p>
00120    * If the syntax is incorrect an exception is thrown.
00121    * 
00122    * @param sqlQuery the SQL query
00123    * @param escapeProcessing should the driver to escape processing before
00124    *          sending to the database ?
00125    * @param timeout an <code>int</code> value
00126    * @param lineSeparator the line separator used in the query
00127    * @param schema a <code>DatabaseSchema</code> value
00128    * @param granularity parsing granularity as defined in
00129    *          <code>ParsingGranularities</code>
00130    * @param isCaseSensitive true if parsing is case sensitive
00131    * @exception SQLException if an error occurs
00132    */
00133   public SelectRequest(String sqlQuery, boolean escapeProcessing, int timeout,
00134       String lineSeparator, DatabaseSchema schema, int granularity,
00135       boolean isCaseSensitive) throws SQLException
00136   {
00137     this(sqlQuery, escapeProcessing, timeout, lineSeparator);
00138     parse(schema, granularity, isCaseSensitive);
00139   }
00140 
00141   /**
00142    * Creates a new <code>SelectRequest</code> instance. The caller must give
00143    * an SQL request, without any leading or trailing spaces and beginning with
00144    * the 'select' keyword (it will not be checked).
00145    * <p>
00146    * The request is not parsed but it can be done later by a call to
00147    * {@link #parse(DatabaseSchema, int, boolean)}.
00148    * 
00149    * @param sqlQuery the SQL query
00150    * @param escapeProcessing should the driver to escape processing before
00151    *          sending to the database ?
00152    * @param timeout an <code>int</code> value
00153    * @param lineSeparator the line separator used in the query
00154    * @see #parse
00155    */
00156   public SelectRequest(String sqlQuery, boolean escapeProcessing, int timeout,
00157       String lineSeparator)
00158   {
00159     super(sqlQuery, escapeProcessing, timeout, lineSeparator);
00160     cacheable = RequestType.CACHEABLE;
00161     isParsed = false;
00162     pkValue = null;
00163   }
00164 
00165   /**
00166    * <p>
00167    * The result of the parsing is accessible through the {@link #getSelect()},
00168    * {@link #getFrom()}and {@link #getWhere()}functions.
00169    * 
00170    * @see org.objectweb.cjdbc.common.sql.AbstractRequest#parse(org.objectweb.cjdbc.common.sql.schema.DatabaseSchema,
00171    *      int, boolean)
00172    */
00173   public void parse(DatabaseSchema schema, int granularity,
00174       boolean isCaseSensitive) throws SQLException
00175   {
00176     if (granularity == ParsingGranularities.NO_PARSING)
00177     {
00178       cacheable = RequestType.CACHEABLE;
00179       isParsed = true;
00180       return;
00181     }
00182 
00183     // Sanity check
00184     if (schema == null)
00185       throw new SQLException(
00186           "Unable to parse request with an undefined database schema");
00187 
00188     String originalSQL = this.trimCarriageReturn();
00189     String sql = originalSQL.toLowerCase();
00190     if (!isCaseSensitive)
00191       originalSQL = sql;
00192 
00193     // Strip 'select'
00194     sql = sql.substring(6).trim();
00195 
00196     // Look for DISTINCT
00197     if (sql.startsWith("distinct"))
00198       sql = sql.substring(8).trim(); // Strip 'distinct '
00199 
00200     // Look for the begining of the FROM clause
00201     int fromIndex = sql.indexOf("from ");
00202     if (fromIndex == -1)
00203       throw new SQLException(
00204           "Unable to find the FROM keyword in this SELECT statement: '" + sql
00205               + "'");
00206 
00207     // Keep SELECT clause for later, we first have to check the
00208     // tables involved in the FROM clause
00209     int fshift = originalSQL.length() - sql.length();
00210     String selectClause = (isCaseSensitive) ? originalSQL.substring(fshift,
00211         fshift + fromIndex) : sql.substring(0, fromIndex);
00212 
00213     // Get rid of FROM
00214     sql = sql.substring(fromIndex + 5).trim();
00215 
00216     // Now find the boundaries of the FROM and WHERE clauses
00217     int whereIndex = 0;
00218     int parenthesis = 0;
00219     int lastParenthesisIdx = 0;
00220     boolean foundWhere = false;
00221     do
00222     {
00223       switch (sql.charAt(whereIndex))
00224       {
00225         case '(' :
00226           parenthesis++;
00227           break;
00228         case ')' :
00229           parenthesis--;
00230           lastParenthesisIdx = whereIndex;
00231           break;
00232         case 'w' :
00233           if (parenthesis == 0)
00234             try
00235             {
00236               foundWhere = (sql.charAt(whereIndex + 1) == 'h')
00237                   && (sql.charAt(whereIndex + 2) == 'e')
00238                   && (sql.charAt(whereIndex + 3) == 'r')
00239                   && (sql.charAt(whereIndex + 4) == 'e');
00240             }
00241             catch (StringIndexOutOfBoundsException ignore)
00242             {
00243               foundWhere = false;
00244             }
00245         default :
00246           break;
00247       }
00248       whereIndex++;
00249     }
00250     while ((!foundWhere) && (whereIndex < sql.length()));
00251     if (foundWhere)
00252       whereIndex--;
00253     else
00254       whereIndex = -1;
00255 
00256     // Warning! Here if whereIndex is -1 (no where clause)
00257     // endWhere is used to find the end of the FROM clause.
00258     // The variable name can be misleading but it's faster to do it this
00259     // way.
00260     int endWhere = sql.indexOf("group by ", lastParenthesisIdx);
00261     if (endWhere == -1)
00262     {
00263       endWhere = sql.indexOf("having ", lastParenthesisIdx);
00264       if (endWhere == -1)
00265       {
00266         endWhere = sql.indexOf("order by ", lastParenthesisIdx);
00267         if (endWhere == -1)
00268         {
00269           endWhere = sql.indexOf("limit ", lastParenthesisIdx);
00270           if (endWhere == -1)
00271             endWhere = sql.length();
00272         }
00273       }
00274     }
00275     int endFrom;
00276     if (whereIndex == -1)
00277       endFrom = endWhere;
00278     else
00279       endFrom = whereIndex;
00280 
00281     try
00282     {
00283       switch (granularity)
00284       {
00285         case ParsingGranularities.NO_PARSING :
00286           return;
00287         case ParsingGranularities.TABLE :
00288           int shift = originalSQL.length() - sql.length();
00289           from = getFromTables(originalSQL.substring(shift, shift + endFrom)
00290               .trim(), schema, isCaseSensitive);
00291           break;
00292         case ParsingGranularities.COLUMN :
00293         case ParsingGranularities.COLUMN_UNIQUE :
00294           shift = originalSQL.length() - sql.length();
00295           from = getFromTables(originalSQL.substring(shift, shift + endFrom)
00296               .trim(), schema, isCaseSensitive);
00297           // Find columns selected in the SELECT clause
00298           select = getSelectedColumns(selectClause, from, isCaseSensitive);
00299           if (whereIndex > 1)
00300             // Find columns involved in the WHERE clause (5="WHERE")
00301             where = getWhereColumns(originalSQL.substring(
00302                 shift + whereIndex + 5, shift + endWhere).trim(), from,
00303                 granularity == ParsingGranularities.COLUMN_UNIQUE,
00304                 isCaseSensitive);
00305           break;
00306         default :
00307           throw new SQLException("Unsupported parsing granularity: '"
00308               + granularity + "'");
00309       }
00310     }
00311     catch (SQLException e)
00312     {
00313       from = null;
00314       select = null;
00315       where = null;
00316       cacheable = RequestType.UNCACHEABLE;
00317       throw e;
00318     }
00319 
00320     //Gokul added this
00321     //I need to have the aliases to determine if any of the OrderBy columns
00322     //are referenced using their alias
00323 
00324     aliasFrom = from;
00325 
00326     if (from != null)
00327     {
00328       // Convert 'from' to an ArrayList of String objects instead of
00329       // AliasedTables objects
00330       int size = from.size();
00331       ArrayList unaliased = new ArrayList(size);
00332       for (int i = 0; i < size; i++)
00333         unaliased
00334             .add(((AliasedDatabaseTable) from.get(i)).getTable().getName());
00335       from = unaliased;
00336     }
00337 
00338     isParsed = true;
00339   }
00340 
00341   /**
00342    * @see AbstractRequest#cloneParsing(AbstractRequest)
00343    */
00344   public void cloneParsing(AbstractRequest request)
00345   {
00346     if (!request.isParsed())
00347       return;
00348     SelectRequest selectRequest = (SelectRequest) request;
00349     select = selectRequest.getSelect();
00350     from = selectRequest.getFrom();
00351     where = selectRequest.getWhere();
00352     cacheable = selectRequest.getCacheAbility();
00353     pkValue = selectRequest.getPkValue();
00354     isParsed = true;
00355   }
00356 
00357   /**
00358    * Extracts the tables from the given <code>FROM</code> clause and retrieves
00359    * their alias if any.
00360    * 
00361    * @param fromClause the <code>FROM</code> clause of the request (without
00362    *          the <code>FROM</code> keyword)
00363    * @param schema the <code>DatabaseSchema</code> this request refers to
00364    * @param isCaseSensitive true if table name parsing is case sensitive
00365    * @return an <code>ArrayList</code> of <code>AliasedDatabaseTable</code>
00366    *         objects
00367    * @exception SQLException if an error occurs
00368    */
00369   private ArrayList getFromTables(String fromClause, DatabaseSchema schema,
00370       boolean isCaseSensitive) throws SQLException
00371   {
00372     ArrayList result = new ArrayList();
00373 
00374     // Search for subselects in from clause
00375     try
00376     {
00377       int subSelect = fromClause.toLowerCase().indexOf("select ");
00378       while (subSelect != -1)
00379       {
00380         int subFromIndex = fromClause.indexOf("from", subSelect + 1) + 5;
00381         int bracket = subFromIndex;
00382         int parenthesis = 1;
00383         do
00384         {
00385           char c = fromClause.charAt(bracket);
00386           switch (c)
00387           {
00388             case '(' :
00389               parenthesis++;
00390               break;
00391             case ')' :
00392               parenthesis--;
00393               break;
00394             default :
00395               break;
00396           }
00397           bracket++;
00398         }
00399         while ((parenthesis > 0) && (bracket < fromClause.length()));
00400 
00401         SelectRequest subQuery = new SelectRequest(fromClause.substring(
00402             subSelect, bracket - 1).trim(), this.escapeProcessing, 0,
00403             getLineSeparator());
00404         subQuery.parse(schema, ParsingGranularities.TABLE, isCaseSensitive);
00405         for (Iterator iter = subQuery.getFrom().iterator(); iter.hasNext();)
00406         {
00407           result.add(new AliasedDatabaseTable(schema.getTable((String) iter
00408               .next(), isCaseSensitive), null));
00409         }
00410 
00411         if (subFromIndex + bracket > fromClause.length())
00412         {
00413           if (subSelect > 0)
00414           {
00415             fromClause = fromClause.substring(0, subSelect - 1).trim();
00416             if ((fromClause.length() > 0)
00417                 && (fromClause.charAt(fromClause.length() - 1) == '('))
00418               fromClause = fromClause.substring(0, fromClause.length() - 1)
00419                   .trim();
00420           }
00421           else
00422             fromClause = "";
00423           break; // Nothing more to process
00424         }
00425         fromClause = (subSelect > 0 ? fromClause.substring(0, subSelect - 1)
00426             .trim() : "")
00427             + fromClause.substring(subFromIndex + bracket).trim();
00428         subSelect = fromClause.toLowerCase().indexOf("select");
00429       }
00430     }
00431     catch (RuntimeException e)
00432     {
00433       // Parsing failed, select everything
00434       return schema.getTables();
00435     }
00436 
00437     // Use a brutal force technique by matching schema table names in the from
00438     // clause
00439     ArrayList tables = schema.getTables();
00440     int size = tables.size();
00441     for (int i = 0; i < size; i++)
00442     {
00443       // Check if this table is found in the FROM string
00444       DatabaseTable t = (DatabaseTable) tables.get(i);
00445       String tableName = t.getName();
00446       if (!isCaseSensitive)
00447         tableName = tableName.toLowerCase();
00448 
00449       // Check that we have a full match and not a partial match
00450       int index;
00451       int afterTableNameIndex = 0;
00452       boolean left;
00453       boolean right;
00454       do
00455       {
00456         index = fromClause.indexOf(tableName, afterTableNameIndex);
00457         if (index == -1)
00458           break;
00459         afterTableNameIndex = index + tableName.length();
00460         left = (index == 0)
00461             || ((index > 0) && ((fromClause.charAt(index - 1) == ' ')
00462                 || (fromClause.charAt(index - 1) == '(')
00463                 || (fromClause.charAt(index - 1) == ',') || (fromClause
00464                 .charAt(index - 1) == getLineSeparator().charAt(
00465                 getLineSeparator().length() - 1))));
00466         right = (afterTableNameIndex >= fromClause.length())
00467             || ((afterTableNameIndex < fromClause.length()) && ((fromClause
00468                 .charAt(afterTableNameIndex) == ' ')
00469                 || (fromClause.charAt(afterTableNameIndex) == ',')
00470                 || (fromClause.charAt(afterTableNameIndex) == ')') || (fromClause
00471                 .charAt(afterTableNameIndex) == getLineSeparator().charAt(0))));
00472       }
00473       while (!left || !right);
00474       if (index != -1)
00475       {
00476         // Check if the table has an alias
00477         // Example: SELECT x.price FROM item x
00478         String alias = null;
00479         index += tableName.length();
00480         if ((index < fromClause.length()) && (fromClause.charAt(index) == ' '))
00481         {
00482           char c;
00483           // Skip spaces before alias
00484           do
00485           {
00486             c = fromClause.charAt(index);
00487             index++;
00488           }
00489           while ((index < fromClause.length()) && (c != ' ')
00490               && (c != getLineSeparator().charAt(0)));
00491           if (index < fromClause.length())
00492           {
00493             int start = index;
00494             do
00495             {
00496               c = fromClause.charAt(index);
00497               index++;
00498             }
00499             while ((index < fromClause.length()) && (c != ' ') && (c != ',')
00500                 && (c != getLineSeparator().charAt(0)));
00501             alias = fromClause.substring(start, index - 1);
00502           }
00503         }
00504         result.add(new AliasedDatabaseTable(t, alias));
00505       }
00506     }
00507     return result;
00508   }
00509 
00510   /**
00511    * Gets all the columns selected in the given <code>SELECT</code> clause.
00512    * <p>
00513    * The selected columns or tables must be found in the given
00514    * <code>ArrayList</code> of <code>AliasedDatabaseTable</code>
00515    * representing the <code>FROM</code> clause of the same request.
00516    * 
00517    * @param selectClause <code>SELECT</code> clause of the request (without
00518    *          the <code>SELECT</code> keyword)
00519    * @param aliasedFrom an <code>ArrayList</code> of
00520    *          <code>AliasedDatabaseTable</code>
00521    * @param isCaseSensitive true if column name parsing is case sensitive
00522    * @return an <code>ArrayList</code> of <code>TableColumn</code>
00523    */
00524   private ArrayList getSelectedColumns(String selectClause,
00525       ArrayList aliasedFrom, boolean isCaseSensitive)
00526   {
00527     StringTokenizer selectTokens = new StringTokenizer(selectClause, ",");
00528     ArrayList result = new ArrayList();
00529     StringBuffer unresolvedTokens = null;
00530 
00531     while (selectTokens.hasMoreTokens())
00532     {
00533       String token = selectTokens.nextToken().trim();
00534       // Check if it is a function, e.g., MAX, COUNT, etc.
00535       if (isSqlFunction(token))
00536       {
00537         // token has the following form:
00538         // max(...)
00539         // or
00540         // count(...)
00541         int leftPar = token.indexOf("(");
00542         token = token.substring(leftPar + 1, token.length() - 1);
00543       }
00544       // Is it using an aliased table name (x.price for example) ?
00545       String alias = null;
00546       int aliasIdx = token.indexOf(".");
00547       if (aliasIdx != -1)
00548       {
00549         alias = token.substring(0, aliasIdx);
00550         token = token.substring(aliasIdx + 1); // Get rid of the '.'
00551       }
00552 
00553       // Discard any AS clause
00554       int as = token.indexOf(" as ");
00555       if (as != -1)
00556         token = token.substring(0, as).trim();
00557 
00558       // Now token only contains the column name
00559 
00560       // Deal with SELECT * or x.*
00561       if (token.indexOf("*") != -1)
00562       {
00563         if (alias == null)
00564         {
00565           // We have to take all colums of all tables of the FROM
00566           // clause
00567           int size = aliasedFrom.size();
00568           for (int i = 0; i < size; i++)
00569           {
00570             DatabaseTable t = ((AliasedDatabaseTable) aliasedFrom.get(i))
00571                 .getTable();
00572             ArrayList cols = t.getColumns();
00573             int colSize = cols.size();
00574             for (int j = 0; j < colSize; j++)
00575               result.add(new TableColumn(t.getName(), ((DatabaseColumn) cols
00576                   .get(j)).getName()));
00577           }
00578           return result;
00579         }
00580         else
00581         {
00582           // Add all colums of the table corresponding to the alias
00583           int size = aliasedFrom.size();
00584           for (int i = 0; i < size; i++)
00585           {
00586             AliasedDatabaseTable adt = (AliasedDatabaseTable) aliasedFrom
00587                 .get(i);
00588             // The alias could be the full name of the table
00589             // instead of a "real" alias
00590             if (alias.equals(adt.getAlias())
00591                 || alias.equals(adt.getTable().getName()))
00592             {
00593               DatabaseTable t = adt.getTable();
00594               ArrayList cols = t.getColumns();
00595               int colSize = cols.size();
00596               for (int j = 0; j < colSize; j++)
00597                 result.add(new TableColumn(t.getName(), ((DatabaseColumn) cols
00598                     .get(i)).getName()));
00599               break;
00600             }
00601           }
00602         }
00603         continue;
00604       }
00605 
00606       // First, we suppose that it's a simple column name.
00607       // If it fails, we will consider it later.
00608       DatabaseColumn col = null;
00609 
00610       if (alias == null)
00611       {
00612         int size = aliasedFrom.size();
00613         for (int i = 0; i < size; i++)
00614         {
00615           DatabaseTable t = ((AliasedDatabaseTable) aliasedFrom.get(i))
00616               .getTable();
00617           col = t.getColumn(token, isCaseSensitive);
00618           if (col != null)
00619           {
00620             result.add(new TableColumn(t.getName(), col.getName()));
00621             break;
00622           }
00623         }
00624       }
00625       else
00626       // same with an alias
00627       {
00628         int size = aliasedFrom.size();
00629         for (int i = 0; i < size; i++)
00630         {
00631           AliasedDatabaseTable t = (AliasedDatabaseTable) aliasedFrom.get(i);
00632           // It can be either an alias or the fully qualified name of
00633           // the table
00634           if (alias.equals(t.getAlias())
00635               || alias.equals(t.getTable().getName()))
00636           {
00637             col = t.getTable().getColumn(token, isCaseSensitive);
00638             if (col != null)
00639             {
00640               result
00641                   .add(new TableColumn(t.getTable().getName(), col.getName()));
00642               break;
00643             }
00644           }
00645         }
00646       }
00647 
00648       if (col == null)
00649       {
00650         if (unresolvedTokens == null)
00651           unresolvedTokens = new StringBuffer();
00652         unresolvedTokens.append(token);
00653         unresolvedTokens.append(" ");
00654       }
00655     }
00656 
00657     if (unresolvedTokens != null)
00658     {
00659       // Those tokens may be complex expressions, so instead of parsing
00660       // them, we use a brutal force technique and we try to directly
00661       // identify every column name of each table.
00662       DatabaseColumn col;
00663 
00664       String unresolvedTokensString = unresolvedTokens.toString();
00665       if (!isCaseSensitive)
00666         unresolvedTokensString = unresolvedTokensString.toLowerCase();
00667 
00668       int asize = aliasedFrom.size();
00669       for (int i = 0; i < asize; i++)
00670       {
00671         DatabaseTable t = ((AliasedDatabaseTable) aliasedFrom.get(i))
00672             .getTable();
00673         ArrayList cols = t.getColumns();
00674         int size = cols.size();
00675         for (int j = 0; j < size; j++)
00676         {
00677           col = (DatabaseColumn) cols.get(j);
00678           String columnName = col.getName();
00679           if (!isCaseSensitive)
00680             columnName = columnName.toLowerCase();
00681 
00682           // if pattern found and column not already in result, it's a
00683           // dependency !
00684           int matchIdx = unresolvedTokensString.indexOf(columnName);
00685           if (matchIdx != -1)
00686             if ((matchIdx == 0)
00687                 || (unresolvedTokens.charAt(matchIdx - 1) == ' ')
00688                 || (unresolvedTokens.charAt(matchIdx - 1) == '(')
00689                 || (unresolvedTokens.charAt(matchIdx - 1) == '.'))
00690             {
00691               TableColumn c = new TableColumn(t.getName(), col.getName());
00692               if (!result.contains(c))
00693                 result.add(c);
00694             }
00695         }
00696       }
00697     }
00698     return result;
00699   }
00700 
00701   /**
00702    * Checks if the string parameter represents an SQL function, e. g., MAX,
00703    * COUNT, SUM, etc.
00704    * 
00705    * @param str A lower-case string that may represent an SQL function
00706    * @return boolean <code>true</code> if it is an SQL function and
00707    *         <code>false</code> otherwise.
00708    */
00709   private boolean isSqlFunction(String str)
00710   {
00711 
00712     if (str != null)
00713     {
00714       if (str.startsWith("max(") && str.endsWith(")"))
00715       {
00716         funcType = SelectRequest.MAX_FUNCTION;
00717         return true;
00718       }
00719       else if (str.startsWith("count(") && str.endsWith(")"))
00720       {
00721         funcType = SelectRequest.COUNT_FUNCTION;
00722         return true;
00723       }
00724       else if (str.startsWith("avg(") && str.endsWith(")"))
00725       {
00726         funcType = SelectRequest.AVERAGE_FUNCTION;
00727         return true;
00728       }
00729       else if (str.startsWith("min(") && str.endsWith(")"))
00730       {
00731         funcType = SelectRequest.MIN_FUNCTION;
00732         return true;
00733       }
00734       else if (str.startsWith("sum(") && str.endsWith(")"))
00735       {
00736         funcType = SelectRequest.SUM_FUNCTION;
00737         return true;
00738       }
00739       else
00740       {
00741         funcType = SelectRequest.NO_FUNCTION;
00742         return false;
00743       }
00744     }
00745     else
00746       return false;
00747   }
00748 
00749   /**
00750    * Gets all the columns involved in the given <code>WHERE</code> clause.
00751    * <p>
00752    * The selected columns or tables must be found in the given
00753    * <code>ArrayList</code> of <code>AliasedDatabaseTable</code>
00754    * representing the <code>FROM</code> clause of the same request.
00755    * 
00756    * @param whereClause <code>WHERE</code> clause of the request (without the
00757    *          <code>WHERE</code> keyword)
00758    * @param aliasedFrom an <code>ArrayList</code> of
00759    *          <code>AliasedDatabaseTable</code>
00760    * @param setUniqueCacheable true if we have to check is this select is
00761    *          <code>UNIQUE</code> or not
00762    * @param isCaseSensitive true if column name parsing is case sensitive
00763    * @return an <code>ArrayList</code> of <code>TableColumn</code>
00764    */
00765   private ArrayList getWhereColumns(String whereClause, ArrayList aliasedFrom,
00766       boolean setUniqueCacheable, boolean isCaseSensitive)
00767   {
00768     ArrayList result = new ArrayList(); // TableColumn
00769     // objects
00770 
00771     if (!isCaseSensitive)
00772       whereClause = whereClause.toLowerCase();
00773 
00774     // Instead of parsing the clause, we use a brutal force technique
00775     // and we try to directly identify every column name of each table.
00776     DatabaseColumn col;
00777     for (int i = 0; i < aliasedFrom.size(); i++)
00778     {
00779       DatabaseTable t = ((AliasedDatabaseTable) aliasedFrom.get(i)).getTable();
00780       ArrayList cols = t.getColumns();
00781       int size = cols.size();
00782       for (int j = 0; j < size; j++)
00783       {
00784         col = (DatabaseColumn) cols.get(j);
00785         // if pattern found and column not already in result, it's a
00786         // dependency !
00787         String columnName = col.getName();
00788         if (!isCaseSensitive)
00789           columnName = columnName.toLowerCase();
00790 
00791         int matchIdx = whereClause.indexOf(columnName);
00792         while (matchIdx > 0)
00793         {
00794           // Try to check that we got the full pattern and not a
00795           // sub-pattern
00796           char beforePattern = whereClause.charAt(matchIdx - 1);
00797           if (((beforePattern >= 'a') && (beforePattern <= 'z'))
00798               || ((beforePattern >= 'A') && (beforePattern <= 'Z'))
00799               || (beforePattern == '_'))
00800             matchIdx = whereClause.indexOf(columnName, matchIdx + 1);
00801           else
00802           {
00803             char afterPattern;
00804             try
00805             {
00806               afterPattern = whereClause.charAt(matchIdx + columnName.length());
00807               if (((afterPattern >= 'a') && (afterPattern <= 'z'))
00808                   || ((afterPattern >= 'A') && (afterPattern <= 'Z'))
00809                   || (afterPattern == '_'))
00810               {
00811                 // This is a subset of the full name of another
00812                 // column,
00813                 //let's jump to next mathing pattern
00814                 matchIdx = whereClause.indexOf(columnName, matchIdx + 1);
00815               }
00816               else
00817                 break;
00818             }
00819             catch (IndexOutOfBoundsException e)
00820             {
00821               break;
00822             }
00823           }
00824         }
00825         if (matchIdx == -1)
00826           continue;
00827         result.add(new TableColumn(t.getName(), col.getName()));
00828 
00829         if (setUniqueCacheable)
00830         { // Check if this request selects a
00831           // unique row
00832           if (!col.isUnique())
00833           { // Column has no unicity constraint,
00834             // we can select multiple rows
00835             // with same value, give up.
00836             setUniqueCacheable = false;
00837             continue;
00838           }
00839 
00840           // Check if the column is in the left side of an equality
00841           // with a
00842           // constant.
00843           // e.g.: 'column_name1 = 10' is ok
00844           // but '5=table_name.column_name2' will fail
00845 
00846           int lookingForEqual = matchIdx + columnName.length();
00847           boolean searchReverse = false;
00848           try
00849           {
00850             while (whereClause.charAt(lookingForEqual) == ' ')
00851               lookingForEqual++;
00852           }
00853           catch (Exception e)
00854           {
00855             searchReverse = true;
00856           }
00857 
00858           String rightSide;
00859 
00860           if (searchReverse || (whereClause.charAt(lookingForEqual) != '='))
00861           {
00862             try
00863             {
00864               // try reverse
00865               StringBuffer sb = new StringBuffer(whereClause.substring(0,
00866                   lookingForEqual));
00867               String reverse = sb.reverse().toString();
00868               reverse = reverse.substring(reverse.indexOf('=') + 1);
00869               sb = new StringBuffer(reverse);
00870               // Get back the original values
00871               sb = sb.reverse();
00872               rightSide = sb.toString();
00873             }
00874             catch (Exception e)
00875             {
00876               // No equality, it is not unique cacheable
00877               setUniqueCacheable = false;
00878               continue;
00879             }
00880           }
00881           else
00882           {
00883             // We found it let's move to next char
00884             int nextSpace = lookingForEqual + 1;
00885             try
00886             {
00887               while (whereClause.charAt(nextSpace) == ' ')
00888                 nextSpace++;
00889             }
00890             catch (Exception e1)
00891             { // This should not happen
00892               // unless we get a query like:
00893               // 'select ... where id= '
00894               setUniqueCacheable = false;
00895               continue;
00896             }
00897 
00898             rightSide = whereClause.substring(nextSpace);
00899           }
00900           char firstChar = rightSide.charAt(0);
00901           if ((firstChar == '\'') || (firstChar == '"')
00902               || ((firstChar >= '0') && (firstChar <= '9'))
00903               || (firstChar == '?'))
00904           { // Ok, the value is either
00905             // '...' or "..." or starts
00906             // with a
00907             // number which is enough for us to consider that it is
00908             // an
00909             // acceptable constant.
00910             pkValue = rightSide;
00911           }
00912           else
00913           {
00914             setUniqueCacheable = false;
00915             continue;
00916           }
00917         }
00918       }
00919     }
00920 
00921     if (setUniqueCacheable && !result.isEmpty())
00922       cacheable = RequestType.UNIQUE_CACHEABLE;
00923 
00924     return result;
00925   }
00926 
00927   /**
00928    * Returns an <code>ArrayList</code> of <code>DatabaseColumn</code>
00929    * objects representing the columns selected in the <code>SELECT</code>
00930    * clause of this request.
00931    * 
00932    * @return an <code>ArrayList</code> of <code>TableColumn</code>
00933    */
00934   public ArrayList getSelect()
00935   {
00936     return select;
00937   }
00938 
00939   /**
00940    * Returns an <code>ArrayList</code> of <code>String</code> objects
00941    * representing the table names found in the <code>FROM</code> clause of
00942    * this request.
00943    * 
00944    * @return an <code>ArrayList</code> of <code>String</code>
00945    */
00946   public ArrayList getFrom()
00947   {
00948     return from;
00949   }
00950 
00951   /**
00952    * Returns an <code>ArrayList</code> of <code>AliasedDatabaseTable</code>
00953    * objects representing the table names found in the <code>FROM</code>
00954    * clause of this request.
00955    * 
00956    * @return an <code>ArrayList</code> of <code>AliasedDatabaseTable</code>
00957    */
00958   public ArrayList getAliasedFrom()
00959   {
00960     return aliasFrom;
00961   }
00962 
00963   /**
00964    * Returns an <code>ArrayList</code> of <code>TableColumn</code> objects
00965    * representing the columns involved in the <code>WHERE</code> clause of
00966    * this request.
00967    * 
00968    * @return an <code>ArrayList</code> of <code>TableColumn</code>
00969    */
00970   public ArrayList getWhere()
00971   {
00972     return where;
00973   }
00974 
00975   /**
00976    * Returns an <code>ArrayList</code> of <code>OrderBy</code> objects
00977    * representing the columns involved in the <code>ORDER BY</code> clause of
00978    * this request.
00979    * 
00980    * @return an <code>ArrayList</code> of <code>OrderBy</code>
00981    */
00982   public ArrayList getOrderBy()
00983   {
00984     return order;
00985   }
00986 
00987   /**
00988    * Returns an <code>Hashtable</code> of <code>String</code> keys
00989    * representing unique column names and <code>String</code> values
00990    * associated with the columns involved in this request.
00991    * 
00992    * @return an <code>Hashtable</code> value
00993    */
00994   public Hashtable getWhereValues()
00995   {
00996     return whereValues;
00997   }
00998 
00999   /**
01000    * @return <code>true</code>
01001    * @see org.objectweb.cjdbc.common.sql.AbstractRequest#isReadRequest()
01002    */
01003   public boolean isReadRequest()
01004   {
01005     return true;
01006   }
01007 
01008   /**
01009    * @return <code>false</code>
01010    * @see org.objectweb.cjdbc.common.sql.AbstractRequest#isWriteRequest()
01011    */
01012   public boolean isWriteRequest()
01013   {
01014     return false;
01015   }
01016 
01017   /**
01018    * @return <code>false</code>
01019    * @see org.objectweb.cjdbc.common.sql.AbstractRequest#isUnknownRequest()
01020    */
01021   public boolean isUnknownRequest()
01022   {
01023     return false;
01024   }
01025 
01026   /**
01027    * @return Returns the pkValue.
01028    */
01029   public String getPkValue()
01030   {
01031     return pkValue;
01032   }
01033 
01034   /**
01035    * @param pkValue The pkValue to set.
01036    */
01037   public void setPkValue(String pkValue)
01038   {
01039     this.pkValue = pkValue;
01040   }
01041 
01042   /**
01043    * Returns the cursorName value.
01044    * 
01045    * @return Returns the cursorName.
01046    */
01047   public String getCursorName()
01048   {
01049     return cursorName;
01050   }
01051 
01052   /**
01053    * Sets the cursorName value.
01054    * 
01055    * @param cursorName The cursorName to set.
01056    */
01057   public void setCursorName(String cursorName)
01058   {
01059     this.cursorName = cursorName;
01060   }
01061 
01062   /**
01063    * Displays some debugging information about this request.
01064    */
01065   public void debug()
01066   {
01067     super.debug();
01068     if (select != null)
01069     {
01070       System.out.println("Selected columns:");
01071       for (int i = 0; i < select.size(); i++)
01072         System.out
01073             .println("  " + ((TableColumn) select.get(i)).getColumnName());
01074     }
01075     else
01076       System.out.println("No information on selected columns");
01077 
01078     if (select != null)
01079     {
01080       System.out.println("");
01081       System.out.println("From tables:");
01082       for (int i = 0; i < from.size(); i++)
01083         System.out.println("  " + from.get(i));
01084     }
01085     else
01086       System.out.println("No information on from tables");
01087 
01088     System.out.println("");
01089     System.out.println("Where columns:");
01090     if (where == null)
01091       System.out.println("  No Where clause");
01092     else
01093       for (int i = 0; i < where.size(); i++)
01094         System.out.print("  " + ((TableColumn) where.get(i)).getColumnName());
01095 
01096     System.out.println("");
01097     System.out.println("PK value: " + pkValue);
01098   }
01099 
01100 }

Generated on Mon Apr 11 22:01:34 2005 for C-JDBC by  doxygen 1.3.9.1