public List <long> GetAllIdsInSectionContaining(long id) { if (Contains(id) == false) { var posInPage = (int)(id % Constants.Storage.PageSize); var pageNumberInSection = (id - posInPage) / Constants.Storage.PageSize; var pageHeaderForId = PageHeaderFor(pageNumberInSection); // this is in another section, cannot free it directly, so we'll forward to the right section var sectionPageNumber = pageHeaderForId->PageNumber - pageHeaderForId->PageNumberInSection - 1; var actualSection = new RawDataSection(_tx, sectionPageNumber); if (actualSection._sectionHeader->SectionOwnerHash != _sectionHeader->SectionOwnerHash) { VoronUnrecoverableErrorException.Raise(_tx.Environment, $"Cannot get all ids in section containing {id} because the raw data section starting in {sectionPageNumber} belongs to a different owner"); } return(actualSection.GetAllIdsInSectionContaining(id)); } var ids = new List <long>(_sectionHeader->NumberOfEntries); for (int i = 0; i < _sectionHeader->NumberOfPages && ids.Count < _sectionHeader->NumberOfEntries; i++) { FillAllIdsInPage(_sectionHeader->PageNumber + i, ids); } return(ids); }
public IIterator MultiRead(Slice key) { TreeNodeHeader *node; var page = FindPageFor(key, out node); if (page == null || page.LastMatch != 0) { return(new EmptyIterator()); } Debug.Assert(node != null); Slice fetchedNodeKey; using (TreeNodeHeader.ToSlicePtr(_llt.Allocator, node, out fetchedNodeKey)) { if (SliceComparer.Equals(fetchedNodeKey, key) == false) { VoronUnrecoverableErrorException.Raise(_llt, "Was unable to retrieve the correct node. Data corruption possible"); } } if (node->Flags == TreeNodeFlags.MultiValuePageRef) { var tree = OpenMultiValueTree(key, node); return(tree.Iterate(true)); } var ptr = DirectAccessFromHeader(node); var nestedPage = new TreePage(ptr, (ushort)GetDataSize(node)); return(new TreePageIterator(_llt, key, this, nestedPage)); }
public static byte *DirectRead(LowLevelTransaction tx, long id, out int size) { var posInPage = (int)(id % Constants.Storage.PageSize); var pageNumberInSection = (id - posInPage) / Constants.Storage.PageSize; var pageHeader = PageHeaderFor(tx, pageNumberInSection); if (posInPage >= pageHeader->NextAllocation) { if (posInPage == 0) { VoronUnrecoverableErrorException.Raise(tx.Environment, $"Asked to load a large value from a raw data section page {pageHeader->PageNumber}, this is a bug"); } VoronUnrecoverableErrorException.Raise(tx.Environment, $"Asked to load a past the allocated values: {id} from page {pageHeader->PageNumber}"); } var sizes = (RawDataEntrySizes *)((byte *)pageHeader + posInPage); if (sizes->UsedSize < 0) { VoronUnrecoverableErrorException.Raise(tx.Environment, $"Asked to load a value that was already freed: {id} from page {pageHeader->PageNumber}"); } if (sizes->AllocatedSize < sizes->UsedSize) { VoronUnrecoverableErrorException.Raise(tx.Environment, $"Asked to load a value that where the allocated size is smaller than the used size: {id} from page {pageHeader->PageNumber}"); } size = sizes->UsedSize; return((byte *)pageHeader + posInPage + sizeof(RawDataEntrySizes)); }
private void SplitWrites(List <OutstandingWrite> writes) { for (var index = 0; index < writes.Count; index++) { var write = writes[index]; try { _cancellationToken.ThrowIfCancellationRequested(); using (var tx = _env.NewTransaction(TransactionFlags.ReadWrite)) { HandleOperations(tx, new List <OutstandingWrite> { write }, _cancellationToken); tx.Commit(); write.Completed(); } } catch (Exception e) { if (e is SEHException) { e = new VoronUnrecoverableErrorException("Error occurred during write", new Win32Exception(error: e.HResult)); } write.Errored(e); } } }
public static void TryThrowingBetterException(SystemException e, LuceneVoronDirectory directory) { if (e.Message.StartsWith("this writer hit an OutOfMemoryError")) { ThrowOutOfMemoryException(); } if (e is Win32Exception win32Exception && win32Exception.IsOutOfMemory()) { ThrowOutOfMemoryException(); } if (e.InnerException is VoronUnrecoverableErrorException) { VoronUnrecoverableErrorException.Raise("Index data is corrupted", e); } if (e.IsOutOfDiskSpaceException()) { // this commit stage is written to the temp scratch buffers var fullPath = directory.TempFullPath; var driveInfo = DiskSpaceChecker.GetDiskSpaceInfo(fullPath); var freeSpace = driveInfo != null?driveInfo.TotalFreeSpace.ToString() : "N/A"; throw new DiskFullException($"There isn't enough space to commit the index to {fullPath}. " + $"Currently available space: {freeSpace}", e); } void ThrowOutOfMemoryException() { throw new OutOfMemoryException("Index writer hit OOM during commit", e); } }
private void ThrowSchemaUpgradeRequired(int schemaVersionVal, string message) { VoronUnrecoverableErrorException.Raise(this, "The schema version of this database is expected to be " + Options.SchemaVersion + " but is actually " + schemaVersionVal + ". " + message); }
public void DeleteSection(long sectionPageNumber) { if (_tx.Flags == TransactionFlags.Read) { ThrowReadOnlyTransaction(sectionPageNumber); } if (sectionPageNumber != _sectionHeader->PageNumber) { // this is in another section, cannot delete it directly, so we'll forward to the right section var actualSection = new RawDataSection(_tx, sectionPageNumber); if (actualSection._sectionHeader->SectionOwnerHash != _sectionHeader->SectionOwnerHash) { VoronUnrecoverableErrorException.Raise(_tx.Environment, $"Cannot delete section because the raw data section starting in {sectionPageNumber} belongs to a different owner"); } actualSection.DeleteSection(sectionPageNumber); return; } for (int i = 0; i < _sectionHeader->NumberOfPages; i++) { _tx.FreePage(_sectionHeader->PageNumber + i + 1); } _tx.FreePage(_sectionHeader->PageNumber); }
public long MultiCount(Slice key) { TreeNodeHeader *node; var page = FindPageFor(key, out node); if (page == null || page.LastMatch != 0) { return(0); } Debug.Assert(node != null); Slice fetchedNodeKey; using (TreeNodeHeader.ToSlicePtr(_llt.Allocator, node, out fetchedNodeKey)) { if (SliceComparer.Equals(fetchedNodeKey, key) == false) { VoronUnrecoverableErrorException.Raise(_llt.Environment, "Was unable to retrieve the correct node. Data corruption possible"); } } if (node->Flags == TreeNodeFlags.MultiValuePageRef) { var tree = OpenMultiValueTree(key, node); return(tree.State.NumberOfEntries); } var nestedPage = new TreePage(DirectAccessFromHeader(node), (ushort)GetDataSize(node)); return(nestedPage.NumberOfEntries); }
private void EnsureNoDuplicateTransactionId(long id) { foreach (var journalFile in _journal.Files) { var lastSeenTxIdByJournal = journalFile.PageTranslationTable.GetLastSeenTransactionId(); if (id <= lastSeenTxIdByJournal) { VoronUnrecoverableErrorException.Raise(_env, $"PTT of journal {journalFile.Number} already contains records for a new write tx. " + $"Tx id = {id}, last seen by journal = {lastSeenTxIdByJournal}"); } if (journalFile.PageTranslationTable.IsEmpty) { continue; } var maxTxIdInJournal = journalFile.PageTranslationTable.MaxTransactionId(); if (id <= maxTxIdInJournal) { VoronUnrecoverableErrorException.Raise(_env, $"PTT of journal {journalFile.Number} already contains records for a new write tx. " + $"Tx id = {id}, max id in journal = {maxTxIdInJournal}"); } } }
private bool TryUseRecentTransactionPage(Slice key, out TreeCursorConstructor cursor, out TreePage page, out TreeNodeHeader *node) { var foundPage = _recentlyFoundPages?.Find(key); if (foundPage == null) { page = null; node = null; cursor = default(TreeCursorConstructor); return(false); } var lastFoundPageNumber = foundPage.Number; if (foundPage.Page != null) { // we can't share the same instance, Page instance may be modified by // concurrently run iterators page = new TreePage(foundPage.Page.Base, foundPage.Page.PageSize); } else { page = GetReadOnlyTreePage(lastFoundPageNumber); } if (page.IsLeaf == false) { VoronUnrecoverableErrorException.Raise(_llt.Environment, "Index points to a non leaf page"); } node = page.Search(_llt, key); // will set the LastSearchPosition cursor = new TreeCursorConstructor(_llt, this, page, foundPage.CursorPath, lastFoundPageNumber); return(true); }
protected void ThrowOnInvalidPageNumber(long pageNumber) { // this is a separate method because we don't want to have an exception throwing in the hot path VoronUnrecoverableErrorException.Raise(_options, "The page " + pageNumber + " was not allocated, allocated pages: " + NumberOfAllocatedPages + " in " + GetSourceName()); }
private void HandleActualWrites(OutstandingWrite mine, CancellationToken token) { List <OutstandingWrite> writes = null; try { writes = BuildBatchGroup(mine); var completedSuccessfully = false; using (var tx = _env.NewTransaction(TransactionFlags.ReadWrite)) { HandleOperations(tx, writes, _cancellationToken); try { tx.Commit(); if (ShouldRecordToDebugJournal) { _debugJournal.Flush(); } completedSuccessfully = true; } catch (Exception e) { if (e is SEHException) { e = new VoronUnrecoverableErrorException("Error occurred during write", new Win32Exception(error: e.HResult)); } // if we have an error during the commit, we can't recover, just fail them all. foreach (var write in writes) { write.Errored(e); } } } if (completedSuccessfully) { foreach (var write in writes) { write.Completed(); } } } catch (Exception e) { if (e is SEHException) { e = new VoronUnrecoverableErrorException("Error occurred during write", new Win32Exception(error: e.HResult)); } HandleWriteFailure(writes, mine, e); } }
public double Free(long id) { if (_tx.Flags == TransactionFlags.Read) { ThrowReadOnlyTransaction(id); } var posInPage = (int)(id % Constants.Storage.PageSize); var pageNumberInSection = (id - posInPage) / Constants.Storage.PageSize; var pageHeader = PageHeaderFor(pageNumberInSection); if (Contains(id) == false) { // this is in another section, cannot free it directly, so we'll forward to the right section var sectionPageNumber = pageHeader->PageNumber - pageHeader->PageNumberInSection - 1; var actualSection = new RawDataSection(_tx, sectionPageNumber); if (actualSection.Contains(id) == false) { VoronUnrecoverableErrorException.Raise(_tx.Environment, $"Cannot delete {id} because the raw data section starting in {sectionPageNumber} with size {actualSection.AllocatedSize} doesn't own it. Possible data corruption?"); } if (actualSection._sectionHeader->SectionOwnerHash != _sectionHeader->SectionOwnerHash) { VoronUnrecoverableErrorException.Raise(_tx.Environment, $"Cannot delete {id} because the raw data section starting in {sectionPageNumber} belongs to a different owner"); } return(actualSection.Free(id)); } pageHeader = ModifyPage(pageHeader); if (posInPage >= pageHeader->NextAllocation) { VoronUnrecoverableErrorException.Raise(_tx.Environment, $"Asked to load a past the allocated values: {id} from page {pageHeader->PageNumber}"); } var sizes = (RawDataEntrySizes *)((byte *)pageHeader + posInPage); if (sizes->UsedSize < 0) { VoronUnrecoverableErrorException.Raise(_tx.Environment, $"Asked to free a value that was already freed: {id} from page {pageHeader->PageNumber}"); } sizes->UsedSize = -1; Memory.Set((byte *)pageHeader + posInPage + sizeof(RawDataEntrySizes), 0, sizes->AllocatedSize); pageHeader->NumberOfEntries--; EnsureHeaderModified(); _sectionHeader->NumberOfEntries--; var sizeFreed = sizes->AllocatedSize + (sizeof(short) * 2); _sectionHeader->AllocatedSize -= sizeFreed; AvailableSpace[pageHeader->PageNumberInSection] += (ushort)sizeFreed; return(Density); }
private void ValidateWritablePages() { foreach (var writableKey in writablePages) { long pageNumber = writableKey.Key; if (!dirtyPagesValidate.Contains(pageNumber)) { VoronUnrecoverableErrorException.Raise(_env, "Writable key is not dirty (which means you are asking for a page modification for no reason)."); } } }
private static MultiValuesReport CreateMultiValuesReport(Tree tree) { var multiValues = new MultiValuesReport(); using (var multiTreeIterator = tree.Iterate(false)) { if (multiTreeIterator.Seek(Slices.BeforeAllKeys)) { do { var currentNode = multiTreeIterator.Current; switch (currentNode->Flags) { case TreeNodeFlags.MultiValuePageRef: { var multiValueTreeHeader = (TreeRootHeader *)((byte *)currentNode + currentNode->KeySize + Constants.Tree.NodeHeaderSize); Debug.Assert(multiValueTreeHeader->Flags == TreeFlags.MultiValue); multiValues.NumberOfEntries += multiValueTreeHeader->NumberOfEntries; multiValues.BranchPages += multiValueTreeHeader->BranchPages; multiValues.LeafPages += multiValueTreeHeader->LeafPages; multiValues.PageCount += multiValueTreeHeader->PageCount; break; } case TreeNodeFlags.Data: { var nestedPage = GetNestedMultiValuePage(tree, tree.DirectAccessFromHeader(currentNode), currentNode); multiValues.NumberOfEntries += nestedPage.NumberOfEntries; break; } case TreeNodeFlags.PageRef: { var overFlowPage = tree.GetReadOnlyTreePage(currentNode->PageNumber); var nestedPage = GetNestedMultiValuePage(tree, overFlowPage.Base + Constants.Tree.PageHeaderSize, currentNode); multiValues.NumberOfEntries += nestedPage.NumberOfEntries; break; } default: VoronUnrecoverableErrorException.Raise(tree.Llt, "currentNode->FixedTreeFlags has value of " + currentNode->Flags); break; } } while (multiTreeIterator.MoveNext()); } } return(multiValues); }
public long GetParentPageOf(TreePage page) { Debug.Assert(page.IsCompressed == false); TreePage p; Slice key; using (page.IsLeaf ? page.GetNodeKey(_llt, 0, out key) : page.GetNodeKey(_llt, 1, out key)) { TreeCursorConstructor cursorConstructor; TreeNodeHeader * node; p = FindPageFor(key, node: out node, cursor: out cursorConstructor, allowCompressed: true); if (page.IsLeaf && p.LastMatch != 0) { if (p.IsCompressed == false) { // if a found page is compressed then we could not find the exact match because // the key we were looking for might belong to an compressed entry // if the page isn't compressed then it's a corruption VoronUnrecoverableErrorException.Raise(_tx.LowLevelTransaction.Environment, $"Could not find a page containing {key} when looking for a parent of {page}. Page {p} was found, last match: {p.LastMatch}."); } #if DEBUG using (var decompressed = DecompressPage(p, skipCache: true)) { decompressed.Search(_llt, key); Debug.Assert(decompressed.LastMatch == 0); } #endif } using (var cursor = cursorConstructor.Build(key)) { while (cursor.PageCount > 0) { if (cursor.CurrentPage.PageNumber == page.PageNumber) { if (cursor.PageCount == 1) { return(-1); // root page } return(cursor.ParentPage.PageNumber); } cursor.Pop(); } } } return(-1); }
public List <long> GetAllIdsInSectionContaining(long id) { if (Contains(id) == false) { var posInPage = (int)(id % Constants.Storage.PageSize); var pageNumberInSection = (id - posInPage) / Constants.Storage.PageSize; var pageHeaderForId = PageHeaderFor(pageNumberInSection); // this is in another section, cannot free it directly, so we'll forward to the right section var sectionPageNumber = pageHeaderForId->PageNumber - pageHeaderForId->PageNumberInSection - 1; var actualSection = new RawDataSection(_tx, sectionPageNumber); if (actualSection._sectionHeader->SectionOwnerHash != _sectionHeader->SectionOwnerHash) { VoronUnrecoverableErrorException.Raise(_tx.Environment, $"Cannot get all ids in section containing {id} because the raw data section starting in {sectionPageNumber} belongs to a different owner"); } return(actualSection.GetAllIdsInSectionContaining(id)); } var ids = new List <long>(_sectionHeader->NumberOfEntries); for (int i = 0; i < _sectionHeader->NumberOfPages && ids.Count < _sectionHeader->NumberOfEntries; i++) { var pageHeader = PageHeaderFor(_sectionHeader->PageNumber + i + 1); var offset = sizeof(RawDataSmallPageHeader); while (offset < Constants.Storage.PageSize) { var sizes = (RawDataEntrySizes *)((byte *)pageHeader + offset); if (sizes->UsedSize != -1) { var currentId = (pageHeader->PageNumber * Constants.Storage.PageSize) + offset; var posInPage = (int)(currentId % Constants.Storage.PageSize); if (posInPage >= pageHeader->NextAllocation) { break; } ids.Add(currentId); if (ids.Count == _sectionHeader->NumberOfEntries) { break; } } offset += sizeof(short) * 2 + sizes->AllocatedSize; } } return(ids); }
public bool TryWriteDirect(long id, int size, out byte *writePos) { if (_tx.Flags == TransactionFlags.Read) { ThrowReadOnlyTransaction(id); } var posInPage = (int)(id % Constants.Storage.PageSize); var pageNumberInSection = (id - posInPage) / Constants.Storage.PageSize; var pageHeader = PageHeaderFor(pageNumberInSection); if (posInPage >= pageHeader->NextAllocation) { VoronUnrecoverableErrorException.Raise(_tx.Environment, $"Asked to load a past the allocated values: {id} from page {pageHeader->PageNumber}"); } var sizes = (RawDataEntrySizes *)((byte *)pageHeader + posInPage); if (sizes->UsedSize < 0) { VoronUnrecoverableErrorException.Raise(_tx.Environment, $"Asked to load a value that was already freed: {id} from page {pageHeader->PageNumber}"); } if (sizes->AllocatedSize < sizes->UsedSize) { VoronUnrecoverableErrorException.Raise(_tx.Environment, "Asked to load a value that where the allocated size is smaller than the used size: " + id + " from page " + pageHeader->PageNumber); } if (sizes->AllocatedSize < size) { writePos = (byte *)0; return(false); // can't write here } pageHeader = ModifyPage(pageHeader); writePos = ((byte *)pageHeader + posInPage + sizeof(short) /*allocated*/ + sizeof(short) /*used*/); // note that we have to do this calc again, pageHeader might have changed ((RawDataEntrySizes *)((byte *)pageHeader + posInPage))->UsedSize = (short)size; return(true); }
internal static string ReadDbId(UpdateStep step) { var metadataTree = step.WriteTx.ReadTree(Constants.MetadataTreeNameSlice); if (metadataTree == null) { VoronUnrecoverableErrorException.Raise(step.WriteTx.LowLevelTransaction, "Could not find metadata tree in database, possible mismatch / corruption?"); } Debug.Assert(metadataTree != null); // ReSharper disable once PossibleNullReferenceException var dbId = metadataTree.Read("db-id"); if (dbId == null) { VoronUnrecoverableErrorException.Raise(step.WriteTx.LowLevelTransaction, "Could not find db id in metadata tree, possible mismatch / corruption?"); } var buffer = new byte[16]; Debug.Assert(dbId != null); // ReSharper disable once PossibleNullReferenceException var dbIdBytes = dbId.Reader.Read(buffer, 0, 16); if (dbIdBytes != 16) { VoronUnrecoverableErrorException.Raise(step.WriteTx.LowLevelTransaction, "The db id value in metadata tree wasn't 16 bytes in size, possible mismatch / corruption?"); } var databaseGuidId = new Guid(buffer); var dbIdStr = new string(' ', 22); fixed(char *pChars = dbIdStr) { var result = Base64.ConvertToBase64ArrayUnpadded(pChars, (byte *)&databaseGuidId, 0, 16); Debug.Assert(result == 22); } return(dbIdStr); }
private void ValidateReadOnlyPages() { foreach (var readOnlyKey in readOnlyPages) { long pageNumber = readOnlyKey.Key; if (_dirtyPages.Contains(pageNumber)) { VoronUnrecoverableErrorException.Raise(_env, "Read only page is dirty (which means you are modifying a page directly in the data -- non transactionally -- )."); } var page = this.GetPage(pageNumber); ulong pageHash = Hashing.XXHash64.Calculate(page.Pointer, (ulong)Environment.Options.PageSize); if (pageHash != readOnlyKey.Value) { VoronUnrecoverableErrorException.Raise(_env, "Read only page content is different (which means you are modifying a page directly in the data -- non transactionally -- )."); } } }
private void ValidateReadOnlyPages() { foreach (var readOnlyKey in readOnlyPages) { long pageNumber = readOnlyKey.Key; if (dirtyPagesValidate.Contains(pageNumber)) { VoronUnrecoverableErrorException.Raise(_env, "Read only page is dirty (which means you are modifying a page directly in the data -- non transactionally -- )."); } var page = GetPage(pageNumber); ulong pageHash = StorageEnvironment.CalculatePageChecksum(page.Pointer, page.PageNumber, page.Flags, page.OverflowSize); if (pageHash != readOnlyKey.Value) { VoronUnrecoverableErrorException.Raise(_env, "Read only page content is different (which means you are modifying a page directly in the data -- non transactionally -- )."); } } }
private bool TryUseRecentTransactionPage(Slice key, out TreePage page, out TreeNodeHeader *node) { node = null; page = null; var recentPages = _recentlyFoundPages; if (recentPages == null) { return(false); } var foundPage = recentPages.Find(key); if (foundPage == null) { return(false); } if (foundPage.Page != null) { // we can't share the same instance, Page instance may be modified by // concurrently run iterators page = new TreePage(foundPage.Page.Base, foundPage.Page.PageSize); } else { page = GetReadOnlyTreePage(foundPage.Number); } if (page.IsLeaf == false) { VoronUnrecoverableErrorException.Raise(_llt.Environment, "Index points to a non leaf page"); } node = page.Search(_llt, key); // will set the LastSearchPosition return(true); }
public bool Update(UpdateStep step) { try { var tx = step.ReadTx; using (var it = tx.LowLevelTransaction.RootObjects.Iterate(false)) { if (it.Seek(Slices.BeforeAllKeys) == false) { return(true); } do { var rootObjectType = tx.GetRootObjectType(it.CurrentKey); if (rootObjectType == RootObjectType.FixedSizeTree) { tx.FixedTreeFor(it.CurrentKey).ValidateTree_Forced(); } else if (rootObjectType == RootObjectType.Table) { var tableTree = tx.ReadTree(it.CurrentKey, RootObjectType.Table); var writtenSchemaData = tableTree.DirectRead(TableSchema.SchemasSlice); var writtenSchemaDataSize = tableTree.GetDataSize(TableSchema.SchemasSlice); var schema = TableSchema.ReadFrom(tx.Allocator, writtenSchemaData, writtenSchemaDataSize); new Table(schema, it.CurrentKey, tx, tableTree, schema.TableType).AssertValidFixedSizeTrees(); } } while (it.MoveNext()); } return(true); } catch (Exception e) { VoronUnrecoverableErrorException.Raise("Failed to update documents from version 13 to 14, due to an internal error. " + "Use the Voron.Recovery tool to export this database and import it into a new database.", e); } return(false); }
private void TrackReadOnlyPage(Page page) { if (writablePages.ContainsKey(page.PageNumber)) { return; } ulong pageHash = StorageEnvironment.CalculatePageChecksum(page.Pointer, page.PageNumber, page.Flags, page.OverflowSize); ulong storedHash; if (readOnlyPages.TryGetValue(page.PageNumber, out storedHash)) { if (pageHash != storedHash) { VoronUnrecoverableErrorException.Raise(_env, "Read Only Page has change between tracking requests. Page #" + page.PageNumber); } } else { readOnlyPages[page.PageNumber] = pageHash; } }
internal void BackgroundFlushWritesToDataFile() { try { _journal.Applicator.ApplyLogsToDataFile(_cancellationTokenSource.Token, // we intentionally don't wait, if the flush lock is held, something else is flushing, so we don't need // to hold the thread TimeSpan.Zero); } catch (TimeoutException) { // we can ignore this, we'll try next time } catch (SEHException sehException) { VoronUnrecoverableErrorException.Raise(this, "Error occurred during flushing journals to the data file", new Win32Exception(sehException.HResult)); } catch (Exception e) { VoronUnrecoverableErrorException.Raise(this, "Error occurred during flushing journals to the data file", e); } }
private void UpgradeSchemaIfRequired() { int schemaVersionVal; var readPersistentContext = new TransactionPersistentContext(true); using (var readTxInner = NewLowLevelTransaction(readPersistentContext, TransactionFlags.Read)) using (var readTx = new Transaction(readTxInner)) { var metadataTree = readTx.ReadTree(Constants.MetadataTreeNameSlice); var schemaVersion = metadataTree.Read("schema-version"); if (schemaVersion == null) { VoronUnrecoverableErrorException.Raise(this, "Could not find schema version in metadata tree, possible mismatch / corruption?"); } schemaVersionVal = schemaVersion.Reader.ReadLittleEndianInt32(); } if (Options.SchemaVersion != 0 && schemaVersionVal != Options.SchemaVersion) { if (schemaVersionVal > Options.SchemaVersion) { ThrowSchemaUpgradeRequired(schemaVersionVal, "You have a schema version is newer than the current supported version."); } Func <Transaction, Transaction, int, bool> upgrader = Options.SchemaUpgrader; if (upgrader == null) { ThrowSchemaUpgradeRequired(schemaVersionVal, "You need to upgrade the schema but there is no schema uprader provided."); } UpgradeSchema(schemaVersionVal, upgrader); } }
private RawDataSmallPageHeader *DefragPage(RawDataSmallPageHeader *pageHeader) { pageHeader = ModifyPage(pageHeader); if (pageHeader->NumberOfEntries == 0) { pageHeader->NextAllocation = (ushort)sizeof(RawDataSmallPageHeader); Memory.Set((byte *)pageHeader + pageHeader->NextAllocation, 0, Constants.Storage.PageSize - pageHeader->NextAllocation); return(pageHeader); } TemporaryPage tmp; using (_tx.Environment.GetTemporaryPage(_tx, out tmp)) { var maxUsedPos = pageHeader->NextAllocation; Memory.Copy(tmp.TempPagePointer, (byte *)pageHeader, Constants.Storage.PageSize); pageHeader->NextAllocation = (ushort)sizeof(RawDataSmallPageHeader); Memory.Set((byte *)pageHeader + pageHeader->NextAllocation, 0, Constants.Storage.PageSize - pageHeader->NextAllocation); pageHeader->NumberOfEntries = 0; var pos = pageHeader->NextAllocation; while (pos < maxUsedPos) { var oldSize = (RawDataEntrySizes *)(tmp.TempPagePointer + pos); if (oldSize->AllocatedSize <= 0) { VoronUnrecoverableErrorException.Raise(_tx, $"Allocated size cannot be zero or negative, but was {oldSize->AllocatedSize} in page {pageHeader->PageNumber}"); } if (oldSize->UsedSize < 0) { pos += (ushort)(oldSize->AllocatedSize + sizeof(RawDataEntrySizes)); continue; // this was freed } var prevId = (pageHeader->PageNumber) * Constants.Storage.PageSize + pos; var newId = (pageHeader->PageNumber) * Constants.Storage.PageSize + pageHeader->NextAllocation; if (prevId != newId) { OnDataMoved(prevId, newId, tmp.TempPagePointer + pos + sizeof(RawDataEntrySizes), oldSize->UsedSize); } var newSize = (RawDataEntrySizes *)(((byte *)pageHeader) + pageHeader->NextAllocation); newSize->AllocatedSize = oldSize->AllocatedSize; newSize->UsedSize = oldSize->UsedSize; pageHeader->NextAllocation += (ushort)sizeof(RawDataEntrySizes); pageHeader->NumberOfEntries++; Memory.Copy(((byte *)pageHeader) + pageHeader->NextAllocation, tmp.TempPagePointer + pos + sizeof(RawDataEntrySizes), oldSize->UsedSize); pageHeader->NextAllocation += (ushort)oldSize->AllocatedSize; pos += (ushort)(oldSize->AllocatedSize + sizeof(RawDataEntrySizes)); } } return(pageHeader); }
private void ThrowStreamSizeMismatch(Slice name, long totalChunksSize, StreamInfo *info) { VoronUnrecoverableErrorException.Raise(_tx.LowLevelTransaction.Environment, $"Stream size mismatch of '{name}' stream. Sum of chunks size is {totalChunksSize} while stream info has {info->TotalSize}"); }
private unsafe void LoadExistingDatabase() { var header = stackalloc TransactionHeader[1]; bool hadIntegrityIssues = _journal.RecoverDatabase(header); if (hadIntegrityIssues) { var message = _journal.Files.Count == 0 ? "Unrecoverable database" : "Database recovered partially. Some data was lost."; _options.InvokeRecoveryError(this, message, null); } var entry = _headerAccessor.CopyHeader(); var nextPageNumber = (header->TransactionId == 0 ? entry.LastPageNumber : header->LastPageNumber) + 1; State = new StorageEnvironmentState(null, nextPageNumber) { NextPageNumber = nextPageNumber, Options = Options }; _transactionsCounter = (header->TransactionId == 0 ? entry.TransactionId : header->TransactionId); var transactionPersistentContext = new TransactionPersistentContext(true); using (var tx = NewLowLevelTransaction(transactionPersistentContext, TransactionFlags.ReadWrite)) { using (var root = Tree.Open(tx, null, Constants.RootTreeNameSlice, header->TransactionId == 0 ? &entry.Root : &header->Root)) { tx.UpdateRootsIfNeeded(root); using (var treesTx = new Transaction(tx)) { var metadataTree = treesTx.ReadTree(Constants.MetadataTreeNameSlice); if (metadataTree == null) { VoronUnrecoverableErrorException.Raise(this, "Could not find metadata tree in database, possible mismatch / corruption?"); } var dbId = metadataTree.Read("db-id"); if (dbId == null) { VoronUnrecoverableErrorException.Raise(this, "Could not find db id in metadata tree, possible mismatch / corruption?"); } var buffer = new byte[16]; var dbIdBytes = dbId.Reader.Read(buffer, 0, 16); if (dbIdBytes != 16) { VoronUnrecoverableErrorException.Raise(this, "The db id value in metadata tree wasn't 16 bytes in size, possible mismatch / corruption?"); } var databseGuidId = _options.GenerateNewDatabaseId == false ? new Guid(buffer) : Guid.NewGuid(); DbId = databseGuidId; FillBase64Id(databseGuidId); if (_options.GenerateNewDatabaseId) { // save the new database id metadataTree.Add("db-id", DbId.ToByteArray()); } var schemaVersion = metadataTree.Read("schema-version"); if (schemaVersion == null) { VoronUnrecoverableErrorException.Raise(this, "Could not find schema version in metadata tree, possible mismatch / corruption?"); } var schemaVersionVal = schemaVersion.Reader.ReadLittleEndianInt32(); if (Options.SchemaVersion != 0 && schemaVersionVal != Options.SchemaVersion) { VoronUnrecoverableErrorException.Raise(this, "The schema version of this database is expected to be " + Options.SchemaVersion + " but is actually " + schemaVersionVal + ". You need to upgrade the schema."); } tx.Commit(); } } } }
private RawDataSmallPageHeader *DefragPage(RawDataSmallPageHeader *pageHeader) { pageHeader = ModifyPage(pageHeader); if (pageHeader->NumberOfEntries == 0) { pageHeader->NextAllocation = (ushort)sizeof(RawDataSmallPageHeader); Memory.Set((byte *)pageHeader + pageHeader->NextAllocation, 0, Constants.Storage.PageSize - pageHeader->NextAllocation); return(pageHeader); } using (_llt.Environment.GetTemporaryPage(_llt, out TemporaryPage tmp)) { var maxUsedPos = pageHeader->NextAllocation; Memory.Copy(tmp.TempPagePointer, (byte *)pageHeader, Constants.Storage.PageSize); pageHeader->NextAllocation = (ushort)sizeof(RawDataSmallPageHeader); Memory.Set((byte *)pageHeader + pageHeader->NextAllocation, 0, Constants.Storage.PageSize - pageHeader->NextAllocation); pageHeader->NumberOfEntries = 0; var pos = pageHeader->NextAllocation; while (pos < maxUsedPos) { var oldSize = (RawDataEntrySizes *)(tmp.TempPagePointer + pos); if (oldSize->AllocatedSize <= 0) { VoronUnrecoverableErrorException.Raise(_llt, $"Allocated size cannot be zero or negative, but was {oldSize->AllocatedSize} in page {pageHeader->PageNumber}"); } if (oldSize->IsFreed) { pos += (ushort)(oldSize->AllocatedSize + sizeof(RawDataEntrySizes)); continue; // this was freed } var prevId = (pageHeader->PageNumber) * Constants.Storage.PageSize + pos; var newId = (pageHeader->PageNumber) * Constants.Storage.PageSize + pageHeader->NextAllocation; byte *entryPos = tmp.TempPagePointer + pos + sizeof(RawDataEntrySizes); if (prevId != newId) { var size = oldSize->UsedSize; if (oldSize->IsCompressed) { using var __ = Table.DecompressValue(_transaction, entryPos, size, out var buffer); OnDataMoved(prevId, newId, buffer.Ptr, buffer.Length); } else { OnDataMoved(prevId, newId, entryPos, oldSize->UsedSize); } } var newSize = (RawDataEntrySizes *)(((byte *)pageHeader) + pageHeader->NextAllocation); newSize->AllocatedSize = oldSize->AllocatedSize; newSize->UsedSize_Buffer = oldSize->UsedSize_Buffer; pageHeader->NextAllocation += (ushort)sizeof(RawDataEntrySizes); pageHeader->NumberOfEntries++; Memory.Copy(((byte *)pageHeader) + pageHeader->NextAllocation, entryPos, oldSize->UsedSize); pageHeader->NextAllocation += (ushort)oldSize->AllocatedSize; pos += (ushort)(oldSize->AllocatedSize + sizeof(RawDataEntrySizes)); } } return(pageHeader); }