/// <summary> /// Get a field name passing mapper type and returns property type /// </summary> private string GetTypeField(Type type, string property, out Type propertyType) { // lets get mapping bettwen .NET class and BsonDocument var map = _mapper.GetPropertyMapper(type); PropertyMapper prop; if (map.TryGetValue(property, out prop)) { propertyType = prop.PropertyType; return(prop.FieldName); } else { throw LiteException.PropertyNotMapped(property); } }
private static PropertyInfo SelectProperty(IEnumerable <PropertyInfo> props, params Func <PropertyInfo, bool>[] predicates) { foreach (var predicate in predicates) { var prop = props.FirstOrDefault(predicate); if (prop != null) { if (!prop.CanRead || !prop.CanWrite) { throw LiteException.PropertyReadWrite(prop); } return(prop); } } return(null); }
/// <summary> /// Override read page decrypting data from disk /// </summary> public override byte[] ReadPage(uint pageID) { var buffer = base.ReadPage(pageID); // when read header, checks passoword if (pageID == 0) { // I know, header page will be double read (it's the price for isolated concerns) var header = (HeaderPage)BasePage.ReadPage(buffer); if (header.DbParams.Password.BinaryCompareTo(_password) != 0) { throw LiteException.DatabaseWrongPassword(); } return(buffer); } return(_crypto.Decrypt(buffer)); }
/// <summary> /// Create a new permanent index in all documents inside this collections if index not exists already. Returns true if index was created or false if already exits /// </summary> /// <param name="field">Document field name (case sensitive)</param> /// <param name="options">All index options</param> public bool EnsureIndex(string field, IndexOptions options) { if (string.IsNullOrEmpty(field)) { throw new ArgumentNullException("field"); } if (options == null) { throw new ArgumentNullException("options"); } if (field == "_id") { return(false); // always exists } if (!CollectionIndex.IndexPattern.IsMatch(field)) { throw LiteException.InvalidFormat("IndexField", field); } return(_engine.EnsureIndex(_name, field, options)); }
/// <summary> /// Add a new collection. Check if name the not exists /// </summary> public CollectionPage Add(string name) { if (string.IsNullOrEmpty(name)) { throw new ArgumentNullException("name"); } if (!CollectionPage.NamePattern.IsMatch(name)) { throw LiteException.InvalidFormat("CollectionName", name); } // get header marked as dirty because I will use header after (and NewPage can get another header instance) var header = _pager.GetPage <HeaderPage>(0, true); // check limit count (8 bytes per collection = 4 to string length, 4 for uint pageID) if (header.CollectionPages.Sum(x => x.Key.Length + 8) + name.Length + 8 >= CollectionPage.MAX_COLLECTIONS_SIZE) { throw LiteException.CollectionLimitExceeded(CollectionPage.MAX_COLLECTIONS_SIZE); } // get new collection page (marked as dirty) var col = _pager.NewPage <CollectionPage>(); // add this page to header page collection header.CollectionPages.Add(name, col.PageID); col.CollectionName = name; // create PK index var pk = _indexer.CreateIndex(col); pk.Field = "_id"; pk.Options = new IndexOptions { Unique = true }; return(col); }
/// <summary> /// Drop an index from a collection /// </summary> public bool DropIndex(string colName, string field) { if (field == "_id") { throw LiteException.IndexDropId(); } return(this.Transaction <bool>(colName, false, (col) => { // no collection, no index if (col == null) { return false; } // mark collection page as dirty before changes _pager.SetDirty(col); // search for index reference var index = col.GetIndex(field); // no index, no drop if (index == null) { return false; } _log.Write(Logger.COMMAND, "drop index on '{0}' :: '{1}'", colName, field); // delete all data pages + indexes pages _indexer.DropIndex(index); // clear index reference index.Clear(); return true; })); }
/// <summary> /// Insert a new node index inside an collection index. /// </summary> private IndexNode AddNode(CollectionIndex index, BsonValue key, byte level) { // calc key size var keyLength = key.GetBytesCount(false); if (keyLength > MAX_INDEX_LENGTH) { throw LiteException.IndexKeyTooLong(); } // creating a new index node var node = new IndexNode(level) { Key = key, KeyLength = (ushort)keyLength }; // get a free page to insert my index node var page = _pager.GetFreePage <IndexPage>(index.FreeIndexPageID, node.Length); node.Position = new PageAddress(page.PageID, page.Nodes.NextIndex()); node.Page = page; // add index node to page page.Nodes.Add(node.Position.Index, node); // update freebytes + items count page.UpdateItemCount(); // now, let's link my index node on right place var cur = this.GetNode(index.HeadNode); // using as cache last IndexNode cache = null; // scan from top left for (var i = IndexNode.MAX_LEVEL_LENGTH - 1; i >= 0; i--) { // get cache for last node cache = cache != null && cache.Position.Equals(cur.Next[i]) ? cache : this.GetNode(cur.Next[i]); // for(; <while_not_this>; <do_this>) { ... } for (; cur.Next[i].IsEmpty == false; cur = cache) { // get cache for last node cache = cache != null && cache.Position.Equals(cur.Next[i]) ? cache : this.GetNode(cur.Next[i]); // read next node to compare var diff = cache.Key.CompareTo(key); // if unique and diff = 0, throw index exception (must rollback transaction - others nodes can be dirty) if (diff == 0 && index.Options.Unique) { throw LiteException.IndexDuplicateKey(index.Field, key); } if (diff == 1) { break; } } if (i <= (level - 1)) // level == length { // cur = current (imediatte before - prev) // node = new inserted node // next = next node (where cur is poiting) _pager.SetDirty(cur.Page); node.Next[i] = cur.Next[i]; node.Prev[i] = cur.Position; cur.Next[i] = node.Position; var next = this.GetNode(node.Next[i]); if (next != null) { _pager.SetDirty(next.Page); next.Prev[i] = node.Position; } } } // add/remove indexPage on freelist if has space _pager.AddOrRemoveToFreeList(page.FreeBytes > IndexPage.INDEX_RESERVED_BYTES, page, index.Page, ref index.FreeIndexPageID); return(node); }
/// <summary> /// Implement update command to a document inside a collection /// </summary> public int Update(string colName, IEnumerable <BsonDocument> docs) { return(this.Transaction <int>(colName, false, (col) => { // no collection, no updates if (col == null) { return 0; } var count = 0; foreach (var doc in docs) { // normalize id before find var id = doc["_id"].Normalize(col.PK.Options); // validate id for null, min/max values if (id.IsNull || id.IsMinValue || id.IsMaxValue) { throw LiteException.InvalidDataType("_id", id); } _log.Write(Logger.COMMAND, "update document on '{0}' :: _id = ", colName, id); // find indexNode from pk index var indexNode = _indexer.Find(col.PK, id, false, Query.Ascending); // if not found document, no updates if (indexNode == null) { continue; } // serialize document in bytes var bytes = BsonSerializer.Serialize(doc); // update data storage var dataBlock = _data.Update(col, indexNode.DataBlock, bytes); // delete/insert indexes - do not touch on PK foreach (var index in col.GetIndexes(false)) { var key = doc.Get(index.Field); var node = _indexer.GetNode(dataBlock.IndexRef[index.Slot]); // check if my index node was changed if (node.Key.CompareTo(key) != 0) { // remove old index node _indexer.Delete(index, node.Position); // and add a new one var newNode = _indexer.AddNode(index, key); // point my index to data object newNode.DataBlock = dataBlock.Position; // set my block page as dirty before change _pager.SetDirty(dataBlock.Page); // point my dataBlock dataBlock.IndexRef[index.Slot] = newNode.Position; } } _cache.CheckPoint(); count++; } return count; })); }
/// <summary> /// Read all properties from a type - store in a static cache - exclude: Id and [BsonIgnore] /// </summary> public static Dictionary <string, PropertyMapper> GetProperties(Type type, Func <string, string> resolvePropertyName) { var dict = new Dictionary <string, PropertyMapper>(StringComparer.OrdinalIgnoreCase); var id = GetIdProperty(type); var ignore = typeof(BsonIgnoreAttribute); var idAttr = typeof(BsonIdAttribute); var fieldAttr = typeof(BsonFieldAttribute); var indexAttr = typeof(BsonIndexAttribute); var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic); foreach (var prop in props) { // ignore indexer property if (prop.GetIndexParameters().Length > 0) { continue; } // ignore not read/write if (!prop.CanRead || !prop.CanWrite) { continue; } // [BsonIgnore] if (prop.IsDefined(ignore, false)) { continue; } // check if property has [BsonField] var bsonField = prop.IsDefined(fieldAttr, false); // create getter/setter IL function var getter = CreateGetMethod(type, prop, bsonField); var setter = CreateSetMethod(type, prop, bsonField); // if not getter or setter - no mapping if (getter == null) { continue; } var name = id != null && id.Equals(prop) ? "_id" : resolvePropertyName(prop.Name); // check if property has [BsonField] with a custom field name if (bsonField) { var field = (BsonFieldAttribute)prop.GetCustomAttributes(fieldAttr, false).FirstOrDefault(); if (field != null && field.Name != null) { name = field.Name; } } // check if property has [BsonId] to get with was setted AutoId = true var autoId = (BsonIdAttribute)prop.GetCustomAttributes(idAttr, false).FirstOrDefault(); // checks if this proerty has [BsonIndex] var index = (BsonIndexAttribute)prop.GetCustomAttributes(indexAttr, false).FirstOrDefault(); // if is _id field, do not accept index definition if (name == "_id") { index = null; } // test if field name is OK (avoid to check in all instances) - do not test internal classes, like DbRef if (BsonDocument.IsValidFieldName(name) == false) { throw LiteException.InvalidFormat(prop.Name, name); } // create a property mapper var p = new PropertyMapper { AutoId = autoId == null ? true : autoId.AutoId, FieldName = name, PropertyName = prop.Name, PropertyType = prop.PropertyType, IndexOptions = index == null ? null : index.Options, Getter = getter, Setter = setter }; dict.Add(prop.Name, p); } return(dict); }
/// <summary> /// Create a new instance from a Type /// </summary> public static object CreateInstance(Type type) { lock (_cacheCtor) { try { CreateObject c = null; if (_cacheCtor.TryGetValue(type, out c)) { return(c()); } else { if (type.IsClass) { var dynMethod = new DynamicMethod("_", type, null); var il = dynMethod.GetILGenerator(); il.Emit(OpCodes.Newobj, type.GetConstructor(Type.EmptyTypes)); il.Emit(OpCodes.Ret); c = (CreateObject)dynMethod.CreateDelegate(typeof(CreateObject)); _cacheCtor.Add(type, c); } else if (type.IsInterface) // some know interfaces { if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IList <>)) { return(CreateInstance(GetGenericListOfType(UnderlyingTypeOf(type)))); } else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ICollection <>)) { return(CreateInstance(GetGenericListOfType(UnderlyingTypeOf(type)))); } else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable <>)) { return(CreateInstance(GetGenericListOfType(UnderlyingTypeOf(type)))); } else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IDictionary <,>)) { var k = type.GetGenericArguments()[0]; var v = type.GetGenericArguments()[1]; return(CreateInstance(GetGenericDictionaryOfType(k, v))); } else { throw LiteException.InvalidCtor(type); } } else // structs { var dynMethod = new DynamicMethod("_", typeof(object), null); var il = dynMethod.GetILGenerator(); var lv = il.DeclareLocal(type); il.Emit(OpCodes.Ldloca_S, lv); il.Emit(OpCodes.Initobj, type); il.Emit(OpCodes.Ldloc_0); il.Emit(OpCodes.Box, type); il.Emit(OpCodes.Ret); c = (CreateObject)dynMethod.CreateDelegate(typeof(CreateObject)); _cacheCtor.Add(type, c); } return(c()); } } catch (Exception) { throw LiteException.InvalidCtor(type); } } }
private BsonValue Serialize(Type type, object obj, int depth) { if (++depth > MAX_DEPTH) { throw LiteException.DocumentMaxDepth(MAX_DEPTH); } if (obj == null) { return(BsonValue.Null); } Func <object, BsonValue> custom; // if is already a bson value if (obj is BsonValue) { return(new BsonValue((BsonValue)obj)); } // test string - mapper has some special options else if (obj is String) { var str = this.TrimWhitespace ? (obj as String).Trim() : (String)obj; if (this.EmptyStringToNull && str.Length == 0) { return(BsonValue.Null); } else { return(new BsonValue(str)); } } // basic Bson data types (cast datatype for better performance optimization) else if (obj is Int32) { return(new BsonValue((Int32)obj)); } else if (obj is Int64) { return(new BsonValue((Int64)obj)); } else if (obj is Double) { return(new BsonValue((Double)obj)); } else if (obj is Byte[]) { return(new BsonValue((Byte[])obj)); } else if (obj is ObjectId) { return(new BsonValue((ObjectId)obj)); } else if (obj is Guid) { return(new BsonValue((Guid)obj)); } else if (obj is Boolean) { return(new BsonValue((Boolean)obj)); } else if (obj is DateTime) { return(new BsonValue((DateTime)obj)); } // basic .net type to convert to bson else if (obj is Int16 || obj is UInt16 || obj is Byte) { return(new BsonValue(Convert.ToInt32(obj))); } else if (obj is UInt32 || obj is UInt64) { return(new BsonValue(Convert.ToInt64(obj))); } else if (obj is Single || obj is Decimal) { return(new BsonValue(Convert.ToDouble(obj))); } else if (obj is Char || obj is Enum) { return(new BsonValue(obj.ToString())); } // check if is a custom type else if (_customSerializer.TryGetValue(type, out custom) || _customSerializer.TryGetValue(obj.GetType(), out custom)) { return(custom(obj)); } // for dictionary else if (obj is IDictionary) { var itemType = type.GetGenericArguments()[1]; return(this.SerializeDictionary(itemType, obj as IDictionary, depth)); } // check if is a list or array else if (obj is IEnumerable) { return(this.SerializeArray(Reflection.GetListItemType(obj), obj as IEnumerable, depth)); } // otherwise serialize as a plain object else { return(this.SerializeObject(type, obj, depth)); } }