diff --git a/Ejdb.BSON/BSONArray.cs b/Ejdb.BSON/BSONArray.cs index b45ab02..b195227 100644 --- a/Ejdb.BSON/BSONArray.cs +++ b/Ejdb.BSON/BSONArray.cs @@ -14,238 +14,101 @@ // Boston, MA 02111-1307 USA. // ============================================================================================ using System; -using System.IO; +using System.Collections; +using System.Globalization; namespace Ejdb.BSON { [Serializable] - public class BSONArray : BSONDocument { - - public override BSONType BSONType { - get { - return BSONType.ARRAY; - } - } - - public object this[int key] { - get { - return GetObjectValue(key.ToString()); - } - } - - public BSONArray() { - } - - public BSONArray(BSONUndefined[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetUndefined(i); - } - } - - public BSONArray(BSONull[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetNull(i); - } - } - - public BSONArray(ushort[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetNumber(i, (int) arr[i]); - } - } - - public BSONArray(uint[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetNumber(i, (long) arr[i]); - } - } - - public BSONArray(ulong[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetNumber(i, (long) arr[i]); - } - } - - public BSONArray(short[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetNumber(i, (int) arr[i]); - } - } - - public BSONArray(string[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetString(i, arr[i]); - } - } - - public BSONArray(int[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetNumber(i, arr[i]); - } - } - - public BSONArray(long[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetNumber(i, arr[i]); - } - } - - public BSONArray(float[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetNumber(i, arr[i]); - } - } - - public BSONArray(double[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetNumber(i, arr[i]); - } - } - - public BSONArray(bool[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetBool(i, arr[i]); - } - } - - public BSONArray(BSONOid[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetOID(i, arr[i]); - } - } - - public BSONArray(DateTime[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetDate(i, arr[i]); - } - } - - public BSONArray(BSONDocument[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetObject(i, arr[i]); - } - } - - public BSONArray(BSONArray[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetArray(i, arr[i]); - } - } - - public BSONArray(BSONRegexp[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetRegexp(i, arr[i]); - } - } - - public BSONArray(BSONTimestamp[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetTimestamp(i, arr[i]); - } - } - - public BSONArray(BSONCodeWScope[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetCodeWScope(i, arr[i]); - } - } - - public BSONArray(BSONBinData[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetBinData(i, arr[i]); - } - } - - public BSONDocument SetNull(int idx) { - return base.SetNull(idx.ToString()); - } - - public BSONDocument SetUndefined(int idx) { - return base.SetUndefined(idx.ToString()); - } - - public BSONDocument SetMaxKey(int idx) { - return base.SetMaxKey(idx.ToString()); - } - - public BSONDocument SetMinKey(int idx) { - return base.SetMinKey(idx.ToString()); - } - - public BSONDocument SetOID(int idx, string oid) { - return base.SetOID(idx.ToString(), oid); - } - - public BSONDocument SetOID(int idx, BSONOid oid) { - return base.SetOID(idx.ToString(), oid); - } - - public BSONDocument SetBool(int idx, bool val) { - return base.SetBool(idx.ToString(), val); - } - - public BSONDocument SetNumber(int idx, int val) { - return base.SetNumber(idx.ToString(), val); - } - - public BSONDocument SetNumber(int idx, long val) { - return base.SetNumber(idx.ToString(), val); - } - - public BSONDocument SetNumber(int idx, double val) { - return base.SetNumber(idx.ToString(), val); - } - - public BSONDocument SetNumber(int idx, float val) { - return base.SetNumber(idx.ToString(), val); - } - - public BSONDocument SetString(int idx, string val) { - return base.SetString(idx.ToString(), val); - } - - public BSONDocument SetCode(int idx, string val) { - return base.SetCode(idx.ToString(), val); - } - - public BSONDocument SetSymbol(int idx, string val) { - return base.SetSymbol(idx.ToString(), val); - } - - public BSONDocument SetDate(int idx, DateTime val) { - return base.SetDate(idx.ToString(), val); - } - - public BSONDocument SetRegexp(int idx, BSONRegexp val) { - return base.SetRegexp(idx.ToString(), val); - } - - public BSONDocument SetBinData(int idx, BSONBinData val) { - return base.SetBinData(idx.ToString(), val); - } - - public BSONDocument SetObject(int idx, BSONDocument val) { - return base.SetDocument(idx.ToString(), val); - } - - public BSONDocument SetArray(int idx, BSONArray val) { - return base.SetArray(idx.ToString(), val); - } - - public BSONDocument SetTimestamp(int idx, BSONTimestamp val) { - return base.SetTimestamp(idx.ToString(), val); - } - - public BSONDocument SetCodeWScope(int idx, BSONCodeWScope val) { - return base.SetCodeWScope(idx.ToString(), val); - } - - protected override void CheckKey(string key) { - int idx; - if (key == null || !int.TryParse(key, out idx) || idx < 0) { - throw new InvalidBSONDataException(string.Format("Invalid array key: {0}", key)); - } - } + public class BsonArray : BsonDocument { + + + public BsonArray() + { + } + + public BsonArray(BsonDocument document) + { + var iterator = new BsonIterator(document); + while (iterator.Next() != BsonType.EOO) + Add(iterator.CurrentKey, iterator.FetchCurrentValue()); + } + + public BsonArray(IEnumerable objects) + { + AddRange(objects); + } + + public override BsonType BSONType + { + get + { + return BsonType.ARRAY; + } + } + + public object this[int key] + { + get { return GetObjectValue(key.ToString()); } + } + + public void SetMaxKey(int idx) + { + Add(BsonValue.GetMaxKey()); + } + + public void SetMinKey(int idx) + { + Add(BsonValue.GetMinKey()); + } + + /// + /// Adds multiple elements to the array. + /// + /// A list of values to add to the array. + /// The array (so method calls can be chained). + public BsonArray AddRange(IEnumerable values) + { + if (values != null) + { + foreach (var value in values) + Add(BsonValue.ValueOf(value)); + } + + return this; + } + + /// + /// Adds an element to the array. + /// + /// The value to add to the array. + /// The array (so method calls can be chained). + public BsonArray Add(BsonValue value) + { + if (value != null) + { + var key = Keys.Count.ToString(CultureInfo.InvariantCulture); + Add(key, value); + } + + return this; + } + + public BsonArray Add(object value) + { + if (value != null) + { + var bsonValue = BsonValue.ValueOf(value); + return Add(bsonValue); + } + + return this; + } + + public int Count + { + get { return KeysCount; } + } } } diff --git a/Ejdb.BSON/BSONBinData.cs b/Ejdb.BSON/BSONBinData.cs index 7aa9e45..ae836fc 100644 --- a/Ejdb.BSON/BSONBinData.cs +++ b/Ejdb.BSON/BSONBinData.cs @@ -19,7 +19,7 @@ namespace Ejdb.BSON { [Serializable] - public sealed class BSONBinData { + public sealed class BsonBinData { readonly byte _subtype; readonly byte[] _data; @@ -35,13 +35,13 @@ public byte[] Data { } } - public BSONBinData(byte subtype, byte[] data) { + public BsonBinData(byte subtype, byte[] data) { _subtype = subtype; _data = new byte[data.Length]; Array.Copy(data, _data, data.Length); } - internal BSONBinData(byte subtype, int len, BinaryReader input) { + internal BsonBinData(byte subtype, int len, BinaryReader input) { _subtype = subtype; _data = input.ReadBytes(len); } diff --git a/Ejdb.BSON/BSONCodeWScope.cs b/Ejdb.BSON/BSONCodeWScope.cs index 8ece275..3f26129 100644 --- a/Ejdb.BSON/BSONCodeWScope.cs +++ b/Ejdb.BSON/BSONCodeWScope.cs @@ -18,13 +18,13 @@ namespace Ejdb.BSON { [Serializable] - public sealed class BSONCodeWScope : BSONDocument { + public sealed class BsonCodeWScope : BsonDocument { readonly string _code; - public override BSONType BSONType { + public override BsonType BSONType { get { - return BSONType.CODEWSCOPE; + return BsonType.CODEWSCOPE; } } @@ -34,13 +34,13 @@ public string Code { } } - public BSONDocument Scope { + public BsonDocument Scope { get { return this; } } - public BSONCodeWScope(string code) { + public BsonCodeWScope(string code) { this._code = code; } @@ -51,10 +51,10 @@ public override bool Equals(object obj) { if (ReferenceEquals(this, obj)) { return true; } - if (!(obj is BSONCodeWScope)) { + if (!(obj is BsonCodeWScope)) { return false; } - BSONCodeWScope cw = (BSONCodeWScope) obj; + BsonCodeWScope cw = (BsonCodeWScope) obj; if (_code != cw._code) { return false; } diff --git a/Ejdb.BSON/BSONConstants.cs b/Ejdb.BSON/BSONConstants.cs index a686af2..38e9ed6 100644 --- a/Ejdb.BSON/BSONConstants.cs +++ b/Ejdb.BSON/BSONConstants.cs @@ -20,9 +20,10 @@ namespace Ejdb.BSON { /// /// Various BSON processing constants and shared values. /// - public static class BSONConstants { + public static class BsonConstants { - static BSONConstants() { + static BsonConstants() + { Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); } @@ -30,6 +31,32 @@ static BSONConstants() { /// Gets or sets the epoch. /// public static DateTime Epoch { get; private set; } + + /// + /// The name of the id field + /// + public const string Id = "_id"; + + /// + /// Converts a DateTime to UTC (with special handling for MinValue and MaxValue). + /// + /// A DateTime. + /// The DateTime in UTC. + public static DateTime ToUniversalTime(DateTime dateTime) + { + if (dateTime == DateTime.MinValue) + { + return DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc); + } + else if (dateTime == DateTime.MaxValue) + { + return DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Utc); + } + else + { + return dateTime.ToUniversalTime(); + } + } } } diff --git a/Ejdb.BSON/BSONDocument.cs b/Ejdb.BSON/BSONDocument.cs index 14ed3ae..5e77a9d 100644 --- a/Ejdb.BSON/BSONDocument.cs +++ b/Ejdb.BSON/BSONDocument.cs @@ -16,12 +16,7 @@ using System; using System.Collections.Generic; using System.IO; -using System.Text; -using System.Diagnostics; using Ejdb.IO; -using Ejdb.BSON; -using Ejdb.Utils; -using System.Reflection; using System.Linq; namespace Ejdb.BSON { @@ -30,82 +25,34 @@ namespace Ejdb.BSON { /// BSON document deserialized data wrapper. /// [Serializable] - public class BSONDocument : IBSONValue, IEnumerable, ICloneable { - static Dictionary> TYPE_SETTERS = - new Dictionary> { - {typeof(bool), (d, k, v) => d.SetBool(k, (bool) v)}, - {typeof(bool[]), (d, k, v) => d.SetArray(k, new BSONArray((bool[]) v))}, - {typeof(byte), (d, k, v) => d.SetNumber(k, (int) v)}, - {typeof(sbyte), (d, k, v) => d.SetNumber(k, (int) v)}, - {typeof(ushort), (d, k, v) => d.SetNumber(k, (int) v)}, - {typeof(ushort[]), (d, k, v) => d.SetArray(k, new BSONArray((ushort[]) v))}, - {typeof(short), (d, k, v) => d.SetNumber(k, (int) v)}, - {typeof(short[]), (d, k, v) => d.SetArray(k, new BSONArray((short[]) v))}, - {typeof(uint), (d, k, v) => d.SetNumber(k, (int) v)}, - {typeof(uint[]), (d, k, v) => d.SetArray(k, new BSONArray((uint[]) v))}, - {typeof(int), (d, k, v) => d.SetNumber(k, (int) v)}, - {typeof(int[]), (d, k, v) => d.SetArray(k, new BSONArray((int[]) v))}, - {typeof(ulong), (d, k, v) => d.SetNumber(k, (long) v)}, - {typeof(ulong[]), (d, k, v) => d.SetArray(k, new BSONArray((ulong[]) v))}, - {typeof(long), (d, k, v) => d.SetNumber(k, (long) v)}, - {typeof(long[]), (d, k, v) => d.SetArray(k, new BSONArray((long[]) v))}, - {typeof(float), (d, k, v) => d.SetNumber(k, (float) v)}, - {typeof(float[]), (d, k, v) => d.SetArray(k, new BSONArray((float[]) v))}, - {typeof(double), (d, k, v) => d.SetNumber(k, (double) v)}, - {typeof(double[]), (d, k, v) => d.SetArray(k, new BSONArray((double[]) v))}, - {typeof(char), (d, k, v) => d.SetString(k, v.ToString())}, - {typeof(string), (d, k, v) => d.SetString(k, (string) v)}, - {typeof(string[]), (d, k, v) => d.SetArray(k, new BSONArray((string[]) v))}, - {typeof(BSONOid), (d, k, v) => d.SetOID(k, (BSONOid) v)}, - {typeof(BSONOid[]), (d, k, v) => d.SetArray(k, new BSONArray((BSONOid[]) v))}, - {typeof(BSONRegexp), (d, k, v) => d.SetRegexp(k, (BSONRegexp) v)}, - {typeof(BSONRegexp[]), (d, k, v) => d.SetArray(k, new BSONArray((BSONRegexp[]) v))}, - {typeof(BSONValue), (d, k, v) => d.SetBSONValue((BSONValue) v)}, - {typeof(BSONTimestamp), (d, k, v) => d.SetTimestamp(k, (BSONTimestamp) v)}, - {typeof(BSONTimestamp[]), (d, k, v) => d.SetArray(k, new BSONArray((BSONTimestamp[]) v))}, - {typeof(BSONCodeWScope), (d, k, v) => d.SetCodeWScope(k, (BSONCodeWScope) v)}, - {typeof(BSONCodeWScope[]), (d, k, v) => d.SetArray(k, new BSONArray((BSONCodeWScope[]) v))}, - {typeof(BSONBinData), (d, k, v) => d.SetBinData(k, (BSONBinData) v)}, - {typeof(BSONBinData[]), (d, k, v) => d.SetArray(k, new BSONArray((BSONBinData[]) v))}, - {typeof(BSONDocument), (d, k, v) => d.SetDocument(k, (BSONDocument) v)}, - {typeof(BSONDocument[]), (d, k, v) => d.SetArray(k, new BSONArray((BSONDocument[]) v))}, - {typeof(BSONArray), (d, k, v) => d.SetArray(k, (BSONArray) v)}, - {typeof(BSONArray[]), (d, k, v) => d.SetArray(k, new BSONArray((BSONArray[]) v))}, - {typeof(DateTime), (d, k, v) => d.SetDate(k, (DateTime) v)}, - {typeof(DateTime[]), (d, k, v) => d.SetArray(k, new BSONArray((DateTime[]) v))}, - {typeof(BSONUndefined), (d, k, v) => d.SetUndefined(k)}, - {typeof(BSONUndefined[]), (d, k, v) => d.SetArray(k, new BSONArray((BSONUndefined[]) v))}, - {typeof(BSONull), (d, k, v) => d.SetNull(k)}, - {typeof(BSONull[]), (d, k, v) => d.SetArray(k, new BSONArray((BSONull[]) v))} - }; - - readonly List _fieldslist; - + public class BsonDocument : IBsonValue, IEnumerable, ICloneable + { [NonSerializedAttribute] - Dictionary _fields; + Dictionary _fields; [NonSerializedAttribute] - int? _cachedhash; + private int? _cachedhash; /// /// BSON Type this document. /// /// - /// Type can be either or + /// Type can be either or /// /// The type of the BSON. - public virtual BSONType BSONType { + public virtual BsonType BSONType { get { - return BSONType.OBJECT; + return BsonType.OBJECT; } } /// /// Gets the document keys. /// - public ICollection Keys { - get { - CheckFields(); + public ICollection Keys + { + get + { return _fields.Keys; } } @@ -115,41 +62,54 @@ public ICollection Keys { /// public int KeysCount { get { - return _fieldslist.Count; + return _fields.Count; } } - public BSONDocument() { - this._fields = null; - this._fieldslist = new List(); + public BsonDocument() { + _fields = new Dictionary(); } - public BSONDocument(BSONIterator it) : this() { - while (it.Next() != BSONType.EOO) { - Add(it.FetchCurrentValue()); + public BsonDocument(BsonIterator it) + : this() + { + while (it.Next() != BsonType.EOO) + { + var value = it.FetchCurrentValue(); + Add(it.CurrentKey, value); } } - public BSONDocument(BSONIterator it, string[] fields) : this() { + /// + /// Initializes a new instance of the BsonDocument class and adds new elements from a dictionary of key/value pairs. + /// + /// A dictionary to initialize the document from. + public BsonDocument(Dictionary dictionary) + { + foreach (var entry in dictionary) + Add(entry.Key, BsonValue.ValueOf(entry.Value)); + } + + public BsonDocument(BsonIterator it, string[] fields) : this() { Array.Sort(fields); - BSONType bt; + BsonType bt; int ind = -1; int nfc = 0; - foreach (string f in fields) { - if (f != null) { + foreach (string f in fields) + { + if (f != null) nfc++; - } } - while ((bt = it.Next()) != BSONType.EOO) { + while ((bt = it.Next()) != BsonType.EOO) { if (nfc < 1) { continue; } string kk = it.CurrentKey; if ((ind = Array.IndexOf(fields, kk)) != -1) { - Add(it.FetchCurrentValue()); + Add(it.CurrentKey, it.FetchCurrentValue()); fields[ind] = null; nfc--; - } else if (bt == BSONType.OBJECT || bt == BSONType.ARRAY) { + } else if (bt == BsonType.OBJECT || bt == BsonType.ARRAY) { string[] narr = null; for (var i = 0; i < fields.Length; ++i) { var f = fields[i]; @@ -168,10 +128,10 @@ public BSONDocument(BSONIterator it, string[] fields) : this() { } } if (narr != null) { - BSONIterator nit = new BSONIterator(it); - BSONDocument ndoc = new BSONDocument(nit, narr); + BsonIterator nit = new BsonIterator(it); + BsonDocument ndoc = new BsonDocument(nit, narr); if (ndoc.KeysCount > 0) { - Add(new BSONValue(bt, kk, ndoc)); + Add(kk, new BsonValue(bt, ndoc)); } } } @@ -179,33 +139,40 @@ public BSONDocument(BSONIterator it, string[] fields) : this() { it.Dispose(); } - public BSONDocument(byte[] bsdata) : this() { - using (BSONIterator it = new BSONIterator(bsdata)) { - while (it.Next() != BSONType.EOO) { - Add(it.FetchCurrentValue()); - } - } + public BsonDocument(byte[] bsdata) + : this() + { + using (var iterator = new BsonIterator(bsdata)) + { + while (iterator.Next() != BsonType.EOO) + Add(iterator.CurrentKey, iterator.FetchCurrentValue()); } - - public BSONDocument(Stream bstream) : this() { - using (BSONIterator it = new BSONIterator(bstream)) { - while (it.Next() != BSONType.EOO) { - Add(it.FetchCurrentValue()); - } - } } - public BSONDocument(BSONDocument doc) : this() { - foreach (var bv in doc) { - Add((BSONValue) bv.Clone()); - } + public BsonDocument(Stream bstream) + : this() + { + using (var iterator = new BsonIterator(bstream)) + { + while (iterator.Next() != BsonType.EOO) + Add(iterator.CurrentKey, iterator.FetchCurrentValue()); } - - public IEnumerator GetEnumerator() { - return _fieldslist.GetEnumerator(); } - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { + public BsonDocument(BsonDocument doc) + : this() + { + foreach (var bv in doc._fields) + Add(bv.Key, (BsonValue) bv.Value.Clone()); + } + + public IEnumerator GetEnumerator() + { + foreach (var entry in _fields) + yield return new BsonValueWithKey(entry.Key, entry.Value, entry.Value.BSONType); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } @@ -213,16 +180,19 @@ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { /// Convert BSON document object into BSON binary data byte array. /// /// The byte array. - public byte[] ToByteArray() { + public byte[] ToByteArray() + { byte[] res; - using (var ms = new MemoryStream()) { + using (var ms = new MemoryStream()) + { Serialize(ms); res = ms.ToArray(); } return res; } - public string ToDebugDataString() { + public string ToDebugDataString() + { return BitConverter.ToString(ToByteArray()); } @@ -230,11 +200,10 @@ public string ToDebugDataString() { /// Gets the field value. /// /// The BSON value. - /// + /// /// Document field name - public BSONValue GetBSONValue(string key) { - CheckFields(); - BSONValue ov; + public BsonValue GetBsonValue(string key) { + BsonValue ov; if (_fields.TryGetValue(key, out ov)) { return ov; } else { @@ -249,10 +218,20 @@ public BSONValue GetBSONValue(string key) { /// Hierarchical field paths are NOT supported. Use [] operator instead. /// /// BSON document key - /// - public object GetObjectValue(string key) { - var bv = GetBSONValue(key); - return bv != null ? bv.Value : null; + /// + public object GetObjectValue(string key) + { + var bv = GetBsonValue(key); + return bv != null ? _UnwrapValue(bv.Value) : null; + } + + private object _UnwrapValue(object value) + { + var array = value as BsonArray; + if (array == null) + return value; + + return array.Select(x => _UnwrapValue(x.Value)).ToArray(); } /// @@ -261,158 +240,47 @@ public object GetObjectValue(string key) { /// true if this document has the specified key; otherwise, false. /// Key. public bool HasKey(string key) { - return (GetBSONValue(key) != null); + return (GetBsonValue(key) != null); } /// - /// Gets the with the specified key. + /// Gets the with the specified key. /// /// /// Getter for hierarchical field paths are supported. /// /// Key. /// Key object or null if the key is not exists or value type is either - /// or - public object this[string key] { - get { - int ind; - if ((ind = key.IndexOf(".", StringComparison.Ordinal)) == -1) { - return GetObjectValue(key); - } else { - string prefix = key.Substring(0, ind); - BSONDocument doc = GetObjectValue(prefix) as BSONDocument; - if (doc == null || key.Length < ind + 2) { - return null; - } - return doc[key.Substring(ind + 1)]; + /// or + public object this[string key] + { + get + { + int ind; + if ((ind = key.IndexOf(".", StringComparison.Ordinal)) == -1) + return GetObjectValue(key); + + string prefix = key.Substring(0, ind); + var doc = GetObjectValue(prefix) as BsonDocument; + if (doc == null || key.Length < ind + 2) { + return null; } + return doc[key.Substring(ind + 1)]; } - set { - object v = value; - if (v == null) { - SetNull(key); - return; - } - Action setter; - Type vtype = v.GetType(); - TYPE_SETTERS.TryGetValue(vtype, out setter); - if (setter == null) { - if (vtype.IsAnonymousType()) { - setter = SetAnonType; - } else { - throw new Exception(string.Format("Unsupported value type: {0} for doc[key] assign operation", v.GetType())); - } - } - setter(this, key, v); + set + { + var bsonValue = BsonValue.ValueOf(value); + Add(key, bsonValue); } } - public static void SetAnonType(BSONDocument doc, string key, object val) { - BSONDocument ndoc = (key == null) ? doc : new BSONDocument(); - Type vtype = val.GetType(); - foreach (PropertyInfo pi in vtype.GetProperties()) { - if (!pi.CanRead) { - continue; - } - ndoc[pi.Name] = pi.GetValue(val, null); - } - if (key != null) { - doc.SetDocument(key, ndoc); - } - } - - public BSONDocument SetNull(string key) { - return SetBSONValue(new BSONValue(BSONType.NULL, key)); - } - - public BSONDocument SetUndefined(string key) { - return SetBSONValue(new BSONValue(BSONType.UNKNOWN, key)); - } - - public BSONDocument SetMaxKey(string key) { - return SetBSONValue(new BSONValue(BSONType.MAXKEY, key)); - } - - public BSONDocument SetMinKey(string key) { - return SetBSONValue(new BSONValue(BSONType.MINKEY, key)); - } - - public BSONDocument SetOID(string key, string oid) { - return SetBSONValue(new BSONValue(BSONType.OID, key, new BSONOid(oid))); - } - - public BSONDocument SetOID(string key, BSONOid oid) { - return SetBSONValue(new BSONValue(BSONType.OID, key, oid)); - } - - public BSONDocument SetBool(string key, bool val) { - return SetBSONValue(new BSONValue(BSONType.BOOL, key, val)); - } - - public BSONDocument SetNumber(string key, int val) { - return SetBSONValue(new BSONValue(BSONType.INT, key, val)); - } - - public BSONDocument SetNumber(string key, long val) { - return SetBSONValue(new BSONValue(BSONType.LONG, key, val)); - } - - public BSONDocument SetNumber(string key, double val) { - return SetBSONValue(new BSONValue(BSONType.DOUBLE, key, val)); - } - - public BSONDocument SetNumber(string key, float val) { - return SetBSONValue(new BSONValue(BSONType.DOUBLE, key, val)); - } - - public BSONDocument SetString(string key, string val) { - return SetBSONValue(new BSONValue(BSONType.STRING, key, val)); - } - - public BSONDocument SetCode(string key, string val) { - return SetBSONValue(new BSONValue(BSONType.CODE, key, val)); - } - - public BSONDocument SetSymbol(string key, string val) { - return SetBSONValue(new BSONValue(BSONType.SYMBOL, key, val)); - } - - public BSONDocument SetDate(string key, DateTime val) { - return SetBSONValue(new BSONValue(BSONType.DATE, key, val)); - } - - public BSONDocument SetRegexp(string key, BSONRegexp val) { - return SetBSONValue(new BSONValue(BSONType.REGEX, key, val)); - } - - public BSONDocument SetBinData(string key, BSONBinData val) { - return SetBSONValue(new BSONValue(BSONType.BINDATA, key, val)); - } - - public BSONDocument SetDocument(string key, BSONDocument val) { - return SetBSONValue(new BSONValue(BSONType.OBJECT, key, val)); - } - - public BSONDocument SetArray(string key, BSONArray val) { - return SetBSONValue(new BSONValue(BSONType.ARRAY, key, val)); - } - - public BSONDocument SetTimestamp(string key, BSONTimestamp val) { - return SetBSONValue(new BSONValue(BSONType.TIMESTAMP, key, val)); - } - - public BSONDocument SetCodeWScope(string key, BSONCodeWScope val) { - return SetBSONValue(new BSONValue(BSONType.CODEWSCOPE, key, val)); - } - - public BSONValue DropValue(string key) { - var bv = GetBSONValue(key); + public BsonValue DropValue(string key) { + var bv = GetBsonValue(key); if (bv == null) { return bv; } _cachedhash = null; _fields.Remove(key); - _fieldslist.RemoveAll(x => x.Key == key); return bv; } @@ -421,266 +289,257 @@ public BSONValue DropValue(string key) { /// public void Clear() { _cachedhash = null; - _fieldslist.Clear(); - if (_fields != null) { - _fields.Clear(); - _fields = null; - } - } - - public void Serialize(Stream os) { - if (os.CanSeek) { - long start = os.Position; - os.Position += 4; //skip int32 document size - using (var bw = new ExtBinaryWriter(os, Encoding.UTF8, true)) { - foreach (BSONValue bv in _fieldslist) { - WriteBSONValue(bv, bw); - } - bw.Write((byte) 0x00); - long end = os.Position; - os.Position = start; - bw.Write((int) (end - start)); - os.Position = end; //go to the end - } - } else { - byte[] darr; - var ms = new MemoryStream(); - using (var bw = new ExtBinaryWriter(ms)) { - foreach (BSONValue bv in _fieldslist) { - WriteBSONValue(bv, bw); - } - darr = ms.ToArray(); - } - using (var bw = new ExtBinaryWriter(os, Encoding.UTF8, true)) { - bw.Write(darr.Length + 4/*doclen*/ + 1/*0x00*/); - bw.Write(darr); - bw.Write((byte) 0x00); - } - } - os.Flush(); - } - - public override bool Equals(object obj) { - if (obj == null) { + _fields.Clear(); + } + + public void Serialize(Stream stream) + { + if (stream.CanSeek) + { + _WriteToSeekableStream(stream); + } + else + { + var ms = new MemoryStream(); + _WriteToSeekableStream(ms); + byte[] bytes = ms.ToArray(); + stream.Write(bytes, 0, bytes.Length); + } + stream.Flush(); + } + + private void _WriteToSeekableStream(Stream stream) + { + long start = stream.Position; + stream.Position += 4; //skip int32 document size + using (var bw = new ExtBinaryWriter(stream, true)) + { + foreach (var bv in _fields) + WriteBsonValue(bv.Key, bv.Value, bw); + + bw.Write((byte) 0x00); + long end = stream.Position; + stream.Position = start; + bw.Write((int) (end - start)); + stream.Position = end; //go to the end + } + } + + public override bool Equals(object obj) { + if (obj == null) return false; - } - if (ReferenceEquals(this, obj)) { + + if (ReferenceEquals(this, obj)) return true; - } - if (!(obj is BSONDocument)) { + + if (!(obj is BsonDocument)) return false; - } - BSONDocument d1 = this; - BSONDocument d2 = ((BSONDocument) obj); - if (d1.KeysCount != d2.KeysCount) { + + var d1 = this; + var d2 = ((BsonDocument) obj); + + if (d1.KeysCount != d2.KeysCount) return false; - } - foreach (BSONValue bv1 in d1._fieldslist) { - BSONValue bv2 = d2.GetBSONValue(bv1.Key); - if (bv1 != bv2) { + + foreach (var bv1 in d1._fields) + { + var bv2 = d2.GetBsonValue(bv1.Key); + if (bv1.Value != bv2) return false; - } } + return true; } - public override int GetHashCode() { - if (_cachedhash != null) { + public override int GetHashCode() + { + if (_cachedhash != null) return (int) _cachedhash; - } - unchecked { + + unchecked + { int hash = 1; - foreach (var bv in _fieldslist) { - hash = (hash * 31) + bv.GetHashCode(); + foreach (var bv in _fields) { + hash = (hash * 31) + bv.Key.GetHashCode() + bv.Value.GetHashCode(); } _cachedhash = hash; } return (int) _cachedhash; } - public static bool operator ==(BSONDocument d1, BSONDocument d2) { + public static bool operator ==(BsonDocument d1, BsonDocument d2) { return Equals(d1, d2); } - public static bool operator !=(BSONDocument d1, BSONDocument d2) { + public static bool operator !=(BsonDocument d1, BsonDocument d2) { return !(d1 == d2); } - public static BSONDocument ValueOf(object val) { - if (val == null) { - return new BSONDocument(); - } - Type vtype = val.GetType(); - if (val is BSONDocument) { - return (BSONDocument) val; - } else if (vtype == typeof(byte[])) { - return new BSONDocument((byte[]) val); - } else if (vtype.IsAnonymousType()) { - BSONDocument doc = new BSONDocument(); - SetAnonType(doc, null, val); - return doc; - } - throw new InvalidCastException(string.Format("Unsupported cast type: {0}", vtype)); + public static BsonDocument ValueOf(object val) + { + if (val == null) + return new BsonDocument(); + + var vtype = val.GetType(); + + if (val is BsonDocument) + return (BsonDocument) val; + + if (vtype == typeof (byte[])) + return new BsonDocument((byte[]) val); + + return BsonValue.GetDocumentForCustomClassObject(val); } public object Clone() { - return new BSONDocument(this); + return new BsonDocument(this); } - public override string ToString() { + public override string ToString() + { return string.Format("[{0}: {1}]", GetType().Name, - string.Join(", ", from bv in _fieldslist select bv.ToString())); + string.Join(", ", from bv in _fields select bv.Value.ToStringWithKey(bv.Key))); } //.////////////////////////////////////////////////////////////////// // Private staff //.////////////////////////////////////////////////////////////////// - internal BSONDocument Add(BSONValue bv) { - _cachedhash = null; - _fieldslist.Add(bv); - if (_fields != null) { - _fields[bv.Key] = bv; - } - return this; - } - internal BSONDocument SetBSONValue(BSONValue val) { + + + public BsonDocument Add(string key, BsonValue val) + { _cachedhash = null; - CheckFields(); - if (val.BSONType == BSONType.STRING && val.Key == "_id") { - val = new BSONValue(BSONType.OID, val.Key, new BSONOid((string) val.Value)); - } - BSONValue ov; - if (_fields.TryGetValue(val.Key, out ov)) { - ov.Key = val.Key; + + if (val.BSONType == BsonType.STRING && key == BsonConstants.Id) + val = new BsonValue(BsonType.OID, new BsonOid((string)val.Value)); + + BsonValue ov; + if (_fields.TryGetValue(key, out ov)) + { ov.BSONType = val.BSONType; ov.Value = val.Value; - } else { - _fieldslist.Add(val); - _fields.Add(val.Key, val); } - return this; - } + else + _fields.Add(key, val); - protected virtual void CheckKey(string key) { - } - - protected void WriteBSONValue(BSONValue bv, ExtBinaryWriter bw) { - BSONType bt = bv.BSONType; - switch (bt) { - case BSONType.EOO: - break; - case BSONType.NULL: - case BSONType.UNDEFINED: - case BSONType.MAXKEY: - case BSONType.MINKEY: - WriteTypeAndKey(bv, bw); - break; - case BSONType.OID: - { - WriteTypeAndKey(bv, bw); - BSONOid oid = (BSONOid) bv.Value; - Debug.Assert(oid._bytes.Length == 12); - bw.Write(oid._bytes); - break; - } - case BSONType.STRING: - case BSONType.CODE: - case BSONType.SYMBOL: - WriteTypeAndKey(bv, bw); - bw.WriteBSONString((string) bv.Value); - break; - case BSONType.BOOL: - WriteTypeAndKey(bv, bw); - bw.Write((bool) bv.Value); - break; - case BSONType.INT: - WriteTypeAndKey(bv, bw); - bw.Write((int) bv.Value); - break; - case BSONType.LONG: - WriteTypeAndKey(bv, bw); - bw.Write((long) bv.Value); - break; - case BSONType.ARRAY: - case BSONType.OBJECT: - { - BSONDocument doc = (BSONDocument) bv.Value; - WriteTypeAndKey(bv, bw); - doc.Serialize(bw.BaseStream); - break; - } - case BSONType.DATE: - { - DateTime dt = (DateTime) bv.Value; - var diff = dt.ToLocalTime() - BSONConstants.Epoch; - long time = (long) Math.Floor(diff.TotalMilliseconds); - WriteTypeAndKey(bv, bw); - bw.Write(time); - break; - } - case BSONType.DOUBLE: - WriteTypeAndKey(bv, bw); - bw.Write((double) bv.Value); - break; - case BSONType.REGEX: - { - BSONRegexp rv = (BSONRegexp) bv.Value; - WriteTypeAndKey(bv, bw); - bw.WriteCString(rv.Re ?? ""); - bw.WriteCString(rv.Opts ?? ""); - break; - } - case BSONType.BINDATA: - { - BSONBinData bdata = (BSONBinData) bv.Value; - WriteTypeAndKey(bv, bw); - bw.Write(bdata.Data.Length); - bw.Write(bdata.Subtype); - bw.Write(bdata.Data); - break; - } - case BSONType.DBREF: - //Unsupported DBREF! - break; - case BSONType.TIMESTAMP: - { - BSONTimestamp ts = (BSONTimestamp) bv.Value; - WriteTypeAndKey(bv, bw); - bw.Write(ts.Inc); - bw.Write(ts.Ts); - break; - } - case BSONType.CODEWSCOPE: - { - BSONCodeWScope cw = (BSONCodeWScope) bv.Value; - WriteTypeAndKey(bv, bw); - using (var cwwr = new ExtBinaryWriter(new MemoryStream())) { - cwwr.WriteBSONString(cw.Code); - cw.Scope.Serialize(cwwr.BaseStream); - byte[] cwdata = ((MemoryStream) cwwr.BaseStream).ToArray(); - bw.Write(cwdata.Length); - bw.Write(cwdata); - } - break; - } - default: - throw new InvalidBSONDataException("Unknown entry type: " + bt); - } + return this; } - protected void WriteTypeAndKey(BSONValue bv, ExtBinaryWriter bw) { + public BsonDocument Add(string key, object value) + { + return Add(key, BsonValue.ValueOf(value)); + } + + protected void WriteBsonValue(string key, BsonValue bv, ExtBinaryWriter bw) + { + BsonType bt = bv.BSONType; + switch (bt) + { + case BsonType.EOO: + break; + case BsonType.NULL: + case BsonType.UNDEFINED: + case BsonType.MAXKEY: + case BsonType.MINKEY: + WriteTypeAndKey(key, bv, bw); + break; + case BsonType.OID: + { + WriteTypeAndKey(key, bv, bw); + var oid = (BsonOid)bv.Value; + bw.Write(oid.ToByteArray()); + break; + } + case BsonType.STRING: + case BsonType.CODE: + case BsonType.SYMBOL: + WriteTypeAndKey(key, bv, bw); + bw.WriteBSONString((string)bv.Value); + break; + case BsonType.BOOL: + WriteTypeAndKey(key, bv, bw); + bw.Write((bool)bv.Value); + break; + case BsonType.INT: + WriteTypeAndKey(key, bv, bw); + bw.Write((int)bv.Value); + break; + case BsonType.LONG: + WriteTypeAndKey(key, bv, bw); + bw.Write((long)bv.Value); + break; + case BsonType.ARRAY: + case BsonType.OBJECT: + { + BsonDocument doc = (BsonDocument)bv.Value; + WriteTypeAndKey(key, bv, bw); + doc.Serialize(bw.BaseStream); + break; + } + case BsonType.DATE: + { + DateTime dt = (DateTime)bv.Value; + var diff = dt.ToUniversalTime() - BsonConstants.Epoch; + long time = (long)Math.Floor(diff.TotalMilliseconds); + WriteTypeAndKey(key, bv, bw); + bw.Write(time); + break; + } + case BsonType.DOUBLE: + WriteTypeAndKey(key, bv, bw); + bw.Write((double)bv.Value); + break; + case BsonType.REGEX: + { + BsonRegexp rv = (BsonRegexp)bv.Value; + WriteTypeAndKey(key, bv, bw); + bw.WriteCString(rv.Re ?? ""); + bw.WriteCString(rv.Opts ?? ""); + break; + } + case BsonType.BINDATA: + { + BsonBinData bdata = (BsonBinData)bv.Value; + WriteTypeAndKey(key, bv, bw); + bw.Write(bdata.Data.Length); + bw.Write(bdata.Subtype); + bw.Write(bdata.Data); + break; + } + case BsonType.DBREF: + //Unsupported DBREF! + break; + case BsonType.TIMESTAMP: + { + BsonTimestamp ts = (BsonTimestamp)bv.Value; + WriteTypeAndKey(key, bv, bw); + bw.Write(ts.Inc); + bw.Write(ts.Ts); + break; + } + case BsonType.CODEWSCOPE: + { + BsonCodeWScope cw = (BsonCodeWScope)bv.Value; + WriteTypeAndKey(key, bv, bw); + using (var cwwr = new ExtBinaryWriter(new MemoryStream())) + { + cwwr.WriteBSONString(cw.Code); + cw.Scope.Serialize(cwwr.BaseStream); + byte[] cwdata = ((MemoryStream)cwwr.BaseStream).ToArray(); + bw.Write(cwdata.Length); + bw.Write(cwdata); + } + break; + } + default: + throw new InvalidBSONDataException("Unknown entry type: " + bt); + } + } + + protected void WriteTypeAndKey(string key, BsonValue bv, ExtBinaryWriter bw) + { bw.Write((byte) bv.BSONType); - bw.WriteCString(bv.Key); - } - - protected void CheckFields() { - if (_fields != null) { - return; - } - _fields = new Dictionary(Math.Max(_fieldslist.Count + 1, 32)); - foreach (var bv in _fieldslist) { - _fields.Add(bv.Key, bv); - } + bw.WriteCString(key); } - } + } } diff --git a/Ejdb.BSON/BSONIterator.cs b/Ejdb.BSON/BSONIterator.cs index ac5ee26..2ff3527 100644 --- a/Ejdb.BSON/BSONIterator.cs +++ b/Ejdb.BSON/BSONIterator.cs @@ -1,364 +1,400 @@ -// ============================================================================================ -// .NET API for EJDB database library http://ejdb.org -// Copyright (C) 2012-2013 Softmotions Ltd -// -// This file is part of EJDB. -// EJDB is free software; you can redistribute it and/or modify it under the terms of -// the GNU Lesser General Public License as published by the Free Software Foundation; either -// version 2.1 of the License or any later version. EJDB is distributed in the hope -// that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License for more details. -// You should have received a copy of the GNU Lesser General Public License along with EJDB; -// if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, -// Boston, MA 02111-1307 USA. -// ============================================================================================ -using System; -using System.Text; -using System.Diagnostics; -using System.IO; -using Ejdb.IO; -using System.Collections.Generic; - -namespace Ejdb.BSON { - - public sealed class BSONIterator : IDisposable, IEnumerable { - ExtBinaryReader _input; - - bool _closeOnDispose = true; - - bool _disposed; - - int _doclen; - - BSONType _ctype = BSONType.UNKNOWN; - - string _entryKey; - - int _entryLen; - - bool _entryDataSkipped; - - BSONValue _entryDataValue; - - /// - /// Returns true if this is disposed. - /// - public bool Disposed { - get { - return _disposed; - } - } - - /// - /// Gets a value indicating whether this is empty. - /// - public bool Empty { - get { - return (_ctype == BSONType.EOO); - } - } - - /// - /// Gets the length of the document in bytes represented by this iterator. - /// - public int DocumentLength { - get { return _doclen; } - private set { _doclen = value; } - } - - /// - /// Gets the current document key pointed by this iterator. - /// - public string CurrentKey { - get { return _entryKey; } - } - - public BSONIterator() { //empty iterator - this._ctype = BSONType.EOO; - } - - public BSONIterator(BSONDocument doc) : this(doc.ToByteArray()) { - } - - public BSONIterator(byte[] bbuf) : this(new MemoryStream(bbuf)) { - } - - public BSONIterator(Stream input) { - if (!input.CanRead) { - Dispose(); - throw new IOException("Input stream must be readable"); - } - if (!input.CanSeek) { - Dispose(); - throw new IOException("Input stream must be seekable"); - } - this._input = new ExtBinaryReader(input); - this._ctype = BSONType.UNKNOWN; - this._doclen = _input.ReadInt32(); - if (this._doclen < 5) { - Dispose(); - throw new InvalidBSONDataException("Unexpected end of BSON document"); - } - } - - BSONIterator(ExtBinaryReader input, int doclen) { - this._input = input; - this._doclen = doclen; - if (this._doclen < 5) { - Dispose(); - throw new InvalidBSONDataException("Unexpected end of BSON document"); - } - } - - internal BSONIterator(BSONIterator it) : this(it._input, it._entryLen + 4) { - _closeOnDispose = false; - it._entryDataSkipped = true; - } - - ~BSONIterator() { - Dispose(); - } - - public void Dispose() { - _disposed = true; - if (_closeOnDispose && _input != null) { - _input.Close(); - _input = null; - } - } - - void CheckDisposed() { - if (Disposed) { - throw new ObjectDisposedException("BSONIterator"); - } - } - - public IEnumerator GetEnumerator() { - while (Next() != BSONType.EOO) { - yield return _ctype; - } - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { - return GetEnumerator(); - } - - public IEnumerable Values() { - while (Next() != BSONType.EOO) { - yield return FetchCurrentValue(); - } - } - - public BSONDocument ToBSONDocument(params string[] fields) { - if (fields.Length > 0) { - return new BSONDocument(this, fields); - } else { - return new BSONDocument(this); - } - } - - public BSONType Next() { - CheckDisposed(); - if (_ctype == BSONType.EOO) { - return BSONType.EOO; - } - if (!_entryDataSkipped && _ctype != BSONType.UNKNOWN) { - SkipData(); - } - byte bv = _input.ReadByte(); - if (!Enum.IsDefined(typeof(BSONType), bv)) { - throw new InvalidBSONDataException("Unknown bson type: " + bv); - } - _entryDataSkipped = false; - _entryDataValue = null; - _entryKey = null; - _ctype = (BSONType) bv; - _entryLen = 0; - if (_ctype != BSONType.EOO) { - ReadKey(); - } - switch (_ctype) { - case BSONType.EOO: - Dispose(); - return BSONType.EOO; - case BSONType.UNDEFINED: - case BSONType.NULL: - case BSONType.MAXKEY: - case BSONType.MINKEY: - _entryLen = 0; - break; - case BSONType.BOOL: - _entryLen = 1; - break; - case BSONType.INT: - _entryLen = 4; - break; - case BSONType.LONG: - case BSONType.DOUBLE: - case BSONType.TIMESTAMP: - case BSONType.DATE: - _entryLen = 8; - break; - case BSONType.OID: - _entryLen = 12; - break; - case BSONType.STRING: - case BSONType.CODE: - case BSONType.SYMBOL: - _entryLen = _input.ReadInt32(); - break; - case BSONType.DBREF: - //Unsupported DBREF! - _entryLen = 12 + _input.ReadInt32(); - break; - case BSONType.BINDATA: - _entryLen = 1 + _input.ReadInt32(); - break; - case BSONType.OBJECT: - case BSONType.ARRAY: - case BSONType.CODEWSCOPE: - _entryLen = _input.ReadInt32() - 4; - Debug.Assert(_entryLen > 0); - break; - case BSONType.REGEX: - _entryLen = 0; - break; - default: - throw new InvalidBSONDataException("Unknown entry type: " + _ctype); - } - return _ctype; - } - - public BSONValue FetchCurrentValue() { - CheckDisposed(); - if (_entryDataSkipped) { - return _entryDataValue; - } - _entryDataSkipped = true; - switch (_ctype) { - case BSONType.EOO: - case BSONType.UNDEFINED: - case BSONType.NULL: - case BSONType.MAXKEY: - case BSONType.MINKEY: - _entryDataValue = new BSONValue(_ctype, _entryKey); - break; - case BSONType.OID: - Debug.Assert(_entryLen == 12); - _entryDataValue = new BSONValue(_ctype, _entryKey, new BSONOid(_input)); - break; - case BSONType.STRING: - case BSONType.CODE: - case BSONType.SYMBOL: - { - Debug.Assert(_entryLen - 1 >= 0); - string sv = Encoding.UTF8.GetString(_input.ReadBytes(_entryLen - 1)); - _entryDataValue = new BSONValue(_ctype, _entryKey, sv); - var rb = _input.ReadByte(); - Debug.Assert(rb == 0x00); //trailing zero byte - break; - } - case BSONType.BOOL: - _entryDataValue = new BSONValue(_ctype, _entryKey, _input.ReadBoolean()); - break; - case BSONType.INT: - _entryDataValue = new BSONValue(_ctype, _entryKey, _input.ReadInt32()); - break; - case BSONType.OBJECT: - case BSONType.ARRAY: - { - BSONDocument doc = (_ctype == BSONType.OBJECT ? new BSONDocument() : new BSONArray()); - BSONIterator sit = new BSONIterator(this); - while (sit.Next() != BSONType.EOO) { - doc.Add(sit.FetchCurrentValue()); - } - _entryDataValue = new BSONValue(_ctype, _entryKey, doc); - break; - } - case BSONType.DOUBLE: - _entryDataValue = new BSONValue(_ctype, _entryKey, _input.ReadDouble()); - break; - case BSONType.LONG: - _entryDataValue = new BSONValue(_ctype, _entryKey, _input.ReadInt64()); - break; - case BSONType.DATE: - _entryDataValue = new BSONValue(_ctype, _entryKey, - BSONConstants.Epoch.AddMilliseconds(_input.ReadInt64())); - break; - case BSONType.TIMESTAMP: - { - int inc = _input.ReadInt32(); - int ts = _input.ReadInt32(); - _entryDataValue = new BSONValue(_ctype, _entryKey, - new BSONTimestamp(inc, ts)); - break; - } - case BSONType.REGEX: - { - string re = _input.ReadCString(); - string opts = _input.ReadCString(); - _entryDataValue = new BSONValue(_ctype, _entryKey, - new BSONRegexp(re, opts)); - break; - } - case BSONType.BINDATA: - { - byte subtype = _input.ReadByte(); - BSONBinData bd = new BSONBinData(subtype, _entryLen - 1, _input); - _entryDataValue = new BSONValue(_ctype, _entryKey, bd); - break; - } - case BSONType.DBREF: - { - //Unsupported DBREF! - SkipData(true); - _entryDataValue = new BSONValue(_ctype, _entryKey); - break; - } - case BSONType.CODEWSCOPE: - { - int cwlen = _entryLen + 4; - Debug.Assert(cwlen > 5); - int clen = _input.ReadInt32(); //code length - string code = Encoding.UTF8.GetString(_input.ReadBytes(clen)); - BSONCodeWScope cw = new BSONCodeWScope(code); - BSONIterator sit = new BSONIterator(_input, _input.ReadInt32()); - while (sit.Next() != BSONType.EOO) { - cw.Add(sit.FetchCurrentValue()); - } - _entryDataValue = new BSONValue(_ctype, _entryKey, cw); - break; - } - } - return _entryDataValue; - } - //.////////////////////////////////////////////////////////////////// - // Private staff - //.////////////////////////////////////////////////////////////////// - internal void SkipData(bool force = false) { - if (_entryDataSkipped && !force) { - return; - } - _entryDataValue = null; - _entryDataSkipped = true; - if (_ctype == BSONType.REGEX) { - _input.SkipCString(); - _input.SkipCString(); - Debug.Assert(_entryLen == 0); - } else if (_entryLen > 0) { - long cpos = _input.BaseStream.Position; - if ((cpos + _entryLen) != _input.BaseStream.Seek(_entryLen, SeekOrigin.Current)) { - throw new IOException("Inconsitent seek within input BSON stream"); - } - _entryLen = 0; - } - } - - string ReadKey() { - _entryKey = _input.ReadCString(); - return _entryKey; - } - } -} - +// ============================================================================================ +// .NET API for EJDB database library http://ejdb.org +// Copyright (C) 2012-2013 Softmotions Ltd +// +// This file is part of EJDB. +// EJDB is free software; you can redistribute it and/or modify it under the terms of +// the GNU Lesser General Public License as published by the Free Software Foundation; either +// version 2.1 of the License or any later version. EJDB is distributed in the hope +// that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +// License for more details. +// You should have received a copy of the GNU Lesser General Public License along with EJDB; +// if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, +// Boston, MA 02111-1307 USA. +// ============================================================================================ +using System; +using System.Linq; +using System.Text; +using System.Diagnostics; +using System.IO; +using Ejdb.IO; +using System.Collections.Generic; + +namespace Ejdb.BSON { + + public sealed class BsonIterator : IDisposable, IEnumerable + { + private static readonly Encoding STRICT_ENCODING = new UTF8Encoding(false, true); + + ExtBinaryReader _input; + + bool _closeOnDispose = true; + + bool _disposed; + + int _doclen; + + BsonType _ctype = BsonType.UNKNOWN; + + string _entryKey; + + int _entryLen; + + bool _entryDataSkipped; + + BsonValue _entryDataValue; + + /// + /// Returns true if this is disposed. + /// + public bool Disposed { + get { + return _disposed; + } + } + + /// + /// Gets a value indicating whether this is empty. + /// + public bool Empty { + get { + return (_ctype == BsonType.EOO); + } + } + + /// + /// Gets the length of the document in bytes represented by this iterator. + /// + public int DocumentLength { + get { return _doclen; } + private set { _doclen = value; } + } + + /// + /// Gets the current document key pointed by this iterator. + /// + public string CurrentKey { + get { return _entryKey; } + } + + public BsonIterator() { //empty iterator + this._ctype = BsonType.EOO; + } + + public BsonIterator(BsonDocument doc) : this(doc.ToByteArray()) { + } + + public BsonIterator(byte[] bbuf) : this(new MemoryStream(bbuf)) { + } + + public BsonIterator(Stream input) { + if (!input.CanRead) { + Dispose(); + throw new IOException("Input stream must be readable"); + } + if (!input.CanSeek) { + Dispose(); + throw new IOException("Input stream must be seekable"); + } + this._input = new ExtBinaryReader(input); + this._ctype = BsonType.UNKNOWN; + this._doclen = _input.ReadInt32(); + if (this._doclen < 5) { + Dispose(); + throw new InvalidBSONDataException("Unexpected end of BSON document"); + } + } + + BsonIterator(ExtBinaryReader input, int doclen) { + this._input = input; + this._doclen = doclen; + if (this._doclen < 5) { + Dispose(); + throw new InvalidBSONDataException("Unexpected end of BSON document"); + } + } + + internal BsonIterator(BsonIterator it) : this(it._input, it._entryLen + 4) { + _closeOnDispose = false; + it._entryDataSkipped = true; + } + + ~BsonIterator() { + Dispose(); + } + + public void Dispose() { + _disposed = true; + if (_closeOnDispose && _input != null) { + _input.Close(); + _input = null; + } + } + + void CheckDisposed() { + if (Disposed) { + throw new ObjectDisposedException("BsonIterator"); + } + } + + public IEnumerator GetEnumerator() { + while (Next() != BsonType.EOO) { + yield return _ctype; + } + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { + return GetEnumerator(); + } + + public IEnumerable Values() { + while (Next() != BsonType.EOO) { + yield return FetchCurrentValue(); + } + } + + public BsonDocument ToBsonDocument(params string[] fields) + { + if (fields.Length > 0) + return new BsonDocument(this, fields); + + return new BsonDocument(this); + } + + public T To() + { + var classMap = BsonClassSerialization.LookupClassMap(typeof (T)); + var obj = classMap.Creator(); + + while (Next() != BsonType.EOO) + { + BsonMemberMap memberMap; + if (classMap.AllMemberMaps.TryGetValue(CurrentKey, out memberMap)) + { + var value = FetchCurrentValue(); + memberMap.Setter(obj, value.Value); + } + else + Debug.Fail(string.Format("Could not find a property '{0}' on type {1}", CurrentKey, typeof (T).Name)); + } + + return (T) obj; + } + + public BsonType Next() { + CheckDisposed(); + if (_ctype == BsonType.EOO) { + return BsonType.EOO; + } + if (!_entryDataSkipped && _ctype != BsonType.UNKNOWN) { + SkipData(); + } + byte bv = _input.ReadByte(); + if (!Enum.IsDefined(typeof(BsonType), bv)) { + throw new InvalidBSONDataException("Unknown bson type: " + bv); + } + _entryDataSkipped = false; + _entryDataValue = null; + _entryKey = null; + _ctype = (BsonType) bv; + _entryLen = 0; + if (_ctype != BsonType.EOO) { + ReadKey(); + } + switch (_ctype) { + case BsonType.EOO: + Dispose(); + return BsonType.EOO; + case BsonType.UNDEFINED: + case BsonType.NULL: + case BsonType.MAXKEY: + case BsonType.MINKEY: + _entryLen = 0; + break; + case BsonType.BOOL: + _entryLen = 1; + break; + case BsonType.INT: + _entryLen = 4; + break; + case BsonType.LONG: + case BsonType.DOUBLE: + case BsonType.TIMESTAMP: + case BsonType.DATE: + _entryLen = 8; + break; + case BsonType.OID: + _entryLen = 12; + break; + case BsonType.STRING: + case BsonType.CODE: + case BsonType.SYMBOL: + _entryLen = _input.ReadInt32(); + break; + case BsonType.DBREF: + //Unsupported DBREF! + _entryLen = 12 + _input.ReadInt32(); + break; + case BsonType.BINDATA: + _entryLen = 1 + _input.ReadInt32(); + break; + case BsonType.OBJECT: + case BsonType.ARRAY: + case BsonType.CODEWSCOPE: + _entryLen = _input.ReadInt32() - 4; + Debug.Assert(_entryLen > 0); + break; + case BsonType.REGEX: + _entryLen = 0; + break; + default: + throw new InvalidBSONDataException("Unknown entry type: " + _ctype); + } + return _ctype; + } + + public BsonValue FetchCurrentValue() { + CheckDisposed(); + if (_entryDataSkipped) { + return _entryDataValue; + } + _entryDataSkipped = true; + switch (_ctype) { + case BsonType.EOO: + case BsonType.UNDEFINED: + case BsonType.NULL: + case BsonType.MAXKEY: + case BsonType.MINKEY: + _entryDataValue = new BsonValue(_ctype, _entryKey); + break; + case BsonType.OID: + Debug.Assert(_entryLen == 12); + var bytes = _input.ReadBytes(12); + _entryDataValue = new BsonValue(_ctype, new BsonOid(bytes)); + break; + case BsonType.STRING: + case BsonType.CODE: + case BsonType.SYMBOL: + { + if (_entryLen <= 0) + { + var message = string.Format("Invalid string length: {0} (the length includes the null terminator so it must be greater than or equal to 1).", _entryLen); + throw new BsonSerializationException(message); + } + + string sv = STRICT_ENCODING.GetString(_input.ReadBytes(_entryLen - 1)); + _entryDataValue = new BsonValue(_ctype, (object) sv); + var finalByte = _input.ReadByte(); + + if (finalByte != 0x00) + throw new BsonSerializationException("String is missing null terminator."); + + break; + } + case BsonType.BOOL: + _entryDataValue = new BsonValue(_ctype, (object) _input.ReadBoolean()); + break; + case BsonType.INT: + _entryDataValue = new BsonValue(_ctype, (object) _input.ReadInt32()); + break; + case BsonType.OBJECT: + case BsonType.ARRAY: + { + BsonDocument doc = (_ctype == BsonType.OBJECT ? new BsonDocument() : new BsonArray()); + BsonIterator sit = new BsonIterator(this); + while (sit.Next() != BsonType.EOO) + { + doc.Add(sit.CurrentKey, sit.FetchCurrentValue()); + } + _entryDataValue = new BsonValue(_ctype, doc); + break; + } + case BsonType.DOUBLE: + _entryDataValue = new BsonValue(_ctype, _input.ReadDouble()); + break; + case BsonType.LONG: + _entryDataValue = new BsonValue(_ctype, _input.ReadInt64()); + break; + case BsonType.DATE: + var milliseconds = _input.ReadInt64(); + var dateTime = BsonConstants.Epoch.AddMilliseconds(milliseconds); + _entryDataValue = new BsonValue(_ctype, dateTime); + break; + case BsonType.TIMESTAMP: + { + int inc = _input.ReadInt32(); + int ts = _input.ReadInt32(); + _entryDataValue = new BsonValue(_ctype, + new BsonTimestamp(inc, ts)); + break; + } + case BsonType.REGEX: + { + string re = _input.ReadCString(); + string opts = _input.ReadCString(); + _entryDataValue = new BsonValue(_ctype, + new BsonRegexp(re, opts)); + break; + } + case BsonType.BINDATA: + { + byte subtype = _input.ReadByte(); + BsonBinData bd = new BsonBinData(subtype, _entryLen - 1, _input); + _entryDataValue = new BsonValue(_ctype, bd); + break; + } + case BsonType.DBREF: + { + //Unsupported DBREF! + SkipData(true); + _entryDataValue = new BsonValue(_ctype, _entryKey); + break; + } + case BsonType.CODEWSCOPE: + { + int cwlen = _entryLen + 4; + Debug.Assert(cwlen > 5); + int clen = _input.ReadInt32(); //code length + string code = STRICT_ENCODING.GetString(_input.ReadBytes(clen)); + BsonCodeWScope cw = new BsonCodeWScope(code); + BsonIterator sit = new BsonIterator(_input, _input.ReadInt32()); + while (sit.Next() != BsonType.EOO) + { + cw.Add(sit.CurrentKey, sit.FetchCurrentValue()); + } + _entryDataValue = new BsonValue(_ctype, (object) cw); + break; + } + } + return _entryDataValue; + } + //.////////////////////////////////////////////////////////////////// + // Private staff + //.////////////////////////////////////////////////////////////////// + internal void SkipData(bool force = false) { + if (_entryDataSkipped && !force) { + return; + } + _entryDataValue = null; + _entryDataSkipped = true; + if (_ctype == BsonType.REGEX) { + _input.SkipCString(); + _input.SkipCString(); + Debug.Assert(_entryLen == 0); + } else if (_entryLen > 0) { + long cpos = _input.BaseStream.Position; + if ((cpos + _entryLen) != _input.BaseStream.Seek(_entryLen, SeekOrigin.Current)) { + throw new IOException("Inconsitent seek within input BSON stream"); + } + _entryLen = 0; + } + } + + string ReadKey() { + _entryKey = _input.ReadCString(); + return _entryKey; + } + } +} + diff --git a/Ejdb.BSON/BSONOid.cs b/Ejdb.BSON/BSONOid.cs index 9c3a835..50dfe7e 100644 --- a/Ejdb.BSON/BSONOid.cs +++ b/Ejdb.BSON/BSONOid.cs @@ -1,140 +1,593 @@ -// ============================================================================================ -// .NET API for EJDB database library http://ejdb.org -// Copyright (C) 2012-2013 Softmotions Ltd -// -// This file is part of EJDB. -// EJDB is free software; you can redistribute it and/or modify it under the terms of -// the GNU Lesser General Public License as published by the Free Software Foundation; either -// version 2.1 of the License or any later version. EJDB is distributed in the hope -// that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License for more details. -// You should have received a copy of the GNU Lesser General Public License along with EJDB; -// if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, -// Boston, MA 02111-1307 USA. -// ============================================================================================ -using System; -using System.IO; - -namespace Ejdb.BSON { - - [Serializable] - public sealed class BSONOid : IComparable, IBSONValue { - - internal byte[] _bytes; - string _cachedString; - - public BSONType BSONType { - get { - return BSONType.OID; - } - } - - BSONOid() { - } - - public BSONOid(string val) { - ParseOIDString(val); - } - - public BSONOid(byte[] val) { - _bytes = new byte[12]; - Array.Copy(val, _bytes, 12); - } - - public BSONOid(BinaryReader reader) { - _bytes = reader.ReadBytes(12); - } - - bool IsValidOid(string oid) { - var i = 0; - for (; i < oid.Length && - ((oid[i] >= 0x30 && oid[i] <= 0x39) || (oid[i] >= 0x61 && oid[i] <= 0x66)); - ++i) { - } - return (i == 24); - } - - void ParseOIDString(string val) { - if (!IsValidOid(val)) { - throw new ArgumentException("Invalid oid string"); - } - var vlen = val.Length; - _bytes = new byte[vlen / 2]; - for (var i = 0; i < vlen; i += 2) { - _bytes[i / 2] = Convert.ToByte(val.Substring(i, 2), 16); - } - } - - public int CompareTo(BSONOid other) { - if (ReferenceEquals(other, null)) { - return 1; - } - var obytes = other._bytes; - for (var x = 0; x < _bytes.Length; x++) { - if (_bytes[x] < obytes[x]) { - return -1; - } - if (_bytes[x] > obytes[x]) { - return 1; - } - } - return 0; - } - - public byte[] ToBytes() { - var b = new byte[12]; - Array.Copy(_bytes, b, 12); - return b; - } - - public override string ToString() { - if (_cachedString == null) { - _cachedString = BitConverter.ToString(_bytes).Replace("-", "").ToLower(); - } - return _cachedString; - } - - public override bool Equals(object obj) { - if (obj == null) { - return false; - } - if (ReferenceEquals(this, obj)) { - return true; - } - if (obj is BSONOid) { - return (CompareTo((BSONOid) obj) == 0); - } - return false; - } - - public override int GetHashCode() { - return ToString().GetHashCode(); - } - - public static bool operator ==(BSONOid a, BSONOid b) { - if (ReferenceEquals(a, b)) - return true; - if ((object) a == null || (object) b == null) { - return false; - } - return a.Equals(b); - } - - public static bool operator !=(BSONOid a, BSONOid b) { - return !(a == b); - } - - public static bool operator >(BSONOid a, BSONOid b) { - return a.CompareTo(b) > 0; - } - - public static bool operator <(BSONOid a, BSONOid b) { - return a.CompareTo(b) < 0; - } - - public static implicit operator BSONOid(string val) { - return new BSONOid(val); - } - } +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Security; +using System.Security.Cryptography; +using System.Text; +using System.Threading; + +namespace Ejdb.BSON +{ + /// + /// Represents an ObjectId (see also BsonObjectId). + /// + [Serializable] + public class BsonOid : IComparable, IEquatable, IConvertible + { + // private static fields + private static BsonOid __emptyInstance = default(BsonOid); + private static int __staticMachine; + private static short __staticPid; + private static int __staticIncrement; // high byte will be masked out when generating new ObjectId + + // private fields + // we're using 14 bytes instead of 12 to hold the ObjectId in memory but unlike a byte[] there is no additional object on the heap + // the extra two bytes are not visible to anyone outside of this class and they buy us considerable simplification + // an additional advantage of this representation is that it will serialize to JSON without any 64 bit overflow problems + private int _timestamp; + private int _machine; + private short _pid; + private int _increment; + + // static constructor + static BsonOid() + { + __staticMachine = GetMachineHash(); + __staticIncrement = (new Random()).Next(); + + try + { + __staticPid = (short)GetCurrentProcessId(); // use low order two bytes only + } + catch (SecurityException) + { + __staticPid = 0; + } + } + + // constructors + /// + /// Initializes a new instance of the ObjectId class. + /// + /// The bytes. + public BsonOid(byte[] bytes) + { + if (bytes == null) + { + throw new ArgumentNullException("bytes"); + } + Unpack(bytes, out _timestamp, out _machine, out _pid, out _increment); + } + + /// + /// Initializes a new instance of the ObjectId class. + /// + /// The timestamp (expressed as a DateTime). + /// The machine hash. + /// The PID. + /// The increment. + public BsonOid(DateTime timestamp, int machine, short pid, int increment) + : this(GetTimestampFromDateTime(timestamp), machine, pid, increment) + { + } + + /// + /// Initializes a new instance of the ObjectId class. + /// + /// The timestamp. + /// The machine hash. + /// The PID. + /// The increment. + public BsonOid(int timestamp, int machine, short pid, int increment) + { + if ((machine & 0xff000000) != 0) + { + throw new ArgumentOutOfRangeException("machine", "The machine value must be between 0 and 16777215 (it must fit in 3 bytes)."); + } + if ((increment & 0xff000000) != 0) + { + throw new ArgumentOutOfRangeException("increment", "The increment value must be between 0 and 16777215 (it must fit in 3 bytes)."); + } + + _timestamp = timestamp; + _machine = machine; + _pid = pid; + _increment = increment; + } + + /// + /// Initializes a new instance of the ObjectId class. + /// + /// The value. + public BsonOid(string value) + { + if (value == null) + throw new ArgumentNullException("value"); + + Unpack(_ParseHexString(value), out _timestamp, out _machine, out _pid, out _increment); + } + + private static byte[] _ParseHexString(string val) + { + if (!IsValidOid(val)) + { + var message = string.Format("'{0}' is not a valid 24 digit hex string.", val); + throw new FormatException(message); + } + + return _UnsafeParseHexString(val); + } + + private static byte[] _UnsafeParseHexString(string val) + { + var vlen = val.Length; + var bytes = new byte[vlen/2]; + for (var i = 0; i < vlen; i += 2) + bytes[i/2] = Convert.ToByte(val.Substring(i, 2), 16); + + return bytes; + } + + private static bool IsValidOid(string oid) + { + var i = 0; + for (; i < oid.Length && ( + (oid[i] >= '0' && oid[i] <= '9') || + (oid[i] >= 'a' && oid[i] <= 'f') || + (oid[i] >= 'A' && oid[i] <= 'F')); ++i) + { + } + + return (i == 24); + } + + // public static properties + /// + /// Gets an instance of ObjectId where the value is empty. + /// + public static BsonOid Empty + { + get { return __emptyInstance; } + } + + // public properties + /// + /// Gets the timestamp. + /// + public int Timestamp + { + get { return _timestamp; } + } + + /// + /// Gets the machine. + /// + public int Machine + { + get { return _machine; } + } + + /// + /// Gets the PID. + /// + public short Pid + { + get { return _pid; } + } + + /// + /// Gets the increment. + /// + public int Increment + { + get { return _increment; } + } + + /// + /// Gets the creation time (derived from the timestamp). + /// + public DateTime CreationTime + { + get { return BsonConstants.Epoch.AddSeconds(_timestamp); } + } + + // public operators + /// + /// Compares two ObjectIds. + /// + /// The first ObjectId. + /// The other ObjectId + /// True if the first ObjectId is less than the second ObjectId. + public static bool operator <(BsonOid lhs, BsonOid rhs) + { + return lhs.CompareTo(rhs) < 0; + } + + /// + /// Compares two ObjectIds. + /// + /// The first ObjectId. + /// The other ObjectId + /// True if the first ObjectId is less than or equal to the second ObjectId. + public static bool operator <=(BsonOid lhs, BsonOid rhs) + { + return lhs.CompareTo(rhs) <= 0; + } + + /// + /// Compares two ObjectIds. + /// + /// The first ObjectId. + /// The other ObjectId. + /// True if the two ObjectIds are equal. + public static bool operator ==(BsonOid lhs, BsonOid rhs) + { + return lhs.Equals(rhs); + } + + /// + /// Compares two ObjectIds. + /// + /// The first ObjectId. + /// The other ObjectId. + /// True if the two ObjectIds are not equal. + public static bool operator !=(BsonOid lhs, BsonOid rhs) + { + return !(lhs == rhs); + } + + /// + /// Compares two ObjectIds. + /// + /// The first ObjectId. + /// The other ObjectId + /// True if the first ObjectId is greather than or equal to the second ObjectId. + public static bool operator >=(BsonOid lhs, BsonOid rhs) + { + return lhs.CompareTo(rhs) >= 0; + } + + /// + /// Compares two ObjectIds. + /// + /// The first ObjectId. + /// The other ObjectId + /// True if the first ObjectId is greather than the second ObjectId. + public static bool operator >(BsonOid lhs, BsonOid rhs) + { + return lhs.CompareTo(rhs) > 0; + } + + // public static methods + /// + /// Generates a new ObjectId with a unique value. + /// + /// An ObjectId. + public static BsonOid GenerateNewId() + { + return GenerateNewId(GetTimestampFromDateTime(DateTime.UtcNow)); + } + + /// + /// Generates a new ObjectId with a unique value (with the timestamp component based on a given DateTime). + /// + /// The timestamp component (expressed as a DateTime). + /// An ObjectId. + public static BsonOid GenerateNewId(DateTime timestamp) + { + return GenerateNewId(GetTimestampFromDateTime(timestamp)); + } + + /// + /// Generates a new ObjectId with a unique value (with the given timestamp). + /// + /// The timestamp component. + /// An ObjectId. + public static BsonOid GenerateNewId(int timestamp) + { + int increment = Interlocked.Increment(ref __staticIncrement) & 0x00ffffff; // only use low order 3 bytes + return new BsonOid(timestamp, __staticMachine, __staticPid, increment); + } + + /// + /// Packs the components of an ObjectId into a byte array. + /// + /// The timestamp. + /// The machine hash. + /// The PID. + /// The increment. + /// A byte array. + public static byte[] Pack(int timestamp, int machine, short pid, int increment) + { + if ((machine & 0xff000000) != 0) + { + throw new ArgumentOutOfRangeException("machine", "The machine value must be between 0 and 16777215 (it must fit in 3 bytes)."); + } + if ((increment & 0xff000000) != 0) + { + throw new ArgumentOutOfRangeException("increment", "The increment value must be between 0 and 16777215 (it must fit in 3 bytes)."); + } + + byte[] bytes = new byte[12]; + bytes[0] = (byte)(timestamp >> 24); + bytes[1] = (byte)(timestamp >> 16); + bytes[2] = (byte)(timestamp >> 8); + bytes[3] = (byte)(timestamp); + bytes[4] = (byte)(machine >> 16); + bytes[5] = (byte)(machine >> 8); + bytes[6] = (byte)(machine); + bytes[7] = (byte)(pid >> 8); + bytes[8] = (byte)(pid); + bytes[9] = (byte)(increment >> 16); + bytes[10] = (byte)(increment >> 8); + bytes[11] = (byte)(increment); + return bytes; + } + + /// + /// Parses a string and creates a new ObjectId. + /// + /// The string value. + /// A ObjectId. + public static BsonOid Parse(string s) + { + if (s == null) + throw new ArgumentNullException("s"); + + + var bytes = _ParseHexString(s); + return new BsonOid(bytes); + } + + /// + /// Tries to parse a string and create a new ObjectId. + /// + /// The string value. + /// The new ObjectId. + /// True if the string was parsed successfully. + public static bool TryParse(string oidString, out BsonOid bsonOid) + { + // don't throw ArgumentNullException if oidString is null + if (oidString != null && oidString.Length == 24) + { + if (IsValidOid(oidString)) + { + byte[] bytes = _UnsafeParseHexString(oidString); + bsonOid = new BsonOid(bytes); + return true; + } + } + + bsonOid = default(BsonOid); + return false; + } + + /// + /// Unpacks a byte array into the components of an ObjectId. + /// + /// A byte array. + /// The timestamp. + /// The machine hash. + /// The PID. + /// The increment. + public static void Unpack(byte[] bytes, out int timestamp, out int machine, out short pid, out int increment) + { + if (bytes == null) + { + throw new ArgumentNullException("bytes"); + } + if (bytes.Length != 12) + { + throw new ArgumentOutOfRangeException("bytes", "Byte array must be 12 bytes long."); + } + timestamp = (bytes[0] << 24) + (bytes[1] << 16) + (bytes[2] << 8) + bytes[3]; + machine = (bytes[4] << 16) + (bytes[5] << 8) + bytes[6]; + pid = (short)((bytes[7] << 8) + bytes[8]); + increment = (bytes[9] << 16) + (bytes[10] << 8) + bytes[11]; + } + + // private static methods + /// + /// Gets the current process id. This method exists because of how CAS operates on the call stack, checking + /// for permissions before executing the method. Hence, if we inlined this call, the calling method would not execute + /// before throwing an exception requiring the try/catch at an even higher level that we don't necessarily control. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private static int GetCurrentProcessId() + { + return Process.GetCurrentProcess().Id; + } + + private static int GetMachineHash() + { + var hostName = Environment.MachineName; // use instead of Dns.HostName so it will work offline + var md5 = MD5.Create(); + var hash = md5.ComputeHash(Encoding.UTF8.GetBytes(hostName)); + return (hash[0] << 16) + (hash[1] << 8) + hash[2]; // use first 3 bytes of hash + } + + private static int GetTimestampFromDateTime(DateTime timestamp) + { + return (int)Math.Floor((BsonConstants.ToUniversalTime(timestamp) - BsonConstants.Epoch).TotalSeconds); + } + + // public methods + /// + /// Compares this ObjectId to another ObjectId. + /// + /// The other ObjectId. + /// A 32-bit signed integer that indicates whether this ObjectId is less than, equal to, or greather than the other. + public int CompareTo(BsonOid other) + { + int r = _timestamp.CompareTo(other._timestamp); + if (r != 0) { return r; } + r = _machine.CompareTo(other._machine); + if (r != 0) { return r; } + r = _pid.CompareTo(other._pid); + if (r != 0) { return r; } + return _increment.CompareTo(other._increment); + } + + /// + /// Compares this ObjectId to another ObjectId. + /// + /// The other ObjectId. + /// True if the two ObjectIds are equal. + public bool Equals(BsonOid rhs) + { + return + _timestamp == rhs._timestamp && + _machine == rhs._machine && + _pid == rhs._pid && + _increment == rhs._increment; + } + + /// + /// Compares this ObjectId to another object. + /// + /// The other object. + /// True if the other object is an ObjectId and equal to this one. + public override bool Equals(object obj) + { + if (obj is BsonOid) + { + return Equals((BsonOid)obj); + } + else + { + return false; + } + } + + /// + /// Gets the hash code. + /// + /// The hash code. + public override int GetHashCode() + { + int hash = 17; + hash = 37 * hash + _timestamp.GetHashCode(); + hash = 37 * hash + _machine.GetHashCode(); + hash = 37 * hash + _pid.GetHashCode(); + hash = 37 * hash + _increment.GetHashCode(); + return hash; + } + + /// + /// Converts the ObjectId to a byte array. + /// + /// A byte array. + public byte[] ToByteArray() + { + return Pack(_timestamp, _machine, _pid, _increment); + } + + /// + /// Returns a string representation of the value. + /// + /// A string representation of the value. + public override string ToString() + { + var bytes = Pack(_timestamp, _machine, _pid, _increment); + return BitConverter.ToString(bytes).Replace("-", "").ToLower(); + } + + // explicit IConvertible implementation + TypeCode IConvertible.GetTypeCode() + { + return TypeCode.Object; + } + + bool IConvertible.ToBoolean(IFormatProvider provider) + { + throw new InvalidCastException(); + } + + byte IConvertible.ToByte(IFormatProvider provider) + { + throw new InvalidCastException(); + } + + char IConvertible.ToChar(IFormatProvider provider) + { + throw new InvalidCastException(); + } + + DateTime IConvertible.ToDateTime(IFormatProvider provider) + { + throw new InvalidCastException(); + } + + decimal IConvertible.ToDecimal(IFormatProvider provider) + { + throw new InvalidCastException(); + } + + double IConvertible.ToDouble(IFormatProvider provider) + { + throw new InvalidCastException(); + } + + short IConvertible.ToInt16(IFormatProvider provider) + { + throw new InvalidCastException(); + } + + int IConvertible.ToInt32(IFormatProvider provider) + { + throw new InvalidCastException(); + } + + long IConvertible.ToInt64(IFormatProvider provider) + { + throw new InvalidCastException(); + } + + sbyte IConvertible.ToSByte(IFormatProvider provider) + { + throw new InvalidCastException(); + } + + float IConvertible.ToSingle(IFormatProvider provider) + { + throw new InvalidCastException(); + } + + string IConvertible.ToString(IFormatProvider provider) + { + return ToString(); + } + + object IConvertible.ToType(Type conversionType, IFormatProvider provider) + { + switch (Type.GetTypeCode(conversionType)) + { + case TypeCode.String: + return ((IConvertible)this).ToString(provider); + case TypeCode.Object: + if (conversionType == typeof (BsonOid)) + return this; + if (conversionType == typeof (BsonValue)) + return BsonValue.Create(this); + break; + } + + throw new InvalidCastException(); + } + + ushort IConvertible.ToUInt16(IFormatProvider provider) + { + throw new InvalidCastException(); + } + + uint IConvertible.ToUInt32(IFormatProvider provider) + { + throw new InvalidCastException(); + } + + ulong IConvertible.ToUInt64(IFormatProvider provider) + { + throw new InvalidCastException(); + } + } } diff --git a/Ejdb.BSON/BSONRegexp.cs b/Ejdb.BSON/BSONRegexp.cs index 9ec6c8b..8434694 100644 --- a/Ejdb.BSON/BSONRegexp.cs +++ b/Ejdb.BSON/BSONRegexp.cs @@ -21,15 +21,15 @@ namespace Ejdb.BSON { /// BSON Regexp complex value. /// [Serializable] - public sealed class BSONRegexp : IBSONValue { + public sealed class BsonRegexp : IBsonValue { readonly string _re; readonly string _opts; - public BSONType BSONType { + public BsonType BSONType { get { - return BSONType.REGEX; + return BsonType.REGEX; } } @@ -45,13 +45,13 @@ public string Opts { } } - BSONRegexp() { + BsonRegexp() { } - public BSONRegexp(string re) : this(re, "") { + public BsonRegexp(string re) : this(re, "") { } - public BSONRegexp(string re, string opts) { + public BsonRegexp(string re, string opts) { this._re = re; this._opts = opts; } @@ -63,10 +63,10 @@ public override bool Equals(object obj) { if (ReferenceEquals(this, obj)) { return true; } - if (!(obj is BSONRegexp)) { + if (!(obj is BsonRegexp)) { return false; } - BSONRegexp other = (BSONRegexp) obj; + BsonRegexp other = (BsonRegexp) obj; return (_re == other._re && _opts == other._opts); } @@ -77,7 +77,7 @@ public override int GetHashCode() { } public override string ToString() { - return string.Format("[BSONRegexp: re={0}, opts={1}]", _re, _opts); + return string.Format("[BsonRegexp: re={0}, opts={1}]", _re, _opts); } } } diff --git a/Ejdb.BSON/BSONTimestamp.cs b/Ejdb.BSON/BSONTimestamp.cs index e0cf39a..b5fce64 100644 --- a/Ejdb.BSON/BSONTimestamp.cs +++ b/Ejdb.BSON/BSONTimestamp.cs @@ -21,22 +21,22 @@ namespace Ejdb.BSON { /// BSON Timestamp complex value. /// [Serializable] - public sealed class BSONTimestamp : IBSONValue { + public sealed class BsonTimestamp : IBsonValue { readonly int _inc; readonly int _ts; - BSONTimestamp() { + BsonTimestamp() { } - public BSONTimestamp(int inc, int ts) { + public BsonTimestamp(int inc, int ts) { this._inc = inc; this._ts = ts; } - public BSONType BSONType { + public BsonType BSONType { get { - return BSONType.TIMESTAMP; + return BsonType.TIMESTAMP; } } @@ -59,10 +59,10 @@ public override bool Equals(object obj) { if (ReferenceEquals(this, obj)) { return true; } - if (!(obj is BSONTimestamp)) { + if (!(obj is BsonTimestamp)) { return false; } - BSONTimestamp other = (BSONTimestamp) obj; + BsonTimestamp other = (BsonTimestamp) obj; return (_inc == other._inc && _ts == other._ts); } @@ -73,7 +73,7 @@ public override int GetHashCode() { } public override string ToString() { - return string.Format("[BSONTimestamp: inc={0}, ts={1}]", _inc, _ts); + return string.Format("[BsonTimestamp: inc={0}, ts={1}]", _inc, _ts); } } } diff --git a/Ejdb.BSON/BSONType.cs b/Ejdb.BSON/BSONType.cs index e7c7498..d74d965 100644 --- a/Ejdb.BSON/BSONType.cs +++ b/Ejdb.BSON/BSONType.cs @@ -17,7 +17,7 @@ namespace Ejdb.BSON { /** BSON types according to the bsonspec (http://bsonspec.org/) */ - public enum BSONType : byte { + public enum BsonType : byte { UNKNOWN = 0xfe, EOO = 0x00, DOUBLE = 0x01, diff --git a/Ejdb.BSON/BSONUndefined.cs b/Ejdb.BSON/BSONUndefined.cs index 2ea8e3a..596aed4 100644 --- a/Ejdb.BSON/BSONUndefined.cs +++ b/Ejdb.BSON/BSONUndefined.cs @@ -17,13 +17,13 @@ namespace Ejdb.BSON { - public sealed class BSONUndefined : IBSONValue { + public sealed class BsonUndefined : IBsonValue { - public static BSONUndefined VALUE = new BSONUndefined(); + public static BsonUndefined VALUE = new BsonUndefined(); - public BSONType BSONType { + public BsonType BSONType { get { - return BSONType.UNDEFINED; + return BsonType.UNDEFINED; } } @@ -34,7 +34,7 @@ public override bool Equals(object obj) { if (ReferenceEquals(this, obj)) { return true; } - if (!(obj is BSONUndefined)) { + if (!(obj is BsonUndefined)) { return false; } return true; @@ -45,7 +45,7 @@ public override int GetHashCode() { } public override string ToString() { - return "[BSONUndefined]"; + return "[BsonUndefined]"; } } } diff --git a/Ejdb.BSON/BSONValue.cs b/Ejdb.BSON/BSONValue.cs index c8a6183..aafccb1 100644 --- a/Ejdb.BSON/BSONValue.cs +++ b/Ejdb.BSON/BSONValue.cs @@ -14,6 +14,7 @@ // Boston, MA 02111-1307 USA. // ============================================================================================ using System; +using System.Collections.Generic; namespace Ejdb.BSON { @@ -21,54 +22,50 @@ namespace Ejdb.BSON { /// BSON field value. /// [Serializable] - public sealed class BSONValue : IBSONValue, ICloneable { + public sealed class BsonValue : IBsonValue, ICloneable + { /// /// BSON.Type /// - public BSONType BSONType { get; internal set; } - - /// - /// BSON field key. - /// - public string Key { get; internal set; } + public BsonType BSONType { get; internal set; } /// /// Deserialized BSON field value. /// public object Value { get; internal set; } - public BSONValue(BSONType type, string key, object value) { - this.BSONType = type; - this.Key = key; - this.Value = value; + public BsonValue(BsonType type, object value) + { + BSONType = type; + Value = value; } - public BSONValue(BSONType type, string key) : this(type, key, null) { + public BsonValue(BsonType type) : this(type, null) { } - public override bool Equals(object obj) { - if (obj == null) { + public override bool Equals(object obj) + { + if (obj == null) return false; - } - if (ReferenceEquals(this, obj)) { + + if (ReferenceEquals(this, obj)) return true; - } - if (obj.GetType() != typeof(BSONValue)) { + + if (obj.GetType() != typeof(BsonValue)) return false; - } - BSONValue other = (BSONValue) obj; - if (BSONType != other.BSONType || Key != other.Key) { + + BsonValue other = (BsonValue) obj; + if (BSONType != other.BSONType) return false; - } - if (Value != null) { + + if (Value != null) return Value.Equals(other.Value); - } else { - return (Value == other.Value); - } + + return (Value == other.Value); } - public static bool operator ==(BSONValue v1, BSONValue v2) { + public static bool operator ==(BsonValue v1, BsonValue v2) { if (ReferenceEquals(v1, v2)) { return true; } @@ -78,23 +75,205 @@ public override bool Equals(object obj) { return v1.Equals(v2); } - public static bool operator !=(BSONValue v1, BSONValue v2) { + public static bool operator !=(BsonValue v1, BsonValue v2) + { return !(v1 == v2); } - public override int GetHashCode() { + public override int GetHashCode() + { unchecked { return BSONType.GetHashCode() ^ (Value != null ? Value.GetHashCode() : 0); } } public override string ToString() { - return string.Format("[BSONValue: BSONType={0}, Key={1}, Value={2}]", BSONType, Key, Value); + return String.Format("[BsonValue: BsonType={0}, Value={1}]", BSONType, Value); + } + + public string ToStringWithKey(string key) + { + return String.Format("[BsonValue: BsonType={0}, Key={1}, Value={2}]", BSONType, key, Value); + } + + public object Clone() + { + return new BsonValue(this.BSONType, this.Value); + } + + public static BsonValue ValueOf(object v) + { + if (v == null) + return GetNull(); + + var arrayValue = v as Array; + if (arrayValue != null) + return Create(new BsonArray(arrayValue)); + + Func setter; + var vtype = v.GetType(); + TYPE_SETTERS.TryGetValue(vtype, out setter); + + if (setter == null) + setter = GetValueForCustomClassObject; + + var bsonValue = setter(v); + return bsonValue; + } + + public static BsonDocument GetDocumentForCustomClassObject(object val) + { + var type = val.GetType(); + if ((type.IsClass || (type.IsValueType && !type.IsPrimitive)) && + !typeof (Array).IsAssignableFrom(type) && + !typeof (Enum).IsAssignableFrom(type)) + { + var bsonDocument = new BsonDocument(); + var classMap = BsonClassSerialization.LookupClassMap(type); + foreach (var member in classMap.AllMemberMaps) + { + var value = member.Value.Getter(val); + bsonDocument.Add(member.Value.ElementName, value); + } + + return bsonDocument; + } + + throw new Exception(string.Format("Type '{0}' not supported for custom class serialization", type.FullName)); + } + + public static BsonValue GetValueForCustomClassObject(object val) + { + return Create(GetDocumentForCustomClassObject(val)); + } + + public static BsonValue GetNull() + { + return new BsonValue(BsonType.NULL); + } + + public static BsonValue GetUndefined() + { + return new BsonValue(BsonType.UNKNOWN); + } + + public static BsonValue GetMaxKey() + { + return new BsonValue(BsonType.MAXKEY); + } + + public static BsonValue GetMinKey() + { + return new BsonValue(BsonType.MINKEY); } - public object Clone() { - return new BSONValue(this.BSONType, this.Key, this.Value); + public static BsonValue Create(BsonOid oid) + { + return new BsonValue(BsonType.OID, oid); } + + public static BsonValue Create(bool val) + { + return new BsonValue(BsonType.BOOL, val); + } + + public static BsonValue Create(int val) + { + return new BsonValue(BsonType.INT, val); + } + + public static BsonValue Create(long val) + { + return new BsonValue(BsonType.LONG, val); + } + + public static BsonValue Create(double val) + { + return new BsonValue(BsonType.DOUBLE, val); + } + + public static BsonValue Create(float val) + { + return new BsonValue(BsonType.DOUBLE, val); + } + + public static BsonValue Create(string val) + { + return new BsonValue(BsonType.STRING, val); + } + + public static BsonValue CreateCode(string val) + { + return new BsonValue(BsonType.CODE, val); + } + + public static BsonValue CreateSymbol(string val) + { + return new BsonValue(BsonType.SYMBOL, val); + } + + public static BsonValue Create(DateTime val) + { + return new BsonValue(BsonType.DATE, val); + } + + public static BsonValue GetRegexp(BsonRegexp val) + { + return new BsonValue(BsonType.REGEX, val); + } + + public static BsonValue GetBinData(BsonBinData val) + { + return new BsonValue(BsonType.BINDATA, val); + } + + public static BsonValue Create(BsonDocument val) + { + return new BsonValue(BsonType.OBJECT, val); + } + + public static BsonValue Create(BsonArray val) + { + return new BsonValue(BsonType.ARRAY, val); + } + + public static BsonValue Create(BsonTimestamp val) + { + return new BsonValue(BsonType.TIMESTAMP, val); + } + + public static BsonValue Create(BsonCodeWScope val) + { + return new BsonValue(BsonType.CODEWSCOPE, val); + } + + private static Dictionary> TYPE_SETTERS = new Dictionary> + { + {typeof(bool), v => Create((bool) v)}, + {typeof(byte), v => Create((int) v)}, + {typeof(sbyte), v => Create((int) v)}, + {typeof(ushort), v => Create((int) v)}, + {typeof(short), v => Create((int) v)}, + {typeof(uint), v => Create((int) v)}, + {typeof(int), v => Create((int) v)}, + {typeof(ulong), v => Create((long) v)}, + {typeof(long), v => Create((long) v)}, + {typeof(float), v => Create((float) v)}, + {typeof(double), v => Create((double) v)}, + {typeof(char), v => Create(v.ToString())}, + {typeof(string), v => Create((string) v)}, + {typeof(BsonOid), v => Create((BsonOid) v)}, + {typeof(BsonRegexp), v => GetRegexp((BsonRegexp) v)}, + {typeof(BsonValue), v => (BsonValue) v}, + {typeof(BsonTimestamp), v => Create((BsonTimestamp) v)}, + {typeof(BsonCodeWScope), v => Create((BsonCodeWScope) v)}, + {typeof(BsonBinData), v => GetBinData((BsonBinData) v)}, + {typeof(BsonDocument), v => Create((BsonDocument) v)}, + {typeof(BsonArray), v => Create((BsonArray) v)}, + {typeof(DateTime), v => Create((DateTime) v)}, + {typeof(BsonUndefined), v => GetUndefined()}, + {typeof(BsonNull), v => GetNull() }, + }; } } diff --git a/Ejdb.BSON/BSONValueWithKey.cs b/Ejdb.BSON/BSONValueWithKey.cs new file mode 100644 index 0000000..3fbd2db --- /dev/null +++ b/Ejdb.BSON/BSONValueWithKey.cs @@ -0,0 +1,32 @@ +// ============================================================================================ +// .NET API for EJDB database library http://ejdb.org +// Copyright (C) 2013-2014 Oliver Klemencic +// +// This file is part of EJDB. +// EJDB is free software; you can redistribute it and/or modify it under the terms of +// the GNU Lesser General Public License as published by the Free Software Foundation; either +// version 2.1 of the License or any later version. EJDB is distributed in the hope +// that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +// License for more details. +// You should have received a copy of the GNU Lesser General Public License along with EJDB; +// if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, +// Boston, MA 02111-1307 USA. +// ============================================================================================ + +namespace Ejdb.BSON +{ + public class BsonValueWithKey + { + public BsonValueWithKey(string key, BsonValue value, BsonType type) + { + Value = value.Value; + Key = key; + } + + public object Value { get; private set; } + + public string Key { get; private set; } + + } +} \ No newline at end of file diff --git a/Ejdb.BSON/BSONull.cs b/Ejdb.BSON/BsonNull.cs similarity index 87% rename from Ejdb.BSON/BSONull.cs rename to Ejdb.BSON/BsonNull.cs index 2e74028..48c6c03 100644 --- a/Ejdb.BSON/BSONull.cs +++ b/Ejdb.BSON/BsonNull.cs @@ -18,13 +18,13 @@ namespace Ejdb.BSON { [Serializable] - public sealed class BSONull : IBSONValue { + public sealed class BsonNull : IBsonValue { - public static BSONull VALUE = new BSONull(); + public static BsonNull VALUE = new BsonNull(); - public BSONType BSONType { + public BsonType BSONType { get { - return BSONType.NULL; + return BsonType.NULL; } } @@ -35,7 +35,7 @@ public override bool Equals(object obj) { if (ReferenceEquals(this, obj)) { return true; } - if (!(obj is BSONull)) { + if (!(obj is BsonNull)) { return false; } return true; @@ -46,7 +46,7 @@ public override int GetHashCode() { } public override string ToString() { - return "[BSONull]"; + return "[BsonNull]"; } } } diff --git a/Ejdb.BSON/BsonSerializationException.cs b/Ejdb.BSON/BsonSerializationException.cs new file mode 100644 index 0000000..c03e02c --- /dev/null +++ b/Ejdb.BSON/BsonSerializationException.cs @@ -0,0 +1,27 @@ +using System; +using System.Runtime.Serialization; + +namespace Ejdb.BSON +{ + [Serializable] + public class BsonSerializationException : Exception + { + public BsonSerializationException(string message, Exception innerException) + : base(message, innerException) + { + } + + public BsonSerializationException(string message) : base(message) + { + } + + public BsonSerializationException() + { + } + + protected BsonSerializationException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} \ No newline at end of file diff --git a/Ejdb.BSON/IBSONValue.cs b/Ejdb.BSON/IBSONValue.cs index 0c426f9..418bce1 100644 --- a/Ejdb.BSON/IBSONValue.cs +++ b/Ejdb.BSON/IBSONValue.cs @@ -19,12 +19,12 @@ namespace Ejdb.BSON { /// /// Base interface of any BSON complex values like Regexp or BSON Timestamp. /// - public interface IBSONValue { + public interface IBsonValue { /// /// BSON Type of complex value. /// /// The type of the BSON. - BSONType BSONType { get; } + BsonType BSONType { get; } } } diff --git a/Ejdb.BSON/Serialization/BsonClassMap.cs b/Ejdb.BSON/Serialization/BsonClassMap.cs new file mode 100644 index 0000000..b5f62f5 --- /dev/null +++ b/Ejdb.BSON/Serialization/BsonClassMap.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq.Expressions; +using System.Reflection; +using System.Runtime.Serialization; + +namespace Ejdb.BSON +{ + public class BsonClassMap + { + private readonly Dictionary _allMembersDictionary; + private Func _creator; + + public BsonClassMap(Type type) + { + ClassType = type; + _allMembersDictionary = _AutoMap(); + } + + private Dictionary _AutoMap() + { + var dictionary = new Dictionary(); + + foreach (var propertyInfo in ClassType.GetProperties(BindingFlags.Instance | BindingFlags.Public)) + { + if (propertyInfo.GetIndexParameters().Length != 0) // skip indexers + continue; + + var memberMap = new BsonMemberMap(propertyInfo); + dictionary.Add(memberMap.ElementName, memberMap); + } + + return dictionary; + } + + // public properties + /// + /// Gets all the member maps (including maps for inherited members). + /// + public Dictionary AllMemberMaps + { + get { return _allMembersDictionary; } + } + + public Func Creator + { + get + { + if (_creator == null) + _creator = _GetCreator(); + + return _creator; + } + } + + private Func _GetCreator() + { + Expression body; + var bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + var defaultConstructor = ClassType.GetConstructor(bindingFlags, null, new Type[0], null); + if (defaultConstructor != null) + { + // lambdaExpression = () => (object) new TClass() + body = Expression.New(defaultConstructor); + } + else + { + // lambdaExpression = () => FormatterServices.GetUninitializedObject(classType) + var getUnitializedObjectMethodInfo = typeof (FormatterServices).GetMethod("GetUninitializedObject", + BindingFlags.Public | BindingFlags.Static); + body = Expression.Call(getUnitializedObjectMethodInfo, Expression.Constant(ClassType)); + } + var lambdaExpression = Expression.Lambda>(body); + return lambdaExpression.Compile(); + } + + public Type ClassType { get; private set; } + } +} \ No newline at end of file diff --git a/Ejdb.BSON/Serialization/BsonClassSerialization.cs b/Ejdb.BSON/Serialization/BsonClassSerialization.cs new file mode 100644 index 0000000..1467aac --- /dev/null +++ b/Ejdb.BSON/Serialization/BsonClassSerialization.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Ejdb.BSON +{ + public class BsonClassSerialization + { + private static readonly Dictionary __classMaps = new Dictionary(); + private static readonly ReaderWriterLockSlim __configurationLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); + + /// + /// Looks up a class map (will AutoMap the class if no class map is registered). + /// + /// The class type. + /// The class map. + public static BsonClassMap LookupClassMap(Type classType) + { + if (classType == null) + throw new ArgumentNullException("classType"); + + __configurationLock.EnterReadLock(); + try + { + BsonClassMap classMap; + if (__classMaps.TryGetValue(classType, out classMap)) + return classMap; + } + finally + { + __configurationLock.ExitReadLock(); + } + + __configurationLock.EnterWriteLock(); + try + { + BsonClassMap classMap; + if (!__classMaps.TryGetValue(classType, out classMap)) + { + // automatically create a classMap for classType and register it + classMap = new BsonClassMap(classType); + _RegisterClassMap(classMap); + } + + return classMap; + } + finally + { + __configurationLock.ExitWriteLock(); + } + } + + + private static void _RegisterClassMap(BsonClassMap classMap) + { + if (classMap == null) + throw new ArgumentNullException("classMap"); + + __configurationLock.EnterWriteLock(); + try + { + __classMaps.Add(classMap.ClassType, classMap); + } + finally + { + __configurationLock.ExitWriteLock(); + } + } + } +} \ No newline at end of file diff --git a/Ejdb.BSON/Serialization/BsonMemberMap.cs b/Ejdb.BSON/Serialization/BsonMemberMap.cs new file mode 100644 index 0000000..80b963f --- /dev/null +++ b/Ejdb.BSON/Serialization/BsonMemberMap.cs @@ -0,0 +1,124 @@ +using System; +using System.Reflection; +using System.Linq.Expressions; + +namespace Ejdb.BSON +{ + public class BsonMemberMap + { + private Func _getter; + private Action _setter; + private readonly PropertyInfo _propertyInfo; + + // constructors + /// + /// Initializes a new instance of the BsonMemberMap class. + /// + /// The member info. + public BsonMemberMap(PropertyInfo propertyInfo) + { + _propertyInfo = propertyInfo; + ElementName = propertyInfo.Name; + } + + public bool IsWritable { get; private set; } + + /// + /// Gets the name of the element. + /// + public string ElementName { get; private set; } + + /// + /// Gets the getter function. + /// + public Func Getter + { + get + { + if (_getter == null) + _getter = _GetPropertyGetter(); + + return _getter; + } + } + + /// + /// Gets the setter function. + /// + public Action Setter + { + get + { + if (_setter == null) + _setter = _GetPropertySetter(); + + return _setter; + } + } + + + /// + /// Readonly indicates that the member is written to the database, but not read from the database. + /// + public bool IsReadOnly + { + get { return !_propertyInfo.CanWrite; } + } + + private Action _GetPropertySetter() + { + var setMethodInfo = _propertyInfo.GetSetMethod(true); + if (IsReadOnly) + { + var message = string.Format("The property '{0} {1}' of class '{2}' has no 'set' accessor. ", + _propertyInfo.PropertyType.FullName, _propertyInfo.Name, _propertyInfo.DeclaringType.FullName); + throw new BsonSerializationException(message); + } + + // lambdaExpression = (obj, value) => ((TClass) obj).SetMethod((TMember) value) + var objParameter = Expression.Parameter(typeof(object), "obj"); + var valueParameter = Expression.Parameter(typeof(object), "value"); + var lambdaExpression = Expression.Lambda>( + Expression.Call( + Expression.Convert(objParameter, _propertyInfo.DeclaringType), + setMethodInfo, + Expression.Convert(valueParameter, _propertyInfo.PropertyType) + ), + objParameter, + valueParameter + ); + + return lambdaExpression.Compile(); + } + + private Func _GetPropertyGetter() + { + if (_propertyInfo != null) + { + var getMethodInfo = _propertyInfo.GetGetMethod(true); + if (getMethodInfo == null) + { + var message = string.Format( + "The property '{0} {1}' of class '{2}' has no 'get' accessor.", + _propertyInfo.PropertyType.FullName, _propertyInfo.Name, _propertyInfo.DeclaringType.FullName); + throw new BsonSerializationException(message); + } + } + + // lambdaExpression = (obj) => (object) ((TClass) obj).Member + var objParameter = Expression.Parameter(typeof(object), "obj"); + var lambdaExpression = Expression.Lambda>( + Expression.Convert( + Expression.MakeMemberAccess( + Expression.Convert(objParameter, _propertyInfo.DeclaringType), + _propertyInfo + ), + typeof(object) + ), + objParameter + ); + + return lambdaExpression.Compile(); + } + } +} \ No newline at end of file diff --git a/Ejdb.DB/EJDB.cs b/Ejdb.DB/EJDB.cs index b2cc214..026da27 100644 --- a/Ejdb.DB/EJDB.cs +++ b/Ejdb.DB/EJDB.cs @@ -1,1074 +1,1126 @@ -// ============================================================================================ -// .NET API for EJDB database library http://ejdb.org -// Copyright (C) 2012-2013 Softmotions Ltd -// -// This file is part of EJDB. -// EJDB is free software; you can redistribute it and/or modify it under the terms of -// the GNU Lesser General Public License as published by the Free Software Foundation; either -// version 2.1 of the License or any later version. EJDB is distributed in the hope -// that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License for more details. -// You should have received a copy of the GNU Lesser General Public License along with EJDB; -// if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, -// Boston, MA 02111-1307 USA. -// ============================================================================================ -using System; -using System.Runtime.InteropServices; -using System.Text; -using Ejdb.BSON; -using Ejdb.Utils; - -namespace Ejdb.DB { - - /// - /// Corresponds to EJCOLLOPTS in ejdb.h - /// - public struct EJDBCollectionOptionsN { - [MarshalAs(UnmanagedType.U1)] - public bool large; - - [MarshalAs(UnmanagedType.U1)] - public bool compressed; - - public long records; - - public int cachedrecords; - } - - /// - /// EJDB database native wrapper. - /// - public class EJDB : IDisposable { - //.////////////////////////////////////////////////////////////////// - // Native open modes - //.////////////////////////////////////////////////////////////////// - /// - /// Open as a reader. - /// - public const int JBOREADER = 1 << 0; - - /// - /// Open as a writer. - /// - public const int JBOWRITER = 1 << 1; - - /// - /// Create if db file not exists. - /// - public const int JBOCREAT = 1 << 2; - - /// - /// Truncate db on open. - /// - public const int JBOTRUNC = 1 << 3; - - /// - /// Open without locking. - /// - public const int JBONOLCK = 1 << 4; - - /// - /// Lock without blocking. - /// - public const int JBOLCKNB = 1 << 5; - - /// - /// Synchronize every transaction with storage. - /// - public const int JBOTSYNC = 1 << 6; - - /// - /// The default open mode (JBOWRITER | JBOCREAT) - /// - public const int DEFAULT_OPEN_MODE = (JBOWRITER | JBOCREAT); - //.////////////////////////////////////////////////////////////////// - // Native index operations & types (ejdb.h) - //.////////////////////////////////////////////////////////////////// - /// - /// Drop index. - /// - const int JBIDXDROP = 1 << 0; - - /// - /// Drop index for all types. - /// - const int JBIDXDROPALL = 1 << 1; - - /// - /// Optimize indexes. - /// - const int JBIDXOP = 1 << 2; - - /// - /// Rebuild index. - /// - const int JBIDXREBLD = 1 << 3; - - /// - /// Number index. - /// - const int JBIDXNUM = 1 << 4; - - /// - /// String index. - /// - const int JBIDXSTR = 1 << 5; - - /// - /// Array token index. - /// - const int JBIDXARR = 1 << 6; - - /// - /// Case insensitive string index. - /// - const int JBIDXISTR = 1 << 7; - - /// - /// The EJDB library version - /// - static string _LIBVERSION; - - /// - /// The EJDB library version hex code. - /// - static long _LIBHEXVERSION; - - /// - /// Name if EJDB library - /// - #if EJDBDLL - public const string EJDB_LIB_NAME = "tcejdbdll"; -#else - public const string EJDB_LIB_NAME = "tcejdb"; - #endif - /// - /// Pointer to the native EJDB instance. - /// - IntPtr _db = IntPtr.Zero; - - bool _throwonfail = true; - //.////////////////////////////////////////////////////////////////// - // Native functions refs - //.////////////////////////////////////////////////////////////////// - - #region NativeRefs - - [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbnew", CallingConvention = CallingConvention.Cdecl)] - internal static extern IntPtr _ejdbnew(); - - [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbdel", CallingConvention = CallingConvention.Cdecl)] - internal static extern IntPtr _ejdbdel([In] IntPtr db); - - [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbopen", CallingConvention = CallingConvention.Cdecl)] - internal static extern bool _ejdbopen([In] IntPtr db, [In] IntPtr path, int mode); - - internal static bool _ejdbopen(IntPtr db, string path, int mode) { - IntPtr pptr = Native.NativeUtf8FromString(path); //UnixMarshal.StringToHeap(path, Encoding.UTF8); - try { - return _ejdbopen(db, pptr, mode); - } finally { - Marshal.FreeHGlobal(pptr); //UnixMarshal.FreeHeap(pptr); - } - } - - [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbclose", CallingConvention = CallingConvention.Cdecl)] - internal static extern bool _ejdbclose([In] IntPtr db); - - [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbisopen", CallingConvention = CallingConvention.Cdecl)] - internal static extern bool _ejdbisopen([In] IntPtr db); - - [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbecode", CallingConvention = CallingConvention.Cdecl)] - internal static extern int _ejdbecode([In] IntPtr db); - - [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdberrmsg", CallingConvention = CallingConvention.Cdecl)] - internal static extern IntPtr _ejdberrmsg(int ecode); - - [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbgetcoll", CallingConvention = CallingConvention.Cdecl)] - internal static extern IntPtr _ejdbgetcoll([In] IntPtr db, [In] IntPtr cname); - - internal static IntPtr _ejdbgetcoll(IntPtr db, string cname) { - IntPtr cptr = Native.NativeUtf8FromString(cname); //UnixMarshal.StringToHeap(cname, Encoding.UTF8); - try { - return _ejdbgetcoll(db, cptr); - } finally { - Marshal.FreeHGlobal(cptr); //UnixMarshal.FreeHeap(cptr); - } - } - - [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbcreatecoll", CallingConvention = CallingConvention.Cdecl)] - internal static extern IntPtr _ejdbcreatecoll([In] IntPtr db, [In] IntPtr cname, IntPtr opts); - - [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbcreatecoll", CallingConvention = CallingConvention.Cdecl)] - internal static extern IntPtr _ejdbcreatecoll([In] IntPtr db, [In] IntPtr cname, ref EJDBCollectionOptionsN opts); - - internal static IntPtr _ejdbcreatecoll(IntPtr db, String cname, EJDBCollectionOptionsN? opts) { - IntPtr cptr = Native.NativeUtf8FromString(cname);//UnixMarshal.StringToHeap(cname, Encoding.UTF8); - try { - if (opts == null) { - return _ejdbcreatecoll(db, cptr, IntPtr.Zero); - } else { - EJDBCollectionOptionsN nopts = (EJDBCollectionOptionsN) opts; - return _ejdbcreatecoll(db, cptr, ref nopts); - } - } finally { - Marshal.FreeHGlobal(cptr); //UnixMarshal.FreeHeap(cptr); - } - } - //EJDB_EXPORT bool ejdbrmcoll(EJDB *jb, const char *colname, bool unlinkfile); - [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbrmcoll", CallingConvention = CallingConvention.Cdecl)] - internal static extern bool _ejdbrmcoll([In] IntPtr db, [In] IntPtr cname, bool unlink); - - internal static bool _ejdbrmcoll(IntPtr db, string cname, bool unlink) { - IntPtr cptr = Native.NativeUtf8FromString(cname);//UnixMarshal.StringToHeap(cname, Encoding.UTF8); - try { - return _ejdbrmcoll(db, cptr, unlink); - } finally { - Marshal.FreeHGlobal(cptr); //UnixMarshal.FreeHeap(cptr); - } - } - //EJDB_EXPORT bson* ejdbcommand2(EJDB *jb, void *cmdbsondata); - [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbcommand2", CallingConvention = CallingConvention.Cdecl)] - internal static extern IntPtr _ejdbcommand([In] IntPtr db, [In] byte[] cmd); - //EJDB_EXPORT bool ejdbsavebson3(EJCOLL *jcoll, void *bsdata, bson_oid_t *oid, bool merge); - [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbsavebson3", CallingConvention = CallingConvention.Cdecl)] - internal static extern bool _ejdbsavebson([In] IntPtr coll, [In] byte[] bsdata, [Out] byte[] oid, [In] bool merge); - //EJDB_EXPORT bson* ejdbloadbson(EJCOLL *coll, const bson_oid_t *oid); - [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbloadbson", CallingConvention = CallingConvention.Cdecl)] - internal static extern IntPtr _ejdbloadbson([In] IntPtr coll, [In] byte[] oid); - //EJDB_EXPORT const char* bson_data2(const bson *b, int *bsize); - [DllImport(EJDB_LIB_NAME, EntryPoint = "bson_data2", CallingConvention = CallingConvention.Cdecl)] - internal static extern IntPtr _bson_data2([In] IntPtr bsptr, out int size); - //EJDB_EXPORT void bson_del(bson *b); - [DllImport(EJDB_LIB_NAME, EntryPoint = "bson_del", CallingConvention = CallingConvention.Cdecl)] - internal static extern void _bson_del([In] IntPtr bsptr); - //EJDB_EXPORT bool ejdbrmbson(EJCOLL *coll, bson_oid_t *oid); - [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbrmbson", CallingConvention = CallingConvention.Cdecl)] - internal static extern bool _ejdbrmbson([In] IntPtr cptr, [In] byte[] oid); - //EJDB_EXPORT bool ejdbsyncdb(EJDB *jb) - [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbsyncdb", CallingConvention = CallingConvention.Cdecl)] - internal static extern bool _ejdbsyncdb([In] IntPtr db); - //EJDB_EXPORT bool ejdbsyncoll(EJDB *jb) - [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbsyncoll", CallingConvention = CallingConvention.Cdecl)] - internal static extern bool _ejdbsyncoll([In] IntPtr coll); - //EJDB_EXPORT bool ejdbsetindex(EJCOLL *coll, const char *ipath, int flags); - [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbsetindex", CallingConvention = CallingConvention.Cdecl)] - internal static extern bool _ejdbsetindex([In] IntPtr coll, [In] IntPtr ipathptr, int flags); - //EJDB_EXPORT bson* ejdbmeta(EJDB *jb) - [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbmeta", CallingConvention = CallingConvention.Cdecl)] - internal static extern IntPtr _ejdbmeta([In] IntPtr db); - //EJDB_EXPORT bool ejdbtranbegin(EJCOLL *coll); - [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbtranbegin", CallingConvention = CallingConvention.Cdecl)] - internal static extern bool _ejdbtranbegin([In] IntPtr coll); - //EJDB_EXPORT bool ejdbtrancommit(EJCOLL *coll); - [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbtrancommit", CallingConvention = CallingConvention.Cdecl)] - internal static extern bool _ejdbtrancommit([In] IntPtr coll); - //EJDB_EXPORT bool ejdbtranabort(EJCOLL *coll); - [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbtranabort", CallingConvention = CallingConvention.Cdecl)] - internal static extern bool _ejdbtranabort([In] IntPtr coll); - //EJDB_EXPORT bool ejdbtranstatus(EJCOLL *jcoll, bool *txactive); - [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbtranstatus", CallingConvention = CallingConvention.Cdecl)] - internal static extern bool _ejdbtranstatus([In] IntPtr coll, out bool txactive); - //EJDB_EXPORT const char *ejdbversion(); - [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbversion", CallingConvention = CallingConvention.Cdecl)] - internal static extern IntPtr _ejdbversion(); - //EJDB_EXPORT bson* json2bson(const char *jsonstr); - [DllImport(EJDB_LIB_NAME, EntryPoint = "json2bson", CallingConvention = CallingConvention.Cdecl)] - internal static extern IntPtr _json2bson([In] IntPtr jsonstr); - - internal static IntPtr _json2bson(string jsonstr) { - IntPtr jsonptr = Native.NativeUtf8FromString(jsonstr); - try { - return _json2bson(jsonptr); - } finally { - Marshal.FreeHGlobal(jsonptr); //UnixMarshal.FreeHeap(jsonptr); - } - } - - internal static bool _ejdbsetindex(IntPtr coll, string ipath, int flags) { - IntPtr ipathptr = Native.NativeUtf8FromString(ipath); //UnixMarshal.StringToHeap(ipath, Encoding.UTF8); - try { - return _ejdbsetindex(coll, ipathptr, flags); - } finally { - Marshal.FreeHGlobal(ipathptr); //UnixMarshal.FreeHeap(ipathptr); - } - } - - #endregion - - /// - /// If true will be thrown in the case of failed operation - /// otherwise method will return boolean status flag. Default value: true - /// - /// true if throw exception on fail; otherwise, false. - public bool ThrowExceptionOnFail { - get { - return _throwonfail; - } - set { - _throwonfail = value; - } - } - - /// - /// Gets the last DB error code or null if underlying native database object does not exist. - /// - /// The last DB error code. - public int? LastDBErrorCode { - get { - return (_db != IntPtr.Zero) ? (int?) _ejdbecode(_db) : null; - } - } - - /// - /// Gets the last DB error message or null if underlying native database object does not exist. - /// - public string LastDBErrorMsg { - get { - int? ecode = LastDBErrorCode; - if (ecode == null) { - return null; - } - return Native.StringFromNativeUtf8(_ejdberrmsg((int) ecode)); //UnixMarshal.PtrToString(_ejdberrmsg((int) ecode), Encoding.UTF8); - } - } - - /// - /// Gets a value indicating whether this EJDB databse is open. - /// - /// true if this instance is open; otherwise, false. - public bool IsOpen { - get { - return _ejdbisopen(_db); - } - } - - /// - /// Gets info of EJDB database itself and its collections. - /// - /// The DB meta. - public BSONDocument DBMeta { - get { - CheckDisposed(true); - //internal static extern IntPtr _ejdbmeta([In] IntPtr db); - IntPtr bsptr = _ejdbmeta(_db); - if (bsptr == IntPtr.Zero) { - throw new EJDBException(this); - } - try { - int size; - IntPtr bsdataptr = _bson_data2(bsptr, out size); - byte[] bsdata = new byte[size]; - Marshal.Copy(bsdataptr, bsdata, 0, bsdata.Length); - return new BSONDocument(bsdata); - } finally { - _bson_del(bsptr); - } - } - } - - /// - /// Gets the EJDB library version. - /// - /// The LIB version. - public static string LIBVersion { - get { - if (_LIBVERSION != null) { - return _LIBVERSION; - } - lock (typeof(EJDB)) { - if (_LIBVERSION != null) { - return _LIBVERSION; - } - IntPtr vres = _ejdbversion(); - if (vres == IntPtr.Zero) { - throw new Exception("Unable to get ejdb library version"); - } - _LIBVERSION = Native.StringFromNativeUtf8(vres); //UnixMarshal.PtrToString(vres, Encoding.UTF8); - } - return _LIBVERSION; - } - } - - /// - /// Gets the EJDB library hex encoded version. - /// - /// - /// E.g: for the version "1.1.13" return value will be: 0x1113 - /// - /// The lib hex version. - public static long LibHexVersion { - get { - if (_LIBHEXVERSION != 0) { - return _LIBHEXVERSION; - } - lock (typeof(EJDB)) { - if (_LIBHEXVERSION != 0) { - return _LIBHEXVERSION; - } - _LIBHEXVERSION = Convert.ToInt64("0x" + LIBVersion.Replace(".", ""), 16); - } - return _LIBHEXVERSION; - } - } - - /// - /// Initializes a new instance of the class. - /// - /// The main database file path. - /// Open mode. - public EJDB(string path, int omode = DEFAULT_OPEN_MODE) { - if (EJDB.LibHexVersion < 0x1113) { - throw new EJDBException("EJDB library version must be at least '1.1.13' or greater"); - } - bool rv; - _db = _ejdbnew(); - if (_db == IntPtr.Zero) { - throw new EJDBException("Unable to create ejdb instance"); - } - try { - rv = _ejdbopen(_db, path, omode); - } catch (Exception) { - Dispose(); - throw; - } - if (!rv) { - throw new EJDBException(this); - } - } - - ~EJDB() { - Dispose(); - } - - public void Dispose() { - if (_db != IntPtr.Zero) { - IntPtr db = _db; - _db = IntPtr.Zero; - if (db != IntPtr.Zero) { - _ejdbdel(db); - } - } - } - - /// - /// Automatically creates new collection if it does't exists. - /// - /// - /// Collection options copts are applied only for newly created collection. - /// For existing collections copts has no effect. - /// - /// false error ocurried. - /// Name of collection. - /// Collection options. - public bool EnsureCollection(string cname, EJDBCollectionOptionsN? copts = null) { - CheckDisposed(); - IntPtr cptr = _ejdbcreatecoll(_db, cname, copts); - bool rv = (cptr != IntPtr.Zero); - if (_throwonfail && !rv) { - throw new EJDBException(this); - } - return rv; - } - - /// - /// Removes collection indetified by cname. - /// - /// false, if error occurred. - /// Name of the collection. - /// If set to true then the collection data file will be removed. - public bool DropCollection(string cname, bool unlink = false) { - CheckDisposed(); - bool rv = _ejdbrmcoll(_db, cname, unlink); - if (_throwonfail && !rv) { - throw new EJDBException(this); - } - return rv; - } - - /// - /// Synchronize entire EJDB database and - /// all of its collections with storage. - /// - /// false, if error occurred. - public bool Sync() { - CheckDisposed(); - //internal static extern bool _ejdbsyncdb([In] IntPtr db); - bool rv = _ejdbsyncdb(_db); - if (_throwonfail && !rv) { - throw new EJDBException(this); - } - return rv; - } - - /// - /// Synchronize content of a EJDB collection database with the file on device. - /// - /// false, if error occurred. - /// Name of collection. - public bool SyncCollection(string cname) { - CheckDisposed(); - IntPtr cptr = _ejdbgetcoll(_db, cname); - if (cptr == IntPtr.Zero) { - return true; - } - //internal static extern bool _ejdbsyncoll([In] IntPtr coll); - bool rv = _ejdbsyncoll(cptr); - if (_throwonfail && !rv) { - throw new EJDBException(this); - } - return rv; - } - - /// - /// DROP indexes of all types for JSON field path. - /// - /// false, if error occurred. - /// Name of collection. - /// JSON indexed field path - public bool DropIndexes(string cname, string ipath) { - bool rv = IndexOperation(cname, ipath, JBIDXDROPALL); - if (_throwonfail && !rv) { - throw new EJDBException(this); - } - return rv; - } - - /// - /// OPTIMIZE indexes of all types for JSON field path. - /// - /// - /// Performs B+ tree index file optimization. - /// - /// false, if error occurred. - /// Name of collection. - /// JSON indexed field path - public bool OptimizeIndexes(string cname, string ipath) { - bool rv = IndexOperation(cname, ipath, JBIDXOP); - if (_throwonfail && !rv) { - throw new EJDBException(this); - } - return rv; - } - - /// - /// Ensure index presence of String type for JSON field path. - /// - /// false, if error occurred. - /// Name of collection. - /// JSON indexed field path - public bool EnsureStringIndex(string cname, string ipath) { - bool rv = IndexOperation(cname, ipath, JBIDXSTR); - if (_throwonfail && !rv) { - throw new EJDBException(this); - } - return rv; - } - - /// - /// Rebuild index of String type for JSON field path. - /// - /// false, if error occurred. - /// Name of collection. - /// JSON indexed field path - public bool RebuildStringIndex(string cname, string ipath) { - bool rv = IndexOperation(cname, ipath, JBIDXSTR | JBIDXREBLD); - if (_throwonfail && !rv) { - throw new EJDBException(this); - } - return rv; - } - - /// - /// Drop index of String type for JSON field path. - /// - /// false, if error occurred. - /// Name of collection. - /// JSON indexed field path - public bool DropStringIndex(string cname, string ipath) { - bool rv = IndexOperation(cname, ipath, JBIDXSTR | JBIDXDROP); - if (_throwonfail && !rv) { - throw new EJDBException(this); - } - return rv; - } - - /// - /// Ensure case insensitive String index for JSON field path. - /// - /// false, if error occurred. - /// Name of collection. - /// JSON indexed field path - public bool EnsureIStringIndex(string cname, string ipath) { - bool rv = IndexOperation(cname, ipath, JBIDXISTR); - if (_throwonfail && !rv) { - throw new EJDBException(this); - } - return rv; - } - - /// - /// Rebuild case insensitive String index for JSON field path. - /// - /// false, if error occurred. - /// Name of collection. - /// JSON indexed field path - public bool RebuildIStringIndex(string cname, string ipath) { - bool rv = IndexOperation(cname, ipath, JBIDXISTR | JBIDXREBLD); - if (_throwonfail && !rv) { - throw new EJDBException(this); - } - return rv; - } - - /// - /// Drop case insensitive String index for JSON field path. - /// - /// false, if error occurred. - /// Name of collection. - /// JSON indexed field path - public bool DropIStringIndex(string cname, string ipath) { - bool rv = IndexOperation(cname, ipath, JBIDXISTR | JBIDXDROP); - if (_throwonfail && !rv) { - throw new EJDBException(this); - } - return rv; - } - - /// - /// Ensure index presence of Number type for JSON field path. - /// - /// false, if error occurred. - /// Name of collection. - /// JSON indexed field path - public bool EnsureNumberIndex(string cname, string ipath) { - bool rv = IndexOperation(cname, ipath, JBIDXNUM); - if (_throwonfail && !rv) { - throw new EJDBException(this); - } - return rv; - } - - /// - /// Rebuild index of Number type for JSON field path. - /// - /// false, if error occurred. - /// Name of collection. - /// JSON indexed field path - public bool RebuildNumberIndex(string cname, string ipath) { - bool rv = IndexOperation(cname, ipath, JBIDXNUM | JBIDXREBLD); - if (_throwonfail && !rv) { - throw new EJDBException(this); - } - return rv; - } - - /// - /// Drop index of Number type for JSON field path. - /// - /// false, if error occurred. - /// Name of collection. - /// JSON indexed field path - public bool DropNumberIndex(string cname, string ipath) { - bool rv = IndexOperation(cname, ipath, JBIDXNUM | JBIDXDROP); - if (_throwonfail && !rv) { - throw new EJDBException(this); - } - return rv; - } - - /// - /// Ensure index presence of Array type for JSON field path. - /// - /// false, if error occurred. - /// Name of collection. - /// JSON indexed field path - public bool EnsureArrayIndex(string cname, string ipath) { - bool rv = IndexOperation(cname, ipath, JBIDXARR); - if (_throwonfail && !rv) { - throw new EJDBException(this); - } - return rv; - } - - /// - /// Rebuild index of Array type for JSON field path. - /// - /// false, if error occurred. - /// Name of collection. - /// JSON indexed field path - public bool RebuildArrayIndex(string cname, string ipath) { - bool rv = IndexOperation(cname, ipath, JBIDXARR | JBIDXREBLD); - if (_throwonfail && !rv) { - throw new EJDBException(this); - } - return rv; - } - - /// - /// Drop index of Array type for JSON field path. - /// - /// false, if error occurred. - /// Name of collection. - /// JSON indexed field path - public bool DropArrayIndex(string cname, string ipath) { - bool rv = IndexOperation(cname, ipath, JBIDXARR | JBIDXDROP); - if (_throwonfail && !rv) { - throw new EJDBException(this); - } - return rv; - } - - /// - /// Begin transaction for EJDB collection. - /// - /// true, if begin was transactioned, false otherwise. - /// Cname. - public bool TransactionBegin(string cname) { - CheckDisposed(); - IntPtr cptr = _ejdbgetcoll(_db, cname); - if (cptr == IntPtr.Zero) { - return true; - } - //internal static extern bool _ejdbtranbegin([In] IntPtr coll); - bool rv = _ejdbtranbegin(cptr); - if (_throwonfail && !rv) { - throw new EJDBException(this); - } - return rv; - } - - /// - /// Commit the transaction. - /// - /// false, if error occurred. - public bool TransactionCommit(string cname) { - CheckDisposed(); - IntPtr cptr = _ejdbgetcoll(_db, cname); - if (cptr == IntPtr.Zero) { - return true; - } - //internal static extern bool _ejdbtrancommit([In] IntPtr coll); - bool rv = _ejdbtrancommit(cptr); - if (_throwonfail && !rv) { - throw new EJDBException(this); - } - return rv; - } - - /// - /// Abort the transaction. - /// - /// false, if error occurred. - public bool AbortTransaction(string cname) { - CheckDisposed(); - IntPtr cptr = _ejdbgetcoll(_db, cname); - if (cptr == IntPtr.Zero) { - return true; - } - //internal static extern bool _ejdbtranabort([In] IntPtr coll); - bool rv = _ejdbtranabort(cptr); - if (_throwonfail && !rv) { - throw new EJDBException(this); - } - return rv; - } - - /// - /// Get the transaction status. - /// - /// false, if error occurred. - /// Name of collection. - /// Out parameter. It true transaction is active. - public bool TransactionStatus(string cname, out bool active) { - CheckDisposed(); - IntPtr cptr = _ejdbgetcoll(_db, cname); - if (cptr == IntPtr.Zero) { - active = false; - return true; - } - bool rv = _ejdbtranstatus(cptr, out active); - if (_throwonfail && !rv) { - throw new EJDBException(this); - } - return rv; - } - - public bool Save(string cname, params object[] docs) { - CheckDisposed(); - IntPtr cptr = _ejdbcreatecoll(_db, cname, null); - if (cptr == IntPtr.Zero) { - if (_throwonfail) { - throw new EJDBException(this); - } else { - return false; - } - } - foreach (var doc in docs) { - if (!Save(cptr, BSONDocument.ValueOf(doc), false)) { - if (_throwonfail) { - throw new EJDBException(this); - } else { - return false; - } - } - } - return true; - } - - /// - /// Executes EJDB command. - /// - /// - /// Supported commands: - /// - /// 1) Exports database collections data. See ejdbexport() method. - /// - /// "export" : { - /// "path" : string, //Exports database collections data - /// "cnames" : [string array]|null, //List of collection names to export - /// "mode" : int|null //Values: null|`JBJSONEXPORT` See ejdb.h#ejdbexport() method - /// } - /// - /// Command response: - /// { - /// "log" : string, //Diagnostic log about executing this command - /// "error" : string|null, //ejdb error message - /// "errorCode" : int|0, //ejdb error code - /// } - /// - /// 2) Imports previously exported collections data into ejdb. - /// - /// "import" : { - /// "path" : string //The directory path in which data resides - /// "cnames" : [string array]|null, //List of collection names to import - /// "mode" : int|null //Values: null|`JBIMPORTUPDATE`|`JBIMPORTREPLACE` See ejdb.h#ejdbimport() method - /// } - /// - /// Command response: - /// { - /// "log" : string, //Diagnostic log about executing this command - /// "error" : string|null, //ejdb error message - /// "errorCode" : int|0, //ejdb error code - /// } - /// - /// Command object - /// Command response. - public BSONDocument Command(BSONDocument cmd) { - CheckDisposed(); - byte[] cmdata = cmd.ToByteArray(); - //internal static extern IntPtr _ejdbcommand([In] IntPtr db, [In] byte[] cmd); - IntPtr cmdret = _ejdbcommand(_db, cmdata); - if (cmdret == IntPtr.Zero) { - return null; - } - byte[] bsdata = BsonPtrIntoByteArray(cmdret); - if (bsdata.Length == 0) { - return null; - } - BSONIterator it = new BSONIterator(bsdata); - return it.ToBSONDocument(); - } - - /// - /// Save the BSON document doc into the collection. - /// - /// Name of collection. - /// BSON documents to save. - /// True on success. - public bool Save(string cname, params BSONDocument[] docs) { - CheckDisposed(); - IntPtr cptr = _ejdbcreatecoll(_db, cname, null); - if (cptr == IntPtr.Zero) { - if (_throwonfail) { - throw new EJDBException(this); - } else { - return false; - } - } - foreach (var doc in docs) { - if (!Save(cptr, doc, false)) { - if (_throwonfail) { - throw new EJDBException(this); - } else { - return false; - } - } - } - return true; - } - - /// - /// Save the BSON document doc into the collection. - /// And merge each doc object identified by _id with doc stored in DB. - /// - /// Name of collection. - /// BSON documents to save. - /// True on success. - public bool SaveMerge(string cname, params BSONDocument[] docs) { - CheckDisposed(); - IntPtr cptr = _ejdbcreatecoll(_db, cname, null); - if (cptr == IntPtr.Zero) { - if (_throwonfail) { - throw new EJDBException(this); - } else { - return false; - } - } - foreach (var doc in docs) { - if (!Save(cptr, doc, true)) { - if (_throwonfail) { - throw new EJDBException(this); - } else { - return false; - } - } - } - return true; - } - - bool Save(IntPtr cptr, BSONDocument doc, bool merge) { - bool rv; - BSONValue bv = doc.GetBSONValue("_id"); - byte[] bsdata = doc.ToByteArray(); - byte[] oiddata = new byte[12]; - //static extern bool _ejdbsavebson([In] IntPtr coll, [In] byte[] bsdata, [Out] byte[] oid, bool merge); - rv = _ejdbsavebson(cptr, bsdata, oiddata, merge); - if (rv && bv == null) { - doc.SetOID("_id", new BSONOid(oiddata)); - } - if (_throwonfail && !rv) { - throw new EJDBException(this); - } - return rv; - } - - /// - /// Loads JSON object identified by OID from the collection. - /// - /// - /// Returns null if object is not found. - /// - /// Cname. - /// Oid. - public BSONIterator Load(string cname, BSONOid oid) { - CheckDisposed(); - IntPtr cptr = _ejdbgetcoll(_db, cname); - if (cptr == IntPtr.Zero) { - return null; - } - //static extern IntPtr _ejdbloadbson([In] IntPtr coll, [In] byte[] oid); - byte[] bsdata = BsonPtrIntoByteArray(_ejdbloadbson(cptr, oid.ToBytes())); - if (bsdata.Length == 0) { - return null; - } - return new BSONIterator(bsdata); - } - - /// - /// Removes stored objects from the collection. - /// - /// Name of collection. - /// Object identifiers. - public bool Remove(string cname, params BSONOid[] oids) { - CheckDisposed(); - IntPtr cptr = _ejdbgetcoll(_db, cname); - if (cptr == IntPtr.Zero) { - return true; - } - //internal static extern bool _ejdbrmbson([In] IntPtr cptr, [In] byte[] oid); - foreach (var oid in oids) { - if (!_ejdbrmbson(cptr, oid.ToBytes())) { - if (_throwonfail) { - throw new EJDBException(this); - } else { - return false; - } - } - } - return true; - } - - /// - /// Creates the query. - /// - /// The query object. - /// BSON query spec. - /// Name of the collection used by default. - public EJDBQuery CreateQuery(object qv = null, string defaultcollection = null) { - CheckDisposed(); - return new EJDBQuery(this, BSONDocument.ValueOf(qv), defaultcollection); - } - - public EJDBQuery CreateQueryFor(string defaultcollection) { - CheckDisposed(); - return new EJDBQuery(this, new BSONDocument(), defaultcollection); - } - - /// - /// Convert JSON string into BSONDocument. - /// Returns `null` if conversion failed. - /// - /// The BSONDocument instance on success. - /// JSON string - public BSONDocument Json2Bson(string json) { - IntPtr bsonret = _json2bson(json); - if (bsonret == IntPtr.Zero) { - return null; - } - byte[] bsdata = BsonPtrIntoByteArray(bsonret); - if (bsdata.Length == 0) { - return null; - } - BSONIterator it = new BSONIterator(bsdata); - return it.ToBSONDocument(); - } - - //.////////////////////////////////////////////////////////////////// - // Private staff // - //.////////////////////////////////////////////////////////////////// - internal IntPtr DBPtr { - get { - CheckDisposed(); - return _db; - } - } - - byte[] BsonPtrIntoByteArray(IntPtr bsptr, bool deletebsptr = true) { - if (bsptr == IntPtr.Zero) { - return new byte[0]; - } - int size; - IntPtr bsdataptr = _bson_data2(bsptr, out size); - byte[] bsdata = new byte[size]; - Marshal.Copy(bsdataptr, bsdata, 0, bsdata.Length); - if (deletebsptr) { - _bson_del(bsptr); - } - return bsdata; - } - - bool IndexOperation(string cname, string ipath, int flags) { - CheckDisposed(true); - IntPtr cptr = _ejdbgetcoll(_db, cname); - if (cptr == IntPtr.Zero) { - return true; - } - //internal static bool _ejdbsetindex(IntPtr coll, string ipath, int flags) - return _ejdbsetindex(cptr, ipath, flags); - } - - internal void CheckDisposed(bool checkopen = false) { - if (_db == IntPtr.Zero) { - throw new ObjectDisposedException("Database is disposed"); - } - if (checkopen) { - if (!IsOpen) { - throw new ObjectDisposedException("Operation on closed EJDB instance"); - } - } - } - } -} - +// ============================================================================================ +// .NET API for EJDB database library http://ejdb.org +// Copyright (C) 2012-2013 Softmotions Ltd +// +// This file is part of EJDB. +// EJDB is free software; you can redistribute it and/or modify it under the terms of +// the GNU Lesser General Public License as published by the Free Software Foundation; either +// version 2.1 of the License or any later version. EJDB is distributed in the hope +// that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +// License for more details. +// You should have received a copy of the GNU Lesser General Public License along with EJDB; +// if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, +// Boston, MA 02111-1307 USA. +// ============================================================================================ +using System; +using System.Runtime.InteropServices; +using System.Text; +using Ejdb.BSON; +using Ejdb.Utils; + +namespace Ejdb.DB { + + /// + /// Corresponds to EJCOLLOPTS in ejdb.h + /// + public struct EJDBCollectionOptionsN { + [MarshalAs(UnmanagedType.U1)] + public bool large; + + [MarshalAs(UnmanagedType.U1)] + public bool compressed; + + public long records; + + public int cachedrecords; + } + + /// + /// EJDB database native wrapper. + /// + public class EJDB : IDisposable { + //.////////////////////////////////////////////////////////////////// + // Native open modes + //.////////////////////////////////////////////////////////////////// + /// + /// Open as a reader. + /// + public const int JBOREADER = 1 << 0; + + /// + /// Open as a writer. + /// + public const int JBOWRITER = 1 << 1; + + /// + /// CreateOid if db file not exists. + /// + public const int JBOCREAT = 1 << 2; + + /// + /// Truncate db on open. + /// + public const int JBOTRUNC = 1 << 3; + + /// + /// Open without locking. + /// + public const int JBONOLCK = 1 << 4; + + /// + /// Lock without blocking. + /// + public const int JBOLCKNB = 1 << 5; + + /// + /// Synchronize every transaction with storage. + /// + public const int JBOTSYNC = 1 << 6; + + /// + /// The default open mode (JBOWRITER | JBOCREAT) + /// + public const int DEFAULT_OPEN_MODE = (JBOWRITER | JBOCREAT); + //.////////////////////////////////////////////////////////////////// + // Native index operations & types (ejdb.h) + //.////////////////////////////////////////////////////////////////// + /// + /// Drop index. + /// + const int JBIDXDROP = 1 << 0; + + /// + /// Drop index for all types. + /// + const int JBIDXDROPALL = 1 << 1; + + /// + /// Optimize indexes. + /// + const int JBIDXOP = 1 << 2; + + /// + /// Rebuild index. + /// + const int JBIDXREBLD = 1 << 3; + + /// + /// Number index. + /// + const int JBIDXNUM = 1 << 4; + + /// + /// String index. + /// + const int JBIDXSTR = 1 << 5; + + /// + /// Array token index. + /// + const int JBIDXARR = 1 << 6; + + /// + /// Case insensitive string index. + /// + const int JBIDXISTR = 1 << 7; + + /// + /// The EJDB library version + /// + static string _LIBVERSION; + + /// + /// The EJDB library version hex code. + /// + static long _LIBHEXVERSION; + + /// + /// Name if EJDB library + /// + #if EJDBDLL + public const string EJDB_LIB_NAME = "tcejdbdll"; +#else + public const string EJDB_LIB_NAME = "tcejdb"; + #endif + /// + /// Pointer to the native EJDB instance. + /// + IntPtr _db = IntPtr.Zero; + + bool _throwonfail = true; + //.////////////////////////////////////////////////////////////////// + // Native functions refs + //.////////////////////////////////////////////////////////////////// + + #region NativeRefs + + [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbnew", CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr _ejdbnew(); + + [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbdel", CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr _ejdbdel([In] IntPtr db); + + [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbopen", CallingConvention = CallingConvention.Cdecl)] + internal static extern bool _ejdbopen([In] IntPtr db, [In] IntPtr path, int mode); + + internal static bool _ejdbopen(IntPtr db, string path, int mode) { + IntPtr pptr = Native.NativeUtf8FromString(path); //UnixMarshal.StringToHeap(path, Encoding.UTF8); + try { + return _ejdbopen(db, pptr, mode); + } finally { + Marshal.FreeHGlobal(pptr); //UnixMarshal.FreeHeap(pptr); + } + } + + [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbclose", CallingConvention = CallingConvention.Cdecl)] + internal static extern bool _ejdbclose([In] IntPtr db); + + [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbisopen", CallingConvention = CallingConvention.Cdecl)] + internal static extern bool _ejdbisopen([In] IntPtr db); + + [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbecode", CallingConvention = CallingConvention.Cdecl)] + internal static extern int _ejdbecode([In] IntPtr db); + + [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdberrmsg", CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr _ejdberrmsg(int ecode); + + [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbgetcoll", CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr _ejdbgetcoll([In] IntPtr db, [In] IntPtr cname); + + internal static IntPtr _ejdbgetcoll(IntPtr db, string cname) { + IntPtr cptr = Native.NativeUtf8FromString(cname); //UnixMarshal.StringToHeap(cname, Encoding.UTF8); + try { + return _ejdbgetcoll(db, cptr); + } finally { + Marshal.FreeHGlobal(cptr); //UnixMarshal.FreeHeap(cptr); + } + } + + [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbcreatecoll", CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr _ejdbcreatecoll([In] IntPtr db, [In] IntPtr cname, IntPtr opts); + + [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbcreatecoll", CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr _ejdbcreatecoll([In] IntPtr db, [In] IntPtr cname, ref EJDBCollectionOptionsN opts); + + internal static IntPtr _ejdbcreatecoll(IntPtr db, String cname, EJDBCollectionOptionsN? opts) { + IntPtr cptr = Native.NativeUtf8FromString(cname);//UnixMarshal.StringToHeap(cname, Encoding.UTF8); + try { + if (opts == null) { + return _ejdbcreatecoll(db, cptr, IntPtr.Zero); + } else { + EJDBCollectionOptionsN nopts = (EJDBCollectionOptionsN) opts; + return _ejdbcreatecoll(db, cptr, ref nopts); + } + } finally { + Marshal.FreeHGlobal(cptr); //UnixMarshal.FreeHeap(cptr); + } + } + //EJDB_EXPORT bool ejdbrmcoll(EJDB *jb, const char *colname, bool unlinkfile); + [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbrmcoll", CallingConvention = CallingConvention.Cdecl)] + internal static extern bool _ejdbrmcoll([In] IntPtr db, [In] IntPtr cname, bool unlink); + + internal static bool _ejdbrmcoll(IntPtr db, string cname, bool unlink) { + IntPtr cptr = Native.NativeUtf8FromString(cname);//UnixMarshal.StringToHeap(cname, Encoding.UTF8); + try { + return _ejdbrmcoll(db, cptr, unlink); + } finally { + Marshal.FreeHGlobal(cptr); //UnixMarshal.FreeHeap(cptr); + } + } + + [DllImport(EJDB_LIB_NAME, EntryPoint = "bson_oid_gen", CallingConvention = CallingConvention.Cdecl)] + internal static extern void _bson_oid_gen([Out] byte[] oid); + + //EJDB_EXPORT bson* ejdbcommand2(EJDB *jb, void *cmdbsondata); + [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbcommand2", CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr _ejdbcommand([In] IntPtr db, [In] byte[] cmd); + //EJDB_EXPORT bool ejdbsavebson3(EJCOLL *jcoll, void *bsdata, bson_oid_t *oid, bool merge); + [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbsavebson3", CallingConvention = CallingConvention.Cdecl)] + internal static extern bool _ejdbsavebson([In] IntPtr coll, [In] byte[] bsdata, [Out] byte[] oid, [In] bool merge); + //EJDB_EXPORT bson* ejdbloadbson(EJCOLL *coll, const bson_oid_t *oid); + [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbloadbson", CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr _ejdbloadbson([In] IntPtr coll, [In] byte[] oid); + //EJDB_EXPORT const char* bson_data2(const bson *b, int *bsize); + [DllImport(EJDB_LIB_NAME, EntryPoint = "bson_data2", CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr _bson_data2([In] IntPtr bsptr, out int size); + //EJDB_EXPORT void bson_del(bson *b); + [DllImport(EJDB_LIB_NAME, EntryPoint = "bson_del", CallingConvention = CallingConvention.Cdecl)] + internal static extern void _bson_del([In] IntPtr bsptr); + //EJDB_EXPORT bool ejdbrmbson(EJCOLL *coll, bson_oid_t *oid); + [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbrmbson", CallingConvention = CallingConvention.Cdecl)] + internal static extern bool _ejdbrmbson([In] IntPtr cptr, [In] byte[] oid); + //EJDB_EXPORT bool ejdbsyncdb(EJDB *jb) + [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbsyncdb", CallingConvention = CallingConvention.Cdecl)] + internal static extern bool _ejdbsyncdb([In] IntPtr db); + //EJDB_EXPORT bool ejdbsyncoll(EJDB *jb) + [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbsyncoll", CallingConvention = CallingConvention.Cdecl)] + internal static extern bool _ejdbsyncoll([In] IntPtr coll); + //EJDB_EXPORT bool ejdbsetindex(EJCOLL *coll, const char *ipath, int flags); + [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbsetindex", CallingConvention = CallingConvention.Cdecl)] + internal static extern bool _ejdbsetindex([In] IntPtr coll, [In] IntPtr ipathptr, int flags); + //EJDB_EXPORT bson* ejdbmeta(EJDB *jb) + [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbmeta", CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr _ejdbmeta([In] IntPtr db); + //EJDB_EXPORT bool ejdbtranbegin(EJCOLL *coll); + [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbtranbegin", CallingConvention = CallingConvention.Cdecl)] + internal static extern bool _ejdbtranbegin([In] IntPtr coll); + //EJDB_EXPORT bool ejdbtrancommit(EJCOLL *coll); + [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbtrancommit", CallingConvention = CallingConvention.Cdecl)] + internal static extern bool _ejdbtrancommit([In] IntPtr coll); + //EJDB_EXPORT bool ejdbtranabort(EJCOLL *coll); + [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbtranabort", CallingConvention = CallingConvention.Cdecl)] + internal static extern bool _ejdbtranabort([In] IntPtr coll); + //EJDB_EXPORT bool ejdbtranstatus(EJCOLL *jcoll, bool *txactive); + [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbtranstatus", CallingConvention = CallingConvention.Cdecl)] + internal static extern bool _ejdbtranstatus([In] IntPtr coll, out bool txactive); + //EJDB_EXPORT const char *ejdbversion(); + [DllImport(EJDB_LIB_NAME, EntryPoint = "ejdbversion", CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr _ejdbversion(); + //EJDB_EXPORT bson* json2bson(const char *jsonstr); + [DllImport(EJDB_LIB_NAME, EntryPoint = "json2bson", CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr _json2bson([In] IntPtr jsonstr); + + internal static IntPtr _json2bson(string jsonstr) { + IntPtr jsonptr = Native.NativeUtf8FromString(jsonstr); + try { + return _json2bson(jsonptr); + } finally { + Marshal.FreeHGlobal(jsonptr); //UnixMarshal.FreeHeap(jsonptr); + } + } + + internal static bool _ejdbsetindex(IntPtr coll, string ipath, int flags) { + IntPtr ipathptr = Native.NativeUtf8FromString(ipath); //UnixMarshal.StringToHeap(ipath, Encoding.UTF8); + try { + return _ejdbsetindex(coll, ipathptr, flags); + } finally { + Marshal.FreeHGlobal(ipathptr); //UnixMarshal.FreeHeap(ipathptr); + } + } + + #endregion + + /// + /// If true will be thrown in the case of failed operation + /// otherwise method will return boolean status flag. Default value: true + /// + /// true if throw exception on fail; otherwise, false. + public bool ThrowExceptionOnFail { + get { + return _throwonfail; + } + set { + _throwonfail = value; + } + } + + /// + /// Gets the last DB error code or null if underlying native database object does not exist. + /// + /// The last DB error code. + public int? LastDBErrorCode { + get { + return (_db != IntPtr.Zero) ? (int?) _ejdbecode(_db) : null; + } + } + + /// + /// Gets the last DB error message or null if underlying native database object does not exist. + /// + public string LastDBErrorMsg { + get { + int? ecode = LastDBErrorCode; + if (ecode == null) { + return null; + } + return Native.StringFromNativeUtf8(_ejdberrmsg((int) ecode)); //UnixMarshal.PtrToString(_ejdberrmsg((int) ecode), Encoding.UTF8); + } + } + + /// + /// Gets a value indicating whether this EJDB databse is open. + /// + /// true if this instance is open; otherwise, false. + public bool IsOpen { + get { + return _ejdbisopen(_db); + } + } + + /// + /// Gets info of EJDB database itself and its collections. + /// + /// The DB meta. + public BsonDocument DBMeta { + get { + CheckDisposed(true); + //internal static extern IntPtr _ejdbmeta([In] IntPtr db); + IntPtr bsptr = _ejdbmeta(_db); + if (bsptr == IntPtr.Zero) { + throw new EJDBException(this); + } + try { + int size; + IntPtr bsdataptr = _bson_data2(bsptr, out size); + byte[] bsdata = new byte[size]; + Marshal.Copy(bsdataptr, bsdata, 0, bsdata.Length); + return new BsonDocument(bsdata); + } finally { + _bson_del(bsptr); + } + } + } + + /// + /// Gets the EJDB library version. + /// + /// The LIB version. + public static string LIBVersion { + get { + if (_LIBVERSION != null) { + return _LIBVERSION; + } + lock (typeof(EJDB)) { + if (_LIBVERSION != null) { + return _LIBVERSION; + } + IntPtr vres = _ejdbversion(); + if (vres == IntPtr.Zero) { + throw new Exception("Unable to get ejdb library version"); + } + _LIBVERSION = Native.StringFromNativeUtf8(vres); //UnixMarshal.PtrToString(vres, Encoding.UTF8); + } + return _LIBVERSION; + } + } + + /// + /// Gets the EJDB library hex encoded version. + /// + /// + /// E.g: for the version "1.1.13" return value will be: 0x1113 + /// + /// The lib hex version. + public static long LibHexVersion { + get { + if (_LIBHEXVERSION != 0) { + return _LIBHEXVERSION; + } + lock (typeof(EJDB)) { + if (_LIBHEXVERSION != 0) { + return _LIBHEXVERSION; + } + _LIBHEXVERSION = Convert.ToInt64("0x" + LIBVersion.Replace(".", ""), 16); + } + return _LIBHEXVERSION; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The main database file path. + /// Open mode. + public EJDB(string path, int omode = DEFAULT_OPEN_MODE) { + if (EJDB.LibHexVersion < 0x1113) { + throw new EJDBException("EJDB library version must be at least '1.1.13' or greater"); + } + bool rv; + _db = _ejdbnew(); + if (_db == IntPtr.Zero) { + throw new EJDBException("Unable to create ejdb instance"); + } + try { + rv = _ejdbopen(_db, path, omode); + } catch (Exception) { + Dispose(); + throw; + } + if (!rv) { + throw new EJDBException(this); + } + } + + ~EJDB() { + Dispose(); + } + + public void Dispose() { + if (_db != IntPtr.Zero) { + IntPtr db = _db; + _db = IntPtr.Zero; + if (db != IntPtr.Zero) { + _ejdbdel(db); + } + } + } + + /// + /// Automatically creates new collection if it does't exists. + /// + /// + /// Collection options copts are applied only for newly created collection. + /// For existing collections copts has no effect. + /// + /// false error ocurried. + /// Name of collection. + /// Collection options. + public bool EnsureCollection(string cname, EJDBCollectionOptionsN? copts = null) { + CheckDisposed(); + IntPtr cptr = _ejdbcreatecoll(_db, cname, copts); + bool rv = (cptr != IntPtr.Zero); + if (_throwonfail && !rv) { + throw new EJDBException(this); + } + return rv; + } + + /// + /// Removes collection indetified by cname. + /// + /// false, if error occurred. + /// Name of the collection. + /// If set to true then the collection data file will be removed. + public bool DropCollection(string cname, bool unlink = false) { + CheckDisposed(); + bool rv = _ejdbrmcoll(_db, cname, unlink); + if (_throwonfail && !rv) { + throw new EJDBException(this); + } + return rv; + } + + /// + /// Synchronize entire EJDB database and + /// all of its collections with storage. + /// + /// false, if error occurred. + public bool Sync() { + CheckDisposed(); + //internal static extern bool _ejdbsyncdb([In] IntPtr db); + bool rv = _ejdbsyncdb(_db); + if (_throwonfail && !rv) { + throw new EJDBException(this); + } + return rv; + } + + /// + /// Synchronize content of a EJDB collection database with the file on device. + /// + /// false, if error occurred. + /// Name of collection. + public bool SyncCollection(string cname) { + CheckDisposed(); + IntPtr cptr = _ejdbgetcoll(_db, cname); + if (cptr == IntPtr.Zero) { + return true; + } + //internal static extern bool _ejdbsyncoll([In] IntPtr coll); + bool rv = _ejdbsyncoll(cptr); + if (_throwonfail && !rv) { + throw new EJDBException(this); + } + return rv; + } + + /// + /// DROP indexes of all types for JSON field path. + /// + /// false, if error occurred. + /// Name of collection. + /// JSON indexed field path + public bool DropIndexes(string cname, string ipath) { + bool rv = IndexOperation(cname, ipath, JBIDXDROPALL); + if (_throwonfail && !rv) { + throw new EJDBException(this); + } + return rv; + } + + /// + /// OPTIMIZE indexes of all types for JSON field path. + /// + /// + /// Performs B+ tree index file optimization. + /// + /// false, if error occurred. + /// Name of collection. + /// JSON indexed field path + public bool OptimizeIndexes(string cname, string ipath) { + bool rv = IndexOperation(cname, ipath, JBIDXOP); + if (_throwonfail && !rv) { + throw new EJDBException(this); + } + return rv; + } + + /// + /// Ensure index presence of String type for JSON field path. + /// + /// false, if error occurred. + /// Name of collection. + /// JSON indexed field path + public bool EnsureStringIndex(string cname, string ipath) { + bool rv = IndexOperation(cname, ipath, JBIDXSTR); + if (_throwonfail && !rv) { + throw new EJDBException(this); + } + return rv; + } + + /// + /// Rebuild index of String type for JSON field path. + /// + /// false, if error occurred. + /// Name of collection. + /// JSON indexed field path + public bool RebuildStringIndex(string cname, string ipath) { + bool rv = IndexOperation(cname, ipath, JBIDXSTR | JBIDXREBLD); + if (_throwonfail && !rv) { + throw new EJDBException(this); + } + return rv; + } + + /// + /// Drop index of String type for JSON field path. + /// + /// false, if error occurred. + /// Name of collection. + /// JSON indexed field path + public bool DropStringIndex(string cname, string ipath) { + bool rv = IndexOperation(cname, ipath, JBIDXSTR | JBIDXDROP); + if (_throwonfail && !rv) { + throw new EJDBException(this); + } + return rv; + } + + /// + /// Ensure case insensitive String index for JSON field path. + /// + /// false, if error occurred. + /// Name of collection. + /// JSON indexed field path + public bool EnsureIStringIndex(string cname, string ipath) { + bool rv = IndexOperation(cname, ipath, JBIDXISTR); + if (_throwonfail && !rv) { + throw new EJDBException(this); + } + return rv; + } + + /// + /// Rebuild case insensitive String index for JSON field path. + /// + /// false, if error occurred. + /// Name of collection. + /// JSON indexed field path + public bool RebuildIStringIndex(string cname, string ipath) { + bool rv = IndexOperation(cname, ipath, JBIDXISTR | JBIDXREBLD); + if (_throwonfail && !rv) { + throw new EJDBException(this); + } + return rv; + } + + /// + /// Drop case insensitive String index for JSON field path. + /// + /// false, if error occurred. + /// Name of collection. + /// JSON indexed field path + public bool DropIStringIndex(string cname, string ipath) { + bool rv = IndexOperation(cname, ipath, JBIDXISTR | JBIDXDROP); + if (_throwonfail && !rv) { + throw new EJDBException(this); + } + return rv; + } + + /// + /// Ensure index presence of Number type for JSON field path. + /// + /// false, if error occurred. + /// Name of collection. + /// JSON indexed field path + public bool EnsureNumberIndex(string cname, string ipath) { + bool rv = IndexOperation(cname, ipath, JBIDXNUM); + if (_throwonfail && !rv) { + throw new EJDBException(this); + } + return rv; + } + + /// + /// Rebuild index of Number type for JSON field path. + /// + /// false, if error occurred. + /// Name of collection. + /// JSON indexed field path + public bool RebuildNumberIndex(string cname, string ipath) { + bool rv = IndexOperation(cname, ipath, JBIDXNUM | JBIDXREBLD); + if (_throwonfail && !rv) { + throw new EJDBException(this); + } + return rv; + } + + /// + /// Drop index of Number type for JSON field path. + /// + /// false, if error occurred. + /// Name of collection. + /// JSON indexed field path + public bool DropNumberIndex(string cname, string ipath) { + bool rv = IndexOperation(cname, ipath, JBIDXNUM | JBIDXDROP); + if (_throwonfail && !rv) { + throw new EJDBException(this); + } + return rv; + } + + /// + /// Ensure index presence of Array type for JSON field path. + /// + /// false, if error occurred. + /// Name of collection. + /// JSON indexed field path + public bool EnsureArrayIndex(string cname, string ipath) { + bool rv = IndexOperation(cname, ipath, JBIDXARR); + if (_throwonfail && !rv) { + throw new EJDBException(this); + } + return rv; + } + + /// + /// Rebuild index of Array type for JSON field path. + /// + /// false, if error occurred. + /// Name of collection. + /// JSON indexed field path + public bool RebuildArrayIndex(string cname, string ipath) { + bool rv = IndexOperation(cname, ipath, JBIDXARR | JBIDXREBLD); + if (_throwonfail && !rv) { + throw new EJDBException(this); + } + return rv; + } + + /// + /// Drop index of Array type for JSON field path. + /// + /// false, if error occurred. + /// Name of collection. + /// JSON indexed field path + public bool DropArrayIndex(string cname, string ipath) { + bool rv = IndexOperation(cname, ipath, JBIDXARR | JBIDXDROP); + if (_throwonfail && !rv) { + throw new EJDBException(this); + } + return rv; + } + + /// + /// Begin transaction for EJDB collection. + /// + /// true, if begin was transactioned, false otherwise. + /// Cname. + public bool TransactionBegin(string cname) { + CheckDisposed(); + IntPtr cptr = _ejdbgetcoll(_db, cname); + if (cptr == IntPtr.Zero) { + return true; + } + //internal static extern bool _ejdbtranbegin([In] IntPtr coll); + bool rv = _ejdbtranbegin(cptr); + if (_throwonfail && !rv) { + throw new EJDBException(this); + } + return rv; + } + + /// + /// Commit the transaction. + /// + /// false, if error occurred. + public bool TransactionCommit(string cname) { + CheckDisposed(); + IntPtr cptr = _ejdbgetcoll(_db, cname); + if (cptr == IntPtr.Zero) { + return true; + } + //internal static extern bool _ejdbtrancommit([In] IntPtr coll); + bool rv = _ejdbtrancommit(cptr); + if (_throwonfail && !rv) { + throw new EJDBException(this); + } + return rv; + } + + /// + /// Abort the transaction. + /// + /// false, if error occurred. + public bool AbortTransaction(string cname) { + CheckDisposed(); + IntPtr cptr = _ejdbgetcoll(_db, cname); + if (cptr == IntPtr.Zero) { + return true; + } + //internal static extern bool _ejdbtranabort([In] IntPtr coll); + bool rv = _ejdbtranabort(cptr); + if (_throwonfail && !rv) { + throw new EJDBException(this); + } + return rv; + } + + /// + /// Get the transaction status. + /// + /// false, if error occurred. + /// Name of collection. + /// Out parameter. It true transaction is active. + public bool TransactionStatus(string cname, out bool active) { + CheckDisposed(); + IntPtr cptr = _ejdbgetcoll(_db, cname); + if (cptr == IntPtr.Zero) { + active = false; + return true; + } + bool rv = _ejdbtranstatus(cptr, out active); + if (_throwonfail && !rv) { + throw new EJDBException(this); + } + return rv; + } + + public static BsonOid GenerateId() + { + byte[] oiddata = new byte[12]; + _bson_oid_gen(oiddata); + return new BsonOid(oiddata); + } + + public bool Save(string cname, params object[] docs) { + CheckDisposed(); + IntPtr cptr = _ejdbcreatecoll(_db, cname, null); + if (cptr == IntPtr.Zero) { + if (_throwonfail) { + throw new EJDBException(this); + } else { + return false; + } + } + foreach (var doc in docs) { + if (!Save(cptr, BsonDocument.ValueOf(doc), false)) { + if (_throwonfail) { + throw new EJDBException(this); + } else { + return false; + } + } + } + return true; + } + + /// + /// Executes EJDB command. + /// + /// + /// Supported commands: + /// + /// 1) Exports database collections data. See ejdbexport() method. + /// + /// "export" : { + /// "path" : string, //Exports database collections data + /// "cnames" : [string array]|null, //List of collection names to export + /// "mode" : int|null //Values: null|`JBJSONEXPORT` See ejdb.h#ejdbexport() method + /// } + /// + /// Command response: + /// { + /// "log" : string, //Diagnostic log about executing this command + /// "error" : string|null, //ejdb error message + /// "errorCode" : int|0, //ejdb error code + /// } + /// + /// 2) Imports previously exported collections data into ejdb. + /// + /// "import" : { + /// "path" : string //The directory path in which data resides + /// "cnames" : [string array]|null, //List of collection names to import + /// "mode" : int|null //Values: null|`JBIMPORTUPDATE`|`JBIMPORTREPLACE` See ejdb.h#ejdbimport() method + /// } + /// + /// Command response: + /// { + /// "log" : string, //Diagnostic log about executing this command + /// "error" : string|null, //ejdb error message + /// "errorCode" : int|0, //ejdb error code + /// } + /// + /// Command object + /// Command response. + public BsonDocument Command(BsonDocument cmd) { + CheckDisposed(); + byte[] cmdata = cmd.ToByteArray(); + //internal static extern IntPtr _ejdbcommand([In] IntPtr db, [In] byte[] cmd); + IntPtr cmdret = _ejdbcommand(_db, cmdata); + if (cmdret == IntPtr.Zero) { + return null; + } + byte[] bsdata = BsonPtrIntoByteArray(cmdret); + if (bsdata.Length == 0) { + return null; + } + BsonIterator it = new BsonIterator(bsdata); + return it.ToBsonDocument(); + } + + /// + /// Save the BSON document doc into the collection. + /// + /// Name of collection. + /// BSON documents to save. + /// True on success. + public bool Save(string cname, params BsonDocument[] docs) { + CheckDisposed(); + IntPtr cptr = _ejdbcreatecoll(_db, cname, null); + if (cptr == IntPtr.Zero) { + if (_throwonfail) { + throw new EJDBException(this); + } else { + return false; + } + } + foreach (var doc in docs) { + if (!Save(cptr, doc, false)) { + if (_throwonfail) { + throw new EJDBException(this); + } else { + return false; + } + } + } + return true; + } + + /// + /// Save the BSON document doc into the collection. + /// And merge each doc object identified by _id with doc stored in DB. + /// + /// Name of collection. + /// BSON documents to save. + /// True on success. + public bool SaveMerge(string cname, params BsonDocument[] docs) { + CheckDisposed(); + IntPtr cptr = _ejdbcreatecoll(_db, cname, null); + if (cptr == IntPtr.Zero) { + if (_throwonfail) { + throw new EJDBException(this); + } else { + return false; + } + } + foreach (var doc in docs) { + if (!Save(cptr, doc, true)) { + if (_throwonfail) { + throw new EJDBException(this); + } else { + return false; + } + } + } + return true; + } + + bool Save(IntPtr cptr, BsonDocument doc, bool merge) { + bool rv; + BsonValue bv = doc.GetBsonValue(BsonConstants.Id); + byte[] bsdata = doc.ToByteArray(); + byte[] oiddata = new byte[12]; + //static extern bool _ejdbsavebson([In] IntPtr coll, [In] byte[] bsdata, [Out] byte[] oid, bool merge); + rv = _ejdbsavebson(cptr, bsdata, oiddata, merge); + if (rv && bv == null) { + doc.Add(BsonConstants.Id, BsonValue.Create(new BsonOid(oiddata))); + } + if (_throwonfail && !rv) { + throw new EJDBException(this); + } + return rv; + } + + /// + /// Loads JSON object identified by OID from the collection. + /// + /// + /// Returns null if object is not found. + /// + /// Cname. + /// Oid. + public BsonIterator Load(string cname, BsonOid oid) { + CheckDisposed(); + IntPtr cptr = _ejdbgetcoll(_db, cname); + if (cptr == IntPtr.Zero) { + return null; + } + //static extern IntPtr _ejdbloadbson([In] IntPtr coll, [In] byte[] oid); + byte[] bsdata = BsonPtrIntoByteArray(_ejdbloadbson(cptr, oid.ToByteArray())); + if (bsdata.Length == 0) { + return null; + } + return new BsonIterator(bsdata); + } + + /// + /// Removes stored objects from the collection. + /// + /// Name of collection. + /// Object identifiers. + public bool Remove(string collection, params BsonOid[] oids) + { + CheckDisposed(); + IntPtr cptr = _ejdbgetcoll(_db, collection); + if (cptr == IntPtr.Zero) { + return true; + } + //internal static extern bool _ejdbrmbson([In] IntPtr cptr, [In] byte[] oid); + foreach (var oid in oids) { + if (!_ejdbrmbson(cptr, oid.ToByteArray())) { + if (_throwonfail) { + throw new EJDBException(this); + } else { + return false; + } + } + } + return true; + } + + /// + /// Removes objects matching the given query from the collection. + /// + /// Name of collection. + /// The query which filters the collection. + public int Remove(string collection, IQuery query) + { + return Update(collection, query, new UpdateBuilder().DropAll()); + } + + /// + /// Creates the query. + /// + /// The query object. + /// BSON query spec. + /// Name of the collection used by default. + public EJDBQuery CreateQuery(object qv = null, string defaultcollection = null) { + CheckDisposed(); + return new EJDBQuery(this, BsonDocument.ValueOf(qv), defaultcollection); + } + + public EJDBQuery CreateQueryFor(string defaultcollection) { + CheckDisposed(); + return new EJDBQuery(this, new BsonDocument(), defaultcollection); + } + + public EJDBQCursor Find(string collection, IQuery query) + { + var ejdbQuery = new EJDBQuery(this, query.Document); + return ejdbQuery.Find(collection); + } + + public int Update(string collection, IQuery query, UpdateBuilder updateBuilder) + { + return Update(collection, query, updateBuilder.Document); + } + + public int Update(string collection, IQuery query, BsonDocument updateDocument) + { + var document = new BsonDocument(); + + if (query != null) + { + foreach (var field in query.Document) + document.Add(field.Key, field.Value); + } + + foreach (var field in updateDocument) + document.Add(field.Key, field.Value); + + var ejdbQuery = new EJDBQuery(this, document); + return ejdbQuery.Update(collection); + } + + /// + /// Convert JSON string into BsonDocument. + /// Returns `null` if conversion failed. + /// + /// The BsonDocument instance on success. + /// JSON string + public BsonDocument Json2Bson(string json) { + IntPtr bsonret = _json2bson(json); + if (bsonret == IntPtr.Zero) { + return null; + } + byte[] bsdata = BsonPtrIntoByteArray(bsonret); + if (bsdata.Length == 0) { + return null; + } + BsonIterator it = new BsonIterator(bsdata); + return it.ToBsonDocument(); + } + + //.////////////////////////////////////////////////////////////////// + // Private staff // + //.////////////////////////////////////////////////////////////////// + internal IntPtr DBPtr { + get { + CheckDisposed(); + return _db; + } + } + + + + byte[] BsonPtrIntoByteArray(IntPtr bsptr, bool deletebsptr = true) { + if (bsptr == IntPtr.Zero) { + return new byte[0]; + } + int size; + IntPtr bsdataptr = _bson_data2(bsptr, out size); + byte[] bsdata = new byte[size]; + Marshal.Copy(bsdataptr, bsdata, 0, bsdata.Length); + if (deletebsptr) { + _bson_del(bsptr); + } + return bsdata; + } + + bool IndexOperation(string cname, string ipath, int flags) { + CheckDisposed(true); + IntPtr cptr = _ejdbgetcoll(_db, cname); + if (cptr == IntPtr.Zero) { + return true; + } + //internal static bool _ejdbsetindex(IntPtr coll, string ipath, int flags) + return _ejdbsetindex(cptr, ipath, flags); + } + + internal void CheckDisposed(bool checkopen = false) { + if (_db == IntPtr.Zero) { + throw new ObjectDisposedException("Database is disposed"); + } + if (checkopen) { + if (!IsOpen) { + throw new ObjectDisposedException("Operation on closed EJDB instance"); + } + } + } + } +} + diff --git a/Ejdb.DB/EJDBQCursor.cs b/Ejdb.DB/EJDBQCursor.cs index e3c83eb..1ad34fd 100644 --- a/Ejdb.DB/EJDBQCursor.cs +++ b/Ejdb.DB/EJDBQCursor.cs @@ -23,7 +23,7 @@ namespace Ejdb.DB { /// /// Query result set container. /// - public class EJDBQCursor : IDisposable, IEnumerable { + public class EJDBQCursor : IDisposable, IEnumerable { //optional query execution log buffer string _log; //current cursor position @@ -66,7 +66,7 @@ internal EJDBQCursor(IntPtr qresptr, int len) { _len = len; } - public BSONIterator this[int idx] { + public BsonIterator this[int idx] { get { if (_qresptr == IntPtr.Zero || idx >= _len || idx < 0) { return null; @@ -79,19 +79,19 @@ public BSONIterator this[int idx] { } byte[] bsdata = new byte[size]; Marshal.Copy(bsdataptr, bsdata, 0, bsdata.Length); - return new BSONIterator(bsdata); + return new BsonIterator(bsdata); } } - public BSONIterator Next() { + public BsonIterator Next() { if (_qresptr == IntPtr.Zero || _pos >= _len) { return null; } return this[_pos++]; } - public IEnumerator GetEnumerator() { - BSONIterator it; + public IEnumerator GetEnumerator() { + BsonIterator it; while ((it = Next()) != null) { yield return it; } diff --git a/Ejdb.DB/EJDBQuery.cs b/Ejdb.DB/EJDBQuery.cs index 95c0bce..1da1717 100644 --- a/Ejdb.DB/EJDBQuery.cs +++ b/Ejdb.DB/EJDBQuery.cs @@ -56,7 +56,7 @@ public class EJDBQuery : IDisposable { /// /// Last used query hints document. /// - BSONDocument _hints; + BsonDocument _hints; /// /// Name of the collection used by default. @@ -110,7 +110,7 @@ public string DefaultCollection { } } - internal EJDBQuery(EJDB jb, BSONDocument qdoc, string defaultcollection = null) { + internal EJDBQuery(EJDB jb, BsonDocument qdoc, string defaultcollection = null) { _qptr = _ejdbcreatequery(jb.DBPtr, qdoc.ToByteArray()); if (_qptr == IntPtr.Zero) { throw new EJDBQueryException(jb); @@ -126,7 +126,7 @@ internal EJDBQuery(EJDB jb, BSONDocument qdoc, string defaultcollection = null) /// Query document. public EJDBQuery AddOR(object docobj) { CheckDisposed(); - BSONDocument doc = BSONDocument.ValueOf(docobj); + BsonDocument doc = BsonDocument.ValueOf(docobj); //static extern IntPtr _ejdbqueryaddor([In] IntPtr jb, [In] IntPtr qptr, [In] byte[] bsdata); IntPtr qptr = _ejdbqueryaddor(_jb.DBPtr, _qptr, doc.ToByteArray()); if (qptr == IntPtr.Zero) { @@ -143,7 +143,7 @@ public EJDBQuery AddOR(object docobj) { /// /// This query object. /// Hints document. - public EJDBQuery SetHints(BSONDocument hints) { + public EJDBQuery SetHints(BsonDocument hints) { CheckDisposed(); //0F-00-00-00-10-24-6D-61-78-00-0A-00-00-00-00 //static extern IntPtr _ejdbqueryhints([In] IntPtr jb, [In] IntPtr qptr, [In] byte[] bsdata); @@ -175,7 +175,7 @@ public EJDBQCursor Find(string cname = null, int qflags = 0) { IntPtr logsptr = IntPtr.Zero; if ((qflags & EXPLAIN_FLAG) != 0) { //static extern IntPtr _tcxstrnew(); - logsptr = _tcxstrnew(); //Create dynamic query execution log buffer + logsptr = _tcxstrnew(); //CreateOid dynamic query execution log buffer } EJDBQCursor cur = null; try { @@ -204,7 +204,7 @@ public EJDBQCursor Find(string cname = null, int qflags = 0) { return cur; } - public BSONIterator FinOne(string cname = null, int qflags = 0) { + public BsonIterator FinOne(string cname = null, int qflags = 0) { using (EJDBQCursor cur = Find(cname, qflags | JBQRYFINDONE_FLAG)) { return cur.Next(); } @@ -229,7 +229,7 @@ public EJDBQuery Max(int max) { throw new ArgumentException("Max limit cannot be negative"); } if (_hints == null) { - _hints = new BSONDocument(); + _hints = new BsonDocument(); } _hints["$max"] = max; _dutyhints = true; @@ -241,7 +241,7 @@ public EJDBQuery Skip(int skip) { throw new ArgumentException("Skip value cannot be negative"); } if (_hints == null) { - _hints = new BSONDocument(); + _hints = new BsonDocument(); } _hints["$skip"] = skip; _dutyhints = true; @@ -250,11 +250,11 @@ public EJDBQuery Skip(int skip) { public EJDBQuery OrderBy(string field, bool asc = true) { if (_hints == null) { - _hints = new BSONDocument(); + _hints = new BsonDocument(); } - BSONDocument oby = _hints["$orderby"] as BSONDocument; + BsonDocument oby = _hints["$orderby"] as BsonDocument; if (oby == null) { - oby = new BSONDocument(); + oby = new BsonDocument(); _hints["$orderby"] = oby; } oby[field] = (asc) ? 1 : -1; @@ -291,11 +291,11 @@ public EJDBQuery SetDefaultCollection(string cname) { //.////////////////////////////////////////////////////////////////// EJDBQuery IncExFields(string[] fields, int inc) { if (_hints == null) { - _hints = new BSONDocument(); + _hints = new BsonDocument(); } - BSONDocument fdoc = _hints["$fields"] as BSONDocument; + BsonDocument fdoc = _hints["$fields"] as BsonDocument; if (fdoc == null) { - fdoc = new BSONDocument(); + fdoc = new BsonDocument(); _hints["$fields"] = fdoc; } foreach (var fn in fields) { diff --git a/Ejdb.DB/Query/DynamicReflectionHelper.cs b/Ejdb.DB/Query/DynamicReflectionHelper.cs new file mode 100644 index 0000000..247dbc1 --- /dev/null +++ b/Ejdb.DB/Query/DynamicReflectionHelper.cs @@ -0,0 +1,64 @@ +// ============================================================================================ +// .NET API for EJDB database library http://ejdb.org +// Copyright (C) 2013-2014 Oliver Klemencic +// +// This file is part of EJDB. +// EJDB is free software; you can redistribute it and/or modify it under the terms of +// the GNU Lesser General Public License as published by the Free Software Foundation; either +// version 2.1 of the License or any later version. EJDB is distributed in the hope +// that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +// License for more details. +// You should have received a copy of the GNU Lesser General Public License along with EJDB; +// if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, +// Boston, MA 02111-1307 USA. +// ============================================================================================ + +using System; +using System.Linq.Expressions; +using System.Reflection; + +namespace Ejdb.DB +{ + public class DynamicReflectionHelper + { + /// + /// + public static PropertyInfo GetProperty(LambdaExpression expression) + { + var memberExpression = _GetMemberExpression(expression); + var propertyInfo = memberExpression.Member as PropertyInfo; + if (propertyInfo == null) + { + var message = string.Format("Could not find a property for expression {0}. Check that your are using a property and not a field", expression); + throw new Exception(message); + } + + return propertyInfo; + } + + public static string GetMethodName(Expression> expression) + { + var body = (MethodCallExpression) expression.Body; + return body.Method.Name; + } + + /// + /// + private static MemberExpression _GetMemberExpression(LambdaExpression expression) + { + MemberExpression memberExpression = null; + + if (expression.Body.NodeType == ExpressionType.Convert || expression.Body.NodeType == ExpressionType.TypeAs) + { + var body = (UnaryExpression) expression.Body; + memberExpression = body.Operand as MemberExpression; + } + + else if (expression.Body.NodeType == ExpressionType.MemberAccess) + memberExpression = expression.Body as MemberExpression; + + return memberExpression; + } + } +} \ No newline at end of file diff --git a/Ejdb.DB/Query/EmptyQuery.cs b/Ejdb.DB/Query/EmptyQuery.cs new file mode 100644 index 0000000..12b71ba --- /dev/null +++ b/Ejdb.DB/Query/EmptyQuery.cs @@ -0,0 +1,17 @@ +using Ejdb.BSON; + +namespace Ejdb.DB +{ + internal class EmptyQuery : IQuery + { + private EmptyQuery() + { } + + public static EmptyQuery Instance = new EmptyQuery(); + + public BsonDocument Document + { + get { return new BsonDocument(); } + } + } +} \ No newline at end of file diff --git a/Ejdb.DB/Query/IPartialQuery.cs b/Ejdb.DB/Query/IPartialQuery.cs new file mode 100644 index 0000000..25105b1 --- /dev/null +++ b/Ejdb.DB/Query/IPartialQuery.cs @@ -0,0 +1,8 @@ +namespace Ejdb.DB +{ + public interface IPartialQuery + { + string QueryOperator { get; } + object ComparisonValue { get; } + } +} \ No newline at end of file diff --git a/Ejdb.DB/Query/IQuery.cs b/Ejdb.DB/Query/IQuery.cs new file mode 100644 index 0000000..8a8ace6 --- /dev/null +++ b/Ejdb.DB/Query/IQuery.cs @@ -0,0 +1,25 @@ +// ============================================================================================ +// .NET API for EJDB database library http://ejdb.org +// Copyright (C) 2013-2014 Oliver Klemencic +// +// This file is part of EJDB. +// EJDB is free software; you can redistribute it and/or modify it under the terms of +// the GNU Lesser General Public License as published by the Free Software Foundation; either +// version 2.1 of the License or any later version. EJDB is distributed in the hope +// that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +// License for more details. +// You should have received a copy of the GNU Lesser General Public License along with EJDB; +// if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, +// Boston, MA 02111-1307 USA. +// ============================================================================================ + +using Ejdb.BSON; + +namespace Ejdb.DB +{ + public interface IQuery + { + BsonDocument Document { get; } + } +} \ No newline at end of file diff --git a/Ejdb.DB/Query/PartialQuery(T).cs b/Ejdb.DB/Query/PartialQuery(T).cs new file mode 100644 index 0000000..1903a04 --- /dev/null +++ b/Ejdb.DB/Query/PartialQuery(T).cs @@ -0,0 +1,25 @@ +namespace Ejdb.DB +{ + public class PartialQuery : IPartialQuery + { + private TMember _comparisonValue; + + public static PartialQuery Create(string queryOperator, TMember comparisonValue) + { + return new PartialQuery(queryOperator, comparisonValue); + } + + private PartialQuery(string queryOperator, TMember comparisonValue) + { + QueryOperator = queryOperator; + _comparisonValue = comparisonValue; + } + + public string QueryOperator { get; private set; } + + public object ComparisonValue + { + get { return _comparisonValue; } + } + } +} \ No newline at end of file diff --git a/Ejdb.DB/Query/PartialQuery.cs b/Ejdb.DB/Query/PartialQuery.cs new file mode 100644 index 0000000..8714cd2 --- /dev/null +++ b/Ejdb.DB/Query/PartialQuery.cs @@ -0,0 +1,35 @@ +// ============================================================================================ +// .NET API for EJDB database library http://ejdb.org +// Copyright (C) 2013-2014 Oliver Klemencic +// +// This file is part of EJDB. +// EJDB is free software; you can redistribute it and/or modify it under the terms of +// the GNU Lesser General Public License as published by the Free Software Foundation; either +// version 2.1 of the License or any later version. EJDB is distributed in the hope +// that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +// License for more details. +// You should have received a copy of the GNU Lesser General Public License along with EJDB; +// if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, +// Boston, MA 02111-1307 USA. +// ============================================================================================ + +namespace Ejdb.DB +{ + public class PartialQuery : IPartialQuery + { + public static PartialQuery Create(string queryOperator, object comparisonValue) + { + return new PartialQuery(queryOperator, comparisonValue); + } + + private PartialQuery(string queryOperator, object comparisonValue) + { + QueryOperator = queryOperator; + ComparisonValue = comparisonValue; + } + + public string QueryOperator { get; private set; } + public object ComparisonValue { get; private set; } + } +} \ No newline at end of file diff --git a/Ejdb.DB/Query/Query(T).cs b/Ejdb.DB/Query/Query(T).cs new file mode 100644 index 0000000..49ed959 --- /dev/null +++ b/Ejdb.DB/Query/Query(T).cs @@ -0,0 +1,119 @@ +// ============================================================================================ +// .NET API for EJDB database library http://ejdb.org +// Copyright (C) 2013-2014 Oliver Klemencic +// +// This file is part of EJDB. +// EJDB is free software; you can redistribute it and/or modify it under the terms of +// the GNU Lesser General Public License as published by the Free Software Foundation; either +// version 2.1 of the License or any later version. EJDB is distributed in the hope +// that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +// License for more details. +// You should have received a copy of the GNU Lesser General Public License along with EJDB; +// if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, +// Boston, MA 02111-1307 USA. +// ============================================================================================ + +using System; +using System.Linq.Expressions; + +namespace Ejdb.DB +{ + public static class Query + { + public static QueryBuilder EQ(Expression> memberExpression, TMember value) + { + return new QueryBuilder().EQ(memberExpression, value); + } + + public static QueryBuilder EqualsIgnoreCase(Expression> memberExpression, string value) + { + return new QueryBuilder().EqualsIgnoreCase(memberExpression, value); + } + + public static QueryBuilder BeginsWith(Expression> memberExpression, string value) + { + return new QueryBuilder().BeginsWith(memberExpression, value); + } + + public static QueryBuilder EndsWith(Expression> memberExpression, string value) + { + return new QueryBuilder().EndsWith(memberExpression, value); + } + + public static QueryBuilder GT(Expression> memberExpression, TMember value) + { + return new QueryBuilder().GT(memberExpression, value); + } + + public static QueryBuilder GTE(Expression> memberExpression, TMember value) + { + return new QueryBuilder().GTE(memberExpression, value); + } + + public static QueryBuilder LT(Expression> memberExpression, TMember value) + { + return new QueryBuilder().LT(memberExpression, value); + } + + public static QueryBuilder LTE(Expression> memberExpression, TMember value) + { + return new QueryBuilder().LTE(memberExpression, value); + } + + public static QueryBuilder In(Expression> memberExpression, params TMember[] comparisonValues) + { + return new QueryBuilder().In(memberExpression, comparisonValues); + } + + public static QueryBuilder NotIn(Expression> memberExpression, params TMember[] comparisonValues) + { + return new QueryBuilder().NotIn(memberExpression, comparisonValues); + } + + public static QueryBuilder NotEquals(Expression> memberExpression, TMember value) + { + return new QueryBuilder().NotEquals(memberExpression, value); + } + + public static QueryBuilder Not(Expression> memberExpression, PartialQuery query) + { + return new QueryBuilder().Not(memberExpression, query); + } + + public static IQuery Between(Expression> memberExpression, TMember comparisonValue1, TMember comparisonValue2) + { + return new QueryBuilder().Between(memberExpression, comparisonValue1, comparisonValue2); + } + + public static QueryBuilder Exists(Expression> memberExpression) + { + return new QueryBuilder().Exists(memberExpression); + } + + public static QueryBuilder NotExists(Expression> memberExpression) + { + return new QueryBuilder().NotExists(memberExpression); + } + + public static QueryBuilder StringMatchesAllTokens(Expression> memberExpression, params string[] values) + { + return new QueryBuilder().StringMatchesAllTokens(memberExpression, values); + } + + public static QueryBuilder StringMatchesAnyTokens(Expression> memberExpression, params string[] values) + { + return new QueryBuilder().StringMatchesAnyTokens(memberExpression, values); + } + + public static QueryBuilder Or(params QueryBuilder[] queries) + { + return new QueryBuilder().Or(queries); + } + + public static QueryBuilder ElemMatch(Expression> memberExpression, params IQuery[] queries) + { + return new QueryBuilder().ElemMatch(memberExpression, queries); + } + } +} \ No newline at end of file diff --git a/Ejdb.DB/Query/Query.cs b/Ejdb.DB/Query/Query.cs new file mode 100644 index 0000000..0d278bc --- /dev/null +++ b/Ejdb.DB/Query/Query.cs @@ -0,0 +1,156 @@ +// ============================================================================================ +// .NET API for EJDB database library http://ejdb.org +// Copyright (C) 2013-2014 Oliver Klemencic +// +// This file is part of EJDB. +// EJDB is free software; you can redistribute it and/or modify it under the terms of +// the GNU Lesser General Public License as published by the Free Software Foundation; either +// version 2.1 of the License or any later version. EJDB is distributed in the hope +// that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +// License for more details. +// You should have received a copy of the GNU Lesser General Public License along with EJDB; +// if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, +// Boston, MA 02111-1307 USA. +// ============================================================================================ + +using System.Linq; +using Ejdb.BSON; + +namespace Ejdb.DB +{ + public class Query : IQuery + { + private readonly BsonDocument mQueryDocument; + + public Query(string fieldName, object query) + { + mQueryDocument = new BsonDocument(); + mQueryDocument[fieldName] = query; + } + + public static IQuery Empty + { + get { return EmptyQuery.Instance; } + } + + public static QueryBuilder EQ(string fieldName, object value) + { + return new QueryBuilder().EQ(fieldName, value); + } + + public static QueryBuilder EqualsIgnoreCase(string fieldName, string value) + { + return new QueryBuilder().EqualsIgnoreCase(fieldName, value); + } + + public static IQuery BeginsWith(string fieldName, string value) + { + return new QueryBuilder().BeginsWith(fieldName, value); + } + + public static IQuery EndsWith(string fieldName, string value) + { + return new QueryBuilder().EndsWith(fieldName, value); + } + + public static QueryBuilder GT(string fieldName, object value) + { + return new QueryBuilder().GT(fieldName, value); + } + + public static QueryBuilder GTE(string fieldName, object value) + { + return new QueryBuilder().GTE(fieldName, value); + } + + public static QueryBuilder LT(string fieldName, object value) + { + return new QueryBuilder().LT(fieldName, value); + } + + public static QueryBuilder LTE(string fieldName, object value) + { + return new QueryBuilder().LTE(fieldName, value); + } + + public static QueryBuilder In(string fieldName, params T[] comparisonValues) + { + return new QueryBuilder().In(fieldName, comparisonValues); + } + + public static QueryBuilder NotIn(string fieldName, params T[] comparisonValues) + { + return new QueryBuilder().NotIn(fieldName, comparisonValues); + } + + public static QueryBuilder NotEquals(string fieldName, object comparisonValue) + { + return new QueryBuilder().NotEquals(fieldName, comparisonValue); + } + + public static QueryBuilder Not(string fieldName, IPartialQuery query) + { + return new QueryBuilder().Not(fieldName, query); + } + + public static QueryBuilder Between(string fieldName, T comparisonValue1, T comparisonValue2) + { + return new QueryBuilder().Between(fieldName, comparisonValue1, comparisonValue2); + } + + /* public static IQuery And(params QueryBuilder[] queries) + { + return _CombinedQuery("$and", queries); + } */ + + public static IQuery Or(params QueryBuilder[] queries) + { + return new QueryBuilder().Or(queries); + } + + public static IQuery Exists(string fieldName) + { + return new QueryBuilder().Exists(fieldName); + } + + public static IQuery NotExists(string fieldName) + { + return new QueryBuilder().NotExists(fieldName); + } + + public static IQuery StringMatchesAllTokens(string fieldName, params string[] values) + { + return new QueryBuilder().StringMatchesAllTokens(fieldName, values); + } + + public static IQuery StringMatchesAnyTokens(string fieldName, params string[] values) + { + return new QueryBuilder().StringMatchesAnyTokens(fieldName, values); + } + + + public static IQuery ElemMatch(string fieldName, params IQuery[] queries) + { + var queryDocument = new BsonDocument(); + + foreach (var query in queries) + { + foreach (var field in query.Document) + queryDocument[field.Key] = field.Value; + } + + return new Query("$elemMatch", queryDocument); + } + + public BsonDocument Document + { + get { return mQueryDocument; } + } + + public static QueryBuilder Join(string fieldName, string foreignCollectionName) + { + return new QueryBuilder().Join(fieldName, foreignCollectionName); + } + } +} \ No newline at end of file diff --git a/Ejdb.DB/Query/QueryBuilder.cs b/Ejdb.DB/Query/QueryBuilder.cs new file mode 100644 index 0000000..679814a --- /dev/null +++ b/Ejdb.DB/Query/QueryBuilder.cs @@ -0,0 +1,118 @@ +using System; +using System.Linq.Expressions; + +namespace Ejdb.DB +{ + public class QueryBuilder : QueryBuilderBase> + { + protected override QueryBuilder This() + { + return this; + } + + + + public QueryBuilder EQ(Expression> memberExpression, TMember value) + { + return EQ(_GetFieldName(memberExpression), value); + } + + public QueryBuilder EqualsIgnoreCase(Expression> memberExpression, string value) + { + return EqualsIgnoreCase(_GetFieldName(memberExpression), value); + } + + public QueryBuilder BeginsWith(Expression> memberExpression, string value) + { + return BeginsWith(_GetFieldName(memberExpression), value); + } + + public QueryBuilder EndsWith(Expression> memberExpression, string value) + { + return EndsWith(_GetFieldName(memberExpression), value); + } + + public QueryBuilder GT(Expression> memberExpression, TMember value) + { + return GT(_GetFieldName(memberExpression), value); + } + + public QueryBuilder GTE(Expression> memberExpression, TMember value) + { + return GTE(_GetFieldName(memberExpression), value); + } + + public QueryBuilder LT(Expression> memberExpression, TMember value) + { + return LT(_GetFieldName(memberExpression), value); + } + + public QueryBuilder LTE(Expression> memberExpression, TMember value) + { + return LTE(_GetFieldName(memberExpression), value); + } + + public QueryBuilder In(Expression> memberExpression, params TMember[] comparisonValues) + { + return In(_GetFieldName(memberExpression), comparisonValues); + } + + public QueryBuilder NotIn(Expression> memberExpression, params TMember[] comparisonValues) + { + return NotIn(_GetFieldName(memberExpression), comparisonValues); + } + + public QueryBuilder NotEquals(Expression> memberExpression, TMember value) + { + return NotEquals(_GetFieldName(memberExpression), value); + } + + public QueryBuilder Not(Expression> memberExpression, PartialQuery query) + { + return Not(_GetFieldName(memberExpression), query); + } + + public QueryBuilder Between(Expression> memberExpression, TMember comparisonValue1, TMember comparisonValue2) + { + return Between(_GetFieldName(memberExpression), comparisonValue1, comparisonValue2); + } + + public QueryBuilder Exists(Expression> memberExpression) + { + return Exists(_GetFieldName(memberExpression)); + } + + public QueryBuilder NotExists(Expression> memberExpression) + { + return NotExists(_GetFieldName(memberExpression)); + } + + public QueryBuilder StringMatchesAllTokens(Expression> memberExpression, params string[] values) + { + return StringMatchesAllTokens(_GetFieldName(memberExpression), values); + } + + public QueryBuilder StringMatchesAnyTokens(Expression> memberExpression, params string[] values) + { + return StringMatchesAnyTokens(_GetFieldName(memberExpression), values); + } + + public QueryBuilder ElemMatch(Expression> memberExpression, params IQuery[] queries) + { + return ElemMatch(_GetFieldName(memberExpression), queries); + } + + private string _GetFieldName(Expression> memberExpression) + { + return DynamicReflectionHelper.GetProperty(memberExpression).Name; + } + } + + public class QueryBuilder : QueryBuilderBase + { + protected override QueryBuilder This() + { + return this; + } + } +} \ No newline at end of file diff --git a/Ejdb.DB/Query/QueryBuilderBase.cs b/Ejdb.DB/Query/QueryBuilderBase.cs new file mode 100644 index 0000000..b63b85f --- /dev/null +++ b/Ejdb.DB/Query/QueryBuilderBase.cs @@ -0,0 +1,164 @@ +using System; +using System.Linq; +using Ejdb.BSON; + +namespace Ejdb.DB +{ + public abstract class QueryBuilderBase : IQuery + where TThis: IQuery + { + private readonly BsonDocument _document; + + protected abstract TThis This(); + + public QueryBuilderBase() + { + _document = new BsonDocument(); + } + + public BsonDocument Document + { + get { return _document; } + } + + public TThis Join(string fieldName, string foreignCollectionName) + { + var doDocument = _GetOrCreateElement("$do", () => new BsonDocument()); + + var innerMostDoc = new BsonDocument() + .Add("$join", foreignCollectionName); + + doDocument.Add(fieldName, innerMostDoc); + return This(); + } + + public TThis EqualsIgnoreCase(string fieldName, string value) + { + return _BinaryQuery("$icase", fieldName, value); + } + + public TThis EQ(string fieldName, object value) + { + _document[fieldName] = value; + return This(); + } + + public TThis Exists(string fieldName) + { + return _BinaryQuery("$exists", fieldName, true); + } + + public TThis NotExists(string fieldName) + { + return _BinaryQuery("$exists", fieldName, false); + } + + public TThis GT(string fieldName, object value) + { + return _BinaryQuery("$gt", fieldName, value); + } + + public TThis GTE(string fieldName, object value) + { + return _BinaryQuery("$gte", fieldName, value); + } + + public TThis LT(string fieldName, object value) + { + return _BinaryQuery("$lt", fieldName, value); + } + + public TThis LTE(string fieldName, object value) + { + return _BinaryQuery("$lte", fieldName, value); + } + + public TThis BeginsWith(string fieldName, string value) + { + return _BinaryQuery("$begin", fieldName, value); + } + + public TThis EndsWith(string fieldName, string value) + { + return _BinaryQuery("end", fieldName, value); + } + + public TThis In(string fieldName, params T[] comparisonValues) + { + return _BinaryQuery("$in", fieldName, comparisonValues); + } + + public TThis NotIn(string fieldName, params T[] comparisonValues) + { + return _BinaryQuery("$nin", fieldName, comparisonValues); + } + + public TThis Between(string fieldName, T comparisonValue1, T comparisonValue2) + { + var comparisonValues = new[] { comparisonValue1, comparisonValue2 }; + return _BinaryQuery("$bt", fieldName, comparisonValues); + } + + public TThis NotEquals(string fieldName, object comparisonValue) + { + return _BinaryQuery("$not", fieldName, comparisonValue); + } + + public TThis Not(string fieldName, IPartialQuery query) + { + var childValue = new BsonDocument(); + childValue.Add(query.QueryOperator, query.ComparisonValue); + return _BinaryQuery("$not", fieldName, childValue); + } + + private TThis _BinaryQuery(string queryOperation, string fieldName, object comparisonValue) + { + _document[fieldName] = new BsonDocument() + .Add(queryOperation, comparisonValue); + + return This(); + } + + public TThis StringMatchesAllTokens(string fieldName, params string[] values) + { + return _BinaryQuery("$strand", fieldName, values); + } + + public TThis StringMatchesAnyTokens(string fieldName, params string[] values) + { + return _BinaryQuery("$stror", fieldName, values); + } + + public TThis Or(params IQuery[] queries) + { + var documents = queries.Select(x => x.Document).ToArray(); + var childValue = new BsonArray(documents); + _document["$or"] = childValue; + return This(); + } + + public TThis ElemMatch(string fieldName, params IQuery[] queries) + { + var queryDocument = new BsonDocument(); + + foreach (var query in queries) + { + foreach (var field in query.Document) + queryDocument[field.Key] = field.Value; + } + + return _BinaryQuery("$elemMatch", fieldName, queryDocument); + } + + private T _GetOrCreateElement(string elementName, Func defaultValue) + { + var element = _document[elementName]; + if (element == null) + { + element = defaultValue(); + _document.Add(elementName, element); + } + return (T)element; + } + } +} \ No newline at end of file diff --git a/Ejdb.DB/Query/Update.cs b/Ejdb.DB/Query/Update.cs new file mode 100644 index 0000000..51a1f4f --- /dev/null +++ b/Ejdb.DB/Query/Update.cs @@ -0,0 +1,83 @@ +using Ejdb.BSON; + +namespace Ejdb.DB +{ + public class Update + { + /// + /// Sets the value of the named element to a new value (see $set). + /// + /// The name of the element to be set. + /// The new value. + /// The builder (so method calls can be chained). + public static UpdateBuilder Set(string name, object value) + { + return new UpdateBuilder().Set(name, value); + } + + /// + /// Increments the named element by a value (see $inc). + /// + /// The name of the element to be incremented. + /// The value to increment by. + /// The builder (so method calls can be chained). + public static UpdateBuilder Inc(string name, object value) + { + return new UpdateBuilder().Inc(name, value); + } + + /// + /// In-place record removal operation ($dropall). + /// + /// The builder (so method calls can be chained). + public static UpdateBuilder DropAll() + { + return new UpdateBuilder().DropAll(); + } + + /// + /// Removes all values from the named array element that are equal to some value (see $pull). + /// + /// The name of the array element. + /// The value to remove. + /// The builder (so method calls can be chained). + public static UpdateBuilder Pull(string name, object value) + { + return new UpdateBuilder().Pull(name, value); + } + + /// + /// Removes all values from the named array element that are equal to any of a list of values (see $pullAll). + /// + /// The name of the array element. + /// The values to remove. + /// The builder (so method calls can be chained). + public static UpdateBuilder PullAll(string name, params T[] values) + { + return new UpdateBuilder().PullAll(name, values); + } + + // public methods + /// + /// Adds a value to a named array element if the value is not already in the array (see $addToSet). + /// + /// The name of the array element. + /// The value to add to the set. + /// The builder (so method calls can be chained). + public static UpdateBuilder AddToSet(string name, object value) + { + return new UpdateBuilder().AddToSet(name, value); + } + + /// + /// Adds a list of values to a named array element adding each value only if it not already in the array (see $addToSet and $each). + /// + /// The name of the array element. + /// The values to add to the set. + /// The builder (so method calls can be chained). + public static UpdateBuilder AddToSetAll(string name, params T[] values) + { + return new UpdateBuilder().AddToSetAll(name, values); + } + } +} \ No newline at end of file diff --git a/Ejdb.DB/Query/UpdateBuilder.cs b/Ejdb.DB/Query/UpdateBuilder.cs new file mode 100644 index 0000000..a7b9b25 --- /dev/null +++ b/Ejdb.DB/Query/UpdateBuilder.cs @@ -0,0 +1,133 @@ +using System; +using Ejdb.BSON; + +namespace Ejdb.DB +{ + public class UpdateBuilder + { + private const string SET_OPERATOR = "$set"; + private const string INC_OPERATOR = "$inc"; + private const string ADD_TO_SET_OPERATOR = "$addToSet"; + private const string ADD_TO_SET_ALL_OPERATOR = "$addToSetAll"; + private const string PULL_OPERATOR = "$pull"; + private const string PULL_ALL_OPERATOR = "$pullAll"; + private const string DROP_ALL_OPERATOR = "$dropall"; + + private readonly BsonDocument _document; + + /// + /// Initializes a new instance of the UpdateBuilder class. + /// + public UpdateBuilder() + { + _document = new BsonDocument(); + } + + internal BsonDocument Document + { + get { return _document; } + } + + /// + /// Sets the value of the named element to a new value (see $set). + /// + /// The name of the element to be set. + /// The new value. + /// The builder (so method calls can be chained). + public UpdateBuilder Set(string name, object value) + { + return _AddOperation(SET_OPERATOR, name, value); + } + + /// + /// Increments the named element by a value (see $inc). + /// + /// The name of the element to be incremented. + /// The value to increment by. + /// The builder (so method calls can be chained). + public UpdateBuilder Inc(string name, object value) + { + return _AddOperation(INC_OPERATOR, name, value); + } + + /// + /// Removes all values from the named array element that are equal to some value (see $pull). + /// + /// The name of the array element. + /// The value to remove. + /// The builder (so method calls can be chained). + public UpdateBuilder Pull(string name, object value) + { + return _AddOperation(PULL_OPERATOR, name, value); + } + + /// + /// Removes all values from the named array element that are equal to any of a list of values (see $pullAll). + /// + /// The name of the array element. + /// The values to remove. + /// The builder (so method calls can be chained). + public UpdateBuilder PullAll(string name, params T[] values) + { + return _AddOperation(PULL_ALL_OPERATOR, name, values); + } + + // public methods + /// + /// Adds a value to a named array element if the value is not already in the array (see $addToSet). + /// + /// The name of the array element. + /// The value to add to the set. + /// The builder (so method calls can be chained). + public UpdateBuilder AddToSet(string name, object value) + { + return _AddOperation(ADD_TO_SET_OPERATOR, name, value); + } + + /// + /// Adds a list of values to a named array element adding each value only if it not already in the array (see $addToSet and $each). + /// + /// The name of the array element. + /// The values to add to the set. + /// The builder (so method calls can be chained). + public UpdateBuilder AddToSetAll(string name, params T[] values) + { + return _AddOperation(ADD_TO_SET_ALL_OPERATOR, name, values); + } + + /// + /// In-place record removal operation ($dropall). + /// + /// The builder (so method calls can be chained). + public UpdateBuilder DropAll() + { + var element = _document[DROP_ALL_OPERATOR] as BsonDocument; + if (element == null) + _document.Add(DROP_ALL_OPERATOR, BsonValue.Create(true)); + + return this; + } + + private UpdateBuilder _AddOperation(string operationName, string name, object value) + { + Verify.NotNull(name, "name"); + Verify.NotNull(value, "value"); + + var element = _GetOrCreateElement(operationName); + element.Add(name, value); + + return this; + } + + private BsonDocument _GetOrCreateElement(string operationName) + { + var element = _document[operationName] as BsonDocument; + if (element == null) + { + element = new BsonDocument(); + _document.Add(operationName, element); + } + return element; + } + } +} \ No newline at end of file diff --git a/Ejdb.IO/ExtBinaryReader.cs b/Ejdb.IO/ExtBinaryReader.cs index a824e18..c3f94af 100644 --- a/Ejdb.IO/ExtBinaryReader.cs +++ b/Ejdb.IO/ExtBinaryReader.cs @@ -13,7 +13,7 @@ // if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, // Boston, MA 02111-1307 USA. // ============================================================================================ -using System; + using System.IO; using System.Text; using System.Collections.Generic; @@ -22,21 +22,27 @@ namespace Ejdb.IO { public class ExtBinaryReader : BinaryReader { - public static Encoding DEFAULT_ENCODING = Encoding.UTF8; + private static readonly Encoding DEFAULT_ENCODING = new UTF8Encoding(false, true); bool _leaveopen; - public ExtBinaryReader(Stream input) : this(input, DEFAULT_ENCODING) { + public ExtBinaryReader(Stream input) + : this(input, DEFAULT_ENCODING) { } - public ExtBinaryReader(Stream input, Encoding encoding) : this(input, encoding, false) { + public ExtBinaryReader(Stream input, Encoding encoding) + : this(input, encoding, false) { } - public ExtBinaryReader(Stream input, bool leaveOpen) : this(input, DEFAULT_ENCODING, leaveOpen) { + public ExtBinaryReader(Stream input, bool leaveOpen) + : this(input, DEFAULT_ENCODING, leaveOpen) + { } - public ExtBinaryReader(Stream input, Encoding encoding, bool leaveopen) : base(input, encoding) { - this._leaveopen = leaveopen; + public ExtBinaryReader(Stream input, Encoding encoding, bool leaveopen) + : base(input, encoding) + { + _leaveopen = leaveopen; } protected override void Dispose(bool disposing) { @@ -44,12 +50,12 @@ protected override void Dispose(bool disposing) { } public string ReadCString() { - List sb = new List(64); - byte bv; - while ((bv = ReadByte()) != 0x00) { - sb.Add(bv); - } - return Encoding.UTF8.GetString(sb.ToArray()); + var bytes = new List(64); + byte currentByte; + while ((currentByte = ReadByte()) != 0x00) + bytes.Add(currentByte); + + return DEFAULT_ENCODING.GetString(bytes.ToArray()); } public void SkipCString() { diff --git a/Ejdb.IO/ExtBinaryWriter.cs b/Ejdb.IO/ExtBinaryWriter.cs index bf158ca..bd9a385 100644 --- a/Ejdb.IO/ExtBinaryWriter.cs +++ b/Ejdb.IO/ExtBinaryWriter.cs @@ -21,18 +21,22 @@ namespace Ejdb.IO { public class ExtBinaryWriter : BinaryWriter { - public static Encoding DEFAULT_ENCODING = Encoding.UTF8; - Encoding _encoding; - bool _leaveopen; + private static readonly Encoding DEFAULT_ENCODING = new UTF8Encoding(false, true); + + private Encoding _encoding; + private bool _leaveopen; public ExtBinaryWriter() { _encoding = DEFAULT_ENCODING; } - public ExtBinaryWriter(Stream output) : this(output, DEFAULT_ENCODING, false) { + public ExtBinaryWriter(Stream output) + : this(output, DEFAULT_ENCODING, false) { } - public ExtBinaryWriter(Stream output, Encoding encoding, bool leaveopen) : base(output, encoding) { + public ExtBinaryWriter(Stream output, Encoding encoding, bool leaveopen) + : base(output, encoding) + { _encoding = encoding; _leaveopen = leaveopen; } @@ -43,7 +47,8 @@ public ExtBinaryWriter(Stream output, Encoding encoding) : this(output, encoding public ExtBinaryWriter(Stream output, bool leaveopen) : this(output, DEFAULT_ENCODING, leaveopen) { } - protected override void Dispose(bool disposing) { + protected override void Dispose(bool disposing) + { base.Dispose(!_leaveopen); } @@ -54,9 +59,16 @@ public void WriteBSONString(string val) { Write((byte) 0x00); } - public void WriteCString(string val) { - if (val.Length > 0) { - Write(_encoding.GetBytes(val)); + public void WriteCString(string value) + { + if (value == null) + throw new ArgumentNullException("value"); + + if (value.IndexOf('\0') != -1) + throw new ArgumentException("CStrings cannot contain nulls.", "value"); + + if (value.Length > 0) { + Write(_encoding.GetBytes(value)); } Write((byte) 0x00); } diff --git a/Ejdb.Tests/MockStream.cs b/Ejdb.Tests/MockStream.cs new file mode 100644 index 0000000..7f0dfdc --- /dev/null +++ b/Ejdb.Tests/MockStream.cs @@ -0,0 +1,19 @@ +using System.IO; + +namespace Ejdb.Tests +{ + public class MockStream : MemoryStream + { + private readonly bool _canSeek; + + public MockStream(bool canSeek) + { + _canSeek = canSeek; + } + + public override bool CanSeek + { + get { return _canSeek; } + } + } +} \ No newline at end of file diff --git a/Ejdb.Tests/TestBSON.cs b/Ejdb.Tests/TestBSON.cs deleted file mode 100644 index 6c56b4f..0000000 --- a/Ejdb.Tests/TestBSON.cs +++ /dev/null @@ -1,287 +0,0 @@ -// ============================================================================================ -// .NET API for EJDB database library http://ejdb.org -// Copyright (C) 2012-2013 Softmotions Ltd -// -// This file is part of EJDB. -// EJDB is free software; you can redistribute it and/or modify it under the terms of -// the GNU Lesser General Public License as published by the Free Software Foundation; either -// version 2.1 of the License or any later version. EJDB is distributed in the hope -// that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License for more details. -// You should have received a copy of the GNU Lesser General Public License along with EJDB; -// if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, -// Boston, MA 02111-1307 USA. -// ============================================================================================ -using System; -using NUnit.Framework; -using Ejdb.BSON; -using System.IO; - -namespace Ejdb.Tests { - - [TestFixture] - public class TestBSON { - - [Test] - public void TestSerializeEmpty() { - BSONDocument doc = new BSONDocument(); - Assert.AreEqual("05-00-00-00-00", doc.ToDebugDataString()); - } - - [Test] - public void TestSerialize1() { - byte[] bdata; - BSONDocument doc = new BSONDocument(); - doc.SetNumber("0", 1); - //0C-00-00-00 len - //10 type - //30-00 key - //01-00-00-00 int val - //00 zero term - bdata = doc.ToByteArray(); - Assert.AreEqual("0C-00-00-00-10-30-00-01-00-00-00-00", doc.ToDebugDataString()); - Assert.AreEqual(bdata.Length, (int) Convert.ToByte(doc.ToDebugDataString().Substring(0, 2), 16)); - - BSONDocument doc2 = new BSONDocument(doc.ToByteArray()); - Assert.AreEqual(1, doc2.KeysCount); - int c = 0; - foreach (BSONValue bv in doc2) { - c++; - Assert.IsNotNull(bv); - Assert.AreEqual(BSONType.INT, bv.BSONType); - Assert.AreEqual("0", bv.Key); - Assert.AreEqual(1, bv.Value); - } - Assert.That(c > 0); - doc2.SetNumber("0", 2); - Assert.AreEqual(1, doc2.KeysCount); - object ival = doc2["0"]; - Assert.IsInstanceOf(typeof(int), ival); - Assert.AreEqual(2, ival); - doc2.SetNumber("1", Int32.MaxValue); - //13-00-00-00 - //10 - //30-00 - //02-00-00-00 - //10-31-00 - //FF-FF-FF-7F - //00 - Assert.AreEqual("13-00-00-00-10-30-00-02-00-00-00-10-31-00-FF-FF-FF-7F-00", - doc2.ToDebugDataString()); - - doc2 = new BSONDocument(doc2); - Assert.AreEqual("13-00-00-00-10-30-00-02-00-00-00-10-31-00-FF-FF-FF-7F-00", - doc2.ToDebugDataString()); - - doc2 = new BSONDocument(doc2.ToByteArray()); - Assert.AreEqual("13-00-00-00-10-30-00-02-00-00-00-10-31-00-FF-FF-FF-7F-00", - doc2.ToDebugDataString()); - - doc = new BSONDocument(); - doc["a"] = 1; - Assert.AreEqual("0C-00-00-00-10-61-00-01-00-00-00-00", doc.ToDebugDataString()); - } - - [Test] - public void TestAnonTypes() { - BSONDocument doc = BSONDocument.ValueOf(new {a = "b", c = 1}); - //15-00-00-00 - //02-61-00 - //02-00-00-00 - //62-00 - //10-63-00-01-00-00-00-00 - Assert.AreEqual("15-00-00-00-02-61-00-02-00-00-00-62-00-10-63-00-01-00-00-00-00", - doc.ToDebugDataString()); - doc["d"] = new{e=new BSONRegexp("r1", "o2")}; //subdocument - //26-00-00-00-02-61-00-02-00-00-00-62-00-10-63-00-01-00-00-00- - //03 - //64-00 - //0E-00-00-00 - //0B - //65-00 - //72-31-00-6F-32-00-00-00 - Assert.AreEqual("26-00-00-00-02-61-00-02-00-00-00-62-00-10-63-00-01-00-00-00-" + - "03-64-00-0E-00-00-00-0B-65-00-72-31-00-6F-32-00-00-00", - doc.ToDebugDataString()); - } - - [Test] - public void TestIterate1() { - var doc = new BSONDocument(); - doc["a"] = "av"; - doc["bb"] = 24; - //doc["ccc"] = BSONDocument.ValueOf(new{na1 = 1, nb = "2"}); - //doc["d"] = new BSONOid("51b9f3af98195c4600000000"); - - //17-00-00-00 +4 - //02-61-00-03-00-00-00-61-76-00 +10 - //10-62-62-00-18-00-00-00 +8 - //00 +1 - Assert.AreEqual("17-00-00-00-02-61-00-03-00-00-00-61-76-00-10-62-62-00-18-00-00-00-00", - doc.ToDebugDataString()); - BSONIterator it = new BSONIterator(doc); - Assert.AreEqual(doc.ToByteArray().Length, it.DocumentLength); - var c = ""; - while (it.Next() != BSONType.EOO) { - c += it.CurrentKey; - } - Assert.AreEqual("abb", c); - it.Dispose(); - - it = new BSONIterator(doc); - var cnt = 0; - while (it.Next() != BSONType.EOO) { - BSONValue bv = it.FetchCurrentValue(); - Assert.IsNotNull(bv); - if (cnt == 0) { - Assert.IsTrue(bv.BSONType == BSONType.STRING); - Assert.IsTrue(bv.Key == "a"); - Assert.AreEqual("av", bv.Value); - } - if (cnt == 1) { - Assert.IsTrue(bv.BSONType == BSONType.INT); - Assert.IsTrue(bv.Key == "bb"); - Assert.AreEqual(24, bv.Value); - } - cnt++; - } - } - - [Test] - public void TestIterate2() { - var doc = new BSONDocument(); - doc["a"] = "av"; - doc["b"] = BSONDocument.ValueOf(new{cc = 1}); - doc["d"] = new BSONOid("51b9f3af98195c4600000000"); - Assert.AreEqual(3, doc.KeysCount); - //Console.WriteLine(doc.KeysCount); - //Console.WriteLine(doc.ToDebugDataString()); - //2E-00-00-00 +4 - //02-61-00-03-00-00-00-61-76-00 +10 (14) - //03-62-00 +3 (17) "d" = - //0D-00-00-00 +4 (21) doc len = 13 - //10-63-63-00-01-00-00-00 -00 +9 (30) - //07-64-00 +3 (33) - //51-B9-F3-AF-98-19-5C-46-00-00-00-00 +12 (45) - //00 +1 (46) - Assert.AreEqual("2E-00-00-00-" + - "02-61-00-03-00-00-00-61-76-00-" + - "03-62-00-" + - "0D-00-00-00-" + - "10-63-63-00-01-00-00-00-00-" + - "07-64-00-" + - "51-B9-F3-AF-98-19-5C-46-00-00-00-00-" + - "00", doc.ToDebugDataString()); - BSONIterator it = new BSONIterator(doc); - int c = 0; - foreach (var bt in it) { - if (c == 0) { - Assert.IsTrue(bt == BSONType.STRING); - } - if (c == 1) { - Assert.IsTrue(bt == BSONType.OBJECT); - } - if (c == 2) { - Assert.IsTrue(bt == BSONType.OID); - } - ++c; - } - bool thrown = false; - Assert.IsTrue(it.Disposed); - try { - it.Next(); - } catch (ObjectDisposedException) { - thrown = true; - } - Assert.IsTrue(thrown); - - c = 0; - it = new BSONIterator(doc); - foreach (var bv in it.Values()) { - if (c == 0) { - Assert.AreEqual("a", bv.Key); - Assert.AreEqual("av", bv.Value); - } - if (c == 1) { - Assert.AreEqual("b", bv.Key); - BSONDocument sdoc = bv.Value as BSONDocument; - Assert.IsNotNull(sdoc); - foreach (var bv2 in new BSONIterator(sdoc).Values()) { - Assert.AreEqual("cc", bv2.Key); - Assert.AreEqual(1, bv2.Value); - Assert.AreEqual(BSONType.INT, bv2.BSONType); - } - } - if (c == 2) { - Assert.AreEqual(BSONType.OID, bv.BSONType); - Assert.IsInstanceOf(typeof(BSONOid), bv.Value); - var oid = bv.Value as BSONOid; - Assert.AreEqual("51b9f3af98195c4600000000", oid.ToString()); - } - c++; - } - } - - [Test] - public void TestIterateRE() { - var doc = new BSONDocument(); - doc["a"] = new BSONRegexp("b", "c"); - doc["d"] = 1; - doc["e"] = BSONDocument.ValueOf(new {f = new BSONRegexp("g", "")}); - doc["h"] = 2; - //28-00-00-00 - //0B-61-00-62-00-63-00 - //10-64-00-01-00-00-00 - //03-65-00-0B-00-00-00 - //0B-66-00-67-00-00-00 - //10-68-00-02-00-00-00-00 - var cs = ""; - foreach (var bt in new BSONIterator(doc)) { - cs += bt.ToString(); - } - Assert.AreEqual("REGEXINTOBJECTINT", cs); - cs = ""; - foreach (var bv in new BSONIterator(doc).Values()) { - if (bv.Key == "a") { - cs += ((BSONRegexp) bv.Value).Re; - cs += ((BSONRegexp) bv.Value).Opts; - } else { - cs += bv.Value; - } - } - Assert.AreEqual("bc1[BSONDocument: [BSONValue: BSONType=REGEX, Key=f, Value=[BSONRegexp: re=g, opts=]]]2", cs); - } - - [Test] - public void TestFilteredDoc() { - var doc = new BSONDocument(); - doc["c"] = "d"; - doc["aaa"] = 11; - doc["ndoc"] = BSONDocument.ValueOf(new { - aaaa = "nv1", - d = "nv2", - nnd = BSONDocument.ValueOf(new { - nnv = true, - nns = "s" - }) - }); - doc["ndoc2"] = BSONDocument.ValueOf(new { - n = "v" - }); - doc["f"] = "f"; - BSONIterator it = new BSONIterator(doc); - BSONDocument doc2 = it.ToBSONDocument("c", "ndoc.d", "ndoc.nnd.nns", "f"); - Assert.AreEqual(3, doc2.KeysCount); - Assert.AreEqual("d", doc2["c"]); - Assert.AreEqual(2, ((BSONDocument) doc2["ndoc"]).KeysCount); - Assert.AreEqual("nv2", ((BSONDocument) doc2["ndoc"])["d"]); - Assert.AreEqual("s", ((BSONDocument) ((BSONDocument) doc2["ndoc"])["nnd"])["nns"]); - Assert.AreEqual("s", doc2["ndoc.nnd.nns"]); - Assert.AreEqual("f", "f"); - //Console.WriteLine("doc2=" + doc2); - - } - } -} - diff --git a/Ejdb.Tests/TestBsonArray.cs b/Ejdb.Tests/TestBsonArray.cs new file mode 100644 index 0000000..4ef10b2 --- /dev/null +++ b/Ejdb.Tests/TestBsonArray.cs @@ -0,0 +1,134 @@ +using System; +using Ejdb.BSON; +using NUnit.Framework; + +namespace Ejdb.Tests +{ + [TestFixture] + public class TestBsonArray + { + [Test] + public void TestAdd() + { + var array = new BsonArray(); + var value = BsonValue.Create(1); + array.Add(value); + Assert.AreEqual(1, array.Count); + Assert.AreEqual(value.Value, array[0]); + } + + [Test] + public void TestAddNull() + { + var array = new BsonArray(); + array.Add(null); + Assert.AreEqual(0, array.Count); + } + + + [Test] + public void TestAddRangeNull() + { + var array = new BsonArray(); + array.AddRange(null); + Assert.AreEqual(0, array.Count); + } + + [Test] + public void TestClone() + { + var array = new BsonArray(); + array.Add(1); + array.Add(2); + array.Add(new BsonArray()); + array.Add(3); + array.Add(4); + + var clone = new BsonArray((BsonDocument) array.Clone()); + Assert.AreEqual(5, clone.Count); + Assert.AreEqual(1, (int)clone[0]); + Assert.AreEqual(2, (int)clone[1]); + + + Assert.AreEqual(array[2], clone[2]); + + Assert.AreEqual(3, (int)clone[3]); + Assert.AreEqual(4, (int)clone[4]); + } + + + [Test] + public void TestClear() + { + var array = new BsonArray { 1, 2 }; + Assert.AreEqual(2, array.Count); + array.Clear(); + Assert.AreEqual(0, array.Count); + } + + [Test] + public void TestCreateObjectArray() + { + var values = new object[] { true, 1, 1.5, null }; // null will be mapped to BsonNull.Value + var array = new BsonArray(values); + Assert.AreEqual(4, array.Count); + Assert.AreEqual(true, array[0]); + Assert.AreEqual(1, array[1]); + Assert.AreEqual(1.5, array[2]); + Assert.AreEqual(null, array[3]); + } + + [Test] + public void TestCreateObjectIdArray() + { + var value1 = BsonOid.GenerateNewId(); + var value2 = BsonOid.GenerateNewId(); + var values = new[] { value1, value2 }; + var array = new BsonArray(values); + Assert.AreEqual(2, array.Count); + Assert.AreEqual(value1, array[0]); + Assert.AreEqual(value2, array[1]); + } + + [Test] + public void TestCreateStringArray() + { + var values = new string[] { "a", "b", null }; // null will be mapped to BsonNull.Value + var array = new BsonArray(values); + Assert.AreEqual(3, array.Count); + Assert.AreEqual("a", array[0]); + Assert.AreEqual("b", array[1]); + Assert.AreEqual(null, array[2]); + } + + [Test] + public void TestEquals() + { + var a = new BsonArray { 1, 2 }; + var b = new BsonArray { 1, 2 }; + var c = new BsonArray { 3, 4 }; + Assert.IsTrue(a.Equals((object)a)); + Assert.IsTrue(a.Equals((object)b)); + Assert.IsFalse(a.Equals((object)c)); + Assert.IsFalse(a.Equals((object)null)); + Assert.IsFalse(a.Equals((object)1)); // types don't match + } + + [Test] + public void TestGetHashCode() + { + var a = new BsonArray { 1, 2 }; + var hashCode = a.GetHashCode(); + Assert.AreEqual(hashCode, a.GetHashCode()); + } + + [Test] + public void TestIndexer() + { + var array = new BsonArray { 1 }; + Assert.AreEqual(1, array[0]); + } + + + } +} \ No newline at end of file diff --git a/Ejdb.Tests/TestBsonOid.cs b/Ejdb.Tests/TestBsonOid.cs new file mode 100644 index 0000000..1aaac1b --- /dev/null +++ b/Ejdb.Tests/TestBsonOid.cs @@ -0,0 +1,338 @@ +using System; +using System.Linq; +using Ejdb.BSON; +using NUnit.Framework; + +namespace Ejdb.Tests +{ + [TestFixture] + public class TestBsonOid + { + [Test] + public void TestByteArrayConstructor() + { + byte[] bytes = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; + var objectId = new BsonOid(bytes); + Assert.AreEqual(0x01020304, objectId.Timestamp); + Assert.AreEqual(0x050607, objectId.Machine); + Assert.AreEqual(0x0809, objectId.Pid); + Assert.AreEqual(0x0a0b0c, objectId.Increment); + Assert.AreEqual(0x050607, objectId.Machine); + Assert.AreEqual(0x0809, objectId.Pid); + Assert.AreEqual(0x0a0b0c, objectId.Increment); + Assert.AreEqual(BsonConstants.Epoch.AddSeconds(0x01020304), objectId.CreationTime); + Assert.AreEqual("0102030405060708090a0b0c", objectId.ToString()); + Assert.IsTrue(bytes.SequenceEqual(objectId.ToByteArray())); + } + + [Test] + public void TestIntIntShortIntConstructor() + { + byte[] bytes = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; + var objectId = new BsonOid(0x01020304, 0x050607, 0x0809, 0x0a0b0c); + Assert.AreEqual(0x01020304, objectId.Timestamp); + Assert.AreEqual(0x050607, objectId.Machine); + Assert.AreEqual(0x0809, objectId.Pid); + Assert.AreEqual(0x0a0b0c, objectId.Increment); + Assert.AreEqual(0x050607, objectId.Machine); + Assert.AreEqual(0x0809, objectId.Pid); + Assert.AreEqual(0x0a0b0c, objectId.Increment); + Assert.AreEqual(BsonConstants.Epoch.AddSeconds(0x01020304), objectId.CreationTime); + Assert.AreEqual("0102030405060708090a0b0c", objectId.ToString()); + Assert.IsTrue(bytes.SequenceEqual(objectId.ToByteArray())); + } + + [Test] + public void TestIntIntShortIntConstructorWithInvalidIncrement() + { + var objectId = new BsonOid(0, 0, 0, 0x00ffffff); + Assert.AreEqual(0x00ffffff, objectId.Increment); + Assert.Throws(() => { var invalidId = new BsonOid(0, 0, 0, 0x01000000); }); + } + + [Test] + public void TestIntIntShortIntConstructorWithInvalidMachine() + { + var objectId = new BsonOid(0, 0x00ffffff, 0, 0); + Assert.AreEqual(0x00ffffff, objectId.Machine); + Assert.Throws(() => { var invalidId = new BsonOid(0, 0x01000000, 0, 0); }); + } + + [Test] + public void TestPackWithInvalidIncrement() + { + var objectId = new BsonOid(BsonOid.Pack(0, 0, 0, 0x00ffffff)); + Assert.AreEqual(0x00ffffff, objectId.Increment); + Assert.Throws(() => { var invalidId = new BsonOid(BsonOid.Pack(0, 0, 0, 0x01000000)); }); + } + + [Test] + public void TestPackWithInvalidMachine() + { + var objectId = new BsonOid(BsonOid.Pack(0, 0x00ffffff, 0, 0)); + Assert.AreEqual(0x00ffffff, objectId.Machine); + Assert.Throws(() => { var invalidId = new BsonOid(BsonOid.Pack(0, 0x01000000, 0, 0)); }); + } + + [Test] + public void TestDateTimeConstructor() + { + byte[] bytes = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; + var timestamp = BsonConstants.Epoch.AddSeconds(0x01020304); + var objectId = new BsonOid(timestamp, 0x050607, 0x0809, 0x0a0b0c); + Assert.AreEqual(0x01020304, objectId.Timestamp); + Assert.AreEqual(0x050607, objectId.Machine); + Assert.AreEqual(0x0809, objectId.Pid); + Assert.AreEqual(0x0a0b0c, objectId.Increment); + Assert.AreEqual(0x050607, objectId.Machine); + Assert.AreEqual(0x0809, objectId.Pid); + Assert.AreEqual(0x0a0b0c, objectId.Increment); + Assert.AreEqual(BsonConstants.Epoch.AddSeconds(0x01020304), objectId.CreationTime); + Assert.AreEqual("0102030405060708090a0b0c", objectId.ToString()); + Assert.IsTrue(bytes.SequenceEqual(objectId.ToByteArray())); + } + + [Test] + public void TestStringConstructor() + { + byte[] bytes = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; + var objectId = new BsonOid("0102030405060708090a0b0c"); + Assert.AreEqual(0x01020304, objectId.Timestamp); + Assert.AreEqual(0x050607, objectId.Machine); + Assert.AreEqual(0x0809, objectId.Pid); + Assert.AreEqual(0x0a0b0c, objectId.Increment); + Assert.AreEqual(0x050607, objectId.Machine); + Assert.AreEqual(0x0809, objectId.Pid); + Assert.AreEqual(0x0a0b0c, objectId.Increment); + Assert.AreEqual(BsonConstants.Epoch.AddSeconds(0x01020304), objectId.CreationTime); + Assert.AreEqual("0102030405060708090a0b0c", objectId.ToString()); + Assert.IsTrue(bytes.SequenceEqual(objectId.ToByteArray())); + } + + [Test] + public void TestGenerateNewId() + { + // compare against two timestamps in case seconds since epoch changes in middle of test + var timestamp1 = (int)Math.Floor((DateTime.UtcNow - BsonConstants.Epoch).TotalSeconds); + var objectId = BsonOid.GenerateNewId(); + var timestamp2 = (int)Math.Floor((DateTime.UtcNow - BsonConstants.Epoch).TotalSeconds); + Assert.IsTrue(objectId.Timestamp == timestamp1 || objectId.Timestamp == timestamp2); + Assert.IsTrue(objectId.Machine != 0); + Assert.IsTrue(objectId.Pid != 0); + } + + [Test] + public void TestGenerateNewIdWithDateTime() + { + var timestamp = new DateTime(2011, 1, 2, 3, 4, 5, DateTimeKind.Utc); + var objectId = BsonOid.GenerateNewId(timestamp); + Assert.IsTrue(objectId.CreationTime == timestamp); + Assert.IsTrue(objectId.Machine != 0); + Assert.IsTrue(objectId.Pid != 0); + } + + [Test] + public void TestGenerateNewIdWithTimestamp() + { + var timestamp = 0x01020304; + var objectId = BsonOid.GenerateNewId(timestamp); + Assert.IsTrue(objectId.Timestamp == timestamp); + Assert.IsTrue(objectId.Machine != 0); + Assert.IsTrue(objectId.Pid != 0); + } + + [Test] + public void TestIComparable() + { + var objectId1 = BsonOid.GenerateNewId(); + var objectId2 = BsonOid.GenerateNewId(); + Assert.AreEqual(0, objectId1.CompareTo(objectId1)); + Assert.AreEqual(-1, objectId1.CompareTo(objectId2)); + Assert.AreEqual(1, objectId2.CompareTo(objectId1)); + Assert.AreEqual(0, objectId2.CompareTo(objectId2)); + } + + [Test] + public void TestCompareEqualGeneratedIds() + { + var objectId1 = BsonOid.GenerateNewId(); + var objectId2 = objectId1; + Assert.IsFalse(objectId1 < objectId2); + Assert.IsTrue(objectId1 <= objectId2); + Assert.IsFalse(objectId1 != objectId2); + Assert.IsTrue(objectId1 == objectId2); + Assert.IsFalse(objectId1 > objectId2); + Assert.IsTrue(objectId1 >= objectId2); + } + + [Test] + public void TestCompareSmallerTimestamp() + { + var objectId1 = new BsonOid("0102030405060708090a0b0c"); + var objectId2 = new BsonOid("0102030505060708090a0b0c"); + Assert.IsTrue(objectId1 < objectId2); + Assert.IsTrue(objectId1 <= objectId2); + Assert.IsTrue(objectId1 != objectId2); + Assert.IsFalse(objectId1 == objectId2); + Assert.IsFalse(objectId1 > objectId2); + Assert.IsFalse(objectId1 >= objectId2); + } + + [Test] + public void TestCompareSmallerMachine() + { + var objectId1 = new BsonOid("0102030405060708090a0b0c"); + var objectId2 = new BsonOid("0102030405060808090a0b0c"); + Assert.IsTrue(objectId1 < objectId2); + Assert.IsTrue(objectId1 <= objectId2); + Assert.IsTrue(objectId1 != objectId2); + Assert.IsFalse(objectId1 == objectId2); + Assert.IsFalse(objectId1 > objectId2); + Assert.IsFalse(objectId1 >= objectId2); + } + + [Test] + public void TestCompareSmallerPid() + { + var objectId1 = new BsonOid("0102030405060708090a0b0c"); + var objectId2 = new BsonOid("01020304050607080a0a0b0c"); + Assert.IsTrue(objectId1 < objectId2); + Assert.IsTrue(objectId1 <= objectId2); + Assert.IsTrue(objectId1 != objectId2); + Assert.IsFalse(objectId1 == objectId2); + Assert.IsFalse(objectId1 > objectId2); + Assert.IsFalse(objectId1 >= objectId2); + } + + [Test] + public void TestCompareSmallerIncrement() + { + var objectId1 = new BsonOid("0102030405060708090a0b0c"); + var objectId2 = new BsonOid("0102030405060708090a0b0d"); + Assert.IsTrue(objectId1 < objectId2); + Assert.IsTrue(objectId1 <= objectId2); + Assert.IsTrue(objectId1 != objectId2); + Assert.IsFalse(objectId1 == objectId2); + Assert.IsFalse(objectId1 > objectId2); + Assert.IsFalse(objectId1 >= objectId2); + } + + [Test] + public void TestCompareSmallerGeneratedId() + { + var objectId1 = BsonOid.GenerateNewId(); + var objectId2 = BsonOid.GenerateNewId(); + Assert.IsTrue(objectId1 < objectId2); + Assert.IsTrue(objectId1 <= objectId2); + Assert.IsTrue(objectId1 != objectId2); + Assert.IsFalse(objectId1 == objectId2); + Assert.IsFalse(objectId1 > objectId2); + Assert.IsFalse(objectId1 >= objectId2); + } + + [Test] + public void TestCompareLargerTimestamp() + { + var objectId1 = new BsonOid("0102030405060708090a0b0c"); + var objectId2 = new BsonOid("0102030305060708090a0b0c"); + Assert.IsFalse(objectId1 < objectId2); + Assert.IsFalse(objectId1 <= objectId2); + Assert.IsTrue(objectId1 != objectId2); + Assert.IsFalse(objectId1 == objectId2); + Assert.IsTrue(objectId1 > objectId2); + Assert.IsTrue(objectId1 >= objectId2); + } + + [Test] + public void TestCompareLargerMachine() + { + var objectId1 = new BsonOid("0102030405060808090a0b0c"); + var objectId2 = new BsonOid("0102030405060708090a0b0c"); + Assert.IsFalse(objectId1 < objectId2); + Assert.IsFalse(objectId1 <= objectId2); + Assert.IsTrue(objectId1 != objectId2); + Assert.IsFalse(objectId1 == objectId2); + Assert.IsTrue(objectId1 > objectId2); + Assert.IsTrue(objectId1 >= objectId2); + } + + [Test] + public void TestCompareLargerPid() + { + var objectId1 = new BsonOid("01020304050607080a0a0b0c"); + var objectId2 = new BsonOid("0102030405060708090a0b0c"); + Assert.IsFalse(objectId1 < objectId2); + Assert.IsFalse(objectId1 <= objectId2); + Assert.IsTrue(objectId1 != objectId2); + Assert.IsFalse(objectId1 == objectId2); + Assert.IsTrue(objectId1 > objectId2); + Assert.IsTrue(objectId1 >= objectId2); + } + + [Test] + public void TestCompareLargerIncrement() + { + var objectId1 = new BsonOid("0102030405060708090a0b0d"); + var objectId2 = new BsonOid("0102030405060708090a0b0c"); + Assert.IsFalse(objectId1 < objectId2); + Assert.IsFalse(objectId1 <= objectId2); + Assert.IsTrue(objectId1 != objectId2); + Assert.IsFalse(objectId1 == objectId2); + Assert.IsTrue(objectId1 > objectId2); + Assert.IsTrue(objectId1 >= objectId2); + } + + [Test] + public void TestCompareLargerGeneratedId() + { + var objectId2 = BsonOid.GenerateNewId(); // generate before objectId2 + var objectId1 = BsonOid.GenerateNewId(); + Assert.IsFalse(objectId1 < objectId2); + Assert.IsFalse(objectId1 <= objectId2); + Assert.IsTrue(objectId1 != objectId2); + Assert.IsFalse(objectId1 == objectId2); + Assert.IsTrue(objectId1 > objectId2); + Assert.IsTrue(objectId1 >= objectId2); + } + + [Test] + public void TestParse() + { + byte[] bytes = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; + var objectId1 = BsonOid.Parse("0102030405060708090a0b0c"); // lower case + var objectId2 = BsonOid.Parse("0102030405060708090A0B0C"); // upper case + Assert.IsTrue(objectId1.ToByteArray().SequenceEqual(objectId2.ToByteArray())); + Assert.IsTrue(objectId1.ToString() == "0102030405060708090a0b0c"); // ToString returns lower case + Assert.IsTrue(objectId1.ToString() == objectId2.ToString()); + Assert.Throws(() => BsonOid.Parse("102030405060708090a0b0c")); // too short + Assert.Throws(() => BsonOid.Parse("x102030405060708090a0b0c")); // invalid character + Assert.Throws(() => BsonOid.Parse("00102030405060708090a0b0c")); // too long + } + + [Test] + public void TestTryParse() + { + byte[] bytes = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; + BsonOid objectId1, objectId2; + Assert.IsTrue(BsonOid.TryParse("0102030405060708090a0b0c", out objectId1)); // lower case + Assert.IsTrue(BsonOid.TryParse("0102030405060708090A0B0C", out objectId2)); // upper case + Assert.IsTrue(objectId1.ToByteArray().SequenceEqual(objectId2.ToByteArray())); + Assert.IsTrue(objectId1.ToString() == "0102030405060708090a0b0c"); // ToString returns lower case + Assert.IsTrue(objectId1.ToString() == objectId2.ToString()); + Assert.IsFalse(BsonOid.TryParse("102030405060708090a0b0c", out objectId1)); // too short + Assert.IsFalse(BsonOid.TryParse("x102030405060708090a0b0c", out objectId1)); // invalid character + Assert.IsFalse(BsonOid.TryParse("00102030405060708090a0b0c", out objectId1)); // too long + Assert.IsFalse(BsonOid.TryParse(null, out objectId1)); // should return false not throw ArgumentNullException + } + + [Test] + public void TestConvertObjectIdToObjectId() + { + var oid = BsonOid.GenerateNewId(); + + var oidConverted = Convert.ChangeType(oid, typeof(BsonOid)); + + Assert.AreEqual(oid, oidConverted); + } + } +} \ No newline at end of file diff --git a/Ejdb.Tests/TestBsonSerialization.cs b/Ejdb.Tests/TestBsonSerialization.cs new file mode 100644 index 0000000..2886dc3 --- /dev/null +++ b/Ejdb.Tests/TestBsonSerialization.cs @@ -0,0 +1,437 @@ +// ============================================================================================ +// .NET API for EJDB database library http://ejdb.org +// Copyright (C) 2012-2013 Softmotions Ltd +// +// This file is part of EJDB. +// EJDB is free software; you can redistribute it and/or modify it under the terms of +// the GNU Lesser General Public License as published by the Free Software Foundation; either +// version 2.1 of the License or any later version. EJDB is distributed in the hope +// that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +// License for more details. +// You should have received a copy of the GNU Lesser General Public License along with EJDB; +// if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, +// Boston, MA 02111-1307 USA. +// ============================================================================================ +using System; +using System.Collections.Generic; +using NUnit.Framework; +using Ejdb.BSON; + +namespace Ejdb.Tests { + + [TestFixture] + public class TestBsonSerialization { + + [Test] + public void SerializeEmpty() + { + var doc = new BsonDocument(); + Assert.AreEqual("05-00-00-00-00", doc.ToDebugDataString()); + } + + [Test] + public void SerializeEmpty_WithoutSeek() + { + using (var stream = new MockStream(canSeek: false)) + { + var doc = new BsonDocument(); + doc.Serialize(stream); + + var bytes = BitConverter.ToString(stream.ToArray()); + Assert.AreEqual("05-00-00-00-00", bytes); + } + } + + [Test] + public void SerializeNumber() + { + byte[] bdata; + BsonDocument doc = new BsonDocument(); + doc.Add("0", BsonValue.Create(1)); + //0C-00-00-00 len + //10 type + //30-00 key + //01-00-00-00 int val + //00 zero term + bdata = doc.ToByteArray(); + Assert.AreEqual("0C-00-00-00-10-30-00-01-00-00-00-00", doc.ToDebugDataString()); + Assert.AreEqual(bdata.Length, (int) Convert.ToByte(doc.ToDebugDataString().Substring(0, 2), 16)); + } + + [Test] + public void CreateBsonDocument() + { + var doc = new BsonDocument(); + doc.Add("0", BsonValue.Create(1)); + + var doc2 = new BsonDocument(doc.ToByteArray()); + Assert.AreEqual(1, doc2.KeysCount); + Assert.AreEqual(1, doc["0"]); + + doc2.Add("0", BsonValue.Create(2)); + Assert.AreEqual(1, doc2.KeysCount); + Assert.AreEqual(2, doc2["0"]); + + doc2.Add("1", BsonValue.Create(Int32.MaxValue)); + + var expected = _PrepareBytesString( + @"13-00-00-00-" // len + + @"10-" // type + + @"30-00-" // key + + @"02-00-00-00-" // int val + + @"10-" // type + + @"31-00-" // key + + @"FF-FF-FF-7F-" // value + + @"00"); // zero term + + Assert.AreEqual(expected, doc2.ToDebugDataString()); + + doc2 = new BsonDocument(doc2); + Assert.AreEqual(expected, doc2.ToDebugDataString()); + + doc2 = new BsonDocument(doc2.ToByteArray()); + Assert.AreEqual(expected, doc2.ToDebugDataString()); + } + + [Test] + public void CreateBsonDocument2() + { + var doc = new BsonDocument(); + doc["a"] = 1; + Assert.AreEqual("0C-00-00-00-10-61-00-01-00-00-00-00", doc.ToDebugDataString()); + } + + [Test] + public void SerializeString() + { + string byteString = @"\x16\x00\x00\x00\x02hello\x00\x06\x00\x00\x00world\x00\x00"; + byte[] bytes = DecodeByteString(byteString); + + var doc = new BsonDocument(bytes); + Assert.AreEqual(1, doc.KeysCount); + + var value = doc["hello"]; + Assert.AreEqual("world", value); + } + + + [Test] + public void SerializeArrayWithVariousTypes() + { + string byteString = @"1\x00\x00\x00\x04BSON\x00&\x00\x00\x00\x020\x00\x08\x00\x00\x00awesome\x00\x011\x00333333\x14@\x102\x00\xc2\x07\x00\x00\x00\x00"; + byte[] bytes = DecodeByteString(byteString); + + var doc = new BsonDocument(bytes); + Assert.AreEqual(1, doc.KeysCount); + var array = (BsonArray)doc["BSON"]; + + Assert.AreEqual("awesome", array[0]); + Assert.AreEqual(5.05, array[1]); + Assert.AreEqual(1986, array[2]); + } + + [Test] + public void SerializeDateTimeUtc() + { + var dateTime = DateTime.SpecifyKind(new DateTime(2010, 1, 1), DateTimeKind.Utc); + _TestSerializeDateTime(dateTime); + } + + [Test] + public void SerializeDateTimeLocal() + { + var dateTime = DateTime.SpecifyKind(new DateTime(2010, 1, 1), DateTimeKind.Local); + _TestSerializeDateTime(dateTime); + } + + private static void _TestSerializeDateTime(DateTime dateTime) + { + var document = new BsonDocument + { + {"date", dateTime} + }; + + var rehydrated = new BsonDocument(document.ToByteArray()); + var dateTime2 = (DateTime) rehydrated["date"]; + Assert.AreEqual(dateTime.ToUniversalTime(), dateTime2.ToUniversalTime()); + } + + + + [Test] + public void SerializeAnonmyousType() + { + var doc = BsonDocument.ValueOf(new {a = "b", c = 1}); + + var expected = _PrepareBytesString( + @"15-00-00-00- " // len + + @"02- " // type 'string' + + @"61-00- " // key 'a' + + @"02-00-00-00- " // length of value 'b' + + @"62-00- " // value 'b' + + @"10- " // type 'int' + + @"63-00- " // key 'c' + + @"01-00-00-00- " // value 1 + + @"00"); // zero term + + Assert.AreEqual(expected, doc.ToDebugDataString()); + } + + [Test] + public void SerializeNamedType() + { + var doc = BsonDocument.ValueOf(new DemoType { a = "b", c = 1 }); + + var expected = _PrepareBytesString( + @"15-00-00-00- " // len + + @"02- " // type 'string' + + @"61-00- " // key 'a' + + @"02-00-00-00- " // length of value 'b' + + @"62-00- " // value 'b' + + @"10- " // type 'int' + + @"63-00- " // key 'c' + + @"01-00-00-00- " // value 1 + + @"00"); // zero term + + Assert.AreEqual(expected, doc.ToDebugDataString()); + } + + class DemoType + { + public string a { get; set; } + public int c { get; set; } + } + + [Test] + public void SerializeRegex() + { + var doc = BsonDocument.ValueOf(new + { + e = new BsonRegexp("r1", "o2") + }); + + var expected = _PrepareBytesString( + @"0E-00-00-00- " // len + + @"0B- " // type 'regex' + + @"65-00- " // key 'e' + + @"72-31-00- " // re 'r1' + term + + @"6F-32-00- " // opt 'o2' + term + + @"00"); // zero term + + Assert.AreEqual(expected, doc.ToDebugDataString()); + } + + [Test] + public void TestIterate1() + { + var doc = new BsonDocument(); + doc["a"] = "av"; + doc["bb"] = 24; + + //doc["ccc"] = BsonDocument.ValueOf(new{na1 = 1, nb = "2"}); + //doc["d"] = new BsonOidOld("51b9f3af98195c4600000000"); + + //17-00-00-00 +4 + //02-61-00-03-00-00-00-61-76-00 +10 + //10-62-62-00-18-00-00-00 +8 + //00 +1 + Assert.AreEqual("17-00-00-00-02-61-00-03-00-00-00-61-76-00-10-62-62-00-18-00-00-00-00", + doc.ToDebugDataString()); + BsonIterator it = new BsonIterator(doc); + Assert.AreEqual(doc.ToByteArray().Length, it.DocumentLength); + var c = ""; + while (it.Next() != BsonType.EOO) { + c += it.CurrentKey; + } + Assert.AreEqual("abb", c); + it.Dispose(); + + it = new BsonIterator(doc); + var cnt = 0; + while (it.Next() != BsonType.EOO) { + BsonValue bv = it.FetchCurrentValue(); + Assert.IsNotNull(bv); + if (cnt == 0) { + Assert.IsTrue(bv.BSONType == BsonType.STRING); + Assert.IsTrue(it.CurrentKey == "a"); + Assert.AreEqual("av", bv.Value); + } + if (cnt == 1) { + Assert.IsTrue(bv.BSONType == BsonType.INT); + Assert.IsTrue(it.CurrentKey == "bb"); + Assert.AreEqual(24, bv.Value); + } + cnt++; + } + } + + [Test] + public void TestIterate2() { + var doc = new BsonDocument(); + doc["a"] = "av"; + doc["b"] = BsonDocument.ValueOf(new{cc = 1}); + doc["d"] = new BsonOid("51b9f3af98195c4600000000"); + Assert.AreEqual(3, doc.KeysCount); + //Console.WriteLine(doc.KeysCount); + //Console.WriteLine(doc.ToDebugDataString()); + //2E-00-00-00 +4 + //02-61-00-03-00-00-00-61-76-00 +10 (14) + //03-62-00 +3 (17) "d" = + //0D-00-00-00 +4 (21) doc len = 13 + //10-63-63-00-01-00-00-00 -00 +9 (30) + //07-64-00 +3 (33) + //51-B9-F3-AF-98-19-5C-46-00-00-00-00 +12 (45) + //00 +1 (46) + Assert.AreEqual("2E-00-00-00-" + + "02-61-00-03-00-00-00-61-76-00-" + + "03-62-00-" + + "0D-00-00-00-" + + "10-63-63-00-01-00-00-00-00-" + + "07-64-00-" + + "51-B9-F3-AF-98-19-5C-46-00-00-00-00-" + + "00", doc.ToDebugDataString()); + BsonIterator it = new BsonIterator(doc); + int c = 0; + foreach (var bt in it) { + if (c == 0) { + Assert.IsTrue(bt == BsonType.STRING); + } + if (c == 1) { + Assert.IsTrue(bt == BsonType.OBJECT); + } + if (c == 2) { + Assert.IsTrue(bt == BsonType.OID); + } + ++c; + } + bool thrown = false; + Assert.IsTrue(it.Disposed); + try { + it.Next(); + } catch (ObjectDisposedException) { + thrown = true; + } + Assert.IsTrue(thrown); + + c = 0; + it = new BsonIterator(doc); + foreach (var bv in it.Values()) { + if (c == 0) { + Assert.AreEqual("a", it.CurrentKey); + Assert.AreEqual("av", bv.Value); + } + if (c == 1) { + Assert.AreEqual("b", it.CurrentKey); + BsonDocument sdoc = bv.Value as BsonDocument; + Assert.IsNotNull(sdoc); + var it2 = new BsonIterator(sdoc); + foreach (var bv2 in it2.Values()) { + Assert.AreEqual("cc", it2.CurrentKey); + Assert.AreEqual(1, bv2.Value); + Assert.AreEqual(BsonType.INT, bv2.BSONType); + } + } + if (c == 2) { + Assert.AreEqual(BsonType.OID, bv.BSONType); + Assert.IsInstanceOf(typeof(BsonOid), bv.Value); + var oid = bv.Value as BsonOid; + Assert.AreEqual("51b9f3af98195c4600000000", oid.ToString()); + } + c++; + } + } + + [Test] + public void TestIterateRE() { + var doc = new BsonDocument(); + doc["a"] = new BsonRegexp("b", "c"); + doc["d"] = 1; + doc["e"] = BsonDocument.ValueOf(new {f = new BsonRegexp("g", "")}); + doc["h"] = 2; + //28-00-00-00 + //0B-61-00-62-00-63-00 + //10-64-00-01-00-00-00 + //03-65-00-0B-00-00-00 + //0B-66-00-67-00-00-00 + //10-68-00-02-00-00-00-00 + var cs = ""; + foreach (var bt in new BsonIterator(doc)) { + cs += bt.ToString(); + } + Assert.AreEqual("REGEXINTOBJECTINT", cs); + cs = ""; + + var it = new BsonIterator(doc); + foreach (var bv in it.Values()) { + if (it.CurrentKey == "a") { + cs += ((BsonRegexp) bv.Value).Re; + cs += ((BsonRegexp) bv.Value).Opts; + } else { + cs += bv.Value; + } + } + Assert.AreEqual("bc1[BsonDocument: [BsonValue: BsonType=REGEX, Key=f, Value=[BsonRegexp: re=g, opts=]]]2", cs); + } + + [Test] + public void TestFilteredDoc() { + var doc = new BsonDocument(); + doc["c"] = "d"; + doc["aaa"] = 11; + doc["ndoc"] = BsonDocument.ValueOf(new { + aaaa = "nv1", + d = "nv2", + nnd = BsonDocument.ValueOf(new { + nnv = true, + nns = "s" + }) + }); + doc["ndoc2"] = BsonDocument.ValueOf(new { + n = "v" + }); + doc["f"] = "f"; + BsonIterator it = new BsonIterator(doc); + BsonDocument doc2 = it.ToBsonDocument("c", "ndoc.d", "ndoc.nnd.nns", "f"); + Assert.AreEqual(3, doc2.KeysCount); + Assert.AreEqual("d", doc2["c"]); + Assert.AreEqual(2, ((BsonDocument) doc2["ndoc"]).KeysCount); + Assert.AreEqual("nv2", ((BsonDocument) doc2["ndoc"])["d"]); + Assert.AreEqual("s", ((BsonDocument) ((BsonDocument) doc2["ndoc"])["nnd"])["nns"]); + Assert.AreEqual("s", doc2["ndoc.nnd.nns"]); + Assert.AreEqual("f", "f"); + //Console.WriteLine("doc2=" + doc2); + } + + + private byte[] DecodeByteString(string byteString) + { + List bytes = new List(byteString.Length); + for (int i = 0; i < byteString.Length; ) + { + char c = byteString[i++]; + if (c == '\\' && ((c = byteString[i++]) != '\\')) + { + int x = __hexDigits.IndexOf(char.ToLower(byteString[i++])); + int y = __hexDigits.IndexOf(char.ToLower(byteString[i++])); + bytes.Add((byte)(16 * x + y)); + } + else + { + bytes.Add((byte)c); + } + } + return bytes.ToArray(); + } + + private static string _PrepareBytesString(string bytesString) + { + return bytesString.Replace(" ", "").Replace("\t", ""); + } + + private static string __hexDigits = "0123456789abcdef"; + } + + +} + diff --git a/Ejdb.Tests/TestBsonStringSerialization.cs b/Ejdb.Tests/TestBsonStringSerialization.cs new file mode 100644 index 0000000..30673b5 --- /dev/null +++ b/Ejdb.Tests/TestBsonStringSerialization.cs @@ -0,0 +1,133 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using Ejdb.BSON; +using Ejdb.IO; +using NUnit.Framework; + +namespace Ejdb.Tests +{ + [TestFixture] + public class TestBsonStringSerialization + { + [Test] + public void ReadCStringEmpty() + { + var bytes = new byte[] { 8, 0, 0, 0, (byte)BsonType.BOOL, 0, 0, 0 }; + Assert.AreEqual(8, bytes.Length); + + var document = new BsonDocument(bytes); + Assert.AreEqual("", document.First().Key); + } + + [Test] + public void TestReadCStringOneCharacter() + { + var bytes = new byte[] { 9, 0, 0, 0, (byte)BsonType.BOOL, (byte)'b', 0, 0, 0 }; + Assert.AreEqual(9, bytes.Length); + var document = new BsonDocument(bytes); + Assert.AreEqual("b", document.First().Key); + } + + [Test] + public void TestReadCStringOneCharacterDecoderException() + { + var bytes = new byte[] { 9, 0, 0, 0, (byte)BsonType.BOOL, 0x80, 0, 0, 0 }; + Assert.AreEqual(9, bytes.Length); + Assert.Throws(() => new BsonDocument(bytes)); + } + + [Test] + public void TestReadCStringTwoCharacters() + { + var bytes = new byte[] { 10, 0, 0, 0, (byte)BsonType.BOOL, (byte)'b', (byte)'b', 0, 0, 0 }; + Assert.AreEqual(10, bytes.Length); + var document = new BsonDocument(bytes); + Assert.AreEqual("bb", document.First().Key); + } + + [Test] + public void TestReadCStringTwoCharactersDecoderException() + { + var bytes = new byte[] { 10, 0, 0, 0, (byte)BsonType.BOOL, (byte)'b', 0x80, 0, 0, 0 }; + Assert.AreEqual(10, bytes.Length); + var ex = Assert.Throws(() => { new BsonDocument(bytes); }); + } + + [Test] + public void TestReadStringEmpty() + { + var bytes = new byte[] { 13, 0, 0, 0, (byte)BsonType.STRING, (byte)'s', 0, 1, 0, 0, 0, 0, 0 }; + Assert.AreEqual(13, bytes.Length); + var document = new BsonDocument(bytes); + Assert.AreEqual("", document["s"]); + } + + [Test] + public void TestReadStringInvalidLength() + { + var bytes = new byte[] { 13, 0, 0, 0, (byte)BsonType.STRING, (byte)'s', 0, 0, 0, 0, 0, 0, 0 }; + Assert.AreEqual(13, bytes.Length); + var ex = Assert.Throws(() => { new BsonDocument(bytes); }); + Assert.AreEqual("Invalid string length: 0 (the length includes the null terminator so it must be greater than or equal to 1).", ex.Message); + } + + [Test] + public void TestReadStringMissingNullTerminator() + { + var bytes = new byte[] { 13, 0, 0, 0, (byte)BsonType.STRING, (byte)'s', 0, 1, 0, 0, 0, 123, 0 }; + Assert.AreEqual(13, bytes.Length); + var ex = Assert.Throws(() => { new BsonDocument(bytes); }); + Assert.AreEqual("String is missing null terminator.", ex.Message); + } + + [Test] + public void TestReadStringOneCharacter() + { + var bytes = new byte[] { 14, 0, 0, 0, (byte)BsonType.STRING, (byte)'s', 0, 2, 0, 0, 0, (byte)'x', 0, 0 }; + Assert.AreEqual(14, bytes.Length); + var document = new BsonDocument(bytes); + Assert.AreEqual("x", document["s"]); + } + + [Test] + public void TestReadStringOneCharacterDecoderException() + { + var bytes = new byte[] { 14, 0, 0, 0, (byte)BsonType.STRING, (byte)'s', 0, 2, 0, 0, 0, 0x80, 0, 0 }; + Assert.AreEqual(14, bytes.Length); + var ex = Assert.Throws(() => { new BsonDocument(bytes); }); + } + + [Test] + public void TestReadStringTwoCharacters() + { + var bytes = new byte[] { 15, 0, 0, 0, (byte)BsonType.STRING, (byte)'s', 0, 3, 0, 0, 0, (byte)'x', (byte)'y', 0, 0 }; + Assert.AreEqual(15, bytes.Length); + var document = new BsonDocument(bytes); + Assert.AreEqual("xy", document["s"]); + } + + [Test] + public void TestReadStringTwoCharactersDecoderException() + { + var bytes = new byte[] { 15, 0, 0, 0, (byte)BsonType.STRING, (byte)'s', 0, 3, 0, 0, 0, (byte)'x', 0x80, 0, 0 }; + Assert.AreEqual(15, bytes.Length); + var ex = Assert.Throws(() => { new BsonDocument(bytes); }); + } + + [Test] + public void TestWriteCStringThrowsWhenValueContainsNulls() + { + var writer = new ExtBinaryWriter(new MemoryStream()); + Assert.Throws(() => writer.WriteCString("a\0b")); + } + + [Test] + public void TestWriteCStringThrowsWhenValueIsNull() + { + var writer = new ExtBinaryWriter(new MemoryStream()); + Assert.Throws(() => writer.WriteCString(null)); + } + } +} \ No newline at end of file diff --git a/Ejdb.Tests/TestCollectionJoins.cs b/Ejdb.Tests/TestCollectionJoins.cs new file mode 100644 index 0000000..6e61e5f --- /dev/null +++ b/Ejdb.Tests/TestCollectionJoins.cs @@ -0,0 +1,71 @@ +using System; +using System.Linq; +using Ejdb.BSON; +using Ejdb.DB; +using NUnit.Framework; +using System.Collections; + +namespace Ejdb.Tests +{ + [TestFixture] + public class TestCollectionJoins + { + private const string ORDERS = "orders"; + private const string CARS = "cars"; + private const string TOPCARS = "topcars"; + + private EJDB _db; + private BsonDocument _car1; + private BsonDocument _car2; + private BsonDocument _car3; + + [TestFixtureSetUp] + public void Setup() + { + _db = new EJDB("testdb1", EJDB.DEFAULT_OPEN_MODE | EJDB.JBOTRUNC); + + _car1 = BsonDocument.ValueOf(new { model = "Honda Accord", year = 2005 }); + _car2 = BsonDocument.ValueOf(new { model = "Toyota Corolla", year = 2011 }); + _car3 = BsonDocument.ValueOf(new { model = "Toyota Camry", year = 2008 }); + _db.Save(CARS, _car1, _car2, _car3); + + var order1 = BsonDocument.ValueOf(new { car = _car1["_id"], pickUpDate = new DateTime(2013, 05, 20), customer = "andy" }); + var order2 = BsonDocument.ValueOf(new { car = _car2["_id"], pickUpDate = new DateTime(2013, 05, 23), customer = "john" }); + var order3 = BsonDocument.ValueOf(new { car = _car2["_id"], pickUpDate = new DateTime(2013, 05, 25), customer = "antony" }); + _db.Save(ORDERS, order1, order2, order3); + + var topCarsJune = BsonDocument.ValueOf(new { month = "June", cars = new[] { _car1["_id"], _car2["_id"] } }); + _db.Save(TOPCARS, topCarsJune); + } + + [Test] + public void FindOrdersWithCars() + { + var ordersWithCars = _db.Find(ORDERS, Query.Join("car", CARS)).Select(x => x.ToBsonDocument()).ToArray(); + Assert.That(ordersWithCars.Length, Is.EqualTo(3)); + _CheckCar(ordersWithCars[0], _car1); + _CheckCar(ordersWithCars[1], _car2); + _CheckCar(ordersWithCars[2], _car2); + } + + [Test] + public void FindTopCars() + { + var query = Query + .Join("cars", CARS) + .EQ("month", "June"); + + var juneTopCars = _db.Find(TOPCARS, query).Single().ToBsonDocument(); + + var topCars = ((object[]) juneTopCars["cars"]).Cast().ToArray(); + Assert.That(topCars[0].Equals(_car1)); + Assert.That(topCars[1].Equals(_car2)); + } + + private void _CheckCar(BsonDocument orderWithCar, BsonDocument expectedCar) + { + var car = (BsonDocument)orderWithCar["car"]; + Assert.That(car.Equals(expectedCar)); + } + } +} \ No newline at end of file diff --git a/Ejdb.Tests/TestEJDB.cs b/Ejdb.Tests/TestEJDB.cs index b8b916b..50bee05 100644 --- a/Ejdb.Tests/TestEJDB.cs +++ b/Ejdb.Tests/TestEJDB.cs @@ -49,24 +49,25 @@ public void Test2EnsureCollection() { public void Test3SaveLoad() { EJDB jb = new EJDB("testdb1", EJDB.DEFAULT_OPEN_MODE | EJDB.JBOTRUNC); Assert.IsTrue(jb.IsOpen); - BSONDocument doc = new BSONDocument().SetNumber("age", 33); - Assert.IsNull(doc["_id"]); + BsonDocument doc = new BsonDocument(); + doc.Add("age", BsonValue.Create(33)); + Assert.IsNull(doc[BsonConstants.Id]); bool rv = jb.Save("mycoll", doc); Assert.IsTrue(rv); Assert.IsNotNull(doc["_id"]); - Assert.IsInstanceOf(typeof(BSONOid), doc["_id"]); + Assert.IsInstanceOf(typeof(BsonOid), doc["_id"]); rv = jb.Save("mycoll", doc); Assert.IsTrue(rv); - BSONIterator it = jb.Load("mycoll", doc["_id"] as BSONOid); + BsonIterator it = jb.Load("mycoll", doc["_id"] as BsonOid); Assert.IsNotNull(it); - BSONDocument doc2 = it.ToBSONDocument(); + BsonDocument doc2 = it.ToBsonDocument(); Assert.AreEqual(doc.ToDebugDataString(), doc2.ToDebugDataString()); Assert.IsTrue(doc == doc2); Assert.AreEqual(1, jb.CreateQueryFor("mycoll").Count()); - Assert.IsTrue(jb.Remove("mycoll", doc["_id"] as BSONOid)); + Assert.IsTrue(jb.Remove("mycoll", doc["_id"] as BsonOid)); Assert.AreEqual(0, jb.CreateQueryFor("mycoll").Count()); jb.Save("mycoll", doc); @@ -82,21 +83,22 @@ public void Test3SaveLoad() { public void Test4Q1() { EJDB jb = new EJDB("testdb1", EJDB.DEFAULT_OPEN_MODE | EJDB.JBOTRUNC); Assert.IsTrue(jb.IsOpen); - BSONDocument doc = new BSONDocument().SetNumber("age", 33); + var doc = new BsonDocument(); + doc.Add("age", BsonValue.Create(33)); Assert.IsNull(doc["_id"]); bool rv = jb.Save("mycoll", doc); Assert.IsTrue(rv); Assert.IsNotNull(doc["_id"]); - EJDBQuery q = jb.CreateQuery(BSONDocument.ValueOf(new{age = 33}), "mycoll"); + EJDBQuery q = jb.CreateQuery(BsonDocument.ValueOf(new{age = 33}), "mycoll"); Assert.IsNotNull(q); using (EJDBQCursor cursor = q.Find()) { Assert.IsNotNull(cursor); Assert.AreEqual(1, cursor.Length); int c = 0; - foreach (BSONIterator oit in cursor) { + foreach (BsonIterator oit in cursor) { c++; Assert.IsNotNull(oit); - BSONDocument rdoc = oit.ToBSONDocument(); + BsonDocument rdoc = oit.ToBsonDocument(); Assert.IsTrue(rdoc.HasKey("_id")); Assert.AreEqual(33, rdoc["age"]); } @@ -119,44 +121,45 @@ public void Test4Q1() { } [Test] - public void Test4Q2() { + public void Test4Q2() + { EJDB jb = new EJDB("testdb1", EJDB.DEFAULT_OPEN_MODE | EJDB.JBOTRUNC); Assert.IsTrue(jb.IsOpen); - var parrot1 = BSONDocument.ValueOf(new{ + var parrot1 = BsonDocument.ValueOf(new{ name = "Grenny", type = "African Grey", male = true, age = 1, birthdate = DateTime.Now, likes = new string[] { "green color", "night", "toys" }, - extra1 = BSONull.VALUE + extra1 = BsonNull.VALUE }); - var parrot2 = BSONDocument.ValueOf(new{ + var parrot2 = BsonDocument.ValueOf(new{ name = "Bounty", type = "Cockatoo", male = false, age = 15, birthdate = DateTime.Now, likes = new string[] { "sugar cane" }, - extra1 = BSONull.VALUE + extra1 = BsonNull.VALUE }); Assert.IsTrue(jb.Save("parrots", parrot1, parrot2)); Assert.AreEqual(2, jb.CreateQueryFor("parrots").Count()); var q = jb.CreateQuery(new{ - name = new BSONRegexp("(grenny|bounty)", "i") + name = new BsonRegexp("(grenny|bounty)", "i") }).SetDefaultCollection("parrots").OrderBy("name"); using (var cur = q.Find()) { Assert.AreEqual(2, cur.Length); - var doc = cur[0].ToBSONDocument(); + var doc = cur[0].ToBsonDocument(); Assert.AreEqual("Bounty", doc["name"]); Assert.AreEqual(15, doc["age"]); - doc = cur[1].ToBSONDocument(); + doc = cur[1].ToBsonDocument(); Assert.AreEqual("Grenny", doc["name"]); Assert.AreEqual(1, doc["age"]); } @@ -178,13 +181,13 @@ public void Test4Q2() { Assert.AreEqual(2, q.Count()); //Console.WriteLine(jb.DBMeta); - //[BSONDocument: [BSONValue: BSONType=STRING, Key=file, Value=testdb1], - //[BSONValue: BSONType=ARRAY, Key=collections, Value=[BSONArray: [BSONValue: BSONType=OBJECT, Key=0, Value=[BSONDocument: [BSONValue: BSONType=STRING, Key=name, Value=parrots], [BSONValue: BSONType=STRING, Key=file, Value=testdb1_parrots], [BSONValue: BSONType=LONG, Key=records, Value=2], [BSONValue: BSONType=OBJECT, Key=options, Value=[BSONDocument: [BSONValue: BSONType=LONG, Key=buckets, Value=131071], [BSONValue: BSONType=LONG, Key=cachedrecords, Value=0], [BSONValue: BSONType=BOOL, Key=large, Value=False], [BSONValue: BSONType=BOOL, Key=compressed, Value=False]]], [BSONValue: BSONType=ARRAY, Key=indexes, Value=[BSONArray: ]]]]]]] + //[BsonDocument: [BsonValue: BsonType=STRING, Key=file, Value=testdb1], + //[BsonValue: BsonType=ARRAY, Key=collections, Value=[BsonArray: [BsonValue: BsonType=OBJECT, Key=0, Value=[BsonDocument: [BsonValue: BsonType=STRING, Key=name, Value=parrots], [BsonValue: BsonType=STRING, Key=file, Value=testdb1_parrots], [BsonValue: BsonType=LONG, Key=records, Value=2], [BsonValue: BsonType=OBJECT, Key=options, Value=[BsonDocument: [BsonValue: BsonType=LONG, Key=buckets, Value=131071], [BsonValue: BsonType=LONG, Key=cachedrecords, Value=0], [BsonValue: BsonType=BOOL, Key=large, Value=False], [BsonValue: BsonType=BOOL, Key=compressed, Value=False]]], [BsonValue: BsonType=ARRAY, Key=indexes, Value=[BsonArray: ]]]]]]] q.Dispose(); //Test command execution - BSONDocument cmret = jb.Command(BSONDocument.ValueOf(new { + BsonDocument cmret = jb.Command(BsonDocument.ValueOf(new { ping = "" })); Assert.IsNotNull(cmret); diff --git a/Ejdb.Tests/TestQuery.cs b/Ejdb.Tests/TestQuery.cs new file mode 100644 index 0000000..ab28613 --- /dev/null +++ b/Ejdb.Tests/TestQuery.cs @@ -0,0 +1,192 @@ +using System; +using System.Linq; +using Ejdb.BSON; +using Ejdb.DB; +using NUnit.Framework; + +namespace Ejdb.Tests +{ + [TestFixture] + public class TestQuery + { + private EJDB _db; + private const string COLLECTION_NAME = "results"; + + [TestFixtureSetUp] + public void Setup() + { + _db = new EJDB("testdb1", EJDB.DEFAULT_OPEN_MODE | EJDB.JBOTRUNC); + + var results = new BsonDocument[100]; + for (int i = 0; i < results.Length; i++) + { + results[i] = BsonDocument.ValueOf(new MeasurementResult + { + _id = BsonOid.GenerateNewId(), + MeasuredTemperature = i, + OpticalRotation = i % 10, + UserName = "test" + i, + }); + } + + _db.Save(COLLECTION_NAME, results); + } + + [Test] + public void ComparisonQueries() + { + _QueryResults(1, new QueryBuilder().EQ("MeasuredTemperature", 5)); + _QueryResults(1, Query.EQ(x => x.MeasuredTemperature, 5)); + + _QueryResults(1, Query.EQ("UserName", "test5")); + _QueryResults(1, Query.EQ(x => x.UserName, "test5")); + + _QueryResults(10, Query.EQ("OpticalRotation", 5)); + _QueryResults(10, Query.EQ(x => x.OpticalRotation, 5)); + + _QueryResults(99, Query.NotEquals("MeasuredTemperature", 5)); + _QueryResults(99, Query.NotEquals(x => x.MeasuredTemperature, 5)); + + _QueryResults(5, Query.LT("MeasuredTemperature", 5)); + _QueryResults(5, Query.LT(x => x.MeasuredTemperature, 5)); + + _QueryResults(6, Query.LTE("MeasuredTemperature", 5)); + _QueryResults(6, Query.LTE(x => x.MeasuredTemperature, 5)); + + _QueryResults(10, Query.Between("MeasuredTemperature", 1, 10)); + _QueryResults(10, Query.Between(x => x.MeasuredTemperature, 1, 10)); + + _QueryResults(2, Query.In("MeasuredTemperature", 47, 85)); + _QueryResults(2, Query.In(x => x.MeasuredTemperature, 47, 85)); + + _QueryResults(98, Query.NotIn("MeasuredTemperature", 47, 85)); + _QueryResults(98, Query.NotIn(x => x.MeasuredTemperature, 47, 85)); + + _QueryResults(11, Query.BeginsWith("UserName", "test1")); + _QueryResults(11, Query.BeginsWith(x => x.UserName, "test1")); + + _QueryResults(1, Query.EqualsIgnoreCase("UserName", "TeSt5")); + _QueryResults(1, Query.EqualsIgnoreCase(x => x.UserName, "TeSt5")); + } + + [Test] + public void OrQuery() + { + _QueryResults(11, Query.Or( + Query.GT("MeasuredTemperature", 98), + Query.EQ("OpticalRotation", 1) + )); + + _QueryResults(11, Query.Or( + Query.GT(x => x.MeasuredTemperature, 98), + Query.EQ(x => x.OpticalRotation, 1) + )); + } + + [Test] + public void AndQuery() + { + _QueryResults(5, Query + .GT("MeasuredTemperature", 50) + .EQ("OpticalRotation", 5)); + + _QueryResults(5, Query + .GT(x => x.MeasuredTemperature, 50) + .EQ(x => x.OpticalRotation, 5)); + } + + + [Test] + public void StringComparisonQueries() + { + _QueryResults(11, Query.BeginsWith("UserName", "test1")); + _QueryResults(11, Query.BeginsWith(x => x.UserName, "test1")); + + _QueryResults(1, Query.EqualsIgnoreCase("UserName", "TeSt5")); + _QueryResults(1, Query.EqualsIgnoreCase(x => x.UserName, "TeSt5")); + } + + [Test] + public void StringMatchesAny_WithStringField() + { + _QueryResults(1, Query.StringMatchesAnyTokens("UserName", "test47")); + _QueryResults(2, Query.StringMatchesAnyTokens("UserName", "test47", "test49")); + + } + + [Test] + public void StringMatchesAll_WithStringField() + { + _QueryResults(0, Query.StringMatchesAllTokens("UserName", "test47", "test49")); + _QueryResults(1, Query.StringMatchesAllTokens("UserName", "test47")); + } + + [Test] + public void StringMatchesAny_WithArrayField() + { + var doc1 = new BsonDocument() + .Add("likes", new[] { "apple", "peach", "grapefruit" }); + + var doc2 = new BsonDocument() + .Add("likes", new[] { "apple", "peach", "mango" }); + + _db.Save(COLLECTION_NAME, doc1, doc2); + + _QueryResults(2, Query.StringMatchesAnyTokens("likes", "apple")); + _QueryResults(2, Query.StringMatchesAnyTokens("likes", "apple", "peach")); + + var results1 = _QueryResults(1, Query.StringMatchesAnyTokens("likes", "grapefruit")); + Assert.IsTrue(results1.Single().Equals(doc1)); + + var results2 = _QueryResults(1, Query.StringMatchesAnyTokens("likes", "mango")); + Assert.IsTrue(results2.Single().Equals(doc2)); + } + + [Test] + public void StringMatchesAll_WithArrayField() + { + var doc1 = new BsonDocument() + .Add("likes", new[] { "apple", "peach", "grapefruit" }); + + var doc2 = new BsonDocument() + .Add("likes", new[] { "apple", "peach", "mango" }); + + _db.Save(COLLECTION_NAME, doc1, doc2); + + _QueryResults(2, Query.StringMatchesAllTokens("likes", "peach")); + + // why do these checks fail? + // _QueryResults(2, Query.StringMatchesAllTokens("likes", "apple", "peach")); + // _QueryResults(1, Query.StringMatchesAllTokens("likes", "apple", "peach", "grapefruit")); + + } + + private BsonDocument[] _QueryResults(int expectedResultCount, IQuery query) + { + Console.WriteLine("Running query .."); + var cursor = _db.Find("results", query); + var results = cursor.Select(x => x.ToBsonDocument()).ToArray(); + Console.WriteLine("Query finished ({0} results).", results.Length); + + if (results.Length != expectedResultCount) + Assert.Fail("Expected {0} results, but got {1}", expectedResultCount, results.Length); + + return results; + } + } + + internal class Customer + { + public string FirstName { get; set; } + public string LastName { get; set; } + public int Age { get; set; } + } + + internal class MeasurementResult + { + public BsonOid _id { get; set; } + public int MeasuredTemperature { get; set; } + public int OpticalRotation { get; set; } + public string UserName { get; set; } + } +} \ No newline at end of file diff --git a/Ejdb.Tests/TestQueryElementMatch.cs b/Ejdb.Tests/TestQueryElementMatch.cs new file mode 100644 index 0000000..68f50ea --- /dev/null +++ b/Ejdb.Tests/TestQueryElementMatch.cs @@ -0,0 +1,77 @@ +using System.Linq; +using Ejdb.BSON; +using Ejdb.DB; +using NUnit.Framework; + +namespace Ejdb.Tests +{ + [TestFixture] + public class TestQueryElementMatch + { + private const string COLLECTION_NAME = "records"; + + private EJDB _db; + + [TestFixtureSetUp] + public void Setup() + { + _db = new EJDB("testdb1", EJDB.DEFAULT_OPEN_MODE | EJDB.JBOTRUNC); + + var city1 = new BsonDocument() + .Add("number", 1) + .Add("zipcode", 109) + .Add("students", new[] { + BsonDocument.ValueOf(new { name = "john", school = 102, age = 10 }), + BsonDocument.ValueOf(new { name = "jess", school = 102, age = 11 }), + BsonDocument.ValueOf(new { name = "jeff", school = 108, age = 15 }) + }); + + var city2 = new BsonDocument() + .Add("number", 2) + .Add("zipcode", 110) + .Add("students", new[] { + BsonDocument.ValueOf(new { name = "ajax", school = 100, age = 7 }), + BsonDocument.ValueOf(new { name = "achilles", school = 100, age = 8 }), + }); + + var city3 = new BsonDocument() + .Add("number", 3) + .Add("zipcode", 109) + .Add("students", new[] { + BsonDocument.ValueOf(new { name = "ajax", school = 100, age = 7 }), + BsonDocument.ValueOf(new { name = "achilles", school = 100, age = 8 }), + }); + + var city4 = new BsonDocument() + .Add("number", 4) + .Add("zipcode", 109) + .Add("students", new[] { + BsonDocument.ValueOf(new { name = "barney", school = 102, age = 7 }) + }); + + _db.Save(COLLECTION_NAME, city1, city2, city3, city4); + } + + + [Test] + public void Example1() + { + var results = _db.Find(COLLECTION_NAME, Query + .EQ("zipcode", 109) + .ElemMatch("students", Query.EQ("school", 102) + )).Select(x => x.ToBsonDocument()).ToArray(); + + Assert.That(results.Length, Is.EqualTo(2)); + + // this is different from MongoDb + Assert.That(_GetNumberOfStudents(results[0]), Is.EqualTo(3)); // should be 1 + Assert.That(_GetNumberOfStudents(results[1]), Is.EqualTo(1)); + } + + private int _GetNumberOfStudents(BsonDocument document) + { + var array = (object[])document["students"]; + return array.Length; + } + } +} \ No newline at end of file diff --git a/Ejdb.Tests/TestUpdate.cs b/Ejdb.Tests/TestUpdate.cs new file mode 100644 index 0000000..6627b47 --- /dev/null +++ b/Ejdb.Tests/TestUpdate.cs @@ -0,0 +1,147 @@ +using Ejdb.BSON; +using Ejdb.DB; +using NUnit.Framework; + +namespace Ejdb.Tests +{ + [TestFixture] + public class TestUpdate + { + private EJDB _db; + private const string COLLECTION_NAME = "results"; + + private static readonly BsonOid _doc1Id = BsonOid.GenerateNewId(); + private static readonly BsonOid _doc2Id = BsonOid.GenerateNewId(); + + [TestFixtureSetUp] + public void FixtureSetup() + { + _db = new EJDB("testdb1", EJDB.DEFAULT_OPEN_MODE | EJDB.JBOTRUNC); + } + + [SetUp] + public void Setup() + { + _db.DropCollection(COLLECTION_NAME); + + var doc1 = new BsonDocument() + .Add("x", 1) + .Add("y", new[] { 1, 2, 3 }) + .Add("_id", _doc1Id); + + _db.Save(COLLECTION_NAME, doc1); + + var doc2 = new BsonDocument() + .Add("x", 2) + .Add("y", new[] { 2, 3, 4 }) + .Add("_id", _doc2Id); + + _db.Save(COLLECTION_NAME, doc2); + } + + [Test] + public void Set() + { + _Execute(Query.EQ("x", 1), Update.Set("x", 10), 1); + _TestDocumentValues(10, 2); + } + + [Test] + public void UpdateSet_EmptyQueryDocument() + { + _Execute(Query.Empty, Update.Set("x", 10), 2); + _TestDocumentValues(10, 10); + } + + [Test] + public void Set_NullQuery() + { + _Execute(null, Update.Set("x", 10), 2); + _TestDocumentValues(10, 10); + } + + [Test] + public void Inc() + { + _Execute(Query.Empty, Update.Inc("x", 10), 2); + _TestDocumentValues(11, 12); + } + + [Test] + public void DropAll() + { + _Execute(Query.Empty, Update.DropAll(), 2); + Assert.That(_db.Find(COLLECTION_NAME, Query.Empty).Length, Is.EqualTo(0)); + } + + [Test] + public void Pull() + { + _Execute(Query.Empty, Update.Pull("y", 1), 2); + + var doc1 = _GetDocument(_doc1Id); + Assert.That(doc1["y"], Is.EqualTo(new[] { 2, 3})); + + var doc2 = _GetDocument(_doc2Id); + Assert.That(doc2["y"], Is.EqualTo(new[] { 2, 3, 4 })); + } + + [Test] + public void PullAll() + { + _Execute(Query.Empty, Update.PullAll("y", 1, 3), 2); + + var doc1 = _GetDocument(_doc1Id); + Assert.That(doc1["y"], Is.EqualTo(new[] { 2 })); + + var doc2 = _GetDocument(_doc2Id); + Assert.That(doc2["y"], Is.EqualTo(new[] { 2, 4 })); + } + + [Test] + public void AddToSet() + { + _Execute(Query.Empty, Update.AddToSet("y", 4), 2); + + var doc1 = _GetDocument(_doc1Id); + Assert.That(doc1["y"], Is.EqualTo(new[] { 1, 2, 3, 4 })); + + var doc2 = _GetDocument(_doc2Id); + Assert.That(doc2["y"], Is.EqualTo(new[] { 2, 3, 4 })); + } + + [Test] + public void AddToSetAll() + { + _Execute(Query.Empty, Update.AddToSetAll("y", 4, 5), 2); + + var doc1 = _GetDocument(_doc1Id); + Assert.That(doc1["y"], Is.EqualTo(new[] { 1, 2, 3, 4, 5 })); + + var doc2 = _GetDocument(_doc2Id); + Assert.That(doc2["y"], Is.EqualTo(new[] { 2, 3, 4, 5 })); + } + + private void _Execute(IQuery query, UpdateBuilder updateQuery, int expectedCountThatMatched) + { + var countMatched = _db.Update(COLLECTION_NAME, query, updateQuery); + Assert.That(countMatched, Is.EqualTo(expectedCountThatMatched)); + } + + private BsonDocument _GetDocument(BsonOid id) + { + return _db.Load(COLLECTION_NAME, id).ToBsonDocument(); + } + + private void _TestDocumentValues(int expectedDoc1Value, int expectedDoc2Value) + { + var doc1 = _GetDocument(_doc1Id); + Assert.That(doc1["x"], Is.EqualTo(expectedDoc1Value)); + + var doc2 = _GetDocument(_doc2Id); + Assert.That(doc2["x"], Is.EqualTo(expectedDoc2Value)); + } + + + } +} \ No newline at end of file diff --git a/Ejdb.Utils/HelperExtensions.cs b/Ejdb.Utils/HelperExtensions.cs new file mode 100644 index 0000000..b3a422e --- /dev/null +++ b/Ejdb.Utils/HelperExtensions.cs @@ -0,0 +1,40 @@ +// ============================================================================================ +// .NET API for EJDB database library http://ejdb.org +// Copyright (C) 2013-2014 Oliver Klemencic +// +// This file is part of EJDB. +// EJDB is free software; you can redistribute it and/or modify it under the terms of +// the GNU Lesser General Public License as published by the Free Software Foundation; either +// version 2.1 of the License or any later version. EJDB is distributed in the hope +// that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +// License for more details. +// You should have received a copy of the GNU Lesser General Public License along with EJDB; +// if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, +// Boston, MA 02111-1307 USA. +// ============================================================================================ + +using Ejdb.DB; + +namespace Ejdb.Utils +{ + public static class HelperExtensions + { + public static EJDBQCursor Find(this EJDB db, IQuery query) + { + var collection = _GetCollectionName(); + return db.Find(collection, query); + } + + public static bool Save(this EJDB db, params T[] objects) + { + var collection = _GetCollectionName(); + return db.Save(collection, objects); + } + + private static string _GetCollectionName() + { + return typeof (T).Name.ToLower(); + } + } +} \ No newline at end of file diff --git a/Ejdb.Utils/Verify.cs b/Ejdb.Utils/Verify.cs new file mode 100644 index 0000000..8a23d1c --- /dev/null +++ b/Ejdb.Utils/Verify.cs @@ -0,0 +1,1528 @@ +// Copyright © Anton Paar GmbH, 2012-2013 + +#region Copyright (c) 2011-06, Olaf Kober +//================================================================================ +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//================================================================================ +#endregion + +using System; +using System.Globalization; +using System.Runtime.CompilerServices; + + +namespace Ejdb +{ + /// + /// This type provides static methods for validating argument values on public methods and throwing appropriate + /// argument exceptions when specific conditions are not met. + /// + public static class Verify + { + #region ++ Public Interface (NotNull) ++ + + /// + /// Verifies that the supplied value is not a null reference, hence, that it refers to a valid object. + /// + /// + /// + /// The value to verify. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A null reference was passed to a method that did not accept it as a valid argument. + + public static void NotNull(Object value, String paramName, Func messageFunc = null) + { + if (value == null) + _ThrowArgumentNullException(paramName, messageFunc); + } + + #endregion + + #region ++ Public Interface (NotEmpty) ++ + + /// + /// Verifies that the supplied string value is neither a null reference nor an empty string. + /// + /// + /// + /// The value to verify. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A null reference was passed to a method that did not accept it as a valid argument. + /// + /// An empty string was passed to a method that did not accept it as a valid argument. + + public static void NotEmpty(String value, String paramName, Func messageFunc = null) + { + if (value == null) + _ThrowArgumentNullException(paramName, messageFunc); + if (value.Length == 0) + _ThrowEmptyStringException(paramName, messageFunc); + } + + #endregion + + #region ++ Public Interface (NotNegative) ++ + + /// + /// Verifies that the supplied value is not negative; thus, that the value is equal to or greater than zero. + /// + /// + /// + /// The value to verify. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// positive (equal to or greater than zero). + + public static void NotNegative(Int32 value, String paramName, Func messageFunc = null) + { + if (value < 0) + _ThrowValueMustBePositiveException(value, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is not negative; thus, that the value is equal to or greater than zero. + /// + /// + /// + /// The value to verify. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// positive (equal to or greater than zero). + + public static void NotNegative(Int64 value, String paramName, Func messageFunc = null) + { + if (value < 0L) + _ThrowValueMustBePositiveException(value, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is not negative; thus, that the value is equal to or greater than zero. + /// + /// + /// + /// The value to verify. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// positive (equal to or greater than zero). + + public static void NotNegative(Single value, String paramName, Func messageFunc = null) + { + if (value < 0.0) + _ThrowValueMustBePositiveException(value, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is not negative; thus, that the value is equal to or greater than zero. + /// + /// + /// + /// The value to verify. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// positive (equal to or greater than zero). + + public static void NotNegative(Double value, String paramName, Func messageFunc = null) + { + if (value < 0.0) + _ThrowValueMustBePositiveException(value, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is not negative; thus, that the value is equal to or greater than zero. + /// + /// + /// + /// The value to verify. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// positive (equal to or greater than zero). + + public static void NotNegative(Decimal value, String paramName, Func messageFunc = null) + { + if (value < 0) + _ThrowValueMustBePositiveException(value, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is not negative; thus, that the value is equal to or greater than zero. + /// + /// + /// + /// The value to verify. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// positive (equal to or greater than zero). + + public static void NotNegative(TimeSpan value, String paramName, Func messageFunc = null) + { + if (value < TimeSpan.Zero) + _ThrowValueMustBePositiveException(value, paramName, messageFunc); + } + + #endregion + + #region ++ Public Interface (AtLeast) ++ + + /// + /// Verifies that the supplied value is at least (equal to or greater than) the specified minimum value. + /// + /// + /// + /// The value to verify. + /// + /// The minimum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// at least (equal to or greater than) the minimum value. + + public static void AtLeast(Byte value, Byte minValue, String paramName, Func messageFunc = null) + { + if (value < minValue) + _ThrowValueMustBeAtLeastException(value, minValue, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is at least (equal to or greater than) the specified minimum value. + /// + /// + /// + /// The value to verify. + /// + /// The minimum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// at least (equal to or greater than) the minimum value. + + public static void AtLeast(Int32 value, Int32 minValue, String paramName, Func messageFunc = null) + { + if (value < minValue) + _ThrowValueMustBeAtLeastException(value, minValue, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is at least (equal to or greater than) the specified minimum value. + /// + /// + /// + /// The value to verify. + /// + /// The minimum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// at least (equal to or greater than) the minimum value. + + public static void AtLeast(Int64 value, Int64 minValue, String paramName, Func messageFunc = null) + { + if (value < minValue) + _ThrowValueMustBeAtLeastException(value, minValue, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is at least (equal to or greater than) the specified minimum value. + /// + /// + /// + /// The value to verify. + /// + /// The minimum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// at least (equal to or greater than) the minimum value. + + public static void AtLeast(Single value, Single minValue, String paramName, Func messageFunc = null) + { + if (value < minValue) + _ThrowValueMustBeAtLeastException(value, minValue, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is at least (equal to or greater than) the specified minimum value. + /// + /// + /// + /// The value to verify. + /// + /// The minimum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// at least (equal to or greater than) the minimum value. + public static void AtLeast(Double value, Double minValue, String paramName, Func messageFunc = null) + { + if (value < minValue) + _ThrowValueMustBeAtLeastException(value, minValue, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is at least (equal to or greater than) the specified minimum value. + /// + /// + /// + /// The value to verify. + /// + /// The minimum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// at least (equal to or greater than) the minimum value. + public static void AtLeast(Decimal value, Decimal minValue, String paramName, Func messageFunc = null) + { + if (value < minValue) + _ThrowValueMustBeAtLeastException(value, minValue, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is at least (equal to or greater than) the specified minimum value. + /// + /// + /// + /// The value to verify. + /// + /// The minimum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// at least (equal to or greater than) the minimum value. + public static void AtLeast(TimeSpan value, TimeSpan minValue, String paramName, Func messageFunc = null) + { + if (value < minValue) + _ThrowValueMustBeAtLeastException(value, minValue, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is at least (equal to or greater than) the specified minimum value. + /// + /// + /// + /// The value to verify. + /// + /// The minimum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// at least (equal to or greater than) the minimum value. + public static void AtLeast(DateTime value, DateTime minValue, String paramName, Func messageFunc = null) + { + if (value < minValue) + _ThrowValueMustBeAtLeastException(value, minValue, paramName, messageFunc); + } + + #endregion + + #region ++ Public Interface (AtMost) ++ + + /// + /// Verifies that the supplied value is at most (equal to or less than) the specified maximum value. + /// + /// + /// + /// The value to verify. + /// + /// The maximum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// at most (equal to or less than) the maximum value. + public static void AtMost(Byte value, Byte maxValue, String paramName, Func messageFunc = null) + { + if (value > maxValue) + _ThrowValueMustBeAtMostException(value, maxValue, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is at most (equal to or less than) the specified maximum value. + /// + /// + /// + /// The value to verify. + /// + /// The maximum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// at most (equal to or less than) the maximum value. + public static void AtMost(Int32 value, Int32 maxValue, String paramName, Func messageFunc = null) + { + if (value > maxValue) + _ThrowValueMustBeAtMostException(value, maxValue, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is at most (equal to or less than) the specified maximum value. + /// + /// + /// + /// The value to verify. + /// + /// The maximum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// at most (equal to or less than) the maximum value. + public static void AtMost(Int64 value, Int64 maxValue, String paramName, Func messageFunc = null) + { + if (value > maxValue) + _ThrowValueMustBeAtMostException(value, maxValue, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is at most (equal to or less than) the specified maximum value. + /// + /// + /// + /// The value to verify. + /// + /// The maximum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// at most (equal to or less than) the maximum value. + public static void AtMost(Single value, Single maxValue, String paramName, Func messageFunc = null) + { + if (value > maxValue) + _ThrowValueMustBeAtMostException(value, maxValue, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is at most (equal to or less than) the specified maximum value. + /// + /// + /// + /// The value to verify. + /// + /// The maximum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// at most (equal to or less than) the maximum value. + public static void AtMost(Double value, Double maxValue, String paramName, Func messageFunc = null) + { + if (value > maxValue) + _ThrowValueMustBeAtMostException(value, maxValue, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is at most (equal to or less than) the specified maximum value. + /// + /// + /// + /// The value to verify. + /// + /// The maximum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// at most (equal to or less than) the maximum value. + public static void AtMost(Decimal value, Decimal maxValue, String paramName, Func messageFunc = null) + { + if (value > maxValue) + _ThrowValueMustBeAtMostException(value, maxValue, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is at most (equal to or less than) the specified maximum value. + /// + /// + /// + /// The value to verify. + /// + /// The maximum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// at most (equal to or less than) the maximum value. + public static void AtMost(TimeSpan value, TimeSpan maxValue, String paramName, Func messageFunc = null) + { + if (value > maxValue) + _ThrowValueMustBeAtMostException(value, maxValue, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is at most (equal to or less than) the specified maximum value. + /// + /// + /// + /// The value to verify. + /// + /// The maximum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// at most (equal to or less than) the maximum value. + public static void AtMost(DateTime value, DateTime maxValue, String paramName, Func messageFunc = null) + { + if (value > maxValue) + _ThrowValueMustBeAtMostException(value, maxValue, paramName, messageFunc); + } + + #endregion + + #region ++ Public Interface (IsGreater) ++ + + /// + /// Verifies that the supplied value is greater than the specified minimum value. + /// + /// + /// + /// The value to verify. + /// + /// The minimum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// greater than the minimum value. + + public static void IsGreater(Byte value, Byte minValue, String paramName, Func messageFunc = null) + { + if (value <= minValue) + _ThrowValueMustBeGreaterThanException(value, minValue, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is greater than the specified minimum value. + /// + /// + /// + /// The value to verify. + /// + /// The minimum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// greater than the minimum value. + + public static void IsGreater(Int32 value, Int32 minValue, String paramName, Func messageFunc = null) + { + if (value <= minValue) + _ThrowValueMustBeGreaterThanException(value, minValue, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is greater than the specified minimum value. + /// + /// + /// + /// The value to verify. + /// + /// The minimum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// greater than the minimum value. + + public static void IsGreater(Int64 value, Int64 minValue, String paramName, Func messageFunc = null) + { + if (value <= minValue) + _ThrowValueMustBeGreaterThanException(value, minValue, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is greater than the specified minimum value. + /// + /// + /// + /// The value to verify. + /// + /// The minimum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// greater than the minimum value. + + public static void IsGreater(Single value, Single minValue, String paramName, Func messageFunc = null) + { + if (value <= minValue) + _ThrowValueMustBeGreaterThanException(value, minValue, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is greater than the specified minimum value. + /// + /// + /// + /// The value to verify. + /// + /// The minimum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// greater than the minimum value. + + public static void IsGreater(Double value, Double minValue, String paramName, Func messageFunc = null) + { + if (value <= minValue) + _ThrowValueMustBeGreaterThanException(value, minValue, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is greater than the specified minimum value. + /// + /// + /// + /// The value to verify. + /// + /// The minimum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// greater than the minimum value. + + public static void IsGreater(Decimal value, Decimal minValue, String paramName, Func messageFunc = null) + { + if (value <= minValue) + _ThrowValueMustBeGreaterThanException(value, minValue, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is greater than the specified minimum value. + /// + /// + /// + /// The value to verify. + /// + /// The minimum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// greater than the minimum value. + + public static void IsGreater(TimeSpan value, TimeSpan minValue, String paramName, Func messageFunc = null) + { + if (value <= minValue) + _ThrowValueMustBeGreaterThanException(value, minValue, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is greater than the specified minimum value. + /// + /// + /// + /// The value to verify. + /// + /// The minimum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// greater than the minimum value. + + public static void IsGreater(DateTime value, DateTime minValue, String paramName, Func messageFunc = null) + { + if (value <= minValue) + _ThrowValueMustBeGreaterThanException(value, minValue, paramName, messageFunc); + } + + #endregion + + #region ++ Public Interface (IsLess) ++ + + /// + /// Verifies that the supplied value is less than the specified maximum value. + /// + /// + /// + /// The value to verify. + /// + /// The maximum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// less than the maximum value. + + public static void IsLess(Byte value, Byte maxValue, String paramName, Func messageFunc = null) + { + if (value >= maxValue) + _ThrowValueMustBeLessThanException(value, maxValue, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is less than the specified maximum value. + /// + /// + /// + /// The value to verify. + /// + /// The maximum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// less than the maximum value. + + public static void IsLess(Int32 value, Int32 maxValue, String paramName, Func messageFunc = null) + { + if (value >= maxValue) + _ThrowValueMustBeLessThanException(value, maxValue, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is less than the specified maximum value. + /// + /// + /// + /// The value to verify. + /// + /// The maximum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// less than the maximum value. + + public static void IsLess(Int64 value, Int64 maxValue, String paramName, Func messageFunc = null) + { + if (value >= maxValue) + _ThrowValueMustBeLessThanException(value, maxValue, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is less than the specified maximum value. + /// + /// + /// + /// The value to verify. + /// + /// The maximum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// less than the maximum value. + + public static void IsLess(Single value, Single maxValue, String paramName, Func messageFunc = null) + { + if (value >= maxValue) + _ThrowValueMustBeLessThanException(value, maxValue, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is less than the specified maximum value. + /// + /// + /// + /// The value to verify. + /// + /// The maximum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// less than the maximum value. + + public static void IsLess(Double value, Double maxValue, String paramName, Func messageFunc = null) + { + if (value >= maxValue) + _ThrowValueMustBeLessThanException(value, maxValue, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is less than the specified maximum value. + /// + /// + /// + /// The value to verify. + /// + /// The maximum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// less than the maximum value. + + public static void IsLess(Decimal value, Decimal maxValue, String paramName, Func messageFunc = null) + { + if (value >= maxValue) + _ThrowValueMustBeLessThanException(value, maxValue, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is less than the specified maximum value. + /// + /// + /// + /// The value to verify. + /// + /// The maximum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// less than the maximum value. + + public static void IsLess(TimeSpan value, TimeSpan maxValue, String paramName, Func messageFunc = null) + { + if (value >= maxValue) + _ThrowValueMustBeLessThanException(value, maxValue, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is less than the specified maximum value. + /// + /// + /// + /// The value to verify. + /// + /// The maximum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// less than the maximum value. + + public static void IsLess(DateTime value, DateTime maxValue, String paramName, Func messageFunc = null) + { + if (value >= maxValue) + _ThrowValueMustBeLessThanException(value, maxValue, paramName, messageFunc); + } + + #endregion + + #region ++ Public Interface (InRange) ++ + + /// + /// Verifies that the supplied value is in the range of the supplied minimum and maximum values, meaning + /// that the value is equal to or greater than the specified minimum value and equal to or less than the + /// specified maximum value. + /// + /// + /// + /// The value to verify. + /// + /// The minimum value. + /// + /// The maximum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// equal to or greater than the minimum value and equal to or less than the maximum value. + + public static void InRange(Byte value, Byte minValue, Byte maxValue, String paramName, Func messageFunc = null) + { + if (value < minValue || value > maxValue) + _ThrowValueMustBeInRangeException(value, minValue, maxValue, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is in the range of the supplied minimum and maximum values, meaning + /// that the value is equal to or greater than the specified minimum value and equal to or less than the + /// specified maximum value. + /// + /// + /// + /// The value to verify. + /// + /// The minimum value. + /// + /// The maximum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// equal to or greater than the minimum value and equal to or less than the maximum value. + + public static void InRange(Int32 value, Int32 minValue, Int32 maxValue, String paramName, Func messageFunc = null) + { + if (value < minValue || value > maxValue) + _ThrowValueMustBeInRangeException(value, minValue, maxValue, paramName, messageFunc); + } + + + /// + /// Verifies that the supplied value is in the range of the supplied minimum and maximum values, meaning + /// that the value is equal to or greater than the specified minimum value and equal to or less than the + /// specified maximum value. + /// + /// + /// + /// The value to verify. + /// + /// The minimum value. + /// + /// The maximum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// equal to or greater than the minimum value and equal to or less than the maximum value. + + public static void InRange(Int64 value, Int64 minValue, Int64 maxValue, String paramName, Func messageFunc = null) + { + if (value < minValue || value > maxValue) + _ThrowValueMustBeInRangeException(value, minValue, maxValue, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is in the range of the supplied minimum and maximum values, meaning + /// that the value is equal to or greater than the specified minimum value and equal to or less than the + /// specified maximum value. + /// + /// + /// + /// The value to verify. + /// + /// The minimum value. + /// + /// The maximum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// equal to or greater than the minimum value and equal to or less than the maximum value. + + public static void InRange(Single value, Single minValue, Single maxValue, String paramName, Func messageFunc = null) + { + if (value < minValue || value > maxValue) + _ThrowValueMustBeInRangeException(value, minValue, maxValue, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is in the range of the supplied minimum and maximum values, meaning + /// that the value is equal to or greater than the specified minimum value and equal to or less than the + /// specified maximum value. + /// + /// + /// + /// The value to verify. + /// + /// The minimum value. + /// + /// The maximum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// equal to or greater than the minimum value and equal to or less than the maximum value. + + public static void InRange(Double value, Double minValue, Double maxValue, String paramName, Func messageFunc = null) + { + if (value < minValue || value > maxValue) + _ThrowValueMustBeInRangeException(value, minValue, maxValue, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is in the range of the supplied minimum and maximum values, meaning + /// that the value is equal to or greater than the specified minimum value and equal to or less than the + /// specified maximum value. + /// + /// + /// + /// The value to verify. + /// + /// The minimum value. + /// + /// The maximum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// equal to or greater than the minimum value and equal to or less than the maximum value. + + public static void InRange(Decimal value, Decimal minValue, Decimal maxValue, String paramName, Func messageFunc = null) + { + if (value < minValue || value > maxValue) + _ThrowValueMustBeInRangeException(value, minValue, maxValue, paramName, messageFunc); + } + + /// + /// Verifies that the supplied value is in the range of the supplied minimum and maximum values, meaning + /// that the value is equal to or greater than the specified minimum value and equal to or less than the + /// specified maximum value. + /// + /// + /// + /// The value to verify. + /// + /// The minimum value. + /// + /// The maximum value. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value must be + /// equal to or greater than the minimum value and equal to or less than the maximum value. + + public static void InRange(TimeSpan value, TimeSpan minValue, TimeSpan maxValue, String paramName, Func messageFunc = null) + { + if (value < minValue || value > maxValue) + _ThrowValueMustBeInRangeException(value, minValue, maxValue, paramName, messageFunc); + } + + #endregion + + #region ++ Public Interface (IsUniversalTime) ++ + + /// + /// Verifies that the supplied date/time value is in coordinated universal time (UTC). + /// + /// + /// + /// The value to verify. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A date/time value was passed to a method that did not accept it as a valid argument, because the + /// value is expected to be in coordinated universal time (UTC). + + public static void IsUniversalTime(DateTime value, String paramName, Func messageFunc = null) + { + if (value.Kind != DateTimeKind.Utc) + _ThrowDateTimeMustBeInUniversalTimeException(paramName, messageFunc); + } + + #endregion + + #region ++ Public Interface (IsLocalTime) ++ + + /// + /// Verifies that the supplied date/time value is in local time. + /// + /// + /// + /// The value to verify. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A date/time value was passed to a method that did not accept it as a valid argument, because the + /// value is expected to be in local time. + + public static void IsLocalTime(DateTime value, String paramName, Func messageFunc = null) + { + if (value.Kind != DateTimeKind.Local) + _ThrowDateTimeMustBeInLocalTimeException(paramName, messageFunc); + } + + #endregion + + #region ++ Public Interface (BufferSegment) ++ + + /// + /// Verifies that the supplied arguments specifying a buffer segment are valid. + /// + /// + /// + /// The byte array containing the elements of the segment. + /// + /// The zero-based index of the first byte in the segment. + /// + /// The number of bytes in the segment. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A null reference was passed to a method that did not accept it as a valid argument. + /// + /// A value was passed to a method that did not accept it as a valid argument, because the value + /// must be positive (equal to or greater than zero). + /// + /// An offset value was passed to a method that did not accept it as a valid argument, because the + /// offset exceeds the length of the associated byte array. + /// + /// A count value was passed to a method that did not accept it as a valid argument, because the count + /// exceeds the length of the associated byte array. + /// + /// Offset and count values were passed to a method that did not accept it as valid arguments, because + /// the sum of offset and count exceeds the length of the associated byte array. + + public static void BufferSegment(Byte[] buffer, Int32 offset, Int32 count, Func messageFunc = null) + { + if (buffer == null) + _ThrowArgumentNullException("buffer", messageFunc); + if (offset < 0) + _ThrowValueMustBePositiveException(offset, "offset", messageFunc); + if (count < 0) + _ThrowValueMustBePositiveException(count, "count", messageFunc); + if ((buffer.Length == 0 && offset > 0) || (buffer.Length > 0 && offset >= buffer.Length)) + _ThrowOffsetExceedsBufferLengthException(offset, "offset", messageFunc); + if (count > buffer.Length) + _ThrowCountExceedsBufferLengthException(count, "count", messageFunc); + if (offset + count > buffer.Length) + _ThrowOffsetAndCountExceedBufferLengthException(offset + count, "offset + count", messageFunc); + } + + #endregion + + #region ++ Public Interface (IsSubclassOf) ++ + + /// + /// Verifies that the supplied type is a sub-class of the specified base class. + /// + /// This method cannot be used to determine whether an interface derives from another interface, + /// or whether a class implements an interface. + /// + /// + /// + /// The type to verify. + /// + /// The base type from which the type must derive. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A null reference was passed to a method that did not accept it as a valid argument. + /// + /// A value was passed to a method that did not accept it as a valid argument, because + /// the value representing a type must be a sub-class of specified base type. + + public static void IsSubclassOf(Type type, Type baseType, String paramName, Func messageFunc = null) + { + if (type == null || baseType == null) + _ThrowArgumentNullException(paramName, messageFunc); + if (type.IsSubclassOf(baseType) == false) + _ThrowTypeMustBeSubclassOfException(baseType, paramName, messageFunc); + } + + #endregion + + #region ++ Public Interface (IsAssignableTo) ++ + + /// + /// Verifies that the supplied type can be assigned to the specified target type, hence, that the supplied + /// type either derives from the target type, if the target type is a base class, or that the target type + /// realizes the target type, if the target type is an interface. + /// + /// + /// + /// The type to verify. + /// + /// The target type to which the type can be assigned. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A null reference was passed to a method that did not accept it as a valid argument. + /// + /// A value was passed to a method that did not accept it as a valid argument, because + /// the value representing a type is not assignable to the specified target type. + + public static void IsAssignableTo(Type type, Type targetType, String paramName, Func messageFunc = null) + { + if (type == null || targetType == null) + _ThrowArgumentNullException(paramName, messageFunc); + if (targetType.IsAssignableFrom(type) == false) + _ThrowTypeMustBeAssignableToException(targetType, paramName, messageFunc); + } + + #endregion + + #region ++ Public Interface (That) ++ + + /// + /// Verifies that the boolean result of a condition is true. + /// + /// + /// + /// The boolean result of a condition, expected to be true. + /// + /// The name of the method parameter that is verified. + /// + /// A callback used to obtain the exception message in case that verification failed. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument. + + public static void That(Boolean conditionResult, String paramName, Func messageFunc = null) + { + if (conditionResult == false) + _ThrowInvalidArgumentException(paramName, messageFunc); + } + + /// + /// Verifies that the boolean result of a condition is true. + /// + /// + /// + /// The boolean result of a condition, expected to be true. + /// + /// The name of the method parameter that is verified. + /// + /// The exception message for the case that the condition result is false. + /// + /// + /// A value was passed to a method that did not accept it as a valid argument. + + public static void That(Boolean conditionResult, String paramName, String message) + { + if (conditionResult == false) + throw new ArgumentException(message, paramName); + } + + #endregion + + #region Implementation + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void _ThrowArgumentNullException(String paramName, Func messageFunc) + { + var message = messageFunc != null ? + messageFunc() : + VerifyResources.NullArgument; + + throw new ArgumentNullException(paramName, message); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void _ThrowEmptyStringException(String paramName, Func messageFunc) + { + var message = messageFunc != null ? + messageFunc() : + VerifyResources.EmptyStringArgument; + + throw new ArgumentException(message, paramName); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void _ThrowValueMustBePositiveException(Object value, String paramName, Func messageFunc) + { + var message = messageFunc != null ? + messageFunc() : + VerifyResources.ValueMustBePositive; + + throw new ArgumentOutOfRangeException(paramName, value, message); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void _ThrowValueMustBeAtLeastException(Object value, Object minValue, String paramName, Func messageFunc) + { + var message = messageFunc != null ? + messageFunc() : + String.Format(CultureInfo.InvariantCulture, VerifyResources.ValueMustBeAtLeast, minValue); + + throw new ArgumentOutOfRangeException(paramName, value, message); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void _ThrowValueMustBeAtMostException(Object value, Object maxValue, String paramName, Func messageFunc) + { + var message = messageFunc != null ? + messageFunc() : + String.Format(CultureInfo.InvariantCulture, VerifyResources.ValueMustBeAtMost, maxValue); + + throw new ArgumentOutOfRangeException(paramName, value, message); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void _ThrowValueMustBeGreaterThanException(Object value, Object minValue, String paramName, Func messageFunc) + { + var message = messageFunc != null ? + messageFunc() : + String.Format(CultureInfo.InvariantCulture, VerifyResources.ValueMustBeGreaterThan, minValue); + + throw new ArgumentOutOfRangeException(paramName, value, message); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void _ThrowValueMustBeLessThanException(Object value, Object maxValue, String paramName, Func messageFunc) + { + var message = messageFunc != null ? + messageFunc() : + String.Format(CultureInfo.InvariantCulture, VerifyResources.ValueMustBeLessThan, maxValue); + + throw new ArgumentOutOfRangeException(paramName, value, message); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void _ThrowValueMustBeInRangeException(Object value, Object minValue, Object maxValue, String paramName, Func messageFunc) + { + var message = messageFunc != null ? + messageFunc() : + String.Format(CultureInfo.InvariantCulture, VerifyResources.ValueMustBeInRange, minValue, maxValue); + + throw new ArgumentOutOfRangeException(paramName, value, message); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void _ThrowDateTimeMustBeInUniversalTimeException(String paramName, Func messageFunc) + { + var message = messageFunc != null ? + messageFunc() : + VerifyResources.DateTimeMustBeInUniversalTime; + + throw new ArgumentException(message, paramName); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void _ThrowDateTimeMustBeInLocalTimeException(String paramName, Func messageFunc) + { + var message = messageFunc != null ? + messageFunc() : + VerifyResources.DateTimeMustBeInLocalTime; + + throw new ArgumentException(message, paramName); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void _ThrowOffsetExceedsBufferLengthException(Object value, String paramName, Func messageFunc) + { + var message = messageFunc != null ? + messageFunc() : + VerifyResources.OffsetExceedsBufferLength; + + throw new ArgumentOutOfRangeException(paramName, value, message); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void _ThrowCountExceedsBufferLengthException(Object value, String paramName, Func messageFunc) + { + var message = messageFunc != null ? + messageFunc() : + VerifyResources.CountExceedsBufferLength; + + throw new ArgumentOutOfRangeException(paramName, value, message); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void _ThrowOffsetAndCountExceedBufferLengthException(Object value, String paramName, Func messageFunc) + { + var message = messageFunc != null ? + messageFunc() : + VerifyResources.OffsetAndCountExceedBufferLength; + + throw new ArgumentOutOfRangeException(paramName, value, message); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void _ThrowTypeMustBeSubclassOfException(Type baseType, String paramName, Func messageFunc) + { + var message = messageFunc != null ? + messageFunc() : + String.Format(CultureInfo.InvariantCulture, VerifyResources.TypeMustBeSubclassOf, baseType); + + throw new ArgumentException(message, paramName); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void _ThrowTypeMustBeAssignableToException(Type targetType, String paramName, Func messageFunc) + { + var message = messageFunc != null ? + messageFunc() : + String.Format(CultureInfo.InvariantCulture, VerifyResources.TypeMustBeAssignableTo, targetType); + + throw new ArgumentException(message, paramName); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void _ThrowInvalidArgumentException(String paramName, Func messageFunc) + { + var message = messageFunc != null ? + messageFunc() : + VerifyResources.InvalidArgument; + + throw new ArgumentException(message, paramName); + } + + #endregion + + } +} diff --git a/Ejdb.Utils/VerifyResources.Designer.cs b/Ejdb.Utils/VerifyResources.Designer.cs new file mode 100644 index 0000000..047505c --- /dev/null +++ b/Ejdb.Utils/VerifyResources.Designer.cs @@ -0,0 +1,234 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.18052 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Ejdb { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class VerifyResources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal VerifyResources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Ejdb.Ejdb.Utils.VerifyResources", typeof(VerifyResources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to A count value was passed to a method that did not accept it as a valid argument, because the count exceeds the length of the associated array.. + /// + internal static string CountExceedsArrayLength { + get { + return ResourceManager.GetString("CountExceedsArrayLength", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A count value was passed to a method that did not accept it as a valid argument, because the count exceeds the length of the associated byte array.. + /// + internal static string CountExceedsBufferLength { + get { + return ResourceManager.GetString("CountExceedsBufferLength", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A date/time value was passed to a method that did not accept it as a valid argument, because the value is expected to be in local time.. + /// + internal static string DateTimeMustBeInLocalTime { + get { + return ResourceManager.GetString("DateTimeMustBeInLocalTime", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A date/time value was passed to a method that did not accept it as a valid argument, because the value is expected to be in coordinated universal time (UTC).. + /// + internal static string DateTimeMustBeInUniversalTime { + get { + return ResourceManager.GetString("DateTimeMustBeInUniversalTime", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An empty string was passed to a method that did not accept it as a valid argument.. + /// + internal static string EmptyStringArgument { + get { + return ResourceManager.GetString("EmptyStringArgument", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A value was passed to a method that did not accept it as a valid argument.. + /// + internal static string InvalidArgument { + get { + return ResourceManager.GetString("InvalidArgument", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A null reference was passed to a method that did not accept it as a valid argument.. + /// + internal static string NullArgument { + get { + return ResourceManager.GetString("NullArgument", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Offset and count values were passed to a method that did not accept it as valid arguments, because the sum of offset and count exceeds the length of the associated array.. + /// + internal static string OffsetAndCountExceedArrayLength { + get { + return ResourceManager.GetString("OffsetAndCountExceedArrayLength", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Offset and count values were passed to a method that did not accept it as valid arguments, because the sum of offset and count exceeds the length of the associated byte array.. + /// + internal static string OffsetAndCountExceedBufferLength { + get { + return ResourceManager.GetString("OffsetAndCountExceedBufferLength", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An offset value was passed to a method that did not accept it as a valid argument, because the offset exceeds the length of the associated array.. + /// + internal static string OffsetExceedsArrayLength { + get { + return ResourceManager.GetString("OffsetExceedsArrayLength", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An offset value was passed to a method that did not accept it as a valid argument, because the offset exceeds the length of the associated byte array.. + /// + internal static string OffsetExceedsBufferLength { + get { + return ResourceManager.GetString("OffsetExceedsBufferLength", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A value was passed to a method that did not accept it as a valid argument, because the value representing a type is not assignable to the specified target type {0}.. + /// + internal static string TypeMustBeAssignableTo { + get { + return ResourceManager.GetString("TypeMustBeAssignableTo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A value was passed to a method that did not accept it as a valid argument, because the value representing a type must be a sub-class of type {0}.. + /// + internal static string TypeMustBeSubclassOf { + get { + return ResourceManager.GetString("TypeMustBeSubclassOf", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A value was passed to a method that did not accept it as a valid argument, because the value must be at least (equal to or greater than) the minimum value of {0}.. + /// + internal static string ValueMustBeAtLeast { + get { + return ResourceManager.GetString("ValueMustBeAtLeast", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A value was passed to a method that did not accept it as a valid argument, because the value must be at most (equal to or less than) the maximum value of {0}.. + /// + internal static string ValueMustBeAtMost { + get { + return ResourceManager.GetString("ValueMustBeAtMost", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A value was passed to a method that did not accept it as a valid argument, because the value must be greater than the minimum value of {0}.. + /// + internal static string ValueMustBeGreaterThan { + get { + return ResourceManager.GetString("ValueMustBeGreaterThan", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A value was passed to a method that did not accept it as a valid argument, because the value must be equal to or greater than the minimum value of {0} and equal to or less than the maximum value of {1}.. + /// + internal static string ValueMustBeInRange { + get { + return ResourceManager.GetString("ValueMustBeInRange", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A value was passed to a method that did not accept it as a valid argument, because the value must be less than the maximum value of {0}.. + /// + internal static string ValueMustBeLessThan { + get { + return ResourceManager.GetString("ValueMustBeLessThan", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A value was passed to a method that did not accept it as a valid argument, because the value must be positive (equal to or greater than zero).. + /// + internal static string ValueMustBePositive { + get { + return ResourceManager.GetString("ValueMustBePositive", resourceCulture); + } + } + } +} diff --git a/Ejdb.Utils/VerifyResources.resx b/Ejdb.Utils/VerifyResources.resx new file mode 100644 index 0000000..a37d967 --- /dev/null +++ b/Ejdb.Utils/VerifyResources.resx @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + A count value was passed to a method that did not accept it as a valid argument, because the count exceeds the length of the associated array. + + + A count value was passed to a method that did not accept it as a valid argument, because the count exceeds the length of the associated byte array. + + + A date/time value was passed to a method that did not accept it as a valid argument, because the value is expected to be in local time. + + + A date/time value was passed to a method that did not accept it as a valid argument, because the value is expected to be in coordinated universal time (UTC). + + + An empty string was passed to a method that did not accept it as a valid argument. + + + A value was passed to a method that did not accept it as a valid argument. + + + A null reference was passed to a method that did not accept it as a valid argument. + + + Offset and count values were passed to a method that did not accept it as valid arguments, because the sum of offset and count exceeds the length of the associated array. + + + Offset and count values were passed to a method that did not accept it as valid arguments, because the sum of offset and count exceeds the length of the associated byte array. + + + An offset value was passed to a method that did not accept it as a valid argument, because the offset exceeds the length of the associated array. + + + An offset value was passed to a method that did not accept it as a valid argument, because the offset exceeds the length of the associated byte array. + + + A value was passed to a method that did not accept it as a valid argument, because the value representing a type is not assignable to the specified target type {0}. + {0} ... type name [String] + + + A value was passed to a method that did not accept it as a valid argument, because the value representing a type must be a sub-class of type {0}. + {0} ... type name [String] + + + A value was passed to a method that did not accept it as a valid argument, because the value must be at least (equal to or greater than) the minimum value of {0}. + {0} ... minimum value [object] + + + A value was passed to a method that did not accept it as a valid argument, because the value must be at most (equal to or less than) the maximum value of {0}. + {0} ... maximum value [object] + + + A value was passed to a method that did not accept it as a valid argument, because the value must be greater than the minimum value of {0}. + {0} ... minimum value [object] + + + A value was passed to a method that did not accept it as a valid argument, because the value must be equal to or greater than the minimum value of {0} and equal to or less than the maximum value of {1}. + {0} ... minimum value [object] +{1} ... maximum value [object] + + + A value was passed to a method that did not accept it as a valid argument, because the value must be less than the maximum value of {0}. + {0} ... maximum value [object] + + + A value was passed to a method that did not accept it as a valid argument, because the value must be positive (equal to or greater than zero). + + \ No newline at end of file diff --git a/ignore.conf b/ignore.conf new file mode 100644 index 0000000..09205b5 --- /dev/null +++ b/ignore.conf @@ -0,0 +1,2 @@ +obj +bin diff --git a/nejdb.csproj b/nejdb.csproj index 6b450a3..5eb0d69 100644 --- a/nejdb.csproj +++ b/nejdb.csproj @@ -1,112 +1,151 @@ - - - - DebugUnix - AnyCPU - 10.0.0 - 2.0 - {A24C964C-771F-4359-8C93-4BFCBE451D8B} - Library - Ejdb - nejdb - EJDB .Net binding (http://ejdb.org) - 65001 - 1.0.0 - - - true - full - false - bin\Debug - DEBUG; - prompt - 4 - false - - - true - bin\Release - prompt - 4 - false - - - true - full - false - bin\Debug - DEBUG;EJDBDLL - prompt - 4 - false - AnyCPU - - - none - true - bin\Release - prompt - 4 - false - EJDBDLL; - AnyCPU - - - - lib\nunit.framework.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - Settings.settings - - - - - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - - - - - - - - - + + + + DebugUnix + AnyCPU + 10.0.0 + 2.0 + {A24C964C-771F-4359-8C93-4BFCBE451D8B} + Library + Ejdb + nejdb + EJDB .Net binding (http://ejdb.org) + 65001 + 1.0.0 + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + false + + + true + bin\Release + prompt + 4 + false + + + true + full + false + bin\Debug + DEBUG;EJDBDLL + prompt + 4 + false + AnyCPU + + + none + true + bin\Release + prompt + 4 + false + EJDBDLL; + AnyCPU + + + + lib\nunit.framework.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + VerifyResources.resx + + + True + True + Settings.settings + + + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + ResXFileCodeGenerator + VerifyResources.Designer.cs + Ejdb + + + + + + + + + + + \ No newline at end of file diff --git a/sample/MainClass.cs b/sample/MainClass.cs new file mode 100644 index 0000000..6dae580 --- /dev/null +++ b/sample/MainClass.cs @@ -0,0 +1,132 @@ +// ============================================================================================ +// .NET API for EJDB database library http://ejdb.org +// Copyright (C) 2012-2013 Softmotions Ltd +// +// This file is part of EJDB. +// EJDB is free software; you can redistribute it and/or modify it under the terms of +// the GNU Lesser General Public License as published by the Free Software Foundation; either +// version 2.1 of the License or any later version. EJDB is distributed in the hope +// that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +// License for more details. +// You should have received a copy of the GNU Lesser General Public License along with EJDB; +// if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, +// Boston, MA 02111-1307 USA. +// ============================================================================================ + +using System; +using System.Diagnostics; +using Ejdb.BSON; +using Ejdb.DB; + +namespace sample +{ + internal class MainClass + { + public static void Main(string[] args) + { + _PerformanceDemo(); + _ZooDemo(); + } + + private static void _PerformanceDemo() + { + var jb = new EJDB("performance-demo", EJDB.DEFAULT_OPEN_MODE | EJDB.JBOTRUNC); + jb.ThrowExceptionOnFail = true; + + var posts = new BsonDocument[5002]; + var text = new string('-', 2000); + + for (int i = 0; i < posts.Length; i++) + { + posts[i] = new BsonDocument() + { + { "Text", text }, + { "CreationDate", BsonValue.Create(DateTime.Now) }, + { "LastChangeDate", BsonValue.Create(DateTime.Now) }, + }; + } + + var collectionName = "posts"; + jb.Save(collectionName, posts); + + var tests = new Tests(); + tests.Add(i => jb.Load(collectionName, (BsonOid) posts[i]["_id"]), "Load without serialization"); + tests.Add(i => + { + var iterator = jb.Load(collectionName, (BsonOid) posts[i]["_id"]); + iterator.ToBsonDocument(); + }, "Load with deserialization as BSON document"); + + tests.Add(i => + { + var iterator = jb.Load(collectionName, (BsonOid)posts[i]["_id"]); + iterator.To(); + }, "Load with deserialization as Post object"); + + tests.Run(500); + Console.ReadLine(); + } + + private static void _ZooDemo() + { + var jb = new EJDB("zoo", EJDB.DEFAULT_OPEN_MODE | EJDB.JBOTRUNC); + jb.ThrowExceptionOnFail = true; + + BsonDocument parrot1 = BsonDocument.ValueOf(new + { + name = "Grenny", + type = "African Grey", + male = true, + age = 1, + birthdate = DateTime.Now, + likes = new[] {"green color", "night", "toys"}, + extra = BsonNull.VALUE + }); + + BsonDocument parrot2 = BsonDocument.ValueOf(new + { + name = "Bounty", + type = "Cockatoo", + male = false, + age = 15, + birthdate = DateTime.Now, + likes = new[] {"sugar cane"} + }); + + jb.Save("parrots", parrot1, parrot2); + + Console.WriteLine("Grenny OID: " + parrot1[BsonConstants.Id]); + Console.WriteLine("Bounty OID: " + parrot2[BsonConstants.Id]); + + EJDBQuery q = jb.CreateQuery(new + { + likes = "toys" + }, "parrots").OrderBy("name"); + + using (EJDBQCursor cur = q.Find()) + { + Console.WriteLine("Found " + cur.Length + " parrots"); + foreach (BsonIterator e in cur) + { + //fetch the `name` and the first element of likes array from the current BSON iterator. + //alternatively you can fetch whole document from the iterator: `e.ToBsonDocument()` + BsonDocument rdoc = e.ToBsonDocument("name", "likes.0"); + Console.WriteLine(string.Format("{0} likes the '{1}'", rdoc["name"], rdoc["likes.0"])); + } + } + q.Dispose(); + jb.Dispose(); + + Console.ReadKey(); + } + } + + public class Post + { + public BsonOid _id { get; set; } + public string Text { get; set; } + public DateTime CreationDate { get; set; } + public DateTime LastChangeDate { get; set; } + } +} \ No newline at end of file diff --git a/sample/Program.cs b/sample/Program.cs deleted file mode 100644 index 9859f13..0000000 --- a/sample/Program.cs +++ /dev/null @@ -1,70 +0,0 @@ -// ============================================================================================ -// .NET API for EJDB database library http://ejdb.org -// Copyright (C) 2012-2013 Softmotions Ltd -// -// This file is part of EJDB. -// EJDB is free software; you can redistribute it and/or modify it under the terms of -// the GNU Lesser General Public License as published by the Free Software Foundation; either -// version 2.1 of the License or any later version. EJDB is distributed in the hope -// that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License for more details. -// You should have received a copy of the GNU Lesser General Public License along with EJDB; -// if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, -// Boston, MA 02111-1307 USA. -// ============================================================================================ -using System; -using Ejdb.DB; -using Ejdb.BSON; - -namespace sample { - - class MainClass { - - public static void Main(string[] args) { - var jb = new EJDB("zoo", EJDB.DEFAULT_OPEN_MODE | EJDB.JBOTRUNC); - jb.ThrowExceptionOnFail = true; - - var parrot1 = BSONDocument.ValueOf(new { - name = "Grenny", - type = "African Grey", - male = true, - age = 1, - birthdate = DateTime.Now, - likes = new string[] { "green color", "night", "toys" }, - extra = BSONull.VALUE - }); - - var parrot2 = BSONDocument.ValueOf(new { - name = "Bounty", - type = "Cockatoo", - male = false, - age = 15, - birthdate = DateTime.Now, - likes = new string[] { "sugar cane" } - }); - - jb.Save("parrots", parrot1, parrot2); - - Console.WriteLine("Grenny OID: " + parrot1["_id"]); - Console.WriteLine("Bounty OID: " + parrot2["_id"]); - - var q = jb.CreateQuery(new { - likes = "toys" - }, "parrots").OrderBy("name"); - - using (var cur = q.Find()) { - Console.WriteLine("Found " + cur.Length + " parrots"); - foreach (var e in cur) { - //fetch the `name` and the first element of likes array from the current BSON iterator. - //alternatively you can fetch whole document from the iterator: `e.ToBSONDocument()` - BSONDocument rdoc = e.ToBSONDocument("name", "likes.0"); - Console.WriteLine(string.Format("{0} likes the '{1}'", rdoc["name"], rdoc["likes.0"])); - } - } - q.Dispose(); - jb.Dispose(); - Console.ReadKey(); - } - } -} diff --git a/sample/Test.cs b/sample/Test.cs new file mode 100644 index 0000000..2c9c923 --- /dev/null +++ b/sample/Test.cs @@ -0,0 +1,17 @@ +using System; +using System.Diagnostics; + +namespace sample +{ + public class Test + { + public static Test Create(Action iteration, string name) + { + return new Test { Iteration = iteration, Name = name }; + } + + public Action Iteration { get; set; } + public string Name { get; set; } + public Stopwatch Watch { get; set; } + } +} \ No newline at end of file diff --git a/sample/Tests.cs b/sample/Tests.cs new file mode 100644 index 0000000..7259c97 --- /dev/null +++ b/sample/Tests.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace sample +{ + class Tests : List + { + public void Add(Action iteration, string name) + { + Add(Test.Create(iteration, name)); + } + + public void Run(int iterations) + { + // warmup + foreach (var test in this) + { + test.Iteration(iterations + 1); + test.Watch = new Stopwatch(); + test.Watch.Reset(); + } + + var rand = new Random(); + for (int i = 1; i <= iterations; i++) + { + foreach (var test in this.OrderBy(ignore => rand.Next())) + { + test.Watch.Start(); + test.Iteration(i); + test.Watch.Stop(); + } + } + + foreach (var test in this.OrderBy(t => t.Watch.ElapsedMilliseconds)) + { + Console.WriteLine(test.Name + " took " + test.Watch.ElapsedMilliseconds + "ms"); + } + } + } +} \ No newline at end of file diff --git a/sample/sample.csproj b/sample/sample.csproj index bf571a8..041fd11 100644 --- a/sample/sample.csproj +++ b/sample/sample.csproj @@ -1,78 +1,80 @@ - - - - DebugWindows - AnyCPU - 10.0.0 - 2.0 - {93049FBC-650E-432C-9F1D-156E621DB96C} - Exe - sample - sample - 1.0.0 - - - true - full - false - bin\Debug - DEBUG; - prompt - 4 - true - AnyCPU - - - full - true - bin\Release - prompt - 4 - true - AnyCPU - - - true - full - false - bin\Debug - DEBUG; - prompt - 4 - true - - - full - true - bin\Release - prompt - 4 - true - - - - - - - - - True - True - Settings.settings - - - - - - {A24C964C-771F-4359-8C93-4BFCBE451D8B} - nejdb - - - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - + + + + DebugWindows + AnyCPU + 10.0.0 + 2.0 + {93049FBC-650E-432C-9F1D-156E621DB96C} + Exe + sample + sample + 1.0.0 + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + true + x86 + + + full + true + bin\Release + prompt + 4 + true + x86 + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + true + + + full + true + bin\Release + prompt + 4 + true + + + + + + + + + True + True + Settings.settings + + + + + + + + {A24C964C-771F-4359-8C93-4BFCBE451D8B} + nejdb + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + \ No newline at end of file