PageLocation ISequentialRecordAccessor.Append( ulong firstPageNumber, ITransaction transaction, ulong recordSize) { if (recordSize == 0) { return(null); } var pageSize = _pageStore.PageSize; if (recordSize > pageSize) { throw new FileLayerException("Invalid attempt to write " + recordSize + " bytes into a page store with " + pageSize + " byte pages"); } var updates = new List <PageUpdate>(); var sequence = 1U; PageLocation indexLocation = new PageLocation { PageStore = _pageStore, PageNumber = firstPageNumber, Offset = _indexPageHeadSize, Length = _indexEntrySize }; PageLocation recordLocation = new PageLocation { PageStore = _pageStore, Length = recordSize }; while (true) { _pageStore.Lock(transaction, indexLocation.PageNumber); using (var indexPage = _pageStore.Get(transaction, indexLocation.PageNumber, CacheHints.MetaData | CacheHints.WithLock)) { var nextIndexPageNumber = BitConverter.ToUInt64(indexPage.Data, 0); if (nextIndexPageNumber == 0UL) { var nextIndexOffset = _indexPageHeadSize; recordLocation.PageNumber = 0UL; while (true) { var nextRecordPageNumber = BitConverter.ToUInt64(indexPage.Data, (int)nextIndexOffset); var nextRecordOffset = BitConverter.ToUInt32(indexPage.Data, (int)(nextIndexOffset + _pageNumberSize)); if (nextRecordPageNumber == 0UL) { indexLocation.Offset = nextIndexOffset; break; } recordLocation.PageNumber = nextRecordPageNumber; recordLocation.Offset = nextRecordOffset; nextIndexOffset += _indexEntrySize; if (nextIndexOffset + _indexEntrySize > pageSize) { var newIndexPageNumber = _pageStore.Allocate(); updates.Add(new PageUpdate { SequenceNumber = sequence++, PageNumber = indexLocation.PageNumber, Offset = 0U, Data = BitConverter.GetBytes(newIndexPageNumber) }); indexLocation.PageNumber = newIndexPageNumber; indexLocation.Offset = _indexPageHeadSize; break; } } break; } indexLocation.PageNumber = nextIndexPageNumber; } } if (recordLocation.PageNumber == 0UL || recordLocation.Offset + recordSize > pageSize) { recordLocation.PageNumber = _pageStore.Allocate(); recordLocation.Offset = 0U; } var newIndexEntry = new byte[_indexEntrySize]; BitConverter.GetBytes(recordLocation.PageNumber).CopyTo(newIndexEntry, 0); BitConverter.GetBytes(recordLocation.Offset + (uint)recordSize).CopyTo(newIndexEntry, _pageNumberSize); updates.Add(new PageUpdate { SequenceNumber = sequence++, PageNumber = indexLocation.PageNumber, Offset = indexLocation.Offset, Data = newIndexEntry }); _pageStore.Update(transaction, updates); return(recordLocation); }
PageLocation ISequentialRecordAccessor.Append(ulong firstPageNumber, ITransaction transaction, ulong recordSize) { if (recordSize == 0) { return(null); } // Find the last index page var indexPageNumber = firstPageNumber; while (true) { _pageStore.Lock(transaction, indexPageNumber); using (var indexPage = _pageStore.Get(transaction, indexPageNumber, CacheHints.MetaData | CacheHints.WithLock)) { var nextIndexPageNumber = BitConverter.ToUInt64(indexPage.Data, 0); if (nextIndexPageNumber == 0UL) { break; } indexPageNumber = nextIndexPageNumber; } } var indexEntryOffset = 8U; var lastRecordSize = 0UL; var lastRecordEndingPageNumber = 0UL; var lastRecordBytesOnLastPage = 0U; using (var lastIndexPage = _pageStore.Get(transaction, indexPageNumber, CacheHints.MetaData | CacheHints.WithLock)) { while (lastIndexPage.Data[indexEntryOffset] != 0) { var indexEntryType = lastIndexPage.Data[indexEntryOffset + 1]; if (indexEntryType == 1 || indexEntryType == 2) { // This is the start of a record lastRecordSize = BitConverter.ToUInt64(lastIndexPage.Data, (int)indexEntryOffset + 2); ulong lastRecordStartingPageNumber = BitConverter.ToUInt64(lastIndexPage.Data, (int)indexEntryOffset + 10); uint lastRecordOffset = BitConverter.ToUInt32(lastIndexPage.Data, (int)indexEntryOffset + 18); lastRecordBytesOnLastPage = (uint)((lastRecordSize + lastRecordOffset) % _pageSize); ulong lastRecordPageCount = (lastRecordSize + lastRecordOffset + _pageSize - 1) / _pageSize; lastRecordEndingPageNumber = lastRecordStartingPageNumber; } else if (indexEntryType == 3 && lastRecordSize != 0) { // This is a record continuation var pageCount = lastIndexPage.Data[indexEntryOffset + 2]; lastRecordEndingPageNumber = BitConverter.ToUInt64(lastIndexPage.Data, (int)indexEntryOffset + 3 + (pageCount - 1) * 8); } // Find the last record and last index entry on this page uint priorIndexEntryOffset = indexEntryOffset; indexEntryOffset += lastIndexPage.Data[indexEntryOffset]; } } var thisRecordStartingPageNumber = (lastRecordEndingPageNumber > 0 && lastRecordBytesOnLastPage < _pageSize) ? lastRecordEndingPageNumber : _pageStore.Allocate(); var thisRecordOffset = thisRecordStartingPageNumber == lastRecordEndingPageNumber ? lastRecordBytesOnLastPage : 0U; var updates = new List <PageUpdate>(); var sequence = 1U; var additionalPageNumbers = AddRecordIndex( updates, ref sequence, indexPageNumber, indexEntryOffset, recordSize, thisRecordStartingPageNumber, thisRecordOffset); _pageStore.Update(transaction, updates); return(new PageLocation { PageStore = _pageStore, PageNumber = thisRecordStartingPageNumber, Offset = thisRecordOffset, Length = recordSize, ContunuationPages = additionalPageNumbers }); }
public void should_snapshot_at_start_of_transaction() { var firstPage = _pageStore.Allocate(); var secondPage = _pageStore.Allocate(); // Modify some pages within a transaction var transaction1 = _database.BeginTransaction(null); _pageStore.BeginTransaction(transaction1); _pageStore.Update( transaction1, new[] { new PageUpdate { SequenceNumber = 1, PageNumber = firstPage, Offset = 3, Data = new byte[] { 98, 99, 100 } }, new PageUpdate { SequenceNumber = 2, PageNumber = secondPage, Offset = 10, Data = new byte[] { 98, 99, 100 } }, }); _database.CommitTransaction(transaction1); _pageStore.CommitTransaction(transaction1); _pageStore.FinalizeTransaction(transaction1); // Start a transaction and read one of the modified pages var transaction2 = _database.BeginTransaction(null); _pageStore.BeginTransaction(transaction2); using (var page = _pageStore.Get(transaction2, firstPage, CacheHints.None)) { Assert.AreEqual(98, page.Data[3]); Assert.AreEqual(99, page.Data[4]); Assert.AreEqual(100, page.Data[5]); } // Make some more changes to these pages for (var i = 0; i < 5; i++) { var transaction = _database.BeginTransaction(null); _pageStore.BeginTransaction(transaction); _pageStore.Update( transaction, new[] { new PageUpdate { SequenceNumber = 1, PageNumber = firstPage, Offset = 3, Data = new byte[] { (byte)i, (byte)(i + 1), (byte)(i + 2) } }, new PageUpdate { SequenceNumber = 2, PageNumber = secondPage, Offset = 10, Data = new byte[] { (byte)i, (byte)(i + 1), (byte)(i + 2) } } }); _database.CommitTransaction(transaction); _pageStore.CommitTransaction(transaction); _pageStore.FinalizeTransaction(transaction); } // Verify that the open transaction has read consistency using (var page = _pageStore.Get(transaction2, firstPage, CacheHints.None)) { Assert.AreEqual(98, page.Data[3]); Assert.AreEqual(99, page.Data[4]); Assert.AreEqual(100, page.Data[5]); } // Verify that the open transaction can not see the updates using (var page = _pageStore.Get(transaction2, secondPage, CacheHints.None)) { Assert.AreEqual(98, page.Data[10]); Assert.AreEqual(99, page.Data[11]); Assert.AreEqual(100, page.Data[12]); } _pageStore.RollbackTransaction(transaction2); // Verify that a new transaction does see the updated values var transaction3 = _database.BeginTransaction(null); _pageStore.BeginTransaction(transaction3); using (var page = _pageStore.Get(transaction3, firstPage, CacheHints.None)) { Assert.AreEqual(4, page.Data[3]); Assert.AreEqual(5, page.Data[4]); Assert.AreEqual(6, page.Data[5]); } using (var page = _pageStore.Get(transaction3, secondPage, CacheHints.None)) { Assert.AreEqual(4, page.Data[10]); Assert.AreEqual(5, page.Data[11]); Assert.AreEqual(6, page.Data[12]); } }