diff --git a/LiteDB/Database/LiteDatabase.cs b/LiteDB/Database/LiteDatabase.cs index 8efaa6cc7..145c35eae 100644 --- a/LiteDB/Database/LiteDatabase.cs +++ b/LiteDB/Database/LiteDatabase.cs @@ -187,7 +187,7 @@ public bool RenameCollection(string oldName, string newName) /// public long Shrink() { - return this.Shrink(_connectionString?.Password); + return this.Shrink(_connectionString == null ? null : _connectionString.Password); } /// diff --git a/LiteDB/Engine/Pages/ExtendPage.cs b/LiteDB/Engine/Pages/ExtendPage.cs index 87885c3e8..825c23ab0 100644 --- a/LiteDB/Engine/Pages/ExtendPage.cs +++ b/LiteDB/Engine/Pages/ExtendPage.cs @@ -16,7 +16,7 @@ internal class ExtendPage : BasePage /// /// Represent the part or full of the object - if this page has NextPageID the object is bigger than this page /// - public Byte[] Data { get; set; } + public byte[] Data { get; set; } public ExtendPage(uint pageID) : base(pageID) diff --git a/LiteDB/Engine/Pages/HeaderPage.cs b/LiteDB/Engine/Pages/HeaderPage.cs index 9e9ed2779..b2a35ed2c 100644 --- a/LiteDB/Engine/Pages/HeaderPage.cs +++ b/LiteDB/Engine/Pages/HeaderPage.cs @@ -22,6 +22,7 @@ internal class HeaderPage : BasePage /// /// Get/Set the pageID that start sequenece with a complete empty pages (can be used as a new page) + /// Must be a field to be used as "ref" /// public uint FreeEmptyPageID; @@ -33,17 +34,17 @@ internal class HeaderPage : BasePage /// /// Database user version [2 bytes] /// - public ushort UserVersion = 0; + public ushort UserVersion { get; set; } /// /// Password hash in SHA1 [20 bytes] /// - public byte[] Password = new byte[20]; + public byte[] Password { get; set; } /// /// When using encryption, store salt for password /// - public byte[] Salt = new byte[16]; + public byte[] Salt { get; set; } /// /// Get a dictionary with all collection pages with pageID link diff --git a/LiteDB/Engine/Update/IDatafileUpdate.cs b/LiteDB/Engine/Update/IDatafileUpdate.cs new file mode 100644 index 000000000..5e15f1206 --- /dev/null +++ b/LiteDB/Engine/Update/IDatafileUpdate.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace LiteDB +{ + interface IDatafileUpdate + { + bool IsFileVersion(Stream stream); + void Initialize(Stream stream); + IEnumerable ReadCollections(); + IEnumerable> ReadIndexes(string colName); + IEnumerable ReadDocuments(string colName); + } +} diff --git a/LiteDB/Engine/Update/V6/DbReader.cs b/LiteDB/Engine/Update/V6/DbReader.cs new file mode 100644 index 000000000..039b08af3 --- /dev/null +++ b/LiteDB/Engine/Update/V6/DbReader.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace LiteDB.Update.V6 +{ + internal class DbReader + { + private const int PAGE_SIZE = 4096; + + private Stream _stream; + + + #region DiskService + + /// + /// Read page bytes from disk and convert to Page object + /// + private T ReadPageDisk(uint pageID) + where T : BasePage_v6 + { + // position cursor in stream + _stream.Seek(pageID * (uint)PAGE_SIZE, SeekOrigin.Begin); + + var buffer = new byte[PAGE_SIZE]; + + // read bytes from stream + _stream.Read(buffer, 0, BasePage.PAGE_SIZE); + + var reader = new ByteReader(buffer); + + // page header + reader.ReadUInt32(); // read PageID + var pageType = (PageType)reader.ReadByte(); + var prevPageID = reader.ReadUInt32(); + var nextPageID = reader.ReadUInt32(); + var itemCount = reader.ReadUInt16(); + reader.ReadUInt16(); // FreeBytes + reader.Skip(8); // reserved 8 bytes + + T page; + + switch (pageType) + { + case PageType.Header: page = this.ReadHeaderPage(reader) as T; break; + case PageType.Collection: page = this.ReadCollectionPage(reader) as T; break; + //case PageType.Index: return new IndexPage(pageID); + //case PageType.Data: return new DataPage(pageID); + //case PageType.Extend: return new ExtendPage(pageID); + //case PageType.Empty: return new EmptyPage(pageID); + default: throw new Exception("Invalid pageType"); + } + + // setting page header + page.PageID = pageID; + page.PageType = pageType; + page.PrevPageID = prevPageID; + page.NextPageID = nextPageID; + page.ItemCount = itemCount; + + return page; + } + + /// + /// Read Header page from ByteReader + /// + private HeaderPage_v6 ReadHeaderPage(ByteReader reader) + { + var page = new HeaderPage_v6(); + + reader.Skip(100); + + var cols = reader.ReadByte(); + for (var i = 0; i < cols; i++) + { + page.CollectionPages.Add(reader.ReadString(), reader.ReadUInt32()); + } + + return page; + } + + /// + /// Read Collection page from ByteReader + /// + private CollectionPage_v6 ReadCollectionPage(ByteReader reader) + { + var page = new CollectionPage_v6(); + + page.CollectionName = reader.ReadString(); + page.DocumentCount = reader.ReadInt64(); + reader.ReadUInt32(); // FreeDataPageID + + page.Indexes = new Dictionary(); + + + return page; + } + + #endregion + + #region PagerService + + private Dictionary _cache = new Dictionary(); + + /// + /// Read a page from cache or from disk. If cache exceed 5000 pages, clear cache + /// + private T GetPage(uint pageID) + where T : BasePage_v6 + { + BasePage_v6 page; + + if(_cache.Count > 5000) _cache.Clear(); + + if(_cache.TryGetValue(pageID, out page)) + { + return (T)page; + } + + page = _cache[pageID] = this.ReadPageDisk(pageID); + + return (T)page; + } + + /// + /// Get all pages in sequence using NextPageID + /// + private IEnumerable GetSeqPages(uint firstPageID) + where T : BasePage_v6 + { + var pageID = firstPageID; + + while (pageID != uint.MaxValue) + { + var page = this.GetPage(pageID); + + pageID = page.NextPageID; + + yield return page; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/LiteDB/Engine/Update/V6/Pages.cs b/LiteDB/Engine/Update/V6/Pages.cs new file mode 100644 index 000000000..b124020e9 --- /dev/null +++ b/LiteDB/Engine/Update/V6/Pages.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; + +namespace LiteDB.Update.V6 +{ + internal class BasePage_v6 + { + public uint PageID { get; set; } + public PageType PageType { get; set; } + public uint PrevPageID { get; set; } + public uint NextPageID { get; set; } + public int ItemCount { get; set; } + } + + internal class HeaderPage_v6 : BasePage_v6 + { + public byte[] Password { get; set; } + public Dictionary CollectionPages { get; set; } + } + + internal class CollectionPage_v6 : BasePage_v6 + { + public string CollectionName { get; set; } + public long DocumentCount { get; set; } + public PageAddress HeaderNode { get; set; } + public Dictionary Indexes { get; set; } + } + + internal class IndexPage_v6 : BasePage_v6 + { + public Dictionary Nodes { get; set; } + } + + internal class DataPage_v6 : BasePage_v6 + { + public Dictionary DataBlocks { get; set; } + } + + internal class ExtendPage_v6 : BasePage_v6 + { + public byte[] Data { get; set; } + } + + internal class DataBlock_v6 + { + public PageAddress Position { get; set; } + public PageAddress[] IndexRef { get; set; } + public uint ExtendPageID { get; set; } + public byte[] Data { get; set; } + } + + internal class IndexNode_v6 + { + public PageAddress Position { get; set; } + public PageAddress[] Prev { get; set; } + public PageAddress[] Next { get; set; } + public ushort KeyLength { get; set; } + public BsonValue Key { get; set; } + public PageAddress DataBlock { get; set; } + } +} \ No newline at end of file diff --git a/LiteDB/LiteDB.NetStandard.csproj b/LiteDB/LiteDB.NetStandard.csproj index 1453c2b5e..f1a255c4c 100644 --- a/LiteDB/LiteDB.NetStandard.csproj +++ b/LiteDB/LiteDB.NetStandard.csproj @@ -151,6 +151,7 @@ + diff --git a/LiteDB/LiteDB.csproj b/LiteDB/LiteDB.csproj index fb3df0785..17085fda5 100644 --- a/LiteDB/LiteDB.csproj +++ b/LiteDB/LiteDB.csproj @@ -26,6 +26,7 @@ 1591 false AnyCPU + 4 pdbonly @@ -57,6 +58,7 @@ + @@ -107,6 +109,7 @@ + diff --git a/LiteDB/Mapper/BsonMapper.cs b/LiteDB/Mapper/BsonMapper.cs index 224666959..cf7540719 100644 --- a/LiteDB/Mapper/BsonMapper.cs +++ b/LiteDB/Mapper/BsonMapper.cs @@ -331,10 +331,9 @@ protected virtual EntityMapper BuildEntityMapper(Type type) var index = (BsonIndexAttribute)memberInfo.GetCustomAttributes(indexAttr, false).FirstOrDefault(); // get data type - var dataType = - (memberInfo as PropertyInfo)?.PropertyType ?? - (memberInfo as FieldInfo)?.FieldType ?? - typeof(object); + var dataType = memberInfo is PropertyInfo ? + (memberInfo as PropertyInfo).PropertyType : + (memberInfo as FieldInfo).FieldType; // check if datatype is list/array var isList = Reflection.IsList(dataType); diff --git a/LiteDB/Mapper/Linq/QueryVisitor.cs b/LiteDB/Mapper/Linq/QueryVisitor.cs index 9a0fc09f3..a92e027f2 100644 --- a/LiteDB/Mapper/Linq/QueryVisitor.cs +++ b/LiteDB/Mapper/Linq/QueryVisitor.cs @@ -107,7 +107,9 @@ private Query VisitExpression(Expression expr, string prefix = null) #else var type = met.Method.DeclaringType; #endif - var paramType = (met.Arguments[0] as MemberExpression)?.Expression.NodeType; + var paramType = met.Arguments[0] is MemberExpression ? + (ExpressionType?)(met.Arguments[0] as MemberExpression).Expression.NodeType : + null; // StartsWith if (method == "StartsWith") diff --git a/LiteDB/Mapper/Reflection/Reflection.cs b/LiteDB/Mapper/Reflection/Reflection.cs index 8941e81af..7132181b6 100644 --- a/LiteDB/Mapper/Reflection/Reflection.cs +++ b/LiteDB/Mapper/Reflection/Reflection.cs @@ -72,10 +72,9 @@ public static GenericSetter CreateGenericSetter(Type type, MemberInfo memberInfo else return null; // no field on Structs } #endif - var dataType = - (memberInfo as PropertyInfo)?.PropertyType ?? - (memberInfo as FieldInfo)?.FieldType ?? - typeof(object); + var dataType = memberInfo is PropertyInfo ? + (memberInfo as PropertyInfo).PropertyType : + (memberInfo as FieldInfo).FieldType; var target = Expression.Parameter(typeof(object), "obj"); var value = Expression.Parameter(typeof(object), "val");