Skip to content

Commit

Permalink
Bugfix mbdavid#351
Browse files Browse the repository at this point in the history
  • Loading branch information
mbdavid committed Nov 27, 2016
1 parent e35cfef commit 8f29140
Show file tree
Hide file tree
Showing 20 changed files with 1,432 additions and 4 deletions.
13 changes: 9 additions & 4 deletions Database/Collections/Index.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,15 @@ public bool DropIndex(string field)
/// </summary>
private void EnsureIndex(IndexNotFoundException ex)
{
// check if property has an index mapped
var entity = _mapper.GetEntityMapper(typeof(T));
var member = entity.Members.FirstOrDefault(x => x.FieldName == ex.Field);
var unique = member == null ? false : member.IsUnique;
var unique = false;

// try get if field are mapped as unique index (only if T isn't BsonDocument)
if (typeof(T) != typeof(BsonDocument))
{
var entity = _mapper.GetEntityMapper(typeof(T));
var member = entity.Members.FirstOrDefault(x => x.FieldName == ex.Field);
unique = member == null ? false : member.IsUnique;
}

_engine.Value.EnsureIndex(ex.Collection, ex.Field, unique);
}
Expand Down
144 changes: 144 additions & 0 deletions Update/V6/Pages/BasePage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
using System;

namespace LiteDB_V6
{
public enum PageType { Empty = 0, Header = 1, Collection = 2, Index = 3, Data = 4, Extend = 5 }

internal abstract class BasePage
{
#region Page Constants

/// <summary>
/// The size of each page in disk - 4096 is NTFS default
/// </summary>
public const int PAGE_SIZE = 4096;

/// <summary>
/// This size is used bytes in header pages 17 bytes (+8 reserved to future use) = 25 bytes
/// </summary>
public const int PAGE_HEADER_SIZE = 25;

/// <summary>
/// Bytes avaiable to store data removing page header size - 4071 bytes
/// </summary>
public const int PAGE_AVAILABLE_BYTES = PAGE_SIZE - PAGE_HEADER_SIZE;

#endregion Page Constants

/// <summary>
/// Represent page number - start in 0 with HeaderPage [4 bytes]
/// </summary>
public uint PageID { get; set; }

/// <summary>
/// Indicate the page type [1 byte] - Must be implemented for each page type
/// </summary>
public abstract PageType PageType { get; }

/// <summary>
/// Represent the previous page. Used for page-sequences - MaxValue represent that has NO previous page [4 bytes]
/// </summary>
public uint PrevPageID { get; set; }

/// <summary>
/// Represent the next page. Used for page-sequences - MaxValue represent that has NO next page [4 bytes]
/// </summary>
public uint NextPageID { get; set; }

/// <summary>
/// Used for all pages to count itens inside this page(bytes, nodes, blocks, ...) [2 bytes]
/// Its Int32 but writes in UInt16
/// </summary>
public int ItemCount { get; set; }

/// <summary>
/// Used to find a free page using only header search [used in FreeList] [2 bytes]
/// Its Int32 but writes in UInt16
/// Its updated when a page modify content length (add/remove items)
/// </summary>
public int FreeBytes { get; set; }

public BasePage(uint pageID)
{
this.PageID = pageID;
this.PrevPageID = uint.MaxValue;
this.NextPageID = uint.MaxValue;
this.ItemCount = 0;
this.FreeBytes = PAGE_AVAILABLE_BYTES;
}

/// <summary>
/// Create a new instance of page based on T type
/// </summary>
public static T CreateInstance<T>(uint pageID)
where T : BasePage
{
var type = typeof(T);

// casting using "as T" #90 / thanks @Skysper
if (type == typeof(HeaderPage)) return new HeaderPage() as T;
if (type == typeof(CollectionPage)) return new CollectionPage(pageID) as T;
if (type == typeof(IndexPage)) return new IndexPage(pageID) as T;
if (type == typeof(DataPage)) return new DataPage(pageID) as T;
if (type == typeof(ExtendPage)) return new ExtendPage(pageID) as T;

throw new Exception("Invalid base page type T");
}

/// <summary>
/// Create a new instance of page based on PageType
/// </summary>
public static BasePage CreateInstance(uint pageID, PageType pageType)
{
switch (pageType)
{
case PageType.Header: return new HeaderPage();
case PageType.Collection: return new CollectionPage(pageID);
case PageType.Index: return new IndexPage(pageID);
case PageType.Data: return new DataPage(pageID);
case PageType.Extend: return new ExtendPage(pageID);
default: throw new Exception("Invalid pageType");
}
}

/// <summary>
/// Read a page with correct instance page object. Checks for pageType
/// </summary>
public static BasePage ReadPage(byte[] buffer)
{
var reader = new ByteReader(buffer);

var pageID = reader.ReadUInt32();
var pageType = (PageType)reader.ReadByte();

if (pageID == 0 && (byte)pageType > 5)
{
throw LiteException.InvalidDatabase();
}

var page = CreateInstance(pageID, pageType);

page.ReadHeader(reader);
page.ReadContent(reader);

page.DiskData = buffer;

return page;
}

private void ReadHeader(ByteReader reader)
{
// first 5 bytes (pageID + pageType) was readed before class create
// this.PageID
// this.PageType

this.PrevPageID = reader.ReadUInt32();
this.NextPageID = reader.ReadUInt32();
this.ItemCount = reader.ReadUInt16();
this.FreeBytes = reader.ReadUInt16();
reader.Skip(8); // reserved 8 bytes
}

protected abstract void ReadContent(ByteReader reader);
}
}
99 changes: 99 additions & 0 deletions Update/V6/Pages/CollectionPage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace LiteDB_V6
{
/// <summary>
/// Represents the collection page AND a collection item, because CollectionPage represent a Collection (1 page = 1 collection). All collections pages are linked with Prev/Next links
/// </summary>
internal class CollectionPage : BasePage
{
/// <summary>
/// Represent maximun bytes that all collections names can be used in header
/// </summary>
public const ushort MAX_COLLECTIONS_SIZE = 3000;

public static Regex NamePattern = new Regex(@"^[\w-]{1,30}$");

/// <summary>
/// Page type = Collection
/// </summary>
public override PageType PageType { get { return PageType.Collection; } }

/// <summary>
/// Name of collection
/// </summary>
public string CollectionName { get; set; }

/// <summary>
/// Get a reference for the free list data page - its private list per collection - each DataPage contains only data for 1 collection (no mixing)
/// Must to be a Field to be used as parameter reference
/// </summary>
public uint FreeDataPageID;

/// <summary>
/// Get the number of documents inside this collection
/// </summary>
public long DocumentCount { get; set; }

/// <summary>
/// Get all indexes from this collection - includes non-used indexes
/// </summary>
public CollectionIndex[] Indexes { get; set; }

public CollectionPage(uint pageID)
: base(pageID)
{
this.FreeDataPageID = uint.MaxValue;
this.DocumentCount = 0;
this.ItemCount = 1; // fixed for CollectionPage
this.FreeBytes = 0; // no free bytes on collection-page - only one collection per page
this.Indexes = new CollectionIndex[CollectionIndex.INDEX_PER_COLLECTION];

for (var i = 0; i < Indexes.Length; i++)
{
this.Indexes[i] = new CollectionIndex() { Page = this, Slot = i };
}
}

protected override void ReadContent(ByteReader reader)
{
this.CollectionName = reader.ReadString();
this.FreeDataPageID = reader.ReadUInt32();
var uintCount = reader.ReadUInt32(); // read as uint (4 bytes)

foreach (var index in this.Indexes)
{
index.Field = reader.ReadString();
index.HeadNode = reader.ReadPageAddress();
index.TailNode = reader.ReadPageAddress();
index.FreeIndexPageID = reader.ReadUInt32();
index.Options.Unique = reader.ReadBoolean();
index.Options.IgnoreCase = reader.ReadBoolean();
index.Options.TrimWhitespace = reader.ReadBoolean();
index.Options.EmptyStringToNull = reader.ReadBoolean();
index.Options.RemoveAccents = reader.ReadBoolean();
}

// be compatible with v2_beta
var longCount = reader.ReadInt64();
this.DocumentCount = Math.Max(uintCount, longCount);

}

/// <summary>
/// Get primary key index (_id index)
/// </summary>
public CollectionIndex PK { get { return this.Indexes[0]; } }

/// <summary>
/// Returns all used indexes
/// </summary>
public IEnumerable<CollectionIndex> GetIndexes(bool includePK)
{
return this.Indexes.Where(x => x.IsEmpty == false && x.Slot >= (includePK ? 0 : 1));
}
}
}
51 changes: 51 additions & 0 deletions Update/V6/Pages/DataPage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.Collections.Generic;
using System.Linq;

namespace LiteDB_V6
{
/// <summary>
/// The DataPage thats stores object data.
/// </summary>
internal class DataPage : BasePage
{
/// <summary>
/// Page type = Extend
/// </summary>
public override PageType PageType { get { return PageType.Data; } }

/// <summary>
/// Returns all data blocks - Each block has one object
/// </summary>
public Dictionary<ushort, DataBlock> DataBlocks { get; set; }

public DataPage(uint pageID)
: base(pageID)
{
this.DataBlocks = new Dictionary<ushort, DataBlock>();
}

protected override void ReadContent(ByteReader reader)
{
this.DataBlocks = new Dictionary<ushort, DataBlock>(ItemCount);

for (var i = 0; i < ItemCount; i++)
{
var block = new DataBlock();

block.Page = this;
block.Position = new PageAddress(this.PageID, reader.ReadUInt16());
block.ExtendPageID = reader.ReadUInt32();

for (var j = 0; j < CollectionIndex.INDEX_PER_COLLECTION; j++)
{
block.IndexRef[j] = reader.ReadPageAddress();
}

var size = reader.ReadUInt16();
block.Data = reader.ReadBytes(size);

this.DataBlocks.Add(block.Position.Index, block);
}
}
}
}
32 changes: 32 additions & 0 deletions Update/V6/Pages/ExtendPage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;

namespace LiteDB_V6
{
/// <summary>
/// Represent a extra data page that contains the object when is not possible store in DataPage (bigger then PAGE_SIZE or on update has no more space on page)
/// Can be used in sequence of pages to store big objects
/// </summary>
internal class ExtendPage : BasePage
{
/// <summary>
/// Page type = Extend
/// </summary>
public override PageType PageType { get { return PageType.Extend; } }

/// <summary>
/// Represent the part or full of the object - if this page has NextPageID the object is bigger than this page
/// </summary>
public Byte[] Data { get; set; }

public ExtendPage(uint pageID)
: base(pageID)
{
this.Data = new byte[0];
}

protected override void ReadContent(ByteReader reader)
{
this.Data = reader.ReadBytes(this.ItemCount);
}
}
}
Loading

0 comments on commit 8f29140

Please sign in to comment.