/* * Copyright (c) 2009, SQL Power Group Inc. * * This file is part of SQL Power Library. * * SQL Power Library is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * SQL Power Library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package ca.sqlpower.sql.jdbcwrapper; import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.sql.Types; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.TreeSet; import org.apache.log4j.Logger; import ca.sqlpower.sql.CachedRowSet; import ca.sqlpower.util.SQLPowerUtils; public class HSQLDBDatabaseMetaDataDecorator extends DatabaseMetaDataDecorator { private static final Logger logger = Logger.getLogger(HSQLDBDatabaseMetaDataDecorator.class); public HSQLDBDatabaseMetaDataDecorator(DatabaseMetaData delegate, ConnectionDecorator connectionDecorator) { super(delegate, connectionDecorator); } @Override public ResultSet getIndexInfo(String catalog, String schema, String table, boolean unique, boolean approximate) throws SQLException { logger.debug("Creating index info for " + makeKey(catalog, schema, table)); // PHASE 1: get the raw information about the indexes (with "incorrect" PK index names) ResultSet rs = super.getIndexInfo(catalog, schema, table, unique, approximate); CachedRowSet indexInfo = new CachedRowSet(); indexInfo.populate(rs); rs.close(); rs = null; // PHASE 2: collect PK information for the same tables // populating this with the primary key name for each table in question Map<String, String> pkNames = new HashMap<String, String>(); // populating this with the columns of the primary keys for each table in question Map<String, Set<String>> pkCols = new HashMap<String, Set<String>>(); // these are all temp variables Set<String> cols = null; rs = super.getPrimaryKeys(catalog, schema, table); String cat = null; String sch = null; String tab = null; while (rs.next()) { if (SQLPowerUtils.areEqual(cat, rs.getString("TABLE_CAT")) && SQLPowerUtils.areEqual(sch, rs.getString("TABLE_SCHEM")) && SQLPowerUtils.areEqual(tab, rs.getString("TABLE_NAME"))) { cols.add(rs.getString("COLUMN_NAME")); } else { cat = rs.getString("TABLE_CAT"); sch = rs.getString("TABLE_SCHEM"); tab = rs.getString("TABLE_NAME"); String key = makeKey(cat, sch, tab); pkNames.put(key, rs.getString("PK_NAME")); cols = new TreeSet<String>(); cols.add(rs.getString("COLUMN_NAME")); pkCols.put(key, cols); } } rs.close(); rs = null; if (logger.isDebugEnabled()) { logger.debug("Discovered PK names: " + pkNames); logger.debug("Discovered PK cols: " + pkCols); } // PHASE 3: determine index->pk name mappings using the information cached during phase 1 Map<String, String> indexToPkName = discoverIndexToPKMappings(indexInfo, pkNames, pkCols); // PHASE 4: update the index names to their corresponding PK names indexInfo.beforeFirst(); while (indexInfo.next()) { String pkName = indexToPkName.get(indexInfo.getString("INDEX_NAME")); if (pkName != null) { indexInfo.updateString("INDEX_NAME", pkName); } } indexInfo.beforeFirst(); return indexInfo; } private Map<String,String> discoverIndexToPKMappings(CachedRowSet indexInfo, Map<String, String> pkNames, Map<String, Set<String>> pkCols) throws SQLException { Map<String,String> indexToPKName = new HashMap<String, String>(); indexInfo.beforeFirst(); String cat = null; String sch = null; String tab = null; String idx = null; Set<String> cols = null; while (indexInfo.next()) { logger.debug("Considering index " + indexInfo.getString("INDEX_NAME") + ", col " + indexInfo.getString("COLUMN_NAME")); if (indexInfo.getBoolean("NON_UNIQUE")) continue; if (indexInfo.getInt("TYPE") == tableIndexStatistic) continue; if (!indexInfo.getString("INDEX_NAME").startsWith("SYS_IDX_")) continue; if (SQLPowerUtils.areEqual(cat, indexInfo.getString("TABLE_CAT")) && SQLPowerUtils.areEqual(sch, indexInfo.getString("TABLE_SCHEM")) && SQLPowerUtils.areEqual(tab, indexInfo.getString("TABLE_NAME")) && SQLPowerUtils.areEqual(idx, indexInfo.getString("INDEX_NAME"))) { cols.add(indexInfo.getString("COLUMN_NAME")); } else { // just finished reading all columns of an index. let's see if it was the table's PK. String key = makeKey(cat, sch, tab); Set<String> colsOfPk = pkCols.get(key); if (colsOfPk != null && colsOfPk.equals(cols)) { // we found the index that implements the PK! logger.debug("Found index name mapping " + idx + " -> " + pkNames.get(key)); indexToPKName.put(idx, pkNames.get(key)); // remove this entry in case there's another unique index on // this table with the same columns (they can't both be the PK!) pkNames.remove(key); } else { logger.debug("Index is not a PK: " + idx + "(my cols: " + cols + "; colsOfPk: " + colsOfPk + ")"); } // prepare for next index cat = indexInfo.getString("TABLE_CAT"); sch = indexInfo.getString("TABLE_SCHEM"); tab = indexInfo.getString("TABLE_NAME"); idx = indexInfo.getString("INDEX_NAME"); cols = new TreeSet<String>(); cols.add(indexInfo.getString("COLUMN_NAME")); } } logger.debug("Finished main loop. doing final check..."); // this is the same as above. need to repeat to handle last index in the result set String key = makeKey(cat, sch, tab); Set<String> colsOfPk = pkCols.get(key); if (colsOfPk != null && colsOfPk.equals(cols)) { // we found the index that implements the PK! logger.debug("Found index name mapping " + idx + " -> " + pkNames.get(key)); indexToPKName.put(idx, pkNames.get(key)); // remove this entry in case there's another unique index on // this table with the same columns (they can't both be the PK!) pkNames.remove(key); } else { logger.debug("Index is not a PK: " + idx + "(my cols: " + cols + "; colsOfPk: " + colsOfPk + ")"); } return indexToPKName; } private String makeKey(String cat, String sch, String tab) { String key = cat + "." + sch + "." + tab; return key; } /** * Fixes a problem where integer columns are marked as having a precision of 0. */ @Override public ResultSet getColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) throws SQLException { ResultSet columns = null; try { columns = super.getColumns(catalog, schemaPattern, tableNamePattern, columnNamePattern); CachedRowSet crs = new CachedRowSet(); crs.populate(columns); while (crs.next()) { int type = crs.getInt(5); if (type == Types.INTEGER && crs.getInt(7) == 0) { crs.updateInt(7, 10); } } crs.beforeFirst(); return crs; } finally { if (columns != null) { try { columns.close(); } catch (SQLException ex) { logger.error("Failed to close original result set", ex); } } } } @Override protected ResultSetDecorator wrap (ResultSet rs) throws SQLException { return new GenericResultSetDecorator(wrap(rs.getStatement()), rs); } @Override protected StatementDecorator wrap (Statement statement) { return new GenericStatementDecorator(connectionDecorator, statement); } }