KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > continuent > sequoia > controller > requests > UpdateRequest


1 /**
2  * Sequoia: Database clustering technology.
3  * Copyright (C) 2002-2004 French National Institute For Research In Computer
4  * Science And Control (INRIA).
5  * Copyright (C) 2005-2006 Continuent, Inc.
6  * Contact: sequoia@continuent.org
7  *
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  *
20  * Initial developer(s): Emmanuel Cecchet.
21  * Contributor(s): Mathieu Peltier.
22  */

23
24 package org.continuent.sequoia.controller.requests;
25
26 import java.io.Serializable JavaDoc;
27 import java.sql.SQLException JavaDoc;
28 import java.util.ArrayList JavaDoc;
29 import java.util.HashMap JavaDoc;
30 import java.util.StringTokenizer JavaDoc;
31 import java.util.TreeSet JavaDoc;
32
33 import org.continuent.sequoia.common.i18n.Translate;
34 import org.continuent.sequoia.controller.semantic.SemanticBehavior;
35 import org.continuent.sequoia.controller.sql.schema.DatabaseColumn;
36 import org.continuent.sequoia.controller.sql.schema.DatabaseSchema;
37 import org.continuent.sequoia.controller.sql.schema.DatabaseTable;
38 import org.continuent.sequoia.controller.sql.schema.TableColumn;
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   protected transient boolean isUnique;
57
58   protected 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    * The request is not parsed but it can be done later by a call to
66    * {@link #parse(DatabaseSchema, int, boolean)}.
67    *
68    * @param sqlQuery the SQL query
69    * @param escapeProcessing should the driver to escape processing before
70    * sending to the database ?
71    * @param timeout an <code>int</code> value
72    * @param lineSeparator the line separator used in the query
73    * @see #parse
74    */

75   public UpdateRequest(String JavaDoc sqlQuery, boolean escapeProcessing, int timeout,
76       String JavaDoc lineSeparator)
77   {
78     super(sqlQuery, escapeProcessing, timeout, lineSeparator,
79         RequestType.UPDATE);
80   }
81
82   /**
83    * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersAggregateList()
84    */

85   public boolean altersAggregateList()
86   {
87     return false;
88   }
89
90   /**
91    * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersDatabaseCatalog()
92    */

93   public boolean altersDatabaseCatalog()
94   {
95     return false;
96   }
97
98   /**
99    * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersDatabaseSchema()
100    */

101   public boolean altersDatabaseSchema()
102   {
103     return false;
104   }
105
106   /**
107    * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersMetadataCache()
108    */

109   public boolean altersMetadataCache()
110   {
111     return false;
112   }
113
114   /**
115    * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersQueryResultCache()
116    */

117   public boolean altersQueryResultCache()
118   {
119     return true;
120   }
121
122   /**
123    * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersSomething()
124    */

125   public boolean altersSomething()
126   {
127     return true;
128   }
129
130   /**
131    * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersStoredProcedureList()
132    */

133   public boolean altersStoredProcedureList()
134   {
135     return false;
136   }
137
138   /**
139    * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersUserDefinedTypes()
140    */

141   public boolean altersUserDefinedTypes()
142   {
143     return false;
144   }
145
146   /**
147    * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersUsers()
148    */

149   public boolean altersUsers()
150   {
151     return false;
152   }
153
154   /**
155    * @see AbstractRequest#cloneParsing(AbstractRequest)
156    */

157   public void cloneParsing(AbstractRequest request)
158   {
159     if (!request.isParsed())
160       return;
161     cloneParsingCommons(request);
162     UpdateRequest updateRequest = (UpdateRequest) request;
163     cloneTableNameAndColumns(updateRequest);
164     updatedValues = updateRequest.getUpdatedValues();
165     isParsed = true;
166   }
167
168   /**
169    * What are the updated values in this request
170    *
171    * @return a hashtable of (colname,value) or null if parsing granularity has
172    * stop computation
173    */

174   public HashMap JavaDoc getUpdatedValues()
175   {
176     return updatedValues;
177   }
178
179   /**
180    * Returns <code>true</code> as this request updates a <code>UNIQUE</code>
181    * row.
182    *
183    * @return <code>false</code>
184    */

185   public boolean isUnique()
186   {
187     return isUnique;
188   }
189
190   /**
191    * @see org.continuent.sequoia.controller.requests.AbstractRequest#needsMacroProcessing()
192    */

193   public boolean needsMacroProcessing()
194   {
195     return true;
196   }
197
198   /**
199    * Parses the SQL request and extract the selected columns and tables given
200    * the <code>DatabaseSchema</code> of the database targeted by this request.
201    * Determines also if this query only deletes a single row, and the equivalent
202    * <code>INSERT</code> statement.
203    * <p>
204    * An exception is thrown when the parsing fails. Warning, this method does
205    * not check the validity of the request. In particular, invalid request could
206    * be parsed without throwing an exception. However, valid SQL request should
207    * never throw an exception.
208    *
209    * @param schema a <code>DatabaseSchema</code> value
210    * @param granularity parsing granularity as defined in
211    * <code>ParsingGranularities</code>
212    * @param isCaseSensitive true if table name parsing is case sensitive
213    * @exception SQLException if the parsing fails
214    */

215   public void parse(DatabaseSchema schema, int granularity,
216       boolean isCaseSensitive) throws SQLException JavaDoc
217   {
218     if (granularity == ParsingGranularities.NO_PARSING)
219     {
220       isParsed = true;
221       return;
222     }
223
224     try
225     {
226       String JavaDoc whereClause = null;
227       isUnique = true;
228
229       String JavaDoc originalSQL = this.trimCarriageReturnAndTabs();
230       String JavaDoc sql = originalSQL.toLowerCase();
231
232       // Strip 'update '
233
sql = sql.substring(7).trim();
234
235       // Look for the SET or WHERE clause
236
int setIdx = sql.indexOf(" set ");
237       if (setIdx == -1)
238         throw new SQLException JavaDoc(
239             "Unable to find the SET keyword in this UPDATE statement: '"
240                 + sqlQueryOrTemplate + "'");
241
242       int whereIdx = sql.indexOf(" where ");
243       if (whereIdx == -1)
244         whereIdx = sql.indexOf(")where ");
245
246       if (isCaseSensitive)
247         sql = originalSQL.substring(7).trim();
248
249       if (whereIdx == -1)
250       {
251         whereIdx = sql.length();
252         isUnique = false;
253       }
254       else
255       {
256         whereIdx++;
257         // whereIdx now points to the 'w' and not the preceding
258
// character ' ' or ')'
259
whereClause = sql.substring(whereIdx + 5);
260         // 5 = "where".length(), do not trim or remove anything after
261
// else the following code will no more work
262
sql = sql.substring(0, whereIdx + 1).trim();
263       }
264
265       tableName = sql.substring(0, setIdx).trim();
266
267       if (schema == null)
268       {
269         writeLockedTables = new TreeSet JavaDoc();
270         writeLockedTables.add(tableName);
271         isParsed = true;
272         return;
273       }
274
275       // Get the table on which UPDATE occurs
276
DatabaseTable t = schema.getTable(tableName, isCaseSensitive);
277       if (t == null)
278         throw new SQLException JavaDoc("Unknown table '" + tableName
279             + "' in this UPDATE statement: '" + sqlQueryOrTemplate + "'");
280       else
281         // Get the real name here (resolves case sentivity problems)
282
tableName = t.getName();
283
284       // Lock this table in write
285
writeLockedTables = new TreeSet JavaDoc();
286       writeLockedTables.add(tableName);
287       addDependingTables(schema, writeLockedTables);
288
289       if (granularity > ParsingGranularities.TABLE)
290       {
291         // We have to get the affected columns
292
// Column names are separated by comas and are before a '=' symbol
293
StringTokenizer JavaDoc columnTokens = new StringTokenizer JavaDoc(sql.substring(
294             setIdx + 5, whereIdx), ",");
295         // 5 = length(" SET ")
296
columns = new ArrayList JavaDoc();
297         DatabaseColumn col = null;
298         while (columnTokens.hasMoreTokens())
299         {
300           String JavaDoc token = columnTokens.nextToken();
301           int eq = token.indexOf("=");
302           if (eq == -1)
303             continue;
304           token = token.substring(0, eq).trim();
305           col = t.getColumn(token, isCaseSensitive);
306           if (col == null)
307           {
308             tableName = null;
309             columns = null;
310             throw new SQLException JavaDoc("Unknown column name '" + token
311                 + "' in this UPDATE statement: '" + sqlQueryOrTemplate + "'");
312           }
313           else
314             columns.add(new TableColumn(tableName, col.getName()));
315         }
316       }
317
318       isParsed = true;
319       if (!isUnique)
320         return;
321       else
322         isUnique = false;
323
324       if (granularity < ParsingGranularities.COLUMN_UNIQUE)
325         return;
326
327       // Prepare hashtable for updated values
328
updatedValues = new HashMap JavaDoc(columns.size());
329
330       // Check whether this update affects a single row or not
331
// Instead of parsing the clause, we use a brutal force technique
332
// and we try to directly identify every column name of the table.
333
DatabaseColumn col = null;
334       ArrayList JavaDoc cols = t.getColumns();
335       int size = cols.size();
336       for (int j = 0; j < size; j++)
337       {
338         col = (DatabaseColumn) cols.get(j);
339         String JavaDoc colName = col.getName();
340         // if pattern found and column not already in result, it's a dependency
341
// !
342
int matchIdx = whereClause.indexOf(colName);
343         while (matchIdx > 0)
344         {
345           // Try to check that we got the full pattern and not a sub-pattern
346
char beforePattern = whereClause.charAt(matchIdx - 1);
347           if (((beforePattern >= 'a') && (beforePattern <= 'z'))
348               || ((beforePattern >= 'A') && (beforePattern <= 'Z'))
349               || (beforePattern == '_'))
350             matchIdx = whereClause.indexOf(colName, matchIdx + 1);
351           else
352           { // Ok it's a good one, check if it is UNIQUE
353
isUnique = col.isUnique();
354             if (!isUnique)
355               return;
356             // Check if this UNIQUE columns stands in the left part of an
357
// equality
358
int eq = whereClause.indexOf("=", matchIdx);
359             if ((eq == -1)
360                 || (whereClause.substring(matchIdx + colName.length(), eq)
361                     .trim().length() > 0))
362             {
363               isUnique = false;
364               return;
365             }
366             do
367             {
368               eq++; // Skip spaces
369
}
370             while (whereClause.charAt(eq) == ' ');
371
372             // Check if we have "..." or '...'
373
char startChar = whereClause.charAt(eq);
374             int end;
375             if ((startChar == '\'') || (startChar == '"'))
376             {
377               eq++;
378               do
379               { // Look for the end of the quote and take care of \' or \"
380
end = whereClause.indexOf(startChar, eq);
381               }
382               while (whereClause.charAt(end - 1) == '\\');
383             }
384             else
385             {
386               // It's a regular value just find the next comma
387
end = whereClause.indexOf(",", eq);
388               if (end == -1)
389                 end = whereClause.length();
390             }
391             pkValue = whereClause.substring(eq, end);
392
393             matchIdx = whereClause.indexOf(colName, matchIdx + 1);
394           }
395         }
396       }
397
398       cacheable = RequestType.UNIQUE_CACHEABLE;
399
400       // Now get the values for each updated field
401
sql = originalSQL.substring(7).substring(0, whereIdx).trim();
402       if (!isCaseSensitive)
403         sql = sql.toLowerCase();
404       int set = sql.toLowerCase().indexOf("set");
405       sql = sql.substring(set + 3).trim();
406
407       for (int j = 0; j < cols.size(); j++)
408       {
409         col = (DatabaseColumn) cols.get(j);
410         // if pattern found and column not already in result, it's a dependency
411
// !
412
String JavaDoc colName = (isCaseSensitive) ? col.getName() : col.getName()
413             .toLowerCase();
414         int matchIdx = sql.indexOf(colName);
415
416         while (matchIdx >= 0)
417         {
418           char afterPattern = sql.charAt(matchIdx + colName.length());
419           if ((afterPattern != '=') && (afterPattern != ' '))
420           {
421             matchIdx = sql.indexOf(colName, matchIdx + colName.length());
422             continue;
423           }
424
425           // Try to check that we got the full pattern and not a sub-pattern
426
char beforePattern = Character.CONTROL;
427           try
428           {
429             beforePattern = sql.charAt(matchIdx - 1);
430           }
431           catch (RuntimeException JavaDoc e)
432           {
433             // nothing
434
}
435           if (((beforePattern >= 'a') && (beforePattern <= 'z')) // Everything
436
// should be
437
// lowercase here
438
|| (beforePattern == '_'))
439             matchIdx = sql.indexOf(colName, matchIdx + 1);
440           else
441           { // Ok, it's good, get the value on the right part of the equality
442
int eq = sql.indexOf("=", matchIdx);
443             do
444             {
445               eq++; // Skip spaces
446
}
447             while (sql.charAt(eq) == ' ');
448
449             // Check if we have "..." or '...'
450
char startChar = sql.charAt(eq);
451             int end;
452             if ((startChar == '\'') || (startChar == '"'))
453             {
454               eq++;
455               do
456               { // Look for the end of the quote and take care of \' or \"
457
end = sql.indexOf(startChar, eq);
458               }
459               while (sql.charAt(end - 1) == '\\');
460             }
461             else
462             {
463               // It's a regular value just find the next comma
464
end = sql.indexOf(",", eq);
465               if (end == -1)
466                 end = sql.length();
467             }
468             updatedValues.put(col.getName(), sql.substring(eq, end).trim());
469             break;
470           }
471         }
472       }
473     }
474     finally
475     {
476       if (isParsed)
477       {
478         setSemantic(new SemanticBehavior(null, writeLockedTables, null,
479             altersDatabaseSchema(), altersMetadataCache(),
480             altersQueryResultCache(), altersUsers(), isReadOnly,
481             needsMacroProcessing(), SemanticBehavior.SERIALIZABLE_ORDER,
482             requiresConnectionPoolFlush
483                 ? SemanticBehavior.FLUSH_ALL_USERS
484                 : SemanticBehavior.FLUSH_NONE));
485       }
486     }
487   }
488
489   /**
490    * Does this request returns a ResultSet?
491    *
492    * @return false
493    */

494   public boolean returnsResultSet()
495   {
496     return false;
497   }
498
499   /**
500    * @see org.continuent.sequoia.controller.requests.AbstractRequest#getParsingResultsAsString()
501    */

502   public String JavaDoc getParsingResultsAsString()
503   {
504     StringBuffer JavaDoc sb = new StringBuffer JavaDoc(super.getParsingResultsAsString());
505     sb.append(Translate.get("request.update.unique", isUnique));
506     if (updatedValues != null && updatedValues.size() > 0)
507     {
508       sb.append(Translate.get("request.update.values"));
509       for (int i = 0; i < updatedValues.size(); i++)
510       {
511         sb
512             .append(Translate.get("request.update.value", new String JavaDoc[]{
513                 updatedValues.keySet().toArray()[i].toString(),
514                 updatedValues.get(updatedValues.keySet().toArray()[i])
515                     .toString()}));
516       }
517     }
518     sb.append(Translate.get("request.alters",
519         new String JavaDoc[]{String.valueOf(altersAggregateList()),
520             String.valueOf(altersDatabaseCatalog()),
521             String.valueOf(altersDatabaseSchema()),
522             String.valueOf(altersMetadataCache()),
523             String.valueOf(altersQueryResultCache()),
524             String.valueOf(altersSomething()),
525             String.valueOf(altersStoredProcedureList()),
526             String.valueOf(altersUserDefinedTypes()),
527             String.valueOf(altersUsers())}));
528     return sb.toString();
529   }
530
531   /**
532    * Displays some debugging information about this request.
533    */

534   public void debug()
535   {
536     super.debug();
537     if (tableName != null)
538       System.out.println("Updated table: " + tableName);
539     else
540       System.out.println("No information about updated table");
541
542     if (columns != null)
543     {
544       System.out.println("Updated columns:");
545       for (int i = 0; i < columns.size(); i++)
546         System.out.println(" "
547             + ((TableColumn) columns.get(i)).getColumnName());
548     }
549     else
550       System.out.println("No information about updated columns");
551
552     System.out.println("Unique update: " + isUnique);
553
554     System.out.println("");
555   }
556 }
Popular Tags