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

DeleteRequest.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.StringTokenizer;
00031 
00032 import org.objectweb.cjdbc.common.sql.schema.AliasedDatabaseTable;
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>DeleteRequest</code> is an SQL request with the following syntax:
00040  * 
00041  * <pre>DELETE [table1] FROM table1,table2,table3,... WHERE search-condition
00042  * or DELETE t WHERE search-condition
00043  * </pre>
00044  * 
00045  * Note that DELETE from multiple tables are not supported but this is not part
00046  * of the SQL standard.
00047  * 
00048  * @author <a href="mailto:Emmanuel.Cecchet@inria.fr">Emmanuel Cecchet </a>
00049  * @author <a href="mailto:Julie.Marguerite@inria.fr">Julie Marguerite </a>
00050  * @author <a href="mailto:Mathieu.Peltier@inrialpes.fr">Mathieu Peltier </a>
00051  * @author <a href="mailto:Sara.Bouchenak@epfl.ch">Sara Bouchenak </a>
00052  * @version 1.0
00053  */
00054 public class DeleteRequest extends AbstractWriteRequest implements Serializable
00055 {
00056   /** <code>true</code> if this query only deletes a single row. */
00057   private transient boolean   isUnique;
00058 
00059   /** <code>ArrayList</code> of <code>String</code> objects */
00060   private transient ArrayList from;
00061 
00062   /**
00063    * <code>ArrayList</code> of values <code>String</code> associated with
00064    * the unique columns involved in this delete query.
00065    * <p>
00066    * The <code>values</code> instance variable is only used when a <code>
00067    * COLUMN_UNIQUE_DELETE</code>
00068    * granularity is applied. Here, the DELETE request is UNIQUE: all columns of
00069    * the WHERE clause are UNIQUE and used in the left part of an equality. When
00070    * such a granularity is used, the <code>columns</code> instance variable
00071    * contains only UNIQUE columns.
00072    * 
00073    * @see org.objectweb.cjdbc.controller.cache.result.CachingGranularities
00074    */
00075   protected ArrayList         whereValues;
00076 
00077   /**
00078    * Creates a new <code>DeleteRequest</code> instance. The caller must give
00079    * an SQL request, without any leading or trailing spaces and beginning with
00080    * 'delete ' (it will not be checked).
00081    * <p>
00082    * If the syntax is incorrect an exception is thrown.
00083    * 
00084    * @param sqlQuery the SQL request
00085    * @param escapeProcessing should the driver to escape processing before
00086    *          sending to the database ?
00087    * @param timeout an <code>int</code> value
00088    * @param lineSeparator the line separator used in the query
00089    * @param schema a <code>DatabaseSchema</code> value
00090    * @param granularity parsing granularity as defined in
00091    *          <code>ParsingGranularities</code>
00092    * @param isCaseSensitive true if parsing is case sensitive
00093    * @exception SQLException if an error occurs
00094    */
00095   public DeleteRequest(String sqlQuery, boolean escapeProcessing, int timeout,
00096       String lineSeparator, DatabaseSchema schema, int granularity,
00097       boolean isCaseSensitive) throws SQLException
00098   {
00099     this(sqlQuery, escapeProcessing, timeout, lineSeparator);
00100     parse(schema, granularity, isCaseSensitive);
00101   }
00102 
00103   /**
00104    * Creates a new <code>DeleteRequest</code> instance. The caller must give
00105    * an SQL request, without any leading or trailing spaces and beginning with
00106    * 'delete ' (it will not be checked).
00107    * <p>
00108    * The request is not parsed but it can be done later by a call to
00109    * {@link #parse(DatabaseSchema, int, boolean)}.
00110    * 
00111    * @param sqlQuery the SQL request
00112    * @param escapeProcessing should the driver to escape processing before
00113    *          sending to the database ?
00114    * @param timeout an <code>int</code> value
00115    * @param lineSeparator the line separator used in the query
00116    * @see #parse
00117    */
00118   public DeleteRequest(String sqlQuery, boolean escapeProcessing, int timeout,
00119       String lineSeparator)
00120   {
00121     super(sqlQuery, escapeProcessing, timeout, lineSeparator);
00122     cacheable = RequestType.UNCACHEABLE;
00123     isParsed = false;
00124     isUnique = false;
00125   }
00126 
00127   /**
00128    * Parses the SQL request and extracts the selected columns and tables given
00129    * the <code>DatabaseSchema</code> of the database targeted by this request.
00130    * <p>
00131    * An exception is thrown when the parsing fails. Warning, this method does
00132    * not check the validity of the request. In particular, invalid request could
00133    * be parsed without throwing an exception. However, valid SQL request should
00134    * never throw an exception.
00135    * 
00136    * @param schema a <code>DatabaseSchema</code> value
00137    * @param granularity parsing granularity as defined in
00138    *          <code>ParsingGranularities</code>
00139    * @param isCaseSensitive if parsing must be case sensitive
00140    * @exception SQLException if the parsing fails
00141    */
00142   public void parse(DatabaseSchema schema, int granularity,
00143       boolean isCaseSensitive) throws SQLException
00144   {
00145     if (granularity == ParsingGranularities.NO_PARSING)
00146     {
00147       isParsed = true;
00148       return;
00149     }
00150 
00151     // Sanity check
00152     if (schema == null)
00153       throw new SQLException(
00154           "Unable to parse request with an undefined database schema");
00155 
00156     String originalSQL = this.trimCarriageReturn();
00157     String sql = originalSQL.toLowerCase();
00158 
00159     int fromIdx = sql.indexOf("from ");
00160     if (fromIdx == -1)
00161     {
00162       // For queries like: DELETE t WHERE ... used by Oracle
00163       fromIdx = 6; // 6 = "delete".length()
00164     }
00165     else
00166     {
00167       // Syntax is usually DELETE FROM t WHERE ... but it can be
00168       // DELETE t1 FROM t1,t2,.... WHERE ...
00169       // If there is something between DELETE and FROM, tableName will use this
00170       // name but the FROM clause will have all tables.
00171       String tableBetweenDeleteAndFrom;
00172       if (isCaseSensitive)
00173         tableBetweenDeleteAndFrom = originalSQL.substring(6, fromIdx).trim();
00174       else
00175         tableBetweenDeleteAndFrom = sql.substring(6, fromIdx).trim();
00176       if (tableBetweenDeleteAndFrom.length() == 0)
00177         tableName = null;
00178       else
00179         tableName = tableBetweenDeleteAndFrom;
00180       fromIdx += 5; // 5 = "from".length()
00181     }
00182 
00183     sql = sql.substring(fromIdx).trim();
00184 
00185     // Look for the WHERE clause
00186     int whereIdx = sql.indexOf("where ");
00187 
00188     if (isCaseSensitive)
00189       sql = originalSQL.substring(originalSQL.length() - sql.length());
00190     if (tableName == null)
00191     { // It was not a DELETE t1 FROM xxx type of query
00192       if (whereIdx == -1)
00193         tableName = sql;
00194       else
00195         tableName = sql.substring(0, whereIdx).trim();
00196     }
00197 
00198     // Get the table on which DELETE occurs
00199     DatabaseTable t = schema.getTable(tableName, isCaseSensitive);
00200     if (t == null)
00201       throw new SQLException("Unknown table '" + tableName
00202           + "' in this DELETE statement: " + sqlQuery + "'");
00203 
00204     try
00205     {
00206       switch (granularity)
00207       {
00208         case ParsingGranularities.NO_PARSING :
00209           return;
00210         case ParsingGranularities.TABLE :
00211           break;
00212         case ParsingGranularities.COLUMN :
00213           from = getFromTables(tableName, schema);
00214           columns = getWhereColumns(sql.substring(whereIdx + 6).trim(), from);
00215 
00216           if (from != null)
00217           {
00218             // Convert 'from' to an ArrayList of String objects instead of
00219             // AliasedTables objects
00220             int size = from.size();
00221             ArrayList unaliased = new ArrayList(size);
00222             for (int i = 0; i < size; i++)
00223               unaliased.add(((AliasedDatabaseTable) from.get(i)).getTable()
00224                   .getName());
00225             from = unaliased;
00226           }
00227           break;
00228         case ParsingGranularities.COLUMN_UNIQUE :
00229           from = getFromTables(tableName, schema);
00230           columns = getWhereColumns(sql.substring(whereIdx + 6).trim(), from);
00231 
00232           if (from != null)
00233           {
00234             // Convert 'from' to an ArrayList of String objects instead of
00235             // AliasedTables objects
00236             int size = from.size();
00237             ArrayList unaliased = new ArrayList(size);
00238             for (int i = 0; i < size; i++)
00239               unaliased.add(((AliasedDatabaseTable) from.get(i)).getTable()
00240                   .getName());
00241             from = unaliased;
00242           }
00243           break;
00244         default :
00245           throw new SQLException("Unsupported parsing granularity: '"
00246               + granularity + "'");
00247       }
00248     }
00249     catch (SQLException e)
00250     {
00251       from = null;
00252       columns = null;
00253       whereValues = null;
00254       throw e;
00255     }
00256 
00257     isParsed = true;
00258   }
00259 
00260   /**
00261    * @see AbstractRequest#cloneParsing(AbstractRequest)
00262    */
00263   public void cloneParsing(AbstractRequest request)
00264   {
00265     if (!request.isParsed())
00266       return;
00267     cloneTableNameAndColumns((AbstractWriteRequest) request);
00268     isParsed = true;
00269   }
00270 
00271   /**
00272    * Extracts the tables from the given <code>FROM</code> clause and retrieves
00273    * their alias if any.
00274    * 
00275    * @param fromClause the <code>FROM</code> clause of the request (without
00276    *          the <code>FROM</code> keyword)
00277    * @param dbs the <code>DatabaseSchema</code> this request refers to
00278    * @return an <code>ArrayList</code> of <code>AliasedDatabaseTable</code>
00279    *         objects
00280    * @exception an <code>SQLException</code> if an error occurs
00281    */
00282   private ArrayList getFromTables(String fromClause, DatabaseSchema dbs)
00283       throws SQLException
00284   {
00285     StringTokenizer tables = new StringTokenizer(fromClause, ",");
00286     ArrayList result = new ArrayList(tables.countTokens());
00287     while (tables.hasMoreTokens())
00288     {
00289       String tableName = tables.nextToken().trim();
00290       // Check if the table has an alias
00291       // Example: SELECT x.price FROM item x
00292       String alias = null;
00293       int aliasIdx = tableName.indexOf(' ');
00294       if (aliasIdx != -1)
00295       {
00296         alias = tableName.substring(aliasIdx);
00297         tableName = tableName.substring(0, aliasIdx);
00298       }
00299 
00300       DatabaseTable table = dbs.getTable(tableName);
00301       if (table == null)
00302         throw new SQLException("Unknown table '" + tableName
00303             + "' in FROM clause of this DELETE statement: '" + sqlQuery + "'");
00304       result.add(new AliasedDatabaseTable(table, alias));
00305     }
00306 
00307     return result;
00308   }
00309 
00310   /**
00311    * Gets all the columns involved in the given <code>WHERE</code> clause.
00312    * <p>
00313    * The selected columns or tables must be found in the given
00314    * <code>ArrayList</code> of <code>AliasedDatabaseTable</code>
00315    * representing the <code>FROM</code> clause of the same request.
00316    * 
00317    * @param whereClause <code>WHERE</code> clause of the request (without the
00318    *          <code>WHERE</code> keyword)
00319    * @param aliasedFrom an <code>ArrayList</code> of
00320    *          <code>AliasedDatabaseTable</code>
00321    * @return an <code>ArrayList</code> of <code>TableColumn</code>
00322    */
00323   private ArrayList getWhereColumns(String whereClause, ArrayList aliasedFrom)
00324   {
00325     ArrayList result = new ArrayList(); // TableColumn objects
00326     ArrayList dbColumns = new ArrayList(); // DatabaseColumn objects
00327 
00328     // Instead of parsing the clause, we use a brutal force technique
00329     // and we try to directly identify every column name of each table.
00330     DatabaseColumn col;
00331     for (int i = 0; i < aliasedFrom.size(); i++)
00332     {
00333       DatabaseTable t = ((AliasedDatabaseTable) aliasedFrom.get(i)).getTable();
00334       ArrayList cols = t.getColumns();
00335       int size = cols.size();
00336       for (int j = 0; j < size; j++)
00337       {
00338         col = (DatabaseColumn) cols.get(j);
00339         // if pattern found and column not already in result, it's a dependency
00340         // !
00341         int matchIdx = whereClause.indexOf(col.getName());
00342         while (matchIdx > 0)
00343         {
00344           // Try to check that we got the full pattern and not a sub-pattern
00345           char beforePattern = whereClause.charAt(matchIdx - 1);
00346           // Everything should be lowercase here
00347           if (((beforePattern >= 'a') && (beforePattern <= 'z')) // Everything
00348               || (beforePattern == '_'))
00349             matchIdx = whereClause.indexOf(col.getName(), matchIdx + 1);
00350           else
00351             break;
00352         }
00353         if (matchIdx == -1)
00354           continue;
00355         result.add(new TableColumn(t.getName(), col.getName()));
00356         if (col.isUnique())
00357           pkValue = col.getName();
00358         dbColumns.add(col);
00359       }
00360     }
00361 
00362     return result;
00363   }
00364 
00365   /**
00366    * Returns an <code>ArrayList</code> of <code>String</code> objects
00367    * representing the values associated with the unique columns involved in this
00368    * request.
00369    * 
00370    * @return an <code>ArrayList</code> value
00371    */
00372   public ArrayList getValues()
00373   {
00374     return whereValues;
00375   }
00376 
00377   /**
00378    * Returns <code>true</code> if this query only deletes a single row.
00379    * 
00380    * @return a <code>boolean</code> value
00381    */
00382   public boolean isUnique()
00383   {
00384     return isUnique;
00385   }
00386 
00387   /**
00388    * @return <code>false</code>
00389    * @see org.objectweb.cjdbc.common.sql.AbstractWriteRequest#isInsert()
00390    */
00391   public boolean isInsert()
00392   {
00393     return false;
00394   }
00395 
00396   /**
00397    * @return <code>false</code>
00398    * @see org.objectweb.cjdbc.common.sql.AbstractWriteRequest#isUpdate()
00399    */
00400   public boolean isUpdate()
00401   {
00402     return false;
00403   }
00404 
00405   /**
00406    * @return <code>true</code>
00407    * @see org.objectweb.cjdbc.common.sql.AbstractWriteRequest#isDelete()
00408    */
00409   public boolean isDelete()
00410   {
00411     return true;
00412   }
00413 
00414   /**
00415    * @return <code>false</code>
00416    * @see org.objectweb.cjdbc.common.sql.AbstractWriteRequest#isCreate()
00417    */
00418   public boolean isCreate()
00419   {
00420     return false;
00421   }
00422 
00423   /**
00424    * @return <code>false</code>
00425    * @see org.objectweb.cjdbc.common.sql.AbstractWriteRequest#isDrop()
00426    */
00427   public boolean isDrop()
00428   {
00429     return false;
00430   }
00431 
00432   /**
00433    * Displays some debugging information about this request.
00434    */
00435   public void debug()
00436   {
00437     super.debug();
00438     System.out.println("Is unique: " + isUnique);
00439     if (tableName != null)
00440       System.out.println("Deleted table: " + tableName);
00441     else
00442       System.out.println("No information about deleted table");
00443 
00444     if (columns != null)
00445     {
00446       System.out.println("Columns columns:");
00447       for (int i = 0; i < columns.size(); i++)
00448         System.out.println("  "
00449             + ((TableColumn) columns.get(i)).getColumnName());
00450     }
00451     else
00452       System.out.println("No information about updated columns");
00453 
00454     System.out.println();
00455   }
00456 
00457   /**
00458    * @see org.objectweb.cjdbc.common.sql.AbstractWriteRequest#isAlter()
00459    */
00460   public boolean isAlter()
00461   {
00462     return false;
00463   }
00464 }

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