/* * Copyright 2000-2012 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.openapi.vcs.changes.dbCommitted; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.*; import com.intellij.openapi.vcs.changes.Change; import com.intellij.openapi.vcs.changes.ContentRevision; import com.intellij.openapi.vcs.changes.committed.ReceivedChangeList; import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; import com.intellij.util.ThrowableConsumer; import com.intellij.util.ThrowableConvertor; import com.intellij.util.ThrowableRunnable; import com.intellij.util.containers.MultiMap; import com.intellij.util.io.DataOutputStream; import org.jetbrains.annotations.NotNull; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; import java.sql.*; import java.util.*; /** * Created with IntelliJ IDEA. * User: Irina.Chernushina * Date: 10/8/12 * Time: 3:05 PM */ public class VcsSqliteLayer { private final static int ourLastPathRevisionBatchSize = 10; private final KnownRepositoryLocations myKnownRepositoryLocations; private final CacheJdbcConnection myConnection; private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.changes.dbCommitted.VcsSqliteLayer"); public VcsSqliteLayer(final Project project, KnownRepositoryLocations locations) { myKnownRepositoryLocations = locations; myConnection = new CacheJdbcConnection(DbSettings.getDbFilePath(project), new ThrowableConsumer<Connection, VcsException>() { @Override public void consume(Connection connection) throws VcsException { initDb(connection); } }); } private void initDb(Connection connection) throws VcsException { try { connection.createStatement().execute(createStatementForTable(SqliteTables.KNOWN_VCS)); connection.createStatement().execute(createStatementForTable(SqliteTables.ROOT)); connection.createStatement().execute(createStatementForTable(SqliteTables.AUTHOR)); connection.createStatement().execute(createStatementForTable(SqliteTables.REVISION)); connection.createStatement().execute(createStatementForTable(SqliteTables.PATHS)); connection.createStatement().execute(createStatementForTable(SqliteTables.PATHS_2_REVS)); connection.createStatement().execute(createStatementForTable(SqliteTables.INCOMING_PATHS)); connection.createStatement().execute("CREATE INDEX " + SqliteTables.IDX_ROOT_URL + " ON ROOT (URL)"); connection.createStatement().execute("CREATE INDEX " + "VCS_FK" + " ON ROOT (VCS_FK)"); connection.createStatement().execute("CREATE INDEX " + SqliteTables.IDX_AUTHOR_NAME + " ON AUTHOR (NAME)"); connection.createStatement().execute("CREATE INDEX " + SqliteTables.IDX_REVISION_DATE + " ON REVISION (DATE)"); connection.createStatement().execute("CREATE INDEX " + SqliteTables.IDX_REVISION_NUMBER_INT + " ON REVISION (NUMBER_INT)"); connection.createStatement().execute("CREATE INDEX " + SqliteTables.IDX_PATHS_PATH + " ON PATHS (PATH)"); } catch (SQLException e) { throw new VcsException(e); } } public void checkVcsRootsAreTracked(final MultiMap<String, String> vcses) throws VcsException { if (vcses.isEmpty()) return; final MultiMap<String, String> copy = new MultiMap<String, String>(); copy.putAllValues(vcses); if (! checkForInMemory(copy)) return; final HashSet<String> vcsNamesSet = new HashSet<String>(vcses.keySet()); for (Iterator<String> iterator = vcsNamesSet.iterator(); iterator.hasNext(); ) { final String key = iterator.next(); if (myKnownRepositoryLocations.exists(key)) { iterator.remove(); } } if (! vcsNamesSet.isEmpty()) { ensureVcsAreInDB(vcsNamesSet); } final Set<Long> rootIdsToCheck = ensurePathsAreInDB(copy); if (! rootIdsToCheck.isEmpty()) { updateLastRevisions(rootIdsToCheck); } } private void updateLastRevisions(Set<Long> rootIdsToCheck) throws VcsException { final PreparedStatement maxStatement = myConnection.getOrCreatePreparedStatement(SqliteTables.PREPARED_SELECT_MAX_REVISION, new ThrowableConvertor<Connection, PreparedStatement, SQLException>() { @Override public PreparedStatement convert(Connection connection) throws SQLException { final String num = SqliteTables.REVISION.NUMBER_INT; return connection.prepareStatement( "SELECT " + num + "MAX_REV, " + SqliteTables.REVISION.DATE + "MAX_DATE FROM " + SqliteTables.REVISION.TABLE_NAME + " WHERE MAX_REV=(" + " SELECT MAX(" + num + ") FROM " + SqliteTables.REVISION.TABLE_NAME + " WHERE " + SqliteTables.REVISION.ROOT_FK + " =?"); } }); final PreparedStatement minStatement = myConnection.getOrCreatePreparedStatement(SqliteTables.PREPARED_SELECT_MIN_REVISION, new ThrowableConvertor<Connection, PreparedStatement, SQLException>() { @Override public PreparedStatement convert(Connection connection) throws SQLException { final String num = SqliteTables.REVISION.NUMBER_INT; return connection.prepareStatement( "SELECT " + num + "MIN_REV, " + SqliteTables.REVISION.DATE + "MIN_DATE FROM " + SqliteTables.REVISION.TABLE_NAME + " WHERE MIN_REV=(" + " SELECT MIN(" + num + ") FROM " + SqliteTables.REVISION.TABLE_NAME + " WHERE " + SqliteTables.REVISION.ROOT_FK + " =?"); } }); try { for (final Long id : rootIdsToCheck) { maxStatement.setLong(1, id); final ResultSet set = maxStatement.executeQuery(); SqliteUtil.readSelectResults(set, new ThrowableRunnable<SQLException>() { @Override public void run() throws SQLException { final long max = set.getLong(1); final long time = set.getLong(2); if (max > 0) {// 0 is === SQL NULL myKnownRepositoryLocations.setLastRevision(id, new RevisionId(max, time)); } } }); minStatement.setLong(1, id); final ResultSet setMin = minStatement.executeQuery(); SqliteUtil.readSelectResults(setMin, new ThrowableRunnable<SQLException>() { @Override public void run() throws SQLException { final long min = setMin.getLong(1); final long time = setMin.getLong(2); if (min > 0) {// 0 is === SQL NULL myKnownRepositoryLocations.setFirstRevision(id, new RevisionId(min, time)); } } }); } } catch (SQLException e) { throw new VcsException(e); } } private Set<Long> ensurePathsAreInDB(final MultiMap<String, String> copy) throws VcsException { final Set<Long> idsToCheck = new HashSet<Long>(); final PreparedStatement select = myConnection.getOrCreatePreparedStatement(SqliteTables.PREPARED_SELECT_ROOTS, new ThrowableConvertor<Connection, PreparedStatement, SQLException>() { @Override public PreparedStatement convert(Connection connection) throws SQLException { return connection.prepareStatement("SELECT * FROM " + SqliteTables.ROOT.TABLE_NAME + " WHERE VCS_FK=?"); } }); try { for (final String vcsName : copy.keySet()) { select.setLong(1, myKnownRepositoryLocations.getVcsKey(vcsName)); final ResultSet set = select.executeQuery(); SqliteUtil.readSelectResults(set, new ThrowableRunnable<SQLException>() { @Override public void run() throws SQLException { final long id = set.getLong(SqliteTables.ROOT.ID); final String url = set.getString(SqliteTables.ROOT.URL); myKnownRepositoryLocations.add(vcsName, url, id); copy.removeValue(vcsName, url); if (myKnownRepositoryLocations.getLastRevision(id) == null) { idsToCheck.add(id); } } }); } if (copy.isEmpty()) return idsToCheck; final PreparedStatement insert = myConnection.getOrCreatePreparedStatement(SqliteTables.PREPARED_INSERT_ROOT, new ThrowableConvertor<Connection, PreparedStatement, SQLException>() { @Override public PreparedStatement convert(Connection connection) throws SQLException { return connection.prepareStatement("INSERT INTO " + SqliteTables.ROOT.TABLE_NAME + " ( " + SqliteTables.ROOT.VCS_FK + ", " + SqliteTables.ROOT.URL + ") VALUES (?,?)", Statement.RETURN_GENERATED_KEYS); } }); for (String vcsName : copy.keySet()) { insert.setLong(1, myKnownRepositoryLocations.getVcsKey(vcsName)); for (String path : copy.get(vcsName)) { insert.setString(2, path); final long id = SqliteUtil.insert(insert); myKnownRepositoryLocations.add(vcsName, path, id); } } myConnection.commit(); } catch (SQLException e) { throw new VcsException(e); } return idsToCheck; } private void ensureVcsAreInDB(final HashSet<String> vcsNamesSet) throws VcsException { final PreparedStatement readVcses = myConnection.getOrCreatePreparedStatement(SqliteTables.PREPARED_SELECT_VCS, new ThrowableConvertor<Connection, PreparedStatement, SQLException>() { @Override public PreparedStatement convert(Connection o) throws SQLException { return o.prepareStatement("SELECT * FROM VCS"); } }); try { final ResultSet set = readVcses.executeQuery(); SqliteUtil.readSelectResults(set, new ThrowableRunnable<SQLException>() { @Override public void run() throws SQLException { final long id = set.getLong(SqliteTables.KNOWN_VCS.ID); final String name = set.getString(SqliteTables.KNOWN_VCS.NAME); myKnownRepositoryLocations.addVcs(name, id); vcsNamesSet.remove(name); } }); if (vcsNamesSet.isEmpty()) return; final PreparedStatement insertStatement = myConnection.getOrCreatePreparedStatement(SqliteTables.PREPARED_INSERT_VCS, new ThrowableConvertor<Connection, PreparedStatement, SQLException>() { @Override public PreparedStatement convert(Connection o) throws SQLException { return o.prepareStatement("INSERT INTO " + SqliteTables.KNOWN_VCS.TABLE_NAME + " (" + SqliteTables.KNOWN_VCS.NAME + ") VALUES (?)", Statement.RETURN_GENERATED_KEYS); } }); for (String name : vcsNamesSet) { insertStatement.setString(1, name); final long id = SqliteUtil.insert(insertStatement); myKnownRepositoryLocations.addVcs(name, id); } myConnection.commit(); } catch (SQLException e) { throw new VcsException(e); } } private boolean checkForInMemory(MultiMap<String, String> map) { for (String vcsName : map.keySet()) { final Collection<String> paths = map.get(vcsName); for (Iterator<String> iterator = paths.iterator(); iterator.hasNext(); ) { final String path = iterator.next(); if (myKnownRepositoryLocations.exists(vcsName, path)) { iterator.remove(); } } } for (String paths : map.values()) { if (! paths.isEmpty()) return true; } return false; } private static String createStatementForTable(SqliteTables.BaseTable baseTable) { return "CREATE TABLE " + baseTable.TABLE_NAME + " ( ID INTEGER PRIMARY KEY, " + baseTable.getCreateTableStatement() + ");"; } public void appendLists(final AbstractVcs vcs, final String root, final List<CommittedChangeList> lists) throws VcsException { //authors, revisions, paths if (lists.isEmpty()) return; assert myKnownRepositoryLocations.exists(vcs.getName(), root); final long locationId = myKnownRepositoryLocations.getLocationId(vcs.getName(), root); long maxRev = -1; long minRev = Long.MAX_VALUE; long maxTime = -1; long minTime = -1; final RevisionId firstRevData = myKnownRepositoryLocations.getFirstRevision(locationId); final Long firstRevision = firstRevData == null ? null : firstRevData.getNumber(); final RevisionId lastRevData = myKnownRepositoryLocations.getLastRevision(locationId); final Long lastRevision = lastRevData == null ? null : lastRevData.getNumber(); final List<List<CommittedChangeList>> split = new CollectionSplitter<CommittedChangeList>(20).split(lists); final Map<String, Long> knowPaths = new HashMap<String, Long>(); for (List<CommittedChangeList> changeLists : split) { final Set<String> names = new HashSet<String>(); final Set<String> paths = new HashSet<String>(); for (Iterator<CommittedChangeList> iterator = changeLists.iterator(); iterator.hasNext(); ) { final CommittedChangeList list = iterator.next(); final long number = list.getNumber(); if (lastRevision != null && number <= lastRevision && number >= firstRevision) { iterator.remove(); continue; } if (number > maxRev) { maxRev = number; maxTime = list.getCommitDate().getTime(); } if (number < minRev) { minRev = number; minTime = list.getCommitDate().getTime(); } names.add(list.getCommitterName()); // todo if null. also comment for (Change change : list.getChangesWithMovedTrees()) { if (change.getBeforeRevision() != null) { paths.add(getPath(change.getBeforeRevision())); } if (change.getAfterRevision() != null) { paths.add(getPath(change.getAfterRevision())); } } } final Map<String, Long> knownAuthors = myKnownRepositoryLocations.filterKnownAuthors(names); checkAndAddAuthors(names, knownAuthors); checkAndAddPaths(paths, knowPaths, locationId); insertChangeListsIfNotExists(vcs, knownAuthors, changeLists, locationId, knowPaths); if (firstRevision == null || minRev < firstRevision) { myKnownRepositoryLocations.setFirstRevision(locationId, new RevisionId(minRev, minTime)); } if (lastRevision == null || maxRev > lastRevision) { myKnownRepositoryLocations.setLastRevision(locationId, new RevisionId(maxRev, maxTime)); } } } private String getPath(ContentRevision revision) { final String path = FileUtil.toSystemIndependentName(revision.getFile().getPath()); return path.endsWith("/") ? path : path + "/"; } private Map<Long, CommittedChangeList> insertChangeListsIfNotExists(AbstractVcs vcs, final Map<String, Long> authors, final List<CommittedChangeList> lists, final long locationId, Map<String, Long> knowPaths) throws VcsException { final PreparedStatement statement = myConnection.getOrCreatePreparedStatement(SqliteTables.PREPARED_INSERT_REVISION, new ThrowableConvertor<Connection, PreparedStatement, SQLException>() { @Override public PreparedStatement convert(Connection connection) throws SQLException { return connection.prepareStatement("INSERT INTO " + SqliteTables.REVISION.TABLE_NAME + " ( " + StringUtil.join(Arrays.asList(SqliteTables.REVISION.ROOT_FK, SqliteTables.REVISION.AUTHOR_FK, SqliteTables.REVISION.DATE, SqliteTables.REVISION.NUMBER_INT, SqliteTables.REVISION.NUMBER_STR, SqliteTables.REVISION.COMMENT, SqliteTables.REVISION.COUNT, SqliteTables.REVISION.RAW_DATA), ", ") + ") VALUES (?,?,?,?,?,?,?,?)", Statement.RETURN_GENERATED_KEYS); } }); final Map<Long, CommittedChangeList> result = new HashMap<Long, CommittedChangeList>(); final CachingCommittedChangesProvider provider = (CachingCommittedChangesProvider)vcs.getCommittedChangesProvider(); try { statement.setLong(1, locationId); for (CommittedChangeList list : lists) { statement.setLong(2, authors.get(list.getCommitterName())); statement.setLong(3, list.getCommitDate().getTime()); statement.setLong(4, list.getNumber()); statement.setString(5, String.valueOf(list.getNumber())); statement.setString(6, list.getComment()); statement.setLong(7, list.getChanges().size()); final BufferExposingByteArrayOutputStream stream = new BufferExposingByteArrayOutputStream(); provider.writeChangeList(new DataOutputStream(stream), list); statement.setBytes(8, stream.toByteArray()); final long id = SqliteUtil.insert(statement); result.put(id, list); insertPathsChanges(knowPaths, list, id); } myConnection.commit(); } catch (SQLException e) { throw new VcsException(e); } catch (IOException e) { throw new VcsException(e); } return result; } private void insertPathsChanges(Map<String, Long> paths, CommittedChangeList list, long listId) throws VcsException { final PreparedStatement insert = myConnection.getOrCreatePreparedStatement(SqliteTables.PREPARED_INSERT_PATH_2_REVS, new ThrowableConvertor<Connection, PreparedStatement, SQLException>() { @Override public PreparedStatement convert(Connection connection) throws SQLException { return connection.prepareStatement("INSERT INTO " + SqliteTables.PATHS_2_REVS.TABLE_NAME + " ( " + StringUtil.join(Arrays.asList(SqliteTables.PATHS_2_REVS.PATH_FK, SqliteTables.PATHS_2_REVS.REVISION_FK, SqliteTables.PATHS_2_REVS.TYPE, SqliteTables.PATHS_2_REVS.COPY_PATH_ID, SqliteTables.PATHS_2_REVS.DELETE_PATH_ID, SqliteTables.PATHS_2_REVS.VISIBLE), " , ") + ") VALUES (?,?,?,?,?,?)", Statement.RETURN_GENERATED_KEYS); } }); try { insert.setLong(2, listId); final Collection<Change> withMoved = list.getChangesWithMovedTrees(); final Set<Change> simple = new HashSet<Change>(list.getChanges()); for (Change change : withMoved) { insertOneChange(paths, insert, change, simple.contains(change)); } } catch (SQLException e) { throw new VcsException(e); } } private void insertOneChange(Map<String, Long> paths, PreparedStatement insert, Change change, final boolean visible) throws SQLException { insert.setLong(6, visible ? 1 : 0); final ChangeTypeEnum type = ChangeTypeEnum.getChangeType(change); if (change.getBeforeRevision() == null) { // added, one path insert.setLong(1, paths.get(getPath(change.getAfterRevision()))); insert.setLong(3, type.getCode()); SqliteUtil.insert(insert); } else if (ChangeTypeEnum.MOVE.equals(type)) { // 2 paths final Long beforeId = paths.get(getPath(change.getBeforeRevision())); insert.setLong(1, beforeId); insert.setLong(3, ChangeTypeEnum.DELETE.getCode()); SqliteUtil.insert(insert); insert.setLong(1, paths.get(getPath(change.getAfterRevision()))); insert.setLong(4, beforeId); insert.setLong(3, type.getCode()); SqliteUtil.insert(insert); } else if (change.getAfterRevision() == null) { insert.setLong(1, paths.get(getPath(change.getBeforeRevision()))); insert.setLong(3, type.getCode()); SqliteUtil.insert(insert); } else { // only after insert.setLong(1, paths.get(getPath(change.getAfterRevision()))); insert.setLong(3, type.getCode()); SqliteUtil.insert(insert); } } private void checkAndAddPaths(final Set<String> paths, final Map<String, Long> known, final Long locationId) throws VcsException { final PreparedStatement select = myConnection.getOrCreatePreparedStatement(SqliteTables.PREPARED_READ_PATH, new ThrowableConvertor<Connection, PreparedStatement, SQLException>() { @Override public PreparedStatement convert(Connection connection) throws SQLException { return connection.prepareStatement("SELECT " + SqliteTables.PATHS.ID + " FROM " + SqliteTables.PATHS.TABLE_NAME + " WHERE " + SqliteTables.PATHS.ROOT_FK + " = ? AND " + SqliteTables.PATHS.PATH + " = ?"); } }); try { select.setLong(1, locationId); for (final String path : paths) { select.setString(2, path); final ResultSet set = select.executeQuery(); SqliteUtil.readSelectResults(set, new ThrowableRunnable<SQLException>() { @Override public void run() throws SQLException { known.put(path, set.getLong(1)); } }); } paths.removeAll(known.keySet()); if (paths.isEmpty()) return; final PreparedStatement insert = myConnection.getOrCreatePreparedStatement(SqliteTables.PREPARED_INSERT_PATH, new ThrowableConvertor<Connection, PreparedStatement, SQLException>() { @Override public PreparedStatement convert(Connection connection) throws SQLException { return connection.prepareStatement("INSERT INTO " + SqliteTables.PATHS.TABLE_NAME + " ( " + SqliteTables.PATHS.ROOT_FK + " , " + SqliteTables.PATHS.PATH + " ) VALUES (?,?)", Statement.RETURN_GENERATED_KEYS); } }); insert.setLong(1, locationId); for (String path : paths) { insert.setString(2, path); final long id = SqliteUtil.insert(insert); known.put(path, id); } } catch (SQLException e) { throw new VcsException(e); } } private void checkAndAddAuthors(final Set<String> names, final Map<String, Long> known) throws VcsException { final PreparedStatement statement = myConnection.getOrCreatePreparedStatement(SqliteTables.PREPARED_FILTER_KNOWN_AUTHORS, new ThrowableConvertor<Connection, PreparedStatement, SQLException>() { @Override public PreparedStatement convert(Connection connection) throws SQLException { return connection.prepareStatement("SELECT " + SqliteTables.AUTHOR.ID + ", " + SqliteTables.AUTHOR.NAME + " FROM " + SqliteTables.AUTHOR.TABLE_NAME + " WHERE " + SqliteTables.AUTHOR.NAME + "=?"); } }); try { for (final Iterator<String> iterator = names.iterator(); iterator.hasNext(); ) { final String name = iterator.next(); statement.setString(1, name); final ResultSet set = statement.executeQuery(); SqliteUtil.readSelectResults(set, new ThrowableRunnable<SQLException>() { @Override public void run() throws SQLException { final long id = set.getLong(SqliteTables.AUTHOR.ID); myKnownRepositoryLocations.addKnownAuthor(name, id); known.put(name, id); iterator.remove(); } }); } if (names.isEmpty()) return; final PreparedStatement insertAuthor = myConnection.getOrCreatePreparedStatement(SqliteTables.PREPARED_ADD_AUTHOR, new ThrowableConvertor<Connection, PreparedStatement, SQLException>() { @Override public PreparedStatement convert(Connection connection) throws SQLException { return connection.prepareStatement("INSERT INTO " + SqliteTables.AUTHOR.TABLE_NAME + " ( " + SqliteTables.AUTHOR.NAME + ") VALUES (?)", Statement.RETURN_GENERATED_KEYS); } }); for (String name : names) { insertAuthor.setString(1, name); final long id = SqliteUtil.insert(insertAuthor); myKnownRepositoryLocations.addKnownAuthor(name, id); known.put(name, id); } } catch (SQLException e) { throw new VcsException(e); } } @NotNull public RevisionId getFirstRevision(final AbstractVcs vcs, final String root) { final String systemIndependent = FileUtil.toSystemIndependentName(root); if (! myKnownRepositoryLocations.exists(vcs.getName(), systemIndependent)) { return RevisionId.FAKE; } final long locationId = myKnownRepositoryLocations.getLocationId(vcs.getName(), systemIndependent); return myKnownRepositoryLocations.getFirstRevision(locationId); } @NotNull public RevisionId getLastRevision(final AbstractVcs vcs, final String root) { final String systemIndependent = FileUtil.toSystemIndependentName(root); if (! myKnownRepositoryLocations.exists(vcs.getName(), systemIndependent)) { return RevisionId.FAKE; } final long locationId = myKnownRepositoryLocations.getLocationId(vcs.getName(), systemIndependent); return myKnownRepositoryLocations.getLastRevision(locationId); } // alternatively, we can use usual lists + a map marking incoming public List<ReceivedChangeList> selectIncoming(final AbstractVcs vcs, final RepositoryLocation location) throws VcsException { final Map<Long, CommittedChangeList> full = new HashMap<Long, CommittedChangeList>(); final TreeMap<Long, Set<String>> incomingPaths = new TreeMap<Long, Set<String>>(); final long locationId = getLocationId(vcs, location); final PreparedStatement select = myConnection.getOrCreatePreparedStatement(SqliteTables.PREPARED_SELECT_INCOMING, new ThrowableConvertor<Connection, PreparedStatement, SQLException>() { @Override public PreparedStatement convert(Connection connection) throws SQLException { // todo control what is selected return connection.prepareStatement("SELECT R." + SqliteTables.REVISION.NUMBER_INT + " , R." + SqliteTables.REVISION.RAW_DATA + " , P." + SqliteTables.PATHS.PATH + " FROM " + SqliteTables.INCOMING_PATHS.TABLE_NAME + "I INNER JOIN " + SqliteTables.PATHS_2_REVS.TABLE_NAME + "PR ON I." + SqliteTables.INCOMING_PATHS.PR_FK + " = PR." + SqliteTables.PATHS_2_REVS.ID + ", " + SqliteTables.REVISION.TABLE_NAME + " R ON PR." + SqliteTables.PATHS_2_REVS.REVISION_FK + " = R." + SqliteTables.REVISION.ID + " , " + SqliteTables.PATHS_2_REVS.TABLE_NAME + "P ON PR." + SqliteTables.PATHS_2_REVS.PATH_FK + " = P." + SqliteTables.PATHS.ID + " WHERE R." + SqliteTables.REVISION.ROOT_FK + "=?"); } }); final CachingCommittedChangesProvider provider = vcs.getCachingCommittedChangesProvider(); try { select.setLong(1, locationId); final ResultSet set = select.executeQuery(); SqliteUtil.readSelectResults(set, new ThrowableRunnable<SQLException>() { @Override public void run() throws SQLException { final long revNum = set.getLong("R." + SqliteTables.REVISION.NUMBER_INT); Set<String> paths = incomingPaths.get(revNum); if (paths == null) { final byte[] bytes = set.getBytes("R." + SqliteTables.REVISION.RAW_DATA); final CommittedChangeList nativeList = readListByProvider(bytes, provider, location); full.put(revNum, nativeList); paths = new HashSet<String>(); incomingPaths.put(revNum, paths); } final String path = set.getString("P." + SqliteTables.PATHS.PATH); paths.add(path); } }); } catch (SQLException e) { throw new VcsException(e); } final List<ReceivedChangeList> result = new ArrayList<ReceivedChangeList>(); for (Map.Entry<Long, Set<String>> entry : incomingPaths.entrySet()) { final Long revNum = entry.getKey(); } // TODO continue here // TODO continue here // TODO continue here // TODO continue here // TODO continue here return null; // return new ArrayList<ReceivedChangeList>(incomingPaths.descendingMap().values()); } private long getLocationId(AbstractVcs vcs, RepositoryLocation location) { final String normalizedLocation = normalizeLocation(location); if (! myKnownRepositoryLocations.exists(vcs.getName(), normalizedLocation)) { assert false; } return myKnownRepositoryLocations.getLocationId(vcs.getName(), normalizedLocation); } public void insertIncoming(final AbstractVcs vcs, final RepositoryLocation location, long pathId, final long lastRev, final long oldRev) throws VcsException { assert lastRev > 0 || oldRev > 0; final long locationId = getLocationId(vcs, location); if (lastRev > 0 && oldRev > 0) { final PreparedStatement insertBoth = myConnection.getOrCreatePreparedStatement(SqliteTables.PREPARED_INSERT_INCOMING, new ThrowableConvertor<Connection, PreparedStatement, SQLException>() { @Override public PreparedStatement convert(Connection connection) throws SQLException { return connection.prepareStatement("INSERT INTO " + SqliteTables.INCOMING_PATHS.TABLE_NAME + " ( " + SqliteTables.INCOMING_PATHS.PR_FK + " ) VALUES (SELECT " + SqliteTables.PATHS_2_REVS.ID + " FROM " + SqliteTables.PATHS_2_REVS.TABLE_NAME + "PR INNER JOIN " + SqliteTables.REVISION.TABLE_NAME + " R ON R." + SqliteTables.REVISION.ID + "=PR." + SqliteTables.PATHS_2_REVS.REVISION_FK + " WHERE R." + SqliteTables.REVISION.NUMBER_INT + "<=? AND R." + SqliteTables.REVISION.NUMBER_INT + ">=? AND R." + SqliteTables.REVISION.ROOT_FK + "=? AND PR." + SqliteTables.PATHS_2_REVS.PATH_FK + "=?)"); } }); try { insertBoth.setLong(1, lastRev); insertBoth.setLong(2, oldRev); insertBoth.setLong(3, locationId); insertBoth.setLong(4, pathId); final int numRows = insertBoth.executeUpdate(); return; } catch (SQLException e) { throw new VcsException(e); } } if (lastRev > 0) { final PreparedStatement insertOnlyLast = myConnection.getOrCreatePreparedStatement(SqliteTables.PREPARED_INSERT_INCOMING, new ThrowableConvertor<Connection, PreparedStatement, SQLException>() { @Override public PreparedStatement convert(Connection connection) throws SQLException { return connection.prepareStatement("INSERT INTO " + SqliteTables.INCOMING_PATHS.TABLE_NAME + " ( " + SqliteTables.INCOMING_PATHS.PR_FK + " ) VALUES (SELECT " + SqliteTables.PATHS_2_REVS.ID + " FROM " + SqliteTables.PATHS_2_REVS.TABLE_NAME + "PR INNER JOIN " + SqliteTables.REVISION.TABLE_NAME + " R ON R." + SqliteTables.REVISION.ID + "=PR." + SqliteTables.PATHS_2_REVS.REVISION_FK + " WHERE R." + SqliteTables.REVISION.NUMBER_INT + "<=? AND R." + SqliteTables.REVISION.ROOT_FK + "=? AND PR." + SqliteTables.PATHS_2_REVS.PATH_FK + "=?)"); } }); try { insertOnlyLast.setLong(1, lastRev); insertOnlyLast.setLong(2, locationId); insertOnlyLast.setLong(3, pathId); final int numRows = insertOnlyLast.executeUpdate(); return; } catch (SQLException e) { throw new VcsException(e); } } // first rev > 0 final PreparedStatement insertOnlyFirst = myConnection.getOrCreatePreparedStatement(SqliteTables.PREPARED_INSERT_INCOMING, new ThrowableConvertor<Connection, PreparedStatement, SQLException>() { @Override public PreparedStatement convert(Connection connection) throws SQLException { return connection.prepareStatement("INSERT INTO " + SqliteTables.INCOMING_PATHS.TABLE_NAME + " ( " + SqliteTables.INCOMING_PATHS.PR_FK + " ) VALUES (SELECT " + SqliteTables.PATHS_2_REVS.ID + " FROM " + SqliteTables.PATHS_2_REVS.TABLE_NAME + "PR INNER JOIN " + SqliteTables.REVISION.TABLE_NAME + " R ON R." + SqliteTables.REVISION.ID + "=PR." + SqliteTables.PATHS_2_REVS.REVISION_FK + " WHERE R." + SqliteTables.REVISION.NUMBER_INT + ">=? AND R." + SqliteTables.REVISION.ROOT_FK + "=? AND PR." + SqliteTables.PATHS_2_REVS.PATH_FK + "=?)"); } }); try { insertOnlyFirst.setLong(1, oldRev); insertOnlyFirst.setLong(2, locationId); insertOnlyFirst.setLong(3, pathId); final int numRows = insertOnlyFirst.executeUpdate(); return; } catch (SQLException e) { throw new VcsException(e); } } public List<CommittedChangeList> readLists(final AbstractVcs vcs, final RepositoryLocation location, final RevisionId last, final RevisionId old, final String subfolder) throws VcsException { final String root = normalizeLocation(location); final RevisionId lastExisitngData = getLastRevision(vcs, root); final RevisionId firstExistingData = getFirstRevision(vcs, root); if (lastExisitngData.isFake() || firstExistingData.isFake()) return Collections.emptyList(); final SelectListsQueryHelper helper = new SelectListsQueryHelper(myConnection, lastExisitngData, firstExistingData, last, old, getLocationId(vcs, location), subfolder); final List<CommittedChangeList> result = new ArrayList<CommittedChangeList>(); try { final PreparedStatement statement = helper.createStatement(); final CachingCommittedChangesProvider provider = (CachingCommittedChangesProvider)vcs.getCommittedChangesProvider(); final ResultSet set = statement.executeQuery(); final Set<Long> controlSet = new HashSet<Long>(); SqliteUtil.readSelectResults(set, new ThrowableRunnable<SQLException>() { @Override public void run() throws SQLException { final long number = set.getLong(SqliteTables.REVISION.NUMBER_INT); if (controlSet.contains(number)) { return; } controlSet.add(number); final byte[] bytes = set.getBytes(SqliteTables.REVISION.RAW_DATA); final CommittedChangeList list = readListByProvider(bytes, provider, location); result.add(list); } }); } catch (SQLException e) { throw new VcsException(e); } return result; } public List<CommittedChangeList> readLists(final AbstractVcs vcs, final RepositoryLocation location, final long lastRev, final long oldRev) throws VcsException { final String root = normalizeLocation(location); final long lastExisting = getLastRevision(vcs, root).getNumber(); final long firstExisting = getFirstRevision(vcs, root).getNumber(); if (lastExisting == -1 || firstExisting == -1) return Collections.emptyList(); final long operatingFirst = oldRev == -1 ? firstExisting : oldRev; final long operatingLast = lastRev == -1 ? lastExisting : lastRev; final PreparedStatement statement = myConnection.getOrCreatePreparedStatement(SqliteTables.PREPARED_SELECT_REVISIONS, new ThrowableConvertor<Connection, PreparedStatement, SQLException>() { @Override public PreparedStatement convert(Connection connection) throws SQLException { // "real" statement - will be used when each committed changes provider will have the method to restore revision uniformly, through changed paths + # // maybe it's safier to call left outer join, but for current VCSes we always have at least one path changed for each revision -> inner join is preferable /*return connection.prepareStatement("SELECT * FROM " + SqliteTables.REVISION.TABLE_NAME + "R , " + SqliteTables.PATHS + "P , "+ SqliteTables.AUTHOR + "A INNER JOIN " + SqliteTables.PATHS_2_REVS + "PR ON PR." + SqliteTables.PATHS_2_REVS.REVISION_FK + "=R." + SqliteTables.REVISION.ID + " AND PR." + SqliteTables.PATHS_2_REVS.PATH_FK + "=" + SqliteTables.PATHS.ID + " AND R." + SqliteTables.REVISION.AUTHOR_FK + "=A." + SqliteTables.AUTHOR.ID + " WHERE R." + SqliteTables.REVISION.NUMBER_INT + ">=? AND R." + SqliteTables.REVISION.NUMBER_INT + "<=?");*/ //1=first, 2=last return connection.prepareStatement("SELECT * FROM " + SqliteTables.REVISION.TABLE_NAME + " WHERE " + SqliteTables.REVISION.NUMBER_INT + ">=? AND " + SqliteTables.REVISION.NUMBER_INT + "<=? ORDER BY " + SqliteTables.REVISION.NUMBER_INT + " DESC"); } }); final List<CommittedChangeList> result = new ArrayList<CommittedChangeList>(); try { statement.setLong(1, operatingFirst); statement.setLong(2, operatingLast); final CachingCommittedChangesProvider provider = (CachingCommittedChangesProvider)vcs.getCommittedChangesProvider(); final ResultSet set = statement.executeQuery(); SqliteUtil.readSelectResults(set, new ThrowableRunnable<SQLException>() { @Override public void run() throws SQLException { final byte[] bytes = set.getBytes(SqliteTables.REVISION.RAW_DATA); final CommittedChangeList list = readListByProvider(bytes, provider, location); result.add(list); /*final long revisionId = set.getLong("R." + SqliteTables.REVISION.ID); CommittedChangeList list = lists.get(revisionId); if (list == null) { final long numberLong = set.getLong("R." + SqliteTables.REVISION.NUMBER_INT); final String numberStr = set.getString("R." + SqliteTables.REVISION.NUMBER_STR); final String comment = set.getString("R." + SqliteTables.REVISION.COMMENT); final Long date = set.getLong("R." + SqliteTables.REVISION.DATE); final String author = set.getString("A." + SqliteTables.REVISION.AUTHOR_FK); list = new CommittedChangeListImpl("", comment, author, numberLong, new Date(date), Collections.<Change>emptyList()); }*/ } }); } catch (SQLException e) { throw new VcsException(e); } return result; } private CommittedChangeList readListByProvider(byte[] bytes, CachingCommittedChangesProvider provider, RepositoryLocation location) throws SQLException { final CommittedChangeList list; try { list = provider.readChangeList(location, new DataInputStream(new ByteArrayInputStream(bytes))); } catch (IOException e) { throw new SQLException(e); } return list; } public PathState getPathState(final AbstractVcs vcs, final RepositoryLocation location, final String path) throws VcsException { String normalizedPath = FileUtil.toSystemIndependentName(path); normalizedPath = normalizedPath.endsWith("/") ? normalizedPath : normalizedPath + "/"; final String normalizedLocation = normalizeLocation(location); if (! myKnownRepositoryLocations.exists(vcs.getName(), normalizedLocation)) return null; final PreparedStatement maxStatement = myConnection.getOrCreatePreparedStatement(SqliteTables.PREPARED_SELECT_PATH_DATA, new ThrowableConvertor<Connection, PreparedStatement, SQLException>() { @Override public PreparedStatement convert(Connection connection) throws SQLException { final String innerQuery = "SELECT MAX(R." + SqliteTables.REVISION.NUMBER_INT + ") MAX FROM " + SqliteTables.PATHS_2_REVS.TABLE_NAME + " PR INNER JOIN " + SqliteTables.REVISION.TABLE_NAME + " R, " + SqliteTables.PATHS.TABLE_NAME + " P ON PR." + SqliteTables.PATHS_2_REVS.REVISION_FK + "=R." + SqliteTables.REVISION.ID + " AND PR." + SqliteTables.PATHS_2_REVS.PATH_FK + "=P." + SqliteTables.PATHS.ID + " WHERE P." + SqliteTables.PATHS.PATH + "=? AND R." + SqliteTables.REVISION.ROOT_FK + "=?"; return connection.prepareStatement("SELECT R." + SqliteTables.REVISION.NUMBER_INT + " REV_NU, PR." + SqliteTables.PATHS_2_REVS.TYPE + " TYPE FROM " + SqliteTables.PATHS_2_REVS.TABLE_NAME + " PR INNER JOIN " + SqliteTables.REVISION.TABLE_NAME + " R, " + SqliteTables.PATHS.TABLE_NAME + " P ON PR." + SqliteTables.PATHS_2_REVS.REVISION_FK + "=R." + SqliteTables.REVISION.ID + " AND PR." + SqliteTables.PATHS_2_REVS.PATH_FK + "=P." + SqliteTables.PATHS.ID + " WHERE P." + SqliteTables.PATHS.PATH + "=? AND R." + SqliteTables.REVISION.ROOT_FK + "=? AND R." + SqliteTables.REVISION.NUMBER_INT + " = (" + innerQuery + ")"); } }); try { maxStatement.setString(1, normalizedPath); maxStatement.setString(3, normalizedPath); final long locationId = myKnownRepositoryLocations.getLocationId(vcs.getName(), normalizedLocation); maxStatement.setLong(2, locationId); maxStatement.setLong(4, locationId); final long type[] = new long[1]; final long maxRev[] = new long[1]; maxRev[0] = -1; final ResultSet set = maxStatement.executeQuery(); SqliteUtil.readSelectResults(set, new ThrowableRunnable<SQLException>() { @Override public void run() throws SQLException { maxRev[0] = set.getLong("REV_NU"); type[0] = set.getLong("TYPE"); } }); if (maxRev[0] <= 0) return null; if (type[0] == -100) return null; final ChangeTypeEnum changeType = ChangeTypeEnum.getChangeType(type[0]); if (changeType == null) return null; return new PathState(maxRev[0], ! ChangeTypeEnum.DELETE.equals(changeType)); } catch (SQLException e) { throw new VcsException(e); } } private String normalizeLocation(RepositoryLocation location) { return FileUtil.toSystemIndependentName(location.toPresentableString()); } // this is batch one /*public <T> void getLastRevisionsForPath(final AbstractVcs vcs, final RepositoryLocation location, final Convertor<T, String> pathConvertor, Set<T> files, final PairConsumer<T, PathState> consumer) throws VcsException { final String normalizedLocation = normalizeLocation(location); if (! myKnownRepositoryLocations.exists(vcs.getName(), normalizedLocation)) return; final long locationId = myKnownRepositoryLocations.getLocationId(vcs.getName(), normalizedLocation); if (files.size() < ourLastPathRevisionBatchSize) { iterateGetPathState(vcs, location, pathConvertor, files, consumer); return; } String s = StringUtil.repeat("?,", ourLastPathRevisionBatchSize); final String repeat = s.substring(0, s.length() - 1); final PreparedStatement maxStatement = myConnection.getOrCreatePreparedStatement(SqliteTables.PREPARED_SELECT_PATH_DATA_BATCH, new ThrowableConvertor<Connection, PreparedStatement, SQLException>() { @Override public PreparedStatement convert(Connection connection) throws SQLException { return connection.prepareStatement("SELECT MAX(R." + SqliteTables.REVISION.NUMBER_INT + ") MAX, P."+ SqliteTables.PATHS.PATH + " PATH, P." + SqliteTables.PATHS.ID + " PATH_ID FROM " + SqliteTables.PATHS_2_REVS.TABLE_NAME + " PR INNER JOIN " + SqliteTables.REVISION.TABLE_NAME + " R, " + SqliteTables.PATHS.TABLE_NAME + " P ON PR." + SqliteTables.PATHS_2_REVS.REVISION_FK + "=R." + SqliteTables.REVISION.ID + " AND PR." + SqliteTables.PATHS_2_REVS.PATH_FK + "=P." + SqliteTables.PATHS.ID + " WHERE P." + SqliteTables.PATHS.PATH + " IN (" + repeat + ") AND R." + SqliteTables.REVISION.ROOT_FK + "=?"); } }); final PreparedStatement typeStatement = myConnection.getOrCreatePreparedStatement(SqliteTables.PREPARED_PATHS_2_REVS_BATCH, new ThrowableConvertor<Connection, PreparedStatement, SQLException>() { @Override public PreparedStatement convert(Connection connection) throws SQLException { return connection.prepareStatement("SELECT PR." + SqliteTables.PATHS_2_REVS.TYPE + " TYPE, R." + SqliteTables.REVISION.NUMBER_INT + " REV_NUM, PR." + SqliteTables.PATHS_2_REVS.PATH_FK + " PATH_ID " + " FROM " + SqliteTables.PATHS_2_REVS.TABLE_NAME + " PR INNER JOIN " + SqliteTables.REVISION.TABLE_NAME + " R ON PR." + SqliteTables.PATHS_2_REVS.REVISION_FK + "=R." + SqliteTables.REVISION.ID + " WHERE R." + SqliteTables.REVISION.NUMBER_INT + " IN (" + repeat + ") AND PR." + SqliteTables.PATHS_2_REVS.PATH_FK + " IN(" + repeat + ")"); } }); final List<List<T>> split = new CollectionSplitter<T>(ourLastPathRevisionBatchSize).split(files); try { maxStatement.setLong(ourLastPathRevisionBatchSize + 1, locationId); for (List<T> list : split) { final Map<String, T> paths2elements = new HashMap<String, T>(); final int size = list.size(); if (size < ourLastPathRevisionBatchSize) { iterateGetPathState(vcs, location, pathConvertor, list, consumer); return; } for (int i = 0; i < size; i++) { T t = list.get(i); final String convert = pathConvertor.convert(t); assert ! paths2elements.containsKey(convert); paths2elements.put(convert, t); maxStatement.setString(i + 1, convert); } final ResultSet set = maxStatement.executeQuery(); final Map<Long, Pair<Long, String>> maxMap = new HashMap<Long, Pair<Long, String>>(); SqliteUtil.readSelectResults(set, new ThrowableRunnable<SQLException>() { @Override public void run() throws SQLException { final long maxRev = set.getLong("MAX"); final String path = set.getString("PATH"); final long pathId = set.getLong("PATH_ID"); maxMap.put(pathId, Pair.create(maxRev, path)); } }); int i = 0; for (Map.Entry<Long, Pair<Long, String>> entry : maxMap.entrySet()) { typeStatement.setLong(i + 1, entry.getValue().getFirst()); // rev # typeStatement.setLong(ourLastPathRevisionBatchSize + i + 1, entry.getKey()); // path id ++ i; } final ResultSet detailsSet = typeStatement.executeQuery(); SqliteUtil.readSelectResults(detailsSet, new ThrowableRunnable<SQLException>() { @Override public void run() throws SQLException { final long type = detailsSet.getLong("TYPE"); final ChangeTypeEnum changeType = ChangeTypeEnum.getChangeType(type); if (changeType == null) { LOG.info("Illegal change type: " + type); return; } final long revNum = detailsSet.getLong("REV_NUM"); final long pathId = detailsSet.getLong("PATH_ID"); final Pair<Long, String> pair = maxMap.get(pathId); if (pair.getFirst() == revNum) { consumer.consume(paths2elements.get(pair.getSecond()), new PathState(revNum, ! ChangeTypeEnum.DELETE.equals(changeType))); } } }); } } catch (SQLException e) { throw new VcsException(e); } }*/ /*private <T> void iterateGetPathState(AbstractVcs vcs, RepositoryLocation location, Convertor<T, String> pathConvertor, Collection<T> files, PairConsumer<T, PathState> consumer) throws VcsException { for (T file : files) { final PathState state = getPathState(vcs, location, pathConvertor.convert(file)); consumer.consume(file, state); } }*/ }