package co.codewizards.cloudstore.local; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import co.codewizards.cloudstore.core.oio.File; import co.codewizards.cloudstore.core.util.AssertUtil; public class RepairDatabase implements Runnable { private static final Logger logger = LoggerFactory.getLogger(RepairDatabase.class); private final File localRoot; private Connection connection; private Statement statement; public RepairDatabase(File localRoot) { this.localRoot = AssertUtil.assertNotNull(localRoot, "localRoot"); } @Override public void run() { try { JdbcConnectionFactory jdbcConnectionFactory = new JdbcConnectionFactory(localRoot); connection = jdbcConnectionFactory.createConnection(); try { statement = connection.createStatement(); try { // testInsert(); executeDerbyCheckTable(); dropForeignKeys(); dropIndices(); executeDerbyCheckTable(); } finally { statement.close(); } } finally { connection.close(); } } catch (SQLException x) { throw new RuntimeException(x); } } private void executeDerbyCheckTable() throws SQLException { // http://objectmix.com/apache/646586-derby-db-files-get-corrupted-2.html statement.execute( "SELECT schemaname, tablename, SYSCS_UTIL.SYSCS_CHECK_TABLE(schemaname, tablename) " + "FROM sys.sysschemas s, sys.systables t " + "WHERE s.schemaid = t.schemaid"); } // private void testInsert() throws SQLException { // connection.setAutoCommit(false); // try { // long filechunkpayload_id_oid; // long histocryptorepofile_id_oid; // int length; // long offset; // Timestamp changed; // Timestamp created; // // try (ResultSet rs = statement.executeQuery("select * from \"histofilechunk\" order by \"id\"")) { // if (! rs.next()) { // logger.warn("Table \"histofilechunk\" is empty! Cannot obtain test data!"); // return; // } // // filechunkpayload_id_oid = rs.getLong("filechunkpayload_id_oid"); // histocryptorepofile_id_oid = rs.getLong("histocryptorepofile_id_oid"); // length = rs.getInt("length"); // offset = rs.getLong("offset"); // changed = rs.getTimestamp("changed"); // created = rs.getTimestamp("created"); // } // // ++offset; // there is a unique key => must change the offset! // // logger.info("testInsert: filechunkpayload_id_oid={}, histocryptorepofile_id_oid={}, length={}, offset={}, changed={}, created={}", // filechunkpayload_id_oid, histocryptorepofile_id_oid, length, offset, changed, created); // // try (PreparedStatement ps = connection.prepareStatement( // "INSERT INTO \"histofilechunk\"" // + " (\"filechunkpayload_id_oid\",\"histocryptorepofile_id_oid\",\"length\",\"offset\",\"changed\",\"created\")" // + " VALUES (?,?,?,?,?,?)")) { // // int paramIdx = 0; // ps.setLong(++paramIdx, filechunkpayload_id_oid); // ps.setLong(++paramIdx, histocryptorepofile_id_oid); // ps.setInt(++paramIdx, length); // ps.setLong(++paramIdx, offset); // ps.setTimestamp(++paramIdx, changed); // ps.setTimestamp(++paramIdx, created); // // try { // ps.execute(); // } catch (Exception x) { // logger.error("testInsert: " + x, x); // return; // } // } // logger.info("testInsert: Success!"); // } finally { // connection.rollback(); // connection.setAutoCommit(true); // } // } private void dropForeignKeys() throws SQLException { // DataNucleus will re-create them. for (String tableName : getTableNames()) { for (String foreignKeyName : getForeignKeyNames(tableName)) { try { statement.execute(String.format("ALTER TABLE \"%s\" DROP CONSTRAINT \"%s\"", tableName, foreignKeyName)); logger.info("dropForeignKeys: Dropped foreign-key '{}' of table '{}'.", foreignKeyName, tableName); } catch (SQLException x) { logger.warn("dropForeignKeys: Could not drop foreign-key '{}' of table '{}': {}", foreignKeyName, tableName, x.toString()); } } } } private void dropIndices() throws SQLException { // DataNucleus will re-create them. for (String tableName : getTableNames()) { for (String indexName : getIndexNames(tableName)) { try { statement.execute(String.format("DROP INDEX \"%s\"", indexName)); logger.info("dropIndices: Dropped index '{}'.", indexName); } catch (SQLException x) { logger.warn("dropIndices: Could not drop index '{}': {}", indexName, x.toString()); } } } } private Collection<String> getTableNames() throws SQLException { ArrayList<String> res = new ArrayList<String>(); final ResultSet rs = connection.getMetaData().getTables(null, null, null, null); while (rs.next()) { final String tableName = rs.getString("TABLE_NAME"); final String tableType = rs.getString("TABLE_TYPE"); if ("SEQUENCE".equals(tableType == null ? null : tableType.toUpperCase())) continue; if (tableName.toLowerCase().startsWith("sys")) continue; res.add(tableName); } rs.close(); return res; } private Collection<String> getForeignKeyNames(String tableName) throws SQLException { Set<String> tableNameAndForeignKeyNameSet = new HashSet<>(); ArrayList<String> res = new ArrayList<String>(); for (String toTableName : getTableNames()) { ResultSet rs = connection.getMetaData().getCrossReference(null, null, toTableName, null, null, tableName); while (rs.next()) { // String parentKeyTableName = rs.getString("PKTABLE_NAME"); // String foreignKeyTableName = rs.getString("FKTABLE_NAME"); String foreignKeyName = rs.getString("FK_NAME"); if (foreignKeyName == null) continue; // if (foreignKeyTableName != null && !tableName.equals(foreignKeyTableName)) // continue; String tableNameAndForeignKeyName = tableName + '.' + foreignKeyName; if (tableNameAndForeignKeyNameSet.add(tableNameAndForeignKeyName)) res.add(foreignKeyName); } rs.close(); } return res; } private Collection<String> getIndexNames(String tableName) throws SQLException { ArrayList<String> res = new ArrayList<String>(); ResultSet rs = connection.getMetaData().getIndexInfo(null, null, tableName, false, true); while (rs.next()) { String indexName = rs.getString("INDEX_NAME"); if (indexName == null) continue; res.add(indexName); } rs.close(); return res; } }