001/* 002 * Syncany, www.syncany.org 003 * Copyright (C) 2011-2016 Philipp C. Heckel <philipp.heckel@gmail.com> 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU General Public License as published by 007 * the Free Software Foundation, either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU General Public License for more details. 014 * 015 * You should have received a copy of the GNU General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018package org.syncany.database.dao; 019 020import java.sql.Connection; 021import java.sql.PreparedStatement; 022import java.sql.ResultSet; 023import java.sql.SQLException; 024import java.util.ArrayList; 025import java.util.Collection; 026import java.util.HashMap; 027import java.util.HashSet; 028import java.util.List; 029import java.util.Map; 030import java.util.Set; 031 032import org.syncany.chunk.MultiChunk; 033import org.syncany.database.ChunkEntry.ChunkChecksum; 034import org.syncany.database.DatabaseVersion.DatabaseVersionStatus; 035import org.syncany.database.DatabaseVersionHeader; 036import org.syncany.database.FileContent.FileChecksum; 037import org.syncany.database.MultiChunkEntry; 038import org.syncany.database.MultiChunkEntry.MultiChunkId; 039import org.syncany.database.VectorClock; 040 041/** 042 * The multi-chunk data access object (DAO) queries and modifies the <i>multichunk</i> and 043 * <i>multichunk_chunk</i> table in the SQL database. These tables correspond to the Java 044 * object {@link MultiChunk}. 045 * 046 * @author Philipp C. Heckel (philipp.heckel@gmail.com) 047 */ 048public class MultiChunkSqlDao extends AbstractSqlDao { 049 public MultiChunkSqlDao(Connection connection) { 050 super(connection); 051 } 052 053 public void writeMultiChunks(Connection connection, long databaseVersionId, Collection<MultiChunkEntry> multiChunks) throws SQLException { 054 for (MultiChunkEntry multiChunk : multiChunks) { 055 PreparedStatement preparedStatement = getStatement(connection, "multichunk.insert.all.writeMultiChunks.sql"); 056 057 preparedStatement.setString(1, multiChunk.getId().toString()); 058 preparedStatement.setLong(2, databaseVersionId); 059 preparedStatement.setLong(3, multiChunk.getSize()); 060 061 preparedStatement.executeUpdate(); 062 preparedStatement.close(); 063 064 writeMultiChunkRefs(connection, multiChunk); 065 } 066 } 067 068 private void writeMultiChunkRefs(Connection connection, MultiChunkEntry multiChunk) throws SQLException { 069 PreparedStatement preparedStatement = getStatement("multichunk.insert.all.writeMultiChunkRefs.sql"); 070 071 for (ChunkChecksum chunkChecksum : multiChunk.getChunks()) { 072 preparedStatement.setString(1, multiChunk.getId().toString()); 073 preparedStatement.setString(2, chunkChecksum.toString()); 074 075 preparedStatement.addBatch(); 076 } 077 078 preparedStatement.executeBatch(); 079 preparedStatement.close(); 080 } 081 082 public void writeMuddyMultiChunks(Map<DatabaseVersionHeader, Collection<MultiChunkEntry>> muddyMultiChunksPerDatabaseVersion) throws SQLException { 083 PreparedStatement preparedStatement = getStatement("multichunk_muddy.insert.muddy.writeMuddyMultiChunks.sql"); 084 085 for (DatabaseVersionHeader muddyDatabaseVersionHeader : muddyMultiChunksPerDatabaseVersion.keySet()) { 086 Collection<MultiChunkEntry> muddyMultiChunks = muddyMultiChunksPerDatabaseVersion.get(muddyDatabaseVersionHeader); 087 088 for (MultiChunkEntry muddyMultiChunk : muddyMultiChunks) { 089 String multiChunkIdStr = muddyMultiChunk.getId().toString(); 090 String clientName = muddyDatabaseVersionHeader.getClient(); 091 Long clientVersion = muddyDatabaseVersionHeader.getVectorClock().getClock(clientName); 092 093 preparedStatement.setString(1, multiChunkIdStr); 094 preparedStatement.setString(2, clientName); 095 preparedStatement.setLong(3, clientVersion); 096 097 preparedStatement.addBatch(); 098 } 099 } 100 101 preparedStatement.executeBatch(); 102 preparedStatement.close(); 103 } 104 105 public void removeUnreferencedMultiChunks() throws SQLException { 106 // Note: Chunk references (multichunk_chunk) must be removed first, because 107 // of the foreign key constraints. 108 109 removeUnreferencedMultiChunkChunkRefs(); 110 removeUnreferencedMultiChunksInt(); 111 } 112 113 private void removeUnreferencedMultiChunksInt() throws SQLException { 114 PreparedStatement preparedStatement = getStatement("multichunk.delete.all.removeUnreferencedMultiChunks.sql"); 115 preparedStatement.executeUpdate(); 116 preparedStatement.close(); 117 } 118 119 private void removeUnreferencedMultiChunkChunkRefs() throws SQLException { 120 PreparedStatement preparedStatement = getStatement("multichunk.delete.all.removeUnreferencedMultiChunkChunkRefs.sql"); 121 preparedStatement.executeUpdate(); 122 preparedStatement.close(); 123 } 124 125 public void removeNonMuddyMultiChunks() throws SQLException { 126 PreparedStatement preparedStatement = getStatement("multichunk_muddy.delete.muddy.removeNonMuddyMultiChunks.sql"); 127 preparedStatement.executeUpdate(); 128 preparedStatement.close(); 129 } 130 131 /** 132 * Note: This method selects also {@link DatabaseVersionStatus#DIRTY DIRTY}. 133 */ 134 public List<MultiChunkId> getMultiChunkIds(FileChecksum fileChecksum) { 135 List<MultiChunkId> multiChunkIds = new ArrayList<MultiChunkId>(); 136 137 if (fileChecksum == null) { 138 return multiChunkIds; 139 } 140 else { 141 try (PreparedStatement preparedStatement = getStatement("multichunk.select.all.getMultiChunkIdsForFileChecksum.sql")) { 142 preparedStatement.setString(1, fileChecksum.toString()); 143 144 try (ResultSet resultSet = preparedStatement.executeQuery()) { 145 while (resultSet.next()) { 146 multiChunkIds.add(MultiChunkId.parseMultiChunkId(resultSet.getString("multichunk_id"))); 147 } 148 149 return multiChunkIds; 150 } 151 } 152 catch (SQLException e) { 153 throw new RuntimeException(e); 154 } 155 } 156 } 157 158 /** 159 * Note: This method selects also {@link DatabaseVersionStatus#DIRTY DIRTY}. 160 */ 161 public Map<MultiChunkId, MultiChunkEntry> getMultiChunks(VectorClock vectorClock) { 162 try (PreparedStatement preparedStatement = getStatement("multichunk.select.all.getMultiChunksWithChunksForDatabaseVersion.sql")) { 163 preparedStatement.setString(1, vectorClock.toString()); 164 165 try (ResultSet resultSet = preparedStatement.executeQuery()) { 166 return createMultiChunkEntriesWithChunks(resultSet); 167 } 168 } 169 catch (SQLException e) { 170 throw new RuntimeException(e); 171 } 172 } 173 174 /** 175 * no commit 176 */ 177 public void updateDirtyMultiChunksNewDatabaseId(long newDatabaseVersionId) { 178 try (PreparedStatement preparedStatement = getStatement("multichunk.update.dirty.updateDirtyMultiChunksNewDatabaseId.sql")) { 179 preparedStatement.setLong(1, newDatabaseVersionId); 180 preparedStatement.executeUpdate(); 181 } 182 catch (SQLException e) { 183 throw new RuntimeException(e); 184 } 185 } 186 187 /** 188 * Note: This method selects also {@link DatabaseVersionStatus#DIRTY DIRTY}. 189 */ 190 public MultiChunkId getMultiChunkId(ChunkChecksum chunkChecksum) { 191 try (PreparedStatement preparedStatement = getStatement("multichunk.select.all.getMultiChunkIdForChunk.sql")) { 192 preparedStatement.setString(1, chunkChecksum.toString()); 193 194 try (ResultSet resultSet = preparedStatement.executeQuery()) { 195 if (resultSet.next()) { 196 return MultiChunkId.parseMultiChunkId(resultSet.getString("multichunk_id")); 197 } 198 } 199 200 return null; 201 } 202 catch (SQLException e) { 203 throw new RuntimeException(e); 204 } 205 } 206 207 /** 208 * Note: This method selects also {@link DatabaseVersionStatus#DIRTY DIRTY}. 209 */ 210 public Map<ChunkChecksum,MultiChunkId> getMultiChunkIdsByChecksums(List<ChunkChecksum> chunkChecksums) { 211 // Gather a unique array of checksum strings (required for query!) 212 Set<ChunkChecksum> chunkChecksumSet = new HashSet<ChunkChecksum>(chunkChecksums); 213 String[] checksums = new String[chunkChecksumSet.size()]; 214 int i = 0; 215 for (ChunkChecksum checksum : chunkChecksumSet) { 216 checksums[i] = checksum.toString(); 217 i++; 218 } 219 220 // Execute query 221 Map<ChunkChecksum, MultiChunkId> result = new HashMap<ChunkChecksum, MultiChunkId>(); 222 try (PreparedStatement preparedStatement = getStatement("multichunk.select.all.getMultiChunkIdForChunks.sql")) { 223 preparedStatement.setArray(1, connection.createArrayOf("varchar", checksums)); 224 try (ResultSet resultSet = preparedStatement.executeQuery()) { 225 while (resultSet.next()) { 226 result.put(ChunkChecksum.parseChunkChecksum(resultSet.getString("chunk_checksum")), 227 MultiChunkId.parseMultiChunkId(resultSet.getString("multichunk_id"))); 228 229 } 230 } 231 232 return result; 233 } 234 catch (SQLException e) { 235 throw new RuntimeException(e); 236 } 237 } 238 239 public List<MultiChunkId> getDirtyMultiChunkIds() { 240 List<MultiChunkId> dirtyMultiChunkIds = new ArrayList<MultiChunkId>(); 241 242 try (PreparedStatement preparedStatement = getStatement("multichunk.select.dirty.getDirtyMultiChunkIds.sql")) { 243 try (ResultSet resultSet = preparedStatement.executeQuery()) { 244 while (resultSet.next()) { 245 dirtyMultiChunkIds.add(MultiChunkId.parseMultiChunkId(resultSet.getString("multichunk_id"))); 246 } 247 248 return dirtyMultiChunkIds; 249 } 250 } 251 catch (SQLException e) { 252 throw new RuntimeException(e); 253 } 254 } 255 256 public Map<MultiChunkId, MultiChunkEntry> getUnusedMultiChunks() { 257 try (PreparedStatement preparedStatement = getStatement("multichunk.select.all.getUnusedMultiChunks.sql")) { 258 try (ResultSet resultSet = preparedStatement.executeQuery()) { 259 return createMultiChunkEntriesWithoutChunks(resultSet); 260 } 261 } 262 catch (SQLException e) { 263 throw new RuntimeException(e); 264 } 265 } 266 267 private Map<MultiChunkId, MultiChunkEntry> createMultiChunkEntriesWithoutChunks(ResultSet resultSet) throws SQLException { 268 Map<MultiChunkId, MultiChunkEntry> unusedMultiChunkIds = new HashMap<MultiChunkId, MultiChunkEntry>(); 269 270 while (resultSet.next()) { 271 MultiChunkId multiChunkId = MultiChunkId.parseMultiChunkId(resultSet.getString("id")); 272 long multiChunkSize = resultSet.getLong("size"); 273 274 unusedMultiChunkIds.put(multiChunkId, new MultiChunkEntry(multiChunkId, multiChunkSize)); 275 } 276 277 return unusedMultiChunkIds; 278 } 279 280 private Map<MultiChunkId, MultiChunkEntry> createMultiChunkEntriesWithChunks(ResultSet resultSet) throws SQLException { 281 Map<MultiChunkId, MultiChunkEntry> multiChunkEntries = new HashMap<MultiChunkId, MultiChunkEntry>(); 282 MultiChunkId currentMultiChunkId = null; 283 284 while (resultSet.next()) { 285 MultiChunkId multiChunkId = MultiChunkId.parseMultiChunkId(resultSet.getString("multichunk_id")); 286 long multiChunkSize = resultSet.getLong("size"); 287 288 MultiChunkEntry multiChunkEntry = null; 289 290 if (currentMultiChunkId != null && currentMultiChunkId.equals(multiChunkId)) { 291 multiChunkEntry = multiChunkEntries.get(multiChunkId); 292 } 293 else { 294 multiChunkEntry = new MultiChunkEntry(multiChunkId, multiChunkSize); 295 } 296 297 multiChunkEntry.addChunk(ChunkChecksum.parseChunkChecksum(resultSet.getString("chunk_checksum"))); 298 multiChunkEntries.put(multiChunkId, multiChunkEntry); 299 300 currentMultiChunkId = multiChunkId; 301 } 302 303 return multiChunkEntries; 304 } 305}