From 244e402e71b720dab08439025d76a0c7bd3c614f Mon Sep 17 00:00:00 2001 From: Oliver Klemencic Date: Fri, 27 Dec 2013 18:36:18 +0100 Subject: [PATCH 01/26] Added support for generating an object id --- Ejdb.BSON/BSONOid.cs | 10 ++++++++++ Ejdb.DB/EJDB.cs | 13 +++++++++++++ 2 files changed, 23 insertions(+) diff --git a/Ejdb.BSON/BSONOid.cs b/Ejdb.BSON/BSONOid.cs index 9c3a835..add79c3 100644 --- a/Ejdb.BSON/BSONOid.cs +++ b/Ejdb.BSON/BSONOid.cs @@ -30,6 +30,16 @@ public BSONType BSONType { } } + public static BSONOid Empty + { + get + { + var result = new BSONOid(); + result._bytes = new byte[12]; + return result; + } + } + BSONOid() { } diff --git a/Ejdb.DB/EJDB.cs b/Ejdb.DB/EJDB.cs index b2cc214..29b7e29 100644 --- a/Ejdb.DB/EJDB.cs +++ b/Ejdb.DB/EJDB.cs @@ -228,6 +228,10 @@ internal static bool _ejdbrmcoll(IntPtr db, string cname, bool unlink) { 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); @@ -795,6 +799,13 @@ public bool TransactionStatus(string cname, out bool active) { 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); @@ -1035,6 +1046,8 @@ internal IntPtr DBPtr { } } + + byte[] BsonPtrIntoByteArray(IntPtr bsptr, bool deletebsptr = true) { if (bsptr == IntPtr.Zero) { return new byte[0]; From cce9838f5d5f90f3cb9eccd26d0c1a5a750c05a5 Mon Sep 17 00:00:00 2001 From: Oliver Klemencic Date: Fri, 27 Dec 2013 18:39:26 +0100 Subject: [PATCH 02/26] Added support for serialization of regular types (not only anonymous types) --- Ejdb.BSON/BSONDocument.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Ejdb.BSON/BSONDocument.cs b/Ejdb.BSON/BSONDocument.cs index 14ed3ae..33ca72e 100644 --- a/Ejdb.BSON/BSONDocument.cs +++ b/Ejdb.BSON/BSONDocument.cs @@ -515,12 +515,12 @@ public static BSONDocument ValueOf(object val) { return (BSONDocument) val; } else if (vtype == typeof(byte[])) { return new BSONDocument((byte[]) val); - } else if (vtype.IsAnonymousType()) { + } else /* if (vtype.IsAnonymousType()) */ { BSONDocument doc = new BSONDocument(); SetAnonType(doc, null, val); return doc; } - throw new InvalidCastException(string.Format("Unsupported cast type: {0}", vtype)); + // throw new InvalidCastException(string.Format("Unsupported cast type: {0}", vtype)); } public object Clone() { From 8569513cd1b517e8b8c4e644b8d9d881bfc0d6a3 Mon Sep 17 00:00:00 2001 From: Oliver Klemencic Date: Fri, 27 Dec 2013 18:40:53 +0100 Subject: [PATCH 03/26] Added helper methods for querying data --- Ejdb.DB/EJDB.cs | 6 +++ Ejdb.DB/Query/IQuery.cs | 12 +++++ Ejdb.DB/Query/Query.cs | 87 ++++++++++++++++++++++++++++++++++ Ejdb.Utils/HelperExtensions.cs | 27 +++++++++++ nejdb.csproj | 3 ++ 5 files changed, 135 insertions(+) create mode 100644 Ejdb.DB/Query/IQuery.cs create mode 100644 Ejdb.DB/Query/Query.cs create mode 100644 Ejdb.Utils/HelperExtensions.cs diff --git a/Ejdb.DB/EJDB.cs b/Ejdb.DB/EJDB.cs index 29b7e29..26bbc82 100644 --- a/Ejdb.DB/EJDB.cs +++ b/Ejdb.DB/EJDB.cs @@ -1017,6 +1017,12 @@ public EJDBQuery CreateQueryFor(string defaultcollection) { return new EJDBQuery(this, new BSONDocument(), defaultcollection); } + public EJDBQCursor Find(string collection, IQuery query) + { + var ejdbQuery = new EJDBQuery(this, query.GetQueryDocument(), collection); + return ejdbQuery.Find(); + } + /// /// Convert JSON string into BSONDocument. /// Returns `null` if conversion failed. diff --git a/Ejdb.DB/Query/IQuery.cs b/Ejdb.DB/Query/IQuery.cs new file mode 100644 index 0000000..5e75b02 --- /dev/null +++ b/Ejdb.DB/Query/IQuery.cs @@ -0,0 +1,12 @@ +// // Copyright © Anton Paar GmbH, 2004-2013 +// + +using Ejdb.BSON; + +namespace Ejdb.DB +{ + public interface IQuery + { + BSONDocument GetQueryDocument(); + } +} \ 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..3421053 --- /dev/null +++ b/Ejdb.DB/Query/Query.cs @@ -0,0 +1,87 @@ +// // Copyright © Anton Paar GmbH, 2004-2013 +// + +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 EQ(string fieldName, object value) + { + return new Query(fieldName, value); + } + + public static IQuery GT(string fieldName, object value) + { + return BinaryQuery("$gt", fieldName, value); + } + + public static IQuery GTE(string fieldName, object value) + { + return BinaryQuery("$gte", fieldName, value); + } + + public static IQuery LT(string fieldName, object value) + { + return BinaryQuery("$lt", fieldName, value); + } + + public static IQuery LTE(string fieldName, object value) + { + return BinaryQuery("$lte", fieldName, value); + } + + public static IQuery In(string fieldName, params int[] comparisonValues) + { + return BinaryQuery("$in", fieldName, comparisonValues.ToArray()); + } + + public static IQuery In(string fieldName, params float[] comparisonValues) + { + return BinaryQuery("$in", fieldName, comparisonValues.ToArray()); + } + + public static IQuery In(string fieldName, params string[] comparisonValues) + { + return BinaryQuery("$in", fieldName, comparisonValues.ToArray()); + } + + public static IQuery In(string fieldName, params double[] comparisonValues) + { + return BinaryQuery("$in", fieldName, comparisonValues.ToArray()); + } + + public static IQuery Not(string fieldName, object comparisonValue) + { + return BinaryQuery("$not", fieldName, comparisonValue); + } + + public static IQuery Between(string fieldName, T comparisonValue1, T comparisonValue2) + { + var comparisonValues = new object[] { comparisonValue1, comparisonValue2 }; + return BinaryQuery("$bt", fieldName, comparisonValues); + } + + public static IQuery BinaryQuery(string queryOperation, string fieldName, object comparisonValue) + { + var query1 = new BSONDocument(); + query1[queryOperation] = comparisonValue; + return new Query(fieldName, query1); + } + + public BSONDocument GetQueryDocument() + { + return mQueryDocument; + } + } +} \ No newline at end of file diff --git a/Ejdb.Utils/HelperExtensions.cs b/Ejdb.Utils/HelperExtensions.cs new file mode 100644 index 0000000..97585ea --- /dev/null +++ b/Ejdb.Utils/HelperExtensions.cs @@ -0,0 +1,27 @@ +// // Copyright © Anton Paar GmbH, 2004-2013 +// + +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/nejdb.csproj b/nejdb.csproj index 6b450a3..abe7d7a 100644 --- a/nejdb.csproj +++ b/nejdb.csproj @@ -61,9 +61,12 @@ + + + From 58585659e0a97811dc3d2a5a34238dce5cebfd3b Mon Sep 17 00:00:00 2001 From: Oliver Klemencic Date: Fri, 27 Dec 2013 18:44:16 +0100 Subject: [PATCH 04/26] Refactoring of "BsonValue" so that it doesn' store the key any more --- Ejdb.BSON/BSONArray.cs | 370 ++++++++++++----------- Ejdb.BSON/BSONDocument.cs | 548 ++++++++++++---------------------- Ejdb.BSON/BSONIterator.cs | 188 ++++++------ Ejdb.BSON/BSONValue.cs | 215 ++++++++++++- Ejdb.BSON/BSONValueWithKey.cs | 21 ++ Ejdb.DB/EJDB.cs | 2 +- Ejdb.Tests/TestBSON.cs | 29 +- Ejdb.Tests/TestEJDB.cs | 6 +- 8 files changed, 727 insertions(+), 652 deletions(-) create mode 100644 Ejdb.BSON/BSONValueWithKey.cs diff --git a/Ejdb.BSON/BSONArray.cs b/Ejdb.BSON/BSONArray.cs index b45ab02..552e443 100644 --- a/Ejdb.BSON/BSONArray.cs +++ b/Ejdb.BSON/BSONArray.cs @@ -36,7 +36,7 @@ public object this[int key] { public BSONArray() { } - public BSONArray(BSONUndefined[] arr) { + public BSONArray(BSONUndefined[] arr) { for (var i = 0; i < arr.Length; ++i) { SetUndefined(i); } @@ -48,199 +48,227 @@ public BSONArray(BSONull[] arr) { } } - public BSONArray(ushort[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetNumber(i, (int) arr[i]); - } - } + public BSONArray(ushort[] arr) + { + for (int i = 0; i < arr.Length; ++i) + SetNumber(i, arr[i]); + } + + public BSONArray(uint[] arr) + { + for (int i = 0; i < arr.Length; ++i) + SetNumber(i, arr[i]); + } + + public BSONArray(ulong[] arr) + { + for (int i = 0; i < arr.Length; ++i) + SetNumber(i, (long) arr[i]); + } + + public BSONArray(short[] arr) + { + for (int i = 0; i < arr.Length; ++i) + SetNumber(i, arr[i]); + } + + public BSONArray(string[] arr) + { + for (int i = 0; i < arr.Length; ++i) + SetString(i, arr[i]); + } + + public BSONArray(int[] arr) + { + for (int i = 0; i < arr.Length; ++i) + SetNumber(i, arr[i]); + } + + public BSONArray(long[] arr) + { + for (int i = 0; i < arr.Length; ++i) + SetNumber(i, arr[i]); + } + + public BSONArray(float[] arr) + { + for (int i = 0; i < arr.Length; ++i) + SetNumber(i, arr[i]); + } + + public BSONArray(double[] arr) + { + for (int i = 0; i < arr.Length; ++i) + SetNumber(i, arr[i]); + } + + public BSONArray(bool[] arr) + { + for (int i = 0; i < arr.Length; ++i) + SetBool(i, arr[i]); + } + + public BSONArray(BSONOid[] arr) + { + for (int i = 0; i < arr.Length; ++i) + SetOID(i, arr[i]); + } + + public BSONArray(DateTime[] arr) + { + for (int i = 0; i < arr.Length; ++i) + SetDate(i, arr[i]); + } + + public BSONArray(BSONDocument[] arr) + { + for (int i = 0; i < arr.Length; ++i) + SetObject(i, arr[i]); + } + + public BSONArray(BSONArray[] arr) + { + for (int i = 0; i < arr.Length; ++i) + SetArray(i, arr[i]); + } + + public BSONArray(BSONRegexp[] arr) + { + for (int i = 0; i < arr.Length; ++i) + SetRegexp(i, arr[i]); + } + + public BSONArray(BSONTimestamp[] arr) + { + for (int i = 0; i < arr.Length; ++i) + SetTimestamp(i, arr[i]); + } + + public BSONArray(BSONCodeWScope[] arr) + { + for (int i = 0; i < arr.Length; ++i) + SetCodeWScope(i, arr[i]); + } + + public BSONArray(BSONBinData[] arr) + { + for (int i = 0; i < arr.Length; ++i) + SetBinData(i, arr[i]); + } + + public void SetNull(int idx) + { + _SetValue(idx, BSONValue.GetNull()); + } + + public void SetUndefined(int idx) + { + _SetValue(idx, BSONValue.GetUndefined()); + } + + public void SetMaxKey(int idx) + { + _SetValue(idx, BSONValue.GetMaxKey()); + } + + public void SetMinKey(int idx) + { + _SetValue(idx, BSONValue.GetMinKey()); + } + + public void SetOID(int idx, string oid) + { + _SetValue(idx, BSONValue.GetOID(oid)); + } + + public void SetOID(int idx, BSONOid oid) + { + _SetValue(idx, BSONValue.GetOID(oid)); + } + + public void SetBool(int idx, bool val) + { + _SetValue(idx, BSONValue.GetBool(val)); + } - public BSONArray(uint[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetNumber(i, (long) arr[i]); - } - } + public void SetNumber(int idx, int val) + { + _SetValue(idx, BSONValue.GetNumber(val)); + } - public BSONArray(ulong[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetNumber(i, (long) arr[i]); - } - } + public void SetNumber(int idx, long val) + { + _SetValue(idx, BSONValue.GetNumber(val)); + } - public BSONArray(short[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetNumber(i, (int) arr[i]); - } - } + public void SetNumber(int idx, double val) + { + _SetValue(idx, BSONValue.GetNumber(val)); + } - public BSONArray(string[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetString(i, arr[i]); - } - } + public void SetNumber(int idx, float val) + { + _SetValue(idx, BSONValue.GetNumber(val)); + } - public BSONArray(int[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetNumber(i, arr[i]); - } - } + public void SetString(int idx, string val) + { + _SetValue(idx, BSONValue.GetString(val)); + } - 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 void SetCode(int idx, string val) + { + _SetValue(idx, BSONValue.GetCode(val)); + } - public BSONArray(double[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetNumber(i, arr[i]); - } - } + public void SetSymbol(int idx, string val) + { + _SetValue(idx, BSONValue.GetSymbol(val)); + } - public BSONArray(bool[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetBool(i, arr[i]); - } - } + public void SetDate(int idx, DateTime val) + { + _SetValue(idx, BSONValue.GetDate(val)); + } - public BSONArray(BSONOid[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetOID(i, arr[i]); - } + public void SetRegexp(int idx, BSONRegexp val) + { + _SetValue(idx, BSONValue.GetRegexp(val)); } - public BSONArray(DateTime[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetDate(i, arr[i]); - } + public void SetBinData(int idx, BSONBinData val) + { + _SetValue(idx, BSONValue.GetBinData(val)); } - public BSONArray(BSONDocument[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetObject(i, arr[i]); - } + public void SetObject(int idx, BSONDocument val) + { + _SetValue(idx, BSONValue.GetDocument(val)); } - public BSONArray(BSONArray[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetArray(i, arr[i]); - } + public void SetArray(int idx, BSONArray val) + { + _SetValue(idx, BSONValue.GetArray(val)); } - public BSONArray(BSONRegexp[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetRegexp(i, arr[i]); - } + public void SetTimestamp(int idx, BSONTimestamp val) + { + _SetValue(idx, BSONValue.GetTimestamp(val)); } - public BSONArray(BSONTimestamp[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetTimestamp(i, arr[i]); - } + public void SetCodeWScope(int idx, BSONCodeWScope val) + { + _SetValue(idx, BSONValue.GetCodeWScope(val)); } - public BSONArray(BSONCodeWScope[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetCodeWScope(i, arr[i]); - } - } + private void _SetValue(int idx, BSONValue val) + { + SetBSONValueNew(idx.ToString(), val); + } - 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) { + 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)); diff --git a/Ejdb.BSON/BSONDocument.cs b/Ejdb.BSON/BSONDocument.cs index 33ca72e..8a065c7 100644 --- a/Ejdb.BSON/BSONDocument.cs +++ b/Ejdb.BSON/BSONDocument.cs @@ -19,9 +19,6 @@ 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,58 +27,9 @@ 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; - - [NonSerializedAttribute] + public class BSONDocument : IBSONValue, IEnumerable, ICloneable + { + [NonSerializedAttribute] Dictionary _fields; [NonSerializedAttribute] @@ -115,18 +63,19 @@ public ICollection Keys { /// public int KeysCount { get { - return _fieldslist.Count; + return _fields.Count; } } public BSONDocument() { - this._fields = null; - this._fieldslist = new List(); + _fields = new Dictionary(); } public BSONDocument(BSONIterator it) : this() { - while (it.Next() != BSONType.EOO) { - Add(it.FetchCurrentValue()); + while (it.Next() != BSONType.EOO) + { + var value = it.FetchCurrentValue(); + Add(it.CurrentKey, value); } } @@ -146,7 +95,7 @@ public BSONDocument(BSONIterator it, string[] fields) : this() { } 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) { @@ -171,7 +120,7 @@ public BSONDocument(BSONIterator it, string[] fields) : this() { 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)); } } } @@ -182,7 +131,7 @@ public BSONDocument(BSONIterator it, string[] fields) : this() { public BSONDocument(byte[] bsdata) : this() { using (BSONIterator it = new BSONIterator(bsdata)) { while (it.Next() != BSONType.EOO) { - Add(it.FetchCurrentValue()); + Add(it.CurrentKey, it.FetchCurrentValue()); } } } @@ -190,22 +139,24 @@ public BSONDocument(byte[] bsdata) : this() { public BSONDocument(Stream bstream) : this() { using (BSONIterator it = new BSONIterator(bstream)) { while (it.Next() != BSONType.EOO) { - Add(it.FetchCurrentValue()); + Add(it.CurrentKey, it.FetchCurrentValue()); } } } public BSONDocument(BSONDocument doc) : this() { - foreach (var bv in doc) { - Add((BSONValue) bv.Clone()); + foreach (var bv in doc._fields) { + Add(bv.Key, (BSONValue) bv.Value.Clone()); } } - public IEnumerator GetEnumerator() { - return _fieldslist.GetEnumerator(); - } + 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() { + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } @@ -280,139 +231,27 @@ public object this[string key] { return GetObjectValue(key); } else { string prefix = key.Substring(0, ind); - BSONDocument doc = GetObjectValue(prefix) as BSONDocument; + 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); - } - } - - 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); + set + { + var bsonValue = BSONValue.ValueOf(value); + SetBSONValueNew(key, bsonValue); } - 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) { + 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,11 +260,7 @@ public BSONValue DropValue(string key) { /// public void Clear() { _cachedhash = null; - _fieldslist.Clear(); - if (_fields != null) { - _fields.Clear(); - _fields = null; - } + _fields.Clear(); } public void Serialize(Stream os) { @@ -433,8 +268,8 @@ public void Serialize(Stream os) { 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); + foreach (var bv in _fields) { + WriteBSONValue(bv.Key, bv.Value, bw); } bw.Write((byte) 0x00); long end = os.Position; @@ -446,8 +281,8 @@ public void Serialize(Stream os) { byte[] darr; var ms = new MemoryStream(); using (var bw = new ExtBinaryWriter(ms)) { - foreach (BSONValue bv in _fieldslist) { - WriteBSONValue(bv, bw); + foreach (var bv in _fields) { + WriteBSONValue(bv.Key, bv.Value, bw); } darr = ms.ToArray(); } @@ -475,9 +310,9 @@ public override bool Equals(object obj) { if (d1.KeysCount != d2.KeysCount) { return false; } - foreach (BSONValue bv1 in d1._fieldslist) { + foreach (var bv1 in d1._fields) { BSONValue bv2 = d2.GetBSONValue(bv1.Key); - if (bv1 != bv2) { + if (bv1.Value != bv2) { return false; } } @@ -490,8 +325,8 @@ public override int GetHashCode() { } 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; } @@ -507,20 +342,18 @@ public override int GetHashCode() { } 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)); + 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.GetAnonTypeDocument(val); } public object Clone() { @@ -529,158 +362,155 @@ public object Clone() { 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) { - _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; - ov.BSONType = val.BSONType; - ov.Value = val.Value; - } else { - _fieldslist.Add(val); - _fields.Add(val.Key, val); - } - return this; - } - - 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); - } - } - - protected void WriteTypeAndKey(BSONValue bv, ExtBinaryWriter bw) { + internal BSONDocument Add(string key, BSONValue bv) + { + _cachedhash = null; + _fields[key] = bv; + return this; + } + + internal BSONDocument SetBSONValueNew(string key, BSONValue val) + { + _cachedhash = null; + CheckFields(); + + if (val.BSONType == BSONType.STRING && key == "_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 + _fields.Add(key, val); + + return this; + } + + protected virtual void CheckKey(string key) { + } + + 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); + 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(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.ToLocalTime() - 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); + bw.WriteCString(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); - } + protected void CheckFields() + { } } } diff --git a/Ejdb.BSON/BSONIterator.cs b/Ejdb.BSON/BSONIterator.cs index ac5ee26..00db3de 100644 --- a/Ejdb.BSON/BSONIterator.cs +++ b/Ejdb.BSON/BSONIterator.cs @@ -237,99 +237,101 @@ public BSONValue FetchCurrentValue() { } _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; - } + 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, (object) 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, (object) sv); + var rb = _input.ReadByte(); + Debug.Assert(rb == 0x00); //trailing zero byte + 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: + _entryDataValue = new BSONValue(_ctype, + BSONConstants.Epoch.AddMilliseconds(_input.ReadInt64())); + 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 = 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.CurrentKey, sit.FetchCurrentValue()); + } + _entryDataValue = new BSONValue(_ctype, (object) cw); + break; + } } return _entryDataValue; } diff --git a/Ejdb.BSON/BSONValue.cs b/Ejdb.BSON/BSONValue.cs index c8a6183..c23458f 100644 --- a/Ejdb.BSON/BSONValue.cs +++ b/Ejdb.BSON/BSONValue.cs @@ -14,6 +14,8 @@ // Boston, MA 02111-1307 USA. // ============================================================================================ using System; +using System.Collections.Generic; +using System.Reflection; namespace Ejdb.BSON { @@ -28,23 +30,18 @@ public sealed class BSONValue : IBSONValue, ICloneable { /// public BSONType BSONType { get; internal set; } - /// - /// BSON field key. - /// - public string Key { 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) { @@ -58,7 +55,7 @@ public override bool Equals(object obj) { return false; } BSONValue other = (BSONValue) obj; - if (BSONType != other.BSONType || Key != other.Key) { + if (BSONType != other.BSONType) { return false; } if (Value != null) { @@ -89,12 +86,204 @@ public override int GetHashCode() { } 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.Key, this.Value); + return new BSONValue(this.BSONType, this.Value); } + + public static BSONValue ValueOf(object v) + { + if (v == null) + return GetNull(); + + Func setter; + var vtype = v.GetType(); + TYPE_SETTERS.TryGetValue(vtype, out setter); + + if (setter == null) + setter = GetAnonTypeValue; + + var bsonValue = setter(v); + return bsonValue; + } + + public static BSONDocument GetAnonTypeDocument(object val) + { + var ndoc = new BSONDocument(); + Type vtype = val.GetType(); + foreach (PropertyInfo pi in vtype.GetProperties()) + { + if (pi.CanRead) + ndoc[pi.Name] = pi.GetValue(val, null); + } + + return ndoc; + } + + public static BSONValue GetAnonTypeValue(object val) + { + return GetDocument(GetAnonTypeDocument(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 static BSONValue GetOID(string oid) + { + return new BSONValue(BSONType.OID, new BSONOid(oid)); + } + + public static BSONValue GetOID(BSONOid oid) + { + return new BSONValue(BSONType.OID, oid); + } + + public static BSONValue GetBool(bool val) + { + return new BSONValue(BSONType.BOOL, val); + } + + public static BSONValue GetNumber(int val) + { + return new BSONValue(BSONType.INT, val); + } + + public static BSONValue GetNumber(long val) + { + return new BSONValue(BSONType.LONG, val); + } + + public static BSONValue GetNumber(double val) + { + return new BSONValue(BSONType.DOUBLE, val); + } + + public static BSONValue GetNumber(float val) + { + return new BSONValue(BSONType.DOUBLE, val); + } + + public static BSONValue GetString(string val) + { + return new BSONValue(BSONType.STRING, val); + } + + public static BSONValue GetCode(string val) + { + return new BSONValue(BSONType.CODE, val); + } + + public static BSONValue GetSymbol(string val) + { + return new BSONValue(BSONType.SYMBOL, val); + } + + public static BSONValue GetDate(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 GetDocument(BSONDocument val) + { + return new BSONValue(BSONType.OBJECT, val); + } + + public static BSONValue GetArray(BSONArray val) + { + return new BSONValue(BSONType.ARRAY, val); + } + + public static BSONValue GetTimestamp(BSONTimestamp val) + { + return new BSONValue(BSONType.TIMESTAMP, val); + } + + public static BSONValue GetCodeWScope(BSONCodeWScope val) + { + return new BSONValue(BSONType.CODEWSCOPE, val); + } + + public static Dictionary> TYPE_SETTERS = new Dictionary> + { + {typeof(bool), v => GetBool((bool) v)}, + {typeof(bool[]), v => GetArray(new BSONArray((bool[]) v))}, + {typeof(byte), v => GetNumber((int) v)}, + {typeof(sbyte), v => GetNumber((int) v)}, + {typeof(ushort), v => GetNumber((int) v)}, + {typeof(ushort[]), v => GetArray(new BSONArray((ushort[]) v))}, + {typeof(short), v => GetNumber((int) v)}, + {typeof(short[]), v => GetArray(new BSONArray((short[]) v))}, + {typeof(uint), v => GetNumber((int) v)}, + {typeof(uint[]), v => GetArray(new BSONArray((uint[]) v))}, + {typeof(int), v => GetNumber((int) v)}, + {typeof(int[]), v => GetArray(new BSONArray((int[]) v))}, + {typeof(ulong), v => GetNumber((long) v)}, + {typeof(ulong[]), v => GetArray(new BSONArray((ulong[]) v))}, + {typeof(long), v => GetNumber((long) v)}, + {typeof(long[]), v => GetArray(new BSONArray((long[]) v))}, + {typeof(float), v => GetNumber((float) v)}, + {typeof(float[]), v => GetArray(new BSONArray((float[]) v))}, + {typeof(double), v => GetNumber((double) v)}, + {typeof(double[]), v => GetArray(new BSONArray((double[]) v))}, + {typeof(char), v => GetString(v.ToString())}, + {typeof(string), v => GetString((string) v)}, + {typeof(string[]), v => GetArray(new BSONArray((string[]) v))}, + {typeof(BSONOid), v => GetOID((BSONOid) v)}, + {typeof(BSONOid[]), v => GetArray(new BSONArray((BSONOid[]) v))}, + {typeof(BSONRegexp), v => GetRegexp((BSONRegexp) v)}, + {typeof(BSONRegexp[]), v => GetArray(new BSONArray((BSONRegexp[]) v))}, + {typeof(BSONValue), v => (BSONValue) v}, + {typeof(BSONTimestamp), v => GetTimestamp((BSONTimestamp) v)}, + {typeof(BSONTimestamp[]), v => GetArray(new BSONArray((BSONTimestamp[]) v))}, + {typeof(BSONCodeWScope), v => GetCodeWScope((BSONCodeWScope) v)}, + {typeof(BSONCodeWScope[]), v => GetArray(new BSONArray((BSONCodeWScope[]) v))}, + {typeof(BSONBinData), v => GetBinData((BSONBinData) v)}, + {typeof(BSONBinData[]), v => GetArray(new BSONArray((BSONBinData[]) v))}, + {typeof(BSONDocument), v => GetDocument((BSONDocument) v)}, + {typeof(BSONDocument[]), v => GetArray(new BSONArray((BSONDocument[]) v))}, + {typeof(BSONArray), v => GetArray((BSONArray) v)}, + {typeof(BSONArray[]), v => GetArray(new BSONArray((BSONArray[]) v))}, + {typeof(DateTime), v => GetDate((DateTime) v)}, + {typeof(DateTime[]), v => GetArray(new BSONArray((DateTime[]) v))}, + {typeof(BSONUndefined), v => GetUndefined()}, + {typeof(BSONUndefined[]), v => GetArray(new BSONArray((BSONUndefined[]) v))}, + {typeof(BSONull), v => GetNull() }, + {typeof(BSONull[]), v => GetArray(new BSONArray((BSONull[]) v))} + }; } } diff --git a/Ejdb.BSON/BSONValueWithKey.cs b/Ejdb.BSON/BSONValueWithKey.cs new file mode 100644 index 0000000..d678e90 --- /dev/null +++ b/Ejdb.BSON/BSONValueWithKey.cs @@ -0,0 +1,21 @@ +// // Copyright © Anton Paar GmbH, 2004-2013 +// +namespace Ejdb.BSON +{ + public class BSONValueWithKey + { + public BSONValueWithKey(string key, BSONValue value, BSONType type) + { + Value = value.Value; + BSONType = type; + Key = key; + } + + public object Value { get; private set; } + + public BSONType BSONType { get; set; } + + public string Key { get; private set; } + + } +} \ No newline at end of file diff --git a/Ejdb.DB/EJDB.cs b/Ejdb.DB/EJDB.cs index 26bbc82..af6d48a 100644 --- a/Ejdb.DB/EJDB.cs +++ b/Ejdb.DB/EJDB.cs @@ -947,7 +947,7 @@ bool Save(IntPtr cptr, BSONDocument doc, bool merge) { //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)); + doc.SetBSONValueNew("_id", BSONValue.GetOID(new BSONOid(oiddata))); } if (_throwonfail && !rv) { throw new EJDBException(this); diff --git a/Ejdb.Tests/TestBSON.cs b/Ejdb.Tests/TestBSON.cs index 6c56b4f..9d57c17 100644 --- a/Ejdb.Tests/TestBSON.cs +++ b/Ejdb.Tests/TestBSON.cs @@ -33,7 +33,7 @@ public void TestSerializeEmpty() { public void TestSerialize1() { byte[] bdata; BSONDocument doc = new BSONDocument(); - doc.SetNumber("0", 1); + doc.SetBSONValueNew("0", BSONValue.GetNumber(1)); //0C-00-00-00 len //10 type //30-00 key @@ -46,7 +46,7 @@ public void TestSerialize1() { BSONDocument doc2 = new BSONDocument(doc.ToByteArray()); Assert.AreEqual(1, doc2.KeysCount); int c = 0; - foreach (BSONValue bv in doc2) { + foreach (var bv in doc2) { c++; Assert.IsNotNull(bv); Assert.AreEqual(BSONType.INT, bv.BSONType); @@ -54,12 +54,12 @@ public void TestSerialize1() { Assert.AreEqual(1, bv.Value); } Assert.That(c > 0); - doc2.SetNumber("0", 2); + doc2.SetBSONValueNew("0", BSONValue.GetNumber(2)); Assert.AreEqual(1, doc2.KeysCount); object ival = doc2["0"]; Assert.IsInstanceOf(typeof(int), ival); Assert.AreEqual(2, ival); - doc2.SetNumber("1", Int32.MaxValue); + doc2.SetBSONValueNew("1", BSONValue.GetNumber(Int32.MaxValue)); //13-00-00-00 //10 //30-00 @@ -136,12 +136,12 @@ public void TestIterate1() { Assert.IsNotNull(bv); if (cnt == 0) { Assert.IsTrue(bv.BSONType == BSONType.STRING); - Assert.IsTrue(bv.Key == "a"); + Assert.IsTrue(it.CurrentKey == "a"); Assert.AreEqual("av", bv.Value); } if (cnt == 1) { - Assert.IsTrue(bv.BSONType == BSONType.INT); - Assert.IsTrue(bv.Key == "bb"); + Assert.IsTrue(bv.BSONType == BSONType.INT); + Assert.IsTrue(it.CurrentKey == "bb"); Assert.AreEqual(24, bv.Value); } cnt++; @@ -200,15 +200,16 @@ public void TestIterate2() { it = new BSONIterator(doc); foreach (var bv in it.Values()) { if (c == 0) { - Assert.AreEqual("a", bv.Key); + Assert.AreEqual("a", it.CurrentKey); Assert.AreEqual("av", bv.Value); } if (c == 1) { - Assert.AreEqual("b", bv.Key); + Assert.AreEqual("b", it.CurrentKey); BSONDocument sdoc = bv.Value as BSONDocument; Assert.IsNotNull(sdoc); - foreach (var bv2 in new BSONIterator(sdoc).Values()) { - Assert.AreEqual("cc", bv2.Key); + 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); } @@ -242,8 +243,10 @@ public void TestIterateRE() { } Assert.AreEqual("REGEXINTOBJECTINT", cs); cs = ""; - foreach (var bv in new BSONIterator(doc).Values()) { - if (bv.Key == "a") { + + 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 { diff --git a/Ejdb.Tests/TestEJDB.cs b/Ejdb.Tests/TestEJDB.cs index b8b916b..b49e3bd 100644 --- a/Ejdb.Tests/TestEJDB.cs +++ b/Ejdb.Tests/TestEJDB.cs @@ -49,7 +49,8 @@ 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); + BSONDocument doc = new BSONDocument(); + doc.SetBSONValueNew("age", BSONValue.GetNumber(33)); Assert.IsNull(doc["_id"]); bool rv = jb.Save("mycoll", doc); Assert.IsTrue(rv); @@ -82,7 +83,8 @@ 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.SetBSONValueNew("age", BSONValue.GetNumber(33)); Assert.IsNull(doc["_id"]); bool rv = jb.Save("mycoll", doc); Assert.IsTrue(rv); From 9da19ba10be9c6740a314c5968c71d4141b5b561 Mon Sep 17 00:00:00 2001 From: Oliver Klemencic Date: Fri, 27 Dec 2013 18:45:00 +0100 Subject: [PATCH 05/26] Renaming of types to use the same names as the MongoDb wrapper --- Ejdb.BSON/BSONArray.cs | 108 +++++++-------- Ejdb.BSON/BSONBinData.cs | 6 +- Ejdb.BSON/BSONCodeWScope.cs | 14 +- Ejdb.BSON/BSONConstants.cs | 10 +- Ejdb.BSON/BSONDocument.cs | 243 +++++++++++++++++----------------- Ejdb.BSON/BSONIterator.cs | 198 +++++++++++++-------------- Ejdb.BSON/BSONOid.cs | 36 ++--- Ejdb.BSON/BSONRegexp.cs | 18 +-- Ejdb.BSON/BSONTimestamp.cs | 16 +-- Ejdb.BSON/BSONType.cs | 2 +- Ejdb.BSON/BSONUndefined.cs | 12 +- Ejdb.BSON/BSONValue.cs | 178 ++++++++++++------------- Ejdb.BSON/BSONValueWithKey.cs | 8 +- Ejdb.BSON/BsonNull.cs | 53 ++++++++ Ejdb.BSON/IBSONValue.cs | 4 +- Ejdb.DB/EJDB.cs | 46 +++---- Ejdb.DB/EJDBQCursor.cs | 12 +- Ejdb.DB/EJDBQuery.cs | 26 ++-- Ejdb.DB/Query/IQuery.cs | 2 +- Ejdb.DB/Query/Query.cs | 29 +--- Ejdb.Tests/TestBSON.cs | 106 +++++++-------- Ejdb.Tests/TestEJDB.cs | 44 +++--- 22 files changed, 605 insertions(+), 566 deletions(-) create mode 100644 Ejdb.BSON/BsonNull.cs diff --git a/Ejdb.BSON/BSONArray.cs b/Ejdb.BSON/BSONArray.cs index 552e443..8d27048 100644 --- a/Ejdb.BSON/BSONArray.cs +++ b/Ejdb.BSON/BSONArray.cs @@ -19,11 +19,11 @@ namespace Ejdb.BSON { [Serializable] - public class BSONArray : BSONDocument { + public class BsonArray : BsonDocument { - public override BSONType BSONType { + public override BsonType BSONType { get { - return BSONType.ARRAY; + return BsonType.ARRAY; } } @@ -33,124 +33,124 @@ public object this[int key] { } } - public BSONArray() { + public BsonArray() { } - public BSONArray(BSONUndefined[] arr) { + public BsonArray(BsonUndefined[] arr) { for (var i = 0; i < arr.Length; ++i) { SetUndefined(i); } } - public BSONArray(BSONull[] arr) { + public BsonArray(BsonNull[] arr) { for (var i = 0; i < arr.Length; ++i) { SetNull(i); } } - public BSONArray(ushort[] arr) + public BsonArray(ushort[] arr) { for (int i = 0; i < arr.Length; ++i) SetNumber(i, arr[i]); } - public BSONArray(uint[] arr) + public BsonArray(uint[] arr) { for (int i = 0; i < arr.Length; ++i) SetNumber(i, arr[i]); } - public BSONArray(ulong[] arr) + public BsonArray(ulong[] arr) { for (int i = 0; i < arr.Length; ++i) SetNumber(i, (long) arr[i]); } - public BSONArray(short[] arr) + public BsonArray(short[] arr) { for (int i = 0; i < arr.Length; ++i) SetNumber(i, arr[i]); } - public BSONArray(string[] arr) + public BsonArray(string[] arr) { for (int i = 0; i < arr.Length; ++i) SetString(i, arr[i]); } - public BSONArray(int[] arr) + public BsonArray(int[] arr) { for (int i = 0; i < arr.Length; ++i) SetNumber(i, arr[i]); } - public BSONArray(long[] arr) + public BsonArray(long[] arr) { for (int i = 0; i < arr.Length; ++i) SetNumber(i, arr[i]); } - public BSONArray(float[] arr) + public BsonArray(float[] arr) { for (int i = 0; i < arr.Length; ++i) SetNumber(i, arr[i]); } - public BSONArray(double[] arr) + public BsonArray(double[] arr) { for (int i = 0; i < arr.Length; ++i) SetNumber(i, arr[i]); } - public BSONArray(bool[] arr) + public BsonArray(bool[] arr) { for (int i = 0; i < arr.Length; ++i) SetBool(i, arr[i]); } - public BSONArray(BSONOid[] arr) + public BsonArray(BsonOid[] arr) { for (int i = 0; i < arr.Length; ++i) SetOID(i, arr[i]); } - public BSONArray(DateTime[] arr) + public BsonArray(DateTime[] arr) { for (int i = 0; i < arr.Length; ++i) SetDate(i, arr[i]); } - public BSONArray(BSONDocument[] arr) + public BsonArray(BsonDocument[] arr) { for (int i = 0; i < arr.Length; ++i) SetObject(i, arr[i]); } - public BSONArray(BSONArray[] arr) + public BsonArray(BsonArray[] arr) { for (int i = 0; i < arr.Length; ++i) SetArray(i, arr[i]); } - public BSONArray(BSONRegexp[] arr) + public BsonArray(BsonRegexp[] arr) { for (int i = 0; i < arr.Length; ++i) SetRegexp(i, arr[i]); } - public BSONArray(BSONTimestamp[] arr) + public BsonArray(BsonTimestamp[] arr) { for (int i = 0; i < arr.Length; ++i) SetTimestamp(i, arr[i]); } - public BSONArray(BSONCodeWScope[] arr) + public BsonArray(BsonCodeWScope[] arr) { for (int i = 0; i < arr.Length; ++i) SetCodeWScope(i, arr[i]); } - public BSONArray(BSONBinData[] arr) + public BsonArray(BsonBinData[] arr) { for (int i = 0; i < arr.Length; ++i) SetBinData(i, arr[i]); @@ -158,112 +158,112 @@ public BSONArray(BSONBinData[] arr) public void SetNull(int idx) { - _SetValue(idx, BSONValue.GetNull()); + _SetValue(idx, BsonValue.GetNull()); } public void SetUndefined(int idx) { - _SetValue(idx, BSONValue.GetUndefined()); + _SetValue(idx, BsonValue.GetUndefined()); } public void SetMaxKey(int idx) { - _SetValue(idx, BSONValue.GetMaxKey()); + _SetValue(idx, BsonValue.GetMaxKey()); } public void SetMinKey(int idx) { - _SetValue(idx, BSONValue.GetMinKey()); + _SetValue(idx, BsonValue.GetMinKey()); } public void SetOID(int idx, string oid) { - _SetValue(idx, BSONValue.GetOID(oid)); + _SetValue(idx, BsonValue.GetOID(oid)); } - public void SetOID(int idx, BSONOid oid) + public void SetOID(int idx, BsonOid oid) { - _SetValue(idx, BSONValue.GetOID(oid)); + _SetValue(idx, BsonValue.GetOID(oid)); } public void SetBool(int idx, bool val) { - _SetValue(idx, BSONValue.GetBool(val)); + _SetValue(idx, BsonValue.GetBool(val)); } public void SetNumber(int idx, int val) { - _SetValue(idx, BSONValue.GetNumber(val)); + _SetValue(idx, BsonValue.GetNumber(val)); } public void SetNumber(int idx, long val) { - _SetValue(idx, BSONValue.GetNumber(val)); + _SetValue(idx, BsonValue.GetNumber(val)); } public void SetNumber(int idx, double val) { - _SetValue(idx, BSONValue.GetNumber(val)); + _SetValue(idx, BsonValue.GetNumber(val)); } public void SetNumber(int idx, float val) { - _SetValue(idx, BSONValue.GetNumber(val)); + _SetValue(idx, BsonValue.GetNumber(val)); } public void SetString(int idx, string val) { - _SetValue(idx, BSONValue.GetString(val)); + _SetValue(idx, BsonValue.GetString(val)); } public void SetCode(int idx, string val) { - _SetValue(idx, BSONValue.GetCode(val)); + _SetValue(idx, BsonValue.GetCode(val)); } public void SetSymbol(int idx, string val) { - _SetValue(idx, BSONValue.GetSymbol(val)); + _SetValue(idx, BsonValue.GetSymbol(val)); } public void SetDate(int idx, DateTime val) { - _SetValue(idx, BSONValue.GetDate(val)); + _SetValue(idx, BsonValue.GetDate(val)); } - public void SetRegexp(int idx, BSONRegexp val) + public void SetRegexp(int idx, BsonRegexp val) { - _SetValue(idx, BSONValue.GetRegexp(val)); + _SetValue(idx, BsonValue.GetRegexp(val)); } - public void SetBinData(int idx, BSONBinData val) + public void SetBinData(int idx, BsonBinData val) { - _SetValue(idx, BSONValue.GetBinData(val)); + _SetValue(idx, BsonValue.GetBinData(val)); } - public void SetObject(int idx, BSONDocument val) + public void SetObject(int idx, BsonDocument val) { - _SetValue(idx, BSONValue.GetDocument(val)); + _SetValue(idx, BsonValue.GetDocument(val)); } - public void SetArray(int idx, BSONArray val) + public void SetArray(int idx, BsonArray val) { - _SetValue(idx, BSONValue.GetArray(val)); + _SetValue(idx, BsonValue.GetArray(val)); } - public void SetTimestamp(int idx, BSONTimestamp val) + public void SetTimestamp(int idx, BsonTimestamp val) { - _SetValue(idx, BSONValue.GetTimestamp(val)); + _SetValue(idx, BsonValue.GetTimestamp(val)); } - public void SetCodeWScope(int idx, BSONCodeWScope val) + public void SetCodeWScope(int idx, BsonCodeWScope val) { - _SetValue(idx, BSONValue.GetCodeWScope(val)); + _SetValue(idx, BsonValue.GetCodeWScope(val)); } - private void _SetValue(int idx, BSONValue val) + private void _SetValue(int idx, BsonValue val) { - SetBSONValueNew(idx.ToString(), val); + Add(idx.ToString(), val); } 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..010fd4e 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,11 @@ 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"; } } diff --git a/Ejdb.BSON/BSONDocument.cs b/Ejdb.BSON/BSONDocument.cs index 8a065c7..a2029ed 100644 --- a/Ejdb.BSON/BSONDocument.cs +++ b/Ejdb.BSON/BSONDocument.cs @@ -27,33 +27,34 @@ namespace Ejdb.BSON { /// BSON document deserialized data wrapper. /// [Serializable] - public class BSONDocument : IBSONValue, IEnumerable, ICloneable + 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; } } @@ -67,29 +68,29 @@ public int KeysCount { } } - public BSONDocument() { - _fields = new Dictionary(); + public BsonDocument() { + _fields = new Dictionary(); } - public BSONDocument(BSONIterator it) : this() { - while (it.Next() != BSONType.EOO) + 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() { + 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; } @@ -98,7 +99,7 @@ public BSONDocument(BSONIterator it, string[] fields) : this() { 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]; @@ -117,10 +118,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(kk, new BSONValue(bt, ndoc)); + Add(kk, new BsonValue(bt, ndoc)); } } } @@ -128,32 +129,32 @@ 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) { + public BsonDocument(byte[] bsdata) : this() { + using (BsonIterator it = new BsonIterator(bsdata)) { + while (it.Next() != BsonType.EOO) { Add(it.CurrentKey, it.FetchCurrentValue()); } } } - public BSONDocument(Stream bstream) : this() { - using (BSONIterator it = new BSONIterator(bstream)) { - while (it.Next() != BSONType.EOO) { + public BsonDocument(Stream bstream) : this() { + using (BsonIterator it = new BsonIterator(bstream)) { + while (it.Next() != BsonType.EOO) { Add(it.CurrentKey, it.FetchCurrentValue()); } } } - public BSONDocument(BSONDocument doc) : this() { + public BsonDocument(BsonDocument doc) : this() { foreach (var bv in doc._fields) { - Add(bv.Key, (BSONValue) bv.Value.Clone()); + Add(bv.Key, (BsonValue) bv.Value.Clone()); } } - public IEnumerator GetEnumerator() + public IEnumerator GetEnumerator() { foreach (var entry in _fields) - yield return new BSONValueWithKey(entry.Key, entry.Value, entry.Value.BSONType); + yield return new BsonValueWithKey(entry.Key, entry.Value, entry.Value.BSONType); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { @@ -166,7 +167,8 @@ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { /// The byte array. public byte[] ToByteArray() { byte[] res; - using (var ms = new MemoryStream()) { + using (var ms = new MemoryStream()) + { Serialize(ms); res = ms.ToArray(); } @@ -181,11 +183,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 { @@ -200,9 +201,9 @@ 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); + var bv = GetBsonValue(key); return bv != null ? bv.Value : null; } @@ -212,18 +213,18 @@ 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 + /// or public object this[string key] { get { int ind; @@ -231,7 +232,7 @@ public object this[string key] { return GetObjectValue(key); } else { string prefix = key.Substring(0, ind); - var doc = GetObjectValue(prefix) as BSONDocument; + var doc = GetObjectValue(prefix) as BsonDocument; if (doc == null || key.Length < ind + 2) { return null; } @@ -240,13 +241,13 @@ public object this[string key] { } set { - var bsonValue = BSONValue.ValueOf(value); - SetBSONValueNew(key, bsonValue); + var bsonValue = BsonValue.ValueOf(value); + Add(key, bsonValue); } } - public BSONValue DropValue(string key) { - var bv = GetBSONValue(key); + public BsonValue DropValue(string key) { + var bv = GetBsonValue(key); if (bv == null) { return bv; } @@ -264,29 +265,34 @@ public void Clear() { } public void Serialize(Stream os) { - if (os.CanSeek) { + if (os.CanSeek) + { long start = os.Position; os.Position += 4; //skip int32 document size - using (var bw = new ExtBinaryWriter(os, Encoding.UTF8, true)) { - foreach (var bv in _fields) { - WriteBSONValue(bv.Key, bv.Value, bw); - } + using (var bw = new ExtBinaryWriter(os, Encoding.UTF8, true)) + { + foreach (var bv in _fields) + WriteBsonValue(bv.Key, bv.Value, 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 { + } else + { byte[] darr; var ms = new MemoryStream(); - using (var bw = new ExtBinaryWriter(ms)) { - foreach (var bv in _fields) { - WriteBSONValue(bv.Key, bv.Value, bw); - } + using (var bw = new ExtBinaryWriter(ms)) + { + foreach (var bv in _fields) + WriteBsonValue(bv.Key, bv.Value, bw); + darr = ms.ToArray(); } - using (var bw = new ExtBinaryWriter(os, Encoding.UTF8, true)) { + using (var bw = new ExtBinaryWriter(os, Encoding.UTF8, true)) + { bw.Write(darr.Length + 4/*doclen*/ + 1/*0x00*/); bw.Write(darr); bw.Write((byte) 0x00); @@ -302,16 +308,16 @@ public override bool Equals(object obj) { if (ReferenceEquals(this, obj)) { return true; } - if (!(obj is BSONDocument)) { + if (!(obj is BsonDocument)) { return false; } - BSONDocument d1 = this; - BSONDocument d2 = ((BSONDocument) obj); + BsonDocument d1 = this; + BsonDocument d2 = ((BsonDocument) obj); if (d1.KeysCount != d2.KeysCount) { return false; } foreach (var bv1 in d1._fields) { - BSONValue bv2 = d2.GetBSONValue(bv1.Key); + BsonValue bv2 = d2.GetBsonValue(bv1.Key); if (bv1.Value != bv2) { return false; } @@ -320,10 +326,10 @@ public override bool Equals(object obj) { } public override int GetHashCode() { - if (_cachedhash != null) { - return (int) _cachedhash; - } - unchecked { + if (_cachedhash != null) + return (int) _cachedhash; + + unchecked { int hash = 1; foreach (var bv in _fields) { hash = (hash * 31) + bv.Key.GetHashCode() + bv.Value.GetHashCode(); @@ -333,31 +339,31 @@ public override int GetHashCode() { 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) { + public static BsonDocument ValueOf(object val) { if (val == null) - return new BSONDocument(); + return new BsonDocument(); var vtype = val.GetType(); - if (val is BSONDocument) - return (BSONDocument) val; + if (val is BsonDocument) + return (BsonDocument) val; if (vtype == typeof (byte[])) - return new BSONDocument((byte[]) val); + return new BsonDocument((byte[]) val); - return BSONValue.GetAnonTypeDocument(val); + return BsonValue.GetAnonTypeDocument(val); } public object Clone() { - return new BSONDocument(this); + return new BsonDocument(this); } public override string ToString() { @@ -368,22 +374,14 @@ public override string ToString() { // Private staff //.////////////////////////////////////////////////////////////////// - internal BSONDocument Add(string key, BSONValue bv) + public BsonDocument Add(string key, BsonValue val) { _cachedhash = null; - _fields[key] = bv; - return this; - } - internal BSONDocument SetBSONValueNew(string key, BSONValue val) - { - _cachedhash = null; - CheckFields(); - - if (val.BSONType == BSONType.STRING && key == "_id") - val = new BSONValue(BSONType.OID, new BSONOid((string)val.Value)); + if (val.BSONType == BsonType.STRING && key == BsonConstants.Id) + val = new BsonValue(BsonType.OID, new BsonOid((string)val.Value)); - BSONValue ov; + BsonValue ov; if (_fields.TryGetValue(key, out ov)) { ov.BSONType = val.BSONType; @@ -398,96 +396,97 @@ internal BSONDocument SetBSONValueNew(string key, BSONValue val) protected virtual void CheckKey(string key) { } - protected void WriteBSONValue(string key, BSONValue bv, ExtBinaryWriter bw) { - BSONType bt = bv.BSONType; + protected void WriteBsonValue(string key, BsonValue bv, ExtBinaryWriter bw) + { + BsonType bt = bv.BSONType; switch (bt) { - case BSONType.EOO: + case BsonType.EOO: break; - case BSONType.NULL: - case BSONType.UNDEFINED: - case BSONType.MAXKEY: - case BSONType.MINKEY: + case BsonType.NULL: + case BsonType.UNDEFINED: + case BsonType.MAXKEY: + case BsonType.MINKEY: WriteTypeAndKey(key, bv, bw); break; - case BSONType.OID: + case BsonType.OID: { WriteTypeAndKey(key, bv, bw); - BSONOid oid = (BSONOid)bv.Value; + 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: + case BsonType.STRING: + case BsonType.CODE: + case BsonType.SYMBOL: WriteTypeAndKey(key, bv, bw); bw.WriteBSONString((string)bv.Value); break; - case BSONType.BOOL: + case BsonType.BOOL: WriteTypeAndKey(key, bv, bw); bw.Write((bool)bv.Value); break; - case BSONType.INT: + case BsonType.INT: WriteTypeAndKey(key, bv, bw); bw.Write((int)bv.Value); break; - case BSONType.LONG: + case BsonType.LONG: WriteTypeAndKey(key, bv, bw); bw.Write((long)bv.Value); break; - case BSONType.ARRAY: - case BSONType.OBJECT: + case BsonType.ARRAY: + case BsonType.OBJECT: { - BSONDocument doc = (BSONDocument)bv.Value; + BsonDocument doc = (BsonDocument)bv.Value; WriteTypeAndKey(key, bv, bw); doc.Serialize(bw.BaseStream); break; } - case BSONType.DATE: + case BsonType.DATE: { DateTime dt = (DateTime)bv.Value; - var diff = dt.ToLocalTime() - BSONConstants.Epoch; + var diff = dt.ToLocalTime() - BsonConstants.Epoch; long time = (long)Math.Floor(diff.TotalMilliseconds); WriteTypeAndKey(key, bv, bw); bw.Write(time); break; } - case BSONType.DOUBLE: + case BsonType.DOUBLE: WriteTypeAndKey(key, bv, bw); bw.Write((double)bv.Value); break; - case BSONType.REGEX: + case BsonType.REGEX: { - BSONRegexp rv = (BSONRegexp)bv.Value; + BsonRegexp rv = (BsonRegexp)bv.Value; WriteTypeAndKey(key, bv, bw); bw.WriteCString(rv.Re ?? ""); bw.WriteCString(rv.Opts ?? ""); break; } - case BSONType.BINDATA: + case BsonType.BINDATA: { - BSONBinData bdata = (BSONBinData)bv.Value; + 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: + case BsonType.DBREF: //Unsupported DBREF! break; - case BSONType.TIMESTAMP: + case BsonType.TIMESTAMP: { - BSONTimestamp ts = (BSONTimestamp)bv.Value; + BsonTimestamp ts = (BsonTimestamp)bv.Value; WriteTypeAndKey(key, bv, bw); bw.Write(ts.Inc); bw.Write(ts.Ts); break; } - case BSONType.CODEWSCOPE: + case BsonType.CODEWSCOPE: { - BSONCodeWScope cw = (BSONCodeWScope)bv.Value; + BsonCodeWScope cw = (BsonCodeWScope)bv.Value; WriteTypeAndKey(key, bv, bw); using (var cwwr = new ExtBinaryWriter(new MemoryStream())) { @@ -504,13 +503,9 @@ protected void WriteBSONValue(string key, BSONValue bv, ExtBinaryWriter bw) { } } - protected void WriteTypeAndKey(string key, BSONValue bv, ExtBinaryWriter bw) { + protected void WriteTypeAndKey(string key, BsonValue bv, ExtBinaryWriter bw) { bw.Write((byte) bv.BSONType); bw.WriteCString(key); } - - protected void CheckFields() - { - } - } + } } diff --git a/Ejdb.BSON/BSONIterator.cs b/Ejdb.BSON/BSONIterator.cs index 00db3de..8b72dfb 100644 --- a/Ejdb.BSON/BSONIterator.cs +++ b/Ejdb.BSON/BSONIterator.cs @@ -22,7 +22,7 @@ namespace Ejdb.BSON { - public sealed class BSONIterator : IDisposable, IEnumerable { + public sealed class BsonIterator : IDisposable, IEnumerable { ExtBinaryReader _input; bool _closeOnDispose = true; @@ -31,7 +31,7 @@ public sealed class BSONIterator : IDisposable, IEnumerable { int _doclen; - BSONType _ctype = BSONType.UNKNOWN; + BsonType _ctype = BsonType.UNKNOWN; string _entryKey; @@ -39,10 +39,10 @@ public sealed class BSONIterator : IDisposable, IEnumerable { bool _entryDataSkipped; - BSONValue _entryDataValue; + BsonValue _entryDataValue; /// - /// Returns true if this is disposed. + /// Returns true if this is disposed. /// public bool Disposed { get { @@ -51,11 +51,11 @@ public bool Disposed { } /// - /// Gets a value indicating whether this is empty. + /// Gets a value indicating whether this is empty. /// public bool Empty { get { - return (_ctype == BSONType.EOO); + return (_ctype == BsonType.EOO); } } @@ -74,17 +74,17 @@ public string CurrentKey { get { return _entryKey; } } - public BSONIterator() { //empty iterator - this._ctype = BSONType.EOO; + public BsonIterator() { //empty iterator + this._ctype = BsonType.EOO; } - public BSONIterator(BSONDocument doc) : this(doc.ToByteArray()) { + public BsonIterator(BsonDocument doc) : this(doc.ToByteArray()) { } - public BSONIterator(byte[] bbuf) : this(new MemoryStream(bbuf)) { + public BsonIterator(byte[] bbuf) : this(new MemoryStream(bbuf)) { } - public BSONIterator(Stream input) { + public BsonIterator(Stream input) { if (!input.CanRead) { Dispose(); throw new IOException("Input stream must be readable"); @@ -94,7 +94,7 @@ public BSONIterator(Stream input) { throw new IOException("Input stream must be seekable"); } this._input = new ExtBinaryReader(input); - this._ctype = BSONType.UNKNOWN; + this._ctype = BsonType.UNKNOWN; this._doclen = _input.ReadInt32(); if (this._doclen < 5) { Dispose(); @@ -102,7 +102,7 @@ public BSONIterator(Stream input) { } } - BSONIterator(ExtBinaryReader input, int doclen) { + BsonIterator(ExtBinaryReader input, int doclen) { this._input = input; this._doclen = doclen; if (this._doclen < 5) { @@ -111,12 +111,12 @@ public BSONIterator(Stream input) { } } - internal BSONIterator(BSONIterator it) : this(it._input, it._entryLen + 4) { + internal BsonIterator(BsonIterator it) : this(it._input, it._entryLen + 4) { _closeOnDispose = false; it._entryDataSkipped = true; } - ~BSONIterator() { + ~BsonIterator() { Dispose(); } @@ -130,12 +130,12 @@ public void Dispose() { void CheckDisposed() { if (Disposed) { - throw new ObjectDisposedException("BSONIterator"); + throw new ObjectDisposedException("BsonIterator"); } } - public IEnumerator GetEnumerator() { - while (Next() != BSONType.EOO) { + public IEnumerator GetEnumerator() { + while (Next() != BsonType.EOO) { yield return _ctype; } } @@ -144,84 +144,84 @@ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } - public IEnumerable Values() { - while (Next() != BSONType.EOO) { + public IEnumerable Values() { + while (Next() != BsonType.EOO) { yield return FetchCurrentValue(); } } - public BSONDocument ToBSONDocument(params string[] fields) { + public BsonDocument ToBsonDocument(params string[] fields) { if (fields.Length > 0) { - return new BSONDocument(this, fields); + return new BsonDocument(this, fields); } else { - return new BSONDocument(this); + return new BsonDocument(this); } } - public BSONType Next() { + public BsonType Next() { CheckDisposed(); - if (_ctype == BSONType.EOO) { - return BSONType.EOO; + if (_ctype == BsonType.EOO) { + return BsonType.EOO; } - if (!_entryDataSkipped && _ctype != BSONType.UNKNOWN) { + if (!_entryDataSkipped && _ctype != BsonType.UNKNOWN) { SkipData(); } byte bv = _input.ReadByte(); - if (!Enum.IsDefined(typeof(BSONType), bv)) { + if (!Enum.IsDefined(typeof(BsonType), bv)) { throw new InvalidBSONDataException("Unknown bson type: " + bv); } _entryDataSkipped = false; _entryDataValue = null; _entryKey = null; - _ctype = (BSONType) bv; + _ctype = (BsonType) bv; _entryLen = 0; - if (_ctype != BSONType.EOO) { + if (_ctype != BsonType.EOO) { ReadKey(); } switch (_ctype) { - case BSONType.EOO: + case BsonType.EOO: Dispose(); - return BSONType.EOO; - case BSONType.UNDEFINED: - case BSONType.NULL: - case BSONType.MAXKEY: - case BSONType.MINKEY: + return BsonType.EOO; + case BsonType.UNDEFINED: + case BsonType.NULL: + case BsonType.MAXKEY: + case BsonType.MINKEY: _entryLen = 0; break; - case BSONType.BOOL: + case BsonType.BOOL: _entryLen = 1; break; - case BSONType.INT: + case BsonType.INT: _entryLen = 4; break; - case BSONType.LONG: - case BSONType.DOUBLE: - case BSONType.TIMESTAMP: - case BSONType.DATE: + case BsonType.LONG: + case BsonType.DOUBLE: + case BsonType.TIMESTAMP: + case BsonType.DATE: _entryLen = 8; break; - case BSONType.OID: + case BsonType.OID: _entryLen = 12; break; - case BSONType.STRING: - case BSONType.CODE: - case BSONType.SYMBOL: + case BsonType.STRING: + case BsonType.CODE: + case BsonType.SYMBOL: _entryLen = _input.ReadInt32(); break; - case BSONType.DBREF: + case BsonType.DBREF: //Unsupported DBREF! _entryLen = 12 + _input.ReadInt32(); break; - case BSONType.BINDATA: + case BsonType.BINDATA: _entryLen = 1 + _input.ReadInt32(); break; - case BSONType.OBJECT: - case BSONType.ARRAY: - case BSONType.CODEWSCOPE: + case BsonType.OBJECT: + case BsonType.ARRAY: + case BsonType.CODEWSCOPE: _entryLen = _input.ReadInt32() - 4; Debug.Assert(_entryLen > 0); break; - case BSONType.REGEX: + case BsonType.REGEX: _entryLen = 0; break; default: @@ -230,106 +230,106 @@ public BSONType Next() { return _ctype; } - public BSONValue FetchCurrentValue() { + 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); + case BsonType.EOO: + case BsonType.UNDEFINED: + case BsonType.NULL: + case BsonType.MAXKEY: + case BsonType.MINKEY: + _entryDataValue = new BsonValue(_ctype, _entryKey); break; - case BSONType.OID: + case BsonType.OID: Debug.Assert(_entryLen == 12); - _entryDataValue = new BSONValue(_ctype, (object) new BSONOid(_input)); + _entryDataValue = new BsonValue(_ctype, (object) new BsonOid(_input)); break; - case BSONType.STRING: - case BSONType.CODE: - case BSONType.SYMBOL: + 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, (object) sv); + _entryDataValue = new BsonValue(_ctype, (object) sv); var rb = _input.ReadByte(); Debug.Assert(rb == 0x00); //trailing zero byte break; } - case BSONType.BOOL: - _entryDataValue = new BSONValue(_ctype, (object) _input.ReadBoolean()); + case BsonType.BOOL: + _entryDataValue = new BsonValue(_ctype, (object) _input.ReadBoolean()); break; - case BSONType.INT: - _entryDataValue = new BSONValue(_ctype, (object) _input.ReadInt32()); + case BsonType.INT: + _entryDataValue = new BsonValue(_ctype, (object) _input.ReadInt32()); break; - case BSONType.OBJECT: - case BSONType.ARRAY: + 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) + 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); + _entryDataValue = new BsonValue(_ctype, doc); break; } - case BSONType.DOUBLE: - _entryDataValue = new BSONValue(_ctype, _input.ReadDouble()); + case BsonType.DOUBLE: + _entryDataValue = new BsonValue(_ctype, _input.ReadDouble()); break; - case BSONType.LONG: - _entryDataValue = new BSONValue(_ctype, _input.ReadInt64()); + case BsonType.LONG: + _entryDataValue = new BsonValue(_ctype, _input.ReadInt64()); break; - case BSONType.DATE: - _entryDataValue = new BSONValue(_ctype, - BSONConstants.Epoch.AddMilliseconds(_input.ReadInt64())); + case BsonType.DATE: + _entryDataValue = new BsonValue(_ctype, + BsonConstants.Epoch.AddMilliseconds(_input.ReadInt64())); break; - case BSONType.TIMESTAMP: + case BsonType.TIMESTAMP: { int inc = _input.ReadInt32(); int ts = _input.ReadInt32(); - _entryDataValue = new BSONValue(_ctype, - new BSONTimestamp(inc, ts)); + _entryDataValue = new BsonValue(_ctype, + new BsonTimestamp(inc, ts)); break; } - case BSONType.REGEX: + case BsonType.REGEX: { string re = _input.ReadCString(); string opts = _input.ReadCString(); - _entryDataValue = new BSONValue(_ctype, - new BSONRegexp(re, opts)); + _entryDataValue = new BsonValue(_ctype, + new BsonRegexp(re, opts)); break; } - case BSONType.BINDATA: + case BsonType.BINDATA: { byte subtype = _input.ReadByte(); - BSONBinData bd = new BSONBinData(subtype, _entryLen - 1, _input); - _entryDataValue = new BSONValue(_ctype, bd); + BsonBinData bd = new BsonBinData(subtype, _entryLen - 1, _input); + _entryDataValue = new BsonValue(_ctype, bd); break; } - case BSONType.DBREF: + case BsonType.DBREF: { //Unsupported DBREF! SkipData(true); - _entryDataValue = new BSONValue(_ctype, _entryKey); + _entryDataValue = new BsonValue(_ctype, _entryKey); break; } - case BSONType.CODEWSCOPE: + 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) + 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); + _entryDataValue = new BsonValue(_ctype, (object) cw); break; } } @@ -344,7 +344,7 @@ internal void SkipData(bool force = false) { } _entryDataValue = null; _entryDataSkipped = true; - if (_ctype == BSONType.REGEX) { + if (_ctype == BsonType.REGEX) { _input.SkipCString(); _input.SkipCString(); Debug.Assert(_entryLen == 0); diff --git a/Ejdb.BSON/BSONOid.cs b/Ejdb.BSON/BSONOid.cs index add79c3..97a03ac 100644 --- a/Ejdb.BSON/BSONOid.cs +++ b/Ejdb.BSON/BSONOid.cs @@ -19,40 +19,40 @@ namespace Ejdb.BSON { [Serializable] - public sealed class BSONOid : IComparable, IBSONValue { + public sealed class BsonOid : IComparable, IBsonValue { internal byte[] _bytes; string _cachedString; - public BSONType BSONType { + public BsonType BSONType { get { - return BSONType.OID; + return BsonType.OID; } } - public static BSONOid Empty + public static BsonOid Empty { get { - var result = new BSONOid(); + var result = new BsonOid(); result._bytes = new byte[12]; return result; } } - BSONOid() { + BsonOid() { } - public BSONOid(string val) { + public BsonOid(string val) { ParseOIDString(val); } - public BSONOid(byte[] val) { + public BsonOid(byte[] val) { _bytes = new byte[12]; Array.Copy(val, _bytes, 12); } - public BSONOid(BinaryReader reader) { + public BsonOid(BinaryReader reader) { _bytes = reader.ReadBytes(12); } @@ -76,7 +76,7 @@ void ParseOIDString(string val) { } } - public int CompareTo(BSONOid other) { + public int CompareTo(BsonOid other) { if (ReferenceEquals(other, null)) { return 1; } @@ -112,8 +112,8 @@ public override bool Equals(object obj) { if (ReferenceEquals(this, obj)) { return true; } - if (obj is BSONOid) { - return (CompareTo((BSONOid) obj) == 0); + if (obj is BsonOid) { + return (CompareTo((BsonOid) obj) == 0); } return false; } @@ -122,7 +122,7 @@ public override int GetHashCode() { return ToString().GetHashCode(); } - public static bool operator ==(BSONOid a, BSONOid b) { + public static bool operator ==(BsonOid a, BsonOid b) { if (ReferenceEquals(a, b)) return true; if ((object) a == null || (object) b == null) { @@ -131,20 +131,20 @@ public override int GetHashCode() { return a.Equals(b); } - public static bool operator !=(BSONOid a, BSONOid b) { + public static bool operator !=(BsonOid a, BsonOid b) { return !(a == b); } - public static bool operator >(BSONOid a, BSONOid b) { + public static bool operator >(BsonOid a, BsonOid b) { return a.CompareTo(b) > 0; } - public static bool operator <(BSONOid a, BSONOid b) { + public static bool operator <(BsonOid a, BsonOid b) { return a.CompareTo(b) < 0; } - public static implicit operator BSONOid(string val) { - return new BSONOid(val); + public static implicit operator BsonOid(string val) { + return new BsonOid(val); } } } 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 c23458f..981e626 100644 --- a/Ejdb.BSON/BSONValue.cs +++ b/Ejdb.BSON/BSONValue.cs @@ -23,25 +23,25 @@ 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; } + public BsonType BSONType { get; internal set; } /// /// Deserialized BSON field value. /// public object Value { get; internal set; } - public BSONValue(BSONType type, object value) + public BsonValue(BsonType type, object value) { BSONType = type; Value = value; } - public BSONValue(BSONType type) : this(type, null) { + public BsonValue(BsonType type) : this(type, null) { } public override bool Equals(object obj) { @@ -51,10 +51,10 @@ public override bool Equals(object obj) { if (ReferenceEquals(this, obj)) { return true; } - if (obj.GetType() != typeof(BSONValue)) { + if (obj.GetType() != typeof(BsonValue)) { return false; } - BSONValue other = (BSONValue) obj; + BsonValue other = (BsonValue) obj; if (BSONType != other.BSONType) { return false; } @@ -65,7 +65,7 @@ public override bool Equals(object obj) { } } - public static bool operator ==(BSONValue v1, BSONValue v2) { + public static bool operator ==(BsonValue v1, BsonValue v2) { if (ReferenceEquals(v1, v2)) { return true; } @@ -75,7 +75,7 @@ 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); } @@ -86,24 +86,24 @@ public override int GetHashCode() { } public override string ToString() { - return String.Format("[BSONValue: BSONType={0}, Value={1}]", BSONType, 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); + return String.Format("[BsonValue: BsonType={0}, Key={1}, Value={2}]", BSONType, key, Value); } public object Clone() { - return new BSONValue(this.BSONType, this.Value); + return new BsonValue(this.BSONType, this.Value); } - public static BSONValue ValueOf(object v) + public static BsonValue ValueOf(object v) { if (v == null) return GetNull(); - Func setter; + Func setter; var vtype = v.GetType(); TYPE_SETTERS.TryGetValue(vtype, out setter); @@ -114,9 +114,9 @@ public static BSONValue ValueOf(object v) return bsonValue; } - public static BSONDocument GetAnonTypeDocument(object val) + public static BsonDocument GetAnonTypeDocument(object val) { - var ndoc = new BSONDocument(); + var ndoc = new BsonDocument(); Type vtype = val.GetType(); foreach (PropertyInfo pi in vtype.GetProperties()) { @@ -127,162 +127,162 @@ public static BSONDocument GetAnonTypeDocument(object val) return ndoc; } - public static BSONValue GetAnonTypeValue(object val) + public static BsonValue GetAnonTypeValue(object val) { return GetDocument(GetAnonTypeDocument(val)); } - public static BSONValue GetNull() + public static BsonValue GetNull() { - return new BSONValue(BSONType.NULL); + return new BsonValue(BsonType.NULL); } - public static BSONValue GetUndefined() + public static BsonValue GetUndefined() { - return new BSONValue(BSONType.UNKNOWN); + return new BsonValue(BsonType.UNKNOWN); } - public static BSONValue GetMaxKey() + public static BsonValue GetMaxKey() { - return new BSONValue(BSONType.MAXKEY); + return new BsonValue(BsonType.MAXKEY); } - public static BSONValue GetMinKey() + public static BsonValue GetMinKey() { - return new BSONValue(BSONType.MINKEY); + return new BsonValue(BsonType.MINKEY); } - public static BSONValue GetOID(string oid) + public static BsonValue GetOID(string oid) { - return new BSONValue(BSONType.OID, new BSONOid(oid)); + return new BsonValue(BsonType.OID, new BsonOid(oid)); } - public static BSONValue GetOID(BSONOid oid) + public static BsonValue GetOID(BsonOid oid) { - return new BSONValue(BSONType.OID, oid); + return new BsonValue(BsonType.OID, oid); } - public static BSONValue GetBool(bool val) + public static BsonValue GetBool(bool val) { - return new BSONValue(BSONType.BOOL, val); + return new BsonValue(BsonType.BOOL, val); } - public static BSONValue GetNumber(int val) + public static BsonValue GetNumber(int val) { - return new BSONValue(BSONType.INT, val); + return new BsonValue(BsonType.INT, val); } - public static BSONValue GetNumber(long val) + public static BsonValue GetNumber(long val) { - return new BSONValue(BSONType.LONG, val); + return new BsonValue(BsonType.LONG, val); } - public static BSONValue GetNumber(double val) + public static BsonValue GetNumber(double val) { - return new BSONValue(BSONType.DOUBLE, val); + return new BsonValue(BsonType.DOUBLE, val); } - public static BSONValue GetNumber(float val) + public static BsonValue GetNumber(float val) { - return new BSONValue(BSONType.DOUBLE, val); + return new BsonValue(BsonType.DOUBLE, val); } - public static BSONValue GetString(string val) + public static BsonValue GetString(string val) { - return new BSONValue(BSONType.STRING, val); + return new BsonValue(BsonType.STRING, val); } - public static BSONValue GetCode(string val) + public static BsonValue GetCode(string val) { - return new BSONValue(BSONType.CODE, val); + return new BsonValue(BsonType.CODE, val); } - public static BSONValue GetSymbol(string val) + public static BsonValue GetSymbol(string val) { - return new BSONValue(BSONType.SYMBOL, val); + return new BsonValue(BsonType.SYMBOL, val); } - public static BSONValue GetDate(DateTime val) + public static BsonValue GetDate(DateTime val) { - return new BSONValue(BSONType.DATE, val); + return new BsonValue(BsonType.DATE, val); } - public static BSONValue GetRegexp(BSONRegexp val) + public static BsonValue GetRegexp(BsonRegexp val) { - return new BSONValue(BSONType.REGEX, val); + return new BsonValue(BsonType.REGEX, val); } - public static BSONValue GetBinData(BSONBinData val) + public static BsonValue GetBinData(BsonBinData val) { - return new BSONValue(BSONType.BINDATA, val); + return new BsonValue(BsonType.BINDATA, val); } - public static BSONValue GetDocument(BSONDocument val) + public static BsonValue GetDocument(BsonDocument val) { - return new BSONValue(BSONType.OBJECT, val); + return new BsonValue(BsonType.OBJECT, val); } - public static BSONValue GetArray(BSONArray val) + public static BsonValue GetArray(BsonArray val) { - return new BSONValue(BSONType.ARRAY, val); + return new BsonValue(BsonType.ARRAY, val); } - public static BSONValue GetTimestamp(BSONTimestamp val) + public static BsonValue GetTimestamp(BsonTimestamp val) { - return new BSONValue(BSONType.TIMESTAMP, val); + return new BsonValue(BsonType.TIMESTAMP, val); } - public static BSONValue GetCodeWScope(BSONCodeWScope val) + public static BsonValue GetCodeWScope(BsonCodeWScope val) { - return new BSONValue(BSONType.CODEWSCOPE, val); + return new BsonValue(BsonType.CODEWSCOPE, val); } - public static Dictionary> TYPE_SETTERS = new Dictionary> + public static Dictionary> TYPE_SETTERS = new Dictionary> { {typeof(bool), v => GetBool((bool) v)}, - {typeof(bool[]), v => GetArray(new BSONArray((bool[]) v))}, + {typeof(bool[]), v => GetArray(new BsonArray((bool[]) v))}, {typeof(byte), v => GetNumber((int) v)}, {typeof(sbyte), v => GetNumber((int) v)}, {typeof(ushort), v => GetNumber((int) v)}, - {typeof(ushort[]), v => GetArray(new BSONArray((ushort[]) v))}, + {typeof(ushort[]), v => GetArray(new BsonArray((ushort[]) v))}, {typeof(short), v => GetNumber((int) v)}, - {typeof(short[]), v => GetArray(new BSONArray((short[]) v))}, + {typeof(short[]), v => GetArray(new BsonArray((short[]) v))}, {typeof(uint), v => GetNumber((int) v)}, - {typeof(uint[]), v => GetArray(new BSONArray((uint[]) v))}, + {typeof(uint[]), v => GetArray(new BsonArray((uint[]) v))}, {typeof(int), v => GetNumber((int) v)}, - {typeof(int[]), v => GetArray(new BSONArray((int[]) v))}, + {typeof(int[]), v => GetArray(new BsonArray((int[]) v))}, {typeof(ulong), v => GetNumber((long) v)}, - {typeof(ulong[]), v => GetArray(new BSONArray((ulong[]) v))}, + {typeof(ulong[]), v => GetArray(new BsonArray((ulong[]) v))}, {typeof(long), v => GetNumber((long) v)}, - {typeof(long[]), v => GetArray(new BSONArray((long[]) v))}, + {typeof(long[]), v => GetArray(new BsonArray((long[]) v))}, {typeof(float), v => GetNumber((float) v)}, - {typeof(float[]), v => GetArray(new BSONArray((float[]) v))}, + {typeof(float[]), v => GetArray(new BsonArray((float[]) v))}, {typeof(double), v => GetNumber((double) v)}, - {typeof(double[]), v => GetArray(new BSONArray((double[]) v))}, + {typeof(double[]), v => GetArray(new BsonArray((double[]) v))}, {typeof(char), v => GetString(v.ToString())}, {typeof(string), v => GetString((string) v)}, - {typeof(string[]), v => GetArray(new BSONArray((string[]) v))}, - {typeof(BSONOid), v => GetOID((BSONOid) v)}, - {typeof(BSONOid[]), v => GetArray(new BSONArray((BSONOid[]) v))}, - {typeof(BSONRegexp), v => GetRegexp((BSONRegexp) v)}, - {typeof(BSONRegexp[]), v => GetArray(new BSONArray((BSONRegexp[]) v))}, - {typeof(BSONValue), v => (BSONValue) v}, - {typeof(BSONTimestamp), v => GetTimestamp((BSONTimestamp) v)}, - {typeof(BSONTimestamp[]), v => GetArray(new BSONArray((BSONTimestamp[]) v))}, - {typeof(BSONCodeWScope), v => GetCodeWScope((BSONCodeWScope) v)}, - {typeof(BSONCodeWScope[]), v => GetArray(new BSONArray((BSONCodeWScope[]) v))}, - {typeof(BSONBinData), v => GetBinData((BSONBinData) v)}, - {typeof(BSONBinData[]), v => GetArray(new BSONArray((BSONBinData[]) v))}, - {typeof(BSONDocument), v => GetDocument((BSONDocument) v)}, - {typeof(BSONDocument[]), v => GetArray(new BSONArray((BSONDocument[]) v))}, - {typeof(BSONArray), v => GetArray((BSONArray) v)}, - {typeof(BSONArray[]), v => GetArray(new BSONArray((BSONArray[]) v))}, + {typeof(string[]), v => GetArray(new BsonArray((string[]) v))}, + {typeof(BsonOid), v => GetOID((BsonOid) v)}, + {typeof(BsonOid[]), v => GetArray(new BsonArray((BsonOid[]) v))}, + {typeof(BsonRegexp), v => GetRegexp((BsonRegexp) v)}, + {typeof(BsonRegexp[]), v => GetArray(new BsonArray((BsonRegexp[]) v))}, + {typeof(BsonValue), v => (BsonValue) v}, + {typeof(BsonTimestamp), v => GetTimestamp((BsonTimestamp) v)}, + {typeof(BsonTimestamp[]), v => GetArray(new BsonArray((BsonTimestamp[]) v))}, + {typeof(BsonCodeWScope), v => GetCodeWScope((BsonCodeWScope) v)}, + {typeof(BsonCodeWScope[]), v => GetArray(new BsonArray((BsonCodeWScope[]) v))}, + {typeof(BsonBinData), v => GetBinData((BsonBinData) v)}, + {typeof(BsonBinData[]), v => GetArray(new BsonArray((BsonBinData[]) v))}, + {typeof(BsonDocument), v => GetDocument((BsonDocument) v)}, + {typeof(BsonDocument[]), v => GetArray(new BsonArray((BsonDocument[]) v))}, + {typeof(BsonArray), v => GetArray((BsonArray) v)}, + {typeof(BsonArray[]), v => GetArray(new BsonArray((BsonArray[]) v))}, {typeof(DateTime), v => GetDate((DateTime) v)}, - {typeof(DateTime[]), v => GetArray(new BSONArray((DateTime[]) v))}, - {typeof(BSONUndefined), v => GetUndefined()}, - {typeof(BSONUndefined[]), v => GetArray(new BSONArray((BSONUndefined[]) v))}, - {typeof(BSONull), v => GetNull() }, - {typeof(BSONull[]), v => GetArray(new BSONArray((BSONull[]) v))} + {typeof(DateTime[]), v => GetArray(new BsonArray((DateTime[]) v))}, + {typeof(BsonUndefined), v => GetUndefined()}, + {typeof(BsonUndefined[]), v => GetArray(new BsonArray((BsonUndefined[]) v))}, + {typeof(BsonNull), v => GetNull() }, + {typeof(BsonNull[]), v => GetArray(new BsonArray((BsonNull[]) v))} }; } } diff --git a/Ejdb.BSON/BSONValueWithKey.cs b/Ejdb.BSON/BSONValueWithKey.cs index d678e90..aeca1c7 100644 --- a/Ejdb.BSON/BSONValueWithKey.cs +++ b/Ejdb.BSON/BSONValueWithKey.cs @@ -2,18 +2,18 @@ // namespace Ejdb.BSON { - public class BSONValueWithKey + public class BsonValueWithKey { - public BSONValueWithKey(string key, BSONValue value, BSONType type) + public BsonValueWithKey(string key, BsonValue value, BsonType type) { Value = value.Value; - BSONType = type; + BsonType = type; Key = key; } public object Value { get; private set; } - public BSONType BSONType { get; set; } + public BsonType BsonType { get; set; } public string Key { get; private set; } diff --git a/Ejdb.BSON/BsonNull.cs b/Ejdb.BSON/BsonNull.cs new file mode 100644 index 0000000..48c6c03 --- /dev/null +++ b/Ejdb.BSON/BsonNull.cs @@ -0,0 +1,53 @@ +// ============================================================================================ +// .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; + +namespace Ejdb.BSON { + + [Serializable] + public sealed class BsonNull : IBsonValue { + + public static BsonNull VALUE = new BsonNull(); + + public BsonType BSONType { + get { + return BsonType.NULL; + } + } + + public override bool Equals(object obj) { + if (obj == null) { + return false; + } + if (ReferenceEquals(this, obj)) { + return true; + } + if (!(obj is BsonNull)) { + return false; + } + return true; + } + + public override int GetHashCode() { + return 0; + } + + public override string ToString() { + return "[BsonNull]"; + } + } +} + 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.DB/EJDB.cs b/Ejdb.DB/EJDB.cs index af6d48a..26ec716 100644 --- a/Ejdb.DB/EJDB.cs +++ b/Ejdb.DB/EJDB.cs @@ -352,7 +352,7 @@ public bool IsOpen { /// Gets info of EJDB database itself and its collections. /// /// The DB meta. - public BSONDocument DBMeta { + public BsonDocument DBMeta { get { CheckDisposed(true); //internal static extern IntPtr _ejdbmeta([In] IntPtr db); @@ -365,7 +365,7 @@ public BSONDocument DBMeta { IntPtr bsdataptr = _bson_data2(bsptr, out size); byte[] bsdata = new byte[size]; Marshal.Copy(bsdataptr, bsdata, 0, bsdata.Length); - return new BSONDocument(bsdata); + return new BsonDocument(bsdata); } finally { _bson_del(bsptr); } @@ -799,11 +799,11 @@ public bool TransactionStatus(string cname, out bool active) { return rv; } - public static BSONOid GenerateId() + public static BsonOid GenerateId() { byte[] oiddata = new byte[12]; _bson_oid_gen(oiddata); - return new BSONOid(oiddata); + return new BsonOid(oiddata); } public bool Save(string cname, params object[] docs) { @@ -817,7 +817,7 @@ public bool Save(string cname, params object[] docs) { } } foreach (var doc in docs) { - if (!Save(cptr, BSONDocument.ValueOf(doc), false)) { + if (!Save(cptr, BsonDocument.ValueOf(doc), false)) { if (_throwonfail) { throw new EJDBException(this); } else { @@ -866,7 +866,7 @@ public bool Save(string cname, params object[] docs) { /// /// Command object /// Command response. - public BSONDocument Command(BSONDocument cmd) { + public BsonDocument Command(BsonDocument cmd) { CheckDisposed(); byte[] cmdata = cmd.ToByteArray(); //internal static extern IntPtr _ejdbcommand([In] IntPtr db, [In] byte[] cmd); @@ -878,8 +878,8 @@ public BSONDocument Command(BSONDocument cmd) { if (bsdata.Length == 0) { return null; } - BSONIterator it = new BSONIterator(bsdata); - return it.ToBSONDocument(); + BsonIterator it = new BsonIterator(bsdata); + return it.ToBsonDocument(); } /// @@ -888,7 +888,7 @@ public BSONDocument Command(BSONDocument cmd) { /// Name of collection. /// BSON documents to save. /// True on success. - public bool Save(string cname, params BSONDocument[] docs) { + public bool Save(string cname, params BsonDocument[] docs) { CheckDisposed(); IntPtr cptr = _ejdbcreatecoll(_db, cname, null); if (cptr == IntPtr.Zero) { @@ -917,7 +917,7 @@ public bool Save(string cname, params BSONDocument[] docs) { /// Name of collection. /// BSON documents to save. /// True on success. - public bool SaveMerge(string cname, params BSONDocument[] docs) { + public bool SaveMerge(string cname, params BsonDocument[] docs) { CheckDisposed(); IntPtr cptr = _ejdbcreatecoll(_db, cname, null); if (cptr == IntPtr.Zero) { @@ -939,15 +939,15 @@ public bool SaveMerge(string cname, params BSONDocument[] docs) { return true; } - bool Save(IntPtr cptr, BSONDocument doc, bool merge) { + bool Save(IntPtr cptr, BsonDocument doc, bool merge) { bool rv; - BSONValue bv = doc.GetBSONValue("_id"); + 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.SetBSONValueNew("_id", BSONValue.GetOID(new BSONOid(oiddata))); + doc.Add(BsonConstants.Id, BsonValue.GetOID(new BsonOid(oiddata))); } if (_throwonfail && !rv) { throw new EJDBException(this); @@ -963,7 +963,7 @@ bool Save(IntPtr cptr, BSONDocument doc, bool merge) { /// /// Cname. /// Oid. - public BSONIterator Load(string cname, BSONOid oid) { + public BsonIterator Load(string cname, BsonOid oid) { CheckDisposed(); IntPtr cptr = _ejdbgetcoll(_db, cname); if (cptr == IntPtr.Zero) { @@ -974,7 +974,7 @@ public BSONIterator Load(string cname, BSONOid oid) { if (bsdata.Length == 0) { return null; } - return new BSONIterator(bsdata); + return new BsonIterator(bsdata); } /// @@ -982,7 +982,7 @@ public BSONIterator Load(string cname, BSONOid oid) { /// /// Name of collection. /// Object identifiers. - public bool Remove(string cname, params BSONOid[] oids) { + public bool Remove(string cname, params BsonOid[] oids) { CheckDisposed(); IntPtr cptr = _ejdbgetcoll(_db, cname); if (cptr == IntPtr.Zero) { @@ -1009,12 +1009,12 @@ public bool Remove(string cname, params BSONOid[] oids) { /// 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); + return new EJDBQuery(this, BsonDocument.ValueOf(qv), defaultcollection); } public EJDBQuery CreateQueryFor(string defaultcollection) { CheckDisposed(); - return new EJDBQuery(this, new BSONDocument(), defaultcollection); + return new EJDBQuery(this, new BsonDocument(), defaultcollection); } public EJDBQCursor Find(string collection, IQuery query) @@ -1024,12 +1024,12 @@ public EJDBQCursor Find(string collection, IQuery query) } /// - /// Convert JSON string into BSONDocument. + /// Convert JSON string into BsonDocument. /// Returns `null` if conversion failed. /// - /// The BSONDocument instance on success. + /// The BsonDocument instance on success. /// JSON string - public BSONDocument Json2Bson(string json) { + public BsonDocument Json2Bson(string json) { IntPtr bsonret = _json2bson(json); if (bsonret == IntPtr.Zero) { return null; @@ -1038,8 +1038,8 @@ public BSONDocument Json2Bson(string json) { if (bsdata.Length == 0) { return null; } - BSONIterator it = new BSONIterator(bsdata); - return it.ToBSONDocument(); + BsonIterator it = new BsonIterator(bsdata); + return it.ToBsonDocument(); } //.////////////////////////////////////////////////////////////////// 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..232de7b 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); @@ -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/IQuery.cs b/Ejdb.DB/Query/IQuery.cs index 5e75b02..1913066 100644 --- a/Ejdb.DB/Query/IQuery.cs +++ b/Ejdb.DB/Query/IQuery.cs @@ -7,6 +7,6 @@ namespace Ejdb.DB { public interface IQuery { - BSONDocument GetQueryDocument(); + BsonDocument GetQueryDocument(); } } \ No newline at end of file diff --git a/Ejdb.DB/Query/Query.cs b/Ejdb.DB/Query/Query.cs index 3421053..5e2bb58 100644 --- a/Ejdb.DB/Query/Query.cs +++ b/Ejdb.DB/Query/Query.cs @@ -8,11 +8,11 @@ namespace Ejdb.DB { public class Query : IQuery { - private readonly BSONDocument mQueryDocument; + private readonly BsonDocument mQueryDocument; public Query(string fieldName, object query) { - mQueryDocument = new BSONDocument(); + mQueryDocument = new BsonDocument(); mQueryDocument[fieldName] = query; } @@ -41,24 +41,9 @@ public static IQuery LTE(string fieldName, object value) return BinaryQuery("$lte", fieldName, value); } - public static IQuery In(string fieldName, params int[] comparisonValues) + public static IQuery In(string fieldName, params T[] comparisonValues) { - return BinaryQuery("$in", fieldName, comparisonValues.ToArray()); - } - - public static IQuery In(string fieldName, params float[] comparisonValues) - { - return BinaryQuery("$in", fieldName, comparisonValues.ToArray()); - } - - public static IQuery In(string fieldName, params string[] comparisonValues) - { - return BinaryQuery("$in", fieldName, comparisonValues.ToArray()); - } - - public static IQuery In(string fieldName, params double[] comparisonValues) - { - return BinaryQuery("$in", fieldName, comparisonValues.ToArray()); + return BinaryQuery("$in", fieldName, comparisonValues); } public static IQuery Not(string fieldName, object comparisonValue) @@ -68,18 +53,18 @@ public static IQuery Not(string fieldName, object comparisonValue) public static IQuery Between(string fieldName, T comparisonValue1, T comparisonValue2) { - var comparisonValues = new object[] { comparisonValue1, comparisonValue2 }; + var comparisonValues = new T[] { comparisonValue1, comparisonValue2 }; return BinaryQuery("$bt", fieldName, comparisonValues); } public static IQuery BinaryQuery(string queryOperation, string fieldName, object comparisonValue) { - var query1 = new BSONDocument(); + var query1 = new BsonDocument(); query1[queryOperation] = comparisonValue; return new Query(fieldName, query1); } - public BSONDocument GetQueryDocument() + public BsonDocument GetQueryDocument() { return mQueryDocument; } diff --git a/Ejdb.Tests/TestBSON.cs b/Ejdb.Tests/TestBSON.cs index 9d57c17..f8ba333 100644 --- a/Ejdb.Tests/TestBSON.cs +++ b/Ejdb.Tests/TestBSON.cs @@ -25,15 +25,15 @@ public class TestBSON { [Test] public void TestSerializeEmpty() { - BSONDocument doc = new BSONDocument(); + BsonDocument doc = new BsonDocument(); Assert.AreEqual("05-00-00-00-00", doc.ToDebugDataString()); } [Test] public void TestSerialize1() { byte[] bdata; - BSONDocument doc = new BSONDocument(); - doc.SetBSONValueNew("0", BSONValue.GetNumber(1)); + BsonDocument doc = new BsonDocument(); + doc.Add("0", BsonValue.GetNumber(1)); //0C-00-00-00 len //10 type //30-00 key @@ -43,23 +43,23 @@ public void TestSerialize1() { 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()); + BsonDocument doc2 = new BsonDocument(doc.ToByteArray()); Assert.AreEqual(1, doc2.KeysCount); int c = 0; foreach (var bv in doc2) { c++; Assert.IsNotNull(bv); - Assert.AreEqual(BSONType.INT, bv.BSONType); + Assert.AreEqual(BsonType.INT, bv.BsonType); Assert.AreEqual("0", bv.Key); Assert.AreEqual(1, bv.Value); } Assert.That(c > 0); - doc2.SetBSONValueNew("0", BSONValue.GetNumber(2)); + doc2.Add("0", BsonValue.GetNumber(2)); Assert.AreEqual(1, doc2.KeysCount); object ival = doc2["0"]; Assert.IsInstanceOf(typeof(int), ival); Assert.AreEqual(2, ival); - doc2.SetBSONValueNew("1", BSONValue.GetNumber(Int32.MaxValue)); + doc2.Add("1", BsonValue.GetNumber(Int32.MaxValue)); //13-00-00-00 //10 //30-00 @@ -70,22 +70,22 @@ public void TestSerialize1() { 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); + 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()); + 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 = 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}); + BsonDocument doc = BsonDocument.ValueOf(new {a = "b", c = 1}); //15-00-00-00 //02-61-00 //02-00-00-00 @@ -93,7 +93,7 @@ public void TestAnonTypes() { //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 + 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 @@ -108,11 +108,11 @@ public void TestAnonTypes() { [Test] public void TestIterate1() { - var doc = new BSONDocument(); + var doc = new BsonDocument(); doc["a"] = "av"; doc["bb"] = 24; - //doc["ccc"] = BSONDocument.ValueOf(new{na1 = 1, nb = "2"}); - //doc["d"] = new BSONOid("51b9f3af98195c4600000000"); + //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 @@ -120,27 +120,27 @@ public void TestIterate1() { //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); + BsonIterator it = new BsonIterator(doc); Assert.AreEqual(doc.ToByteArray().Length, it.DocumentLength); var c = ""; - while (it.Next() != BSONType.EOO) { + while (it.Next() != BsonType.EOO) { c += it.CurrentKey; } Assert.AreEqual("abb", c); it.Dispose(); - it = new BSONIterator(doc); + it = new BsonIterator(doc); var cnt = 0; - while (it.Next() != BSONType.EOO) { - BSONValue bv = it.FetchCurrentValue(); + while (it.Next() != BsonType.EOO) { + BsonValue bv = it.FetchCurrentValue(); Assert.IsNotNull(bv); if (cnt == 0) { - Assert.IsTrue(bv.BSONType == BSONType.STRING); + 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(bv.BSONType == BsonType.INT); Assert.IsTrue(it.CurrentKey == "bb"); Assert.AreEqual(24, bv.Value); } @@ -150,10 +150,10 @@ public void TestIterate1() { [Test] public void TestIterate2() { - var doc = new BSONDocument(); + var doc = new BsonDocument(); doc["a"] = "av"; - doc["b"] = BSONDocument.ValueOf(new{cc = 1}); - doc["d"] = new BSONOid("51b9f3af98195c4600000000"); + 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()); @@ -173,17 +173,17 @@ public void TestIterate2() { "07-64-00-" + "51-B9-F3-AF-98-19-5C-46-00-00-00-00-" + "00", doc.ToDebugDataString()); - BSONIterator it = new BSONIterator(doc); + BsonIterator it = new BsonIterator(doc); int c = 0; foreach (var bt in it) { if (c == 0) { - Assert.IsTrue(bt == BSONType.STRING); + Assert.IsTrue(bt == BsonType.STRING); } if (c == 1) { - Assert.IsTrue(bt == BSONType.OBJECT); + Assert.IsTrue(bt == BsonType.OBJECT); } if (c == 2) { - Assert.IsTrue(bt == BSONType.OID); + Assert.IsTrue(bt == BsonType.OID); } ++c; } @@ -197,7 +197,7 @@ public void TestIterate2() { Assert.IsTrue(thrown); c = 0; - it = new BSONIterator(doc); + it = new BsonIterator(doc); foreach (var bv in it.Values()) { if (c == 0) { Assert.AreEqual("a", it.CurrentKey); @@ -205,19 +205,19 @@ public void TestIterate2() { } if (c == 1) { Assert.AreEqual("b", it.CurrentKey); - BSONDocument sdoc = bv.Value as BSONDocument; + BsonDocument sdoc = bv.Value as BsonDocument; Assert.IsNotNull(sdoc); - var it2 = new BSONIterator(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); + 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(BsonType.OID, bv.BSONType); + Assert.IsInstanceOf(typeof(BsonOid), bv.Value); + var oid = bv.Value as BsonOid; Assert.AreEqual("51b9f3af98195c4600000000", oid.ToString()); } c++; @@ -226,10 +226,10 @@ public void TestIterate2() { [Test] public void TestIterateRE() { - var doc = new BSONDocument(); - doc["a"] = new BSONRegexp("b", "c"); + var doc = new BsonDocument(); + doc["a"] = new BsonRegexp("b", "c"); doc["d"] = 1; - doc["e"] = BSONDocument.ValueOf(new {f = new BSONRegexp("g", "")}); + doc["e"] = BsonDocument.ValueOf(new {f = new BsonRegexp("g", "")}); doc["h"] = 2; //28-00-00-00 //0B-61-00-62-00-63-00 @@ -238,48 +238,48 @@ public void TestIterateRE() { //0B-66-00-67-00-00-00 //10-68-00-02-00-00-00-00 var cs = ""; - foreach (var bt in new BSONIterator(doc)) { + foreach (var bt in new BsonIterator(doc)) { cs += bt.ToString(); } Assert.AreEqual("REGEXINTOBJECTINT", cs); cs = ""; - var it = new BSONIterator(doc); + 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; + 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); + Assert.AreEqual("bc1[BsonDocument: [BsonValue: BsonType=REGEX, Key=f, Value=[BsonRegexp: re=g, opts=]]]2", cs); } [Test] public void TestFilteredDoc() { - var doc = new BSONDocument(); + var doc = new BsonDocument(); doc["c"] = "d"; doc["aaa"] = 11; - doc["ndoc"] = BSONDocument.ValueOf(new { + doc["ndoc"] = BsonDocument.ValueOf(new { aaaa = "nv1", d = "nv2", - nnd = BSONDocument.ValueOf(new { + nnd = BsonDocument.ValueOf(new { nnv = true, nns = "s" }) }); - doc["ndoc2"] = BSONDocument.ValueOf(new { + 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"); + 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(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/TestEJDB.cs b/Ejdb.Tests/TestEJDB.cs index b49e3bd..43b975e 100644 --- a/Ejdb.Tests/TestEJDB.cs +++ b/Ejdb.Tests/TestEJDB.cs @@ -49,25 +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(); - doc.SetBSONValueNew("age", BSONValue.GetNumber(33)); - Assert.IsNull(doc["_id"]); + BsonDocument doc = new BsonDocument(); + doc.Add("age", BsonValue.GetNumber(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); @@ -83,22 +83,22 @@ public void Test3SaveLoad() { public void Test4Q1() { EJDB jb = new EJDB("testdb1", EJDB.DEFAULT_OPEN_MODE | EJDB.JBOTRUNC); Assert.IsTrue(jb.IsOpen); - var doc = new BSONDocument(); - doc.SetBSONValueNew("age", BSONValue.GetNumber(33)); + var doc = new BsonDocument(); + doc.Add("age", BsonValue.GetNumber(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"]); } @@ -125,40 +125,40 @@ 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"]); } @@ -180,13 +180,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); From 0a8032c23dc2b6990819b38cc4cd24383e150753 Mon Sep 17 00:00:00 2001 From: Oliver Klemencic Date: Fri, 27 Dec 2013 18:45:56 +0100 Subject: [PATCH 06/26] Added further query helpers --- Ejdb.DB/Query/Query.cs | 59 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/Ejdb.DB/Query/Query.cs b/Ejdb.DB/Query/Query.cs index 5e2bb58..94aff68 100644 --- a/Ejdb.DB/Query/Query.cs +++ b/Ejdb.DB/Query/Query.cs @@ -21,43 +21,86 @@ public static IQuery EQ(string fieldName, object value) return new Query(fieldName, value); } + public static IQuery EqualsIgnoreCase(string fieldName, string value) + { + return _BinaryQuery("$icase", fieldName, value); + } + + public static IQuery BeginsWith(string fieldName, string value) + { + return _BinaryQuery("$begin", fieldName, value); + } + + public static IQuery EndsWith(string fieldName, string value) + { + return _BinaryQuery("end", fieldName, value); + } + public static IQuery GT(string fieldName, object value) { - return BinaryQuery("$gt", fieldName, value); + return _BinaryQuery("$gt", fieldName, value); } public static IQuery GTE(string fieldName, object value) { - return BinaryQuery("$gte", fieldName, value); + return _BinaryQuery("$gte", fieldName, value); } public static IQuery LT(string fieldName, object value) { - return BinaryQuery("$lt", fieldName, value); + return _BinaryQuery("$lt", fieldName, value); } public static IQuery LTE(string fieldName, object value) { - return BinaryQuery("$lte", fieldName, value); + return _BinaryQuery("$lte", fieldName, value); } public static IQuery In(string fieldName, params T[] comparisonValues) { - return BinaryQuery("$in", fieldName, comparisonValues); + return _BinaryQuery("$in", fieldName, comparisonValues); + } + + public static IQuery NotIn(string fieldName, params T[] comparisonValues) + { + return _BinaryQuery("$nin", fieldName, comparisonValues); } public static IQuery Not(string fieldName, object comparisonValue) { - return BinaryQuery("$not", fieldName, comparisonValue); + return _BinaryQuery("$not", fieldName, comparisonValue); } + /* public static IQuery Not(string fieldName, IQuery query) + { + var childValue = query.GetQueryDocument(); + return new Query("$not", childValue); + } */ + public static IQuery Between(string fieldName, T comparisonValue1, T comparisonValue2) { var comparisonValues = new T[] { comparisonValue1, comparisonValue2 }; - return BinaryQuery("$bt", fieldName, comparisonValues); + return _BinaryQuery("$bt", fieldName, comparisonValues); + } + + public static IQuery And(params IQuery[] queries) + { + return _CombinedQuery("$and", queries); + } + + public static IQuery Or(params IQuery[] queries) + { + return _CombinedQuery("$or", queries); + } + + private static IQuery _CombinedQuery(string combinator, IQuery[] queries) + { + var documents = queries.Select(x => x.GetQueryDocument()).ToArray(); + var childValue = new BsonArray(documents); + return new Query(combinator, childValue); } - public static IQuery BinaryQuery(string queryOperation, string fieldName, object comparisonValue) + private static IQuery _BinaryQuery(string queryOperation, string fieldName, object comparisonValue) { var query1 = new BsonDocument(); query1[queryOperation] = comparisonValue; From c38f9fddc59b890acb662f51f6722a8bf6520c5a Mon Sep 17 00:00:00 2001 From: Oliver Klemencic Date: Fri, 27 Dec 2013 18:46:34 +0100 Subject: [PATCH 07/26] Simplified array handling --- Ejdb.BSON/BSONArray.cs | 224 ++--------------------------------------- Ejdb.BSON/BSONValue.cs | 26 +---- 2 files changed, 12 insertions(+), 238 deletions(-) diff --git a/Ejdb.BSON/BSONArray.cs b/Ejdb.BSON/BSONArray.cs index 8d27048..f593842 100644 --- a/Ejdb.BSON/BSONArray.cs +++ b/Ejdb.BSON/BSONArray.cs @@ -14,7 +14,6 @@ // Boston, MA 02111-1307 USA. // ============================================================================================ using System; -using System.IO; namespace Ejdb.BSON { @@ -33,138 +32,15 @@ public object this[int key] { } } - public BsonArray() { - } - - public BsonArray(BsonUndefined[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetUndefined(i); - } - } - - public BsonArray(BsonNull[] arr) { - for (var i = 0; i < arr.Length; ++i) { - SetNull(i); - } - } - - public BsonArray(ushort[] arr) - { - for (int i = 0; i < arr.Length; ++i) - SetNumber(i, arr[i]); - } - - public BsonArray(uint[] arr) - { - for (int i = 0; i < arr.Length; ++i) - SetNumber(i, arr[i]); - } - - public BsonArray(ulong[] arr) - { - for (int i = 0; i < arr.Length; ++i) - SetNumber(i, (long) arr[i]); - } - - public BsonArray(short[] arr) - { - for (int i = 0; i < arr.Length; ++i) - SetNumber(i, arr[i]); - } - - public BsonArray(string[] arr) - { - for (int i = 0; i < arr.Length; ++i) - SetString(i, arr[i]); - } - - public BsonArray(int[] arr) - { - for (int i = 0; i < arr.Length; ++i) - SetNumber(i, arr[i]); - } - - public BsonArray(long[] arr) - { - for (int i = 0; i < arr.Length; ++i) - SetNumber(i, arr[i]); - } - - public BsonArray(float[] arr) - { - for (int i = 0; i < arr.Length; ++i) - SetNumber(i, arr[i]); - } - - public BsonArray(double[] arr) - { - for (int i = 0; i < arr.Length; ++i) - SetNumber(i, arr[i]); - } - - public BsonArray(bool[] arr) - { - for (int i = 0; i < arr.Length; ++i) - SetBool(i, arr[i]); - } - - public BsonArray(BsonOid[] arr) - { - for (int i = 0; i < arr.Length; ++i) - SetOID(i, arr[i]); - } - - public BsonArray(DateTime[] arr) - { - for (int i = 0; i < arr.Length; ++i) - SetDate(i, arr[i]); - } - - public BsonArray(BsonDocument[] arr) - { - for (int i = 0; i < arr.Length; ++i) - SetObject(i, arr[i]); - } - - public BsonArray(BsonArray[] arr) - { - for (int i = 0; i < arr.Length; ++i) - SetArray(i, arr[i]); - } - - public BsonArray(BsonRegexp[] arr) - { - for (int i = 0; i < arr.Length; ++i) - SetRegexp(i, arr[i]); - } - - public BsonArray(BsonTimestamp[] arr) - { - for (int i = 0; i < arr.Length; ++i) - SetTimestamp(i, arr[i]); - } - - public BsonArray(BsonCodeWScope[] arr) - { - for (int i = 0; i < arr.Length; ++i) - SetCodeWScope(i, arr[i]); - } - - public BsonArray(BsonBinData[] arr) - { - for (int i = 0; i < arr.Length; ++i) - SetBinData(i, arr[i]); - } - - public void SetNull(int idx) - { - _SetValue(idx, BsonValue.GetNull()); + public BsonArray() + { } - public void SetUndefined(int idx) - { - _SetValue(idx, BsonValue.GetUndefined()); - } + public BsonArray(Array objects) + { + for (int i = 0; i < objects.Length; i++) + _SetValue(i, BsonValue.ValueOf(objects.GetValue(i))); + } public void SetMaxKey(int idx) { @@ -176,97 +52,11 @@ public void SetMinKey(int idx) _SetValue(idx, BsonValue.GetMinKey()); } - public void SetOID(int idx, string oid) - { - _SetValue(idx, BsonValue.GetOID(oid)); - } - - public void SetOID(int idx, BsonOid oid) - { - _SetValue(idx, BsonValue.GetOID(oid)); - } - - public void SetBool(int idx, bool val) - { - _SetValue(idx, BsonValue.GetBool(val)); - } - - public void SetNumber(int idx, int val) - { - _SetValue(idx, BsonValue.GetNumber(val)); - } - - public void SetNumber(int idx, long val) - { - _SetValue(idx, BsonValue.GetNumber(val)); - } - - public void SetNumber(int idx, double val) - { - _SetValue(idx, BsonValue.GetNumber(val)); - } - - public void SetNumber(int idx, float val) - { - _SetValue(idx, BsonValue.GetNumber(val)); - } - - public void SetString(int idx, string val) - { - _SetValue(idx, BsonValue.GetString(val)); - } - - public void SetCode(int idx, string val) - { - _SetValue(idx, BsonValue.GetCode(val)); - } - - public void SetSymbol(int idx, string val) - { - _SetValue(idx, BsonValue.GetSymbol(val)); - } - - public void SetDate(int idx, DateTime val) - { - _SetValue(idx, BsonValue.GetDate(val)); - } - - public void SetRegexp(int idx, BsonRegexp val) - { - _SetValue(idx, BsonValue.GetRegexp(val)); - } - - public void SetBinData(int idx, BsonBinData val) - { - _SetValue(idx, BsonValue.GetBinData(val)); - } - - public void SetObject(int idx, BsonDocument val) - { - _SetValue(idx, BsonValue.GetDocument(val)); - } - - public void SetArray(int idx, BsonArray val) - { - _SetValue(idx, BsonValue.GetArray(val)); - } - - public void SetTimestamp(int idx, BsonTimestamp val) - { - _SetValue(idx, BsonValue.GetTimestamp(val)); - } - - public void SetCodeWScope(int idx, BsonCodeWScope val) - { - _SetValue(idx, BsonValue.GetCodeWScope(val)); - } - private void _SetValue(int idx, BsonValue val) { Add(idx.ToString(), val); } - protected override void CheckKey(string key) { int idx; diff --git a/Ejdb.BSON/BSONValue.cs b/Ejdb.BSON/BSONValue.cs index 981e626..8b23b12 100644 --- a/Ejdb.BSON/BSONValue.cs +++ b/Ejdb.BSON/BSONValue.cs @@ -103,6 +103,10 @@ public static BsonValue ValueOf(object v) if (v == null) return GetNull(); + var arrayValue = v as Array; + if (arrayValue != null) + return GetArray(new BsonArray(arrayValue)); + Func setter; var vtype = v.GetType(); TYPE_SETTERS.TryGetValue(vtype, out setter); @@ -237,52 +241,32 @@ public static BsonValue GetCodeWScope(BsonCodeWScope val) return new BsonValue(BsonType.CODEWSCOPE, val); } - public static Dictionary> TYPE_SETTERS = new Dictionary> + private static Dictionary> TYPE_SETTERS = new Dictionary> { {typeof(bool), v => GetBool((bool) v)}, - {typeof(bool[]), v => GetArray(new BsonArray((bool[]) v))}, {typeof(byte), v => GetNumber((int) v)}, {typeof(sbyte), v => GetNumber((int) v)}, {typeof(ushort), v => GetNumber((int) v)}, - {typeof(ushort[]), v => GetArray(new BsonArray((ushort[]) v))}, {typeof(short), v => GetNumber((int) v)}, - {typeof(short[]), v => GetArray(new BsonArray((short[]) v))}, {typeof(uint), v => GetNumber((int) v)}, - {typeof(uint[]), v => GetArray(new BsonArray((uint[]) v))}, {typeof(int), v => GetNumber((int) v)}, - {typeof(int[]), v => GetArray(new BsonArray((int[]) v))}, {typeof(ulong), v => GetNumber((long) v)}, - {typeof(ulong[]), v => GetArray(new BsonArray((ulong[]) v))}, {typeof(long), v => GetNumber((long) v)}, - {typeof(long[]), v => GetArray(new BsonArray((long[]) v))}, {typeof(float), v => GetNumber((float) v)}, - {typeof(float[]), v => GetArray(new BsonArray((float[]) v))}, {typeof(double), v => GetNumber((double) v)}, - {typeof(double[]), v => GetArray(new BsonArray((double[]) v))}, {typeof(char), v => GetString(v.ToString())}, {typeof(string), v => GetString((string) v)}, - {typeof(string[]), v => GetArray(new BsonArray((string[]) v))}, {typeof(BsonOid), v => GetOID((BsonOid) v)}, - {typeof(BsonOid[]), v => GetArray(new BsonArray((BsonOid[]) v))}, {typeof(BsonRegexp), v => GetRegexp((BsonRegexp) v)}, - {typeof(BsonRegexp[]), v => GetArray(new BsonArray((BsonRegexp[]) v))}, {typeof(BsonValue), v => (BsonValue) v}, {typeof(BsonTimestamp), v => GetTimestamp((BsonTimestamp) v)}, - {typeof(BsonTimestamp[]), v => GetArray(new BsonArray((BsonTimestamp[]) v))}, {typeof(BsonCodeWScope), v => GetCodeWScope((BsonCodeWScope) v)}, - {typeof(BsonCodeWScope[]), v => GetArray(new BsonArray((BsonCodeWScope[]) v))}, {typeof(BsonBinData), v => GetBinData((BsonBinData) v)}, - {typeof(BsonBinData[]), v => GetArray(new BsonArray((BsonBinData[]) v))}, {typeof(BsonDocument), v => GetDocument((BsonDocument) v)}, - {typeof(BsonDocument[]), v => GetArray(new BsonArray((BsonDocument[]) v))}, {typeof(BsonArray), v => GetArray((BsonArray) v)}, - {typeof(BsonArray[]), v => GetArray(new BsonArray((BsonArray[]) v))}, {typeof(DateTime), v => GetDate((DateTime) v)}, - {typeof(DateTime[]), v => GetArray(new BsonArray((DateTime[]) v))}, {typeof(BsonUndefined), v => GetUndefined()}, - {typeof(BsonUndefined[]), v => GetArray(new BsonArray((BsonUndefined[]) v))}, {typeof(BsonNull), v => GetNull() }, - {typeof(BsonNull[]), v => GetArray(new BsonArray((BsonNull[]) v))} }; } } From 2d728cb587f9c58cff6269e56c3b12579939b7db Mon Sep 17 00:00:00 2001 From: Oliver Klemencic Date: Fri, 27 Dec 2013 18:53:08 +0100 Subject: [PATCH 08/26] Further work on serialization (adaption so it's similar to MongoDb) --- Ejdb.BSON/BSONArray.cs | 96 ++++++--- Ejdb.BSON/BSONDocument.cs | 114 +++++----- Ejdb.BSON/BSONValue.cs | 75 +++---- Ejdb.BSON/BSONull.cs | 53 ----- Ejdb.DB/EJDB.cs | 4 +- Ejdb.DB/EJDBQuery.cs | 2 +- Ejdb.DB/Query/Query.cs | 10 + Ejdb.IO/ExtBinaryWriter.cs | 7 +- Ejdb.Tests/StreamWithoutSeek.cs | 12 ++ Ejdb.Tests/TestBsonArray.cs | 134 ++++++++++++ .../{TestBSON.cs => TestBsonSerialization.cs} | 201 +++++++++++++----- Ejdb.Tests/TestEJDB.cs | 7 +- 12 files changed, 481 insertions(+), 234 deletions(-) delete mode 100644 Ejdb.BSON/BSONull.cs create mode 100644 Ejdb.Tests/StreamWithoutSeek.cs create mode 100644 Ejdb.Tests/TestBsonArray.cs rename Ejdb.Tests/{TestBSON.cs => TestBsonSerialization.cs} (61%) diff --git a/Ejdb.BSON/BSONArray.cs b/Ejdb.BSON/BSONArray.cs index f593842..fa30ffb 100644 --- a/Ejdb.BSON/BSONArray.cs +++ b/Ejdb.BSON/BSONArray.cs @@ -14,56 +14,104 @@ // Boston, MA 02111-1307 USA. // ============================================================================================ using System; +using System.Collections; +using System.Globalization; namespace Ejdb.BSON { [Serializable] public class BsonArray : BsonDocument { - public override BsonType BSONType { - get { - return BsonType.ARRAY; - } + + public BsonArray() + { } - public object this[int key] { - get { - return GetObjectValue(key.ToString()); - } - } + public BsonArray(BsonDocument document) + { + var iterator = new BsonIterator(document); + while (iterator.Next() != BsonType.EOO) + Add(iterator.CurrentKey, iterator.FetchCurrentValue()); + } - public BsonArray() + public BsonArray(IEnumerable objects) { - } + AddRange(objects); + } + + public override BsonType BSONType + { + get + { + return BsonType.ARRAY; + } + } - public BsonArray(Array objects) + public object this[int key] { - for (int i = 0; i < objects.Length; i++) - _SetValue(i, BsonValue.ValueOf(objects.GetValue(i))); + get + { + return GetObjectValue(key.ToString()); + } } public void SetMaxKey(int idx) { - _SetValue(idx, BsonValue.GetMaxKey()); + Add(BsonValue.GetMaxKey()); } public void SetMinKey(int idx) { - _SetValue(idx, BsonValue.GetMinKey()); + Add(BsonValue.GetMinKey()); } - private void _SetValue(int idx, BsonValue val) + /// + /// 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) { - Add(idx.ToString(), val); + if (values != null) + { + foreach (var value in values) + Add(BsonValue.ValueOf(value)); + } + + return this; } - protected override void CheckKey(string key) + /// + /// 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) { - int idx; - if (key == null || !int.TryParse(key, out idx) || idx < 0) { - throw new InvalidBSONDataException(string.Format("Invalid array key: {0}", key)); - } - } + 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/BSONDocument.cs b/Ejdb.BSON/BSONDocument.cs index a2029ed..7069a7e 100644 --- a/Ejdb.BSON/BSONDocument.cs +++ b/Ejdb.BSON/BSONDocument.cs @@ -129,27 +129,32 @@ 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.CurrentKey, 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.CurrentKey, it.FetchCurrentValue()); - } - } + public BsonDocument(Stream bstream) + : this() + { + using (var iterator = new BsonIterator(bstream)) + { + while (iterator.Next() != BsonType.EOO) + Add(iterator.CurrentKey, iterator.FetchCurrentValue()); + } } - public BsonDocument(BsonDocument doc) : this() { - foreach (var bv in doc._fields) { - Add(bv.Key, (BsonValue) bv.Value.Clone()); - } - } + public BsonDocument(BsonDocument doc) + : this() + { + foreach (var bv in doc._fields) + Add(bv.Key, (BsonValue) bv.Value.Clone()); + } public IEnumerator GetEnumerator() { @@ -165,7 +170,8 @@ 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()) { @@ -175,7 +181,8 @@ public byte[] ToByteArray() { return res; } - public string ToDebugDataString() { + public string ToDebugDataString() + { return BitConverter.ToString(ToByteArray()); } @@ -264,44 +271,40 @@ public void Clear() { _fields.Clear(); } - public void Serialize(Stream os) { - if (os.CanSeek) + public void Serialize(Stream stream) + { + if (stream.CanSeek) { - long start = os.Position; - os.Position += 4; //skip int32 document size - using (var bw = new ExtBinaryWriter(os, Encoding.UTF8, true)) - { - foreach (var bv in _fields) - WriteBsonValue(bv.Key, bv.Value, 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 + _WriteToSeekableStream(stream); + } + else { - byte[] darr; - var ms = new MemoryStream(); - using (var bw = new ExtBinaryWriter(ms)) - { - foreach (var bv in _fields) - WriteBsonValue(bv.Key, bv.Value, 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(); + var ms = new MemoryStream(); + _WriteToSeekableStream(ms); + byte[] bytes = ms.ToArray(); + stream.Write(bytes, 0, bytes.Length); + } + stream.Flush(); } - public override bool Equals(object obj) { + private void _WriteToSeekableStream(Stream stream) + { + long start = stream.Position; + stream.Position += 4; //skip int32 document size + using (var bw = new ExtBinaryWriter(stream, Encoding.UTF8, 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; } @@ -393,10 +396,7 @@ public BsonDocument Add(string key, BsonValue val) return this; } - protected virtual void CheckKey(string key) { - } - - protected void WriteBsonValue(string key, BsonValue bv, ExtBinaryWriter bw) + protected void WriteBsonValue(string key, BsonValue bv, ExtBinaryWriter bw) { BsonType bt = bv.BSONType; switch (bt) diff --git a/Ejdb.BSON/BSONValue.cs b/Ejdb.BSON/BSONValue.cs index 8b23b12..51cd7ca 100644 --- a/Ejdb.BSON/BSONValue.cs +++ b/Ejdb.BSON/BSONValue.cs @@ -23,7 +23,8 @@ namespace Ejdb.BSON { /// BSON field value. /// [Serializable] - public sealed class BsonValue : IBsonValue, ICloneable { + public sealed class BsonValue : IBsonValue, ICloneable + { /// /// BSON.Type @@ -105,7 +106,7 @@ public static BsonValue ValueOf(object v) var arrayValue = v as Array; if (arrayValue != null) - return GetArray(new BsonArray(arrayValue)); + return Create(new BsonArray(arrayValue)); Func setter; var vtype = v.GetType(); @@ -133,7 +134,7 @@ public static BsonDocument GetAnonTypeDocument(object val) public static BsonValue GetAnonTypeValue(object val) { - return GetDocument(GetAnonTypeDocument(val)); + return Create(GetAnonTypeDocument(val)); } public static BsonValue GetNull() @@ -156,57 +157,57 @@ public static BsonValue GetMinKey() return new BsonValue(BsonType.MINKEY); } - public static BsonValue GetOID(string oid) + public static BsonValue CreateOid(string oid) { return new BsonValue(BsonType.OID, new BsonOid(oid)); } - public static BsonValue GetOID(BsonOid oid) + public static BsonValue Create(BsonOid oid) { return new BsonValue(BsonType.OID, oid); } - public static BsonValue GetBool(bool val) + public static BsonValue Create(bool val) { return new BsonValue(BsonType.BOOL, val); } - public static BsonValue GetNumber(int val) + public static BsonValue Create(int val) { return new BsonValue(BsonType.INT, val); } - public static BsonValue GetNumber(long val) + public static BsonValue Create(long val) { return new BsonValue(BsonType.LONG, val); } - public static BsonValue GetNumber(double val) + public static BsonValue Create(double val) { return new BsonValue(BsonType.DOUBLE, val); } - public static BsonValue GetNumber(float val) + public static BsonValue Create(float val) { return new BsonValue(BsonType.DOUBLE, val); } - public static BsonValue GetString(string val) + public static BsonValue Create(string val) { return new BsonValue(BsonType.STRING, val); } - public static BsonValue GetCode(string val) + public static BsonValue CreateCode(string val) { return new BsonValue(BsonType.CODE, val); } - public static BsonValue GetSymbol(string val) + public static BsonValue CreateSymbol(string val) { return new BsonValue(BsonType.SYMBOL, val); } - public static BsonValue GetDate(DateTime val) + public static BsonValue Create(DateTime val) { return new BsonValue(BsonType.DATE, val); } @@ -221,50 +222,50 @@ public static BsonValue GetBinData(BsonBinData val) return new BsonValue(BsonType.BINDATA, val); } - public static BsonValue GetDocument(BsonDocument val) + public static BsonValue Create(BsonDocument val) { return new BsonValue(BsonType.OBJECT, val); } - public static BsonValue GetArray(BsonArray val) + public static BsonValue Create(BsonArray val) { return new BsonValue(BsonType.ARRAY, val); } - public static BsonValue GetTimestamp(BsonTimestamp val) + public static BsonValue Create(BsonTimestamp val) { return new BsonValue(BsonType.TIMESTAMP, val); } - public static BsonValue GetCodeWScope(BsonCodeWScope val) + public static BsonValue Create(BsonCodeWScope val) { return new BsonValue(BsonType.CODEWSCOPE, val); } private static Dictionary> TYPE_SETTERS = new Dictionary> { - {typeof(bool), v => GetBool((bool) v)}, - {typeof(byte), v => GetNumber((int) v)}, - {typeof(sbyte), v => GetNumber((int) v)}, - {typeof(ushort), v => GetNumber((int) v)}, - {typeof(short), v => GetNumber((int) v)}, - {typeof(uint), v => GetNumber((int) v)}, - {typeof(int), v => GetNumber((int) v)}, - {typeof(ulong), v => GetNumber((long) v)}, - {typeof(long), v => GetNumber((long) v)}, - {typeof(float), v => GetNumber((float) v)}, - {typeof(double), v => GetNumber((double) v)}, - {typeof(char), v => GetString(v.ToString())}, - {typeof(string), v => GetString((string) v)}, - {typeof(BsonOid), v => GetOID((BsonOid) v)}, + {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 => GetTimestamp((BsonTimestamp) v)}, - {typeof(BsonCodeWScope), v => GetCodeWScope((BsonCodeWScope) v)}, + {typeof(BsonTimestamp), v => Create((BsonTimestamp) v)}, + {typeof(BsonCodeWScope), v => Create((BsonCodeWScope) v)}, {typeof(BsonBinData), v => GetBinData((BsonBinData) v)}, - {typeof(BsonDocument), v => GetDocument((BsonDocument) v)}, - {typeof(BsonArray), v => GetArray((BsonArray) v)}, - {typeof(DateTime), v => GetDate((DateTime) 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/BSONull.cs b/Ejdb.BSON/BSONull.cs deleted file mode 100644 index 2e74028..0000000 --- a/Ejdb.BSON/BSONull.cs +++ /dev/null @@ -1,53 +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; - -namespace Ejdb.BSON { - - [Serializable] - public sealed class BSONull : IBSONValue { - - public static BSONull VALUE = new BSONull(); - - public BSONType BSONType { - get { - return BSONType.NULL; - } - } - - public override bool Equals(object obj) { - if (obj == null) { - return false; - } - if (ReferenceEquals(this, obj)) { - return true; - } - if (!(obj is BSONull)) { - return false; - } - return true; - } - - public override int GetHashCode() { - return 0; - } - - public override string ToString() { - return "[BSONull]"; - } - } -} - diff --git a/Ejdb.DB/EJDB.cs b/Ejdb.DB/EJDB.cs index 26ec716..14ff976 100644 --- a/Ejdb.DB/EJDB.cs +++ b/Ejdb.DB/EJDB.cs @@ -54,7 +54,7 @@ public class EJDB : IDisposable { public const int JBOWRITER = 1 << 1; /// - /// Create if db file not exists. + /// CreateOid if db file not exists. /// public const int JBOCREAT = 1 << 2; @@ -947,7 +947,7 @@ bool Save(IntPtr cptr, BsonDocument doc, bool merge) { //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.GetOID(new BsonOid(oiddata))); + doc.Add(BsonConstants.Id, BsonValue.Create(new BsonOid(oiddata))); } if (_throwonfail && !rv) { throw new EJDBException(this); diff --git a/Ejdb.DB/EJDBQuery.cs b/Ejdb.DB/EJDBQuery.cs index 232de7b..1da1717 100644 --- a/Ejdb.DB/EJDBQuery.cs +++ b/Ejdb.DB/EJDBQuery.cs @@ -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 { diff --git a/Ejdb.DB/Query/Query.cs b/Ejdb.DB/Query/Query.cs index 94aff68..968abf5 100644 --- a/Ejdb.DB/Query/Query.cs +++ b/Ejdb.DB/Query/Query.cs @@ -93,6 +93,16 @@ public static IQuery Or(params IQuery[] queries) return _CombinedQuery("$or", queries); } + public static IQuery Exists(string name) + { + return null; + } + + public static IQuery ElemMatch(string name, BsonDocument queryDocument) + { + return null; + } + private static IQuery _CombinedQuery(string combinator, IQuery[] queries) { var documents = queries.Select(x => x.GetQueryDocument()).ToArray(); diff --git a/Ejdb.IO/ExtBinaryWriter.cs b/Ejdb.IO/ExtBinaryWriter.cs index bf158ca..2096eaa 100644 --- a/Ejdb.IO/ExtBinaryWriter.cs +++ b/Ejdb.IO/ExtBinaryWriter.cs @@ -32,7 +32,9 @@ public ExtBinaryWriter() { 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 +45,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); } diff --git a/Ejdb.Tests/StreamWithoutSeek.cs b/Ejdb.Tests/StreamWithoutSeek.cs new file mode 100644 index 0000000..2dc0d34 --- /dev/null +++ b/Ejdb.Tests/StreamWithoutSeek.cs @@ -0,0 +1,12 @@ +using System.IO; + +namespace Ejdb.Tests +{ + public class StreamWithoutSeek : MemoryStream + { + public override bool CanSeek + { + get { return false; } + } + } +} \ No newline at end of file diff --git a/Ejdb.Tests/TestBsonArray.cs b/Ejdb.Tests/TestBsonArray.cs new file mode 100644 index 0000000..a31213f --- /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 = new BsonOid(Guid.NewGuid().ToByteArray()); + var value2 = new BsonOid(Guid.NewGuid().ToByteArray()); + 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/TestBSON.cs b/Ejdb.Tests/TestBsonSerialization.cs similarity index 61% rename from Ejdb.Tests/TestBSON.cs rename to Ejdb.Tests/TestBsonSerialization.cs index f8ba333..f3f32d5 100644 --- a/Ejdb.Tests/TestBSON.cs +++ b/Ejdb.Tests/TestBsonSerialization.cs @@ -14,6 +14,7 @@ // Boston, MA 02111-1307 USA. // ============================================================================================ using System; +using System.Collections.Generic; using NUnit.Framework; using Ejdb.BSON; using System.IO; @@ -21,19 +22,34 @@ namespace Ejdb.Tests { [TestFixture] - public class TestBSON { + public class TestBsonSerialization { [Test] - public void TestSerializeEmpty() { - BsonDocument doc = new BsonDocument(); + 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 StreamWithoutSeek()) + { + var doc = new BsonDocument(); + doc.Serialize(stream); + + var bytes = BitConverter.ToString(stream.ToArray()); + Assert.AreEqual("05-00-00-00-00", bytes); + } + } + [Test] - public void TestSerialize1() { + public void SerializeNumber() + { byte[] bdata; BsonDocument doc = new BsonDocument(); - doc.Add("0", BsonValue.GetNumber(1)); + doc.Add("0", BsonValue.Create(1)); //0C-00-00-00 len //10 type //30-00 key @@ -41,58 +57,109 @@ public void TestSerialize1() { //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 (var 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.Add("0", BsonValue.GetNumber(2)); - Assert.AreEqual(1, doc2.KeysCount); - object ival = doc2["0"]; - Assert.IsInstanceOf(typeof(int), ival); - Assert.AreEqual(2, ival); - doc2.Add("1", BsonValue.GetNumber(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()); + 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)); + + //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 + const string expectedByteArray = "13-00-00-00-10-30-00-02-00-00-00-10-31-00-FF-FF-FF-7F-00"; + + Assert.AreEqual(expectedByteArray, doc2.ToDebugDataString()); + + doc2 = new BsonDocument(doc2); + Assert.AreEqual(expectedByteArray, doc2.ToDebugDataString()); + + doc2 = new BsonDocument(doc2.ToByteArray()); + Assert.AreEqual(expectedByteArray, 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 TestAnonTypes() { + 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()); + + //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 + + + //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("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 @@ -107,7 +174,8 @@ public void TestAnonTypes() { } [Test] - public void TestIterate1() { + public void TestIterate1() + { var doc = new BsonDocument(); doc["a"] = "av"; doc["bb"] = 24; @@ -283,8 +351,31 @@ public void TestFilteredDoc() { 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 __hexDigits = "0123456789abcdef"; + } } diff --git a/Ejdb.Tests/TestEJDB.cs b/Ejdb.Tests/TestEJDB.cs index 43b975e..01ed407 100644 --- a/Ejdb.Tests/TestEJDB.cs +++ b/Ejdb.Tests/TestEJDB.cs @@ -50,7 +50,7 @@ public void Test3SaveLoad() { EJDB jb = new EJDB("testdb1", EJDB.DEFAULT_OPEN_MODE | EJDB.JBOTRUNC); Assert.IsTrue(jb.IsOpen); BsonDocument doc = new BsonDocument(); - doc.Add("age", BsonValue.GetNumber(33)); + doc.Add("age", BsonValue.Create(33)); Assert.IsNull(doc[BsonConstants.Id]); bool rv = jb.Save("mycoll", doc); Assert.IsTrue(rv); @@ -84,7 +84,7 @@ public void Test4Q1() { EJDB jb = new EJDB("testdb1", EJDB.DEFAULT_OPEN_MODE | EJDB.JBOTRUNC); Assert.IsTrue(jb.IsOpen); var doc = new BsonDocument(); - doc.Add("age", BsonValue.GetNumber(33)); + doc.Add("age", BsonValue.Create(33)); Assert.IsNull(doc["_id"]); bool rv = jb.Save("mycoll", doc); Assert.IsTrue(rv); @@ -121,7 +121,8 @@ 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); From 15dd92709272ed4f87630f63dfc0d25488897187 Mon Sep 17 00:00:00 2001 From: Oliver Klemencic Date: Fri, 27 Dec 2013 18:57:30 +0100 Subject: [PATCH 09/26] Added further tests, refactorings --- Ejdb.BSON/BSONDocument.cs | 43 +++++-- Ejdb.BSON/BSONIterator.cs | 28 +++-- Ejdb.BSON/BsonSerializationException.cs | 27 +++++ Ejdb.IO/ExtBinaryReader.cs | 32 +++--- Ejdb.IO/ExtBinaryWriter.cs | 23 ++-- Ejdb.Tests/MockStream.cs | 19 ++++ Ejdb.Tests/TestBsonSerialization.cs | 132 ++++++++++++--------- Ejdb.Tests/TestBsonStringSerialization.cs | 133 ++++++++++++++++++++++ nejdb.csproj | 9 +- 9 files changed, 354 insertions(+), 92 deletions(-) create mode 100644 Ejdb.BSON/BsonSerializationException.cs create mode 100644 Ejdb.Tests/MockStream.cs create mode 100644 Ejdb.Tests/TestBsonStringSerialization.cs diff --git a/Ejdb.BSON/BSONDocument.cs b/Ejdb.BSON/BSONDocument.cs index 7069a7e..e62b065 100644 --- a/Ejdb.BSON/BSONDocument.cs +++ b/Ejdb.BSON/BSONDocument.cs @@ -72,7 +72,9 @@ public BsonDocument() { _fields = new Dictionary(); } - public BsonDocument(BsonIterator it) : this() { + public BsonDocument(BsonIterator it) + : this() + { while (it.Next() != BsonType.EOO) { var value = it.FetchCurrentValue(); @@ -80,6 +82,16 @@ public BsonDocument(BsonIterator it) : 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; @@ -291,7 +303,7 @@ private void _WriteToSeekableStream(Stream stream) { long start = stream.Position; stream.Position += 4; //skip int32 document size - using (var bw = new ExtBinaryWriter(stream, Encoding.UTF8, true)) + using (var bw = new ExtBinaryWriter(stream, true)) { foreach (var bv in _fields) WriteBsonValue(bv.Key, bv.Value, bw); @@ -314,17 +326,19 @@ public override bool Equals(object obj) { 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 (var bv1 in d1._fields) { - BsonValue bv2 = d2.GetBsonValue(bv1.Key); - if (bv1.Value != bv2) { + + foreach (var bv1 in d1._fields) + { + var bv2 = d2.GetBsonValue(bv1.Key); + if (bv1.Value != bv2) return false; - } } + return true; } @@ -377,6 +391,8 @@ public override string ToString() { // Private staff //.////////////////////////////////////////////////////////////////// + + public BsonDocument Add(string key, BsonValue val) { _cachedhash = null; @@ -396,6 +412,11 @@ public BsonDocument Add(string key, BsonValue val) return this; } + 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; @@ -446,7 +467,7 @@ protected void WriteBsonValue(string key, BsonValue bv, ExtBinaryWriter bw) case BsonType.DATE: { DateTime dt = (DateTime)bv.Value; - var diff = dt.ToLocalTime() - BsonConstants.Epoch; + var diff = dt.LocalTime() - BsonConstants.Epoch; long time = (long)Math.Floor(diff.TotalMilliseconds); WriteTypeAndKey(key, bv, bw); bw.Write(time); diff --git a/Ejdb.BSON/BSONIterator.cs b/Ejdb.BSON/BSONIterator.cs index 8b72dfb..605987a 100644 --- a/Ejdb.BSON/BSONIterator.cs +++ b/Ejdb.BSON/BSONIterator.cs @@ -22,7 +22,10 @@ namespace Ejdb.BSON { - public sealed class BsonIterator : IDisposable, IEnumerable { + public sealed class BsonIterator : IDisposable, IEnumerable + { + private static readonly Encoding STRICT_ENCODING = new UTF8Encoding(false, true); + ExtBinaryReader _input; bool _closeOnDispose = true; @@ -252,11 +255,19 @@ public BsonValue FetchCurrentValue() { case BsonType.CODE: case BsonType.SYMBOL: { - Debug.Assert(_entryLen - 1 >= 0); - string sv = Encoding.UTF8.GetString(_input.ReadBytes(_entryLen - 1)); + 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 rb = _input.ReadByte(); - Debug.Assert(rb == 0x00); //trailing zero byte + var finalByte = _input.ReadByte(); + + if (finalByte != 0x00) + throw new BsonSerializationException("String is missing null terminator."); + break; } case BsonType.BOOL: @@ -284,8 +295,9 @@ public BsonValue FetchCurrentValue() { _entryDataValue = new BsonValue(_ctype, _input.ReadInt64()); break; case BsonType.DATE: - _entryDataValue = new BsonValue(_ctype, - BsonConstants.Epoch.AddMilliseconds(_input.ReadInt64())); + var milliseconds = _input.ReadInt64(); + var dateTime = BsonConstants.Epoch.AddMilliseconds(milliseconds); + _entryDataValue = new BsonValue(_ctype, dateTime); break; case BsonType.TIMESTAMP: { @@ -322,7 +334,7 @@ public BsonValue FetchCurrentValue() { int cwlen = _entryLen + 4; Debug.Assert(cwlen > 5); int clen = _input.ReadInt32(); //code length - string code = Encoding.UTF8.GetString(_input.ReadBytes(clen)); + 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) 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.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 2096eaa..bd9a385 100644 --- a/Ejdb.IO/ExtBinaryWriter.cs +++ b/Ejdb.IO/ExtBinaryWriter.cs @@ -21,15 +21,17 @@ 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) @@ -57,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/TestBsonSerialization.cs b/Ejdb.Tests/TestBsonSerialization.cs index f3f32d5..e7a54dc 100644 --- a/Ejdb.Tests/TestBsonSerialization.cs +++ b/Ejdb.Tests/TestBsonSerialization.cs @@ -17,7 +17,6 @@ using System.Collections.Generic; using NUnit.Framework; using Ejdb.BSON; -using System.IO; namespace Ejdb.Tests { @@ -34,7 +33,7 @@ public void SerializeEmpty() [Test] public void SerializeEmpty_WithoutSeek() { - using (var stream = new StreamWithoutSeek()) + using (var stream = new MockStream(canSeek: false)) { var doc = new BsonDocument(); doc.Serialize(stream); @@ -76,23 +75,23 @@ public void CreateBsonDocument() doc2.Add("1", BsonValue.Create(Int32.MaxValue)); - //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 - const string expectedByteArray = "13-00-00-00-10-30-00-02-00-00-00-10-31-00-FF-FF-FF-7F-00"; + 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(expectedByteArray, doc2.ToDebugDataString()); + Assert.AreEqual(expected, doc2.ToDebugDataString()); doc2 = new BsonDocument(doc2); - Assert.AreEqual(expectedByteArray, doc2.ToDebugDataString()); + Assert.AreEqual(expected, doc2.ToDebugDataString()); doc2 = new BsonDocument(doc2.ToByteArray()); - Assert.AreEqual(expectedByteArray, doc2.ToDebugDataString()); + Assert.AreEqual(expected, doc2.ToDebugDataString()); } [Test] @@ -132,46 +131,70 @@ public void SerializeArrayWithVariousTypes() 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} + }; - [Test] - public void TestAnonTypes() + var rehydrated = new BsonDocument(document.ToByteArray()); + var dateTime2 = (DateTime) rehydrated["date"]; + Assert.AreEqual(dateTime.ToUniversalTime(), dateTime2.ToUniversalTime()); + } + + [Test] + public void SerializeAnonmyousType() { - BsonDocument doc = BsonDocument.ValueOf(new {a = "b", c = 1}); - - //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 - - - //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("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()); + 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 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() @@ -179,6 +202,7 @@ 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"); @@ -374,8 +398,14 @@ private byte[] DecodeByteString(string byteString) return bytes.ToArray(); } - private static string __hexDigits = "0123456789abcdef"; + 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/nejdb.csproj b/nejdb.csproj index abe7d7a..80f2402 100644 --- a/nejdb.csproj +++ b/nejdb.csproj @@ -60,12 +60,17 @@ + + - + + + + @@ -86,7 +91,7 @@ - + True From b0c9928e5373c38e5a2d5d25cff03c775e2000e2 Mon Sep 17 00:00:00 2001 From: Oliver Klemencic Date: Fri, 27 Dec 2013 18:57:55 +0100 Subject: [PATCH 10/26] Fixed date/time serialization --- Ejdb.BSON/BSONDocument.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ejdb.BSON/BSONDocument.cs b/Ejdb.BSON/BSONDocument.cs index e62b065..5b3895c 100644 --- a/Ejdb.BSON/BSONDocument.cs +++ b/Ejdb.BSON/BSONDocument.cs @@ -467,7 +467,7 @@ protected void WriteBsonValue(string key, BsonValue bv, ExtBinaryWriter bw) case BsonType.DATE: { DateTime dt = (DateTime)bv.Value; - var diff = dt.LocalTime() - BsonConstants.Epoch; + var diff = dt.ToUniversalTime() - BsonConstants.Epoch; long time = (long)Math.Floor(diff.TotalMilliseconds); WriteTypeAndKey(key, bv, bw); bw.Write(time); From 2fd4246e3c1db12ea8bbcf6591aed8d88db33927 Mon Sep 17 00:00:00 2001 From: Oliver Klemencic Date: Fri, 27 Dec 2013 18:59:13 +0100 Subject: [PATCH 11/26] Added suport for ID generation on the C# side --- Ejdb.BSON/BSONConstants.cs | 21 + Ejdb.BSON/BSONDocument.cs | 5 +- Ejdb.BSON/BSONIterator.cs | 3 +- Ejdb.BSON/BSONOid.cs | 727 ++++++++++++++++++++++------ Ejdb.BSON/BSONValue.cs | 7 +- Ejdb.DB/EJDB.cs | 7 +- Ejdb.Tests/StreamWithoutSeek.cs | 12 - Ejdb.Tests/TestBsonArray.cs | 4 +- Ejdb.Tests/TestBsonOid.cs | 338 +++++++++++++ Ejdb.Tests/TestBsonSerialization.cs | 10 +- Ejdb.Tests/TestEJDB.cs | 6 +- nejdb.csproj | 3 +- 12 files changed, 966 insertions(+), 177 deletions(-) delete mode 100644 Ejdb.Tests/StreamWithoutSeek.cs create mode 100644 Ejdb.Tests/TestBsonOid.cs diff --git a/Ejdb.BSON/BSONConstants.cs b/Ejdb.BSON/BSONConstants.cs index 010fd4e..38e9ed6 100644 --- a/Ejdb.BSON/BSONConstants.cs +++ b/Ejdb.BSON/BSONConstants.cs @@ -36,6 +36,27 @@ static BsonConstants() /// 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 5b3895c..7b0e3b4 100644 --- a/Ejdb.BSON/BSONDocument.cs +++ b/Ejdb.BSON/BSONDocument.cs @@ -433,9 +433,8 @@ protected void WriteBsonValue(string key, BsonValue bv, ExtBinaryWriter bw) case BsonType.OID: { WriteTypeAndKey(key, bv, bw); - BsonOid oid = (BsonOid)bv.Value; - Debug.Assert(oid._bytes.Length == 12); - bw.Write(oid._bytes); + var oid = (BsonOid)bv.Value; + bw.Write(oid.ToByteArray()); break; } case BsonType.STRING: diff --git a/Ejdb.BSON/BSONIterator.cs b/Ejdb.BSON/BSONIterator.cs index 605987a..f2e00c4 100644 --- a/Ejdb.BSON/BSONIterator.cs +++ b/Ejdb.BSON/BSONIterator.cs @@ -249,7 +249,8 @@ public BsonValue FetchCurrentValue() { break; case BsonType.OID: Debug.Assert(_entryLen == 12); - _entryDataValue = new BsonValue(_ctype, (object) new BsonOid(_input)); + var bytes = _input.ReadBytes(12); + _entryDataValue = new BsonValue(_ctype, new BsonOid(bytes)); break; case BsonType.STRING: case BsonType.CODE: diff --git a/Ejdb.BSON/BSONOid.cs b/Ejdb.BSON/BSONOid.cs index 97a03ac..50dfe7e 100644 --- a/Ejdb.BSON/BSONOid.cs +++ b/Ejdb.BSON/BSONOid.cs @@ -1,150 +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; - } - } +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 + 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) { - var result = new BsonOid(); - result._bytes = new byte[12]; - return result; + 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); } - 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); - } - } + /// + /// 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/BSONValue.cs b/Ejdb.BSON/BSONValue.cs index 51cd7ca..74c12f4 100644 --- a/Ejdb.BSON/BSONValue.cs +++ b/Ejdb.BSON/BSONValue.cs @@ -157,12 +157,7 @@ public static BsonValue GetMinKey() return new BsonValue(BsonType.MINKEY); } - public static BsonValue CreateOid(string oid) - { - return new BsonValue(BsonType.OID, new BsonOid(oid)); - } - - public static BsonValue Create(BsonOid oid) + public static BsonValue Create(BsonOid oid) { return new BsonValue(BsonType.OID, oid); } diff --git a/Ejdb.DB/EJDB.cs b/Ejdb.DB/EJDB.cs index 14ff976..58f5fd4 100644 --- a/Ejdb.DB/EJDB.cs +++ b/Ejdb.DB/EJDB.cs @@ -970,7 +970,7 @@ public BsonIterator Load(string cname, BsonOid oid) { return null; } //static extern IntPtr _ejdbloadbson([In] IntPtr coll, [In] byte[] oid); - byte[] bsdata = BsonPtrIntoByteArray(_ejdbloadbson(cptr, oid.ToBytes())); + byte[] bsdata = BsonPtrIntoByteArray(_ejdbloadbson(cptr, oid.ToByteArray())); if (bsdata.Length == 0) { return null; } @@ -982,7 +982,8 @@ public BsonIterator Load(string cname, BsonOid oid) { /// /// Name of collection. /// Object identifiers. - public bool Remove(string cname, params BsonOid[] oids) { + public bool Remove(string cname, params BsonOid[] oids) + { CheckDisposed(); IntPtr cptr = _ejdbgetcoll(_db, cname); if (cptr == IntPtr.Zero) { @@ -990,7 +991,7 @@ public bool Remove(string cname, params BsonOid[] oids) { } //internal static extern bool _ejdbrmbson([In] IntPtr cptr, [In] byte[] oid); foreach (var oid in oids) { - if (!_ejdbrmbson(cptr, oid.ToBytes())) { + if (!_ejdbrmbson(cptr, oid.ToByteArray())) { if (_throwonfail) { throw new EJDBException(this); } else { diff --git a/Ejdb.Tests/StreamWithoutSeek.cs b/Ejdb.Tests/StreamWithoutSeek.cs deleted file mode 100644 index 2dc0d34..0000000 --- a/Ejdb.Tests/StreamWithoutSeek.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.IO; - -namespace Ejdb.Tests -{ - public class StreamWithoutSeek : MemoryStream - { - public override bool CanSeek - { - get { return false; } - } - } -} \ No newline at end of file diff --git a/Ejdb.Tests/TestBsonArray.cs b/Ejdb.Tests/TestBsonArray.cs index a31213f..4ef10b2 100644 --- a/Ejdb.Tests/TestBsonArray.cs +++ b/Ejdb.Tests/TestBsonArray.cs @@ -81,8 +81,8 @@ public void TestCreateObjectArray() [Test] public void TestCreateObjectIdArray() { - var value1 = new BsonOid(Guid.NewGuid().ToByteArray()); - var value2 = new BsonOid(Guid.NewGuid().ToByteArray()); + var value1 = BsonOid.GenerateNewId(); + var value2 = BsonOid.GenerateNewId(); var values = new[] { value1, value2 }; var array = new BsonArray(values); Assert.AreEqual(2, array.Count); 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 index e7a54dc..b842a81 100644 --- a/Ejdb.Tests/TestBsonSerialization.cs +++ b/Ejdb.Tests/TestBsonSerialization.cs @@ -157,6 +157,8 @@ private static void _TestSerializeDateTime(DateTime dateTime) Assert.AreEqual(dateTime.ToUniversalTime(), dateTime2.ToUniversalTime()); } + + [Test] public void SerializeAnonmyousType() { @@ -204,7 +206,7 @@ public void TestIterate1() doc["bb"] = 24; //doc["ccc"] = BsonDocument.ValueOf(new{na1 = 1, nb = "2"}); - //doc["d"] = new BsonOid("51b9f3af98195c4600000000"); + //doc["d"] = new BsonOidOld("51b9f3af98195c4600000000"); //17-00-00-00 +4 //02-61-00-03-00-00-00-61-76-00 +10 @@ -245,7 +247,7 @@ public void TestIterate2() { var doc = new BsonDocument(); doc["a"] = "av"; doc["b"] = BsonDocument.ValueOf(new{cc = 1}); - doc["d"] = new BsonOid("51b9f3af98195c4600000000"); + doc["d"] = new BsonOid("51b9f3af98195c4600000000"); Assert.AreEqual(3, doc.KeysCount); //Console.WriteLine(doc.KeysCount); //Console.WriteLine(doc.ToDebugDataString()); @@ -308,8 +310,8 @@ public void TestIterate2() { } if (c == 2) { Assert.AreEqual(BsonType.OID, bv.BSONType); - Assert.IsInstanceOf(typeof(BsonOid), bv.Value); - var oid = bv.Value as BsonOid; + Assert.IsInstanceOf(typeof(BsonOid), bv.Value); + var oid = bv.Value as BsonOid; Assert.AreEqual("51b9f3af98195c4600000000", oid.ToString()); } c++; diff --git a/Ejdb.Tests/TestEJDB.cs b/Ejdb.Tests/TestEJDB.cs index 01ed407..50bee05 100644 --- a/Ejdb.Tests/TestEJDB.cs +++ b/Ejdb.Tests/TestEJDB.cs @@ -55,11 +55,11 @@ public void Test3SaveLoad() { 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(); @@ -67,7 +67,7 @@ public void Test3SaveLoad() { 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); diff --git a/nejdb.csproj b/nejdb.csproj index 80f2402..a42cfdc 100644 --- a/nejdb.csproj +++ b/nejdb.csproj @@ -60,6 +60,7 @@ + @@ -69,13 +70,13 @@ + - From 00d7a610856c42592f145563ea817f413748b889 Mon Sep 17 00:00:00 2001 From: Oliver Klemencic Date: Fri, 27 Dec 2013 19:00:42 +0100 Subject: [PATCH 12/26] Added improved support for serialization of custom types --- Ejdb.BSON/BSONDocument.cs | 6 +- Ejdb.BSON/BSONValue.cs | 30 +++++--- Ejdb.BSON/Serialization/BsonClassMap.cs | 46 ++++++++++++ .../Serialization/BsonClassSerialization.cs | 72 ++++++++++++++++++ Ejdb.BSON/Serialization/BsonMemberMap.cs | 73 +++++++++++++++++++ Ejdb.Tests/TestBsonSerialization.cs | 26 ++++++- nejdb.csproj | 4 + 7 files changed, 244 insertions(+), 13 deletions(-) create mode 100644 Ejdb.BSON/Serialization/BsonClassMap.cs create mode 100644 Ejdb.BSON/Serialization/BsonClassSerialization.cs create mode 100644 Ejdb.BSON/Serialization/BsonMemberMap.cs diff --git a/Ejdb.BSON/BSONDocument.cs b/Ejdb.BSON/BSONDocument.cs index 7b0e3b4..33f848d 100644 --- a/Ejdb.BSON/BSONDocument.cs +++ b/Ejdb.BSON/BSONDocument.cs @@ -20,6 +20,7 @@ using System.Diagnostics; using Ejdb.IO; using System.Linq; +using Ejdb.Utils; namespace Ejdb.BSON { @@ -364,7 +365,8 @@ public override int GetHashCode() { return !(d1 == d2); } - public static BsonDocument ValueOf(object val) { + public static BsonDocument ValueOf(object val) + { if (val == null) return new BsonDocument(); @@ -376,7 +378,7 @@ public static BsonDocument ValueOf(object val) { if (vtype == typeof (byte[])) return new BsonDocument((byte[]) val); - return BsonValue.GetAnonTypeDocument(val); + return BsonValue.GetDocumentForCustomClassObject(val); } public object Clone() { diff --git a/Ejdb.BSON/BSONValue.cs b/Ejdb.BSON/BSONValue.cs index 74c12f4..145f2f1 100644 --- a/Ejdb.BSON/BSONValue.cs +++ b/Ejdb.BSON/BSONValue.cs @@ -16,6 +16,8 @@ using System; using System.Collections.Generic; using System.Reflection; +using Ejdb.DB; +using Ejdb.Tests; namespace Ejdb.BSON { @@ -113,28 +115,36 @@ public static BsonValue ValueOf(object v) TYPE_SETTERS.TryGetValue(vtype, out setter); if (setter == null) - setter = GetAnonTypeValue; + setter = GetValueForCustomClassObject; var bsonValue = setter(v); return bsonValue; } - public static BsonDocument GetAnonTypeDocument(object val) + public static BsonDocument GetDocumentForCustomClassObject(object val) { - var ndoc = new BsonDocument(); - Type vtype = val.GetType(); - foreach (PropertyInfo pi in vtype.GetProperties()) + var type = val.GetType(); + if ((type.IsClass || (type.IsValueType && !type.IsPrimitive)) && + !typeof (Array).IsAssignableFrom(type) && + !typeof (Enum).IsAssignableFrom(type)) { - if (pi.CanRead) - ndoc[pi.Name] = pi.GetValue(val, null); + var bsonDocument = new BsonDocument(); + var classMap = BsonClassSerialization.LookupClassMap(type); + foreach (var member in classMap.AllMemberMaps) + { + var value = member.Getter(val); + bsonDocument.Add(member.ElementName, value); + } + + return bsonDocument; } - return ndoc; + throw new Exception(string.Format("Type '{0}' not supported for custom class serialization", type.FullName)); } - public static BsonValue GetAnonTypeValue(object val) + public static BsonValue GetValueForCustomClassObject(object val) { - return Create(GetAnonTypeDocument(val)); + return Create(GetDocumentForCustomClassObject(val)); } public static BsonValue GetNull() diff --git a/Ejdb.BSON/Serialization/BsonClassMap.cs b/Ejdb.BSON/Serialization/BsonClassMap.cs new file mode 100644 index 0000000..4667764 --- /dev/null +++ b/Ejdb.BSON/Serialization/BsonClassMap.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Reflection; + +namespace Ejdb.BSON +{ + public class BsonClassMap + { + private readonly ReadOnlyCollection _allMemberMapsReadonly; + + public BsonClassMap(Type type) + { + ClassType = type; + _allMemberMapsReadonly = new ReadOnlyCollection(_AutoMap()); + } + + + private List _AutoMap() + { + var list = new List(); + + foreach (var propertyInfo in ClassType.GetProperties(BindingFlags.Instance | BindingFlags.Public)) + { + if (propertyInfo.GetIndexParameters().Length != 0) // skip indexers + continue; + + list.Add(new BsonMemberMap(propertyInfo)); + } + + return list; + } + + // public properties + /// + /// Gets all the member maps (including maps for inherited members). + /// + public ReadOnlyCollection AllMemberMaps + { + get { return _allMemberMapsReadonly; } + } + + + 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..4add994 --- /dev/null +++ b/Ejdb.BSON/Serialization/BsonClassSerialization.cs @@ -0,0 +1,72 @@ +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..68b4eb0 --- /dev/null +++ b/Ejdb.BSON/Serialization/BsonMemberMap.cs @@ -0,0 +1,73 @@ +using System; +using System.Reflection; +using System.Linq.Expressions; + +namespace Ejdb.BSON +{ + public class BsonMemberMap + { + private Func _getter; + private readonly MemberInfo _memberInfo; + + // constructors + /// + /// Initializes a new instance of the BsonMemberMap class. + /// + /// The member info. + public BsonMemberMap(MemberInfo memberInfo) + { + _memberInfo = memberInfo; + ElementName = memberInfo.Name; + } + + /// + /// Gets the name of the element. + /// + public string ElementName { get; private set; } + + /// + /// Gets the getter function. + /// + public Func Getter + { + get + { + if (_getter == null) + _getter = GetGetter(); + + return _getter; + } + } + + private Func GetGetter() + { + var propertyInfo = _memberInfo as PropertyInfo; + 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, _memberInfo.DeclaringType), + _memberInfo + ), + typeof(object) + ), + objParameter + ); + + return lambdaExpression.Compile(); + } + } +} \ No newline at end of file diff --git a/Ejdb.Tests/TestBsonSerialization.cs b/Ejdb.Tests/TestBsonSerialization.cs index b842a81..2886dc3 100644 --- a/Ejdb.Tests/TestBsonSerialization.cs +++ b/Ejdb.Tests/TestBsonSerialization.cs @@ -176,8 +176,32 @@ public void SerializeAnonmyousType() + @"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() diff --git a/nejdb.csproj b/nejdb.csproj index a42cfdc..d507900 100644 --- a/nejdb.csproj +++ b/nejdb.csproj @@ -64,7 +64,11 @@ + + + + From f643731d287852d38de0c5677eaf1712eeccc08b Mon Sep 17 00:00:00 2001 From: Oliver Klemencic Date: Fri, 27 Dec 2013 19:01:32 +0100 Subject: [PATCH 13/26] Further work on the improved query API --- Ejdb.DB/Query/DynamicReflectionHelper.cs | 51 ++++++++++ Ejdb.DB/Query/IPartialQuery.cs | 8 ++ Ejdb.DB/Query/PartialQuery(T).cs | 25 +++++ Ejdb.DB/Query/PartialQuery.cs | 19 ++++ Ejdb.DB/Query/Query(T).cs | 103 +++++++++++++++++++ Ejdb.DB/Query/Query.cs | 36 ++++--- Ejdb.Tests/TestQuery.cs | 120 +++++++++++++++++++++++ 7 files changed, 351 insertions(+), 11 deletions(-) create mode 100644 Ejdb.DB/Query/DynamicReflectionHelper.cs create mode 100644 Ejdb.DB/Query/IPartialQuery.cs create mode 100644 Ejdb.DB/Query/PartialQuery(T).cs create mode 100644 Ejdb.DB/Query/PartialQuery.cs create mode 100644 Ejdb.DB/Query/Query(T).cs create mode 100644 Ejdb.Tests/TestQuery.cs diff --git a/Ejdb.DB/Query/DynamicReflectionHelper.cs b/Ejdb.DB/Query/DynamicReflectionHelper.cs new file mode 100644 index 0000000..8571a04 --- /dev/null +++ b/Ejdb.DB/Query/DynamicReflectionHelper.cs @@ -0,0 +1,51 @@ +// // Copyright © Anton Paar GmbH, 2004-2013 +// + +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/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/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..29e9fdc --- /dev/null +++ b/Ejdb.DB/Query/PartialQuery.cs @@ -0,0 +1,19 @@ +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..941d7c7 --- /dev/null +++ b/Ejdb.DB/Query/Query(T).cs @@ -0,0 +1,103 @@ +using System; +using System.Linq.Expressions; + +namespace Ejdb.DB +{ + public static class Query + { + public static IQuery EQ(Expression> memberExpression, TMember value) + { + var fieldName = _GetFieldName(memberExpression); + return Query.EQ(fieldName, value); + } + + public static IQuery EqualsIgnoreCase(Expression> memberExpression, string value) + { + var fieldName = _GetFieldName(memberExpression); + return Query.EqualsIgnoreCase(fieldName, value); + } + + public static IQuery BeginsWith(Expression> memberExpression, string value) + { + var fieldName = _GetFieldName(memberExpression); + return Query.BeginsWith(fieldName, value); + } + + public static IQuery EndsWith(Expression> memberExpression, string value) + { + var fieldName = _GetFieldName(memberExpression); + return Query.EndsWith(fieldName, value); + } + + public static IQuery GT(Expression> memberExpression, TMember value) + { + var fieldName = _GetFieldName(memberExpression); + return Query.GT(fieldName, value); + } + + public static IQuery GTE(Expression> memberExpression, TMember value) + { + var fieldName = _GetFieldName(memberExpression); + return Query.GTE(fieldName, value); + } + + public static IQuery LT(Expression> memberExpression, TMember value) + { + var fieldName = _GetFieldName(memberExpression); + return Query.LT(fieldName, value); + } + + public static IQuery LTE(Expression> memberExpression, TMember value) + { + var fieldName = _GetFieldName(memberExpression); + return Query.LTE(fieldName, value); + } + + public static IQuery In(Expression> memberExpression, params TMember[] comparisonValues) + { + var fieldName = _GetFieldName(memberExpression); + return Query.In(fieldName, comparisonValues); + } + + public static IQuery NotIn(Expression> memberExpression, params TMember[] comparisonValues) + { + var fieldName = _GetFieldName(memberExpression); + return Query.NotIn(fieldName, comparisonValues); + } + + public static IQuery NotEquals(Expression> memberExpression, TMember value) + { + var fieldName = _GetFieldName(memberExpression); + return Query.NotEquals(fieldName, value); + } + + public static IQuery Not(Expression> memberExpression, PartialQuery query) + { + var fieldName = _GetFieldName(memberExpression); + return Query.Not(fieldName, query); + } + + public static IQuery Between(Expression> memberExpression, TMember comparisonValue1, TMember comparisonValue2) + { + var fieldName = _GetFieldName(memberExpression); + return Query.Between(fieldName, comparisonValue1, comparisonValue2); + } + + public static IQuery Exists(Expression> memberExpression) + { + var fieldName = _GetFieldName(memberExpression); + return Query.Exists(fieldName); + } + + public static IQuery NotExists(Expression> memberExpression) + { + var fieldName = _GetFieldName(memberExpression); + return Query.NotExists(fieldName); + } + + private static string _GetFieldName(Expression> memberExpression) + { + return DynamicReflectionHelper.GetProperty(memberExpression).Name; + } + } +} \ No newline at end of file diff --git a/Ejdb.DB/Query/Query.cs b/Ejdb.DB/Query/Query.cs index 968abf5..0716529 100644 --- a/Ejdb.DB/Query/Query.cs +++ b/Ejdb.DB/Query/Query.cs @@ -66,20 +66,21 @@ public static IQuery NotIn(string fieldName, params T[] comparisonValues) return _BinaryQuery("$nin", fieldName, comparisonValues); } - public static IQuery Not(string fieldName, object comparisonValue) + public static IQuery NotEquals(string fieldName, object comparisonValue) { return _BinaryQuery("$not", fieldName, comparisonValue); } - /* public static IQuery Not(string fieldName, IQuery query) + public static IQuery Not(string fieldName, IPartialQuery query) { - var childValue = query.GetQueryDocument(); - return new Query("$not", childValue); - } */ + var childValue = new BsonDocument(); + childValue.Add(query.QueryOperator, query.ComparisonValue); + return _BinaryQuery("$not", fieldName, childValue); + } public static IQuery Between(string fieldName, T comparisonValue1, T comparisonValue2) { - var comparisonValues = new T[] { comparisonValue1, comparisonValue2 }; + var comparisonValues = new[] { comparisonValue1, comparisonValue2 }; return _BinaryQuery("$bt", fieldName, comparisonValues); } @@ -93,17 +94,30 @@ public static IQuery Or(params IQuery[] queries) return _CombinedQuery("$or", queries); } - public static IQuery Exists(string name) + public static IQuery Exists(string fieldName) + { + return _BinaryQuery("$exists", fieldName, true); + } + + public static IQuery NotExists(string fieldName) { - return null; + return _BinaryQuery("$exists", fieldName, false); } - public static IQuery ElemMatch(string name, BsonDocument queryDocument) + public static IQuery ElemMatch(string fieldName, params IQuery[] queries) { - return null; + var queryDocument = new BsonDocument(); + + foreach (var query in queries) + { + foreach (var field in query.GetQueryDocument()) + queryDocument[field.Key] = field.Value; + } + + return new Query("$elemMatch", queryDocument); } - private static IQuery _CombinedQuery(string combinator, IQuery[] queries) + private static IQuery _CombinedQuery(string combinator, params IQuery[] queries) { var documents = queries.Select(x => x.GetQueryDocument()).ToArray(); var childValue = new BsonArray(documents); diff --git a/Ejdb.Tests/TestQuery.cs b/Ejdb.Tests/TestQuery.cs new file mode 100644 index 0000000..cce4d37 --- /dev/null +++ b/Ejdb.Tests/TestQuery.cs @@ -0,0 +1,120 @@ +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"; + + [SetUp] + 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 Queries() + { + _QueryResults(1, Query.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")); + + _QueryResults(5, Query.And( + Query.GT("MeasuredTemperature", 50), + Query.EQ("OpticalRotation", 5) + )); + + _QueryResults(5, Query.And( + Query.GT(x => x.MeasuredTemperature, 50), + Query.EQ(x => x.OpticalRotation, 5) + )); + + _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) + )); + } + + 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 From 4180d411e1c8411ed7d247c3fe72eeb595c16cf5 Mon Sep 17 00:00:00 2001 From: Oliver Klemencic Date: Fri, 27 Dec 2013 19:14:44 +0100 Subject: [PATCH 14/26] Cleaned up whitespaced, braces and copyright text --- Ejdb.BSON/BSONDocument.cs | 455 ++++++++++++----------- Ejdb.BSON/BSONValue.cs | 394 ++++++++++---------- Ejdb.BSON/BSONValueWithKey.cs | 18 +- Ejdb.DB/Query/DynamicReflectionHelper.cs | 17 +- Ejdb.DB/Query/IQuery.cs | 17 +- Ejdb.DB/Query/PartialQuery.cs | 18 +- Ejdb.DB/Query/Query(T).cs | 18 +- Ejdb.DB/Query/Query.cs | 17 +- Ejdb.Utils/HelperExtensions.cs | 17 +- nejdb.csproj | 4 + sample/Program.cs | 21 +- 11 files changed, 551 insertions(+), 445 deletions(-) diff --git a/Ejdb.BSON/BSONDocument.cs b/Ejdb.BSON/BSONDocument.cs index 33f848d..9613c88 100644 --- a/Ejdb.BSON/BSONDocument.cs +++ b/Ejdb.BSON/BSONDocument.cs @@ -16,11 +16,8 @@ using System; using System.Collections.Generic; using System.IO; -using System.Text; -using System.Diagnostics; using Ejdb.IO; using System.Linq; -using Ejdb.Utils; namespace Ejdb.BSON { @@ -28,9 +25,9 @@ namespace Ejdb.BSON { /// BSON document deserialized data wrapper. /// [Serializable] - public class BsonDocument : IBsonValue, IEnumerable, ICloneable - { - [NonSerializedAttribute] + public class BsonDocument : IBsonValue, IEnumerable, ICloneable + { + [NonSerializedAttribute] Dictionary _fields; [NonSerializedAttribute] @@ -53,9 +50,9 @@ public virtual BsonType BSONType { /// Gets the document keys. /// public ICollection Keys - { + { get - { + { return _fields.Keys; } } @@ -70,12 +67,12 @@ public int KeysCount { } public BsonDocument() { - _fields = new Dictionary(); + _fields = new Dictionary(); } public BsonDocument(BsonIterator it) - : this() - { + : this() + { while (it.Next() != BsonType.EOO) { var value = it.FetchCurrentValue(); @@ -83,23 +80,23 @@ public BsonDocument(BsonIterator it) } } - /// - /// 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)); - } + /// + /// 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; int ind = -1; int nfc = 0; - foreach (string f in fields) - { + foreach (string f in fields) + { if (f != null) nfc++; } @@ -143,39 +140,39 @@ public BsonDocument(BsonIterator it, string[] fields) : this() { } public BsonDocument(byte[] bsdata) - : this() - { + : this() + { using (var iterator = new BsonIterator(bsdata)) - { - while (iterator.Next() != BsonType.EOO) - Add(iterator.CurrentKey, iterator.FetchCurrentValue()); - } + { + while (iterator.Next() != BsonType.EOO) + Add(iterator.CurrentKey, iterator.FetchCurrentValue()); + } } public BsonDocument(Stream bstream) - : this() - { + : this() + { using (var iterator = new BsonIterator(bstream)) - { - while (iterator.Next() != BsonType.EOO) - Add(iterator.CurrentKey, iterator.FetchCurrentValue()); - } + { + while (iterator.Next() != BsonType.EOO) + Add(iterator.CurrentKey, iterator.FetchCurrentValue()); + } } public BsonDocument(BsonDocument doc) - : this() - { + : this() + { foreach (var bv in doc._fields) - Add(bv.Key, (BsonValue) bv.Value.Clone()); - } + 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); - } + 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() { + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } @@ -184,10 +181,10 @@ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { /// /// The byte array. public byte[] ToByteArray() - { + { byte[] res; using (var ms = new MemoryStream()) - { + { Serialize(ms); res = ms.ToArray(); } @@ -195,7 +192,7 @@ public byte[] ToByteArray() } public string ToDebugDataString() - { + { return BitConverter.ToString(ToByteArray()); } @@ -261,7 +258,7 @@ public object this[string key] { } set { - var bsonValue = BsonValue.ValueOf(value); + var bsonValue = BsonValue.ValueOf(value); Add(key, bsonValue); } } @@ -285,48 +282,48 @@ public void 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); - } + } + 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 - } + 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) { + 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; - } + var d1 = this; var d2 = ((BsonDocument) obj); @@ -334,7 +331,7 @@ public override bool Equals(object obj) { return false; foreach (var bv1 in d1._fields) - { + { var bv2 = d2.GetBsonValue(bv1.Key); if (bv1.Value != bv2) return false; @@ -343,11 +340,13 @@ public override bool Equals(object obj) { return true; } - public override int GetHashCode() { - if (_cachedhash != null) - return (int) _cachedhash; + public override int GetHashCode() + { + if (_cachedhash != null) + return (int) _cachedhash; - unchecked { + unchecked + { int hash = 1; foreach (var bv in _fields) { hash = (hash * 31) + bv.Key.GetHashCode() + bv.Value.GetHashCode(); @@ -366,168 +365,170 @@ public override int GetHashCode() { } public static BsonDocument ValueOf(object val) - { - if (val == null) - return new BsonDocument(); + { + if (val == null) + return new BsonDocument(); - var vtype = val.GetType(); + var vtype = val.GetType(); - if (val is BsonDocument) - return (BsonDocument) val; + if (val is BsonDocument) + return (BsonDocument) val; - if (vtype == typeof (byte[])) - return new BsonDocument((byte[]) val); + if (vtype == typeof (byte[])) + return new BsonDocument((byte[]) val); - return BsonValue.GetDocumentForCustomClassObject(val); + return BsonValue.GetDocumentForCustomClassObject(val); } public object Clone() { return new BsonDocument(this); } - public override string ToString() { + public override string ToString() + { return string.Format("[{0}: {1}]", GetType().Name, - string.Join(", ", from bv in _fields select bv.Value.ToStringWithKey(bv.Key))); + string.Join(", ", from bv in _fields select bv.Value.ToStringWithKey(bv.Key))); } //.////////////////////////////////////////////////////////////////// // Private staff //.////////////////////////////////////////////////////////////////// - - - public BsonDocument Add(string key, BsonValue val) - { - _cachedhash = null; - - 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 - _fields.Add(key, val); - - return this; - } - - 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) { + + + public BsonDocument Add(string key, BsonValue val) + { + _cachedhash = null; + + 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 + _fields.Add(key, val); + + return this; + } + + 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(key); + bw.WriteCString(key); } } } diff --git a/Ejdb.BSON/BSONValue.cs b/Ejdb.BSON/BSONValue.cs index 145f2f1..aca3c7e 100644 --- a/Ejdb.BSON/BSONValue.cs +++ b/Ejdb.BSON/BSONValue.cs @@ -15,9 +15,6 @@ // ============================================================================================ using System; using System.Collections.Generic; -using System.Reflection; -using Ejdb.DB; -using Ejdb.Tests; namespace Ejdb.BSON { @@ -39,7 +36,7 @@ public sealed class BsonValue : IBsonValue, ICloneable public object Value { get; internal set; } public BsonValue(BsonType type, object value) - { + { BSONType = type; Value = value; } @@ -47,25 +44,25 @@ public BsonValue(BsonType type, object value) 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) { + 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) { @@ -78,11 +75,13 @@ 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); } @@ -92,188 +91,189 @@ public override string ToString() { 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 string ToStringWithKey(string key) + { + return String.Format("[BsonValue: BsonType={0}, Key={1}, Value={2}]", BSONType, key, Value); + } - public object Clone() { + 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.Getter(val); - bsonDocument.Add(member.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 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() }, - }; + 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.Getter(val); + bsonDocument.Add(member.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 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 index aeca1c7..01031a3 100644 --- a/Ejdb.BSON/BSONValueWithKey.cs +++ b/Ejdb.BSON/BSONValueWithKey.cs @@ -1,5 +1,19 @@ -// // Copyright © Anton Paar GmbH, 2004-2013 -// +// ============================================================================================ +// .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 diff --git a/Ejdb.DB/Query/DynamicReflectionHelper.cs b/Ejdb.DB/Query/DynamicReflectionHelper.cs index 8571a04..247dbc1 100644 --- a/Ejdb.DB/Query/DynamicReflectionHelper.cs +++ b/Ejdb.DB/Query/DynamicReflectionHelper.cs @@ -1,5 +1,18 @@ -// // Copyright © Anton Paar GmbH, 2004-2013 -// +// ============================================================================================ +// .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; diff --git a/Ejdb.DB/Query/IQuery.cs b/Ejdb.DB/Query/IQuery.cs index 1913066..c064c0f 100644 --- a/Ejdb.DB/Query/IQuery.cs +++ b/Ejdb.DB/Query/IQuery.cs @@ -1,5 +1,18 @@ -// // Copyright © Anton Paar GmbH, 2004-2013 -// +// ============================================================================================ +// .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; diff --git a/Ejdb.DB/Query/PartialQuery.cs b/Ejdb.DB/Query/PartialQuery.cs index 29e9fdc..8714cd2 100644 --- a/Ejdb.DB/Query/PartialQuery.cs +++ b/Ejdb.DB/Query/PartialQuery.cs @@ -1,4 +1,20 @@ -namespace Ejdb.DB +// ============================================================================================ +// .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 { diff --git a/Ejdb.DB/Query/Query(T).cs b/Ejdb.DB/Query/Query(T).cs index 941d7c7..c4d5a0b 100644 --- a/Ejdb.DB/Query/Query(T).cs +++ b/Ejdb.DB/Query/Query(T).cs @@ -1,4 +1,20 @@ -using System; +// ============================================================================================ +// .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 diff --git a/Ejdb.DB/Query/Query.cs b/Ejdb.DB/Query/Query.cs index 0716529..3eaad13 100644 --- a/Ejdb.DB/Query/Query.cs +++ b/Ejdb.DB/Query/Query.cs @@ -1,5 +1,18 @@ -// // Copyright © Anton Paar GmbH, 2004-2013 -// +// ============================================================================================ +// .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; diff --git a/Ejdb.Utils/HelperExtensions.cs b/Ejdb.Utils/HelperExtensions.cs index 97585ea..b3a422e 100644 --- a/Ejdb.Utils/HelperExtensions.cs +++ b/Ejdb.Utils/HelperExtensions.cs @@ -1,5 +1,18 @@ -// // Copyright © Anton Paar GmbH, 2004-2013 -// +// ============================================================================================ +// .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; diff --git a/nejdb.csproj b/nejdb.csproj index d507900..1a22e22 100644 --- a/nejdb.csproj +++ b/nejdb.csproj @@ -64,7 +64,11 @@ + + + + diff --git a/sample/Program.cs b/sample/Program.cs index 9859f13..6fe356e 100644 --- a/sample/Program.cs +++ b/sample/Program.cs @@ -14,6 +14,7 @@ // Boston, MA 02111-1307 USA. // ============================================================================================ using System; +using System.Linq; using Ejdb.DB; using Ejdb.BSON; @@ -21,21 +22,22 @@ namespace sample { class MainClass { - public static void Main(string[] args) { + 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 { + 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 + extra = BsonNull.VALUE }); - var parrot2 = BSONDocument.ValueOf(new { + var parrot2 = BsonDocument.ValueOf(new { name = "Bounty", type = "Cockatoo", male = false, @@ -46,8 +48,8 @@ public static void Main(string[] args) { jb.Save("parrots", parrot1, parrot2); - Console.WriteLine("Grenny OID: " + parrot1["_id"]); - Console.WriteLine("Bounty OID: " + parrot2["_id"]); + Console.WriteLine("Grenny OID: " + parrot1[BsonConstants.Id]); + Console.WriteLine("Bounty OID: " + parrot2[BsonConstants.Id]); var q = jb.CreateQuery(new { likes = "toys" @@ -57,14 +59,15 @@ public static void Main(string[] args) { 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"); + //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(); + + Console.ReadKey(); } } } From d323a0db7d85c628770e8a5629c0306467d76b69 Mon Sep 17 00:00:00 2001 From: Oliver Klemencic Date: Fri, 27 Dec 2013 22:21:59 +0100 Subject: [PATCH 15/26] Added performance demo --- sample/MainClass.cs | 120 +++++++++++++++++++++++++++++++++ sample/Program.cs | 73 -------------------- sample/sample.csproj | 154 +++++++++++++++++++++---------------------- 3 files changed, 197 insertions(+), 150 deletions(-) create mode 100644 sample/MainClass.cs delete mode 100644 sample/Program.cs diff --git a/sample/MainClass.cs b/sample/MainClass.cs new file mode 100644 index 0000000..bd047b9 --- /dev/null +++ b/sample/MainClass.cs @@ -0,0 +1,120 @@ +// ============================================================================================ +// .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]; + for (int i = 0; i < posts.Length; i++) + { + posts[i] = new BsonDocument() + { + { "Text", BsonValue.Create(DateTime.Now) }, + { "CreationDate", BsonValue.Create(DateTime.Now) }, + { "LastChangeDate", BsonValue.Create(DateTime.Now) }, + }; + } + + var collectionName = "posts"; + jb.Save(collectionName, posts); + + int count = 500; + BsonDocument doc; + var watch = new Stopwatch(); + watch.Start(); + for (int i = 0; i < count; i++) + { + var iterator = jb.Load(collectionName, (BsonOid) posts[i]["_id"]); + doc = iterator.ToBsonDocument(); + } + + watch.Stop(); + + Console.WriteLine("Loading {0} documents took {1}ms", count, watch.ElapsedMilliseconds); + 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(); + } + } +} \ No newline at end of file diff --git a/sample/Program.cs b/sample/Program.cs deleted file mode 100644 index 6fe356e..0000000 --- a/sample/Program.cs +++ /dev/null @@ -1,73 +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 System.Linq; -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 = BsonNull.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[BsonConstants.Id]); - Console.WriteLine("Bounty OID: " + parrot2[BsonConstants.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/sample.csproj b/sample/sample.csproj index bf571a8..12b5b2b 100644 --- a/sample/sample.csproj +++ b/sample/sample.csproj @@ -1,78 +1,78 @@ - - - - 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 From e53ef2d3e9ef1668682f29a85783d46aea98ad1e Mon Sep 17 00:00:00 2001 From: Oliver Klemencic Date: Fri, 27 Dec 2013 22:22:26 +0100 Subject: [PATCH 16/26] Added ignore.conf --- ignore.conf | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 ignore.conf 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 From ea2623f311fd0f19dce6ebd42e9d2e464e6f6021 Mon Sep 17 00:00:00 2001 From: Oliver Klemencic Date: Sat, 28 Dec 2013 18:41:28 +0100 Subject: [PATCH 17/26] Added support for deserialization of BSON results --- Ejdb.BSON/BSONIterator.cs | 779 +++++++++--------- Ejdb.BSON/BSONValue.cs | 4 +- Ejdb.BSON/Serialization/BsonClassMap.cs | 124 ++- .../Serialization/BsonClassSerialization.cs | 2 - Ejdb.BSON/Serialization/BsonMemberMap.cs | 87 +- nejdb.csproj | 256 +++--- sample/MainClass.cs | 38 +- sample/Test.cs | 17 + sample/Tests.cs | 42 + sample/sample.csproj | 2 + 10 files changed, 764 insertions(+), 587 deletions(-) create mode 100644 sample/Test.cs create mode 100644 sample/Tests.cs diff --git a/Ejdb.BSON/BSONIterator.cs b/Ejdb.BSON/BSONIterator.cs index f2e00c4..2ff3527 100644 --- a/Ejdb.BSON/BSONIterator.cs +++ b/Ejdb.BSON/BSONIterator.cs @@ -1,379 +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 - { - 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); - } 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); - 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; - } - } -} - +// ============================================================================================ +// .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/BSONValue.cs b/Ejdb.BSON/BSONValue.cs index aca3c7e..aafccb1 100644 --- a/Ejdb.BSON/BSONValue.cs +++ b/Ejdb.BSON/BSONValue.cs @@ -132,8 +132,8 @@ public static BsonDocument GetDocumentForCustomClassObject(object val) var classMap = BsonClassSerialization.LookupClassMap(type); foreach (var member in classMap.AllMemberMaps) { - var value = member.Getter(val); - bsonDocument.Add(member.ElementName, value); + var value = member.Value.Getter(val); + bsonDocument.Add(member.Value.ElementName, value); } return bsonDocument; diff --git a/Ejdb.BSON/Serialization/BsonClassMap.cs b/Ejdb.BSON/Serialization/BsonClassMap.cs index 4667764..b5f62f5 100644 --- a/Ejdb.BSON/Serialization/BsonClassMap.cs +++ b/Ejdb.BSON/Serialization/BsonClassMap.cs @@ -1,46 +1,80 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Reflection; - -namespace Ejdb.BSON -{ - public class BsonClassMap - { - private readonly ReadOnlyCollection _allMemberMapsReadonly; - - public BsonClassMap(Type type) - { - ClassType = type; - _allMemberMapsReadonly = new ReadOnlyCollection(_AutoMap()); - } - - - private List _AutoMap() - { - var list = new List(); - - foreach (var propertyInfo in ClassType.GetProperties(BindingFlags.Instance | BindingFlags.Public)) - { - if (propertyInfo.GetIndexParameters().Length != 0) // skip indexers - continue; - - list.Add(new BsonMemberMap(propertyInfo)); - } - - return list; - } - - // public properties - /// - /// Gets all the member maps (including maps for inherited members). - /// - public ReadOnlyCollection AllMemberMaps - { - get { return _allMemberMapsReadonly; } - } - - - public Type ClassType { get; private set; } - } +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 index 4add994..1467aac 100644 --- a/Ejdb.BSON/Serialization/BsonClassSerialization.cs +++ b/Ejdb.BSON/Serialization/BsonClassSerialization.cs @@ -17,9 +17,7 @@ public class BsonClassSerialization public static BsonClassMap LookupClassMap(Type classType) { if (classType == null) - { throw new ArgumentNullException("classType"); - } __configurationLock.EnterReadLock(); try diff --git a/Ejdb.BSON/Serialization/BsonMemberMap.cs b/Ejdb.BSON/Serialization/BsonMemberMap.cs index 68b4eb0..80b963f 100644 --- a/Ejdb.BSON/Serialization/BsonMemberMap.cs +++ b/Ejdb.BSON/Serialization/BsonMemberMap.cs @@ -5,20 +5,23 @@ namespace Ejdb.BSON { public class BsonMemberMap - { - private Func _getter; - private readonly MemberInfo _memberInfo; + { + private Func _getter; + private Action _setter; + private readonly PropertyInfo _propertyInfo; // constructors /// /// Initializes a new instance of the BsonMemberMap class. /// - /// The member info. - public BsonMemberMap(MemberInfo memberInfo) + /// The member info. + public BsonMemberMap(PropertyInfo propertyInfo) { - _memberInfo = memberInfo; - ElementName = memberInfo.Name; - } + _propertyInfo = propertyInfo; + ElementName = propertyInfo.Name; + } + + public bool IsWritable { get; private set; } /// /// Gets the name of the element. @@ -33,23 +36,71 @@ public Func Getter get { if (_getter == null) - _getter = GetGetter(); + _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 GetGetter() - { - var propertyInfo = _memberInfo as PropertyInfo; - if (propertyInfo != null) + private Func _GetPropertyGetter() + { + if (_propertyInfo != null) { - var getMethodInfo = propertyInfo.GetGetMethod(true); + 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); + _propertyInfo.PropertyType.FullName, _propertyInfo.Name, _propertyInfo.DeclaringType.FullName); throw new BsonSerializationException(message); } } @@ -59,8 +110,8 @@ private Func GetGetter() var lambdaExpression = Expression.Lambda>( Expression.Convert( Expression.MakeMemberAccess( - Expression.Convert(objParameter, _memberInfo.DeclaringType), - _memberInfo + Expression.Convert(objParameter, _propertyInfo.DeclaringType), + _propertyInfo ), typeof(object) ), diff --git a/nejdb.csproj b/nejdb.csproj index 1a22e22..b7da8d7 100644 --- a/nejdb.csproj +++ b/nejdb.csproj @@ -1,129 +1,129 @@ - - - - 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 + Settings.settings + + + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + \ No newline at end of file diff --git a/sample/MainClass.cs b/sample/MainClass.cs index bd047b9..6dae580 100644 --- a/sample/MainClass.cs +++ b/sample/MainClass.cs @@ -35,11 +35,13 @@ private static void _PerformanceDemo() 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", BsonValue.Create(DateTime.Now) }, + { "Text", text }, { "CreationDate", BsonValue.Create(DateTime.Now) }, { "LastChangeDate", BsonValue.Create(DateTime.Now) }, }; @@ -48,19 +50,21 @@ private static void _PerformanceDemo() var collectionName = "posts"; jb.Save(collectionName, posts); - int count = 500; - BsonDocument doc; - var watch = new Stopwatch(); - watch.Start(); - for (int i = 0; i < count; i++) - { - var iterator = jb.Load(collectionName, (BsonOid) posts[i]["_id"]); - doc = iterator.ToBsonDocument(); - } - - watch.Stop(); + 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"); - Console.WriteLine("Loading {0} documents took {1}ms", count, watch.ElapsedMilliseconds); + 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(); } @@ -117,4 +121,12 @@ private static void _ZooDemo() 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/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 12b5b2b..041fd11 100644 --- a/sample/sample.csproj +++ b/sample/sample.csproj @@ -60,6 +60,8 @@ True Settings.settings + + From a777e00286e2e803a4eaacab17f83e0cb0ed6229 Mon Sep 17 00:00:00 2001 From: Oliver Klemencic Date: Thu, 2 Jan 2014 00:17:40 +0100 Subject: [PATCH 18/26] Included missing file --- nejdb.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/nejdb.csproj b/nejdb.csproj index b7da8d7..0e90c90 100644 --- a/nejdb.csproj +++ b/nejdb.csproj @@ -81,6 +81,7 @@ + From 50f4fc61511283e258b11cdab91a571bb3593e3f Mon Sep 17 00:00:00 2001 From: Oliver Klemencic Date: Thu, 2 Jan 2014 00:46:45 +0100 Subject: [PATCH 19/26] Added support for $set operation via typed query --- Ejdb.BSON/BSONValueWithKey.cs | 3 -- Ejdb.DB/EJDB.cs | 18 ++++++++++-- Ejdb.DB/Query/Update.cs | 18 ++++++++++++ Ejdb.DB/Query/UpdateBuilder.cs | 50 ++++++++++++++++++++++++++++++++++ Ejdb.Tests/TestUpdate.cs | 45 ++++++++++++++++++++++++++++++ nejdb.csproj | 3 ++ 6 files changed, 132 insertions(+), 5 deletions(-) create mode 100644 Ejdb.DB/Query/Update.cs create mode 100644 Ejdb.DB/Query/UpdateBuilder.cs create mode 100644 Ejdb.Tests/TestUpdate.cs diff --git a/Ejdb.BSON/BSONValueWithKey.cs b/Ejdb.BSON/BSONValueWithKey.cs index 01031a3..3fbd2db 100644 --- a/Ejdb.BSON/BSONValueWithKey.cs +++ b/Ejdb.BSON/BSONValueWithKey.cs @@ -21,14 +21,11 @@ public class BsonValueWithKey public BsonValueWithKey(string key, BsonValue value, BsonType type) { Value = value.Value; - BsonType = type; Key = key; } public object Value { get; private set; } - public BsonType BsonType { get; set; } - public string Key { get; private set; } } diff --git a/Ejdb.DB/EJDB.cs b/Ejdb.DB/EJDB.cs index 58f5fd4..bfaa455 100644 --- a/Ejdb.DB/EJDB.cs +++ b/Ejdb.DB/EJDB.cs @@ -1020,10 +1020,24 @@ public EJDBQuery CreateQueryFor(string defaultcollection) { public EJDBQCursor Find(string collection, IQuery query) { - var ejdbQuery = new EJDBQuery(this, query.GetQueryDocument(), collection); - return ejdbQuery.Find(); + var ejdbQuery = new EJDBQuery(this, query.GetQueryDocument()); + return ejdbQuery.Find(collection); } + public int Update(string collection, IQuery query, UpdateBuilder updateBuilder) + { + var document = new BsonDocument(); + + foreach (var field in query.GetQueryDocument()) + document.Add(field.Key, field.Value); + + foreach (var field in updateBuilder.Document) + 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. diff --git a/Ejdb.DB/Query/Update.cs b/Ejdb.DB/Query/Update.cs new file mode 100644 index 0000000..abcb175 --- /dev/null +++ b/Ejdb.DB/Query/Update.cs @@ -0,0 +1,18 @@ +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); + } + } +} \ 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..9ee257e --- /dev/null +++ b/Ejdb.DB/Query/UpdateBuilder.cs @@ -0,0 +1,50 @@ +using System; +using Ejdb.BSON; + +namespace Ejdb.DB +{ + public class UpdateBuilder + { + private const string SET_OPERATOR = "$set"; + + 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) + { + if (name == null) + throw new ArgumentNullException("name"); + + if (value == null) + throw new ArgumentNullException("value"); + + var element = _document[SET_OPERATOR] as BsonDocument; + if (element == null) + { + element = new BsonDocument(); + _document.Add(SET_OPERATOR, element); + } + + element.Add(name, value); + return this; + } + } +} \ No newline at end of file diff --git a/Ejdb.Tests/TestUpdate.cs b/Ejdb.Tests/TestUpdate.cs new file mode 100644 index 0000000..b258ae4 --- /dev/null +++ b/Ejdb.Tests/TestUpdate.cs @@ -0,0 +1,45 @@ +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"; + + [SetUp] + 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 UpdateSet() + { + var doc = new BsonDocument().Add("x", 1); + _db.Save(COLLECTION_NAME, doc); + var countMatched = _db.Update(COLLECTION_NAME, Query.EQ("x", 1), Update.Set("x", 2)); + Assert.That(countMatched, Is.EqualTo(1)); + + doc = _db.Load(COLLECTION_NAME, (BsonOid) doc["_id"]).ToBsonDocument(); + Assert.AreEqual(2, doc["x"]); + } + } +} \ No newline at end of file diff --git a/nejdb.csproj b/nejdb.csproj index 0e90c90..7abe0f5 100644 --- a/nejdb.csproj +++ b/nejdb.csproj @@ -73,6 +73,8 @@ + + @@ -82,6 +84,7 @@ + From c5873a898baca8cb4f43da37f06f7f4bbd7d5894 Mon Sep 17 00:00:00 2001 From: Oliver Klemencic Date: Thu, 2 Jan 2014 08:34:11 +0100 Subject: [PATCH 20/26] Added empty query --- Ejdb.DB/EJDB.cs | 2190 +++++++++++++++++------------------ Ejdb.DB/Query/EmptyQuery.cs | 18 + Ejdb.DB/Query/Query.cs | 307 ++--- Ejdb.Tests/TestUpdate.cs | 12 + nejdb.csproj | 1 + 5 files changed, 1282 insertions(+), 1246 deletions(-) create mode 100644 Ejdb.DB/Query/EmptyQuery.cs diff --git a/Ejdb.DB/EJDB.cs b/Ejdb.DB/EJDB.cs index bfaa455..91f83f6 100644 --- a/Ejdb.DB/EJDB.cs +++ b/Ejdb.DB/EJDB.cs @@ -1,1029 +1,1029 @@ -// ============================================================================================ -// .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 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.ToByteArray())) { - 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); - } - - public EJDBQCursor Find(string collection, IQuery query) - { +// ============================================================================================ +// .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 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.ToByteArray())) { + 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); + } + + public EJDBQCursor Find(string collection, IQuery query) + { var ejdbQuery = new EJDBQuery(this, query.GetQueryDocument()); - return ejdbQuery.Find(collection); - } - + return ejdbQuery.Find(collection); + } + public int Update(string collection, IQuery query, UpdateBuilder updateBuilder) { var document = new BsonDocument(); @@ -1036,73 +1036,73 @@ public int Update(string collection, IQuery query, UpdateBuilder updateBuilder) 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"); - } - } - } - } -} - + } + + /// + /// 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/Query/EmptyQuery.cs b/Ejdb.DB/Query/EmptyQuery.cs new file mode 100644 index 0000000..b31b6f5 --- /dev/null +++ b/Ejdb.DB/Query/EmptyQuery.cs @@ -0,0 +1,18 @@ +using Ejdb.BSON; + +namespace Ejdb.DB +{ + internal class EmptyQuery : IQuery + { + private EmptyQuery() + { + } + + public static EmptyQuery Instance = new EmptyQuery(); + + public BsonDocument GetQueryDocument() + { + return new BsonDocument(); + } + } +} \ No newline at end of file diff --git a/Ejdb.DB/Query/Query.cs b/Ejdb.DB/Query/Query.cs index 3eaad13..e39a7f2 100644 --- a/Ejdb.DB/Query/Query.cs +++ b/Ejdb.DB/Query/Query.cs @@ -1,152 +1,157 @@ -// ============================================================================================ -// .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 EQ(string fieldName, object value) - { - return new Query(fieldName, value); - } - - public static IQuery EqualsIgnoreCase(string fieldName, string value) - { - return _BinaryQuery("$icase", fieldName, value); - } - - public static IQuery BeginsWith(string fieldName, string value) - { - return _BinaryQuery("$begin", fieldName, value); - } - - public static IQuery EndsWith(string fieldName, string value) - { - return _BinaryQuery("end", fieldName, value); - } - - public static IQuery GT(string fieldName, object value) - { - return _BinaryQuery("$gt", fieldName, value); - } - - public static IQuery GTE(string fieldName, object value) - { - return _BinaryQuery("$gte", fieldName, value); - } - - public static IQuery LT(string fieldName, object value) - { - return _BinaryQuery("$lt", fieldName, value); - } - - public static IQuery LTE(string fieldName, object value) - { - return _BinaryQuery("$lte", fieldName, value); - } - - public static IQuery In(string fieldName, params T[] comparisonValues) - { - return _BinaryQuery("$in", fieldName, comparisonValues); - } - - public static IQuery NotIn(string fieldName, params T[] comparisonValues) - { - return _BinaryQuery("$nin", fieldName, comparisonValues); - } - - public static IQuery NotEquals(string fieldName, object comparisonValue) - { - return _BinaryQuery("$not", fieldName, comparisonValue); - } - - public static IQuery Not(string fieldName, IPartialQuery query) - { - var childValue = new BsonDocument(); - childValue.Add(query.QueryOperator, query.ComparisonValue); - return _BinaryQuery("$not", fieldName, childValue); - } - - public static IQuery Between(string fieldName, T comparisonValue1, T comparisonValue2) - { - var comparisonValues = new[] { comparisonValue1, comparisonValue2 }; - return _BinaryQuery("$bt", fieldName, comparisonValues); - } - - public static IQuery And(params IQuery[] queries) - { - return _CombinedQuery("$and", queries); - } - - public static IQuery Or(params IQuery[] queries) - { - return _CombinedQuery("$or", queries); - } - - public static IQuery Exists(string fieldName) - { - return _BinaryQuery("$exists", fieldName, true); - } - - public static IQuery NotExists(string fieldName) - { - return _BinaryQuery("$exists", fieldName, false); - } - - public static IQuery ElemMatch(string fieldName, params IQuery[] queries) - { - var queryDocument = new BsonDocument(); - - foreach (var query in queries) - { - foreach (var field in query.GetQueryDocument()) - queryDocument[field.Key] = field.Value; - } - - return new Query("$elemMatch", queryDocument); - } - - private static IQuery _CombinedQuery(string combinator, params IQuery[] queries) - { - var documents = queries.Select(x => x.GetQueryDocument()).ToArray(); - var childValue = new BsonArray(documents); - return new Query(combinator, childValue); - } - - private static IQuery _BinaryQuery(string queryOperation, string fieldName, object comparisonValue) - { - var query1 = new BsonDocument(); - query1[queryOperation] = comparisonValue; - return new Query(fieldName, query1); - } - - public BsonDocument GetQueryDocument() - { - return mQueryDocument; - } - } +// ============================================================================================ +// .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 IQuery EQ(string fieldName, object value) + { + return new Query(fieldName, value); + } + + public static IQuery EqualsIgnoreCase(string fieldName, string value) + { + return _BinaryQuery("$icase", fieldName, value); + } + + public static IQuery BeginsWith(string fieldName, string value) + { + return _BinaryQuery("$begin", fieldName, value); + } + + public static IQuery EndsWith(string fieldName, string value) + { + return _BinaryQuery("end", fieldName, value); + } + + public static IQuery GT(string fieldName, object value) + { + return _BinaryQuery("$gt", fieldName, value); + } + + public static IQuery GTE(string fieldName, object value) + { + return _BinaryQuery("$gte", fieldName, value); + } + + public static IQuery LT(string fieldName, object value) + { + return _BinaryQuery("$lt", fieldName, value); + } + + public static IQuery LTE(string fieldName, object value) + { + return _BinaryQuery("$lte", fieldName, value); + } + + public static IQuery In(string fieldName, params T[] comparisonValues) + { + return _BinaryQuery("$in", fieldName, comparisonValues); + } + + public static IQuery NotIn(string fieldName, params T[] comparisonValues) + { + return _BinaryQuery("$nin", fieldName, comparisonValues); + } + + public static IQuery NotEquals(string fieldName, object comparisonValue) + { + return _BinaryQuery("$not", fieldName, comparisonValue); + } + + public static IQuery Not(string fieldName, IPartialQuery query) + { + var childValue = new BsonDocument(); + childValue.Add(query.QueryOperator, query.ComparisonValue); + return _BinaryQuery("$not", fieldName, childValue); + } + + public static IQuery Between(string fieldName, T comparisonValue1, T comparisonValue2) + { + var comparisonValues = new[] { comparisonValue1, comparisonValue2 }; + return _BinaryQuery("$bt", fieldName, comparisonValues); + } + + public static IQuery And(params IQuery[] queries) + { + return _CombinedQuery("$and", queries); + } + + public static IQuery Or(params IQuery[] queries) + { + return _CombinedQuery("$or", queries); + } + + public static IQuery Exists(string fieldName) + { + return _BinaryQuery("$exists", fieldName, true); + } + + public static IQuery NotExists(string fieldName) + { + return _BinaryQuery("$exists", fieldName, false); + } + + public static IQuery ElemMatch(string fieldName, params IQuery[] queries) + { + var queryDocument = new BsonDocument(); + + foreach (var query in queries) + { + foreach (var field in query.GetQueryDocument()) + queryDocument[field.Key] = field.Value; + } + + return new Query("$elemMatch", queryDocument); + } + + private static IQuery _CombinedQuery(string combinator, params IQuery[] queries) + { + var documents = queries.Select(x => x.GetQueryDocument()).ToArray(); + var childValue = new BsonArray(documents); + return new Query(combinator, childValue); + } + + private static IQuery _BinaryQuery(string queryOperation, string fieldName, object comparisonValue) + { + var query1 = new BsonDocument(); + query1[queryOperation] = comparisonValue; + return new Query(fieldName, query1); + } + + public BsonDocument GetQueryDocument() + { + return mQueryDocument; + } + } } \ No newline at end of file diff --git a/Ejdb.Tests/TestUpdate.cs b/Ejdb.Tests/TestUpdate.cs index b258ae4..ce7b428 100644 --- a/Ejdb.Tests/TestUpdate.cs +++ b/Ejdb.Tests/TestUpdate.cs @@ -41,5 +41,17 @@ public void UpdateSet() doc = _db.Load(COLLECTION_NAME, (BsonOid) doc["_id"]).ToBsonDocument(); Assert.AreEqual(2, doc["x"]); } + + [Test] + public void TestUpdateEmptyQueryDocument() + { + var doc = new BsonDocument().Add("x", 1); + _db.Save(COLLECTION_NAME, doc); + var countMatched = _db.Update(COLLECTION_NAME, Query.Empty, Update.Set("x", 2)); + Assert.That(countMatched, Is.EqualTo(1)); + + doc = _db.Load(COLLECTION_NAME, (BsonOid)doc["_id"]).ToBsonDocument(); + Assert.AreEqual(2, doc["x"]); + } } } \ No newline at end of file diff --git a/nejdb.csproj b/nejdb.csproj index 7abe0f5..d5bc19e 100644 --- a/nejdb.csproj +++ b/nejdb.csproj @@ -65,6 +65,7 @@ + From aed06e92f239aa33ac6c98d3eec29f62f1e513e0 Mon Sep 17 00:00:00 2001 From: Oliver Klemencic Date: Thu, 2 Jan 2014 09:44:27 +0100 Subject: [PATCH 21/26] Added update operation support --- Ejdb.BSON/BSONArray.cs | 5 +- Ejdb.BSON/BSONDocument.cs | 13 +- Ejdb.DB/EJDB.cs | 30 +- Ejdb.DB/Query/Update.cs | 67 +- Ejdb.DB/Query/UpdateBuilder.cs | 105 +- Ejdb.Tests/TestQuery.cs | 2 +- Ejdb.Tests/TestUpdate.cs | 144 ++- Ejdb.Utils/Verify.cs | 1528 ++++++++++++++++++++++++ Ejdb.Utils/VerifyResources.Designer.cs | 234 ++++ Ejdb.Utils/VerifyResources.resx | 185 +++ nejdb.csproj | 13 + 11 files changed, 2274 insertions(+), 52 deletions(-) create mode 100644 Ejdb.Utils/Verify.cs create mode 100644 Ejdb.Utils/VerifyResources.Designer.cs create mode 100644 Ejdb.Utils/VerifyResources.resx diff --git a/Ejdb.BSON/BSONArray.cs b/Ejdb.BSON/BSONArray.cs index fa30ffb..b195227 100644 --- a/Ejdb.BSON/BSONArray.cs +++ b/Ejdb.BSON/BSONArray.cs @@ -49,10 +49,7 @@ public override BsonType BSONType public object this[int key] { - get - { - return GetObjectValue(key.ToString()); - } + get { return GetObjectValue(key.ToString()); } } public void SetMaxKey(int idx) diff --git a/Ejdb.BSON/BSONDocument.cs b/Ejdb.BSON/BSONDocument.cs index 9613c88..be2c429 100644 --- a/Ejdb.BSON/BSONDocument.cs +++ b/Ejdb.BSON/BSONDocument.cs @@ -220,8 +220,17 @@ public BsonValue GetBsonValue(string key) { /// BSON document key /// public object GetObjectValue(string key) { - var bv = GetBsonValue(key); - return bv != null ? bv.Value : null; + 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(); } /// diff --git a/Ejdb.DB/EJDB.cs b/Ejdb.DB/EJDB.cs index 91f83f6..5727b9a 100644 --- a/Ejdb.DB/EJDB.cs +++ b/Ejdb.DB/EJDB.cs @@ -980,12 +980,12 @@ public BsonIterator Load(string cname, BsonOid oid) { /// /// Removes stored objects from the collection. /// - /// Name of collection. + /// Name of collection. /// Object identifiers. - public bool Remove(string cname, params BsonOid[] oids) + public bool Remove(string collection, params BsonOid[] oids) { CheckDisposed(); - IntPtr cptr = _ejdbgetcoll(_db, cname); + IntPtr cptr = _ejdbgetcoll(_db, collection); if (cptr == IntPtr.Zero) { return true; } @@ -1002,6 +1002,16 @@ public bool Remove(string cname, params BsonOid[] oids) 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. /// @@ -1025,13 +1035,21 @@ public EJDBQCursor Find(string collection, IQuery query) } 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(); - foreach (var field in query.GetQueryDocument()) - document.Add(field.Key, field.Value); + if (query != null) + { + foreach (var field in query.GetQueryDocument()) + document.Add(field.Key, field.Value); + } - foreach (var field in updateBuilder.Document) + foreach (var field in updateDocument) document.Add(field.Key, field.Value); var ejdbQuery = new EJDBQuery(this, document); diff --git a/Ejdb.DB/Query/Update.cs b/Ejdb.DB/Query/Update.cs index abcb175..51a1f4f 100644 --- a/Ejdb.DB/Query/Update.cs +++ b/Ejdb.DB/Query/Update.cs @@ -13,6 +13,71 @@ public class Update 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 index 9ee257e..71b2c5e 100644 --- a/Ejdb.DB/Query/UpdateBuilder.cs +++ b/Ejdb.DB/Query/UpdateBuilder.cs @@ -6,6 +6,12 @@ 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; @@ -30,21 +36,98 @@ internal BsonDocument Document /// The builder (so method calls can be chained). public UpdateBuilder Set(string name, object value) { - if (name == null) - throw new ArgumentNullException("name"); - - if (value == null) - throw new ArgumentNullException("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 = _GetElement(operationName); + element.Add(name, value); + + return this; + } - var element = _document[SET_OPERATOR] as BsonDocument; + private BsonDocument _GetElement(string operationName) + { + var element = _document[operationName] as BsonDocument; if (element == null) { element = new BsonDocument(); - _document.Add(SET_OPERATOR, element); + _document.Add(operationName, element); } - - element.Add(name, value); - return this; - } + return element; + } } } \ No newline at end of file diff --git a/Ejdb.Tests/TestQuery.cs b/Ejdb.Tests/TestQuery.cs index cce4d37..975344d 100644 --- a/Ejdb.Tests/TestQuery.cs +++ b/Ejdb.Tests/TestQuery.cs @@ -12,7 +12,7 @@ public class TestQuery private EJDB _db; private const string COLLECTION_NAME = "results"; - [SetUp] + [TestFixtureSetUp] public void Setup() { _db = new EJDB("testdb1", EJDB.DEFAULT_OPEN_MODE | EJDB.JBOTRUNC); diff --git a/Ejdb.Tests/TestUpdate.cs b/Ejdb.Tests/TestUpdate.cs index ce7b428..6627b47 100644 --- a/Ejdb.Tests/TestUpdate.cs +++ b/Ejdb.Tests/TestUpdate.cs @@ -10,48 +10,138 @@ 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 = new EJDB("testdb1", EJDB.DEFAULT_OPEN_MODE | EJDB.JBOTRUNC); + _db.DropCollection(COLLECTION_NAME); + + var doc1 = new BsonDocument() + .Add("x", 1) + .Add("y", new[] { 1, 2, 3 }) + .Add("_id", _doc1Id); - /* 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, doc1); - _db.Save(COLLECTION_NAME, results); */ + var doc2 = new BsonDocument() + .Add("x", 2) + .Add("y", new[] { 2, 3, 4 }) + .Add("_id", _doc2Id); + + _db.Save(COLLECTION_NAME, doc2); } [Test] - public void UpdateSet() + public void Set() { - var doc = new BsonDocument().Add("x", 1); - _db.Save(COLLECTION_NAME, doc); - var countMatched = _db.Update(COLLECTION_NAME, Query.EQ("x", 1), Update.Set("x", 2)); - Assert.That(countMatched, Is.EqualTo(1)); + _Execute(Query.EQ("x", 1), Update.Set("x", 10), 1); + _TestDocumentValues(10, 2); + } - doc = _db.Load(COLLECTION_NAME, (BsonOid) doc["_id"]).ToBsonDocument(); - Assert.AreEqual(2, doc["x"]); + [Test] + public void UpdateSet_EmptyQueryDocument() + { + _Execute(Query.Empty, Update.Set("x", 10), 2); + _TestDocumentValues(10, 10); } [Test] - public void TestUpdateEmptyQueryDocument() + public void Set_NullQuery() { - var doc = new BsonDocument().Add("x", 1); - _db.Save(COLLECTION_NAME, doc); - var countMatched = _db.Update(COLLECTION_NAME, Query.Empty, Update.Set("x", 2)); - Assert.That(countMatched, Is.EqualTo(1)); + _Execute(null, Update.Set("x", 10), 2); + _TestDocumentValues(10, 10); + } - doc = _db.Load(COLLECTION_NAME, (BsonOid)doc["_id"]).ToBsonDocument(); - Assert.AreEqual(2, doc["x"]); + [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/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/nejdb.csproj b/nejdb.csproj index d5bc19e..3fa0966 100644 --- a/nejdb.csproj +++ b/nejdb.csproj @@ -76,6 +76,7 @@ + @@ -107,6 +108,11 @@ + + True + True + VerifyResources.resx + True True @@ -122,6 +128,13 @@ Settings.Designer.cs + + + ResXFileCodeGenerator + VerifyResources.Designer.cs + Ejdb + + From 13d9357db0b03171c9c75e5cf9ec495179990e18 Mon Sep 17 00:00:00 2001 From: Oliver Klemencic Date: Thu, 2 Jan 2014 13:07:18 +0100 Subject: [PATCH 22/26] Refactoring of query handling to support collection joins --- Ejdb.BSON/BSONDocument.cs | 30 +++--- Ejdb.DB/EJDB.cs | 4 +- Ejdb.DB/Query/EmptyQuery.cs | 7 +- Ejdb.DB/Query/IQuery.cs | 4 +- Ejdb.DB/Query/Query(T).cs | 135 +++++++++++++------------- Ejdb.DB/Query/Query.cs | 106 +++++++++++---------- Ejdb.DB/Query/QueryBuilder.cs | 101 ++++++++++++++++++++ Ejdb.DB/Query/QueryBuilderBase.cs | 153 ++++++++++++++++++++++++++++++ Ejdb.DB/Query/UpdateBuilder.cs | 4 +- Ejdb.Tests/TestCollectionJoins.cs | 71 ++++++++++++++ Ejdb.Tests/TestQuery.cs | 29 +++--- nejdb.csproj | 3 + 12 files changed, 486 insertions(+), 161 deletions(-) create mode 100644 Ejdb.DB/Query/QueryBuilder.cs create mode 100644 Ejdb.DB/Query/QueryBuilderBase.cs create mode 100644 Ejdb.Tests/TestCollectionJoins.cs diff --git a/Ejdb.BSON/BSONDocument.cs b/Ejdb.BSON/BSONDocument.cs index be2c429..5e77a9d 100644 --- a/Ejdb.BSON/BSONDocument.cs +++ b/Ejdb.BSON/BSONDocument.cs @@ -219,7 +219,8 @@ public BsonValue GetBsonValue(string key) { /// /// BSON document key /// - public object GetObjectValue(string key) { + public object GetObjectValue(string key) + { var bv = GetBsonValue(key); return bv != null ? _UnwrapValue(bv.Value) : null; } @@ -251,23 +252,24 @@ public bool HasKey(string key) { /// 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); - var doc = GetObjectValue(prefix) as BsonDocument; - if (doc == null || key.Length < ind + 2) { - return null; - } - return doc[key.Substring(ind + 1)]; + 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 { - var bsonValue = BsonValue.ValueOf(value); + var bsonValue = BsonValue.ValueOf(value); Add(key, bsonValue); } } diff --git a/Ejdb.DB/EJDB.cs b/Ejdb.DB/EJDB.cs index 5727b9a..026da27 100644 --- a/Ejdb.DB/EJDB.cs +++ b/Ejdb.DB/EJDB.cs @@ -1030,7 +1030,7 @@ public EJDBQuery CreateQueryFor(string defaultcollection) { public EJDBQCursor Find(string collection, IQuery query) { - var ejdbQuery = new EJDBQuery(this, query.GetQueryDocument()); + var ejdbQuery = new EJDBQuery(this, query.Document); return ejdbQuery.Find(collection); } @@ -1045,7 +1045,7 @@ public int Update(string collection, IQuery query, BsonDocument updateDocument) if (query != null) { - foreach (var field in query.GetQueryDocument()) + foreach (var field in query.Document) document.Add(field.Key, field.Value); } diff --git a/Ejdb.DB/Query/EmptyQuery.cs b/Ejdb.DB/Query/EmptyQuery.cs index b31b6f5..12b71ba 100644 --- a/Ejdb.DB/Query/EmptyQuery.cs +++ b/Ejdb.DB/Query/EmptyQuery.cs @@ -5,14 +5,13 @@ namespace Ejdb.DB internal class EmptyQuery : IQuery { private EmptyQuery() - { - } + { } public static EmptyQuery Instance = new EmptyQuery(); - public BsonDocument GetQueryDocument() + public BsonDocument Document { - return new BsonDocument(); + get { return new BsonDocument(); } } } } \ No newline at end of file diff --git a/Ejdb.DB/Query/IQuery.cs b/Ejdb.DB/Query/IQuery.cs index c064c0f..8a8ace6 100644 --- a/Ejdb.DB/Query/IQuery.cs +++ b/Ejdb.DB/Query/IQuery.cs @@ -19,7 +19,7 @@ namespace Ejdb.DB { public interface IQuery - { - BsonDocument GetQueryDocument(); + { + BsonDocument Document { get; } } } \ No newline at end of file diff --git a/Ejdb.DB/Query/Query(T).cs b/Ejdb.DB/Query/Query(T).cs index c4d5a0b..d87f5f0 100644 --- a/Ejdb.DB/Query/Query(T).cs +++ b/Ejdb.DB/Query/Query(T).cs @@ -20,96 +20,91 @@ namespace Ejdb.DB { public static class Query - { - public static IQuery EQ(Expression> memberExpression, TMember value) - { - var fieldName = _GetFieldName(memberExpression); - return Query.EQ(fieldName, value); - } - - public static IQuery EqualsIgnoreCase(Expression> memberExpression, string value) - { - var fieldName = _GetFieldName(memberExpression); - return Query.EqualsIgnoreCase(fieldName, value); - } - - public static IQuery BeginsWith(Expression> memberExpression, string value) - { - var fieldName = _GetFieldName(memberExpression); - return Query.BeginsWith(fieldName, value); - } - - public static IQuery EndsWith(Expression> memberExpression, string value) - { - var fieldName = _GetFieldName(memberExpression); - return Query.EndsWith(fieldName, value); - } - - public static IQuery GT(Expression> memberExpression, TMember value) - { - var fieldName = _GetFieldName(memberExpression); - return Query.GT(fieldName, value); - } - - public static IQuery GTE(Expression> memberExpression, TMember value) - { - var fieldName = _GetFieldName(memberExpression); - return Query.GTE(fieldName, value); - } - - public static IQuery LT(Expression> memberExpression, TMember value) - { - var fieldName = _GetFieldName(memberExpression); - return Query.LT(fieldName, value); - } - - public static IQuery LTE(Expression> memberExpression, TMember value) - { - var fieldName = _GetFieldName(memberExpression); - return Query.LTE(fieldName, value); - } - - public static IQuery In(Expression> memberExpression, params TMember[] comparisonValues) - { - var fieldName = _GetFieldName(memberExpression); - return Query.In(fieldName, comparisonValues); - } - - public static IQuery NotIn(Expression> memberExpression, params TMember[] comparisonValues) - { - var fieldName = _GetFieldName(memberExpression); - return Query.NotIn(fieldName, comparisonValues); + { + 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 IQuery NotEquals(Expression> memberExpression, TMember value) { - var fieldName = _GetFieldName(memberExpression); - return Query.NotEquals(fieldName, value); + return new QueryBuilder().NotEquals(memberExpression, value); } public static IQuery Not(Expression> memberExpression, PartialQuery query) { - var fieldName = _GetFieldName(memberExpression); - return Query.Not(fieldName, query); + return new QueryBuilder().Not(memberExpression, query); } public static IQuery Between(Expression> memberExpression, TMember comparisonValue1, TMember comparisonValue2) { - var fieldName = _GetFieldName(memberExpression); - return Query.Between(fieldName, comparisonValue1, comparisonValue2); + return new QueryBuilder().Between(memberExpression, comparisonValue1, comparisonValue2); } public static IQuery Exists(Expression> memberExpression) { - var fieldName = _GetFieldName(memberExpression); - return Query.Exists(fieldName); + return new QueryBuilder().Exists(memberExpression); } public static IQuery NotExists(Expression> memberExpression) { - var fieldName = _GetFieldName(memberExpression); - return Query.NotExists(fieldName); - } + return new QueryBuilder().NotExists(memberExpression); + } + + /* public static QueryBuilder And(QueryBuilder[] queries) + { + + } */ + + public static QueryBuilder Or(params QueryBuilder[] queries) + { + return new QueryBuilder().Or(queries); + } private static string _GetFieldName(Expression> memberExpression) { diff --git a/Ejdb.DB/Query/Query.cs b/Ejdb.DB/Query/Query.cs index e39a7f2..3515863 100644 --- a/Ejdb.DB/Query/Query.cs +++ b/Ejdb.DB/Query/Query.cs @@ -34,92 +34,89 @@ public static IQuery Empty get { return EmptyQuery.Instance; } } - public static IQuery EQ(string fieldName, object value) - { - return new Query(fieldName, value); - } + public static QueryBuilder EQ(string fieldName, object value) + { + return new QueryBuilder().EQ(fieldName, value); + } - public static IQuery EqualsIgnoreCase(string fieldName, string value) - { - return _BinaryQuery("$icase", fieldName, value); + public static QueryBuilder EqualsIgnoreCase(string fieldName, string value) + { + return new QueryBuilder().EqualsIgnoreCase(fieldName, value); } - public static IQuery BeginsWith(string fieldName, string value) + public static IQuery BeginsWith(string fieldName, string value) { return _BinaryQuery("$begin", fieldName, value); } - public static IQuery EndsWith(string fieldName, string value) + public static IQuery EndsWith(string fieldName, string value) { return _BinaryQuery("end", fieldName, value); } - public static IQuery GT(string fieldName, object value) - { - return _BinaryQuery("$gt", fieldName, value); - } + public static QueryBuilder GT(string fieldName, object value) + { + return new QueryBuilder().GT(fieldName, value); + } - public static IQuery GTE(string fieldName, object value) - { - return _BinaryQuery("$gte", fieldName, value); - } + public static QueryBuilder GTE(string fieldName, object value) + { + return new QueryBuilder().GTE(fieldName, value); + } - public static IQuery LT(string fieldName, object value) + public static QueryBuilder LT(string fieldName, object value) { - return _BinaryQuery("$lt", fieldName, value); + return new QueryBuilder().LT(fieldName, value); } - public static IQuery LTE(string fieldName, object value) + public static QueryBuilder LTE(string fieldName, object value) { - return _BinaryQuery("$lte", fieldName, value); + return new QueryBuilder().LTE(fieldName, value); } - public static IQuery In(string fieldName, params T[] comparisonValues) - { - return _BinaryQuery("$in", fieldName, comparisonValues); + public static QueryBuilder In(string fieldName, params T[] comparisonValues) + { + return new QueryBuilder().In(fieldName, comparisonValues); } - public static IQuery NotIn(string fieldName, params T[] comparisonValues) - { - return _BinaryQuery("$nin", fieldName, comparisonValues); + public static QueryBuilder NotIn(string fieldName, params T[] comparisonValues) + { + return new QueryBuilder().NotIn(fieldName, comparisonValues); } - public static IQuery NotEquals(string fieldName, object comparisonValue) - { - return _BinaryQuery("$not", fieldName, comparisonValue); + public static QueryBuilder NotEquals(string fieldName, object comparisonValue) + { + return new QueryBuilder().NotEquals(fieldName, comparisonValue); } - public static IQuery Not(string fieldName, IPartialQuery query) + public static QueryBuilder Not(string fieldName, IPartialQuery query) { - var childValue = new BsonDocument(); - childValue.Add(query.QueryOperator, query.ComparisonValue); - return _BinaryQuery("$not", fieldName, childValue); + return new QueryBuilder().Not(fieldName, query); } - public static IQuery Between(string fieldName, T comparisonValue1, T comparisonValue2) + public static QueryBuilder Between(string fieldName, T comparisonValue1, T comparisonValue2) { - var comparisonValues = new[] { comparisonValue1, comparisonValue2 }; - return _BinaryQuery("$bt", fieldName, comparisonValues); + return new QueryBuilder().Between(fieldName, comparisonValue1, comparisonValue2); } - public static IQuery And(params IQuery[] queries) + /* public static IQuery And(params QueryBuilder[] queries) { return _CombinedQuery("$and", queries); - } + } */ - public static IQuery Or(params IQuery[] queries) - { - return _CombinedQuery("$or", queries); - } + public static IQuery Or(params QueryBuilder[] queries) + { + return new QueryBuilder().Or(queries); + } public static IQuery Exists(string fieldName) { - return _BinaryQuery("$exists", fieldName, true); + return new QueryBuilder().Exists(fieldName); } public static IQuery NotExists(string fieldName) { - return _BinaryQuery("$exists", fieldName, false); + return new QueryBuilder().NotExists(fieldName); } public static IQuery ElemMatch(string fieldName, params IQuery[] queries) @@ -128,16 +125,16 @@ public static IQuery ElemMatch(string fieldName, params IQuery[] queries) foreach (var query in queries) { - foreach (var field in query.GetQueryDocument()) + foreach (var field in query.Document) queryDocument[field.Key] = field.Value; } return new Query("$elemMatch", queryDocument); } - private static IQuery _CombinedQuery(string combinator, params IQuery[] queries) + private static IQuery _CombinedQuery(string combinator, params QueryBuilder[] queries) { - var documents = queries.Select(x => x.GetQueryDocument()).ToArray(); + var documents = queries.Select(x => x.Document).ToArray(); var childValue = new BsonArray(documents); return new Query(combinator, childValue); } @@ -149,9 +146,14 @@ private static IQuery _BinaryQuery(string queryOperation, string fieldName, obje return new Query(fieldName, query1); } - public BsonDocument GetQueryDocument() - { - return mQueryDocument; - } + 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..2b24c22 --- /dev/null +++ b/Ejdb.DB/Query/QueryBuilder.cs @@ -0,0 +1,101 @@ +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 IQuery NotEquals(Expression> memberExpression, TMember value) + { + return NotEquals(_GetFieldName(memberExpression), value); + } + + public IQuery Not(Expression> memberExpression, PartialQuery query) + { + return Not(_GetFieldName(memberExpression), query); + } + + public IQuery Between(Expression> memberExpression, TMember comparisonValue1, TMember comparisonValue2) + { + return Between(_GetFieldName(memberExpression), comparisonValue1, comparisonValue2); + } + + public IQuery Exists(Expression> memberExpression) + { + return Exists(_GetFieldName(memberExpression)); + } + + public IQuery NotExists(Expression> memberExpression) + { + return NotExists(_GetFieldName(memberExpression)); + } + + 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..5099070 --- /dev/null +++ b/Ejdb.DB/Query/QueryBuilderBase.cs @@ -0,0 +1,153 @@ +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 And(params QueryBuilder[] queries) + { + return + + _CombinedQuery("$and", queries); + } */ + + public TThis Or(params IQuery[] queries) + { + return _CombinedQuery("$or", queries); + } + + private TThis _CombinedQuery(string combinator, params IQuery[] queries) + { + var documents = queries.Select(x => x.Document).ToArray(); + var childValue = new BsonArray(documents); + _document[combinator] = childValue; + return This(); + } + + 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/UpdateBuilder.cs b/Ejdb.DB/Query/UpdateBuilder.cs index 71b2c5e..a7b9b25 100644 --- a/Ejdb.DB/Query/UpdateBuilder.cs +++ b/Ejdb.DB/Query/UpdateBuilder.cs @@ -113,13 +113,13 @@ private UpdateBuilder _AddOperation(string operationName, string name, object va Verify.NotNull(name, "name"); Verify.NotNull(value, "value"); - var element = _GetElement(operationName); + var element = _GetOrCreateElement(operationName); element.Add(name, value); return this; } - private BsonDocument _GetElement(string operationName) + private BsonDocument _GetOrCreateElement(string operationName) { var element = _document[operationName] as BsonDocument; if (element == null) 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/TestQuery.cs b/Ejdb.Tests/TestQuery.cs index 975344d..27abff2 100644 --- a/Ejdb.Tests/TestQuery.cs +++ b/Ejdb.Tests/TestQuery.cs @@ -35,7 +35,7 @@ public void Setup() [Test] public void Queries() { - _QueryResults(1, Query.EQ("MeasuredTemperature", 5)); + _QueryResults(1, new QueryBuilder().EQ("MeasuredTemperature", 5)); _QueryResults(1, Query.EQ(x => x.MeasuredTemperature, 5)); _QueryResults(1, Query.EQ("UserName", "test5")); @@ -66,24 +66,23 @@ public void Queries() _QueryResults(11, Query.BeginsWith(x => x.UserName, "test1")); _QueryResults(1, Query.EqualsIgnoreCase("UserName", "TeSt5")); - _QueryResults(1, Query.EqualsIgnoreCase(x => x.UserName, "TeSt5")); - - _QueryResults(5, Query.And( - Query.GT("MeasuredTemperature", 50), - Query.EQ("OpticalRotation", 5) - )); - - _QueryResults(5, Query.And( - Query.GT(x => x.MeasuredTemperature, 50), - Query.EQ(x => x.OpticalRotation, 5) - )); + _QueryResults(1, Query.EqualsIgnoreCase(x => x.UserName, "TeSt5")); + + + _QueryResults(5, Query + .GT("MeasuredTemperature", 50) + .EQ("OpticalRotation", 5)); + + _QueryResults(5, Query + .GT(x => x.MeasuredTemperature, 50) + .EQ(x => x.OpticalRotation, 5)); _QueryResults(11, Query.Or( Query.GT("MeasuredTemperature", 98), Query.EQ("OpticalRotation", 1) - )); - - _QueryResults(11, Query.Or( + )); + + _QueryResults(11, Query.Or( Query.GT(x => x.MeasuredTemperature, 98), Query.EQ(x => x.OpticalRotation, 1) )); diff --git a/nejdb.csproj b/nejdb.csproj index 3fa0966..d7e2d63 100644 --- a/nejdb.csproj +++ b/nejdb.csproj @@ -74,8 +74,11 @@ + + + From 8e0815a602d9e0025ccd1f6a32cbe9e4f9fbbb8d Mon Sep 17 00:00:00 2001 From: Oliver Klemencic Date: Thu, 2 Jan 2014 13:18:24 +0100 Subject: [PATCH 23/26] Further work on queries --- Ejdb.DB/Query/Query(T).cs | 40 +++++------ Ejdb.DB/Query/Query.cs | 35 +++++----- Ejdb.DB/Query/QueryBuilder.cs | 20 ++++-- Ejdb.DB/Query/QueryBuilderBase.cs | 13 ++-- Ejdb.Tests/TestQuery.cs | 111 +++++++++++++++++++++++++----- 5 files changed, 151 insertions(+), 68 deletions(-) diff --git a/Ejdb.DB/Query/Query(T).cs b/Ejdb.DB/Query/Query(T).cs index d87f5f0..66647eb 100644 --- a/Ejdb.DB/Query/Query(T).cs +++ b/Ejdb.DB/Query/Query(T).cs @@ -69,14 +69,14 @@ public static QueryBuilder In(Expression NotIn(Expression> memberExpression, params TMember[] comparisonValues) { return new QueryBuilder().NotIn(memberExpression, comparisonValues); - } - - public static IQuery NotEquals(Expression> memberExpression, TMember value) + } + + public static QueryBuilder NotEquals(Expression> memberExpression, TMember value) { return new QueryBuilder().NotEquals(memberExpression, value); - } - - public static IQuery Not(Expression> memberExpression, PartialQuery query) + } + + public static QueryBuilder Not(Expression> memberExpression, PartialQuery query) { return new QueryBuilder().Not(memberExpression, query); } @@ -84,31 +84,31 @@ public static IQuery Not(Expression> memberExp public static IQuery Between(Expression> memberExpression, TMember comparisonValue1, TMember comparisonValue2) { return new QueryBuilder().Between(memberExpression, comparisonValue1, comparisonValue2); - } - - public static IQuery Exists(Expression> memberExpression) + } + + public static QueryBuilder Exists(Expression> memberExpression) { return new QueryBuilder().Exists(memberExpression); - } - - public static IQuery NotExists(Expression> memberExpression) + } + + public static QueryBuilder NotExists(Expression> memberExpression) { return new QueryBuilder().NotExists(memberExpression); } - /* public static QueryBuilder And(QueryBuilder[] queries) + 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); } - - private static string _GetFieldName(Expression> memberExpression) - { - return DynamicReflectionHelper.GetProperty(memberExpression).Name; - } } } \ No newline at end of file diff --git a/Ejdb.DB/Query/Query.cs b/Ejdb.DB/Query/Query.cs index 3515863..0d278bc 100644 --- a/Ejdb.DB/Query/Query.cs +++ b/Ejdb.DB/Query/Query.cs @@ -45,13 +45,13 @@ public static QueryBuilder EqualsIgnoreCase(string fieldName, string value) } public static IQuery BeginsWith(string fieldName, string value) - { - return _BinaryQuery("$begin", fieldName, value); - } + { + return new QueryBuilder().BeginsWith(fieldName, value); + } public static IQuery EndsWith(string fieldName, string value) - { - return _BinaryQuery("end", fieldName, value); + { + return new QueryBuilder().EndsWith(fieldName, value); } public static QueryBuilder GT(string fieldName, object value) @@ -119,6 +119,17 @@ 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(); @@ -132,20 +143,6 @@ public static IQuery ElemMatch(string fieldName, params IQuery[] queries) return new Query("$elemMatch", queryDocument); } - private static IQuery _CombinedQuery(string combinator, params QueryBuilder[] queries) - { - var documents = queries.Select(x => x.Document).ToArray(); - var childValue = new BsonArray(documents); - return new Query(combinator, childValue); - } - - private static IQuery _BinaryQuery(string queryOperation, string fieldName, object comparisonValue) - { - var query1 = new BsonDocument(); - query1[queryOperation] = comparisonValue; - return new Query(fieldName, query1); - } - public BsonDocument Document { get { return mQueryDocument; } diff --git a/Ejdb.DB/Query/QueryBuilder.cs b/Ejdb.DB/Query/QueryBuilder.cs index 2b24c22..b203c6f 100644 --- a/Ejdb.DB/Query/QueryBuilder.cs +++ b/Ejdb.DB/Query/QueryBuilder.cs @@ -60,31 +60,41 @@ public QueryBuilder NotIn(Expression(Expression> memberExpression, TMember value) + public QueryBuilder NotEquals(Expression> memberExpression, TMember value) { return NotEquals(_GetFieldName(memberExpression), value); } - public IQuery Not(Expression> memberExpression, PartialQuery query) + public QueryBuilder Not(Expression> memberExpression, PartialQuery query) { return Not(_GetFieldName(memberExpression), query); } - public IQuery Between(Expression> memberExpression, TMember comparisonValue1, TMember comparisonValue2) + public QueryBuilder Between(Expression> memberExpression, TMember comparisonValue1, TMember comparisonValue2) { return Between(_GetFieldName(memberExpression), comparisonValue1, comparisonValue2); } - public IQuery Exists(Expression> memberExpression) + public QueryBuilder Exists(Expression> memberExpression) { return Exists(_GetFieldName(memberExpression)); } - public IQuery NotExists(Expression> 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); + } + private string _GetFieldName(Expression> memberExpression) { return DynamicReflectionHelper.GetProperty(memberExpression).Name; diff --git a/Ejdb.DB/Query/QueryBuilderBase.cs b/Ejdb.DB/Query/QueryBuilderBase.cs index 5099070..a3872a7 100644 --- a/Ejdb.DB/Query/QueryBuilderBase.cs +++ b/Ejdb.DB/Query/QueryBuilderBase.cs @@ -119,12 +119,15 @@ private TThis _BinaryQuery(string queryOperation, string fieldName, object compa return This(); } - /* public TThis And(params QueryBuilder[] queries) + public TThis StringMatchesAllTokens(string fieldName, params string[] values) { - return - - _CombinedQuery("$and", queries); - } */ + return _BinaryQuery("$strand", fieldName, values); + } + + public TThis StringMatchesAnyTokens(string fieldName, params string[] values) + { + return _BinaryQuery("$stror", fieldName, values); + } public TThis Or(params IQuery[] queries) { diff --git a/Ejdb.Tests/TestQuery.cs b/Ejdb.Tests/TestQuery.cs index 27abff2..ab28613 100644 --- a/Ejdb.Tests/TestQuery.cs +++ b/Ejdb.Tests/TestQuery.cs @@ -33,7 +33,7 @@ public void Setup() } [Test] - public void Queries() + public void ComparisonQueries() { _QueryResults(1, new QueryBuilder().EQ("MeasuredTemperature", 5)); _QueryResults(1, Query.EQ(x => x.MeasuredTemperature, 5)); @@ -67,28 +67,101 @@ public void Queries() _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(5, Query - .GT("MeasuredTemperature", 50) - .EQ("OpticalRotation", 5)); + _QueryResults(11, Query.Or( + Query.GT(x => x.MeasuredTemperature, 98), + Query.EQ(x => x.OpticalRotation, 1) + )); + } - _QueryResults(5, Query - .GT(x => x.MeasuredTemperature, 50) - .EQ(x => x.OpticalRotation, 5)); - - _QueryResults(11, Query.Or( - Query.GT("MeasuredTemperature", 98), - Query.EQ("OpticalRotation", 1) - )); + [Test] + public void AndQuery() + { + _QueryResults(5, Query + .GT("MeasuredTemperature", 50) + .EQ("OpticalRotation", 5)); - _QueryResults(11, Query.Or( - Query.GT(x => x.MeasuredTemperature, 98), - Query.EQ(x => x.OpticalRotation, 1) - )); - } - - private BsonDocument[] _QueryResults(int expectedResultCount, IQuery query) + _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); From 4e8e712426e25df83dcf03b472f307bf39f32ee6 Mon Sep 17 00:00:00 2001 From: Oliver Klemencic Date: Thu, 2 Jan 2014 13:19:15 +0100 Subject: [PATCH 24/26] Added element match query support --- Ejdb.Tests/TestQueryElementMatch.cs | 79 +++++++++++++++++++++++++++++ nejdb.csproj | 1 + 2 files changed, 80 insertions(+) create mode 100644 Ejdb.Tests/TestQueryElementMatch.cs diff --git a/Ejdb.Tests/TestQueryElementMatch.cs b/Ejdb.Tests/TestQueryElementMatch.cs new file mode 100644 index 0000000..191a695 --- /dev/null +++ b/Ejdb.Tests/TestQueryElementMatch.cs @@ -0,0 +1,79 @@ +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.And( + Query.EQ("zipcode", 109), + Query.ElemMatch("students", Query.EQ("school", 102)) + )).Select(x => x.ToBsonDocument()).ToArray(); + + Assert.That(results.Length, Is.EqualTo(3)); + + // this is different from MongoDb + + Assert.That(_GetNumberOfStudents(results[0]), Is.EqualTo(3)); // should be 1 + Assert.That(_GetNumberOfStudents(results[1]), Is.EqualTo(2)); // should be 0 + Assert.That(_GetNumberOfStudents(results[2]), Is.EqualTo(1)); // should be 1 + } + + private int _GetNumberOfStudents(BsonDocument document) + { + var array = (object[])document["students"]; + return array.Length; + } + } +} \ No newline at end of file diff --git a/nejdb.csproj b/nejdb.csproj index d7e2d63..5eb0d69 100644 --- a/nejdb.csproj +++ b/nejdb.csproj @@ -79,6 +79,7 @@ + From dda0d29babbc3ac974566839593a93d4a132e106 Mon Sep 17 00:00:00 2001 From: Oliver Klemencic Date: Thu, 2 Jan 2014 13:25:07 +0100 Subject: [PATCH 25/26] Added element match support --- Ejdb.DB/Query/QueryBuilderBase.cs | 20 ++++++++++++++------ Ejdb.Tests/TestQueryElementMatch.cs | 12 +++++------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/Ejdb.DB/Query/QueryBuilderBase.cs b/Ejdb.DB/Query/QueryBuilderBase.cs index a3872a7..b63b85f 100644 --- a/Ejdb.DB/Query/QueryBuilderBase.cs +++ b/Ejdb.DB/Query/QueryBuilderBase.cs @@ -130,18 +130,26 @@ public TThis StringMatchesAnyTokens(string fieldName, params string[] values) } public TThis Or(params IQuery[] queries) - { - return _CombinedQuery("$or", queries); - } - - private TThis _CombinedQuery(string combinator, params IQuery[] queries) { var documents = queries.Select(x => x.Document).ToArray(); var childValue = new BsonArray(documents); - _document[combinator] = childValue; + _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]; diff --git a/Ejdb.Tests/TestQueryElementMatch.cs b/Ejdb.Tests/TestQueryElementMatch.cs index 191a695..68f50ea 100644 --- a/Ejdb.Tests/TestQueryElementMatch.cs +++ b/Ejdb.Tests/TestQueryElementMatch.cs @@ -56,18 +56,16 @@ public void Setup() [Test] public void Example1() { - var results = _db.Find(COLLECTION_NAME, Query.And( - Query.EQ("zipcode", 109), - Query.ElemMatch("students", Query.EQ("school", 102)) + 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(3)); + 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(2)); // should be 0 - Assert.That(_GetNumberOfStudents(results[2]), Is.EqualTo(1)); // should be 1 + Assert.That(_GetNumberOfStudents(results[1]), Is.EqualTo(1)); } private int _GetNumberOfStudents(BsonDocument document) From 0a740ffa6f0897a7f09367c8422e17f67b2653bd Mon Sep 17 00:00:00 2001 From: Oliver Klemencic Date: Thu, 2 Jan 2014 13:27:12 +0100 Subject: [PATCH 26/26] Minor --- Ejdb.DB/Query/Query(T).cs | 5 +++++ Ejdb.DB/Query/QueryBuilder.cs | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/Ejdb.DB/Query/Query(T).cs b/Ejdb.DB/Query/Query(T).cs index 66647eb..49ed959 100644 --- a/Ejdb.DB/Query/Query(T).cs +++ b/Ejdb.DB/Query/Query(T).cs @@ -109,6 +109,11 @@ public static QueryBuilder StringMatchesAnyTokens(Expression 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/QueryBuilder.cs b/Ejdb.DB/Query/QueryBuilder.cs index b203c6f..679814a 100644 --- a/Ejdb.DB/Query/QueryBuilder.cs +++ b/Ejdb.DB/Query/QueryBuilder.cs @@ -10,6 +10,8 @@ protected override QueryBuilder This() return this; } + + public QueryBuilder EQ(Expression> memberExpression, TMember value) { return EQ(_GetFieldName(memberExpression), value); @@ -95,6 +97,11 @@ public QueryBuilder StringMatchesAnyTokens(Expression ElemMatch(Expression> memberExpression, params IQuery[] queries) + { + return ElemMatch(_GetFieldName(memberExpression), queries); + } + private string _GetFieldName(Expression> memberExpression) { return DynamicReflectionHelper.GetProperty(memberExpression).Name;