use of org.projectnessie.versioned.persist.adapter.CommitLogEntry in project nessie by projectnessie.
the class AbstractMergeTransplant method transplant.
@ParameterizedTest
@ValueSource(ints = { 3, 10, DEFAULT_KEY_LIST_DISTANCE, DEFAULT_KEY_LIST_DISTANCE + 1, 100 })
void transplant(int numCommits) throws Exception {
AtomicInteger unifier = new AtomicInteger();
Function<ByteString, ByteString> metadataUpdater = commitMeta -> ByteString.copyFromUtf8(commitMeta.toStringUtf8() + " transplanted " + unifier.getAndIncrement());
Hash[] commits = mergeTransplant(numCommits, (target, expectedHead, branch, commitHashes, i) -> databaseAdapter.transplant(target, expectedHead, Arrays.asList(commitHashes).subList(0, i + 1), metadataUpdater));
BranchName conflict = BranchName.of("conflict");
// no conflict, when transplanting the commits from against the current HEAD of the
// conflict-branch
Hash noConflictHead = databaseAdapter.hashOnReference(conflict, Optional.empty());
Hash transplanted = databaseAdapter.transplant(conflict, Optional.of(noConflictHead), Arrays.asList(commits), metadataUpdater);
int offset = unifier.get();
try (Stream<CommitLogEntry> log = databaseAdapter.commitLog(transplanted).limit(commits.length)) {
AtomicInteger testOffset = new AtomicInteger(offset);
assertThat(log.map(CommitLogEntry::getMetadata).map(ByteString::toStringUtf8)).containsExactlyElementsOf(IntStream.range(0, commits.length).map(i -> commits.length - i - 1).mapToObj(i -> "commit " + i + " transplanted " + testOffset.decrementAndGet()).collect(Collectors.toList()));
}
// again, no conflict (same as above, just again)
transplanted = databaseAdapter.transplant(conflict, Optional.empty(), Arrays.asList(commits), metadataUpdater);
offset = unifier.get();
try (Stream<CommitLogEntry> log = databaseAdapter.commitLog(transplanted).limit(commits.length)) {
AtomicInteger testOffset = new AtomicInteger(offset);
assertThat(log.map(CommitLogEntry::getMetadata).map(ByteString::toStringUtf8)).containsExactlyElementsOf(IntStream.range(0, commits.length).map(i -> commits.length - i - 1).mapToObj(i -> "commit " + i + " transplanted " + testOffset.decrementAndGet()).collect(Collectors.toList()));
}
assertThatThrownBy(() -> databaseAdapter.transplant(conflict, Optional.empty(), Collections.emptyList(), Function.identity())).isInstanceOf(IllegalArgumentException.class).hasMessage("No hashes to transplant given.");
}
use of org.projectnessie.versioned.persist.adapter.CommitLogEntry in project nessie by projectnessie.
the class TxDatabaseAdapter method doFetchMultipleFromCommitLog.
@Override
protected List<CommitLogEntry> doFetchMultipleFromCommitLog(ConnectionWrapper c, List<Hash> hashes) {
String sql = sqlForManyPlaceholders(SqlStatements.SELECT_COMMIT_LOG_MANY, hashes.size());
try (PreparedStatement ps = c.conn().prepareStatement(sql)) {
ps.setString(1, config.getRepositoryId());
for (int i = 0; i < hashes.size(); i++) {
ps.setString(2 + i, hashes.get(i).asString());
}
Map<Hash, CommitLogEntry> result = new HashMap<>(hashes.size() * 2);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
CommitLogEntry entry = protoToCommitLogEntry(rs.getBytes(1));
result.put(entry.getHash(), entry);
}
}
return hashes.stream().map(result::get).collect(Collectors.toList());
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
use of org.projectnessie.versioned.persist.adapter.CommitLogEntry in project nessie by projectnessie.
the class AbstractDatabaseAdapter method buildIndividualCommit.
/**
* Builds a {@link CommitLogEntry} using the given values. This function also includes a {@link
* KeyList}, if triggered by the values of {@code currentKeyListDistance} and {@link
* DatabaseAdapterConfig#getKeyListDistance()}, so read operations may happen.
*/
protected CommitLogEntry buildIndividualCommit(OP_CONTEXT ctx, long timeInMicros, List<Hash> parentHashes, long commitSeq, ByteString commitMeta, List<KeyWithBytes> puts, List<Key> deletes, int currentKeyListDistance, Consumer<Hash> newKeyLists, @Nonnull Function<Hash, CommitLogEntry> inMemoryCommits) throws ReferenceNotFoundException {
Hash commitHash = individualCommitHash(parentHashes, commitMeta, puts, deletes);
int keyListDistance = currentKeyListDistance + 1;
CommitLogEntry entry = CommitLogEntry.of(timeInMicros, commitHash, commitSeq, parentHashes, commitMeta, puts, deletes, keyListDistance, null, Collections.emptyList());
if (keyListDistance >= config.getKeyListDistance()) {
entry = buildKeyList(ctx, entry, newKeyLists, inMemoryCommits);
}
return entry;
}
use of org.projectnessie.versioned.persist.adapter.CommitLogEntry in project nessie by projectnessie.
the class AbstractDatabaseAdapter method transplantAttempt.
/**
* Logic implementation of a transplant-attempt.
*
* @param ctx technical operation context
* @param targetBranch target reference with expected HEAD
* @param expectedHead if present, {@code targetBranch}'s current HEAD must be equal to this value
* @param targetHead current HEAD of {@code targetBranch}
* @param sequenceToTransplant sequential list of commits to transplant from {@code source}
* @param branchCommits consumer for the individual commits to merge
* @param newKeyLists consumer for optimistically written {@link KeyListEntity}s
* @param rewriteMetadata function to rewrite the commit-metadata for copied commits
* @return hash of the last commit-log-entry written to {@code targetBranch}
*/
protected Hash transplantAttempt(OP_CONTEXT ctx, long timeInMicros, BranchName targetBranch, Optional<Hash> expectedHead, Hash targetHead, List<Hash> sequenceToTransplant, Consumer<Hash> branchCommits, Consumer<Hash> newKeyLists, Function<ByteString, ByteString> rewriteMetadata) throws ReferenceNotFoundException, ReferenceConflictException {
if (sequenceToTransplant.isEmpty()) {
throw new IllegalArgumentException("No hashes to transplant given.");
}
// 1. ensure 'expectedHash' is a parent of HEAD-of-'targetBranch' & collect keys
List<CommitLogEntry> targetEntriesReverseChronological = new ArrayList<>();
hashOnRef(ctx, targetHead, targetBranch, expectedHead, targetEntriesReverseChronological::add);
// Exclude the expected-hash on the target-branch from key-collisions check
if (!targetEntriesReverseChronological.isEmpty() && expectedHead.isPresent() && targetEntriesReverseChronological.get(0).getHash().equals(expectedHead.get())) {
targetEntriesReverseChronological.remove(0);
}
Collections.reverse(targetEntriesReverseChronological);
// 2. Collect modified keys.
Set<Key> keysTouchedOnTarget = collectModifiedKeys(targetEntriesReverseChronological);
// 4. ensure 'sequenceToTransplant' is sequential
int[] index = new int[] { sequenceToTransplant.size() - 1 };
Hash lastHash = sequenceToTransplant.get(sequenceToTransplant.size() - 1);
List<CommitLogEntry> commitsToTransplantChronological = takeUntilExcludeLast(readCommitLogStream(ctx, lastHash), e -> {
int i = index[0]--;
if (i == -1) {
return true;
}
if (!e.getHash().equals(sequenceToTransplant.get(i))) {
throw new IllegalArgumentException("Sequence of hashes is not contiguous.");
}
return false;
}).collect(Collectors.toList());
// 5. check for key-collisions
checkForKeyCollisions(ctx, targetHead, keysTouchedOnTarget, commitsToTransplantChronological);
// (no need to verify the global states during a transplant)
// 6. re-apply commits in 'sequenceToTransplant' onto 'targetBranch'
targetHead = copyCommits(ctx, timeInMicros, targetHead, commitsToTransplantChronological, newKeyLists, rewriteMetadata);
// 7. Write commits
commitsToTransplantChronological.stream().map(CommitLogEntry::getHash).forEach(branchCommits);
writeMultipleCommits(ctx, commitsToTransplantChronological);
return targetHead;
}
use of org.projectnessie.versioned.persist.adapter.CommitLogEntry in project nessie by projectnessie.
the class AbstractDatabaseAdapter method checkForKeyCollisions.
/**
* For merge/transplant, verifies that the given commits do not touch any of the given keys.
*
* @param commitsChronological list of commit-log-entries, in order of commit-operations,
* chronological order
*/
protected void checkForKeyCollisions(OP_CONTEXT ctx, Hash refHead, Set<Key> keysTouchedOnTarget, List<CommitLogEntry> commitsChronological) throws ReferenceConflictException, ReferenceNotFoundException {
Set<Key> keyCollisions = new HashSet<>();
for (int i = commitsChronological.size() - 1; i >= 0; i--) {
CommitLogEntry sourceCommit = commitsChronological.get(i);
Stream.concat(sourceCommit.getPuts().stream().map(KeyWithBytes::getKey), sourceCommit.getDeletes().stream()).filter(keysTouchedOnTarget::contains).forEach(keyCollisions::add);
}
if (!keyCollisions.isEmpty()) {
removeKeyCollisionsForNamespaces(ctx, refHead, commitsChronological.get(commitsChronological.size() - 1).getHash(), keyCollisions);
if (!keyCollisions.isEmpty()) {
throw new ReferenceConflictException(String.format("The following keys have been changed in conflict: %s", keyCollisions.stream().map(k -> String.format("'%s'", k.toString())).collect(Collectors.joining(", "))));
}
}
}
Aggregations