0001: /**
0002: * Sequoia: Database clustering technology.
0003: * Copyright (C) 2002-2004 French National Institute For Research In Computer
0004: * Science And Control (INRIA).
0005: * Copyright (C) 2005 AmicoSoft, Inc. dba Emic Networks
0006: * Contact: sequoia@continuent.org
0007: *
0008: * Licensed under the Apache License, Version 2.0 (the "License");
0009: * you may not use this file except in compliance with the License.
0010: * You may obtain a copy of the License at
0011: *
0012: * http://www.apache.org/licenses/LICENSE-2.0
0013: *
0014: * Unless required by applicable law or agreed to in writing, software
0015: * distributed under the License is distributed on an "AS IS" BASIS,
0016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0017: * See the License for the specific language governing permissions and
0018: * limitations under the License.
0019: *
0020: * Initial developer(s): Emmanuel Cecchet.
0021: * Contributor(s): Julie Marguerite, Mathieu Peltier, Sara Bouchenak.
0022: */package org.continuent.sequoia.controller.requests;
0023:
0024: import java.io.Serializable;
0025: import java.sql.SQLException;
0026: import java.util.ArrayList;
0027: import java.util.Collection;
0028: import java.util.ConcurrentModificationException;
0029: import java.util.Hashtable;
0030: import java.util.Iterator;
0031: import java.util.StringTokenizer;
0032:
0033: import org.continuent.sequoia.common.i18n.Translate;
0034: import org.continuent.sequoia.common.sql.schema.AliasedDatabaseTable;
0035: import org.continuent.sequoia.common.sql.schema.DatabaseColumn;
0036: import org.continuent.sequoia.common.sql.schema.DatabaseSchema;
0037: import org.continuent.sequoia.common.sql.schema.DatabaseTable;
0038: import org.continuent.sequoia.common.sql.schema.TableColumn;
0039:
0040: /**
0041: * A <code>SelectRequest</code> is an SQL request returning a
0042: * {@link java.sql.ResultSet}. It may also have database side-effects.
0043: * <p>
0044: * It has the following syntax:
0045: *
0046: * <pre>
0047: * SELECT [ALL|DISTINCT] select-item[,select-item]*
0048: * FROM table-specification[,table-specification]*
0049: * [WHERE search-condition]
0050: * [GROUP BY grouping-column[,grouping-column]]
0051: * [HAVING search-condition]
0052: * [ORDER BY sort-specification[,sort-specification]]
0053: * [LIMIT ignored]
0054: * </pre>
0055: *
0056: * Note that table-specification in the <code>FROM</code> clause can be a
0057: * sub-select. Everything after the end of the <code>WHERE</code> clause is
0058: * ignored.
0059: *
0060: * @author <a href="mailto:Emmanuel.Cecchet@inria.fr">Emmanuel Cecchet </a>
0061: * @author <a href="mailto:Julie.Marguerite@inria.fr">Julie Marguerite </a>
0062: * @author <a href="mailto:Mathieu.Peltier@inrialpes.fr">Mathieu Peltier </a>
0063: * @author <a href="mailto:Sara.Bouchenak@epfl.ch">Sara Bouchenak </a>
0064: * @version 1.0
0065: */
0066: public class SelectRequest extends AbstractRequest implements
0067: Serializable {
0068: private static final long serialVersionUID = 6498520472410320514L;
0069:
0070: /**
0071: * Set to true if this SelectRequest must be broadcasted on the cluster
0072: * (useful for queries like SELECT FOR UPDATE
0073: */
0074: private boolean mustBroadcast = false;
0075:
0076: /** <code>ArrayList</code> of <code>TableColumn</code> objects. */
0077: protected transient ArrayList select;
0078:
0079: /** <code>ArrayList</code> of <code>String</code> objects. */
0080: protected transient Collection from;
0081:
0082: /** <code>ArrayList</code> of <code>AliasedTable</code> objects */
0083: protected transient Collection aliasFrom;
0084:
0085: /** <code>ArrayList</code> of <code>TableColumn</code> objects. */
0086: protected transient ArrayList where;
0087:
0088: /** <code>ArrayList</code> of <code>OrderBy</code> objects */
0089: protected transient ArrayList order;
0090:
0091: /** Some values to keep track of function in the SELECT request */
0092: public static final int NO_FUNCTION = 0;
0093: /** Represents a SQL max() macro */
0094: public static final int MAX_FUNCTION = 1;
0095: /** Represents a SQL min() macro */
0096: public static final int MIN_FUNCTION = 2;
0097: /** Represents a SQL average() macro */
0098: public static final int AVERAGE_FUNCTION = 3;
0099: /** Represents a SQL count() macro */
0100: public static final int COUNT_FUNCTION = 4;
0101: /** Represents a SQL sum() macro */
0102: public static final int SUM_FUNCTION = 5;
0103:
0104: /** Need to keep track of type of query, e.g. MAX, COUNT, etc. */
0105: public transient int funcType = 0;
0106:
0107: /** Primary key value in case of a unique selection */
0108: protected transient String pkValue = null;
0109:
0110: /**
0111: * <code>Hashtable</code> of String keys corresponding to column names and
0112: * String values corresponding to the values associated with the UNIQUE
0113: * columns of a UNIQUE SELECT.
0114: * <p>
0115: * Used with the <code>COLUMN_UNIQUE_DELETE</code> granularity.
0116: *
0117: * @see org.continuent.sequoia.controller.cache.result.CachingGranularities
0118: */
0119: protected transient Hashtable whereValues;
0120:
0121: /**
0122: * Creates a new <code>SelectRequest</code> instance. The caller must give
0123: * an SQL request, without any leading or trailing spaces and beginning with
0124: * the 'select' keyword (it will not be checked).
0125: * <p>
0126: * The request is not parsed but it can be done later by a call to
0127: * {@link #parse(DatabaseSchema, int, boolean)}.
0128: *
0129: * @param sqlQuery the SQL query
0130: * @param escapeProcessing should the driver to escape processing before
0131: * sending to the database ?
0132: * @param timeout an <code>int</code> value
0133: * @param lineSeparator the line separator used in the query
0134: * @see #parse
0135: */
0136: public SelectRequest(String sqlQuery, boolean escapeProcessing,
0137: int timeout, String lineSeparator) {
0138: super (sqlQuery, escapeProcessing, timeout, lineSeparator,
0139: RequestType.SELECT);
0140: }
0141:
0142: /**
0143: * @see AbstractRequest#AbstractRequest(java.lang.String, boolean, int,
0144: * java.lang.String, int)
0145: */
0146: protected SelectRequest(String sqlQuery, boolean escapeProcessing,
0147: int timeout, String lineSeparator, int type) {
0148: super (sqlQuery, escapeProcessing, timeout, lineSeparator, type);
0149: }
0150:
0151: /**
0152: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersAggregateList()
0153: */
0154: public boolean altersAggregateList() {
0155: return false;
0156: }
0157:
0158: /**
0159: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersDatabaseCatalog()
0160: */
0161: public boolean altersDatabaseCatalog() {
0162: return false;
0163: }
0164:
0165: /**
0166: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersDatabaseSchema()
0167: */
0168: public boolean altersDatabaseSchema() {
0169: return false;
0170: }
0171:
0172: /**
0173: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersMetadataCache()
0174: */
0175: public boolean altersMetadataCache() {
0176: return false;
0177: }
0178:
0179: /**
0180: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersQueryResultCache()
0181: */
0182: public boolean altersQueryResultCache() {
0183: return false;
0184: }
0185:
0186: /**
0187: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersSomething()
0188: */
0189: public boolean altersSomething() {
0190: return false;
0191: }
0192:
0193: /**
0194: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersStoredProcedureList()
0195: */
0196: public boolean altersStoredProcedureList() {
0197: return false;
0198: }
0199:
0200: /**
0201: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersUserDefinedTypes()
0202: */
0203: public boolean altersUserDefinedTypes() {
0204: return false;
0205: }
0206:
0207: /**
0208: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersUsers()
0209: */
0210: public boolean altersUsers() {
0211: return false;
0212: }
0213:
0214: /**
0215: * <p>
0216: * The result of the parsing is accessible through the {@link #getSelect()},
0217: * {@link #getFrom()}and {@link #getWhere()}functions.
0218: *
0219: * @see org.continuent.sequoia.controller.requests.AbstractRequest#parse(org.continuent.sequoia.common.sql.schema.DatabaseSchema,
0220: * int, boolean)
0221: */
0222: public void parse(DatabaseSchema schema, int granularity,
0223: boolean isCaseSensitive) throws SQLException {
0224: if ((granularity == ParsingGranularities.NO_PARSING)
0225: || (schema == null)) {
0226: cacheable = RequestType.UNCACHEABLE;
0227: isParsed = true;
0228: return;
0229: }
0230:
0231: String originalSQL = this .trimCarriageReturnAndTabs();
0232:
0233: while (originalSQL.charAt(0) == '(')
0234: originalSQL = originalSQL.substring(1);
0235:
0236: String sql = originalSQL.toLowerCase();
0237: if (!isCaseSensitive)
0238: originalSQL = sql;
0239:
0240: // Strip 'select'
0241: sql = sql.substring(6).trim();
0242:
0243: // Look for DISTINCT
0244: if (sql.startsWith("distinct"))
0245: sql = sql.substring(8).trim(); // Strip 'distinct '
0246:
0247: // Look for the begining of the FROM clause
0248: int fromIndex = sql.indexOf("from ");
0249: if (fromIndex == -1)
0250: // No FROM keyword found, stop here
0251: return;
0252:
0253: // Keep SELECT clause for later, we first have to check the
0254: // tables involved in the FROM clause
0255: int fshift = originalSQL.length() - sql.length();
0256: String selectClause = (isCaseSensitive) ? originalSQL
0257: .substring(fshift, fshift + fromIndex) : sql.substring(
0258: 0, fromIndex);
0259:
0260: // Get rid of FROM
0261: sql = sql.substring(fromIndex + 5).trim();
0262:
0263: // Now find the boundaries of the FROM and WHERE clauses
0264: int whereIndex = 0;
0265: int parenthesis = 0;
0266: int lastParenthesisIdx = 0;
0267: boolean foundWhere = false;
0268: do {
0269: switch (sql.charAt(whereIndex)) {
0270: case '(':
0271: parenthesis++;
0272: break;
0273: case ')':
0274: parenthesis--;
0275: lastParenthesisIdx = whereIndex;
0276: break;
0277: case 'w':
0278: if (parenthesis == 0)
0279: try {
0280: foundWhere = (sql.charAt(whereIndex + 1) == 'h')
0281: && (sql.charAt(whereIndex + 2) == 'e')
0282: && (sql.charAt(whereIndex + 3) == 'r')
0283: && (sql.charAt(whereIndex + 4) == 'e');
0284: } catch (StringIndexOutOfBoundsException ignore) {
0285: foundWhere = false;
0286: }
0287: break;
0288: default:
0289: break;
0290: }
0291: whereIndex++;
0292: } while ((!foundWhere) && (whereIndex < sql.length()));
0293: if (foundWhere)
0294: whereIndex--;
0295: else
0296: whereIndex = -1;
0297:
0298: // Warning! Here if whereIndex is -1 (no where clause)
0299: // endWhere is used to find the end of the FROM clause.
0300: // The variable name can be misleading but it's faster to do it this
0301: // way.
0302: int endWhere = sql.indexOf("group by ", lastParenthesisIdx);
0303: if (endWhere == -1) {
0304: endWhere = sql.indexOf("having ", lastParenthesisIdx);
0305: if (endWhere == -1) {
0306: endWhere = sql.indexOf("order by ", lastParenthesisIdx);
0307: if (endWhere == -1) {
0308: endWhere = sql
0309: .indexOf("limit ", lastParenthesisIdx);
0310: if (endWhere == -1)
0311: endWhere = sql.length();
0312: }
0313: }
0314: }
0315: int endFrom;
0316: if (whereIndex == -1)
0317: endFrom = endWhere;
0318: else
0319: endFrom = whereIndex;
0320:
0321: try {
0322: switch (granularity) {
0323: case ParsingGranularities.NO_PARSING:
0324: return;
0325: case ParsingGranularities.TABLE:
0326: int shift = originalSQL.length() - sql.length();
0327: from = getFromTables(originalSQL.substring(shift,
0328: shift + endFrom).trim(), schema,
0329: isCaseSensitive);
0330: break;
0331: case ParsingGranularities.COLUMN:
0332: case ParsingGranularities.COLUMN_UNIQUE:
0333: shift = originalSQL.length() - sql.length();
0334: from = getFromTables(originalSQL.substring(shift,
0335: shift + endFrom).trim(), schema,
0336: isCaseSensitive);
0337: // Find columns selected in the SELECT clause
0338: select = getSelectedColumns(selectClause, from,
0339: isCaseSensitive);
0340: if (whereIndex > 1)
0341: // Find columns involved in the WHERE clause (5="WHERE")
0342: where = getWhereColumns(
0343: originalSQL.substring(
0344: shift + whereIndex + 5,
0345: shift + endWhere).trim(),
0346: from,
0347: granularity == ParsingGranularities.COLUMN_UNIQUE,
0348: isCaseSensitive);
0349: break;
0350: default:
0351: throw new SQLException(
0352: "Unsupported parsing granularity: '"
0353: + granularity + "'");
0354: }
0355: } catch (SQLException e) {
0356: from = null;
0357: select = null;
0358: where = null;
0359: cacheable = RequestType.UNCACHEABLE;
0360: throw e;
0361: }
0362:
0363: // Gokul added this
0364: // I need to have the aliases to determine if any of the OrderBy columns
0365: // are referenced using their alias
0366:
0367: aliasFrom = from;
0368:
0369: if (from != null) {
0370: // Convert 'from' to an ArrayList of String objects instead of
0371: // AliasedTables objects
0372: int size = from.size();
0373: ArrayList unaliased = new ArrayList(size);
0374: for (Iterator iter = from.iterator(); iter.hasNext();)
0375: unaliased.add(((AliasedDatabaseTable) iter.next())
0376: .getTable().getName());
0377: from = unaliased;
0378: }
0379:
0380: isParsed = true;
0381: }
0382:
0383: /**
0384: * @see AbstractRequest#cloneParsing(AbstractRequest)
0385: */
0386: public void cloneParsing(AbstractRequest request) {
0387: if (!request.isParsed())
0388: return;
0389: SelectRequest selectRequest = (SelectRequest) request;
0390: select = selectRequest.getSelect();
0391: from = selectRequest.getFrom();
0392: where = selectRequest.getWhere();
0393: cacheable = selectRequest.getCacheAbility();
0394: pkValue = selectRequest.getPkValue();
0395: isParsed = true;
0396: }
0397:
0398: /**
0399: * Extracts the tables from the given <code>FROM</code> clause and retrieves
0400: * their alias if any.
0401: *
0402: * @param fromClause the <code>FROM</code> clause of the request (without
0403: * the <code>FROM</code> keyword)
0404: * @param schema the <code>DatabaseSchema</code> this request refers to
0405: * @param isCaseSensitive true if table name parsing is case sensitive
0406: * @return an <code>ArrayList</code> of <code>AliasedDatabaseTable</code>
0407: * objects
0408: * @exception SQLException if an error occurs
0409: */
0410: private Collection getFromTables(String fromClause,
0411: DatabaseSchema schema, boolean isCaseSensitive)
0412: throws SQLException {
0413: ArrayList result = new ArrayList();
0414:
0415: // Search for subselects in from clause
0416: try {
0417: int subSelect = fromClause.toLowerCase().indexOf("select ");
0418: while (subSelect != -1) {
0419: int subFromIndex = fromClause.indexOf("from",
0420: subSelect + 1) + 5;
0421: int bracket = subFromIndex;
0422: int parenthesis = 1;
0423: do {
0424: char c = fromClause.charAt(bracket);
0425: switch (c) {
0426: case '(':
0427: parenthesis++;
0428: break;
0429: case ')':
0430: parenthesis--;
0431: break;
0432: default:
0433: break;
0434: }
0435: bracket++;
0436: } while ((parenthesis > 0)
0437: && (bracket < fromClause.length()));
0438:
0439: SelectRequest subQuery = new SelectRequest(fromClause
0440: .substring(subSelect, bracket - 1).trim(),
0441: this .escapeProcessing, 0, getLineSeparator());
0442: subQuery.parse(schema, ParsingGranularities.TABLE,
0443: isCaseSensitive);
0444: for (Iterator iter = subQuery.getFrom().iterator(); iter
0445: .hasNext();) {
0446: result.add(new AliasedDatabaseTable(schema
0447: .getTable((String) iter.next(),
0448: isCaseSensitive), null));
0449: }
0450:
0451: if (subFromIndex + bracket > fromClause.length()) {
0452: if (subSelect > 0) {
0453: fromClause = fromClause.substring(0,
0454: subSelect - 1).trim();
0455: if ((fromClause.length() > 0)
0456: && (fromClause.charAt(fromClause
0457: .length() - 1) == '('))
0458: fromClause = fromClause.substring(0,
0459: fromClause.length() - 1).trim();
0460: } else
0461: fromClause = "";
0462: break; // Nothing more to process
0463: }
0464: fromClause = (subSelect > 0 ? fromClause.substring(0,
0465: subSelect - 1).trim() : "")
0466: + fromClause.substring(subFromIndex + bracket)
0467: .trim();
0468: subSelect = fromClause.toLowerCase().indexOf("select");
0469: }
0470: } catch (RuntimeException e) {
0471: // Parsing failed, select everything
0472: Collection unaliasedTables = schema.getTables().values();
0473: ArrayList fromAliasedTables = new ArrayList(unaliasedTables
0474: .size());
0475: for (Iterator iter = unaliasedTables.iterator(); iter
0476: .hasNext();) {
0477: DatabaseTable t = (DatabaseTable) iter.next();
0478: fromAliasedTables
0479: .add(new AliasedDatabaseTable(t, null));
0480: }
0481: return fromAliasedTables;
0482: }
0483:
0484: // Use a brutal force technique by matching schema table names in the from
0485: // clause
0486: Collection tables = schema.getTables().values();
0487: // Note that we use an iterator here since the tables might be modified
0488: // concurrently by a write query that alters the database schema. In case
0489: // of a concurrent modification, iter.next() will fail and we will restart
0490: // the parsing and this will prevent the disgracious error message reported
0491: // by BUG #303423.
0492: for (Iterator iter = tables.iterator(); iter.hasNext();) {
0493: // Check if this table is found in the FROM string
0494: DatabaseTable t;
0495: try {
0496: t = (DatabaseTable) iter.next();
0497: } catch (ConcurrentModificationException race) {
0498: iter = tables.iterator();
0499: continue;
0500: }
0501: String tableName = t.getName();
0502: if (!isCaseSensitive)
0503: tableName = tableName.toLowerCase();
0504:
0505: // Check that we have a full match and not a partial match
0506: int index;
0507: int afterTableNameIndex = 0;
0508: boolean left;
0509: boolean right;
0510: do {
0511: index = fromClause.indexOf(tableName,
0512: afterTableNameIndex);
0513: if (index == -1)
0514: break;
0515: afterTableNameIndex = index + tableName.length();
0516: left = (index == 0)
0517: || ((index > 0) && ((fromClause
0518: .charAt(index - 1) == ' ')
0519: || (fromClause.charAt(index - 1) == '(')
0520: || (fromClause.charAt(index - 1) == ',') || (fromClause
0521: .charAt(index - 1) == getLineSeparator()
0522: .charAt(getLineSeparator().length() - 1))));
0523: right = (afterTableNameIndex >= fromClause.length())
0524: || ((afterTableNameIndex < fromClause.length()) && ((fromClause
0525: .charAt(afterTableNameIndex) == ' ')
0526: || (fromClause
0527: .charAt(afterTableNameIndex) == ',')
0528: || (fromClause
0529: .charAt(afterTableNameIndex) == ')') || (fromClause
0530: .charAt(afterTableNameIndex) == getLineSeparator()
0531: .charAt(0))));
0532: } while (!left || !right);
0533: if (index != -1) {
0534: // Check if the table has an alias
0535: // Example: SELECT x.price FROM item x
0536: String alias = null;
0537: index += tableName.length();
0538: if ((index < fromClause.length())
0539: && (fromClause.charAt(index) == ' ')) {
0540: char c;
0541: // Skip spaces before alias
0542: do {
0543: c = fromClause.charAt(index);
0544: index++;
0545: } while ((index < fromClause.length())
0546: && (c != ' ')
0547: && (c != getLineSeparator().charAt(0)));
0548: if (index < fromClause.length()) {
0549: int start = index;
0550: do {
0551: c = fromClause.charAt(index);
0552: index++;
0553: } while ((index < fromClause.length())
0554: && (c != ' ') && (c != ',')
0555: && (c != getLineSeparator().charAt(0)));
0556: alias = fromClause.substring(start, index - 1);
0557: }
0558: }
0559: result.add(new AliasedDatabaseTable(t, alias));
0560: }
0561: }
0562:
0563: return result;
0564: }
0565:
0566: /**
0567: * Gets all the columns selected in the given <code>SELECT</code> clause.
0568: * <p>
0569: * The selected columns or tables must be found in the given
0570: * <code>ArrayList</code> of <code>AliasedDatabaseTable</code>
0571: * representing the <code>FROM</code> clause of the same request.
0572: *
0573: * @param selectClause <code>SELECT</code> clause of the request (without
0574: * the <code>SELECT</code> keyword)
0575: * @param aliasedFrom a <code>Collection</code> of
0576: * <code>AliasedDatabaseTable</code>
0577: * @param isCaseSensitive true if column name parsing is case sensitive
0578: * @return an <code>ArrayList</code> of <code>TableColumn</code>
0579: */
0580: private ArrayList getSelectedColumns(String selectClause,
0581: Collection aliasedFrom, boolean isCaseSensitive) {
0582: StringTokenizer selectTokens = new StringTokenizer(
0583: selectClause, ",");
0584: ArrayList result = new ArrayList();
0585: StringBuffer unresolvedTokens = null;
0586:
0587: while (selectTokens.hasMoreTokens()) {
0588: String token = selectTokens.nextToken().trim();
0589: // Check if it is a function, e.g., MAX, COUNT, etc.
0590: if (isSqlFunction(token)) {
0591: // token has the following form:
0592: // max(...)
0593: // or
0594: // count(...)
0595: int leftPar = token.indexOf("(");
0596: token = token
0597: .substring(leftPar + 1, token.length() - 1);
0598: }
0599: // Is it using an aliased table name (x.price for example) ?
0600: String alias = null;
0601: int aliasIdx = token.indexOf(".");
0602: if (aliasIdx != -1) {
0603: alias = token.substring(0, aliasIdx);
0604: token = token.substring(aliasIdx + 1); // Get rid of the '.'
0605: }
0606:
0607: // Discard any AS clause
0608: int as = token.indexOf(" as ");
0609: if (as != -1)
0610: token = token.substring(0, as).trim();
0611:
0612: // Now token only contains the column name
0613:
0614: // Deal with SELECT * or x.*
0615: if (token.indexOf("*") != -1) {
0616: if (alias == null) {
0617: // We have to take all colums of all tables of the FROM
0618: // clause
0619: for (Iterator iter = aliasedFrom.iterator(); iter
0620: .hasNext();) {
0621: DatabaseTable t = ((AliasedDatabaseTable) iter
0622: .next()).getTable();
0623: ArrayList cols = t.getColumns();
0624: int colSize = cols.size();
0625: for (int j = 0; j < colSize; j++)
0626: result.add(new TableColumn(t.getName(),
0627: ((DatabaseColumn) cols.get(j))
0628: .getName()));
0629: }
0630: return result;
0631: } else {
0632: // Add all colums of the table corresponding to the alias
0633: for (Iterator iter = aliasedFrom.iterator(); iter
0634: .hasNext();) {
0635: AliasedDatabaseTable adt = (AliasedDatabaseTable) iter
0636: .next();
0637: // The alias could be the full name of the table
0638: // instead of a "real" alias
0639: if (alias.equals(adt.getAlias())
0640: || alias.equals(adt.getTable()
0641: .getName())) {
0642: DatabaseTable t = adt.getTable();
0643: ArrayList cols = t.getColumns();
0644: int colSize = cols.size();
0645: for (int j = 0; j < colSize; j++)
0646: result.add(new TableColumn(t.getName(),
0647: ((DatabaseColumn) cols.get(j))
0648: .getName()));
0649: break;
0650: }
0651: }
0652: }
0653: continue;
0654: }
0655:
0656: // First, we suppose that it's a simple column name.
0657: // If it fails, we will consider it later.
0658: DatabaseColumn col = null;
0659:
0660: if (alias == null) {
0661: for (Iterator iter = aliasedFrom.iterator(); iter
0662: .hasNext();) {
0663: DatabaseTable t = ((AliasedDatabaseTable) iter
0664: .next()).getTable();
0665: col = t.getColumn(token, isCaseSensitive);
0666: if (col != null) {
0667: result.add(new TableColumn(t.getName(), col
0668: .getName()));
0669: break;
0670: }
0671: }
0672: } else
0673: // same with an alias
0674: {
0675: for (Iterator iter = aliasedFrom.iterator(); iter
0676: .hasNext();) {
0677: AliasedDatabaseTable t = (AliasedDatabaseTable) iter
0678: .next();
0679: // It can be either an alias or the fully qualified name of
0680: // the table
0681: if (alias.equals(t.getAlias())
0682: || alias.equals(t.getTable().getName())) {
0683: col = t.getTable().getColumn(token,
0684: isCaseSensitive);
0685: if (col != null) {
0686: result.add(new TableColumn(t.getTable()
0687: .getName(), col.getName()));
0688: break;
0689: }
0690: }
0691: }
0692: }
0693:
0694: if (col == null) {
0695: if (unresolvedTokens == null)
0696: unresolvedTokens = new StringBuffer();
0697: unresolvedTokens.append(token);
0698: unresolvedTokens.append(" ");
0699: }
0700: }
0701:
0702: if (unresolvedTokens != null) {
0703: // Those tokens may be complex expressions, so instead of parsing
0704: // them, we use a brutal force technique and we try to directly
0705: // identify every column name of each table.
0706: DatabaseColumn col;
0707:
0708: String unresolvedTokensString = unresolvedTokens.toString();
0709: if (!isCaseSensitive)
0710: unresolvedTokensString = unresolvedTokensString
0711: .toLowerCase();
0712:
0713: for (Iterator iter = aliasedFrom.iterator(); iter.hasNext();) {
0714: DatabaseTable t = ((AliasedDatabaseTable) iter.next())
0715: .getTable();
0716: ArrayList cols = t.getColumns();
0717: int size = cols.size();
0718: for (int j = 0; j < size; j++) {
0719: col = (DatabaseColumn) cols.get(j);
0720: String columnName = col.getName();
0721: if (!isCaseSensitive)
0722: columnName = columnName.toLowerCase();
0723:
0724: // if pattern found and column not already in result, it's a
0725: // dependency !
0726: int matchIdx = unresolvedTokensString
0727: .indexOf(columnName);
0728: if (matchIdx != -1)
0729: if ((matchIdx == 0)
0730: || (unresolvedTokens
0731: .charAt(matchIdx - 1) == ' ')
0732: || (unresolvedTokens
0733: .charAt(matchIdx - 1) == '(')
0734: || (unresolvedTokens
0735: .charAt(matchIdx - 1) == '.')) {
0736: TableColumn c = new TableColumn(
0737: t.getName(), col.getName());
0738: if (!result.contains(c))
0739: result.add(c);
0740: }
0741: }
0742: }
0743: }
0744: return result;
0745: }
0746:
0747: /**
0748: * Checks if the string parameter represents an SQL function, e. g., MAX,
0749: * COUNT, SUM, etc.
0750: *
0751: * @param str A lower-case string that may represent an SQL function
0752: * @return boolean <code>true</code> if it is an SQL function and
0753: * <code>false</code> otherwise.
0754: */
0755: private boolean isSqlFunction(String str) {
0756:
0757: if (str != null) {
0758: if (str.startsWith("max(") && str.endsWith(")")) {
0759: funcType = SelectRequest.MAX_FUNCTION;
0760: return true;
0761: } else if (str.startsWith("count(") && str.endsWith(")")) {
0762: funcType = SelectRequest.COUNT_FUNCTION;
0763: return true;
0764: } else if (str.startsWith("avg(") && str.endsWith(")")) {
0765: funcType = SelectRequest.AVERAGE_FUNCTION;
0766: return true;
0767: } else if (str.startsWith("min(") && str.endsWith(")")) {
0768: funcType = SelectRequest.MIN_FUNCTION;
0769: return true;
0770: } else if (str.startsWith("sum(") && str.endsWith(")")) {
0771: funcType = SelectRequest.SUM_FUNCTION;
0772: return true;
0773: } else {
0774: funcType = SelectRequest.NO_FUNCTION;
0775: return false;
0776: }
0777: } else
0778: return false;
0779: }
0780:
0781: /**
0782: * Gets all the columns involved in the given <code>WHERE</code> clause.
0783: * <p>
0784: * The selected columns or tables must be found in the given
0785: * <code>ArrayList</code> of <code>AliasedDatabaseTable</code>
0786: * representing the <code>FROM</code> clause of the same request.
0787: *
0788: * @param whereClause <code>WHERE</code> clause of the request (without the
0789: * <code>WHERE</code> keyword)
0790: * @param aliasedFrom a <code>Collection</code> of
0791: * <code>AliasedDatabaseTable</code>
0792: * @param setUniqueCacheable true if we have to check is this select is
0793: * <code>UNIQUE</code> or not
0794: * @param isCaseSensitive true if column name parsing is case sensitive
0795: * @return an <code>ArrayList</code> of <code>TableColumn</code>
0796: */
0797: private ArrayList getWhereColumns(String whereClause,
0798: Collection aliasedFrom, boolean setUniqueCacheable,
0799: boolean isCaseSensitive) {
0800: ArrayList result = new ArrayList(); // TableColumn
0801: // objects
0802:
0803: if (!isCaseSensitive)
0804: whereClause = whereClause.toLowerCase();
0805:
0806: // Instead of parsing the clause, we use a brutal force technique
0807: // and we try to directly identify every column name of each table.
0808: DatabaseColumn col;
0809: for (Iterator iter = aliasedFrom.iterator(); iter.hasNext();) {
0810: DatabaseTable t = ((AliasedDatabaseTable) iter.next())
0811: .getTable();
0812: ArrayList cols = t.getColumns();
0813: int size = cols.size();
0814: for (int j = 0; j < size; j++) {
0815: col = (DatabaseColumn) cols.get(j);
0816: // if pattern found and column not already in result, it's a
0817: // dependency !
0818: String columnName = col.getName();
0819: if (!isCaseSensitive)
0820: columnName = columnName.toLowerCase();
0821:
0822: int matchIdx = whereClause.indexOf(columnName);
0823: while (matchIdx > 0) {
0824: // Try to check that we got the full pattern and not a
0825: // sub-pattern
0826: char beforePattern = whereClause
0827: .charAt(matchIdx - 1);
0828: if (((beforePattern >= 'a') && (beforePattern <= 'z'))
0829: || ((beforePattern >= 'A') && (beforePattern <= 'Z'))
0830: || (beforePattern == '_'))
0831: matchIdx = whereClause.indexOf(columnName,
0832: matchIdx + 1);
0833: else {
0834: char afterPattern;
0835: try {
0836: afterPattern = whereClause.charAt(matchIdx
0837: + columnName.length());
0838: if (((afterPattern >= 'a') && (afterPattern <= 'z'))
0839: || ((afterPattern >= 'A') && (afterPattern <= 'Z'))
0840: || (afterPattern == '_')) {
0841: // This is a subset of the full name of another
0842: // column,
0843: // let's jump to next mathing pattern
0844: matchIdx = whereClause.indexOf(
0845: columnName, matchIdx + 1);
0846: } else
0847: break;
0848: } catch (IndexOutOfBoundsException e) {
0849: break;
0850: }
0851: }
0852: }
0853: if (matchIdx == -1)
0854: continue;
0855: result.add(new TableColumn(t.getName(), col.getName()));
0856:
0857: if (setUniqueCacheable) { // Check if this request selects a
0858: // unique row
0859: if (!col.isUnique()) { // Column has no unicity constraint,
0860: // we can select multiple rows
0861: // with same value, give up.
0862: setUniqueCacheable = false;
0863: continue;
0864: }
0865:
0866: // Check if the column is in the left side of an equality
0867: // with a
0868: // constant.
0869: // e.g.: 'column_name1 = 10' is ok
0870: // but '5=table_name.column_name2' will fail
0871:
0872: int lookingForEqual = matchIdx
0873: + columnName.length();
0874: boolean searchReverse = false;
0875: try {
0876: while (whereClause.charAt(lookingForEqual) == ' ')
0877: lookingForEqual++;
0878: } catch (Exception e) {
0879: searchReverse = true;
0880: }
0881:
0882: String rightSide;
0883:
0884: if (searchReverse
0885: || (whereClause.charAt(lookingForEqual) != '=')) {
0886: try {
0887: // try reverse
0888: StringBuffer sb = new StringBuffer(
0889: whereClause.substring(0,
0890: lookingForEqual));
0891: String reverse = sb.reverse().toString();
0892: reverse = reverse.substring(reverse
0893: .indexOf('=') + 1);
0894: sb = new StringBuffer(reverse);
0895: // Get back the original values
0896: sb = sb.reverse();
0897: rightSide = sb.toString();
0898: } catch (Exception e) {
0899: // No equality, it is not unique cacheable
0900: setUniqueCacheable = false;
0901: continue;
0902: }
0903: } else {
0904: // We found it let's move to next char
0905: int nextSpace = lookingForEqual + 1;
0906: try {
0907: while (whereClause.charAt(nextSpace) == ' ')
0908: nextSpace++;
0909: } catch (Exception e1) { // This should not happen
0910: // unless we get a query like:
0911: // 'select ... where id= '
0912: setUniqueCacheable = false;
0913: continue;
0914: }
0915:
0916: rightSide = whereClause.substring(nextSpace);
0917: }
0918: char firstChar = rightSide.charAt(0);
0919: if ((firstChar == '\'')
0920: || (firstChar == '"')
0921: || ((firstChar >= '0') && (firstChar <= '9'))
0922: || (firstChar == '?')) { // Ok, the value is either
0923: // '...' or "..." or starts
0924: // with a
0925: // number which is enough for us to consider that it is
0926: // an
0927: // acceptable constant.
0928: pkValue = rightSide;
0929: } else {
0930: setUniqueCacheable = false;
0931: continue;
0932: }
0933: }
0934: }
0935: }
0936:
0937: if (setUniqueCacheable && !result.isEmpty())
0938: cacheable = RequestType.UNIQUE_CACHEABLE;
0939:
0940: return result;
0941: }
0942:
0943: /**
0944: * Returns a <code>Collection</code> of <code>AliasedDatabaseTable</code>
0945: * objects representing the table names found in the <code>FROM</code>
0946: * clause of this request.
0947: *
0948: * @return a <code>Collection</code> of <code>AliasedDatabaseTable</code>
0949: */
0950: public Collection getAliasedFrom() {
0951: return aliasFrom;
0952: }
0953:
0954: /**
0955: * Returns a <code>Collection</code> of <code>String</code> objects
0956: * representing the table names found in the <code>FROM</code> clause of
0957: * this request.
0958: *
0959: * @return a <code>Collection</code> of <code>String</code>
0960: */
0961: public Collection getFrom() {
0962: return from;
0963: }
0964:
0965: /**
0966: * Returns an <code>ArrayList</code> of <code>OrderBy</code> objects
0967: * representing the columns involved in the <code>ORDER BY</code> clause of
0968: * this request.
0969: *
0970: * @return an <code>ArrayList</code> of <code>OrderBy</code>
0971: */
0972: public ArrayList getOrderBy() {
0973: return order;
0974: }
0975:
0976: /**
0977: * @return Returns the pkValue.
0978: */
0979: public String getPkValue() {
0980: return pkValue;
0981: }
0982:
0983: /**
0984: * Returns an <code>ArrayList</code> of <code>DatabaseColumn</code>
0985: * objects representing the columns selected in the <code>SELECT</code>
0986: * clause of this request.
0987: *
0988: * @return an <code>ArrayList</code> of <code>TableColumn</code>
0989: */
0990: public ArrayList getSelect() {
0991: return select;
0992: }
0993:
0994: /**
0995: * Returns an <code>ArrayList</code> of <code>TableColumn</code> objects
0996: * representing the columns involved in the <code>WHERE</code> clause of
0997: * this request.
0998: *
0999: * @return an <code>ArrayList</code> of <code>TableColumn</code>
1000: */
1001: public ArrayList getWhere() {
1002: return where;
1003: }
1004:
1005: /**
1006: * Returns an <code>Hashtable</code> of <code>String</code> keys
1007: * representing unique column names and <code>String</code> values
1008: * associated with the columns involved in this request.
1009: *
1010: * @return an <code>Hashtable</code> value
1011: */
1012: public Hashtable getWhereValues() {
1013: return whereValues;
1014: }
1015:
1016: /**
1017: * Returns the mustBroadcast value.
1018: *
1019: * @return Returns the mustBroadcast.
1020: */
1021: public boolean isMustBroadcast() {
1022: return mustBroadcast;
1023: }
1024:
1025: /**
1026: * @return <code>true</code>
1027: * @see org.continuent.sequoia.controller.requests.AbstractRequest#needsMacroProcessing()
1028: */
1029: public boolean needsMacroProcessing() {
1030: return true;
1031: }
1032:
1033: /**
1034: * Does this request returns a ResultSet?
1035: *
1036: * @return false
1037: */
1038: public boolean returnsResultSet() {
1039: return true;
1040: }
1041:
1042: /**
1043: * Sets the mustBroadcast value.
1044: *
1045: * @param mustBroadcast The mustBroadcast to set.
1046: */
1047: public void setMustBroadcast(boolean mustBroadcast) {
1048: this .mustBroadcast = mustBroadcast;
1049: }
1050:
1051: /**
1052: * @param pkValue The pkValue to set.
1053: */
1054: public void setPkValue(String pkValue) {
1055: this .pkValue = pkValue;
1056: }
1057:
1058: /**
1059: * @see org.continuent.sequoia.controller.requests.AbstractRequest#getParsingResultsAsString()
1060: */
1061: public String getParsingResultsAsString() {
1062: StringBuffer sb = new StringBuffer(super
1063: .getParsingResultsAsString());
1064: if (select != null && select.size() > 0) {
1065: sb.append(Translate.get("request.select.selects"));
1066: for (int i = 0; i < select.size(); i++) {
1067: sb.append(Translate.get("request.select.select", select
1068: .get(i)));
1069: }
1070: }
1071: if (from != null && from.size() > 0) {
1072: sb.append(Translate.get("request.select.froms"));
1073: for (int i = 0; i < from.size(); i++) {
1074: sb.append(Translate.get("request.select.from", from
1075: .toArray()[i]));
1076: }
1077: }
1078: if (aliasFrom != null && aliasFrom.size() > 0) {
1079: sb.append(Translate.get("request.select.alias.froms"));
1080: for (int i = 0; i < aliasFrom.size(); i++) {
1081: sb.append(Translate.get("request.select.alias.from",
1082: ((AliasedDatabaseTable) aliasFrom.toArray()[i])
1083: .getAlias()));
1084: }
1085: }
1086: if (where != null && where.size() > 0) {
1087: sb.append(Translate.get("request.select.wheres"));
1088: for (int i = 0; i < where.size(); i++) {
1089: sb.append(Translate.get("request.select.where", where
1090: .toArray()[i]));
1091: }
1092: }
1093: if (order != null && order.size() > 0) {
1094: sb.append(Translate.get("request.select.orders"));
1095: for (int i = 0; i < order.size(); i++) {
1096: sb.append(Translate.get("request.select.order", where
1097: .toArray()[i]));
1098: }
1099: }
1100: return sb.toString();
1101: }
1102:
1103: /**
1104: * Displays some debugging information about this request.
1105: */
1106: public void debug() {
1107: super .debug();
1108: if (select != null) {
1109: System.out.println("Selected columns:");
1110: for (int i = 0; i < select.size(); i++)
1111: System.out
1112: .println(" "
1113: + ((TableColumn) select.get(i))
1114: .getColumnName());
1115: } else
1116: System.out.println("No information on selected columns");
1117:
1118: if (select != null) {
1119: System.out.println("");
1120: System.out.println("From tables:");
1121: for (Iterator iter = from.iterator(); iter.hasNext();)
1122: for (int i = 0; i < from.size(); i++)
1123: System.out.println(" " + iter.next());
1124: } else
1125: System.out.println("No information on from tables");
1126:
1127: System.out.println("");
1128: System.out.println("Where columns:");
1129: if (where == null)
1130: System.out.println(" No Where clause");
1131: else
1132: for (int i = 0; i < where.size(); i++)
1133: System.out.print(" "
1134: + ((TableColumn) where.get(i)).getColumnName());
1135:
1136: System.out.println("");
1137: System.out.println("PK value: " + pkValue);
1138: }
1139:
1140: }
|