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}