private IndexReadAllResult ReadAllEventsForwardInternal(TFPos pos, int maxCount, int maxSearchWindow, IEventFilter eventFilter) { var records = new List <CommitEventRecord>(); var nextPos = pos; // in case we are at position after which there is no commit at all, in that case we have to force // PreparePosition to long.MaxValue, so if you decide to read backwards from PrevPos, // you will receive all prepares. var prevPos = new TFPos(pos.CommitPosition, long.MaxValue); var consideredEventsCount = 0L; var firstCommit = true; var reachedEndOfStream = false; using (var reader = _backend.BorrowReader()) { long nextCommitPos = pos.CommitPosition; while (records.Count < maxCount && consideredEventsCount < maxSearchWindow) { if (nextCommitPos > _indexCommitter.LastIndexedPosition) { reachedEndOfStream = true; break; } reader.Reposition(nextCommitPos); SeqReadResult result; while ((result = reader.TryReadNext()).Success && !IsCommitAlike(result.LogRecord)) { // skip until commit } if (!result.Success) // no more records in TF { break; } nextCommitPos = result.RecordPostPosition; switch (result.LogRecord.RecordType) { case LogRecordType.Prepare: case LogRecordType.Stream: { var prepare = (IPrepareLogRecord <TStreamId>)result.LogRecord; if (firstCommit) { firstCommit = false; prevPos = new TFPos(result.RecordPrePosition, result.RecordPrePosition); } if (prepare.Flags.HasAnyOf(PrepareFlags.Data | PrepareFlags.StreamDelete) && new TFPos(prepare.LogPosition, prepare.LogPosition) >= pos) { var streamName = _streamNames.LookupName(prepare.EventStreamId); var eventRecord = new EventRecord(eventNumber: prepare.ExpectedVersion + 1, prepare, streamName); consideredEventsCount++; if (eventFilter.IsEventAllowed(eventRecord)) { records.Add(new CommitEventRecord(eventRecord, prepare.LogPosition)); } nextPos = new TFPos(result.RecordPostPosition, 0); } break; } case LogRecordType.Commit: { var commit = (CommitLogRecord)result.LogRecord; if (firstCommit) { firstCommit = false; // for backward pass we want to allow read the same commit and skip read prepares, // so we put post-position of commit and post-position of prepare as TFPos for backward pass prevPos = new TFPos(result.RecordPostPosition, pos.PreparePosition); } reader.Reposition(commit.TransactionPosition); while (consideredEventsCount < maxCount) { result = reader.TryReadNext(); if (!result.Success) // no more records in TF { break; } // prepare with TransactionEnd could be scavenged already // so we could reach the same commit record. In that case have to stop if (result.LogRecord.LogPosition >= commit.LogPosition) { break; } if (result.LogRecord.RecordType != LogRecordType.Prepare) { continue; } var prepare = (IPrepareLogRecord <TStreamId>)result.LogRecord; if (prepare.TransactionPosition != commit.TransactionPosition) // wrong prepare { continue; } // prepare with useful data or delete tombstone if (prepare.Flags.HasAnyOf(PrepareFlags.Data | PrepareFlags.StreamDelete) && new TFPos(commit.LogPosition, prepare.LogPosition) >= pos) { var streamName = _streamNames.LookupName(prepare.EventStreamId); var eventRecord = new EventRecord(commit.FirstEventNumber + prepare.TransactionOffset, prepare, streamName); consideredEventsCount++; if (eventFilter.IsEventAllowed(eventRecord)) { records.Add(new CommitEventRecord(eventRecord, commit.LogPosition)); } // for forward pass position is inclusive, // so we put pre-position of commit and post-position of prepare nextPos = new TFPos(commit.LogPosition, result.RecordPostPosition); } if (prepare.Flags.HasAnyOf(PrepareFlags.TransactionEnd)) { break; } } break; } default: throw new Exception(string.Format("Unexpected log record type: {0}.", result.LogRecord.RecordType)); } } return(new IndexReadAllResult(records, pos, nextPos, prevPos, reachedEndOfStream, consideredEventsCount)); } }
private IndexReadAllResult ReadAllEventsBackwardInternal(TFPos pos, int maxCount, int maxSearchWindow, IEventFilter eventFilter) { var records = new List <CommitEventRecord>(); var nextPos = pos; // in case we are at position after which there is no commit at all, in that case we have to force // PreparePosition to 0, so if you decide to read backwards from PrevPos, // you will receive all prepares. var prevPos = new TFPos(pos.CommitPosition, 0); var consideredEventsCount = 0L; bool firstCommit = true; var reachedEndOfStream = false; using (var reader = _backend.BorrowReader()) { long nextCommitPostPos = pos.CommitPosition; while (records.Count < maxCount && consideredEventsCount < maxSearchWindow) { reader.Reposition(nextCommitPostPos); SeqReadResult result; while ((result = reader.TryReadPrev()).Success && !IsCommitAlike(result.LogRecord)) { // skip until commit } if (!result.Success) { // no more records in TF reachedEndOfStream = true; break; } nextCommitPostPos = result.RecordPrePosition; if (nextCommitPostPos > _indexCommitter.LastIndexedPosition) { continue; } switch (result.LogRecord.RecordType) { case LogRecordType.Prepare: { var prepare = (PrepareLogRecord)result.LogRecord; if (firstCommit) { firstCommit = false; prevPos = new TFPos(result.RecordPostPosition, result.RecordPostPosition); } if (prepare.Flags.HasAnyOf(PrepareFlags.Data | PrepareFlags.StreamDelete) && new TFPos(result.RecordPostPosition, result.RecordPostPosition) <= pos) { var eventRecord = new EventRecord(prepare.ExpectedVersion + 1 /* EventNumber */, prepare); consideredEventsCount++; if (eventFilter.IsEventAllowed(eventRecord)) { records.Add(new CommitEventRecord(eventRecord, prepare.LogPosition)); } // for backward pass we allow read the same commit, but force to skip last read prepare // so we put post-position of commit and pre-position of prepare nextPos = new TFPos(result.RecordPrePosition, result.RecordPrePosition); } break; } case LogRecordType.Commit: { var commit = (CommitLogRecord)result.LogRecord; if (firstCommit) { firstCommit = false; // for forward pass we allow read the same commit and as we have post-positions here // we can put just prepare post-position as prepare pre-position for forward read // so we put pre-position of commit and post-position of prepare prevPos = new TFPos(commit.LogPosition, pos.PreparePosition); } var commitPostPos = result.RecordPostPosition; // as we don't know exact position of the last record of transaction, // we have to sequentially scan backwards, so no need to reposition while (consideredEventsCount < maxCount) { result = reader.TryReadPrev(); if (!result.Success) // no more records in TF { break; } // prepare with TransactionBegin could be scavenged already // so we could reach beyond the start of transaction. In that case we have to stop. if (result.LogRecord.LogPosition < commit.TransactionPosition) { break; } if (result.LogRecord.RecordType != LogRecordType.Prepare) { continue; } var prepare = (PrepareLogRecord)result.LogRecord; if (prepare.TransactionPosition != commit.TransactionPosition) // wrong prepare { continue; } // prepare with useful data or delete tombstone if (prepare.Flags.HasAnyOf(PrepareFlags.Data | PrepareFlags.StreamDelete) && new TFPos(commitPostPos, result.RecordPostPosition) <= pos) { var eventRecord = new EventRecord(commit.FirstEventNumber + prepare.TransactionOffset, prepare); consideredEventsCount++; if (eventFilter.IsEventAllowed(eventRecord)) { records.Add(new CommitEventRecord(eventRecord, commit.LogPosition)); } // for backward pass we allow read the same commit, but force to skip last read prepare // so we put post-position of commit and pre-position of prepare nextPos = new TFPos(commitPostPos, prepare.LogPosition); } if (prepare.Flags.HasAnyOf(PrepareFlags.TransactionBegin)) { break; } } break; } default: throw new Exception(string.Format("Unexpected log record type: {0}.", result.LogRecord.RecordType)); } } return(new IndexReadAllResult(records, pos, nextPos, prevPos, reachedEndOfStream, consideredEventsCount)); } }