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

UpdateRequest.java

00001 /**
00002  * C-JDBC: Clustered JDBC.
00003  * Copyright (C) 2002-2004 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): Mathieu Peltier.
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.HashMap;
00031 import java.util.StringTokenizer;
00032 
00033 import org.objectweb.cjdbc.common.sql.schema.DatabaseColumn;
00034 import org.objectweb.cjdbc.common.sql.schema.DatabaseSchema;
00035 import org.objectweb.cjdbc.common.sql.schema.DatabaseTable;
00036 import org.objectweb.cjdbc.common.sql.schema.TableColumn;
00037 
00038 /**
00039  * An <code>UpdateRequest</code> is an SQL request with the following syntax:
00040  * 
00041  * <pre>
00042  *   UPDATE table-name SET (column-name=expression[,column-name=expression]*) WHERE search-condition
00043  * </pre>
00044  * 
00045  * @author <a href="mailto:Emmanuel.Cecchet@inria.fr">Emmanuel Cecchet </a>
00046  * @author <a href="mailto:Mathieu.Peltier@inrialpes.fr">Mathieu Peltier </a>
00047  * @version 1.0
00048  */
00049 public class UpdateRequest extends AbstractWriteRequest implements Serializable
00050 {
00051   /** <code>true</code> if this request updates a <code>UNIQUE</code> row. */
00052   private transient boolean isUnique;
00053 
00054   private transient HashMap updatedValues = null;
00055 
00056   /**
00057    * Creates a new <code>UpdateRequest</code> instance. The caller must give
00058    * an SQL request, without any leading or trailing spaces and beginning with
00059    * 'update ' (it will not be checked).
00060    * <p>
00061    * If the syntax is incorrect an exception is thrown.
00062    * 
00063    * @param sqlQuery the SQL query
00064    * @param escapeProcessing should the driver to escape processing before
00065    *          sending to the database ?
00066    * @param timeout an <code>int</code> value
00067    * @param lineSeparator the line separator used in the query
00068    * @param schema a <code>DatabaseSchema</code> value
00069    * @param granularity parsing granularity as defined in
00070    *          <code>ParsingGranularities</code>
00071    * @param isCaseSensitive true if parsing is case sensitive
00072    * @exception SQLException if an error occurs
00073    */
00074   public UpdateRequest(String sqlQuery, boolean escapeProcessing, int timeout,
00075       String lineSeparator, DatabaseSchema schema, int granularity,
00076       boolean isCaseSensitive) throws SQLException
00077   {
00078     this(sqlQuery, escapeProcessing, timeout, lineSeparator);
00079     parse(schema, granularity, isCaseSensitive);
00080   }
00081 
00082   /**
00083    * Creates a new <code>UpdateRequest</code> instance. The caller must give
00084    * an SQL request, without any leading or trailing spaces and beginning with
00085    * 'update ' (it will not be checked).
00086    * <p>
00087    * The request is not parsed but it can be done later by a call to
00088    * {@link #parse(DatabaseSchema, int, boolean)}.
00089    * 
00090    * @param sqlQuery the SQL query
00091    * @param escapeProcessing should the driver to escape processing before
00092    *          sending to the database ?
00093    * @param timeout an <code>int</code> value
00094    * @param lineSeparator the line separator used in the query
00095    * @see #parse
00096    */
00097   public UpdateRequest(String sqlQuery, boolean escapeProcessing, int timeout,
00098       String lineSeparator)
00099   {
00100     super(sqlQuery, escapeProcessing, timeout, lineSeparator);
00101     cacheable = RequestType.UNCACHEABLE;
00102     isParsed = false;
00103     isUnique = false;
00104   }
00105 
00106   /**
00107    * Parses the SQL request and extract the selected columns and tables given
00108    * the <code>DatabaseSchema</code> of the database targeted by this request.
00109    * Determines also if this query only deletes a single row, and the equivalent
00110    * <code>INSERT</code> statement.
00111    * <p>
00112    * An exception is thrown when the parsing fails. Warning, this method does
00113    * not check the validity of the request. In particular, invalid request could
00114    * be parsed without throwing an exception. However, valid SQL request should
00115    * never throw an exception.
00116    * 
00117    * @param schema a <code>DatabaseSchema</code> value
00118    * @param granularity parsing granularity as defined in
00119    *          <code>ParsingGranularities</code>
00120    * @param isCaseSensitive true if table name parsing is case sensitive
00121    * @exception SQLException if the parsing fails
00122    */
00123   public void parse(DatabaseSchema schema, int granularity,
00124       boolean isCaseSensitive) throws SQLException
00125   {
00126     if (granularity == ParsingGranularities.NO_PARSING)
00127     {
00128       isParsed = true;
00129       return;
00130     }
00131 
00132     // Sanity check
00133     if (schema == null)
00134       throw new SQLException(
00135           "Unable to parse request with an undefined database schema");
00136 
00137     String whereClause = null;
00138     isUnique = true;
00139 
00140     String originalSQL = this.trimCarriageReturn();
00141     String sql = originalSQL.toLowerCase();
00142 
00143     // Strip 'update '
00144     sql = sql.substring(7).trim();
00145 
00146     // Look for the SET or WHERE clause
00147     int setIdx = sql.indexOf("set ");
00148     int whereIdx = sql.indexOf("where ");
00149     if (setIdx == -1)
00150       throw new SQLException(
00151           "Unable to find the SET keyword in this UPDATE statement: '"
00152               + sqlQuery + "'");
00153 
00154     if (isCaseSensitive)
00155       sql = originalSQL.substring(7).trim();
00156 
00157     if (whereIdx == -1)
00158     {
00159       whereIdx = sql.length();
00160       isUnique = false;
00161     }
00162     else
00163     {
00164       whereClause = sql.substring(whereIdx + 5);
00165       // 5 = "where".length(), do not trim or remove anything after
00166       // else the following code will no more work
00167       sql = sql.substring(0, whereIdx + 1).trim();
00168     }
00169 
00170     // Get the table on which UPDATE occurs
00171     DatabaseTable t = schema.getTable(sql.substring(0, setIdx).trim(),
00172         isCaseSensitive);
00173     if (t == null)
00174       throw new SQLException("Unknown table '" + tableName
00175           + "' in this UPDATE statement: '" + sqlQuery + "'");
00176     else
00177       // Get the real name here (resolves case sentivity problems)
00178       tableName = t.getName();
00179 
00180     if (granularity > ParsingGranularities.TABLE)
00181     {
00182       // We have to get the affected columns
00183       // Column names are separated by comas and are before a '=' symbol
00184       StringTokenizer columnTokens = new StringTokenizer(sql.substring(
00185           setIdx + 4, whereIdx), ",");
00186       // 4=length("SET ")
00187       columns = new ArrayList();
00188       DatabaseColumn col = null;
00189       while (columnTokens.hasMoreTokens())
00190       {
00191         String token = columnTokens.nextToken();
00192         int eq = token.indexOf("=");
00193         if (eq == -1)
00194           continue;
00195         token = token.substring(0, eq).trim();
00196         col = t.getColumn(token, isCaseSensitive);
00197         if (col == null)
00198         {
00199           tableName = null;
00200           columns = null;
00201           throw new SQLException("Unknown column name '" + token
00202               + "' in this UPDATE statement: '" + sqlQuery + "'");
00203         }
00204         else
00205           columns.add(new TableColumn(tableName, col.getName()));
00206       }
00207     }
00208 
00209     isParsed = true;
00210     if (!isUnique)
00211       return;
00212     else
00213       isUnique = false;
00214 
00215     if (granularity < ParsingGranularities.COLUMN_UNIQUE)
00216       return;
00217 
00218     // Prepare hashtable for updated values
00219     updatedValues = new HashMap(columns.size());
00220 
00221     // Check whether this update affects a single row or not
00222     // Instead of parsing the clause, we use a brutal force technique
00223     // and we try to directly identify every column name of the table.
00224     DatabaseColumn col = null;
00225     ArrayList cols = t.getColumns();
00226     int size = cols.size();
00227     for (int j = 0; j < size; j++)
00228     {
00229       col = (DatabaseColumn) cols.get(j);
00230       String colName = col.getName();
00231       // if pattern found and column not already in result, it's a dependency !
00232       int matchIdx = whereClause.indexOf(colName);
00233       while (matchIdx > 0)
00234       {
00235         // Try to check that we got the full pattern and not a sub-pattern
00236         char beforePattern = whereClause.charAt(matchIdx - 1);
00237         if (((beforePattern >= 'a') && (beforePattern <= 'z'))
00238             || ((beforePattern >= 'A') && (beforePattern <= 'Z'))
00239             || (beforePattern == '_'))
00240           matchIdx = whereClause.indexOf(colName, matchIdx + 1);
00241         else
00242         { // Ok it's a good one, check if it is UNIQUE
00243           isUnique = col.isUnique();
00244           if (!isUnique)
00245             return;
00246           // Check if this UNIQUE columns stands in the left part of an
00247           // equality
00248           int eq = whereClause.indexOf("=", matchIdx);
00249           if ((eq == -1)
00250               || (whereClause.substring(matchIdx + colName.length(), eq).trim()
00251                   .length() > 0))
00252           {
00253             isUnique = false;
00254             return;
00255           }
00256           do
00257           {
00258             eq++; // Skip spaces
00259           }
00260           while (whereClause.charAt(eq) == ' ');
00261 
00262           // Check if we have "..." or '...'
00263           char startChar = whereClause.charAt(eq);
00264           int end;
00265           if ((startChar == '\'') || (startChar == '"'))
00266           {
00267             eq++;
00268             do
00269             { // Look for the end of the quote and take care of \' or \"
00270               end = whereClause.indexOf(startChar, eq);
00271             }
00272             while (whereClause.charAt(end - 1) == '\\');
00273           }
00274           else
00275           {
00276             // It's a regular value just find the next comma
00277             end = whereClause.indexOf(",", eq);
00278             if (end == -1)
00279               end = whereClause.length();
00280           }
00281           pkValue = whereClause.substring(eq, end);
00282 
00283           matchIdx = whereClause.indexOf(colName, matchIdx + 1);
00284         }
00285       }
00286     }
00287 
00288     cacheable = RequestType.UNIQUE_CACHEABLE;
00289 
00290     // Now get the values for each updated field
00291     sql = originalSQL.substring(7).substring(0, whereIdx).trim();
00292     if(!isCaseSensitive)
00293       sql.toLowerCase();
00294     int set = sql.toLowerCase().indexOf("set");
00295     sql = sql.substring(set+3).trim();
00296     
00297     for (int j = 0; j < cols.size(); j++)
00298     {
00299       col = (DatabaseColumn) cols.get(j);
00300       // if pattern found and column not already in result, it's a dependency !
00301       String colName = (isCaseSensitive) ? col.getName() : col.getName().toLowerCase();
00302       int matchIdx = sql.indexOf(colName);
00303 
00304       while (matchIdx >= 0)
00305       {
00306         char afterPattern = sql.charAt(matchIdx + colName.length());
00307         if ((afterPattern != '=') && (afterPattern != ' '))
00308         {
00309           matchIdx = sql.indexOf(colName, matchIdx + colName.length());
00310           continue;
00311         }
00312 
00313         // Try to check that we got the full pattern and not a sub-pattern
00314         char beforePattern = Character.CONTROL;
00315         try
00316         {
00317           beforePattern = sql.charAt(matchIdx - 1);
00318         }
00319         catch (RuntimeException e)
00320         {
00321           // nothing
00322         }
00323         if (((beforePattern >= 'a') && (beforePattern <= 'z')) // Everything
00324             // should be
00325             // lowercase here
00326             || (beforePattern == '_'))
00327           matchIdx = sql.indexOf(colName, matchIdx + 1);
00328         else
00329         { // Ok, it's good, get the value on the right part of the equality
00330           int eq = sql.indexOf("=", matchIdx);
00331           do
00332           {
00333             eq++; // Skip spaces
00334           }
00335           while (sql.charAt(eq) == ' ');
00336 
00337           // Check if we have "..." or '...'
00338           char startChar = sql.charAt(eq);
00339           int end;
00340           if ((startChar == '\'') || (startChar == '"'))
00341           {
00342             eq++;
00343             do
00344             { // Look for the end of the quote and take care of \' or \"
00345               end = sql.indexOf(startChar, eq);
00346             }
00347             while (sql.charAt(end - 1) == '\\');
00348           }
00349           else
00350           {
00351             // It's a regular value just find the next comma
00352             end = sql.indexOf(",", eq);
00353             if (end == -1)
00354               end = sql.length();
00355           }
00356           updatedValues.put(col.getName(), sql.substring(eq, end).trim());
00357           break;
00358         }
00359       }
00360     }
00361   }
00362 
00363   /**
00364    * What are the updated values in this request
00365    * 
00366    * @return a hashtable of (colname,value) or null if parsing granularity has
00367    *         stop computation
00368    */
00369   public HashMap getUpdatedValues()
00370   {
00371     return updatedValues;
00372   }
00373 
00374   /**
00375    * @see AbstractRequest#cloneParsing(AbstractRequest)
00376    */
00377   public void cloneParsing(AbstractRequest request)
00378   {
00379     if (!request.isParsed())
00380       return;
00381     cloneTableNameAndColumns((AbstractWriteRequest) request);
00382     updatedValues = ((UpdateRequest) request).getUpdatedValues();
00383     isParsed = true;
00384   }
00385 
00386   /**
00387    * @return <code>false</code>
00388    * @see org.objectweb.cjdbc.common.sql.AbstractWriteRequest#isInsert()
00389    */
00390   public boolean isInsert()
00391   {
00392     return false;
00393   }
00394 
00395   /**
00396    * @return <code>true</code>
00397    * @see org.objectweb.cjdbc.common.sql.AbstractWriteRequest#isUpdate()
00398    */
00399   public boolean isUpdate()
00400   {
00401     return true;
00402   }
00403 
00404   /**
00405    * @return <code>false</code>
00406    * @see org.objectweb.cjdbc.common.sql.AbstractWriteRequest#isDelete()
00407    */
00408   public boolean isDelete()
00409   {
00410     return false;
00411   }
00412 
00413   /**
00414    * @return <code>false</code>
00415    * @see org.objectweb.cjdbc.common.sql.AbstractWriteRequest#isCreate()
00416    */
00417   public boolean isCreate()
00418   {
00419     return false;
00420   }
00421 
00422   /**
00423    * @return <code>false</code>
00424    * @see org.objectweb.cjdbc.common.sql.AbstractWriteRequest#isDrop()
00425    */
00426   public boolean isDrop()
00427   {
00428     return false;
00429   }
00430 
00431   /**
00432    * Returns <code>true</code> as this request updates a <code>UNIQUE</code>
00433    * row.
00434    * 
00435    * @return <code>false</code>
00436    */
00437   public boolean isUnique()
00438   {
00439     return isUnique;
00440   }
00441 
00442   /**
00443    * Displays some debugging information about this request.
00444    */
00445   public void debug()
00446   {
00447     super.debug();
00448     if (tableName != null)
00449       System.out.println("Updated table: " + tableName);
00450     else
00451       System.out.println("No information about updated table");
00452 
00453     if (columns != null)
00454     {
00455       System.out.println("Updated columns:");
00456       for (int i = 0; i < columns.size(); i++)
00457         System.out.println("  "
00458             + ((TableColumn) columns.get(i)).getColumnName());
00459     }
00460     else
00461       System.out.println("No information about updated columns");
00462 
00463     System.out.println("Unique update: " + isUnique);
00464 
00465     System.out.println("");
00466   }
00467   /**
00468    * @see org.objectweb.cjdbc.common.sql.AbstractWriteRequest#isAlter()
00469    */
00470   public boolean isAlter()
00471   {
00472     return false;
00473   }
00474 }

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