blob: 9695ec7e54087bbe88813d6817b03861b65182bb [file] [log] [blame]
/*
* 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.remove(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);
}
}*/
}