0001: /**
0002: * Copyright (C) 2006 NetMind Consulting Bt.
0003: *
0004: * This library is free software; you can redistribute it and/or
0005: * modify it under the terms of the GNU Lesser General Public
0006: * License as published by the Free Software Foundation; either
0007: * version 3 of the License, or (at your option) any later version.
0008: *
0009: * This library is distributed in the hope that it will be useful,
0010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0012: * Lesser General Public License for more details.
0013: *
0014: * You should have received a copy of the GNU Lesser General Public
0015: * License along with this library; if not, write to the Free Software
0016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0017: */package hu.netmind.persistence;
0018:
0019: import java.util.Map;
0020: import java.sql.Connection;
0021: import java.sql.PreparedStatement;
0022: import java.sql.SQLException;
0023: import java.util.Iterator;
0024: import java.util.HashSet;
0025: import java.util.Set;
0026: import java.util.Map;
0027: import java.util.Vector;
0028: import java.util.Date;
0029: import java.util.Collections;
0030: import java.util.Collection;
0031: import java.util.List;
0032: import java.util.HashMap;
0033: import java.util.Stack;
0034: import java.util.LinkedList;
0035: import java.sql.Timestamp;
0036: import org.apache.log4j.Logger;
0037: import java.sql.ResultSet;
0038: import java.sql.DatabaseMetaData;
0039: import java.sql.Types;
0040: import java.sql.ResultSetMetaData;
0041: import hu.netmind.persistence.parser.Expression;
0042: import hu.netmind.persistence.parser.ConstantTerm;
0043: import hu.netmind.persistence.parser.ReferenceTerm;
0044: import hu.netmind.persistence.parser.TableTerm;
0045: import hu.netmind.persistence.parser.OrderBy;
0046: import hu.netmind.persistence.parser.QueryStatement;
0047:
0048: /**
0049: * This is a generic database implementation. It contains no optimization,
0050: * and tries to be as generic with types and sql syntax as possible, this also
0051: * means, it cannot handle limits and offsets, which causes <strong>all</strong>
0052: * results to be returned, even with lazy lists.
0053: * @author Brautigam Robert
0054: * @version Revision: $Revision$
0055: */
0056: public class GenericDatabase implements DatabaseImplementation {
0057: private static Logger logger = Logger
0058: .getLogger(GenericDatabase.class);
0059: private static Logger sqlLogger = Logger
0060: .getLogger("hu.netmind.persistence.sql");
0061: private static PerformanceLogger perfLogger = PerformanceLogger
0062: .getLogger();
0063:
0064: /**
0065: * Save basic attribute information, so we have it ready when needed.
0066: */
0067: private Map tableAttributes = Collections
0068: .synchronizedMap(new HashMap());
0069:
0070: public GenericDatabase() {
0071: }
0072:
0073: protected Map getAttributes(String tableName) {
0074: return (Map) tableAttributes.get(tableName.toLowerCase());
0075: }
0076:
0077: protected Class getAttributeType(String tableName,
0078: String attributeName) {
0079: return (Class) getAttributes(tableName).get(
0080: attributeName.toLowerCase());
0081: }
0082:
0083: private void setAttributes(String tableName, Map attributes) {
0084: tableAttributes.put(tableName, attributes);
0085: }
0086:
0087: /**
0088: * Make a save statement for given table, id and changed attributes.
0089: * Override this method in a subclass for a non-generic behaviour.
0090: * The attributes' placeholders must be in the same order as given
0091: * by the attributeNames list.
0092: * @param tableName The table to save attributes to.
0093: * @param keyNames The keys of object to save (All object entries have keys).
0094: * @param attributeNames The attributes that will change.
0095: * the statement.
0096: * @param keys Keys.
0097: * @return An SQL save/update statement specific to database backend.
0098: */
0099: protected String getSaveStatement(String tableName, List keyNames,
0100: List attributeNames, Map keys) {
0101: // Make a generic update statement
0102: StringBuffer statement = new StringBuffer("update " + tableName
0103: + " set ");
0104: for (int i = 0; i < attributeNames.size(); i++)
0105: statement.append(attributeNames.get(i).toString() + "=?,");
0106: statement.delete(statement.length() - 1, statement.length());
0107: if (keyNames.size() > 0)
0108: statement.append(" where ");
0109: for (int i = 0; i < keyNames.size(); i++) {
0110: String keyName = keyNames.get(i).toString();
0111: if (keys.get(keyName) == null) {
0112: statement.append(keyName + " is null and ");
0113: keyNames.remove(keyName);
0114: i--;
0115: } else
0116: statement.append(keyName + "=? and ");
0117: }
0118: statement.delete(statement.length() - 5, statement.length());
0119: sqlLogger.debug("preparing update statement: "
0120: + statement.toString());
0121: // Return
0122: return statement.toString();
0123: }
0124:
0125: /**
0126: * Modifies an object already in database with given fields.
0127: * @param tableName The table to save attributes to.
0128: * @param id The id of object to save (All object entries have an id).
0129: * @param attributes The attributes in form of name:value pairs.
0130: */
0131: public DatabaseStatistics save(Connection connection,
0132: String tableName, Map keys, Map attributes) {
0133: DatabaseStatistics stats = new DatabaseStatistics();
0134: // Clear null values from attributes
0135: attributes = new HashMap(attributes);
0136: Vector nullVector = new Vector();
0137: nullVector.add(null);
0138: attributes.values().removeAll(nullVector);
0139: // Make a generic insert statement
0140: Vector attributeNames = new Vector(attributes.keySet());
0141: Vector keyNames = new Vector(keys.keySet());
0142: String statement = getSaveStatement(tableName, keyNames,
0143: attributeNames, keys);
0144: // Prepare
0145: PreparedStatement pstmt;
0146: try {
0147: pstmt = connection.prepareStatement(statement);
0148: int i = 0;
0149: for (; i < attributeNames.size(); i++) {
0150: Object value = getSQLValue(attributes
0151: .get(attributeNames.get(i)));
0152: sqlLogger.debug("setting statement parameter #" + i
0153: + ": " + value);
0154: pstmt.setObject(i + 1, value);
0155: }
0156: for (; i < keyNames.size() + attributeNames.size(); i++) {
0157: Object value = getSQLValue(keys.get(keyNames.get(i
0158: - attributeNames.size())));
0159: sqlLogger.debug("setting statement parameter #" + i
0160: + ": " + value);
0161: pstmt.setObject(i + 1, value);
0162: }
0163: } catch (Exception e) {
0164: throw new StoreException("cannot prepare statement: "
0165: + statement, e);
0166: }
0167: // Execute
0168: try {
0169: if (logger.isDebugEnabled())
0170: logger.debug("excuting update statement: " + statement);
0171: long startTime = System.currentTimeMillis();
0172: pstmt.executeUpdate();
0173: long endTime = System.currentTimeMillis();
0174: perfLogger.log("Update statement execution",
0175: new int[] { (int) (endTime - startTime) });
0176: stats.setUpdateCount(1);
0177: stats.setUpdateTime(endTime - startTime);
0178: } catch (Exception e) {
0179: throw new StoreException("exception while sql update", e);
0180: } finally {
0181: try {
0182: pstmt.close();
0183: } catch (Exception e) {
0184: logger.debug("unable to close statement", e);
0185: }
0186: }
0187: // Return
0188: return stats;
0189: }
0190:
0191: /**
0192: * Make an insert statement for given table, id and attributes.
0193: * Override this method in a subclass for a non-generic behaviour.
0194: * The attributes' placeholders must be in the same order as given
0195: * by the attributeNames list.
0196: * @param tableName The table to insert attributes to.
0197: * @param attributeNames The attributes that will be inserted.
0198: * @return An SQL insert statement specific to database backend.
0199: */
0200: protected String getInsertStatement(String tableName,
0201: List attributeNames) {
0202: // Make a generic insert statement
0203: StringBuffer statement = new StringBuffer("insert into "
0204: + tableName + " (");
0205: for (int i = 0; i < attributeNames.size(); i++)
0206: statement.append(attributeNames.get(i).toString() + ",");
0207: statement.delete(statement.length() - 1, statement.length());
0208: statement.append(") values (");
0209: for (int i = 0; i < attributeNames.size(); i++)
0210: statement.append("?,");
0211: statement.delete(statement.length() - 1, statement.length());
0212: statement.append(")");
0213: sqlLogger.debug("preparing insert statement: "
0214: + statement.toString());
0215: // Return
0216: return statement.toString();
0217: }
0218:
0219: /**
0220: * Insert an object into the database.
0221: * @param tableName The table to save attributes to.
0222: * @param id The id of object to save (All object entries have an id).
0223: * @param attributes The attributes in form of name:value pairs.
0224: */
0225: public DatabaseStatistics insert(Connection connection,
0226: String tableName, Map attributes) {
0227: DatabaseStatistics stats = new DatabaseStatistics();
0228: // Clear null values from attributes
0229: attributes = new HashMap(attributes);
0230: Vector nullVector = new Vector();
0231: nullVector.add(null);
0232: attributes.values().removeAll(nullVector);
0233: // Make a generic insert statement
0234: Vector attributeNames = new Vector(attributes.keySet());
0235: String statement = getInsertStatement(tableName, attributeNames);
0236: // Execute
0237: PreparedStatement pstmt;
0238: try {
0239: pstmt = connection.prepareStatement(statement);
0240: for (int i = 0; i < attributeNames.size(); i++) {
0241: Object value = getSQLValue(attributes
0242: .get(attributeNames.get(i)));
0243: sqlLogger.debug("setting statement parameter #" + i
0244: + ": " + value);
0245: pstmt.setObject(i + 1, value);
0246: }
0247: } catch (Exception e) {
0248: throw new StoreException("cannot prepare statement: "
0249: + statement, e);
0250: }
0251: // Execute
0252: try {
0253: if (logger.isDebugEnabled())
0254: logger.debug("excuting insert statement: " + statement);
0255: long startTime = System.currentTimeMillis();
0256: pstmt.executeUpdate();
0257: long endTime = System.currentTimeMillis();
0258: perfLogger.log("Insert statement execution",
0259: new int[] { (int) (endTime - startTime) });
0260: stats.setInsertCount(1);
0261: stats.setInsertTime(endTime - startTime);
0262: } catch (Exception e) {
0263: throw new StoreException("exception while sql insert", e);
0264: } finally {
0265: try {
0266: pstmt.close();
0267: } catch (Exception e) {
0268: logger.debug("unable to close statement", e);
0269: }
0270: }
0271: // Return
0272: return stats;
0273: }
0274:
0275: /**
0276: * Make a remove statement for given table and attributes.
0277: * Override this method in a subclass for a non-generic behaviour.
0278: * The attributes' placeholders must be in the same order as given
0279: * by the attributeNames list.
0280: * @param tableName The table to remove attributes from.
0281: * @param attributeNames The attributes that will be search for by the remove.
0282: * @return An SQL insert statement specific to database backend.
0283: */
0284: protected String getRemoveStatement(String tableName,
0285: List attributeNames) {
0286: // Make a generic delete statement
0287: // Please note the paticularly funny delete-6 to handle
0288: // empty lists and list ends.
0289: StringBuffer statement = new StringBuffer("delete from "
0290: + tableName + " where ");
0291: for (int i = 0; i < attributeNames.size(); i++)
0292: statement.append(attributeNames.get(i).toString()
0293: + "=? and ");
0294: statement.delete(statement.length() - 6, statement.length());
0295: sqlLogger.debug("preparing delete statement: "
0296: + statement.toString());
0297: // Return
0298: return statement.toString();
0299: }
0300:
0301: /**
0302: * Remove an entry from database.
0303: * @param tableName The table to remove object from.
0304: * @param attributes The attributes which identify the object.
0305: * Equality is assumed with each attribute and it's value.
0306: */
0307: public DatabaseStatistics remove(Connection connection,
0308: String tableName, Map attributes) {
0309: DatabaseStatistics stats = new DatabaseStatistics();
0310: // Clear null values from attributes
0311: attributes = new HashMap(attributes);
0312: Vector nullVector = new Vector();
0313: nullVector.add(null);
0314: attributes.values().removeAll(nullVector);
0315: // Make statement
0316: Vector attributeNames = new Vector(attributes.keySet());
0317: String statement = getRemoveStatement(tableName, attributeNames);
0318: // Prepare
0319: PreparedStatement pstmt;
0320: try {
0321: pstmt = connection.prepareStatement(statement);
0322: for (int i = 0; i < attributeNames.size(); i++)
0323: pstmt.setObject(i + 1, getSQLValue(attributes
0324: .get(attributeNames.get(i))));
0325: } catch (Exception e) {
0326: throw new StoreException("cannot prepare statement: "
0327: + statement, e);
0328: }
0329: // Execute
0330: try {
0331: long startTime = System.currentTimeMillis();
0332: pstmt.executeUpdate();
0333: long endTime = System.currentTimeMillis();
0334: perfLogger.log("Remove statement execution",
0335: new int[] { (int) (endTime - startTime) });
0336: stats.setDeleteCount(1);
0337: stats.setDeleteTime(endTime - startTime);
0338: } catch (Exception e) {
0339: throw new StoreException("exception while sql delete", e);
0340: } finally {
0341: try {
0342: pstmt.close();
0343: } catch (Exception e) {
0344: logger.debug("unable to close statement", e);
0345: }
0346: }
0347: // Return
0348: return stats;
0349: }
0350:
0351: /**
0352: * Drop the table with given name.
0353: * This method is not called directly, but from <code>ensureTable</code>.
0354: * @param tableName The table to drop.
0355: */
0356: protected DatabaseStatistics dropTable(Connection connection,
0357: String tableName) {
0358: DatabaseStatistics stats = new DatabaseStatistics();
0359: // Create statement
0360: String statement = "drop table " + tableName;
0361: // Execute statement
0362: long startTime = System.currentTimeMillis();
0363: executeUpdate(connection, statement);
0364: long endTime = System.currentTimeMillis();
0365: stats.setSchemaCount(1);
0366: stats.setSchemaTime(endTime - startTime);
0367: return stats;
0368: }
0369:
0370: /**
0371: * Get the create table statement before the attributes part.
0372: */
0373: protected String getCreateTableStatement(Connection connection,
0374: String tableName) {
0375: return "create table " + tableName;
0376: }
0377:
0378: /**
0379: * Create table with given name, attribute types, and keys.
0380: * This method is not called directly, but from <code>ensureTable</code>.
0381: * @param tableName The table to create.
0382: * @param attributeTypes The attribute names together with which
0383: * java class they should hold.
0384: */
0385: protected DatabaseStatistics createTable(Connection connection,
0386: String tableName, Map attributeTypes, List keyAttributeNames) {
0387: DatabaseStatistics stats = new DatabaseStatistics();
0388: // Create statement
0389: StringBuffer statement = new StringBuffer(
0390: getCreateTableStatement(connection, tableName) + " (");
0391: Iterator iterator = attributeTypes.entrySet().iterator();
0392: while (iterator.hasNext()) {
0393: Map.Entry entry = (Map.Entry) iterator.next();
0394: statement.append(entry.getKey().toString()
0395: + " "
0396: + getSQLTypeName(getSQLType(((Class) entry
0397: .getValue()))) + ",");
0398: }
0399: if ((keyAttributeNames != null)
0400: && (keyAttributeNames.size() > 0)) {
0401: statement.append(" primary key (");
0402: for (int i = 0; i < keyAttributeNames.size(); i++)
0403: statement.append(keyAttributeNames.get(i) + ",");
0404: statement
0405: .delete(statement.length() - 1, statement.length());
0406: statement.append(") ");
0407: }
0408: statement.delete(statement.length() - 1, statement.length());
0409: statement.append(")");
0410: // Execute statement
0411: long startTime = System.currentTimeMillis();
0412: executeUpdate(connection, statement.toString());
0413: long endTime = System.currentTimeMillis();
0414: stats.setSchemaCount(1);
0415: stats.setSchemaTime(endTime - startTime);
0416: // Create initial indexes (currently all attributes will be indexed)
0417: // Do not create for reserved tables
0418: if ((!tableName.equalsIgnoreCase("ids"))
0419: && (!tableName.equalsIgnoreCase("tablemap"))
0420: && (!tableName.equalsIgnoreCase("nodes"))
0421: && (!tableName.equalsIgnoreCase("classes")))
0422: stats.add(createIndexes(connection, tableName,
0423: attributeTypes));
0424: return stats;
0425: }
0426:
0427: /**
0428: * Get the statement to drop a column.
0429: */
0430: protected String getDropColumnStatement(String tableName,
0431: String columnName) {
0432: return "alter table " + tableName + " drop column "
0433: + columnName;
0434: }
0435:
0436: /**
0437: * Get the statement to add a column to a table.
0438: */
0439: protected String getAddColumnStatement(String tableName,
0440: String columnName, String columnType) {
0441: return "alter table " + tableName + " add column " + columnName
0442: + " " + columnType;
0443: }
0444:
0445: /**
0446: * Drop a column from a table.
0447: * @param connection The connection object.
0448: * @param tableName The table to drop column from.
0449: * @param columnName The column to drop.
0450: */
0451: protected DatabaseStatistics dropColumn(Connection connection,
0452: String tableName, String columnName) {
0453: DatabaseStatistics stats = new DatabaseStatistics();
0454: long startTime = System.currentTimeMillis();
0455: executeUpdate(connection, getDropColumnStatement(tableName,
0456: columnName));
0457: long endTime = System.currentTimeMillis();
0458: stats.setSchemaCount(1);
0459: stats.setSchemaTime(endTime - startTime);
0460: return stats;
0461: }
0462:
0463: /**
0464: * Add a column to a table.
0465: * @param connection The connection object.
0466: * @param tableName The table to drop column from.
0467: * @param columnName The column to create.
0468: * @param columnType The column type to create.
0469: */
0470: protected DatabaseStatistics addColumn(Connection connection,
0471: String tableName, String columnName, String columnType) {
0472: DatabaseStatistics stats = new DatabaseStatistics();
0473: long startTime = System.currentTimeMillis();
0474: executeUpdate(connection, getAddColumnStatement(tableName,
0475: columnName, columnType));
0476: long endTime = System.currentTimeMillis();
0477: stats.setSchemaCount(1);
0478: stats.setSchemaTime(endTime - startTime);
0479: return stats;
0480: }
0481:
0482: /**
0483: * Alter the table. Implementation: Not all databases support
0484: * altering multiple columns, so all columns are separately modified.
0485: * @param connection The connection object.
0486: * @param tableName The table name.
0487: * @param removedAttributes Attribute names which should be removed.
0488: * @param addedAttributes Attribute names which should be added.
0489: * @param attributeTypes Types in form of Classes to given names.
0490: */
0491: protected DatabaseStatistics alterTable(Connection connection,
0492: String tableName, List removedAttributes,
0493: List addedAttributes, Map attributeTypes,
0494: List keyAttributeNames) {
0495: DatabaseStatistics stats = new DatabaseStatistics();
0496: if ((removedAttributes.size() == 0)
0497: && (addedAttributes.size() == 0)) {
0498: logger
0499: .debug("table '"
0500: + tableName
0501: + "' schema matches class, no modification required.");
0502: return stats;
0503: }
0504: logger.debug("table layout mismatch for table '" + tableName
0505: + "': removed columns: " + removedAttributes
0506: + ", added columns: " + addedAttributes);
0507: // Assemble statements and execute them
0508: for (int i = 0; i < removedAttributes.size(); i++) {
0509: String attributeName = (String) removedAttributes.get(i);
0510: stats.add(dropColumn(connection, tableName, attributeName));
0511: }
0512: for (int i = 0; i < addedAttributes.size(); i++) {
0513: String attributeName = (String) addedAttributes.get(i);
0514: String sqlTypeName = getSQLTypeName(getSQLType((Class) attributeTypes
0515: .get(addedAttributes.get(i))));
0516: stats.add(addColumn(connection, tableName, attributeName,
0517: sqlTypeName));
0518: }
0519: // Create initial indexes (currently all new attributes will be indexed)
0520: HashMap addedTypes = new HashMap();
0521: for (int i = 0; i < addedAttributes.size(); i++) {
0522: Object name = addedAttributes.get(i);
0523: addedTypes.put(name, attributeTypes.get(name));
0524: }
0525: stats.add(createIndexes(connection, tableName, addedTypes));
0526: return stats;
0527: }
0528:
0529: /**
0530: * Get index creation statement for a given table and field.
0531: * @return The statement to use, or null, of no such index can be
0532: * created.
0533: */
0534: protected String getCreateIndexStatement(String indexName,
0535: String tableName, String field, Class fieldClass) {
0536: if (fieldClass.equals(byte[].class))
0537: return null;
0538: return "create index " + indexName + " on " + tableName + " ("
0539: + field + ")";
0540: }
0541:
0542: /**
0543: * Get an unused index name.
0544: */
0545: protected String getCreateIndexName(Connection connection,
0546: String tableName, String field) {
0547: try {
0548: // Get max length
0549: DatabaseMetaData dmd = connection.getMetaData();
0550: int maxTableNameLength = dmd.getMaxTableNameLength();
0551: if (maxTableNameLength == 0)
0552: maxTableNameLength = Integer.MAX_VALUE;
0553: // Check whether trivial name is good enough
0554: String result = tableName + "_idx_" + field;
0555: if (result.length() <= maxTableNameLength)
0556: return result;
0557: // Assemble all indexes to table
0558: HashSet indexNames = new HashSet();
0559: ResultSet rs = dmd.getIndexInfo(null, null, tableName,
0560: false, true);
0561: while (rs.next()) {
0562: String indexName = rs.getString("INDEX_NAME");
0563: if (indexName != null)
0564: indexNames.add(indexName.toLowerCase());
0565: }
0566: rs.close();
0567: // Create an index name which does not currently exist
0568: if (logger.isDebugEnabled())
0569: logger.debug("creating index name, existing indexes: "
0570: + indexNames);
0571: result = result.substring(0, maxTableNameLength - 4)
0572: .toLowerCase();
0573: int nameIndex = 1;
0574: while (indexNames.contains(result + nameIndex))
0575: nameIndex++;
0576: // Return abbr. name
0577: logger.debug("new index name: " + result + nameIndex);
0578: return result + nameIndex;
0579: } catch (SQLException e) {
0580: throw new StoreException(
0581: "Could not compute approriate index name.", e);
0582: }
0583: }
0584:
0585: /**
0586: * Create indexes to the given attributes.
0587: * @param connection The SQL connection.
0588: * @param tableName The table to create indexes to.
0589: * @param attributeTypes The attributes and their types to create indexes to.
0590: */
0591: protected DatabaseStatistics createIndexes(Connection connection,
0592: String tableName, Map attributeTypes) {
0593: DatabaseStatistics stats = new DatabaseStatistics();
0594: Iterator entries = attributeTypes.entrySet().iterator();
0595: while (entries.hasNext()) {
0596: Map.Entry entry = (Map.Entry) entries.next();
0597: String indexName = getCreateIndexName(connection,
0598: tableName, entry.getKey().toString());
0599: String statement = getCreateIndexStatement(indexName,
0600: tableName, entry.getKey().toString(), (Class) entry
0601: .getValue());
0602: if (statement != null) {
0603: long startTime = System.currentTimeMillis();
0604: executeUpdate(connection, statement);
0605: long endTime = System.currentTimeMillis();
0606: stats.setSchemaCount(stats.getSchemaCount() + 1);
0607: stats.setSchemaTime(stats.getSchemaTime()
0608: + (endTime - startTime));
0609: }
0610: }
0611: return stats;
0612: }
0613:
0614: /**
0615: * Get the data types of a given table.
0616: * @return A map of names with the sql type number as value.
0617: */
0618: protected HashMap getTableAttributeTypes(Connection connection,
0619: String tableName) throws SQLException {
0620: DatabaseMetaData dmd = connection.getMetaData();
0621: ResultSet rs = dmd.getColumns(null, null, tableName, null);
0622: HashMap databaseAttributeTypes = new HashMap();
0623: while (rs.next()) {
0624: // The tablename was a wildcard, so make sure that we
0625: // get the right table's column (thanks Daniel)
0626: if (rs.getString("TABLE_NAME").equalsIgnoreCase(tableName)) {
0627: int type = getTableAttributeType(rs);
0628: databaseAttributeTypes.put(rs.getObject("COLUMN_NAME")
0629: .toString().toLowerCase(), new Integer(type));
0630: }
0631: }
0632: rs.close();
0633: return databaseAttributeTypes;
0634: }
0635:
0636: /**
0637: * Override this method to get different types for attributes than
0638: * the database reports.
0639: */
0640: protected int getTableAttributeType(ResultSet rs)
0641: throws SQLException {
0642: return rs.getInt("DATA_TYPE");
0643: }
0644:
0645: /**
0646: * Ensure that table exists in database. Implementation does
0647: * not check keys. If keys differ, this implementation will not correct
0648: * the problem. Column renames are not detected, if a column is renamed,
0649: * the old column will be dropped and a new column will be created.
0650: * @param tableName The table to check.
0651: * @param attributeTypes The attribute names together with which
0652: * java class they should hold.
0653: * @param keyAttributeNames The keys of table.
0654: */
0655: public DatabaseStatistics ensureTable(Connection connection,
0656: String tableName, Map attributeTypes,
0657: List keyAttributeNames, boolean create) {
0658: DatabaseStatistics stats = new DatabaseStatistics();
0659: try {
0660: if (create) {
0661: // Query whether table exists
0662: HashMap databaseAttributeTypes = getTableAttributeTypes(
0663: connection, tableName);
0664: // Diff the database schema and attribute schema
0665: logger
0666: .debug("comparing database schema with class, database has: "
0667: + databaseAttributeTypes
0668: + ", class has: " + attributeTypes);
0669: if (databaseAttributeTypes.size() == 0) {
0670: // No columns found, better create that table
0671: logger.debug("table '" + tableName
0672: + "' did not exist, creating.");
0673: stats.add(createTable(connection, tableName,
0674: attributeTypes, keyAttributeNames));
0675: } else {
0676: // Columns found, so make diff
0677: // Note, that not only removed and added columns count, but
0678: // also columns which types have changed, since that also
0679: // means deleting and adding the column
0680: Vector removedAttributes = new Vector(
0681: databaseAttributeTypes.keySet());
0682: removedAttributes
0683: .removeAll(attributeTypes.keySet());
0684: Vector addedAttributes = new Vector(attributeTypes
0685: .keySet());
0686: addedAttributes.removeAll(databaseAttributeTypes
0687: .keySet());
0688: Vector changedAttributes = new Vector(
0689: databaseAttributeTypes.keySet());
0690: changedAttributes
0691: .retainAll(attributeTypes.keySet());
0692: Iterator changedAttributesIterator = changedAttributes
0693: .iterator();
0694: while (changedAttributesIterator.hasNext()) {
0695: String attribute = (String) changedAttributesIterator
0696: .next().toString();
0697: if (getSQLTypeName(
0698: ((Integer) databaseAttributeTypes
0699: .get(attribute)).intValue())
0700: .equals(
0701: getSQLTypeName(getSQLType((Class) attributeTypes
0702: .get(attribute)))))
0703: changedAttributesIterator.remove(); // Remove if type matches
0704: }
0705: removedAttributes.addAll(changedAttributes);
0706: addedAttributes.addAll(changedAttributes);
0707: // If all columns are to be removed, drop the table and re-create
0708: // it, or else first drop the old columns and add the new ones
0709: databaseAttributeTypes.remove("persistence_id");
0710: databaseAttributeTypes.remove("persistence_start");
0711: databaseAttributeTypes.remove("persistence_end");
0712: databaseAttributeTypes.remove("persistence_txend");
0713: databaseAttributeTypes
0714: .remove("persistence_txstart");
0715: databaseAttributeTypes
0716: .remove("persistence_txstartid");
0717: databaseAttributeTypes
0718: .remove("persistence_txendid");
0719: if ((removedAttributes.size() == databaseAttributeTypes
0720: .size())
0721: && (removedAttributes.size() > 0)) {
0722: // Uh-oh, all attributes changed, re-create table
0723: logger
0724: .debug("table '"
0725: + tableName
0726: + "' will be re-created, because all attributes changed apparently, removed: "
0727: + removedAttributes
0728: + ", added: " + addedAttributes);
0729: stats.add(dropTable(connection, tableName));
0730: stats.add(createTable(connection, tableName,
0731: attributeTypes, keyAttributeNames));
0732: } else if ((removedAttributes.size() > 0)
0733: || (addedAttributes.size() > 0)) {
0734: // Partial change, only change what is necessary
0735: logger.debug("table '" + tableName
0736: + "' has a partial change, removing: "
0737: + removedAttributes + ", adding: "
0738: + addedAttributes);
0739: stats.add(alterTable(connection, tableName,
0740: removedAttributes, addedAttributes,
0741: attributeTypes, keyAttributeNames));
0742: } else {
0743: logger.debug("nothing to change on table: "
0744: + tableName);
0745: }
0746: }
0747: } else {
0748: logger
0749: .debug("table is not to be created: "
0750: + tableName);
0751: }
0752: // Remember these attributes
0753: setAttributes(tableName, attributeTypes);
0754: } catch (Exception e) {
0755: throw new StoreException("could not ensure table exists: "
0756: + tableName, e);
0757: }
0758: // Return
0759: return stats;
0760: }
0761:
0762: /**
0763: * Get the limit component of statement, if it can be expressed in
0764: * the current database with simple statement part.
0765: * @param limits The limits to apply.
0766: */
0767: protected String getLimitStatement(String statement, Limits limits,
0768: List types) {
0769: return statement;
0770: }
0771:
0772: /**
0773: * Get the count statement for the given statement.
0774: */
0775: protected String getCountStatement(String stmt) {
0776: return "select count(*) from (" + stmt + ") as cr";
0777: }
0778:
0779: /**
0780: * Get the table declaration for a select statment.
0781: */
0782: protected String getTableDeclaration(String tableName, String alias) {
0783: if (alias == null)
0784: return tableName;
0785: else
0786: return tableName + " as " + alias;
0787: }
0788:
0789: /**
0790: * Assemble the query columns of a given term.
0791: */
0792: protected String getQuerySource(TableTerm term, Set queryColumns,
0793: List types) {
0794: // Determine name
0795: String name = term.getName();
0796: // List all attributes one-by-one
0797: StringBuffer result = new StringBuffer();
0798: Map attributeTypes = getAttributes(term.getTableName());
0799: if (attributeTypes == null)
0800: throw new StoreException(
0801: "attributes types not present for table: "
0802: + term.getTableName() + ", map: "
0803: + tableAttributes);
0804: Iterator attributeEntryIterator = attributeTypes.entrySet()
0805: .iterator();
0806: while (attributeEntryIterator.hasNext()) {
0807: Map.Entry entry = (Map.Entry) attributeEntryIterator.next();
0808: String attributeName = (String) entry.getKey();
0809: Class attributeType = (Class) entry.getValue();
0810: if (!attributeName.startsWith("persistence_")) {
0811: result.append(name + "." + attributeName + ",");
0812: types.add(attributeType);
0813: checkAttribute(queryColumns, attributeName);
0814: }
0815: }
0816: if ((result.length() > 0)
0817: && (result.charAt(result.length() - 1) == ','))
0818: result.deleteCharAt(result.length() - 1);
0819: return result.toString();
0820: }
0821:
0822: /**
0823: * Check the attribute whether it's already contained in the
0824: * columns.
0825: */
0826: private void checkAttribute(Set queryColumns, String attributeName) {
0827: if (!queryColumns.add(attributeName))
0828: throw new StoreException(
0829: "query has multiple columns with same name, try to add: "
0830: + attributeName
0831: + ", attributes until now: " + queryColumns);
0832: }
0833:
0834: /**
0835: * Assemble the query columns of select statement.
0836: * @param stmt The statement to get query source from.
0837: * @param types The type list to fill in. Each queried column's
0838: * type should be inserted into this list in order.
0839: * @return The columns part of the select statement.
0840: */
0841: protected String getQuerySource(QueryStatement stmt, List types) {
0842: if (logger.isDebugEnabled())
0843: logger.debug("computing query source from: "
0844: + stmt.getSelectTerms());
0845: HashSet queryColumns = new HashSet();
0846: StringBuffer querySource = new StringBuffer();
0847: for (int i = 0; i < stmt.getSelectTerms().size(); i++) {
0848: if (querySource.length() > 0)
0849: querySource.append(",");
0850: TableTerm term = (TableTerm) stmt.getSelectTerms().get(i);
0851: if (term instanceof ReferenceTerm) {
0852: logger.debug("adding reference term: " + term);
0853: // This is specifically an attribute
0854: ReferenceTerm refTerm = (ReferenceTerm) term;
0855: querySource.append(refTerm.getName() + "."
0856: + refTerm.getColumnName());
0857: if (refTerm.getColumnAlias() != null) {
0858: querySource.append(" as "
0859: + refTerm.getColumnAlias());
0860: checkAttribute(queryColumns, refTerm
0861: .getColumnAlias());
0862: } else {
0863: checkAttribute(queryColumns, refTerm
0864: .getColumnName());
0865: }
0866: Class attributeType = getAttributeType(term
0867: .getTableName(), ((ReferenceTerm) term)
0868: .getColumnName());
0869: types.add(attributeType);
0870: } else {
0871: if (logger.isDebugEnabled())
0872: logger.debug("adding table term: " + term
0873: + ", left terms: "
0874: + term.getLeftTableTerms());
0875: // This is a table
0876: querySource.append(getQuerySource(term, queryColumns,
0877: types));
0878: for (int o = 0; o < term.getLeftTableTerms().size(); o++) {
0879: TableTerm leftTableTerm = (TableTerm) term
0880: .getLeftTableTerms().get(o);
0881: String sourcePart = getQuerySource(leftTableTerm,
0882: queryColumns, types);
0883: if (sourcePart.length() > 0) {
0884: if (querySource.length() > 0)
0885: querySource.append(",");
0886: querySource.append(sourcePart);
0887: }
0888: }
0889: }
0890: }
0891: // Add persistence id/startserial/endserial
0892: TableTerm firstTerm = null;
0893: if (stmt.getSelectTerms().size() > 0)
0894: firstTerm = (TableTerm) stmt.getSelectTerms().get(0);
0895: if ((!"tablemap".equalsIgnoreCase(firstTerm.getTableName()))
0896: && (!"classes".equalsIgnoreCase(firstTerm
0897: .getTableName()))
0898: && (!"ids".equalsIgnoreCase(firstTerm.getTableName()))
0899: && (!"nodes".equalsIgnoreCase(firstTerm.getTableName()))
0900: && (stmt.getMode() != QueryStatement.MODE_VIEW)) {
0901: // Add persistence id
0902: if (querySource.length() > 0)
0903: querySource.append(",");
0904: querySource.append(firstTerm.getName() + ".persistence_id");
0905: types.add(Long.class);
0906: // Add serials
0907: Vector terms = new Vector();
0908: terms.add(firstTerm);
0909: terms.addAll(firstTerm.getLeftTableTerms());
0910: for (int i = 0; i < terms.size(); i++) {
0911: querySource.append(",");
0912: querySource
0913: .append(firstTerm.getName()
0914: + ".persistence_start as persistence_start"
0915: + i);
0916: types.add(Long.class);
0917: querySource.append(",");
0918: querySource.append(firstTerm.getName()
0919: + ".persistence_end as persistence_end" + i);
0920: types.add(Long.class);
0921: }
0922: }
0923: // Return
0924: return querySource.toString();
0925: }
0926:
0927: /**
0928: * Transform the expression. This method is called on each subsequent
0929: * expressions too, so implementation does not have to be recursive.
0930: * @param expr The expression to possibly transform.
0931: * @return A transformed expression.
0932: */
0933: protected Expression transformExpression(Expression expr) {
0934: return expr;
0935: }
0936:
0937: /**
0938: * Checks whether a list of table terms contains a given reference term,
0939: * but not by using the standard <code>equals()</code> method, but by
0940: * comparing a reference term with own equality.
0941: */
0942: private boolean containsReferenceTerm(List terms, ReferenceTerm term) {
0943: for (int i = 0; i < terms.size(); i++) {
0944: TableTerm objRaw = (TableTerm) terms.get(i);
0945: if (objRaw instanceof ReferenceTerm) {
0946: ReferenceTerm obj = (ReferenceTerm) objRaw;
0947: if ((obj.equals(term))
0948: && (obj.getColumnName().equals(term
0949: .getColumnName())))
0950: return true;
0951: }
0952: }
0953: return false;
0954: }
0955:
0956: /**
0957: * Select objects from database as ordered list of attribute maps.
0958: * @param connection The connection to run statements in.
0959: * @param stmt The query statement.
0960: * @param limits The limits of the result. (Offset, maximum result count)
0961: * @param result The result object.
0962: */
0963: public DatabaseStatistics search(Connection connection,
0964: QueryStatement stmt, Limits limits,
0965: SearchResult searchResult) {
0966: DatabaseStatistics stats = new DatabaseStatistics();
0967: Expression expression = stmt.getQueryExpression();
0968: List orderBys = stmt.getOrderByList();
0969: // Assemble statement:
0970: // - Parse conditions, determine all tables to join
0971: // - Create statement without orderbys and limits
0972: // - Run count statement to determine full count
0973: // - Apply limits and orderbys to statement
0974: // - Run statement to get results
0975:
0976: // Compute query columns and tables
0977: Vector types = new Vector();
0978: String querySource = getQuerySource(stmt, types);
0979: if (logger.isDebugEnabled())
0980: logger.debug("query source: " + querySource
0981: + ", type vector: " + types + ", select terms: "
0982: + stmt.getSelectTerms());
0983: // So first, parse conditions, also compute the conditions string
0984: // in the process. This could be easily written in a recursive
0985: // algorithm, but there are too many dependencies.
0986: StringBuffer conditionPart = new StringBuffer(); // Will hold the where statement
0987: if ((expression != null) && (expression.size() > 0))
0988: conditionPart.append(" where ");
0989: HashSet tables = new HashSet(); // Will hold table specifier terms
0990: tables.addAll(stmt.getSelectTerms());
0991: Vector statementValues = new Vector(); // Will hold values for '?' signs in-order
0992: // Here comes the algorithm to walk the expression tree
0993: if (logger.isDebugEnabled())
0994: logger
0995: .debug("original expression, that will be translated to sql: "
0996: + expression);
0997: Stack openNodes = new Stack(); // Used in traversing the expression tree
0998: if (expression != null)
0999: openNodes.push(new Vector(transformExpression(expression)));
1000: while (!openNodes.isEmpty()) {
1001: // Select the current expression part list
1002: Vector parts = (Vector) openNodes.peek();
1003: // If there are no parts, then return
1004: if (parts.size() == 0) {
1005: openNodes.pop();
1006: if (!openNodes.isEmpty())
1007: conditionPart.append(")"); // Close sub-expression
1008: continue; // 20 goto 10
1009: }
1010: // Process the most left symbol
1011: Object nextPart = parts.remove(0);
1012: // There are basically 4 cases depending on the class of nextPart
1013: if (nextPart instanceof Expression) {
1014: // It is an expression, so start new block and push expression
1015: // items
1016: conditionPart.append("(");
1017: openNodes.push(new Vector(
1018: transformExpression((Expression) nextPart)));
1019: }
1020: if (nextPart instanceof String) {
1021: // It is an operator, so just append
1022: conditionPart.append(" " + nextPart.toString() + " ");
1023: }
1024: if (nextPart instanceof ConstantTerm) {
1025: // It is a constant, add ? and remember value
1026: Object value = ((ConstantTerm) nextPart).getValue();
1027: if (value instanceof Collection) {
1028: if (logger.isDebugEnabled())
1029: logger
1030: .debug("detected collection constant value: "
1031: + value);
1032: // Add multiple values as an enumeration
1033: Iterator valueIterator = ((Collection) value)
1034: .iterator();
1035: conditionPart.append(" (");
1036: while (valueIterator.hasNext()) {
1037: Object singleValue = valueIterator.next();
1038: if (singleValue == null) {
1039: conditionPart.append("null,");
1040: } else {
1041: conditionPart.append("?,");
1042: statementValues.add(singleValue);
1043: }
1044: }
1045: if (conditionPart
1046: .charAt(conditionPart.length() - 1) == ',')
1047: conditionPart.delete(
1048: conditionPart.length() - 1,
1049: conditionPart.length());
1050: conditionPart.append(") ");
1051: } else {
1052: if (logger.isDebugEnabled())
1053: logger.debug("detected single constant value: "
1054: + value);
1055: // Add a single value
1056: if (value == null) {
1057: conditionPart.append(" null ");
1058: } else {
1059: conditionPart.append(" ? ");
1060: statementValues.add(value);
1061: }
1062: }
1063: }
1064: if (nextPart instanceof ReferenceTerm) {
1065: // It is a reference type, remember name
1066: ReferenceTerm term = (ReferenceTerm) nextPart;
1067: if (!stmt.getAllLeftTableTerms().contains(term)) {
1068: if (logger.isDebugEnabled())
1069: logger.debug("adding table: " + term + "."
1070: + term.getColumnName()
1071: + ", as all left terms are: "
1072: + stmt.getAllLeftTableTerms());
1073: tables.add(term);
1074: }
1075: conditionPart.append(" " + term.getName() + "."
1076: + term.getColumnName() + " ");
1077: }
1078: }
1079: // Determine whether select will be distinct. Currently it is _not_
1080: // distinct if some field returned would be blob type.
1081: boolean isDistinct = !types.contains(byte[].class);
1082: // Create tables full part (with left joins)
1083: logger.debug("creating tables part...");
1084: StringBuffer tablesPart = new StringBuffer();
1085: Iterator tableIterator = tables.iterator();
1086: while (tableIterator.hasNext()) {
1087: TableTerm tableTerm = (TableTerm) tableIterator.next();
1088: if (stmt.getAllLeftTableTerms().contains(tableTerm))
1089: continue; // Do not include tables which will be included as left terms
1090: tablesPart.append(getTableDeclaration(tableTerm
1091: .getTableName(), tableTerm.getAlias()));
1092: if (stmt.getSelectTerms().contains(tableTerm)) {
1093: // This is a selected table, so include left terms
1094: for (int i = 0; i < tableTerm.getLeftTableTerms()
1095: .size(); i++) {
1096: TableTerm leftTableTerm = (TableTerm) tableTerm
1097: .getLeftTableTerms().get(i);
1098: String leftTableName = null;
1099: tablesPart.append(" left join ");
1100: tablesPart.append(getTableDeclaration(leftTableTerm
1101: .getTableName(), leftTableTerm.getAlias()));
1102: leftTableName = leftTableTerm.getTableName();
1103: tablesPart.append(" on (" + tableTerm.getName()
1104: + ".persistence_id = "
1105: + leftTableTerm.getName()
1106: + ".persistence_id)");
1107: }
1108: }
1109: tablesPart.append(",");
1110: }
1111: tablesPart.delete(tablesPart.length() - 1, tablesPart.length());
1112: // Calculate order by
1113: StringBuffer orderByTerm = new StringBuffer();
1114: if ((orderBys != null) && (orderBys.size() > 0)) {
1115: orderByTerm.append(" order by ");
1116: for (int i = 0; i < orderBys.size(); i++) {
1117: OrderBy orderBy = (OrderBy) orderBys.get(i);
1118: ReferenceTerm orderReferenceTerm = new ReferenceTerm(
1119: orderBy.getReferenceTerm());
1120: if (((stmt.getMode() == QueryStatement.MODE_FIND) && (!stmt
1121: .getSelectTerms().contains(
1122: orderBy.getReferenceTerm())))
1123: || ((stmt.getMode() == QueryStatement.MODE_VIEW) && (!containsReferenceTerm(
1124: stmt.getSelectTerms(), orderBy
1125: .getReferenceTerm())))) {
1126: // Order by is not referencing the main tables. To maintain
1127: // distinct select, we must add this attribute to result
1128: querySource += "," + orderReferenceTerm.getName()
1129: + "." + orderReferenceTerm.getColumnName()
1130: + " as ordercol" + i;
1131: orderReferenceTerm.setColumnAlias("ordercol" + i);
1132: Class attributeType = getAttributeType(
1133: orderReferenceTerm.getTableName(),
1134: orderReferenceTerm.getColumnName());
1135: types.add(attributeType);
1136: }
1137: // Add to order term
1138: if (orderReferenceTerm.getColumnAlias() != null)
1139: orderByTerm.append(orderReferenceTerm
1140: .getColumnAlias());
1141: else
1142: orderByTerm.append(orderReferenceTerm.getName()
1143: + "." + orderReferenceTerm.getColumnName());
1144: orderByTerm
1145: .append(orderBy.getDirection() == OrderBy.ASCENDING ? " asc,"
1146: : " desc,");
1147: }
1148: orderByTerm.delete(orderByTerm.length() - 1, orderByTerm
1149: .length());
1150: }
1151: if (logger.isDebugEnabled())
1152: logger.debug("select types: " + types);
1153: // Create count statement
1154: StringBuffer subStatement = new StringBuffer("select "
1155: + (isDistinct ? "distinct " : "") + querySource
1156: + " from ");
1157: subStatement.append(tablesPart.toString());
1158: subStatement.append(conditionPart.toString());
1159: StringBuffer statement = new StringBuffer(
1160: getCountStatement(subStatement.toString()));
1161: logger.debug("preparing counting statement: "
1162: + statement.toString());
1163: String countStatement = statement.toString();
1164: // Prepare count statement
1165: PreparedStatement countPstmt;
1166: try {
1167: countPstmt = connection.prepareStatement(countStatement);
1168: for (int i = 0; i < statementValues.size(); i++) {
1169: Object value = getSQLValue(statementValues.get(i));
1170: logger.debug("setting statement parameter #" + i + ": "
1171: + value);
1172: sqlLogger.debug("setting statement parameter #" + i
1173: + ": " + value);
1174: countPstmt.setObject(i + 1, value);
1175: }
1176: } catch (Exception e) {
1177: throw new StoreException("cannot prepare statement: "
1178: + statement.toString(), e);
1179: }
1180: // Now create full statement (add order and limit)
1181: statement = new StringBuffer("select "
1182: + (isDistinct ? "distinct " : "") + querySource
1183: + " from ");
1184: statement.append(tablesPart.toString());
1185: statement.append(conditionPart.toString());
1186: statement.append(orderByTerm.toString());
1187: // Add limits
1188: if (limits != null)
1189: statement = new StringBuffer(getLimitStatement(statement
1190: .toString(), limits, types));
1191: // Run statement
1192: Vector result = new Vector();
1193: PreparedStatement pstmt = null;
1194: if ((limits == null) || (!limits.isEmpty())) {
1195: // Prepare
1196: try {
1197: pstmt = connection.prepareStatement(statement
1198: .toString());
1199: for (int i = 0; i < statementValues.size(); i++) {
1200: Object value = getSQLValue(statementValues.get(i));
1201: logger.debug("setting statement parameter #" + i
1202: + ": " + value);
1203: sqlLogger.debug("setting statement parameter #" + i
1204: + ": " + value);
1205: pstmt.setObject(i + 1, value);
1206: }
1207: } catch (Exception e) {
1208: throw new StoreException("cannot prepare statement: "
1209: + statement.toString(), e);
1210: }
1211: // Execute
1212: try {
1213: sqlLogger.debug("running select statement: "
1214: + statement.toString());
1215: long startTime = System.currentTimeMillis();
1216: prepareStatement(pstmt, limits);
1217: ResultSet rs = pstmt.executeQuery();
1218: long endTime = System.currentTimeMillis();
1219: perfLogger.log("Query statement execution",
1220: new int[] { (int) (endTime - startTime) });
1221: stats.setSelectCount(stats.getSelectCount() + 1);
1222: stats.setSelectTime(stats.getSelectTime()
1223: + (endTime - startTime));
1224: ResultSetMetaData rsmd = rs.getMetaData();
1225: // Get result and pack the attributes into a map
1226: prepareResultSet(rs, limits);
1227: while (rs.next()) {
1228: Map attributes = new HashMap();
1229: for (int i = 0; i < rsmd.getColumnCount(); i++) {
1230: String columnName = rsmd.getColumnName(i + 1)
1231: .toLowerCase();
1232: Object columnValue = getJavaValue(rs
1233: .getObject(i + 1), rsmd
1234: .getColumnType(i + 1), (Class) types
1235: .get(i));
1236: if (columnName.startsWith("persistence_start")) {
1237: // Handle persistence_starts
1238: Long previousValue = (Long) attributes
1239: .get("persistence_start");
1240: if ((previousValue == null)
1241: || (previousValue.longValue() < ((Long) columnValue)
1242: .longValue()))
1243: attributes.put("persistence_start",
1244: columnValue);
1245: } else if (columnName
1246: .startsWith("persistence_end")) {
1247: // Handle persistence_ends
1248: Long previousValue = (Long) attributes
1249: .get("persistence_end");
1250: if ((previousValue == null)
1251: || (previousValue.longValue() > ((Long) columnValue)
1252: .longValue()))
1253: attributes.put("persistence_end",
1254: columnValue);
1255: } else {
1256: // Normal attributes
1257: attributes.put(columnName, columnValue);
1258: }
1259: }
1260: result.add(attributes);
1261: }
1262: rs.close();
1263: } catch (Exception e) {
1264: throw new StoreException("exception while sql select",
1265: e);
1266: } finally {
1267: try {
1268: pstmt.close();
1269: } catch (Exception e) {
1270: logger.debug("unable to close statement", e);
1271: }
1272: }
1273: } // End valid limits (running of query)
1274: // Execute count statement if necessary
1275: long resultSize;
1276: if (limits == null) {
1277: // If there are no limits, the result is a full result
1278: resultSize = result.size();
1279: } else if (limits.getSize() >= 0) {
1280: // Size already known (possibly from previous select)
1281: resultSize = limits.getSize();
1282: } else if ((limits.getLimit() > 0)
1283: && (result.size() < limits.getLimit())) {
1284: // Count statement not necessary, this is the last page, we can
1285: // compute the size
1286: resultSize = limits.getOffset() + result.size();
1287: } else {
1288: // We must get the full size the hard way, so select
1289: if (sqlLogger.isDebugEnabled())
1290: sqlLogger.debug("running count statement: "
1291: + countStatement);
1292: try {
1293: long startTime = System.currentTimeMillis();
1294: ResultSet rs = countPstmt.executeQuery();
1295: long endTime = System.currentTimeMillis();
1296: perfLogger.log("Query count statement execution",
1297: new int[] { (int) (endTime - startTime) });
1298: stats.setSelectCount(stats.getSelectCount() + 1);
1299: stats.setSelectTime(stats.getSelectTime()
1300: + (endTime - startTime));
1301: rs.next();
1302: resultSize = rs.getLong(1);
1303: rs.close();
1304: } catch (Exception e) {
1305: throw new StoreException(
1306: "exception while sql select count", e);
1307: } finally {
1308: try {
1309: countPstmt.close();
1310: } catch (Exception e) {
1311: logger.debug("unable to close statement", e);
1312: }
1313: }
1314: }
1315: // Assemble result and return
1316: searchResult.setResultSize(resultSize);
1317: searchResult.setResult(result);
1318: sqlLogger.debug("returning result, size: " + result.size()
1319: + " / " + resultSize);
1320: return stats;
1321: }
1322:
1323: /**
1324: * Convert incoming value from database into java format.
1325: */
1326: protected Object getJavaValue(Object value, int type, Class javaType) {
1327: if (value instanceof Timestamp)
1328: return new Date(((Timestamp) value).getTime());
1329: return value;
1330: }
1331:
1332: /**
1333: * Convert incoming values from java into database acceptable format.
1334: */
1335: protected Object getSQLValue(Object value) {
1336: if ((value != null) && (value instanceof Date))
1337: return new Timestamp(((Date) value).getTime());
1338: return value;
1339: }
1340:
1341: /**
1342: * Get the class for an sql type.
1343: */
1344: protected String getSQLTypeName(int sqltype) {
1345: switch (sqltype) {
1346: case Types.DATE:
1347: case Types.TIME:
1348: case Types.TIMESTAMP:
1349: return "timestamp";
1350: case Types.LONGVARCHAR:
1351: case Types.VARCHAR:
1352: return "text";
1353: case Types.BIT:
1354: case Types.BOOLEAN:
1355: return "boolean";
1356: case Types.INTEGER:
1357: case Types.NUMERIC:
1358: case Types.DECIMAL:
1359: return "integer";
1360: case Types.BIGINT:
1361: return "bigint";
1362: case Types.SMALLINT:
1363: case Types.TINYINT:
1364: return "smallint";
1365: case Types.DOUBLE:
1366: case Types.FLOAT:
1367: case Types.REAL:
1368: return "float";
1369: case Types.CHAR:
1370: return "char";
1371: case Types.BLOB:
1372: case Types.BINARY:
1373: case Types.VARBINARY:
1374: case Types.LONGVARBINARY:
1375: return "blob";
1376: default:
1377: }
1378: throw new StoreException(
1379: "no sql type definition programmed for type: "
1380: + sqltype);
1381: }
1382:
1383: /**
1384: * Get the sql type string for a class.
1385: */
1386: protected int getSQLType(Class type) {
1387: if ((Date.class.equals(type)))
1388: return Types.TIMESTAMP;
1389: if (String.class.equals(type))
1390: return Types.VARCHAR;
1391: if ((boolean.class.equals(type))
1392: || (Boolean.class.equals(type)))
1393: return Types.BOOLEAN;
1394: if ((int.class.equals(type)) || (Integer.class.equals(type)))
1395: return Types.INTEGER;
1396: if ((long.class.equals(type)) || (Long.class.equals(type)))
1397: return Types.BIGINT;
1398: if ((byte.class.equals(type)) || (Byte.class.equals(type)))
1399: return Types.SMALLINT;
1400: if (byte[].class.equals(type))
1401: return Types.BLOB;
1402: if ((double.class.equals(type)) || (Double.class.equals(type)))
1403: return Types.DOUBLE;
1404: if ((float.class.equals(type)) || (Float.class.equals(type)))
1405: return Types.FLOAT;
1406: if ((short.class.equals(type)) || (Short.class.equals(type)))
1407: return Types.SMALLINT;
1408: if ((char.class.equals(type)) || (Character.class.equals(type)))
1409: return Types.CHAR;
1410: throw new StoreException("type: " + type
1411: + " is not an allowed primitive type.");
1412: }
1413:
1414: /**
1415: * Execute update statement. Simply and safely execute the
1416: * given statement.
1417: * @param connection The connection to execute statement on.
1418: * @param statement The statement to execute.
1419: */
1420: protected void executeUpdate(Connection connection, String statement) {
1421: sqlLogger.debug("executing update statement: " + statement);
1422: // Prepare
1423: PreparedStatement pstmt;
1424: try {
1425: pstmt = connection.prepareStatement(statement.toString());
1426: } catch (Exception e) {
1427: throw new StoreException("cannot prepare statement: "
1428: + statement.toString(), e);
1429: }
1430: // Execute
1431: try {
1432: pstmt.executeUpdate();
1433: } catch (Exception e) {
1434: throw new StoreException(
1435: "exception while executing statement: " + statement,
1436: e);
1437: } finally {
1438: try {
1439: pstmt.close();
1440: } catch (Exception e) {
1441: logger.debug("unable to close statement", e);
1442: }
1443: }
1444: }
1445:
1446: /**
1447: * Prepare the sql statment to be executed.
1448: */
1449: protected void prepareStatement(PreparedStatement pstmt,
1450: Limits limits) throws SQLException {
1451: }
1452:
1453: /**
1454: * Prepare the result set to be iterated.
1455: */
1456: protected void prepareResultSet(ResultSet rs, Limits limits)
1457: throws SQLException {
1458: }
1459:
1460: /**
1461: * The generic implementation holds no resources, so do nothing.
1462: */
1463: public void release(ConnectionSource source) {
1464: }
1465:
1466: }
|