KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > objectweb > cjdbc > common > sql > UpdateRequest


1 /**
2  * C-JDBC: Clustered JDBC.
3  * Copyright (C) 2002-2004 French National Institute For Research In Computer
4  * Science And Control (INRIA).
5  * Contact: c-jdbc@objectweb.org
6  *
7  * This library is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU Lesser General Public License as published by the
9  * Free Software Foundation; either version 2.1 of the License, or any later
10  * version.
11  *
12  * This library is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
15  * for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with this library; if not, write to the Free Software Foundation,
19  * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
20  *
21  * Initial developer(s): Emmanuel Cecchet.
22  * Contributor(s): Mathieu Peltier.
23  */

24
25 package org.objectweb.cjdbc.common.sql;
26
27 import java.io.IOException JavaDoc;
28 import java.io.Serializable JavaDoc;
29 import java.sql.SQLException JavaDoc;
30 import java.util.ArrayList JavaDoc;
31 import java.util.HashMap JavaDoc;
32 import java.util.StringTokenizer JavaDoc;
33
34 import org.objectweb.cjdbc.common.sql.schema.DatabaseColumn;
35 import org.objectweb.cjdbc.common.sql.schema.DatabaseSchema;
36 import org.objectweb.cjdbc.common.sql.schema.DatabaseTable;
37 import org.objectweb.cjdbc.common.sql.schema.TableColumn;
38 import org.objectweb.cjdbc.common.stream.CJDBCInputStream;
39
40 /**
41  * An <code>UpdateRequest</code> is an SQL request with the following syntax:
42  *
43  * <pre>
44  * UPDATE table-name SET (column-name=expression[,column-name=expression]*) WHERE search-condition
45  * </pre>
46  *
47  * @author <a HREF="mailto:Emmanuel.Cecchet@inria.fr">Emmanuel Cecchet </a>
48  * @author <a HREF="mailto:Mathieu.Peltier@inrialpes.fr">Mathieu Peltier </a>
49  * @version 1.0
50  */

51 public class UpdateRequest extends AbstractWriteRequest implements Serializable JavaDoc
52 {
53   private static final long serialVersionUID = 1943340529813559587L;
54
55   /** <code>true</code> if this request updates a <code>UNIQUE</code> row. */
56   private transient boolean isUnique;
57
58   private transient HashMap JavaDoc updatedValues = null;
59
60   /**
61    * Creates a new <code>UpdateRequest</code> instance. The caller must give
62    * an SQL request, without any leading or trailing spaces and beginning with
63    * 'update ' (it will not be checked).
64    * <p>
65    * If the syntax is incorrect an exception is thrown.
66    *
67    * @param sqlQuery the SQL query
68    * @param escapeProcessing should the driver to escape processing before
69    * sending to the database ?
70    * @param timeout an <code>int</code> value
71    * @param lineSeparator the line separator used in the query
72    * @param schema a <code>DatabaseSchema</code> value
73    * @param granularity parsing granularity as defined in
74    * <code>ParsingGranularities</code>
75    * @param isCaseSensitive true if parsing is case sensitive
76    * @exception SQLException if an error occurs
77    */

78   public UpdateRequest(String JavaDoc sqlQuery, boolean escapeProcessing, int timeout,
79       String JavaDoc lineSeparator, DatabaseSchema schema, int granularity,
80       boolean isCaseSensitive) throws SQLException JavaDoc
81   {
82     this(sqlQuery, escapeProcessing, timeout, lineSeparator);
83     parse(schema, granularity, isCaseSensitive);
84   }
85
86   /**
87    * Creates a new <code>UpdateRequest</code> instance. The caller must give
88    * an SQL request, without any leading or trailing spaces and beginning with
89    * 'update ' (it will not be checked).
90    * <p>
91    * The request is not parsed but it can be done later by a call to
92    * {@link #parse(DatabaseSchema, int, boolean)}.
93    *
94    * @param sqlQuery the SQL query
95    * @param escapeProcessing should the driver to escape processing before
96    * sending to the database ?
97    * @param timeout an <code>int</code> value
98    * @param lineSeparator the line separator used in the query
99    * @see #parse
100    */

101   public UpdateRequest(String JavaDoc sqlQuery, boolean escapeProcessing, int timeout,
102       String JavaDoc lineSeparator)
103   {
104     super(sqlQuery, escapeProcessing, timeout, lineSeparator,
105         RequestType.UPDATE);
106   }
107
108   /**
109    * @see AbstractWriteRequest
110    */

111   public UpdateRequest(CJDBCInputStream in) throws IOException JavaDoc
112   {
113     super(in, RequestType.UPDATE);
114   }
115
116   /**
117    * Parses the SQL request and extract the selected columns and tables given
118    * the <code>DatabaseSchema</code> of the database targeted by this request.
119    * Determines also if this query only deletes a single row, and the equivalent
120    * <code>INSERT</code> statement.
121    * <p>
122    * An exception is thrown when the parsing fails. Warning, this method does
123    * not check the validity of the request. In particular, invalid request could
124    * be parsed without throwing an exception. However, valid SQL request should
125    * never throw an exception.
126    *
127    * @param schema a <code>DatabaseSchema</code> value
128    * @param granularity parsing granularity as defined in
129    * <code>ParsingGranularities</code>
130    * @param isCaseSensitive true if table name parsing is case sensitive
131    * @exception SQLException if the parsing fails
132    */

133   public void parse(DatabaseSchema schema, int granularity,
134       boolean isCaseSensitive) throws SQLException JavaDoc
135   {
136     if (granularity == ParsingGranularities.NO_PARSING)
137     {
138       isParsed = true;
139       return;
140     }
141
142     // Sanity check
143
if (schema == null)
144       throw new SQLException JavaDoc(
145           "Unable to parse request with an undefined database schema");
146
147     String JavaDoc whereClause = null;
148     isUnique = true;
149
150     String JavaDoc originalSQL = this.trimCarriageReturnAndTabs();
151     String JavaDoc sql = originalSQL.toLowerCase();
152
153     // Strip 'update '
154
sql = sql.substring(7).trim();
155
156     // Look for the SET or WHERE clause
157
int setIdx = sql.indexOf("set ");
158     int whereIdx = sql.indexOf("where ");
159     if (setIdx == -1)
160       throw new SQLException JavaDoc(
161           "Unable to find the SET keyword in this UPDATE statement: '"
162               + sqlQuery + "'");
163
164     if (isCaseSensitive)
165       sql = originalSQL.substring(7).trim();
166
167     if (whereIdx == -1)
168     {
169       whereIdx = sql.length();
170       isUnique = false;
171     }
172     else
173     {
174       whereClause = sql.substring(whereIdx + 5);
175       // 5 = "where".length(), do not trim or remove anything after
176
// else the following code will no more work
177
sql = sql.substring(0, whereIdx + 1).trim();
178     }
179
180     // Get the table on which UPDATE occurs
181
DatabaseTable t = schema.getTable(sql.substring(0, setIdx).trim(),
182         isCaseSensitive);
183     if (t == null)
184       throw new SQLException JavaDoc("Unknown table '" + tableName
185           + "' in this UPDATE statement: '" + sqlQuery + "'");
186     else
187       // Get the real name here (resolves case sentivity problems)
188
tableName = t.getName();
189
190     if (granularity > ParsingGranularities.TABLE)
191     {
192       // We have to get the affected columns
193
// Column names are separated by comas and are before a '=' symbol
194
StringTokenizer JavaDoc columnTokens = new StringTokenizer JavaDoc(sql.substring(
195           setIdx + 4, whereIdx), ",");
196       // 4=length("SET ")
197
columns = new ArrayList JavaDoc();
198       DatabaseColumn col = null;
199       while (columnTokens.hasMoreTokens())
200       {
201         String JavaDoc token = columnTokens.nextToken();
202         int eq = token.indexOf("=");
203         if (eq == -1)
204           continue;
205         token = token.substring(0, eq).trim();
206         col = t.getColumn(token, isCaseSensitive);
207         if (col == null)
208         {
209           tableName = null;
210           columns = null;
211           throw new SQLException JavaDoc("Unknown column name '" + token
212               + "' in this UPDATE statement: '" + sqlQuery + "'");
213         }
214         else
215           columns.add(new TableColumn(tableName, col.getName()));
216       }
217     }
218
219     isParsed = true;
220     if (!isUnique)
221       return;
222     else
223       isUnique = false;
224
225     if (granularity < ParsingGranularities.COLUMN_UNIQUE)
226       return;
227
228     // Prepare hashtable for updated values
229
updatedValues = new HashMap JavaDoc(columns.size());
230
231     // Check whether this update affects a single row or not
232
// Instead of parsing the clause, we use a brutal force technique
233
// and we try to directly identify every column name of the table.
234
DatabaseColumn col = null;
235     ArrayList JavaDoc cols = t.getColumns();
236     int size = cols.size();
237     for (int j = 0; j < size; j++)
238     {
239       col = (DatabaseColumn) cols.get(j);
240       String JavaDoc colName = col.getName();
241       // if pattern found and column not already in result, it's a dependency !
242
int matchIdx = whereClause.indexOf(colName);
243       while (matchIdx > 0)
244       {
245         // Try to check that we got the full pattern and not a sub-pattern
246
char beforePattern = whereClause.charAt(matchIdx - 1);
247         if (((beforePattern >= 'a') && (beforePattern <= 'z'))
248             || ((beforePattern >= 'A') && (beforePattern <= 'Z'))
249             || (beforePattern == '_'))
250           matchIdx = whereClause.indexOf(colName, matchIdx + 1);
251         else
252         { // Ok it's a good one, check if it is UNIQUE
253
isUnique = col.isUnique();
254           if (!isUnique)
255             return;
256           // Check if this UNIQUE columns stands in the left part of an
257
// equality
258
int eq = whereClause.indexOf("=", matchIdx);
259           if ((eq == -1)
260               || (whereClause.substring(matchIdx + colName.length(), eq).trim()
261                   .length() > 0))
262           {
263             isUnique = false;
264             return;
265           }
266           do
267           {
268             eq++; // Skip spaces
269
}
270           while (whereClause.charAt(eq) == ' ');
271
272           // Check if we have "..." or '...'
273
char startChar = whereClause.charAt(eq);
274           int end;
275           if ((startChar == '\'') || (startChar == '"'))
276           {
277             eq++;
278             do
279             { // Look for the end of the quote and take care of \' or \"
280
end = whereClause.indexOf(startChar, eq);
281             }
282             while (whereClause.charAt(end - 1) == '\\');
283           }
284           else
285           {
286             // It's a regular value just find the next comma
287
end = whereClause.indexOf(",", eq);
288             if (end == -1)
289               end = whereClause.length();
290           }
291           pkValue = whereClause.substring(eq, end);
292
293           matchIdx = whereClause.indexOf(colName, matchIdx + 1);
294         }
295       }
296     }
297
298     cacheable = RequestType.UNIQUE_CACHEABLE;
299
300     // Now get the values for each updated field
301
sql = originalSQL.substring(7).substring(0, whereIdx).trim();
302     if (!isCaseSensitive)
303       sql.toLowerCase();
304     int set = sql.toLowerCase().indexOf("set");
305     sql = sql.substring(set + 3).trim();
306
307     for (int j = 0; j < cols.size(); j++)
308     {
309       col = (DatabaseColumn) cols.get(j);
310       // if pattern found and column not already in result, it's a dependency !
311
String JavaDoc colName = (isCaseSensitive) ? col.getName() : col.getName()
312           .toLowerCase();
313       int matchIdx = sql.indexOf(colName);
314
315       while (matchIdx >= 0)
316       {
317         char afterPattern = sql.charAt(matchIdx + colName.length());
318         if ((afterPattern != '=') && (afterPattern != ' '))
319         {
320           matchIdx = sql.indexOf(colName, matchIdx + colName.length());
321           continue;
322         }
323
324         // Try to check that we got the full pattern and not a sub-pattern
325
char beforePattern = Character.CONTROL;
326         try
327         {
328           beforePattern = sql.charAt(matchIdx - 1);
329         }
330         catch (RuntimeException JavaDoc e)
331         {
332           // nothing
333
}
334         if (((beforePattern >= 'a') && (beforePattern <= 'z')) // Everything
335
// should be
336
// lowercase here
337
|| (beforePattern == '_'))
338           matchIdx = sql.indexOf(colName, matchIdx + 1);
339         else
340         { // Ok, it's good, get the value on the right part of the equality
341
int eq = sql.indexOf("=", matchIdx);
342           do
343           {
344             eq++; // Skip spaces
345
}
346           while (sql.charAt(eq) == ' ');
347
348           // Check if we have "..." or '...'
349
char startChar = sql.charAt(eq);
350           int end;
351           if ((startChar == '\'') || (startChar == '"'))
352           {
353             eq++;
354             do
355             { // Look for the end of the quote and take care of \' or \"
356
end = sql.indexOf(startChar, eq);
357             }
358             while (sql.charAt(end - 1) == '\\');
359           }
360           else
361           {
362             // It's a regular value just find the next comma
363
end = sql.indexOf(",", eq);
364             if (end == -1)
365               end = sql.length();
366           }
367           updatedValues.put(col.getName(), sql.substring(eq, end).trim());
368           break;
369         }
370       }
371     }
372   }
373
374   /**
375    * What are the updated values in this request
376    *
377    * @return a hashtable of (colname,value) or null if parsing granularity has
378    * stop computation
379    */

380   public HashMap JavaDoc getUpdatedValues()
381   {
382     return updatedValues;
383   }
384
385   /**
386    * @see AbstractRequest#cloneParsing(AbstractRequest)
387    */

388   public void cloneParsing(AbstractRequest request)
389   {
390     if (!request.isParsed())
391       return;
392     cloneTableNameAndColumns((AbstractWriteRequest) request);
393     updatedValues = ((UpdateRequest) request).getUpdatedValues();
394     isParsed = true;
395   }
396
397   /**
398    * Returns <code>true</code> as this request updates a <code>UNIQUE</code>
399    * row.
400    *
401    * @return <code>false</code>
402    */

403   public boolean isUnique()
404   {
405     return isUnique;
406   }
407
408   /**
409    * @see org.objectweb.cjdbc.common.sql.AbstractRequest#needsMacroProcessing()
410    */

411   public boolean needsMacroProcessing()
412   {
413     return true;
414   }
415
416   /**
417    * @see org.objectweb.cjdbc.common.sql.AbstractRequest#returnsResultSet()
418    */

419   public boolean returnsResultSet()
420   {
421     return false;
422   }
423
424   /**
425    * Displays some debugging information about this request.
426    */

427   public void debug()
428   {
429     super.debug();
430     if (tableName != null)
431       System.out.println("Updated table: " + tableName);
432     else
433       System.out.println("No information about updated table");
434
435     if (columns != null)
436     {
437       System.out.println("Updated columns:");
438       for (int i = 0; i < columns.size(); i++)
439         System.out.println(" "
440             + ((TableColumn) columns.get(i)).getColumnName());
441     }
442     else
443       System.out.println("No information about updated columns");
444
445     System.out.println("Unique update: " + isUnique);
446
447     System.out.println("");
448   }
449 }
Popular Tags