001: /**
002: * Sequoia: Database clustering technology.
003: * Copyright (C) 2002-2004 French National Institute For Research In Computer
004: * Science And Control (INRIA).
005: * Copyright (C) 2005 AmicoSoft, Inc. dba Emic Networks
006: * Copyright (C) 2005-2006 Continuent, Inc.
007: * Contact: sequoia@continuent.org
008: *
009: * Licensed under the Apache License, Version 2.0 (the "License");
010: * you may not use this file except in compliance with the License.
011: * You may obtain a copy of the License at
012: *
013: * http://www.apache.org/licenses/LICENSE-2.0
014: *
015: * Unless required by applicable law or agreed to in writing, software
016: * distributed under the License is distributed on an "AS IS" BASIS,
017: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018: * See the License for the specific language governing permissions and
019: * limitations under the License.
020: *
021: * Initial developer(s): Emmanuel Cecchet.
022: * Contributor(s): Julie Marguerite, Mathieu Peltier, Sara Bouchenak.
023: */package org.continuent.sequoia.controller.requests;
024:
025: import java.io.Serializable;
026: import java.sql.SQLException;
027: import java.util.ArrayList;
028: import java.util.StringTokenizer;
029: import java.util.TreeSet;
030:
031: import org.continuent.sequoia.common.i18n.Translate;
032: import org.continuent.sequoia.common.sql.schema.AliasedDatabaseTable;
033: import org.continuent.sequoia.common.sql.schema.DatabaseColumn;
034: import org.continuent.sequoia.common.sql.schema.DatabaseSchema;
035: import org.continuent.sequoia.common.sql.schema.DatabaseTable;
036: import org.continuent.sequoia.common.sql.schema.TableColumn;
037:
038: /**
039: * An <code>DeleteRequest</code> is an SQL request with the following syntax:
040: *
041: * <pre>DELETE [table1] FROM table1,table2,table3,... WHERE search-condition
042: * or DELETE t WHERE search-condition
043: * </pre>
044: *
045: * Note that DELETE from multiple tables are not supported but this is not part
046: * of the SQL standard.
047: *
048: * @author <a href="mailto:Emmanuel.Cecchet@inria.fr">Emmanuel Cecchet </a>
049: * @author <a href="mailto:Julie.Marguerite@inria.fr">Julie Marguerite </a>
050: * @author <a href="mailto:Mathieu.Peltier@inrialpes.fr">Mathieu Peltier </a>
051: * @author <a href="mailto:Sara.Bouchenak@epfl.ch">Sara Bouchenak </a>
052: * @version 1.0
053: */
054: public class DeleteRequest extends AbstractWriteRequest implements
055: Serializable {
056: private static final long serialVersionUID = 2321073659092135758L;
057:
058: /** <code>true</code> if this query only deletes a single row. */
059: protected transient boolean isUnique = false;
060:
061: /** <code>ArrayList</code> of <code>String</code> objects */
062: protected transient ArrayList from;
063:
064: /**
065: * <code>ArrayList</code> of values <code>String</code> associated with
066: * the unique columns involved in this delete query.
067: * <p>
068: * The <code>values</code> instance variable is only used when a <code>
069: * COLUMN_UNIQUE_DELETE</code>
070: * granularity is applied. Here, the DELETE request is UNIQUE: all columns of
071: * the WHERE clause are UNIQUE and used in the left part of an equality. When
072: * such a granularity is used, the <code>columns</code> instance variable
073: * contains only UNIQUE columns.
074: *
075: * @see org.continuent.sequoia.controller.cache.result.CachingGranularities
076: */
077: protected ArrayList whereValues;
078:
079: /**
080: * Creates a new <code>DeleteRequest</code> instance. The caller must give
081: * an SQL request, without any leading or trailing spaces and beginning with
082: * 'delete ' (it will not be checked).
083: * <p>
084: * The request is not parsed but it can be done later by a call to
085: * {@link #parse(DatabaseSchema, int, boolean)}.
086: *
087: * @param sqlQuery the SQL request
088: * @param escapeProcessing should the driver to escape processing before
089: * sending to the database ?
090: * @param timeout an <code>int</code> value
091: * @param lineSeparator the line separator used in the query
092: * @see #parse
093: */
094: public DeleteRequest(String sqlQuery, boolean escapeProcessing,
095: int timeout, String lineSeparator) {
096: super (sqlQuery, escapeProcessing, timeout, lineSeparator,
097: RequestType.DELETE);
098: }
099:
100: /**
101: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersAggregateList()
102: */
103: public boolean altersAggregateList() {
104: return false;
105: }
106:
107: /**
108: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersDatabaseCatalog()
109: */
110: public boolean altersDatabaseCatalog() {
111: return false;
112: }
113:
114: /**
115: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersDatabaseSchema()
116: */
117: public boolean altersDatabaseSchema() {
118: return false;
119: }
120:
121: /**
122: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersMetadataCache()
123: */
124: public boolean altersMetadataCache() {
125: return false;
126: }
127:
128: /**
129: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersQueryResultCache()
130: */
131: public boolean altersQueryResultCache() {
132: return true;
133: }
134:
135: /**
136: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersSomething()
137: */
138: public boolean altersSomething() {
139: return true;
140: }
141:
142: /**
143: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersStoredProcedureList()
144: */
145: public boolean altersStoredProcedureList() {
146: return false;
147: }
148:
149: /**
150: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersUserDefinedTypes()
151: */
152: public boolean altersUserDefinedTypes() {
153: return false;
154: }
155:
156: /**
157: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersUsers()
158: */
159: public boolean altersUsers() {
160: return false;
161: }
162:
163: /**
164: * @see AbstractRequest#cloneParsing(AbstractRequest)
165: */
166: public void cloneParsing(AbstractRequest request) {
167: if (!request.isParsed())
168: return;
169: cloneTableNameAndColumns((AbstractWriteRequest) request);
170: isParsed = true;
171: }
172:
173: /**
174: * Extracts the tables from the given <code>FROM</code> clause and retrieves
175: * their alias if any.
176: *
177: * @param fromClause the <code>FROM</code> clause of the request (without
178: * the <code>FROM</code> keyword)
179: * @param dbs the <code>DatabaseSchema</code> this request refers to
180: * @return an <code>ArrayList</code> of <code>AliasedDatabaseTable</code>
181: * objects
182: * @exception an <code>SQLException</code> if an error occurs
183: */
184: private ArrayList getFromTables(String fromClause,
185: DatabaseSchema dbs) throws SQLException {
186: StringTokenizer tables = new StringTokenizer(fromClause, ",");
187: ArrayList result = new ArrayList(tables.countTokens());
188: while (tables.hasMoreTokens()) {
189: String dropTableName = tables.nextToken().trim();
190: // Check if the table has an alias
191: // Example: SELECT x.price FROM item x
192: String alias = null;
193: int aliasIdx = dropTableName.indexOf(' ');
194: if (aliasIdx != -1) {
195: alias = dropTableName.substring(aliasIdx);
196: dropTableName = dropTableName.substring(0, aliasIdx);
197: }
198:
199: DatabaseTable table = dbs.getTable(dropTableName);
200: if (table == null)
201: throw new SQLException(
202: "Unknown table '"
203: + dropTableName
204: + "' in FROM clause of this DELETE statement: '"
205: + sqlQueryOrTemplate + "'");
206: result.add(new AliasedDatabaseTable(table, alias));
207: }
208:
209: return result;
210: }
211:
212: /**
213: * Gets all the columns involved in the given <code>WHERE</code> clause.
214: * <p>
215: * The selected columns or tables must be found in the given
216: * <code>ArrayList</code> of <code>AliasedDatabaseTable</code>
217: * representing the <code>FROM</code> clause of the same request.
218: *
219: * @param whereClause <code>WHERE</code> clause of the request (without the
220: * <code>WHERE</code> keyword)
221: * @param aliasedFrom an <code>ArrayList</code> of
222: * <code>AliasedDatabaseTable</code>
223: * @return an <code>ArrayList</code> of <code>TableColumn</code>
224: */
225: private ArrayList getWhereColumns(String whereClause,
226: ArrayList aliasedFrom) {
227: ArrayList result = new ArrayList(); // TableColumn objects
228: ArrayList dbColumns = new ArrayList(); // DatabaseColumn objects
229:
230: // Instead of parsing the clause, we use a brutal force technique
231: // and we try to directly identify every column name of each table.
232: DatabaseColumn col;
233: for (int i = 0; i < aliasedFrom.size(); i++) {
234: DatabaseTable t = ((AliasedDatabaseTable) aliasedFrom
235: .get(i)).getTable();
236: ArrayList cols = t.getColumns();
237: int size = cols.size();
238: for (int j = 0; j < size; j++) {
239: col = (DatabaseColumn) cols.get(j);
240: // if pattern found and column not already in result, it's a dependency
241: // !
242: int matchIdx = whereClause.indexOf(col.getName());
243: while (matchIdx > 0) {
244: // Try to check that we got the full pattern and not a sub-pattern
245: char beforePattern = whereClause
246: .charAt(matchIdx - 1);
247: // Everything should be lowercase here
248: if (((beforePattern >= 'a') && (beforePattern <= 'z')) // Everything
249: || (beforePattern == '_'))
250: matchIdx = whereClause.indexOf(col.getName(),
251: matchIdx + 1);
252: else
253: break;
254: }
255: if (matchIdx == -1)
256: continue;
257: result.add(new TableColumn(t.getName(), col.getName()));
258: if (col.isUnique())
259: pkValue = col.getName();
260: dbColumns.add(col);
261: }
262: }
263:
264: return result;
265: }
266:
267: /**
268: * Returns an <code>ArrayList</code> of <code>String</code> objects
269: * representing the values associated with the unique columns involved in this
270: * request.
271: *
272: * @return an <code>ArrayList</code> value
273: */
274: public ArrayList getValues() {
275: return whereValues;
276: }
277:
278: /**
279: * Returns <code>true</code> if this query only deletes a single row.
280: *
281: * @return a <code>boolean</code> value
282: */
283: public boolean isUnique() {
284: return isUnique;
285: }
286:
287: /**
288: * @see org.continuent.sequoia.controller.requests.AbstractRequest#needsMacroProcessing()
289: */
290: public boolean needsMacroProcessing() {
291: return true;
292: }
293:
294: /**
295: * Parses the SQL request and extracts the selected columns and tables given
296: * the <code>DatabaseSchema</code> of the database targeted by this request.
297: * <p>
298: * An exception is thrown when the parsing fails. Warning, this method does
299: * not check the validity of the request. In particular, invalid request could
300: * be parsed without throwing an exception. However, valid SQL request should
301: * never throw an exception.
302: *
303: * @param schema a <code>DatabaseSchema</code> value
304: * @param granularity parsing granularity as defined in
305: * <code>ParsingGranularities</code>
306: * @param isCaseSensitive if parsing must be case sensitive
307: * @exception SQLException if the parsing fails
308: */
309: public void parse(DatabaseSchema schema, int granularity,
310: boolean isCaseSensitive) throws SQLException {
311: if (granularity == ParsingGranularities.NO_PARSING) {
312: isParsed = true;
313: return;
314: }
315:
316: String originalSQL = this .trimCarriageReturnAndTabs();
317: String sql = originalSQL.toLowerCase();
318:
319: int fromIdx = sql.indexOf("from ");
320: if (fromIdx == -1) {
321: // For queries like: DELETE t WHERE ... used by Oracle
322: fromIdx = 6; // 6 = "delete".length()
323: } else {
324: // Syntax is usually DELETE FROM t WHERE ... but it can be
325: // DELETE t1 FROM t1,t2,.... WHERE ...
326: // If there is something between DELETE and FROM, tableName will use this
327: // name but the FROM clause will have all tables.
328: String tableBetweenDeleteAndFrom;
329: if (isCaseSensitive)
330: tableBetweenDeleteAndFrom = originalSQL.substring(6,
331: fromIdx).trim();
332: else
333: tableBetweenDeleteAndFrom = sql.substring(6, fromIdx)
334: .trim();
335: if (tableBetweenDeleteAndFrom.length() == 0)
336: tableName = null;
337: else
338: tableName = tableBetweenDeleteAndFrom;
339: fromIdx += 5; // 5 = "from".length()
340: }
341:
342: sql = sql.substring(fromIdx).trim();
343:
344: // Look for the WHERE clause
345: int whereIdx = sql.indexOf("where ");
346:
347: if (isCaseSensitive)
348: sql = originalSQL.substring(originalSQL.length()
349: - sql.length());
350: if (tableName == null) { // It was not a DELETE t1 FROM xxx type of query
351: if (whereIdx == -1)
352: tableName = sql;
353: else
354: tableName = sql.substring(0, whereIdx).trim();
355: }
356:
357: if (schema == null) {
358: writeLockedTables = new TreeSet();
359: writeLockedTables.add(tableName);
360: isParsed = true;
361: return;
362: }
363:
364: // Get the table on which DELETE occurs
365: DatabaseTable t = schema.getTable(tableName, isCaseSensitive);
366: if (t == null)
367: throw new SQLException("Unknown table '" + tableName
368: + "' in this DELETE statement: "
369: + sqlQueryOrTemplate + "'");
370: else
371: // Get the real name here (resolves case sentivity problems)
372: tableName = t.getName();
373:
374: writeLockedTables = new TreeSet();
375: writeLockedTables.add(tableName);
376: addDependingTables(schema, writeLockedTables);
377:
378: try {
379: switch (granularity) {
380: case ParsingGranularities.NO_PARSING:
381: return;
382: case ParsingGranularities.TABLE:
383: break;
384: case ParsingGranularities.COLUMN:
385: from = getFromTables(tableName, schema);
386: columns = getWhereColumns(sql.substring(whereIdx + 6)
387: .trim(), from);
388:
389: if (from != null) {
390: // Convert 'from' to an ArrayList of String objects instead of
391: // AliasedTables objects
392: int size = from.size();
393: ArrayList unaliased = new ArrayList(size);
394: for (int i = 0; i < size; i++)
395: unaliased.add(((AliasedDatabaseTable) from
396: .get(i)).getTable().getName());
397: from = unaliased;
398: }
399: break;
400: case ParsingGranularities.COLUMN_UNIQUE:
401: from = getFromTables(tableName, schema);
402: columns = getWhereColumns(sql.substring(whereIdx + 6)
403: .trim(), from);
404:
405: if (from != null) {
406: // Convert 'from' to an ArrayList of String objects instead of
407: // AliasedTables objects
408: int size = from.size();
409: ArrayList unaliased = new ArrayList(size);
410: for (int i = 0; i < size; i++)
411: unaliased.add(((AliasedDatabaseTable) from
412: .get(i)).getTable().getName());
413: from = unaliased;
414: }
415: break;
416: default:
417: throw new SQLException(
418: "Unsupported parsing granularity: '"
419: + granularity + "'");
420: }
421: } catch (SQLException e) {
422: from = null;
423: columns = null;
424: whereValues = null;
425: throw e;
426: }
427:
428: isParsed = true;
429: }
430:
431: /**
432: * Does this request returns a ResultSet?
433: *
434: * @return false
435: */
436: public boolean returnsResultSet() {
437: return false;
438: }
439:
440: /**
441: * Displays some debugging information about this request.
442: */
443: public void debug() {
444: super .debug();
445: System.out.println("Is unique: " + isUnique);
446: if (tableName != null)
447: System.out.println("Deleted table: " + tableName);
448: else
449: System.out.println("No information about deleted table");
450:
451: if (columns != null) {
452: System.out.println("Columns columns:");
453: for (int i = 0; i < columns.size(); i++)
454: System.out.println(" "
455: + ((TableColumn) columns.get(i))
456: .getColumnName());
457: } else
458: System.out.println("No information about updated columns");
459:
460: System.out.println();
461: }
462:
463: /**
464: * @see org.continuent.sequoia.controller.requests.AbstractRequest#getParsingResultsAsString()
465: */
466: public String getParsingResultsAsString() {
467: StringBuffer sb = new StringBuffer(super
468: .getParsingResultsAsString());
469: sb.append(Translate.get("request.delete.single.row", isUnique));
470: if (from != null && from.size() > 0) {
471: sb.append(Translate.get("request.from.tables"));
472: for (int i = 0; i < from.size(); i++) {
473: sb.append(Translate.get("request.from.table", from
474: .get(i)));
475: }
476: }
477: if (whereValues != null && whereValues.size() > 0) {
478: sb.append(Translate.get("request.where.tables"));
479: for (int i = 0; i < whereValues.size(); i++) {
480: sb.append(Translate.get("request.where.table",
481: whereValues.get(i)));
482: }
483: }
484: return sb.toString();
485: }
486: }
|