| 1 | /** |
| 2 | * Copyright (C) 2006 NetMind Consulting Bt. |
| 3 | * |
| 4 | * This library is free software; you can redistribute it and/or |
| 5 | * modify it under the terms of the GNU Lesser General Public |
| 6 | * License as published by the Free Software Foundation; either |
| 7 | * version 3 of the License, or (at your option) any later version. |
| 8 | * |
| 9 | * This library is distributed in the hope that it will be useful, |
| 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 12 | * Lesser General Public License for more details. |
| 13 | * |
| 14 | * You should have received a copy of the GNU Lesser General Public |
| 15 | * License along with this library; if not, write to the Free Software |
| 16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| 17 | */ |
| 18 | |
| 19 | package hu.netmind.beankeeper.db.impl; |
| 20 | |
| 21 | import java.sql.*; |
| 22 | import java.util.*; |
| 23 | import hu.netmind.beankeeper.parser.*; |
| 24 | import hu.netmind.beankeeper.transaction.*; |
| 25 | import hu.netmind.beankeeper.db.*; |
| 26 | import org.apache.log4j.Logger; |
| 27 | |
| 28 | /** |
| 29 | * HSQL database implementation. |
| 30 | * @author Brautigam Robert |
| 31 | * @version CVS Revision: $Revision$ |
| 32 | */ |
| 33 | public class HSQLDatabaseImpl extends GenericDatabase implements Database |
| 34 | { |
| 35 | private static Logger logger = Logger.getLogger(HSQLDatabaseImpl.class); |
| 36 | |
| 37 | /** |
| 38 | * Issue a checkpoint to the server. This works with embedded |
| 39 | * and client-server case too, because it does not cause the |
| 40 | * server to exit. |
| 41 | */ |
| 42 | public void release() |
| 43 | { |
| 44 | Connection connection = getConnectionSource().getConnection(); |
| 45 | executeUpdate(connection,"checkpoint defrag"); |
| 46 | super.release(); |
| 47 | } |
| 48 | |
| 49 | /** |
| 50 | * Get the limit component of statement, if it can be expressed in |
| 51 | * the current database with simple statement part. |
| 52 | * @param limits The limits to apply. |
| 53 | */ |
| 54 | protected String getLimitStatement(String statement, Limits limits, List types) |
| 55 | { |
| 56 | StringBuffer result = new StringBuffer(statement); |
| 57 | if ( limits.getLimit() > 0 ) |
| 58 | result.append(" limit "+limits.getLimit()); |
| 59 | if ( limits.getOffset() > 0 ) |
| 60 | result.append(" offset "+limits.getOffset()); |
| 61 | return result.toString(); |
| 62 | } |
| 63 | |
| 64 | /** |
| 65 | * Get the class for an sql type. |
| 66 | */ |
| 67 | protected String getSQLTypeName(int sqltype) |
| 68 | { |
| 69 | switch ( sqltype ) |
| 70 | { |
| 71 | case Types.VARCHAR: |
| 72 | return "varchar"; |
| 73 | case Types.BLOB: |
| 74 | case Types.BINARY: |
| 75 | case Types.VARBINARY: |
| 76 | return "binary"; |
| 77 | default: |
| 78 | return super.getSQLTypeName(sqltype); |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | /** |
| 83 | * Fix custom data types not supported by database. |
| 84 | */ |
| 85 | protected int getTableAttributeType(ResultSet rs) |
| 86 | throws SQLException |
| 87 | { |
| 88 | int columnType = rs.getInt("DATA_TYPE"); |
| 89 | // Recognize boolean type |
| 90 | if ( (columnType == Types.DECIMAL) && (rs.getInt("COLUMN_SIZE")==1) ) |
| 91 | columnType = Types.BOOLEAN; |
| 92 | return columnType; |
| 93 | } |
| 94 | |
| 95 | /** |
| 96 | * Get the data types of a given table. |
| 97 | * @return A map of names with the sql type number as value. |
| 98 | */ |
| 99 | protected HashMap getTableAttributeTypes(Connection connection, |
| 100 | String tableName) |
| 101 | throws SQLException |
| 102 | { |
| 103 | return super.getTableAttributeTypes(connection,tableName.toUpperCase()); |
| 104 | } |
| 105 | |
| 106 | /** |
| 107 | * Transform functions for hsql. |
| 108 | */ |
| 109 | protected List transformTerms(List terms) |
| 110 | { |
| 111 | for ( int i=0; i<terms.size(); i++ ) |
| 112 | { |
| 113 | Object term = terms.get(i); |
| 114 | if ( term instanceof ReferenceTerm ) |
| 115 | { |
| 116 | Function function = ((ReferenceTerm) term).getFunction(); |
| 117 | if ( (function!=null) && (function instanceof MathematicalPostfixFunction) && |
| 118 | (((MathematicalPostfixFunction)function).getFunction().equals(">>")) ) |
| 119 | { |
| 120 | // Hsqldb does not support the bitshift operator, so we modify |
| 121 | // the function |
| 122 | MathematicalPostfixFunction mathFunction = (MathematicalPostfixFunction) function; |
| 123 | ((ReferenceTerm)term).setFunction( |
| 124 | new MathematicalPostfixFunction("/",""+ |
| 125 | (1L<<(Long.parseLong(mathFunction.getOperand()))) )); |
| 126 | } |
| 127 | } |
| 128 | } |
| 129 | return terms; |
| 130 | } |
| 131 | |
| 132 | /** |
| 133 | * Transform 'ilike' to upper case like. |
| 134 | * @param expr The expression to possibly transform. |
| 135 | * @return A transformed expression. |
| 136 | */ |
| 137 | protected Expression transformExpression(Expression expr) |
| 138 | { |
| 139 | Expression result = new Expression(expr); |
| 140 | result.clear(); |
| 141 | for ( int i=0; i<expr.size(); i++ ) |
| 142 | { |
| 143 | Object item = expr.get(i); |
| 144 | if ( "ilike".equals(item) ) |
| 145 | { |
| 146 | // Here we need to upper() the argument before and after like |
| 147 | Object arg = result.remove(result.size()-1); |
| 148 | result.add("upper("); |
| 149 | result.add(arg); |
| 150 | result.add(")"); |
| 151 | result.add("like"); |
| 152 | result.add("upper("); |
| 153 | result.add(expr.get(i+1)); |
| 154 | result.add(")"); |
| 155 | i++; // We used an argument |
| 156 | } else { |
| 157 | result.add(item); |
| 158 | } |
| 159 | } |
| 160 | // Transform functions too |
| 161 | transformTerms(result); |
| 162 | return result; |
| 163 | } |
| 164 | |
| 165 | /** |
| 166 | * Get an unused index name. |
| 167 | */ |
| 168 | protected String getCreateIndexName(Connection connection, String tableName, |
| 169 | String field) |
| 170 | { |
| 171 | return super.getCreateIndexName(connection,tableName.toUpperCase(),field); |
| 172 | } |
| 173 | |
| 174 | /** |
| 175 | * Drop a column from a table. HSQL does not drop the column if |
| 176 | * there are indexes to it, so first remove the indexes in question. |
| 177 | * @param connection The connection object. |
| 178 | * @param tableName The table to drop column from. |
| 179 | * @param columnName The column to drop. |
| 180 | */ |
| 181 | protected TransactionStatistics dropColumn(Connection connection, String tableName, |
| 182 | String columnName) |
| 183 | { |
| 184 | TransactionStatistics stats = new TransactionStatistics(); |
| 185 | try |
| 186 | { |
| 187 | logger.debug("detemining indexes before dropping column: "+columnName); |
| 188 | // Determine indexes for given column, and remove them mercilessly |
| 189 | long startTime = System.currentTimeMillis(); |
| 190 | DatabaseMetaData dmd = connection.getMetaData(); |
| 191 | ResultSet rs = dmd.getIndexInfo(null,null,tableName.toUpperCase(),false,false); |
| 192 | while ( rs.next() ) |
| 193 | { |
| 194 | if ( logger.isDebugEnabled() ) |
| 195 | logger.debug("got index '"+rs.getString("INDEX_NAME")+"', it's column is: "+rs.getString("COLUMN_NAME")); |
| 196 | // Got index, but only drop it, when it is about our petit column |
| 197 | if ( columnName.equalsIgnoreCase(rs.getString("COLUMN_NAME")) ) |
| 198 | { |
| 199 | // It is about the column given, so drop it, for great justice |
| 200 | executeUpdate(connection,"drop index "+rs.getString("INDEX_NAME")); |
| 201 | stats.setSchemaCount(stats.getSchemaCount()+1); |
| 202 | } |
| 203 | } |
| 204 | long endTime = System.currentTimeMillis(); |
| 205 | stats.setSchemaCount(stats.getSchemaCount()+1); |
| 206 | stats.setSchemaTime(endTime-startTime); |
| 207 | } catch ( SQLException e ) { |
| 208 | logger.error("error while trying to remove indexes for column: "+tableName+"."+columnName,e); |
| 209 | } |
| 210 | // Now we can drop the column |
| 211 | stats.add(super.dropColumn(connection,tableName,columnName)); |
| 212 | return stats; |
| 213 | } |
| 214 | |
| 215 | /** |
| 216 | * Get the create table statement before the attributes part. |
| 217 | */ |
| 218 | protected String getCreateTableStatement(Connection connection, String tableName) |
| 219 | { |
| 220 | return "create cached table "+tableName; |
| 221 | } |
| 222 | |
| 223 | } |
| 224 | |
| 225 | |
| 226 | |