001package co.codewizards.cloudstore.local; 002 003import java.sql.Connection; 004import java.sql.ResultSet; 005import java.sql.SQLException; 006import java.sql.Statement; 007import java.util.ArrayList; 008import java.util.Collection; 009import java.util.HashSet; 010import java.util.Set; 011 012import org.slf4j.Logger; 013import org.slf4j.LoggerFactory; 014 015import co.codewizards.cloudstore.core.oio.File; 016import co.codewizards.cloudstore.core.util.AssertUtil; 017 018public class RepairDatabase implements Runnable { 019 private static final Logger logger = LoggerFactory.getLogger(RepairDatabase.class); 020 021 private final File localRoot; 022 023 private Connection connection; 024 private Statement statement; 025 026 public RepairDatabase(File localRoot) { 027 this.localRoot = AssertUtil.assertNotNull(localRoot, "localRoot"); 028 } 029 030 @Override 031 public void run() { 032 try { 033 JdbcConnectionFactory jdbcConnectionFactory = new JdbcConnectionFactory(localRoot); 034 connection = jdbcConnectionFactory.createConnection(); 035 try { 036 statement = connection.createStatement(); 037 try { 038// testInsert(); 039 executeDerbyCheckTable(); 040 dropForeignKeys(); 041 dropIndices(); 042 executeDerbyCheckTable(); 043 } finally { 044 statement.close(); 045 } 046 } finally { 047 connection.close(); 048 } 049 } catch (SQLException x) { 050 throw new RuntimeException(x); 051 } 052 } 053 054 private void executeDerbyCheckTable() throws SQLException { 055 // http://objectmix.com/apache/646586-derby-db-files-get-corrupted-2.html 056 statement.execute( 057 "SELECT schemaname, tablename, SYSCS_UTIL.SYSCS_CHECK_TABLE(schemaname, tablename) " 058 + "FROM sys.sysschemas s, sys.systables t " 059 + "WHERE s.schemaid = t.schemaid"); 060 } 061 062// private void testInsert() throws SQLException { 063// connection.setAutoCommit(false); 064// try { 065// long filechunkpayload_id_oid; 066// long histocryptorepofile_id_oid; 067// int length; 068// long offset; 069// Timestamp changed; 070// Timestamp created; 071// 072// try (ResultSet rs = statement.executeQuery("select * from \"histofilechunk\" order by \"id\"")) { 073// if (! rs.next()) { 074// logger.warn("Table \"histofilechunk\" is empty! Cannot obtain test data!"); 075// return; 076// } 077// 078// filechunkpayload_id_oid = rs.getLong("filechunkpayload_id_oid"); 079// histocryptorepofile_id_oid = rs.getLong("histocryptorepofile_id_oid"); 080// length = rs.getInt("length"); 081// offset = rs.getLong("offset"); 082// changed = rs.getTimestamp("changed"); 083// created = rs.getTimestamp("created"); 084// } 085// 086// ++offset; // there is a unique key => must change the offset! 087// 088// logger.info("testInsert: filechunkpayload_id_oid={}, histocryptorepofile_id_oid={}, length={}, offset={}, changed={}, created={}", 089// filechunkpayload_id_oid, histocryptorepofile_id_oid, length, offset, changed, created); 090// 091// try (PreparedStatement ps = connection.prepareStatement( 092// "INSERT INTO \"histofilechunk\"" 093// + " (\"filechunkpayload_id_oid\",\"histocryptorepofile_id_oid\",\"length\",\"offset\",\"changed\",\"created\")" 094// + " VALUES (?,?,?,?,?,?)")) { 095// 096// int paramIdx = 0; 097// ps.setLong(++paramIdx, filechunkpayload_id_oid); 098// ps.setLong(++paramIdx, histocryptorepofile_id_oid); 099// ps.setInt(++paramIdx, length); 100// ps.setLong(++paramIdx, offset); 101// ps.setTimestamp(++paramIdx, changed); 102// ps.setTimestamp(++paramIdx, created); 103// 104// try { 105// ps.execute(); 106// } catch (Exception x) { 107// logger.error("testInsert: " + x, x); 108// return; 109// } 110// } 111// logger.info("testInsert: Success!"); 112// } finally { 113// connection.rollback(); 114// connection.setAutoCommit(true); 115// } 116// } 117 118 private void dropForeignKeys() throws SQLException { // DataNucleus will re-create them. 119 for (String tableName : getTableNames()) { 120 for (String foreignKeyName : getForeignKeyNames(tableName)) { 121 try { 122 statement.execute(String.format("ALTER TABLE \"%s\" DROP CONSTRAINT \"%s\"", tableName, foreignKeyName)); 123 logger.info("dropForeignKeys: Dropped foreign-key '{}' of table '{}'.", foreignKeyName, tableName); 124 } catch (SQLException x) { 125 logger.warn("dropForeignKeys: Could not drop foreign-key '{}' of table '{}': {}", foreignKeyName, tableName, x.toString()); 126 } 127 } 128 } 129 } 130 131 private void dropIndices() throws SQLException { // DataNucleus will re-create them. 132 for (String tableName : getTableNames()) { 133 for (String indexName : getIndexNames(tableName)) { 134 try { 135 statement.execute(String.format("DROP INDEX \"%s\"", indexName)); 136 logger.info("dropIndices: Dropped index '{}'.", indexName); 137 } catch (SQLException x) { 138 logger.warn("dropIndices: Could not drop index '{}': {}", indexName, x.toString()); 139 } 140 } 141 } 142 } 143 144 private Collection<String> getTableNames() throws SQLException 145 { 146 ArrayList<String> res = new ArrayList<String>(); 147 148 final ResultSet rs = connection.getMetaData().getTables(null, null, null, null); 149 while (rs.next()) { 150 final String tableName = rs.getString("TABLE_NAME"); 151 final String tableType = rs.getString("TABLE_TYPE"); 152 153 if ("SEQUENCE".equals(tableType == null ? null : tableType.toUpperCase())) 154 continue; 155 156 if (tableName.toLowerCase().startsWith("sys")) 157 continue; 158 159 res.add(tableName); 160 } 161 rs.close(); 162 163 return res; 164 } 165 166 private Collection<String> getForeignKeyNames(String tableName) throws SQLException { 167 Set<String> tableNameAndForeignKeyNameSet = new HashSet<>(); 168 ArrayList<String> res = new ArrayList<String>(); 169 170 for (String toTableName : getTableNames()) { 171 ResultSet rs = connection.getMetaData().getCrossReference(null, null, toTableName, null, null, tableName); 172 while (rs.next()) { 173// String parentKeyTableName = rs.getString("PKTABLE_NAME"); 174// String foreignKeyTableName = rs.getString("FKTABLE_NAME"); 175 String foreignKeyName = rs.getString("FK_NAME"); 176 if (foreignKeyName == null) 177 continue; 178 179// if (foreignKeyTableName != null && !tableName.equals(foreignKeyTableName)) 180// continue; 181 182 String tableNameAndForeignKeyName = tableName + '.' + foreignKeyName; 183 if (tableNameAndForeignKeyNameSet.add(tableNameAndForeignKeyName)) 184 res.add(foreignKeyName); 185 } 186 rs.close(); 187 } 188 189 return res; 190 } 191 192 private Collection<String> getIndexNames(String tableName) throws SQLException { 193 ArrayList<String> res = new ArrayList<String>(); 194 195 ResultSet rs = connection.getMetaData().getIndexInfo(null, null, tableName, false, true); 196 while (rs.next()) { 197 String indexName = rs.getString("INDEX_NAME"); 198 if (indexName == null) 199 continue; 200 201 res.add(indexName); 202 } 203 rs.close(); 204 205 return res; 206 } 207}